mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 01:08:28 +02:00
Even better layouts
This commit is contained in:
@@ -2,16 +2,25 @@ import classnames from 'classnames';
|
|||||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
|
import { clamp } from '../lib/clamp';
|
||||||
import { RequestPane } from './RequestPane';
|
import { RequestPane } from './RequestPane';
|
||||||
import { ResponsePane } from './ResponsePane';
|
import { ResponsePane } from './ResponsePane';
|
||||||
import { ResizeBar } from './Workspace';
|
import { ResizeBar } from './Workspace';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
style: CSSProperties;
|
||||||
|
vertical?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const rqst = { gridArea: 'rqst' };
|
const rqst = { gridArea: 'rqst' };
|
||||||
const resp = { gridArea: 'resp' };
|
const resp = { gridArea: 'resp' };
|
||||||
const drag = { gridArea: 'drag' };
|
const drag = { gridArea: 'drag' };
|
||||||
|
|
||||||
export default function RequestResponse() {
|
const DEFAULT = 0.5;
|
||||||
const DEFAULT = 0.5;
|
const MIN_WIDTH_PX = 10;
|
||||||
|
const MIN_HEIGHT_PX = 100;
|
||||||
|
|
||||||
|
export default function RequestResponse({ style, vertical }: Props) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const widthKv = useKeyValue<number>({ key: 'body_width', defaultValue: DEFAULT });
|
const widthKv = useKeyValue<number>({ key: 'body_width', defaultValue: DEFAULT });
|
||||||
const heightKv = useKeyValue<number>({ key: 'body_height', defaultValue: DEFAULT });
|
const heightKv = useKeyValue<number>({ key: 'body_height', defaultValue: DEFAULT });
|
||||||
@@ -22,14 +31,14 @@ export default function RequestResponse() {
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const vertical = false;
|
|
||||||
const styles = useMemo<CSSProperties>(
|
const styles = useMemo<CSSProperties>(
|
||||||
() => ({
|
() => ({
|
||||||
|
...style,
|
||||||
gridTemplate: vertical
|
gridTemplate: vertical
|
||||||
? `
|
? `
|
||||||
' ${rqst.gridArea}' ${1 - height}fr
|
' ${rqst.gridArea}' minmax(0,${1 - height}fr)
|
||||||
' ${drag.gridArea}' auto
|
' ${drag.gridArea}' auto
|
||||||
' ${resp.gridArea}' ${height}fr
|
' ${resp.gridArea}' minmax(0,${height}fr)
|
||||||
/ 1fr
|
/ 1fr
|
||||||
`
|
`
|
||||||
: `
|
: `
|
||||||
@@ -37,7 +46,7 @@ export default function RequestResponse() {
|
|||||||
/ ${1 - width}fr auto ${width}fr
|
/ ${1 - width}fr auto ${width}fr
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
[vertical, width, height],
|
[vertical, width, height, style],
|
||||||
);
|
);
|
||||||
|
|
||||||
const unsub = () => {
|
const unsub = () => {
|
||||||
@@ -68,13 +77,21 @@ export default function RequestResponse() {
|
|||||||
move: (e: MouseEvent) => {
|
move: (e: MouseEvent) => {
|
||||||
e.preventDefault(); // Prevent text selection and things
|
e.preventDefault(); // Prevent text selection and things
|
||||||
if (vertical) {
|
if (vertical) {
|
||||||
const newHeightPx = startHeight - (e.clientY - mouseStartY);
|
const maxHeightPx = containerRect.height - MIN_HEIGHT_PX;
|
||||||
const newHeight = newHeightPx / containerRect.height;
|
const newHeightPx = clamp(
|
||||||
heightKv.set(newHeight);
|
startHeight - (e.clientY - mouseStartY),
|
||||||
|
MIN_HEIGHT_PX,
|
||||||
|
maxHeightPx,
|
||||||
|
);
|
||||||
|
heightKv.set(newHeightPx / containerRect.height);
|
||||||
} else {
|
} else {
|
||||||
const newWidthPx = startWidth - (e.clientX - mouseStartX);
|
const maxWidthPx = containerRect.width - MIN_WIDTH_PX;
|
||||||
const newWidth = newWidthPx / containerRect.width;
|
const newWidthPx = clamp(
|
||||||
widthKv.set(newWidth);
|
startWidth - (e.clientX - mouseStartX),
|
||||||
|
MIN_WIDTH_PX,
|
||||||
|
maxWidthPx,
|
||||||
|
);
|
||||||
|
widthKv.set(newWidthPx / containerRect.width);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
up: (e: MouseEvent) => {
|
up: (e: MouseEvent) => {
|
||||||
@@ -93,9 +110,9 @@ export default function RequestResponse() {
|
|||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="grid w-full h-full p-3" style={styles}>
|
<div ref={containerRef} className="grid w-full h-full p-3" style={styles}>
|
||||||
<div style={rqst}>
|
<div style={rqst}>
|
||||||
<RequestPane fullHeight />
|
<RequestPane fullHeight={!vertical} />
|
||||||
</div>
|
</div>
|
||||||
<div style={drag} className={classnames('relative', vertical ? 'h-3' : 'w-3')}>
|
<div style={drag} className={classnames('relative flex-grow-0', vertical ? 'h-3' : 'w-3')}>
|
||||||
<ResizeBar
|
<ResizeBar
|
||||||
isResizing={isResizing}
|
isResizing={isResizing}
|
||||||
onResizeStart={handleResizeStart}
|
onResizeStart={handleResizeStart}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const side = { gridArea: 'side' };
|
|||||||
const head = { gridArea: 'head' };
|
const head = { gridArea: 'head' };
|
||||||
const body = { gridArea: 'body' };
|
const body = { gridArea: 'body' };
|
||||||
|
|
||||||
|
const FLOATING_SIDEBAR_BREAKPOINT = 960;
|
||||||
|
|
||||||
export default function Workspace() {
|
export default function Workspace() {
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const styles = useMemo<CSSProperties>(
|
const styles = useMemo<CSSProperties>(
|
||||||
@@ -26,23 +28,17 @@ export default function Workspace() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid w-full h-full" style={styles}>
|
<div className="grid w-full h-full" style={styles}>
|
||||||
<SidebarContainer style={side} floating={windowSize.width < 800}>
|
<SidebarContainer style={side} floating={windowSize.width < FLOATING_SIDEBAR_BREAKPOINT}>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</SidebarContainer>
|
</SidebarContainer>
|
||||||
<HeaderContainer>
|
<HeaderContainer>
|
||||||
<WorkspaceHeader className="pointer-events-none" />
|
<WorkspaceHeader className="pointer-events-none" />
|
||||||
</HeaderContainer>
|
</HeaderContainer>
|
||||||
<BodyContainer>
|
<RequestResponse style={body} vertical={windowSize.width < FLOATING_SIDEBAR_BREAKPOINT} />
|
||||||
<RequestResponse />
|
|
||||||
</BodyContainer>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const BodyContainer = memo(function BodyContainer({ children }: { children: ReactNode }) {
|
|
||||||
return <div style={body}>{children}</div>;
|
|
||||||
});
|
|
||||||
|
|
||||||
const HeaderContainer = memo(function HeaderContainer({ children }: { children: ReactNode }) {
|
const HeaderContainer = memo(function HeaderContainer({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -57,7 +57,11 @@ export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown items={items}>
|
<Dropdown items={items}>
|
||||||
<Button size="sm" className={classnames(className, '!px-2 truncate')} forDropdown>
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className={classnames(className, 'text-gray-800 !px-2 truncate')}
|
||||||
|
forDropdown
|
||||||
|
>
|
||||||
{activeWorkspace?.name}
|
{activeWorkspace?.name}
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function WorkspaceHeader({ className }: Props) {
|
|||||||
/>
|
/>
|
||||||
<WorkspaceDropdown className="pointer-events-auto" />
|
<WorkspaceDropdown className="pointer-events-auto" />
|
||||||
</HStack>
|
</HStack>
|
||||||
<div className="flex-[2] text-center text-gray-700 text-sm truncate pointer-events-none">
|
<div className="flex-[2] text-center text-gray-800 text-sm truncate pointer-events-none">
|
||||||
{activeRequest?.name}
|
{activeRequest?.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex justify-end -mr-2 pointer-events-none">
|
<div className="flex-1 flex justify-end -mr-2 pointer-events-none">
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-line {
|
.cm-line {
|
||||||
@apply text-gray-900 pl-1 pr-1.5;
|
@apply text-gray-800 pl-1 pr-1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-placeholder {
|
.cm-placeholder {
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ export function Tabs<T>({
|
|||||||
<HStack space={1}>
|
<HStack space={1}>
|
||||||
{tabs.map((t) => {
|
{tabs.map((t) => {
|
||||||
const isActive = t.value === value;
|
const isActive = t.value === value;
|
||||||
|
const btnClassName = classnames(
|
||||||
|
isActive ? 'bg-gray-100 text-gray-800' : 'text-gray-600 hover:text-gray-900',
|
||||||
|
);
|
||||||
if (t.options) {
|
if (t.options) {
|
||||||
return (
|
return (
|
||||||
<RadioDropdown
|
<RadioDropdown
|
||||||
@@ -81,9 +84,7 @@ export function Tabs<T>({
|
|||||||
color="custom"
|
color="custom"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={isActive ? undefined : () => handleTabChange(t.value)}
|
onClick={isActive ? undefined : () => handleTabChange(t.value)}
|
||||||
className={classnames(
|
className={btnClassName}
|
||||||
isActive ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{t.options.items.find((i) => i.value === t.options?.value)?.label ?? ''}
|
{t.options.items.find((i) => i.value === t.options?.value)?.label ?? ''}
|
||||||
<Icon icon="triangleDown" className="-mr-1.5" />
|
<Icon icon="triangleDown" className="-mr-1.5" />
|
||||||
@@ -97,9 +98,7 @@ export function Tabs<T>({
|
|||||||
color="custom"
|
color="custom"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleTabChange(t.value)}
|
onClick={() => handleTabChange(t.value)}
|
||||||
className={classnames(
|
className={btnClassName}
|
||||||
isActive ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{t.label}
|
{t.label}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user