mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-26 11:21:16 +01:00
Container queries!
This commit is contained in:
@@ -42,10 +42,6 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
|
||||
[activeResponse],
|
||||
);
|
||||
|
||||
if (activeResponse === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classnames(className, 'p-3')}>
|
||||
<div
|
||||
@@ -61,57 +57,59 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
|
||||
alignItems="center"
|
||||
className="italic text-gray-700 text-sm w-full mb-1 flex-shrink-0 pl-2"
|
||||
>
|
||||
{activeResponse && activeResponse.status > 0 && (
|
||||
<div className="whitespace-nowrap">
|
||||
<StatusColor statusCode={activeResponse.status}>
|
||||
{activeResponse.status}
|
||||
{activeResponse.statusReason && ` ${activeResponse.statusReason}`}
|
||||
</StatusColor>
|
||||
•
|
||||
{activeResponse.elapsed}ms •
|
||||
{Math.round(activeResponse.body.length / 1000)} KB
|
||||
</div>
|
||||
)}
|
||||
{activeResponse && (
|
||||
<>
|
||||
<div className="whitespace-nowrap">
|
||||
<StatusColor statusCode={activeResponse.status}>
|
||||
{activeResponse.status}
|
||||
{activeResponse.statusReason && ` ${activeResponse.statusReason}`}
|
||||
</StatusColor>
|
||||
•
|
||||
{activeResponse.elapsed}ms •
|
||||
{Math.round(activeResponse.body.length / 1000)} KB
|
||||
</div>
|
||||
|
||||
<HStack alignItems="center" className="ml-auto h-8">
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified',
|
||||
onSelect: toggleViewMode,
|
||||
},
|
||||
'-----',
|
||||
{
|
||||
label: 'Clear Response',
|
||||
onSelect: deleteResponse.mutate,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{
|
||||
label: `Clear ${responses.length} ${pluralize('Response', responses.length)}`,
|
||||
onSelect: deleteAllResponses.mutate,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
'-----',
|
||||
...responses.slice(0, 10).map((r) => ({
|
||||
label: r.status + ' - ' + r.elapsed + ' ms',
|
||||
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
|
||||
onSelect: () => setActiveResponseId(r.id),
|
||||
})),
|
||||
]}
|
||||
>
|
||||
<DropdownMenuTrigger>
|
||||
<IconButton
|
||||
title="Show response history"
|
||||
icon="triangleDown"
|
||||
className="ml-auto"
|
||||
size="sm"
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
</Dropdown>
|
||||
</HStack>
|
||||
<HStack alignItems="center" className="ml-auto h-8">
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified',
|
||||
onSelect: toggleViewMode,
|
||||
},
|
||||
'-----',
|
||||
{
|
||||
label: 'Clear Response',
|
||||
onSelect: deleteResponse.mutate,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{
|
||||
label: `Clear ${responses.length} ${pluralize('Response', responses.length)}`,
|
||||
onSelect: deleteAllResponses.mutate,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
'-----',
|
||||
...responses.slice(0, 10).map((r) => ({
|
||||
label: r.status + ' - ' + r.elapsed + ' ms',
|
||||
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
|
||||
onSelect: () => setActiveResponseId(r.id),
|
||||
})),
|
||||
]}
|
||||
>
|
||||
<DropdownMenuTrigger>
|
||||
<IconButton
|
||||
title="Show response history"
|
||||
icon="triangleDown"
|
||||
className="ml-auto"
|
||||
size="sm"
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
</Dropdown>
|
||||
</HStack>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{activeResponse?.error ? (
|
||||
{!activeResponse ? null : activeResponse?.error ? (
|
||||
<div className="p-1">
|
||||
<div className="text-white bg-red-500 px-3 py-3 rounded">{activeResponse.error}</div>
|
||||
</div>
|
||||
|
||||
@@ -12,11 +12,10 @@ import { getEmptyImage } from 'react-dnd-html5-backend';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||
import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
|
||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||
import { clamp } from '../lib/clamp';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import { Button } from './core/Button';
|
||||
import { Dropdown, DropdownMenuTrigger } from './core/Dropdown';
|
||||
@@ -32,21 +31,17 @@ interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const MIN_WIDTH = 110;
|
||||
const INITIAL_WIDTH = 200;
|
||||
const MAX_WIDTH = 500;
|
||||
|
||||
enum ItemTypes {
|
||||
REQUEST = 'request',
|
||||
}
|
||||
|
||||
export const Sidebar = memo(function Sidebar({ className }: Props) {
|
||||
const [isResizing, setIsRisizing] = useState<boolean>(false);
|
||||
const width = useKeyValue<number>({ key: 'sidebar_width', initialValue: INITIAL_WIDTH });
|
||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||
const requests = useRequests();
|
||||
const activeRequest = useActiveRequest();
|
||||
const createRequest = useCreateRequest({ navigateAfter: true });
|
||||
const width = useSidebarWidth();
|
||||
|
||||
const moveState = useRef<{ move: (e: MouseEvent) => void; up: () => void } | null>(null);
|
||||
const unsub = () => {
|
||||
@@ -56,28 +51,28 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleResizeReset = useCallback(() => {
|
||||
width.set(INITIAL_WIDTH);
|
||||
}, []);
|
||||
const handleResizeStart = useCallback(
|
||||
(e: ReactMouseEvent<HTMLDivElement>) => {
|
||||
if (width.value === undefined) return;
|
||||
|
||||
const handleResizeStart = useCallback((e: ReactMouseEvent<HTMLDivElement>) => {
|
||||
unsub();
|
||||
const mouseStartX = e.clientX;
|
||||
const startWidth = width.value;
|
||||
moveState.current = {
|
||||
move: (e: MouseEvent) => {
|
||||
const newWidth = clamp(startWidth + (e.clientX - mouseStartX), MIN_WIDTH, MAX_WIDTH);
|
||||
width.set(newWidth);
|
||||
},
|
||||
up: () => {
|
||||
unsub();
|
||||
setIsRisizing(false);
|
||||
},
|
||||
};
|
||||
document.documentElement.addEventListener('mousemove', moveState.current.move);
|
||||
document.documentElement.addEventListener('mouseup', moveState.current.up);
|
||||
setIsRisizing(true);
|
||||
}, []);
|
||||
unsub();
|
||||
const mouseStartX = e.clientX;
|
||||
const startWidth = width.value;
|
||||
moveState.current = {
|
||||
move: (e: MouseEvent) => {
|
||||
width.set(startWidth + (e.clientX - mouseStartX));
|
||||
},
|
||||
up: () => {
|
||||
unsub();
|
||||
setIsResizing(false);
|
||||
},
|
||||
};
|
||||
document.documentElement.addEventListener('mousemove', moveState.current.move);
|
||||
document.documentElement.addEventListener('mouseup', moveState.current.up);
|
||||
setIsResizing(true);
|
||||
},
|
||||
[width.value],
|
||||
);
|
||||
|
||||
const sidebarStyles = useMemo(() => ({ width: width.value }), [width.value]);
|
||||
const sidebarWidth = width.value - 1; // Minus 1 for the border
|
||||
@@ -89,7 +84,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
||||
aria-hidden
|
||||
className="group absolute z-10 right-0 w-1 top-0 bottom-0 cursor-ew-resize flex justify-end"
|
||||
onMouseDown={handleResizeStart}
|
||||
onDoubleClick={handleResizeReset}
|
||||
onDoubleClick={width.reset}
|
||||
>
|
||||
<div // drag-divider
|
||||
className={classnames(
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import classnames from 'classnames';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useWindowSize } from 'react-use';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||
import { Dropdown, DropdownMenuTrigger } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
@@ -17,8 +19,18 @@ export default function Workspace() {
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const deleteRequest = useDeleteRequest(activeRequest?.id ?? null);
|
||||
const { width } = useWindowSize();
|
||||
const isSideBySide = width > 900;
|
||||
|
||||
const mainContentRef = useRef<HTMLDivElement>(null);
|
||||
const windowSize = useWindowSize();
|
||||
const sidebarWidth = useSidebarWidth();
|
||||
|
||||
const mainContentWidth = useMemo(() => {
|
||||
return mainContentRef.current?.getBoundingClientRect().width ?? 0;
|
||||
// TODO: Use container query subscription instead of minitoring everything
|
||||
}, [mainContentRef.current, windowSize, sidebarWidth.value]);
|
||||
|
||||
const isSideBySide = mainContentWidth > 700;
|
||||
|
||||
if (activeWorkspace == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -26,7 +38,11 @@ export default function Workspace() {
|
||||
return (
|
||||
<div className="grid grid-cols-[auto_1fr] grid-rows-1 h-full text-gray-900">
|
||||
<Sidebar />
|
||||
<div data-tauri-drag-region className="grid grid-rows-[auto_minmax(0,1fr)] h-full">
|
||||
<div
|
||||
ref={mainContentRef}
|
||||
data-tauri-drag-region
|
||||
className="grid grid-rows-[auto_minmax(0,1fr)] h-full"
|
||||
>
|
||||
<HStack
|
||||
as={WindowDragRegion}
|
||||
justifyContent="center"
|
||||
|
||||
@@ -43,14 +43,14 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
|
||||
() =>
|
||||
classnames(
|
||||
className,
|
||||
'outline-none',
|
||||
'outline-none whitespace-nowrap',
|
||||
'border border-transparent focus-visible:border-blue-300',
|
||||
'rounded-md flex items-center',
|
||||
colorStyles[color || 'default'],
|
||||
justify === 'start' && 'justify-start',
|
||||
justify === 'center' && 'justify-center',
|
||||
size === 'md' && 'h-9 px-3',
|
||||
size === 'sm' && 'h-7 px-2.5 text-sm',
|
||||
size === 'md' && 'h-md px-3',
|
||||
size === 'sm' && 'h-sm px-2.5 text-sm',
|
||||
),
|
||||
[color, size, justify, className],
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ export function Checkbox({ checked, onChange, className, disabled }: Props) {
|
||||
onCheckedChange={onChange}
|
||||
className={classnames(
|
||||
className,
|
||||
'flex-shrink-0 w-5 h-5 border border-gray-200 rounded',
|
||||
'flex-shrink-0 w-4 h-4 border border-gray-200 rounded',
|
||||
'focus:border-focus',
|
||||
'disabled:opacity-disabled',
|
||||
'outline-none',
|
||||
@@ -28,7 +28,7 @@ export function Checkbox({ checked, onChange, className, disabled }: Props) {
|
||||
>
|
||||
<CB.Indicator className="flex items-center justify-center">
|
||||
{checked === 'indeterminate' && <Icon icon="dividerH" />}
|
||||
{checked === true && <Icon icon="check" />}
|
||||
{checked === true && <Icon size="sm" icon="check" />}
|
||||
</CB.Indicator>
|
||||
</CB.Root>
|
||||
);
|
||||
|
||||
@@ -81,8 +81,8 @@ export function Input({
|
||||
'relative w-full rounded-md text-gray-900',
|
||||
'border border-gray-200 focus-within:border-focus',
|
||||
!isValid && 'border-invalid',
|
||||
size === 'md' && 'h-9',
|
||||
size === 'sm' && 'h-7',
|
||||
size === 'md' && 'h-md',
|
||||
size === 'sm' && 'h-sm',
|
||||
)}
|
||||
>
|
||||
{leftSlot}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CheckedState } from '@radix-ui/react-checkbox';
|
||||
import classNames from 'classnames';
|
||||
import classnames from 'classnames';
|
||||
import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { XYCoord } from 'react-dnd';
|
||||
@@ -121,6 +122,7 @@ export const PairEditor = memo(function PairEditor({
|
||||
<div
|
||||
className={classnames(
|
||||
className,
|
||||
'@container',
|
||||
'pb-6 grid',
|
||||
// NOTE: Add padding to top so overflow doesn't hide drop marker
|
||||
'py-1',
|
||||
@@ -236,7 +238,7 @@ const FormRow = memo(function FormRow({
|
||||
<div
|
||||
ref={ref}
|
||||
className={classnames(
|
||||
'pb-2 group grid grid-cols-[auto_minmax(0,1fr)_auto]',
|
||||
'pb-2 group grid grid-cols-[auto_auto_minmax(0,1fr)_auto]',
|
||||
'grid-rows-1 items-center',
|
||||
!pairContainer.pair.enabled && 'opacity-60',
|
||||
)}
|
||||
@@ -244,7 +246,7 @@ const FormRow = memo(function FormRow({
|
||||
{!isLast ? (
|
||||
<div
|
||||
className={classnames(
|
||||
'py-2 h-9 w-3 flex items-center',
|
||||
'py-2 h-7 w-3 flex items-center',
|
||||
'justify-center opacity-0 hover:opacity-100',
|
||||
)}
|
||||
>
|
||||
@@ -253,15 +255,16 @@ const FormRow = memo(function FormRow({
|
||||
) : (
|
||||
<span className="w-3" />
|
||||
)}
|
||||
<HStack space={2} alignItems="center">
|
||||
<Checkbox
|
||||
disabled={isLast}
|
||||
checked={isLast ? false : !!pairContainer.pair.enabled}
|
||||
className={classnames(isLast && '!opacity-disabled')}
|
||||
onChange={handleChangeEnabled}
|
||||
/>
|
||||
<Checkbox
|
||||
disabled={isLast}
|
||||
checked={isLast ? false : !!pairContainer.pair.enabled}
|
||||
className={classnames('mr-2', isLast && '!opacity-disabled')}
|
||||
onChange={handleChangeEnabled}
|
||||
/>
|
||||
<div className={classNames('flex flex-col @xs:flex-row gap-2 items-center')}>
|
||||
<Input
|
||||
hideLabel
|
||||
size="sm"
|
||||
require={!isLast && !!pairContainer.pair.enabled && !!pairContainer.pair.value}
|
||||
useTemplating
|
||||
containerClassName={classnames(isLast && 'border-dashed')}
|
||||
@@ -275,6 +278,7 @@ const FormRow = memo(function FormRow({
|
||||
/>
|
||||
<Input
|
||||
hideLabel
|
||||
size="sm"
|
||||
containerClassName={classnames(isLast && 'border-dashed')}
|
||||
defaultValue={pairContainer.pair.value}
|
||||
label="Value"
|
||||
@@ -285,7 +289,7 @@ const FormRow = memo(function FormRow({
|
||||
useTemplating
|
||||
autocomplete={valueAutocomplete?.(pairContainer.pair.name)}
|
||||
/>
|
||||
</HStack>
|
||||
</div>
|
||||
<IconButton
|
||||
aria-hidden={!onDelete}
|
||||
disabled={!onDelete}
|
||||
|
||||
@@ -17,7 +17,11 @@ interface HStackProps extends BaseStackProps {
|
||||
|
||||
export function HStack({ className, space, children, ...props }: HStackProps) {
|
||||
return (
|
||||
<BaseStack className={classnames(className, 'flex-row', space && gapClasses[space])} {...props}>
|
||||
<BaseStack
|
||||
direction="row"
|
||||
className={classnames(className, 'flex-row', space && gapClasses[space])}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</BaseStack>
|
||||
);
|
||||
@@ -30,7 +34,8 @@ export type VStackProps = BaseStackProps & {
|
||||
export function VStack({ className, space, children, ...props }: VStackProps) {
|
||||
return (
|
||||
<BaseStack
|
||||
className={classnames(className, 'w-full h-full flex-col', space && gapClasses[space])}
|
||||
direction="col"
|
||||
className={classnames(className, 'w-full h-full', space && gapClasses[space])}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -43,15 +48,25 @@ type BaseStackProps = HTMLAttributes<HTMLElement> & {
|
||||
space?: keyof typeof gapClasses;
|
||||
alignItems?: 'start' | 'center';
|
||||
justifyContent?: 'start' | 'center' | 'end';
|
||||
direction?: 'row' | 'col';
|
||||
};
|
||||
|
||||
function BaseStack({ className, alignItems, justifyContent, children, as }: BaseStackProps) {
|
||||
function BaseStack({
|
||||
className,
|
||||
direction,
|
||||
alignItems,
|
||||
justifyContent,
|
||||
children,
|
||||
as,
|
||||
}: BaseStackProps) {
|
||||
const Component = as ?? 'div';
|
||||
return (
|
||||
<Component
|
||||
className={classnames(
|
||||
className,
|
||||
'flex',
|
||||
direction === 'row' && 'flex-row',
|
||||
direction === 'col' && 'flex-col',
|
||||
alignItems === 'center' && 'items-center',
|
||||
alignItems === 'start' && 'items-start',
|
||||
justifyContent === 'start' && 'justify-start',
|
||||
|
||||
@@ -128,6 +128,7 @@ export const TabContent = memo(function TabContent({
|
||||
}: TabContentProps) {
|
||||
return (
|
||||
<T.Content
|
||||
tabIndex={-1}
|
||||
forceMount
|
||||
value={value}
|
||||
className={classnames(className, 'tab-content', 'w-full h-full overflow-auto')}
|
||||
|
||||
Reference in New Issue
Block a user