diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index d716f14a..7e41776e 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -51,18 +51,22 @@ export const Sidebar = memo(function Sidebar({ className }: Props) { const activeWorkspace = useActiveWorkspace(); const routes = useAppRoutes(); const [hasFocus, setHasFocus] = useState(false); - const [selectedIndex, setSelectedIndex] = useState(null); + const [selectedId, setSelectedId] = useState(null); const [selectedTree, setSelectedTree] = useState(null); - const { tree, treeParentMap } = useMemo<{ + const { tree, treeParentMap, selectableRequests } = useMemo<{ tree: TreeNode | null; treeParentMap: Record; + selectableRequests: { id: string; index: number; tree: TreeNode }[]; }>(() => { const treeParentMap: Record = {}; + const selectableRequests: { id: string; index: number; tree: TreeNode }[] = []; if (activeWorkspace == null) { - return { tree: null, treeParentMap }; + return { tree: null, treeParentMap, selectableRequests }; } + let selectableRequestIndex = 0; + // Put requests and folders into a tree structure const next = (node: TreeNode): TreeNode => { const childItems = [...requests, ...folders].filter((f) => @@ -74,27 +78,31 @@ export const Sidebar = memo(function Sidebar({ className }: Props) { for (const item of childItems) { treeParentMap[item.id] = node; node.children.push(next({ item, children: [], depth })); + if (item.model === 'http_request') { + selectableRequests.push({ id: item.id, index: selectableRequestIndex++, tree: node }); + } } return node; }; const tree = next({ item: activeWorkspace, children: [], depth: 0 }); - return { tree, treeParentMap }; + + return { tree, treeParentMap, selectableRequests }; }, [activeWorkspace, requests, folders]); // TODO: Move these listeners to a central place useListenToTauriEvent('new_request', async () => createRequest.mutate({})); const focusActiveRequest = useCallback( - (forced?: { index: number; tree: TreeNode }) => { + (forced?: { id: string; tree: TreeNode }) => { const tree = forced?.tree ?? treeParentMap[activeRequestId ?? 'n/a'] ?? null; const children = tree?.children ?? []; - const index = forced?.index ?? children.findIndex((m) => m.item.id === activeRequestId); - if (index < 0) { + const id = forced?.id ?? children.find((m) => m.item.id === activeRequestId)?.item.id ?? null; + if (id == null) { return; } - setSelectedIndex(index >= 0 ? index : null); + setSelectedId(id); setSelectedTree(tree); setHasFocus(true); sidebarRef.current?.focus(); @@ -106,8 +114,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) { (id: string) => { const tree = treeParentMap[id ?? 'n/a'] ?? null; const children = tree?.children ?? []; - const index = children.findIndex((m) => m.item.id === id) ?? -99; - const node = children[index] ?? null; + const node = children.find((m) => m.item.id === id) ?? null; if (node == null || tree == null || node.item.model === 'workspace') { return; } @@ -122,18 +129,18 @@ export const Sidebar = memo(function Sidebar({ className }: Props) { workspaceId: item.workspaceId, environmentId: activeEnvironmentId ?? undefined, }); - setSelectedIndex(index); + setSelectedId(id); setSelectedTree(tree); - focusActiveRequest({ index, tree }); + focusActiveRequest({ id, tree }); } }, [treeParentMap, routes, activeEnvironmentId, focusActiveRequest], ); const handleClearSelected = useCallback(() => { - setSelectedIndex(null); + setSelectedId(null); setSelectedTree(null); - }, [setSelectedIndex]); + }, []); const handleFocus = useCallback(() => { if (hasFocus) return; @@ -147,11 +154,11 @@ export const Sidebar = memo(function Sidebar({ className }: Props) { if (!hasFocus) return; e.preventDefault(); - const selectedRequest = requests[selectedIndex ?? -1]; - if (selectedRequest === undefined) return; - deleteAnyRequest.mutate(selectedRequest.id); + const selected = selectableRequests.find((r) => r.id === selectedId); + if (selected == null) return; + deleteAnyRequest.mutate(selected.id); }, - [deleteAnyRequest, hasFocus, requests, selectedIndex], + [deleteAnyRequest, hasFocus, selectableRequests, selectedId], ); useKeyPressEvent('Backspace', handleDeleteKey); @@ -163,8 +170,8 @@ export const Sidebar = memo(function Sidebar({ className }: Props) { if (hidden || hasFocus) return; // Select 0 index on focus if none selected focusActiveRequest( - selectedTree != null && selectedIndex != null - ? { index: selectedIndex ?? 0, tree: selectedTree } + selectedTree != null && selectedId != null + ? { id: selectedId, tree: selectedTree } : undefined, ); }, @@ -173,12 +180,15 @@ export const Sidebar = memo(function Sidebar({ className }: Props) { useKeyPressEvent('Enter', (e) => { if (!hasFocus) return; - const request = requests[selectedIndex ?? -1]; - if (!request || request.id === activeRequestId) return; + const selected = selectableRequests.find((r) => r.id === selectedId); + if (!selected || selected.id === activeRequestId || activeWorkspace == null) { + return; + } + e.preventDefault(); routes.navigate('request', { - requestId: request.id, - workspaceId: request.workspaceId, + requestId: selected.id, + workspaceId: activeWorkspace?.id, environmentId: activeEnvironmentId ?? undefined, }); }); @@ -187,28 +197,34 @@ export const Sidebar = memo(function Sidebar({ className }: Props) { 'ArrowUp', () => { if (!hasFocus) return; - let newIndex = (selectedIndex ?? requests.length) - 1; - if (newIndex < 0) { - newIndex = requests.length - 1; + const i = selectableRequests.findIndex((r) => r.id === selectedId); + const newSelectable = selectableRequests[i - 1]; + if (newSelectable == null) { + return; } - setSelectedIndex(newIndex); + + setSelectedId(newSelectable.id); + setSelectedTree(newSelectable.tree); }, undefined, - [hasFocus, requests, selectedIndex], + [hasFocus, selectableRequests, selectedId, setSelectedId, setSelectedTree], ); useKey( 'ArrowDown', () => { if (!hasFocus) return; - let newIndex = (selectedIndex ?? -1) + 1; - if (newIndex > requests.length - 1) { - newIndex = 0; + const i = selectableRequests.findIndex((r) => r.id === selectedId); + const newSelectable = selectableRequests[i + 1]; + if (newSelectable == null) { + return; } - setSelectedIndex(newIndex); + + setSelectedId(newSelectable.id); + setSelectedTree(newSelectable.tree); }, undefined, - [hasFocus, requests, selectedIndex], + [hasFocus, selectableRequests, selectedId, setSelectedId, setSelectedTree], ); const updateAnyRequest = useUpdateAnyRequest(); const updateAnyFolder = useUpdateAnyFolder(); @@ -320,7 +336,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) { > ; hoveredTree: TreeNode | null; @@ -357,7 +373,7 @@ interface SidebarItemsProps { function SidebarItems({ tree, focused, - selectedIndex, + selectedId, selectedTree, draggingId, onSelect, @@ -377,7 +393,7 @@ function SidebarItems({ )}