Refactor sidebar display

This commit is contained in:
Gregory Schier
2023-04-01 20:58:53 -07:00
parent 55ce39e39d
commit 35e25842be
9 changed files with 67 additions and 69 deletions

View File

@@ -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>

View File

@@ -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" /> : <></>,

View File

@@ -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}

View File

@@ -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}
/> />
</> </>
)} )}

View File

@@ -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

View File

@@ -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],
);
}

View 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]);
}

View 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,
});
}

View File

@@ -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]);
} }