diff --git a/apps/array/src/renderer/components/KeyboardShortcutsSheet.tsx b/apps/array/src/renderer/components/KeyboardShortcutsSheet.tsx index 55f5bee3e..bfa8a3cdb 100644 --- a/apps/array/src/renderer/components/KeyboardShortcutsSheet.tsx +++ b/apps/array/src/renderer/components/KeyboardShortcutsSheet.tsx @@ -23,7 +23,6 @@ export function KeyboardShortcutsSheet({ "general", "navigation", "panels", - "taskList", "editor", ]; diff --git a/apps/array/src/renderer/constants/keyboard-shortcuts.ts b/apps/array/src/renderer/constants/keyboard-shortcuts.ts index c92bb52f9..c5d915bef 100644 --- a/apps/array/src/renderer/constants/keyboard-shortcuts.ts +++ b/apps/array/src/renderer/constants/keyboard-shortcuts.ts @@ -11,20 +11,12 @@ export const SHORTCUTS = { SWITCH_TAB: "mod+1,mod+2,mod+3,mod+4,mod+5,mod+6,mod+7,mod+8,mod+9", OPEN_IN_EDITOR: "mod+o", COPY_PATH: "mod+shift+c", - TASK_NAV_UP: "up", - TASK_NAV_DOWN: "down", - TASK_SELECT: "enter", TASK_REFRESH: "mod+r", BLUR: "escape", SUBMIT_BLUR: "mod+enter", } as const; -export type ShortcutCategory = - | "general" - | "navigation" - | "panels" - | "editor" - | "taskList"; +export type ShortcutCategory = "general" | "navigation" | "panels" | "editor"; export interface KeyboardShortcut { id: string; @@ -117,20 +109,6 @@ export const KEYBOARD_SHORTCUTS: KeyboardShortcut[] = [ category: "panels", context: "Task detail", }, - { - id: "task-nav-up", - keys: SHORTCUTS.TASK_NAV_UP, - description: "Navigate up", - category: "taskList", - context: "Task list", - }, - { - id: "task-nav-down", - keys: SHORTCUTS.TASK_NAV_DOWN, - description: "Navigate down", - category: "taskList", - context: "Task list", - }, { id: "editor-bold", keys: "mod+b", @@ -166,7 +144,6 @@ export const CATEGORY_LABELS: Record = { navigation: "Navigation", panels: "Panels & Tabs", editor: "Editor", - taskList: "Task List", }; export function getShortcutsByCategory(): Record< @@ -178,7 +155,6 @@ export function getShortcutsByCategory(): Record< navigation: [], panels: [], editor: [], - taskList: [], }; for (const shortcut of KEYBOARD_SHORTCUTS) { grouped[shortcut.category].push(shortcut); diff --git a/apps/array/src/renderer/features/task-detail/components/ChangesPanel.tsx b/apps/array/src/renderer/features/task-detail/components/ChangesPanel.tsx index f8bf8f964..ffeb813b1 100644 --- a/apps/array/src/renderer/features/task-detail/components/ChangesPanel.tsx +++ b/apps/array/src/renderer/features/task-detail/components/ChangesPanel.tsx @@ -1,10 +1,13 @@ import { FileIcon } from "@components/ui/FileIcon"; import { PanelMessage } from "@components/ui/PanelMessage"; import { isDiffTabActiveInTree, usePanelLayoutStore } from "@features/panels"; +import { usePendingPermissionsForTask } from "@features/sessions/stores/sessionStore"; import { GitActionsBar } from "@features/task-detail/components/GitActionsBar"; import { useTaskData } from "@features/task-detail/hooks/useTaskData"; import { ArrowCounterClockwiseIcon, + CaretDownIcon, + CaretUpIcon, CodeIcon, CopyIcon, FilePlus, @@ -24,7 +27,8 @@ import { useExternalAppsStore } from "@stores/externalAppsStore"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { showMessageBox } from "@utils/dialog"; import { handleExternalAppAction } from "@utils/handleExternalAppAction"; -import { useState } from "react"; +import { useCallback, useState } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; import { selectWorktreePath, useWorkspaceStore, @@ -350,6 +354,9 @@ export function ChangesPanel({ taskId, task }: ChangesPanelProps) { const worktreePath = useWorkspaceStore(selectWorktreePath(taskId)); const repoPath = worktreePath ?? taskData.repoPath; const layout = usePanelLayoutStore((state) => state.getLayout(taskId)); + const openDiff = usePanelLayoutStore((state) => state.openDiff); + const pendingPermissions = usePendingPermissionsForTask(taskId); + const hasPendingPermissions = pendingPermissions.size > 0; const { data: changedFiles = [], isLoading } = useQuery({ queryKey: ["changed-files-head", repoPath], @@ -362,6 +369,50 @@ export function ChangesPanel({ taskId, task }: ChangesPanelProps) { refetchOnWindowFocus: true, }); + const getActiveIndex = useCallback((): number => { + if (!layout) return -1; + return changedFiles.findIndex((file) => + isDiffTabActiveInTree(layout.panelTree, file.path, file.status), + ); + }, [layout, changedFiles]); + + const handleKeyNavigation = useCallback( + (direction: "up" | "down") => { + if (changedFiles.length === 0) return; + + const currentIndex = getActiveIndex(); + const startIndex = + currentIndex === -1 + ? direction === "down" + ? -1 + : changedFiles.length + : currentIndex; + const newIndex = + direction === "up" + ? Math.max(0, startIndex - 1) + : Math.min(changedFiles.length - 1, startIndex + 1); + + const file = changedFiles[newIndex]; + if (file) { + openDiff(taskId, file.path, file.status); + } + }, + [changedFiles, getActiveIndex, openDiff, taskId], + ); + + useHotkeys( + "up", + () => handleKeyNavigation("up"), + { enabled: !hasPendingPermissions }, + [handleKeyNavigation, hasPendingPermissions], + ); + useHotkeys( + "down", + () => handleKeyNavigation("down"), + { enabled: !hasPendingPermissions }, + [handleKeyNavigation, hasPendingPermissions], + ); + const isFileActive = (file: ChangedFile): boolean => { if (!layout) return false; return isDiffTabActiveInTree(layout.panelTree, file.path, file.status); @@ -404,6 +455,16 @@ export function ChangesPanel({ taskId, task }: ChangesPanelProps) { isActive={isFileActive(file)} /> ))} + + + + / + + + + to switch files + + diff --git a/apps/array/src/renderer/features/task-list/hooks/useTaskKeyboardNavigation.ts b/apps/array/src/renderer/features/task-list/hooks/useTaskKeyboardNavigation.ts deleted file mode 100644 index 848dc0e52..000000000 --- a/apps/array/src/renderer/features/task-list/hooks/useTaskKeyboardNavigation.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { SHORTCUTS } from "@renderer/constants/keyboard-shortcuts"; -import type { Task } from "@shared/types"; -import { useCallback } from "react"; -import { useHotkeys } from "react-hotkeys-hook"; - -export function useTaskKeyboardNavigation( - filteredTasks: Task[], - selectedIndex: number | null, - hoveredIndex: number | null, - contextMenuIndex: number | null, - setSelectedIndex: (index: number | null) => void, - setHoveredIndex: (index: number | null) => void, - onSelectTask: (task: Task) => void, - refetch: () => void, -) { - const handleKeyNavigation = useCallback( - (direction: "up" | "down") => { - setHoveredIndex(null); - const startIndex = selectedIndex ?? hoveredIndex ?? 0; - if (direction === "up") { - setSelectedIndex(Math.max(0, startIndex - 1)); - } else { - setSelectedIndex(Math.min(filteredTasks.length - 1, startIndex + 1)); - } - }, - [ - filteredTasks.length, - hoveredIndex, - selectedIndex, - setHoveredIndex, - setSelectedIndex, - ], - ); - - const handleSelectCurrent = useCallback(() => { - const index = selectedIndex ?? hoveredIndex; - if (index !== null && filteredTasks[index]) { - onSelectTask(filteredTasks[index]); - } - }, [filteredTasks, selectedIndex, hoveredIndex, onSelectTask]); - - useHotkeys( - SHORTCUTS.TASK_NAV_UP, - () => handleKeyNavigation("up"), - { enableOnFormTags: false, enabled: contextMenuIndex === null }, - [handleKeyNavigation, contextMenuIndex], - ); - - useHotkeys( - SHORTCUTS.TASK_NAV_DOWN, - () => handleKeyNavigation("down"), - { enableOnFormTags: false, enabled: contextMenuIndex === null }, - [handleKeyNavigation, contextMenuIndex], - ); - - useHotkeys( - SHORTCUTS.TASK_SELECT, - handleSelectCurrent, - { enableOnFormTags: false, enabled: contextMenuIndex === null }, - [handleSelectCurrent, contextMenuIndex], - ); - - useHotkeys(SHORTCUTS.TASK_REFRESH, () => refetch(), [refetch]); -}