mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-17 00:56:37 +01:00
271 lines
9.4 KiB
TypeScript
271 lines
9.4 KiB
TypeScript
import classNames from 'classnames';
|
|
import * as m from 'motion/react-m';
|
|
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
import {duplicateWebsocketRequest} from "../commands/duplicateWebsocketRequest";
|
|
import {
|
|
useEnsureActiveCookieJar,
|
|
useSubscribeActiveCookieJarId,
|
|
} from '../hooks/useActiveCookieJar';
|
|
import { useSubscribeActiveEnvironmentId } from '../hooks/useActiveEnvironment';
|
|
import { getActiveRequest, useActiveRequest } from '../hooks/useActiveRequest';
|
|
import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId';
|
|
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
|
import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest';
|
|
import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest';
|
|
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
|
|
import { useHotKey } from '../hooks/useHotKey';
|
|
import { useImportData } from '../hooks/useImportData';
|
|
import { useSubscribeRecentCookieJars } from '../hooks/useRecentCookieJars';
|
|
import { useSubscribeRecentEnvironments } from '../hooks/useRecentEnvironments';
|
|
import { useSubscribeRecentRequests } from '../hooks/useRecentRequests';
|
|
import { useSubscribeRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
|
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
|
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
|
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
|
import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle';
|
|
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
|
|
import { useWorkspaces } from '../hooks/useWorkspaces';
|
|
import { Banner } from './core/Banner';
|
|
import { Button } from './core/Button';
|
|
import { HotKeyList } from './core/HotKeyList';
|
|
import { FeedbackLink } from './core/Link';
|
|
import { HStack } from './core/Stacks';
|
|
import { CreateDropdown } from './CreateDropdown';
|
|
import { GrpcConnectionLayout } from './GrpcConnectionLayout';
|
|
import { HeaderSize } from './HeaderSize';
|
|
import { HttpRequestLayout } from './HttpRequestLayout';
|
|
import { Overlay } from './Overlay';
|
|
import { ResizeHandle } from './ResizeHandle';
|
|
import { Sidebar } from './sidebar/Sidebar';
|
|
import { SidebarActions } from './sidebar/SidebarActions';
|
|
import { WebsocketRequestLayout } from './WebsocketRequestLayout';
|
|
import { WorkspaceHeader } from './WorkspaceHeader';
|
|
|
|
const side = { gridArea: 'side' };
|
|
const head = { gridArea: 'head' };
|
|
const body = { gridArea: 'body' };
|
|
const drag = { gridArea: 'drag' };
|
|
|
|
export function Workspace() {
|
|
// First, subscribe to some things applicable to workspaces
|
|
useGlobalWorkspaceHooks();
|
|
|
|
const workspaces = useWorkspaces();
|
|
const { setWidth, width, resetWidth } = useSidebarWidth();
|
|
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
|
|
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
|
|
const floating = useShouldFloatSidebar();
|
|
const [isResizing, setIsResizing] = useState<boolean>(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<HTMLDivElement>) => {
|
|
if (width === undefined) return;
|
|
|
|
unsub();
|
|
const mouseStartX = e.clientX;
|
|
const startWidth = width;
|
|
moveState.current = {
|
|
move: async (e: MouseEvent) => {
|
|
e.preventDefault(); // Prevent text selection and things
|
|
const newWidth = startWidth + (e.clientX - mouseStartX);
|
|
if (newWidth < 50) {
|
|
await setSidebarHidden(true);
|
|
resetWidth();
|
|
} else {
|
|
await setSidebarHidden(false);
|
|
setWidth(newWidth);
|
|
}
|
|
},
|
|
up: (e: MouseEvent) => {
|
|
e.preventDefault();
|
|
unsub();
|
|
setIsResizing(false);
|
|
},
|
|
};
|
|
document.documentElement.addEventListener('mousemove', moveState.current.move);
|
|
document.documentElement.addEventListener('mouseup', moveState.current.up);
|
|
setIsResizing(true);
|
|
},
|
|
[width, setSidebarHidden, resetWidth, setWidth],
|
|
);
|
|
|
|
const sideWidth = sidebarHidden ? 0 : width;
|
|
const styles = useMemo<CSSProperties>(
|
|
() => ({
|
|
gridTemplate: floating
|
|
? `
|
|
' ${head.gridArea}' auto
|
|
' ${body.gridArea}' minmax(0,1fr)
|
|
/ 1fr`
|
|
: `
|
|
' ${head.gridArea} ${head.gridArea} ${head.gridArea}' auto
|
|
' ${side.gridArea} ${drag.gridArea} ${body.gridArea}' minmax(0,1fr)
|
|
/ ${sideWidth}px 0 1fr`,
|
|
}),
|
|
[sideWidth, floating],
|
|
);
|
|
|
|
// We're loading still
|
|
if (workspaces.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
style={styles}
|
|
className={classNames(
|
|
'grid w-full h-full',
|
|
// Animate sidebar width changes but only when not resizing
|
|
// because it's too slow to animate on mouse move
|
|
!isResizing && 'transition-grid',
|
|
)}
|
|
>
|
|
{floating ? (
|
|
<Overlay
|
|
open={!floatingSidebarHidden}
|
|
portalName="sidebar"
|
|
onClose={() => setFloatingSidebarHidden(true)}
|
|
>
|
|
<m.div
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
className={classNames(
|
|
'x-theme-sidebar',
|
|
'absolute top-0 left-0 bottom-0 bg-surface border-r border-border-subtle w-[14rem]',
|
|
'grid grid-rows-[auto_1fr]',
|
|
)}
|
|
>
|
|
<HeaderSize size="lg" className="border-transparent">
|
|
<SidebarActions />
|
|
</HeaderSize>
|
|
<Sidebar />
|
|
</m.div>
|
|
</Overlay>
|
|
) : (
|
|
<>
|
|
<div style={side} className={classNames('x-theme-sidebar', 'overflow-hidden bg-surface')}>
|
|
<Sidebar className="border-r border-border-subtle" />
|
|
</div>
|
|
<ResizeHandle
|
|
className="-translate-x-3"
|
|
justify="end"
|
|
side="right"
|
|
isResizing={isResizing}
|
|
onResizeStart={handleResizeStart}
|
|
onReset={resetWidth}
|
|
/>
|
|
</>
|
|
)}
|
|
<HeaderSize
|
|
data-tauri-drag-region
|
|
size="lg"
|
|
className="x-theme-appHeader bg-surface"
|
|
style={head}
|
|
>
|
|
<WorkspaceHeader className="pointer-events-none" />
|
|
</HeaderSize>
|
|
<WorkspaceBody />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function WorkspaceBody() {
|
|
const activeRequest = useActiveRequest();
|
|
const activeWorkspace = useActiveWorkspace();
|
|
const importData = useImportData();
|
|
|
|
if (activeWorkspace == null) {
|
|
return (
|
|
<div className="m-auto">
|
|
<Banner color="warning" className="max-w-[30rem]">
|
|
The active workspace was not found. Select a workspace from the header menu or report this
|
|
bug to <FeedbackLink />
|
|
</Banner>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (activeRequest == null) {
|
|
return (
|
|
<HotKeyList
|
|
hotkeys={['http_request.create', 'sidebar.focus', 'settings.show']}
|
|
bottomSlot={
|
|
<HStack space={1} justifyContent="center" className="mt-3">
|
|
<Button variant="border" size="sm" onClick={() => importData.mutate()}>
|
|
Import
|
|
</Button>
|
|
<CreateDropdown hideFolder>
|
|
<Button variant="border" forDropdown size="sm">
|
|
New Request
|
|
</Button>
|
|
</CreateDropdown>
|
|
</HStack>
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (activeRequest.model === 'grpc_request') {
|
|
return <GrpcConnectionLayout style={body} />;
|
|
} else if (activeRequest.model === 'websocket_request') {
|
|
return <WebsocketRequestLayout style={body} activeRequest={activeRequest} />;
|
|
} else {
|
|
return <HttpRequestLayout activeRequest={activeRequest} style={body} />;
|
|
}
|
|
}
|
|
|
|
function useGlobalWorkspaceHooks() {
|
|
useEnsureActiveCookieJar();
|
|
|
|
useSubscribeActiveRequestId();
|
|
useSubscribeActiveEnvironmentId();
|
|
useSubscribeActiveCookieJarId();
|
|
|
|
useSubscribeRecentRequests();
|
|
useSubscribeRecentWorkspaces();
|
|
useSubscribeRecentEnvironments();
|
|
useSubscribeRecentCookieJars();
|
|
|
|
useSyncWorkspaceRequestTitle();
|
|
|
|
const toggleCommandPalette = useToggleCommandPalette();
|
|
useHotKey('command_palette.toggle', toggleCommandPalette);
|
|
|
|
const activeRequest = useActiveRequest();
|
|
const duplicateHttpRequest = useDuplicateHttpRequest({
|
|
id: activeRequest?.id ?? null,
|
|
navigateAfter: true,
|
|
});
|
|
const duplicateGrpcRequest = useDuplicateGrpcRequest({
|
|
id: activeRequest?.id ?? null,
|
|
navigateAfter: true,
|
|
});
|
|
|
|
useHotKey('http_request.duplicate', async () => {
|
|
const activeRequest = getActiveRequest();
|
|
if (activeRequest == null) {
|
|
// Nothing
|
|
} else if (activeRequest.model === 'http_request') {
|
|
await duplicateHttpRequest.mutateAsync();
|
|
} else if (activeRequest.model === 'grpc_request') {
|
|
await duplicateGrpcRequest.mutateAsync();
|
|
} else if (activeRequest.model === 'websocket_request') {
|
|
await duplicateWebsocketRequest.mutateAsync(activeRequest.id);
|
|
} else {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
throw new Error('Failed to duplicate invalid request model: ' + (activeRequest as any).model);
|
|
}
|
|
});
|
|
}
|