import classnames from 'classnames'; import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactNode } from 'react'; import React, { memo, useCallback, useMemo, useRef, useState } from 'react'; import { useWindowSize } from 'react-use'; import { useKeyValue } from '../../hooks/useKeyValue'; import { useSidebarDisplay } from '../../hooks/useSidebarDisplay'; import { RequestPane } from '../RequestPane'; import { ResponsePane } from '../ResponsePane'; import { Sidebar } from '../Sidebar'; import { WorkspaceHeader } from '../WorkspaceHeader'; const side = { gridArea: 'side' }; const head = { gridArea: 'head' }; const rqst = { gridArea: 'rqst' }; const resp = { gridArea: 'resp' }; export function WorkspaceLayout() { const windowSize = useWindowSize(); const vertical = windowSize.width < 800; const styles = useMemo( () => vertical ? { gridTemplate: ` ' ${head.gridArea} ${head.gridArea}' auto ' ${side.gridArea} ${rqst.gridArea}' 1fr ' ${side.gridArea} ${resp.gridArea}' 1fr / auto 1fr `, } : { gridTemplate: ` ' ${head.gridArea} ${head.gridArea} ${head.gridArea}' auto ' ${side.gridArea} ${rqst.gridArea} ${resp.gridArea}' 1fr / auto 1fr auto `, }, [vertical], ); return (
); } const HeaderContainer = memo(function HeaderContainer({ children }: { children: ReactNode }) { return (
{children}
); }); const RequestContainer = memo(function RequestContainer({ children }: { children: ReactNode }) { return (
{children}
); }); const ResponseContainer = memo(function ResponseContainer({ children }: { children: ReactNode }) { const displayKv = useKeyValue({ key: 'response_width', defaultValue: 400 }); const [isResizing, setIsResizing] = useState(false); const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>( null, ); const unsub = () => { if (moveState.current !== null) { document.documentElement.removeEventListener('mousemove', moveState.current.move); document.documentElement.removeEventListener('mouseup', moveState.current.up); } }; const handleReset = useCallback(() => displayKv.set(500), []); const handleResizeStart = useCallback( (e: ReactMouseEvent) => { if (displayKv.value === undefined) return; unsub(); const mouseStartX = e.clientX; const startWidth = displayKv.value; moveState.current = { move: (e: MouseEvent) => { e.preventDefault(); // Prevent text selection and things displayKv.set(startWidth - (e.clientX - mouseStartX)); }, up: (e: MouseEvent) => { e.preventDefault(); unsub(); setIsResizing(false); }, }; document.documentElement.addEventListener('mousemove', moveState.current.move); document.documentElement.addEventListener('mouseup', moveState.current.up); setIsResizing(true); }, [displayKv.value], ); const sidebarStyles = useMemo( () => ({ width: displayKv.value, // No width when hidden }), [displayKv.value], ); return (
{children}
); }); interface SidebarContainerProps { children: ReactNode; style: CSSProperties; floating?: boolean; } const SidebarContainer = memo(function SidebarContainer({ children, style, floating, }: SidebarContainerProps) { const sidebar = useSidebarDisplay(); const [isResizing, setIsResizing] = useState(false); const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>( null, ); const unsub = () => { if (moveState.current !== null) { document.documentElement.removeEventListener('mousemove', moveState.current.move); document.documentElement.removeEventListener('mouseup', moveState.current.up); } }; const handleResizeStart = useCallback( (e: ReactMouseEvent) => { if (sidebar.width === undefined) return; unsub(); const mouseStartX = e.clientX; const startWidth = sidebar.width; moveState.current = { move: (e: MouseEvent) => { e.preventDefault(); // Prevent text selection and things sidebar.set(startWidth + (e.clientX - mouseStartX)); }, up: (e: MouseEvent) => { e.preventDefault(); unsub(); setIsResizing(false); }, }; document.documentElement.addEventListener('mousemove', moveState.current.move); document.documentElement.addEventListener('mouseup', moveState.current.up); setIsResizing(true); }, [sidebar.width], ); const sidebarStyles = useMemo( () => ({ ...style, width: sidebar.hidden ? 0 : sidebar.width, // No width when hidden borderWidth: sidebar.hidden ? 0 : undefined, // No border when hidden }), [sidebar.width, sidebar.hidden, style], ); const commonClassname = classnames('overflow-hidden bg-gray-100 border-highlight'); if (floating) { return (
{children}
); } return (
{children}
); }); interface ResizeBarProps { isResizing: boolean; onResizeStart: (e: ReactMouseEvent) => void; onReset?: () => void; side: 'left' | 'right'; } function ResizeBar({ onResizeStart, onReset, isResizing, side }: ResizeBarProps) { return (
{/* Show global overlay with cursor style to ensure cursor remains the same when moving quickly */} {isResizing &&
}
); }