mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 01:28:35 +02:00
Add context menu support and Vim keybindings in Sidebar and Tree components
This commit is contained in:
@@ -104,7 +104,7 @@ function Sidebar({ className }: { className?: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Select the 0th index on focus if none selected
|
// Select the 0th index on focus if none selected
|
||||||
focusActiveItem();
|
setTimeout(focusActiveItem, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDragEnd = useCallback(async function handleDragEnd({
|
const handleDragEnd = useCallback(async function handleDragEnd({
|
||||||
@@ -360,6 +360,12 @@ const sidebarTreeAtom = atom<TreeNode<SidebarModel> | null>((get) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
'sidebar.context_menu': {
|
||||||
|
enable: isSidebarFocused,
|
||||||
|
cb: async function (tree: TreeHandle) {
|
||||||
|
tree.showContextMenu();
|
||||||
|
},
|
||||||
|
},
|
||||||
'sidebar.selected.delete': {
|
'sidebar.selected.delete': {
|
||||||
enable: isSidebarFocused,
|
enable: isSidebarFocused,
|
||||||
cb: async function (_: TreeHandle, items: SidebarModel[]) {
|
cb: async function (_: TreeHandle, items: SidebarModel[]) {
|
||||||
|
|||||||
@@ -76,9 +76,11 @@ export interface TreeProps<T extends { id: string }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TreeHandle {
|
export interface TreeHandle {
|
||||||
|
treeId: string;
|
||||||
focus: () => void;
|
focus: () => void;
|
||||||
selectItem: (id: string) => void;
|
selectItem: (id: string) => void;
|
||||||
renameItem: (id: string) => void;
|
renameItem: (id: string) => void;
|
||||||
|
showContextMenu: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TreeInner<T extends { id: string }>(
|
function TreeInner<T extends { id: string }>(
|
||||||
@@ -132,14 +134,24 @@ function TreeInner<T extends { id: string }>(
|
|||||||
|
|
||||||
const treeHandle = useMemo<TreeHandle>(
|
const treeHandle = useMemo<TreeHandle>(
|
||||||
() => ({
|
() => ({
|
||||||
|
treeId,
|
||||||
focus: tryFocus,
|
focus: tryFocus,
|
||||||
renameItem: (id) => treeItemRefs.current[id]?.rename(),
|
renameItem: (id) => treeItemRefs.current[id]?.rename(),
|
||||||
selectItem: (id) => {
|
selectItem: (id) => {
|
||||||
setSelected([id], false);
|
setSelected([id], false);
|
||||||
jotaiStore.set(focusIdsFamily(treeId), { anchorId: id, lastId: id });
|
jotaiStore.set(focusIdsFamily(treeId), { anchorId: id, lastId: id });
|
||||||
},
|
},
|
||||||
|
showContextMenu: async () => {
|
||||||
|
if (getContextMenu == null) return;
|
||||||
|
const items = getSelectedItems(treeId, selectableItems);
|
||||||
|
const menuItems = await getContextMenu(treeHandle, items);
|
||||||
|
const lastSelectedId = jotaiStore.get(focusIdsFamily(treeId)).lastId;
|
||||||
|
const rect = lastSelectedId ? treeItemRefs.current[lastSelectedId]?.rect() : null;
|
||||||
|
if (rect == null) return;
|
||||||
|
setShowContextMenu({ items: menuItems, x: rect.x, y: rect.y });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[setSelected, treeId, tryFocus],
|
[getContextMenu, selectableItems, setSelected, treeId, tryFocus],
|
||||||
);
|
);
|
||||||
|
|
||||||
useImperativeHandle(ref, (): TreeHandle => treeHandle, [treeHandle]);
|
useImperativeHandle(ref, (): TreeHandle => treeHandle, [treeHandle]);
|
||||||
@@ -268,7 +280,7 @@ function TreeInner<T extends { id: string }>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
useKey(
|
useKey(
|
||||||
'ArrowUp',
|
(e) => e.key === 'ArrowUp' || e.key.toLowerCase() === 'k',
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!isSidebarFocused()) return;
|
if (!isSidebarFocused()) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -279,7 +291,7 @@ function TreeInner<T extends { id: string }>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
useKey(
|
useKey(
|
||||||
'ArrowDown',
|
(e) => e.key === 'ArrowDown' || e.key.toLowerCase() === 'j',
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!isSidebarFocused()) return;
|
if (!isSidebarFocused()) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -291,7 +303,7 @@ function TreeInner<T extends { id: string }>(
|
|||||||
|
|
||||||
// If the selected item is a collapsed folder, expand it. Otherwise, select next item
|
// If the selected item is a collapsed folder, expand it. Otherwise, select next item
|
||||||
useKey(
|
useKey(
|
||||||
'ArrowRight',
|
(e) => e.key === 'ArrowRight' || e.key.toLowerCase() === 'l',
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!isSidebarFocused()) return;
|
if (!isSidebarFocused()) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -317,7 +329,7 @@ function TreeInner<T extends { id: string }>(
|
|||||||
// If the selected item is in a folder, select its parent.
|
// If the selected item is in a folder, select its parent.
|
||||||
// If the selected item is an expanded folder, collapse it.
|
// If the selected item is an expanded folder, collapse it.
|
||||||
useKey(
|
useKey(
|
||||||
'ArrowLeft',
|
(e) => e.key === 'ArrowLeft' || e.key.toLowerCase() === 'h',
|
||||||
(e) => {
|
(e) => {
|
||||||
if (!isSidebarFocused()) return;
|
if (!isSidebarFocused()) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export type TreeItemProps<T extends { id: string }> = Pick<
|
|||||||
export interface TreeItemHandle {
|
export interface TreeItemHandle {
|
||||||
rename: () => void;
|
rename: () => void;
|
||||||
isRenaming: boolean;
|
isRenaming: boolean;
|
||||||
|
rect: () => DOMRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HOVER_CLOSED_FOLDER_DELAY = 800;
|
const HOVER_CLOSED_FOLDER_DELAY = 800;
|
||||||
@@ -70,6 +71,12 @@ function TreeItem_<T extends { id: string }>({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
isRenaming: editing,
|
isRenaming: editing,
|
||||||
|
rect: () => {
|
||||||
|
if (listItemRef.current == null) {
|
||||||
|
return new DOMRect(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
return listItemRef.current.getBoundingClientRect();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}, [addRef, editing, getEditOptions, node.item]);
|
}, [addRef, editing, getEditOptions, node.item]);
|
||||||
|
|
||||||
@@ -225,7 +232,7 @@ function TreeItem_<T extends { id: string }>({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const items = await getContextMenu(node.item);
|
const items = await getContextMenu(node.item);
|
||||||
setShowContextMenu({ items, x: e.clientX, y: e.clientY });
|
setShowContextMenu({ items, x: e.clientX ?? 100, y: e.clientY ?? 100 });
|
||||||
},
|
},
|
||||||
[getContextMenu, node.item],
|
[getContextMenu, node.item],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export type HotkeyAction =
|
|||||||
| 'sidebar.selected.duplicate'
|
| 'sidebar.selected.duplicate'
|
||||||
| 'sidebar.selected.rename'
|
| 'sidebar.selected.rename'
|
||||||
| 'sidebar.focus'
|
| 'sidebar.focus'
|
||||||
|
| 'sidebar.context_menu'
|
||||||
| 'url_bar.focus'
|
| 'url_bar.focus'
|
||||||
| 'workspace_settings.show';
|
| 'workspace_settings.show';
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ const hotkeys: Record<HotkeyAction, string[]> = {
|
|||||||
'sidebar.selected.duplicate': ['CmdCtrl+d'],
|
'sidebar.selected.duplicate': ['CmdCtrl+d'],
|
||||||
'sidebar.selected.rename': ['Enter'],
|
'sidebar.selected.rename': ['Enter'],
|
||||||
'sidebar.focus': ['CmdCtrl+b'],
|
'sidebar.focus': ['CmdCtrl+b'],
|
||||||
|
'sidebar.context_menu': type() === 'macos' ? ['Control+Enter'] : ['Alt+Insert'],
|
||||||
'url_bar.focus': ['CmdCtrl+l'],
|
'url_bar.focus': ['CmdCtrl+l'],
|
||||||
'workspace_settings.show': ['CmdCtrl+;'],
|
'workspace_settings.show': ['CmdCtrl+;'],
|
||||||
};
|
};
|
||||||
@@ -75,6 +77,7 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
|
|||||||
'sidebar.selected.duplicate': 'Duplicate Selected Sidebar Item',
|
'sidebar.selected.duplicate': 'Duplicate Selected Sidebar Item',
|
||||||
'sidebar.selected.rename': 'Rename Selected Sidebar Item',
|
'sidebar.selected.rename': 'Rename Selected Sidebar Item',
|
||||||
'sidebar.focus': 'Focus or Toggle Sidebar',
|
'sidebar.focus': 'Focus or Toggle Sidebar',
|
||||||
|
'sidebar.context_menu': 'Show Context Menu',
|
||||||
'url_bar.focus': 'Focus URL',
|
'url_bar.focus': 'Focus URL',
|
||||||
'workspace_settings.show': 'Open Workspace Settings',
|
'workspace_settings.show': 'Open Workspace Settings',
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user