mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:18:30 +02:00
Fix arrow navigation for nested sidebar
This commit is contained in:
@@ -51,18 +51,22 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
const [hasFocus, setHasFocus] = useState<boolean>(false);
|
const [hasFocus, setHasFocus] = useState<boolean>(false);
|
||||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const [selectedTree, setSelectedTree] = useState<TreeNode | null>(null);
|
const [selectedTree, setSelectedTree] = useState<TreeNode | null>(null);
|
||||||
|
|
||||||
const { tree, treeParentMap } = useMemo<{
|
const { tree, treeParentMap, selectableRequests } = useMemo<{
|
||||||
tree: TreeNode | null;
|
tree: TreeNode | null;
|
||||||
treeParentMap: Record<string, TreeNode>;
|
treeParentMap: Record<string, TreeNode>;
|
||||||
|
selectableRequests: { id: string; index: number; tree: TreeNode }[];
|
||||||
}>(() => {
|
}>(() => {
|
||||||
const treeParentMap: Record<string, TreeNode> = {};
|
const treeParentMap: Record<string, TreeNode> = {};
|
||||||
|
const selectableRequests: { id: string; index: number; tree: TreeNode }[] = [];
|
||||||
if (activeWorkspace == null) {
|
if (activeWorkspace == null) {
|
||||||
return { tree: null, treeParentMap };
|
return { tree: null, treeParentMap, selectableRequests };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let selectableRequestIndex = 0;
|
||||||
|
|
||||||
// Put requests and folders into a tree structure
|
// Put requests and folders into a tree structure
|
||||||
const next = (node: TreeNode): TreeNode => {
|
const next = (node: TreeNode): TreeNode => {
|
||||||
const childItems = [...requests, ...folders].filter((f) =>
|
const childItems = [...requests, ...folders].filter((f) =>
|
||||||
@@ -74,27 +78,31 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
for (const item of childItems) {
|
for (const item of childItems) {
|
||||||
treeParentMap[item.id] = node;
|
treeParentMap[item.id] = node;
|
||||||
node.children.push(next({ item, children: [], depth }));
|
node.children.push(next({ item, children: [], depth }));
|
||||||
|
if (item.model === 'http_request') {
|
||||||
|
selectableRequests.push({ id: item.id, index: selectableRequestIndex++, tree: node });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
const tree = next({ item: activeWorkspace, children: [], depth: 0 });
|
const tree = next({ item: activeWorkspace, children: [], depth: 0 });
|
||||||
return { tree, treeParentMap };
|
|
||||||
|
return { tree, treeParentMap, selectableRequests };
|
||||||
}, [activeWorkspace, requests, folders]);
|
}, [activeWorkspace, requests, folders]);
|
||||||
|
|
||||||
// TODO: Move these listeners to a central place
|
// TODO: Move these listeners to a central place
|
||||||
useListenToTauriEvent('new_request', async () => createRequest.mutate({}));
|
useListenToTauriEvent('new_request', async () => createRequest.mutate({}));
|
||||||
|
|
||||||
const focusActiveRequest = useCallback(
|
const focusActiveRequest = useCallback(
|
||||||
(forced?: { index: number; tree: TreeNode }) => {
|
(forced?: { id: string; tree: TreeNode }) => {
|
||||||
const tree = forced?.tree ?? treeParentMap[activeRequestId ?? 'n/a'] ?? null;
|
const tree = forced?.tree ?? treeParentMap[activeRequestId ?? 'n/a'] ?? null;
|
||||||
const children = tree?.children ?? [];
|
const children = tree?.children ?? [];
|
||||||
const index = forced?.index ?? children.findIndex((m) => m.item.id === activeRequestId);
|
const id = forced?.id ?? children.find((m) => m.item.id === activeRequestId)?.item.id ?? null;
|
||||||
if (index < 0) {
|
if (id == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedIndex(index >= 0 ? index : null);
|
setSelectedId(id);
|
||||||
setSelectedTree(tree);
|
setSelectedTree(tree);
|
||||||
setHasFocus(true);
|
setHasFocus(true);
|
||||||
sidebarRef.current?.focus();
|
sidebarRef.current?.focus();
|
||||||
@@ -106,8 +114,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
(id: string) => {
|
(id: string) => {
|
||||||
const tree = treeParentMap[id ?? 'n/a'] ?? null;
|
const tree = treeParentMap[id ?? 'n/a'] ?? null;
|
||||||
const children = tree?.children ?? [];
|
const children = tree?.children ?? [];
|
||||||
const index = children.findIndex((m) => m.item.id === id) ?? -99;
|
const node = children.find((m) => m.item.id === id) ?? null;
|
||||||
const node = children[index] ?? null;
|
|
||||||
if (node == null || tree == null || node.item.model === 'workspace') {
|
if (node == null || tree == null || node.item.model === 'workspace') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -122,18 +129,18 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
workspaceId: item.workspaceId,
|
workspaceId: item.workspaceId,
|
||||||
environmentId: activeEnvironmentId ?? undefined,
|
environmentId: activeEnvironmentId ?? undefined,
|
||||||
});
|
});
|
||||||
setSelectedIndex(index);
|
setSelectedId(id);
|
||||||
setSelectedTree(tree);
|
setSelectedTree(tree);
|
||||||
focusActiveRequest({ index, tree });
|
focusActiveRequest({ id, tree });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[treeParentMap, routes, activeEnvironmentId, focusActiveRequest],
|
[treeParentMap, routes, activeEnvironmentId, focusActiveRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClearSelected = useCallback(() => {
|
const handleClearSelected = useCallback(() => {
|
||||||
setSelectedIndex(null);
|
setSelectedId(null);
|
||||||
setSelectedTree(null);
|
setSelectedTree(null);
|
||||||
}, [setSelectedIndex]);
|
}, []);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleFocus = useCallback(() => {
|
||||||
if (hasFocus) return;
|
if (hasFocus) return;
|
||||||
@@ -147,11 +154,11 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
if (!hasFocus) return;
|
if (!hasFocus) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const selectedRequest = requests[selectedIndex ?? -1];
|
const selected = selectableRequests.find((r) => r.id === selectedId);
|
||||||
if (selectedRequest === undefined) return;
|
if (selected == null) return;
|
||||||
deleteAnyRequest.mutate(selectedRequest.id);
|
deleteAnyRequest.mutate(selected.id);
|
||||||
},
|
},
|
||||||
[deleteAnyRequest, hasFocus, requests, selectedIndex],
|
[deleteAnyRequest, hasFocus, selectableRequests, selectedId],
|
||||||
);
|
);
|
||||||
|
|
||||||
useKeyPressEvent('Backspace', handleDeleteKey);
|
useKeyPressEvent('Backspace', handleDeleteKey);
|
||||||
@@ -163,8 +170,8 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
if (hidden || hasFocus) return;
|
if (hidden || hasFocus) return;
|
||||||
// Select 0 index on focus if none selected
|
// Select 0 index on focus if none selected
|
||||||
focusActiveRequest(
|
focusActiveRequest(
|
||||||
selectedTree != null && selectedIndex != null
|
selectedTree != null && selectedId != null
|
||||||
? { index: selectedIndex ?? 0, tree: selectedTree }
|
? { id: selectedId, tree: selectedTree }
|
||||||
: undefined,
|
: undefined,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -173,12 +180,15 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
|
|
||||||
useKeyPressEvent('Enter', (e) => {
|
useKeyPressEvent('Enter', (e) => {
|
||||||
if (!hasFocus) return;
|
if (!hasFocus) return;
|
||||||
const request = requests[selectedIndex ?? -1];
|
const selected = selectableRequests.find((r) => r.id === selectedId);
|
||||||
if (!request || request.id === activeRequestId) return;
|
if (!selected || selected.id === activeRequestId || activeWorkspace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
routes.navigate('request', {
|
routes.navigate('request', {
|
||||||
requestId: request.id,
|
requestId: selected.id,
|
||||||
workspaceId: request.workspaceId,
|
workspaceId: activeWorkspace?.id,
|
||||||
environmentId: activeEnvironmentId ?? undefined,
|
environmentId: activeEnvironmentId ?? undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -187,28 +197,34 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
'ArrowUp',
|
'ArrowUp',
|
||||||
() => {
|
() => {
|
||||||
if (!hasFocus) return;
|
if (!hasFocus) return;
|
||||||
let newIndex = (selectedIndex ?? requests.length) - 1;
|
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
||||||
if (newIndex < 0) {
|
const newSelectable = selectableRequests[i - 1];
|
||||||
newIndex = requests.length - 1;
|
if (newSelectable == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
setSelectedIndex(newIndex);
|
|
||||||
|
setSelectedId(newSelectable.id);
|
||||||
|
setSelectedTree(newSelectable.tree);
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
[hasFocus, requests, selectedIndex],
|
[hasFocus, selectableRequests, selectedId, setSelectedId, setSelectedTree],
|
||||||
);
|
);
|
||||||
|
|
||||||
useKey(
|
useKey(
|
||||||
'ArrowDown',
|
'ArrowDown',
|
||||||
() => {
|
() => {
|
||||||
if (!hasFocus) return;
|
if (!hasFocus) return;
|
||||||
let newIndex = (selectedIndex ?? -1) + 1;
|
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
||||||
if (newIndex > requests.length - 1) {
|
const newSelectable = selectableRequests[i + 1];
|
||||||
newIndex = 0;
|
if (newSelectable == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
setSelectedIndex(newIndex);
|
|
||||||
|
setSelectedId(newSelectable.id);
|
||||||
|
setSelectedTree(newSelectable.tree);
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
[hasFocus, requests, selectedIndex],
|
[hasFocus, selectableRequests, selectedId, setSelectedId, setSelectedTree],
|
||||||
);
|
);
|
||||||
const updateAnyRequest = useUpdateAnyRequest();
|
const updateAnyRequest = useUpdateAnyRequest();
|
||||||
const updateAnyFolder = useUpdateAnyFolder();
|
const updateAnyFolder = useUpdateAnyFolder();
|
||||||
@@ -320,7 +336,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
>
|
>
|
||||||
<SidebarItems
|
<SidebarItems
|
||||||
treeParentMap={treeParentMap}
|
treeParentMap={treeParentMap}
|
||||||
selectedIndex={selectedIndex}
|
selectedId={selectedId}
|
||||||
selectedTree={selectedTree}
|
selectedTree={selectedTree}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
tree={tree}
|
tree={tree}
|
||||||
@@ -342,7 +358,7 @@ interface SidebarItemsProps {
|
|||||||
tree: TreeNode;
|
tree: TreeNode;
|
||||||
focused: boolean;
|
focused: boolean;
|
||||||
draggingId: string | null;
|
draggingId: string | null;
|
||||||
selectedIndex: number | null;
|
selectedId: string | null;
|
||||||
selectedTree: TreeNode | null;
|
selectedTree: TreeNode | null;
|
||||||
treeParentMap: Record<string, TreeNode>;
|
treeParentMap: Record<string, TreeNode>;
|
||||||
hoveredTree: TreeNode | null;
|
hoveredTree: TreeNode | null;
|
||||||
@@ -357,7 +373,7 @@ interface SidebarItemsProps {
|
|||||||
function SidebarItems({
|
function SidebarItems({
|
||||||
tree,
|
tree,
|
||||||
focused,
|
focused,
|
||||||
selectedIndex,
|
selectedId,
|
||||||
selectedTree,
|
selectedTree,
|
||||||
draggingId,
|
draggingId,
|
||||||
onSelect,
|
onSelect,
|
||||||
@@ -377,7 +393,7 @@ function SidebarItems({
|
|||||||
<DropMarker depth={tree.depth} />
|
<DropMarker depth={tree.depth} />
|
||||||
)}
|
)}
|
||||||
<DraggableSidebarItem
|
<DraggableSidebarItem
|
||||||
selected={selectedIndex === i && selectedTree?.item.id === tree.item.id}
|
selected={selectedId === child.item.id}
|
||||||
itemId={child.item.id}
|
itemId={child.item.id}
|
||||||
itemName={child.item.name}
|
itemName={child.item.name}
|
||||||
itemModel={child.item.model}
|
itemModel={child.item.model}
|
||||||
@@ -399,7 +415,7 @@ function SidebarItems({
|
|||||||
hoveredTree={hoveredTree}
|
hoveredTree={hoveredTree}
|
||||||
hoveredIndex={hoveredIndex}
|
hoveredIndex={hoveredIndex}
|
||||||
focused={focused}
|
focused={focused}
|
||||||
selectedIndex={selectedIndex}
|
selectedId={selectedId}
|
||||||
selectedTree={selectedTree}
|
selectedTree={selectedTree}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
handleMove={handleMove}
|
handleMove={handleMove}
|
||||||
|
|||||||
Reference in New Issue
Block a user