mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:08:32 +02:00
Refactor sidebar display
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
|
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import { persistQueryClient } from '@tanstack/react-query-persist-client';
|
import { persistQueryClient } from '@tanstack/react-query-persist-client';
|
||||||
import { MotionConfig } from 'framer-motion';
|
import { MotionConfig } from 'framer-motion';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
@@ -45,7 +46,7 @@ export function App() {
|
|||||||
<Suspense>
|
<Suspense>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
<TauriListeners />
|
<TauriListeners />
|
||||||
{/*<ReactQueryDevtools initialIsOpen={false} />*/}
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DialogProvider>
|
</DialogProvider>
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified',
|
label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified',
|
||||||
onSelect: toggleViewMode,
|
onSelect: toggleViewMode,
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator', label: 'Actions' },
|
||||||
{
|
{
|
||||||
label: 'Clear Response',
|
label: 'Clear Response',
|
||||||
onSelect: deleteResponse.mutate,
|
onSelect: deleteResponse.mutate,
|
||||||
@@ -90,7 +90,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
hidden: responses.length <= 1,
|
hidden: responses.length <= 1,
|
||||||
disabled: responses.length === 0,
|
disabled: responses.length === 0,
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator', label: 'History' },
|
||||||
...responses.slice(0, 10).map((r) => ({
|
...responses.slice(0, 10).map((r) => ({
|
||||||
label: r.status + ' - ' + r.elapsed + ' ms',
|
label: r.status + ' - ' + r.elapsed + ' ms',
|
||||||
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
|
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||||
import { useSidebarDisplay } from '../hooks/useSidebarDisplay';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
|
|
||||||
export const SidebarActions = memo(function SidebarDisplayToggle() {
|
export const SidebarActions = memo(function SidebarDisplayToggle() {
|
||||||
const sidebarDisplay = useSidebarDisplay();
|
const { hidden, toggle } = useSidebarHidden();
|
||||||
const createRequest = useCreateRequest({ navigateAfter: true });
|
const createRequest = useCreateRequest({ navigateAfter: true });
|
||||||
const handleCreateRequest = useCallback(() => {
|
const handleCreateRequest = useCallback(() => {
|
||||||
createRequest.mutate({ name: 'New Request' });
|
createRequest.mutate({ name: 'New Request' });
|
||||||
@@ -13,11 +13,11 @@ export const SidebarActions = memo(function SidebarDisplayToggle() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={sidebarDisplay.toggle}
|
onClick={toggle}
|
||||||
className="pointer-events-auto"
|
className="pointer-events-auto"
|
||||||
size="sm"
|
size="sm"
|
||||||
title="Show sidebar"
|
title="Show sidebar"
|
||||||
icon={sidebarDisplay.hidden ? 'leftPanelHidden' : 'leftPanelVisible'}
|
icon={hidden ? 'leftPanelHidden' : 'leftPanelVisible'}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleCreateRequest}
|
onClick={handleCreateRequest}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import type {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useWindowSize } from 'react-use';
|
import { useWindowSize } from 'react-use';
|
||||||
import { useSidebarDisplay } from '../hooks/useSidebarDisplay';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
|
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||||
import { WINDOW_FLOATING_SIDEBAR_WIDTH } from '../lib/constants';
|
import { WINDOW_FLOATING_SIDEBAR_WIDTH } from '../lib/constants';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
@@ -25,7 +26,9 @@ const body = { gridArea: 'body' };
|
|||||||
const drag = { gridArea: 'drag' };
|
const drag = { gridArea: 'drag' };
|
||||||
|
|
||||||
export default function Workspace() {
|
export default function Workspace() {
|
||||||
const sidebar = useSidebarDisplay();
|
const { set: setWidth, value: width, reset: resetWidth } = useSidebarWidth();
|
||||||
|
const { show, hide, hidden } = useSidebarHidden();
|
||||||
|
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const [floating, setFloating] = useState<boolean>(false);
|
const [floating, setFloating] = useState<boolean>(false);
|
||||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||||
@@ -35,14 +38,17 @@ export default function Workspace() {
|
|||||||
|
|
||||||
// float/un-float sidebar on window resize
|
// float/un-float sidebar on window resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH) {
|
const shouldHide = windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
|
||||||
|
if (shouldHide && !hidden) {
|
||||||
setFloating(true);
|
setFloating(true);
|
||||||
sidebar.hide();
|
hide();
|
||||||
} else {
|
} else if (!shouldHide && hidden) {
|
||||||
setFloating(false);
|
setFloating(false);
|
||||||
sidebar.show();
|
show();
|
||||||
}
|
}
|
||||||
}, [sidebar, windowSize.width]);
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [windowSize.width]);
|
||||||
|
|
||||||
const unsub = () => {
|
const unsub = () => {
|
||||||
if (moveState.current !== null) {
|
if (moveState.current !== null) {
|
||||||
@@ -53,15 +59,15 @@ export default function Workspace() {
|
|||||||
|
|
||||||
const handleResizeStart = useCallback(
|
const handleResizeStart = useCallback(
|
||||||
(e: ReactMouseEvent<HTMLDivElement>) => {
|
(e: ReactMouseEvent<HTMLDivElement>) => {
|
||||||
if (sidebar.width === undefined) return;
|
if (width === undefined) return;
|
||||||
|
|
||||||
unsub();
|
unsub();
|
||||||
const mouseStartX = e.clientX;
|
const mouseStartX = e.clientX;
|
||||||
const startWidth = sidebar.width;
|
const startWidth = width;
|
||||||
moveState.current = {
|
moveState.current = {
|
||||||
move: async (e: MouseEvent) => {
|
move: async (e: MouseEvent) => {
|
||||||
e.preventDefault(); // Prevent text selection and things
|
e.preventDefault(); // Prevent text selection and things
|
||||||
sidebar.set(startWidth + (e.clientX - mouseStartX));
|
setWidth(startWidth + (e.clientX - mouseStartX));
|
||||||
},
|
},
|
||||||
up: (e: MouseEvent) => {
|
up: (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -73,10 +79,10 @@ export default function Workspace() {
|
|||||||
document.documentElement.addEventListener('mouseup', moveState.current.up);
|
document.documentElement.addEventListener('mouseup', moveState.current.up);
|
||||||
setIsResizing(true);
|
setIsResizing(true);
|
||||||
},
|
},
|
||||||
[sidebar],
|
[setWidth, width],
|
||||||
);
|
);
|
||||||
|
|
||||||
const sideWidth = sidebar.hidden ? 0 : sidebar.width;
|
const sideWidth = hidden ? 0 : width;
|
||||||
const styles = useMemo<CSSProperties>(
|
const styles = useMemo<CSSProperties>(
|
||||||
() => ({
|
() => ({
|
||||||
gridTemplate: floating
|
gridTemplate: floating
|
||||||
@@ -118,7 +124,7 @@ export default function Workspace() {
|
|||||||
<WorkspaceHeader className="pointer-events-none" />
|
<WorkspaceHeader className="pointer-events-none" />
|
||||||
</HeaderSize>
|
</HeaderSize>
|
||||||
{floating ? (
|
{floating ? (
|
||||||
<Overlay open={!sidebar.hidden} portalName="sidebar" onClose={sidebar.hide}>
|
<Overlay open={!hidden} portalName="sidebar" onClose={hide}>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: -10 }}
|
initial={{ opacity: 0, x: -10 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
@@ -149,7 +155,7 @@ export default function Workspace() {
|
|||||||
side="right"
|
side="right"
|
||||||
isResizing={isResizing}
|
isResizing={isResizing}
|
||||||
onResizeStart={handleResizeStart}
|
onResizeStart={handleResizeStart}
|
||||||
onReset={sidebar.reset}
|
onReset={resetWidth}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ const introspectionRequestBody = JSON.stringify({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function useIntrospectGraphQL(baseRequest: HttpRequest) {
|
export function useIntrospectGraphQL(baseRequest: HttpRequest) {
|
||||||
const url = useDebouncedValue(baseRequest.url);
|
// Debounce the URL because it can change rapidly, and we don't
|
||||||
|
// want to send so many requests.
|
||||||
|
const debouncedUrl = useDebouncedValue(baseRequest.url);
|
||||||
|
|
||||||
return useQuery<GraphQLSchema, Error>({
|
return useQuery<GraphQLSchema, Error>({
|
||||||
queryKey: ['introspectGraphQL', { url }],
|
queryKey: ['introspectGraphQL', { url: debouncedUrl }],
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
// staleTime: 1000 * 60 * 60, // 1 hour
|
// staleTime: 1000 * 60 * 60, // 1 hour
|
||||||
refetchInterval: 1000 * 60, // Refetch every minute
|
refetchInterval: 1000 * 60, // Refetch every minute
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
|
||||||
import { useKeyValue } from './useKeyValue';
|
|
||||||
|
|
||||||
const START_WIDTH = 200;
|
|
||||||
const MIN_WIDTH = 150;
|
|
||||||
const COLLAPSE_WIDTH = MIN_WIDTH * 0.25;
|
|
||||||
|
|
||||||
export const sidebarDisplayKey = 'sidebar_display';
|
|
||||||
export const sidebarDisplayDefaultValue: SidebarDisplay = { hidden: false, width: START_WIDTH };
|
|
||||||
|
|
||||||
export interface SidebarDisplay {
|
|
||||||
hidden: boolean;
|
|
||||||
width: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSidebarDisplay() {
|
|
||||||
const display = useKeyValue<SidebarDisplay>({
|
|
||||||
namespace: NAMESPACE_NO_SYNC,
|
|
||||||
key: sidebarDisplayKey,
|
|
||||||
defaultValue: sidebarDisplayDefaultValue,
|
|
||||||
});
|
|
||||||
const hidden = display.value?.hidden ?? false;
|
|
||||||
const width = display.value?.width ?? START_WIDTH;
|
|
||||||
|
|
||||||
const set = useCallback(
|
|
||||||
(width: number) => {
|
|
||||||
const hidden = width < COLLAPSE_WIDTH;
|
|
||||||
display.set({ hidden, width: Math.max(MIN_WIDTH, width) });
|
|
||||||
},
|
|
||||||
[display],
|
|
||||||
);
|
|
||||||
const hide = useCallback(() => display.set((v) => ({ ...v, hidden: true })), [display]);
|
|
||||||
const show = useCallback(() => display.set((v) => ({ ...v, hidden: false })), [display]);
|
|
||||||
const toggle = useCallback(() => display.set((v) => ({ ...v, hidden: !v.hidden })), [display]);
|
|
||||||
const reset = display.reset;
|
|
||||||
|
|
||||||
return useMemo(
|
|
||||||
() => ({ width, hidden, set, reset, hide, show, toggle }),
|
|
||||||
[hidden, hide, reset, set, show, toggle, width],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
20
src-web/hooks/useSidebarHidden.ts
Normal file
20
src-web/hooks/useSidebarHidden.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||||
|
import { useKeyValue } from './useKeyValue';
|
||||||
|
|
||||||
|
export function useSidebarHidden() {
|
||||||
|
const { set, value } = useKeyValue<boolean>({
|
||||||
|
namespace: NAMESPACE_NO_SYNC,
|
||||||
|
key: 'sidebar_hidden',
|
||||||
|
defaultValue: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return {
|
||||||
|
show: () => set(false),
|
||||||
|
hide: () => set(true),
|
||||||
|
toggle: () => set((h) => !h),
|
||||||
|
hidden: value,
|
||||||
|
};
|
||||||
|
}, [set, value]);
|
||||||
|
}
|
||||||
10
src-web/hooks/useSidebarWidth.ts
Normal file
10
src-web/hooks/useSidebarWidth.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||||
|
import { useKeyValue } from './useKeyValue';
|
||||||
|
|
||||||
|
export function useSidebarWidth() {
|
||||||
|
return useKeyValue<number>({
|
||||||
|
namespace: NAMESPACE_NO_SYNC,
|
||||||
|
key: 'sidebar_width',
|
||||||
|
defaultValue: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -15,14 +15,14 @@ import { requestsQueryKey } from './useRequests';
|
|||||||
import { useRequestUpdateKey } from './useRequestUpdateKey';
|
import { useRequestUpdateKey } from './useRequestUpdateKey';
|
||||||
import { responsesQueryKey } from './useResponses';
|
import { responsesQueryKey } from './useResponses';
|
||||||
import { routePaths } from './useRoutes';
|
import { routePaths } from './useRoutes';
|
||||||
import { useSidebarDisplay } from './useSidebarDisplay';
|
import { useSidebarHidden } from './useSidebarHidden';
|
||||||
import { workspacesQueryKey } from './useWorkspaces';
|
import { workspacesQueryKey } from './useWorkspaces';
|
||||||
|
|
||||||
const unsubFns: (() => void)[] = [];
|
const unsubFns: (() => void)[] = [];
|
||||||
export const UPDATE_DEBOUNCE_MILLIS = 100;
|
export const UPDATE_DEBOUNCE_MILLIS = 100;
|
||||||
|
|
||||||
export function useTauriListeners() {
|
export function useTauriListeners() {
|
||||||
const sidebarDisplay = useSidebarDisplay();
|
const { toggle } = useSidebarHidden();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export function useTauriListeners() {
|
|||||||
listen(event, debounce(fn, UPDATE_DEBOUNCE_MILLIS));
|
listen(event, debounce(fn, UPDATE_DEBOUNCE_MILLIS));
|
||||||
}
|
}
|
||||||
|
|
||||||
listen<void>('toggle_sidebar', sidebarDisplay.toggle);
|
listen<void>('toggle_sidebar', toggle);
|
||||||
listen<void>('refresh', () => location.reload());
|
listen<void>('refresh', () => location.reload());
|
||||||
|
|
||||||
listenDebounced<Model>('created_model', ({ payload, windowLabel }) => {
|
listenDebounced<Model>('created_model', ({ payload, windowLabel }) => {
|
||||||
@@ -156,5 +156,5 @@ export function useTauriListeners() {
|
|||||||
unsub();
|
unsub();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [queryClient, sidebarDisplay.toggle, wasUpdatedExternally]);
|
}, [queryClient, toggle, wasUpdatedExternally]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user