Task attempt dec9197e-0572-4216-ac38-cc3b122df210 - Final changes
This commit is contained in:
32
frontend/src/components/keyboard-shortcuts-demo.tsx
Normal file
32
frontend/src/components/keyboard-shortcuts-demo.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useKeyboardShortcuts } from '@/lib/keyboard-shortcuts';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
export function KeyboardShortcutsDemo() {
|
||||
const shortcuts = useKeyboardShortcuts({
|
||||
navigate: undefined,
|
||||
currentPath: '/demo',
|
||||
hasOpenDialog: false,
|
||||
closeDialog: () => {},
|
||||
openCreateTask: () => {}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle>Keyboard Shortcuts</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
{Object.values(shortcuts).map((shortcut) => (
|
||||
<div key={shortcut.key} className="flex justify-between items-center">
|
||||
<span className="text-sm">{shortcut.description}</span>
|
||||
<kbd className="px-2 py-1 text-xs bg-muted rounded border">
|
||||
{shortcut.key === 'KeyC' ? 'C' : shortcut.key}
|
||||
</kbd>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import * as React from "react"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useDialogKeyboardShortcuts } from "@/lib/keyboard-shortcuts"
|
||||
|
||||
const Dialog = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
@@ -10,6 +11,13 @@ const Dialog = React.forwardRef<
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}
|
||||
>(({ className, open, onOpenChange, children, ...props }, ref) => {
|
||||
// Add keyboard shortcut support for closing dialog with Esc
|
||||
useDialogKeyboardShortcuts(() => {
|
||||
if (open && onOpenChange) {
|
||||
onOpenChange(false);
|
||||
}
|
||||
});
|
||||
|
||||
if (!open) return null
|
||||
|
||||
return (
|
||||
|
||||
112
frontend/src/lib/keyboard-shortcuts.ts
Normal file
112
frontend/src/lib/keyboard-shortcuts.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
// Define available keyboard shortcuts
|
||||
export interface KeyboardShortcut {
|
||||
key: string;
|
||||
description: string;
|
||||
action: (context?: KeyboardShortcutContext) => void;
|
||||
requiresModifier?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface KeyboardShortcutContext {
|
||||
navigate?: ReturnType<typeof useNavigate>;
|
||||
closeDialog?: () => void;
|
||||
openCreateTask?: () => void;
|
||||
currentPath?: string;
|
||||
hasOpenDialog?: boolean;
|
||||
}
|
||||
|
||||
// Centralized shortcut definitions
|
||||
export const createKeyboardShortcuts = (context: KeyboardShortcutContext): Record<string, KeyboardShortcut> => ({
|
||||
'Escape': {
|
||||
key: 'Escape',
|
||||
description: 'Go back or close dialog',
|
||||
action: () => {
|
||||
// If there's an open dialog, close it
|
||||
if (context.hasOpenDialog && context.closeDialog) {
|
||||
context.closeDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, navigate back
|
||||
if (context.navigate) {
|
||||
const currentPath = context.currentPath || window.location.pathname;
|
||||
|
||||
// Navigate back based on current path
|
||||
if (currentPath.includes('/attempts/') && currentPath.includes('/compare')) {
|
||||
// From compare page, go back to task details
|
||||
const taskPath = currentPath.split('/attempts/')[0];
|
||||
context.navigate(taskPath);
|
||||
} else if (currentPath.includes('/tasks/') && !currentPath.endsWith('/tasks')) {
|
||||
// From task details, go back to project tasks
|
||||
const projectPath = currentPath.split('/tasks/')[0] + '/tasks';
|
||||
context.navigate(projectPath);
|
||||
} else if (currentPath.includes('/projects/') && currentPath.includes('/tasks')) {
|
||||
// From project tasks, go back to projects
|
||||
context.navigate('/projects');
|
||||
} else if (currentPath !== '/' && currentPath !== '/projects') {
|
||||
// Default: go to projects page
|
||||
context.navigate('/projects');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'KeyC': {
|
||||
key: 'c',
|
||||
description: 'Create new task',
|
||||
action: () => {
|
||||
if (context.openCreateTask) {
|
||||
context.openCreateTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Hook to register global keyboard shortcuts
|
||||
export function useKeyboardShortcuts(context: KeyboardShortcutContext) {
|
||||
const shortcuts = createKeyboardShortcuts(context);
|
||||
|
||||
const handleKeyDown = useCallback((event: KeyboardEvent) => {
|
||||
// Don't trigger shortcuts when typing in input fields
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't trigger shortcuts when modifier keys are pressed (except for specific shortcuts)
|
||||
if (event.ctrlKey || event.metaKey || event.altKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcut = shortcuts[event.code] || shortcuts[event.key];
|
||||
|
||||
if (shortcut && !shortcut.disabled) {
|
||||
event.preventDefault();
|
||||
shortcut.action(context);
|
||||
}
|
||||
}, [shortcuts, context]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleKeyDown]);
|
||||
|
||||
return shortcuts;
|
||||
}
|
||||
|
||||
// Hook for dialog-specific keyboard shortcuts
|
||||
export function useDialogKeyboardShortcuts(onClose: () => void) {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, [onClose]);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Card, CardContent } from "@/components/ui/card";
|
||||
import { ArrowLeft, Plus } from "lucide-react";
|
||||
import { makeRequest } from "@/lib/api";
|
||||
import { TaskFormDialog } from "@/components/tasks/TaskFormDialog";
|
||||
import { useKeyboardShortcuts } from "@/lib/keyboard-shortcuts";
|
||||
|
||||
import { TaskKanbanBoard } from "@/components/tasks/TaskKanbanBoard";
|
||||
import type { TaskStatus, TaskWithAttemptStatus } from "shared/types";
|
||||
@@ -36,6 +37,21 @@ export function ProjectTasks() {
|
||||
const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false);
|
||||
const [editingTask, setEditingTask] = useState<Task | null>(null);
|
||||
|
||||
// Define task creation handler
|
||||
const handleCreateNewTask = () => {
|
||||
setEditingTask(null);
|
||||
setIsTaskDialogOpen(true);
|
||||
};
|
||||
|
||||
// Setup keyboard shortcuts
|
||||
useKeyboardShortcuts({
|
||||
navigate,
|
||||
currentPath: `/projects/${projectId}/tasks`,
|
||||
hasOpenDialog: isTaskDialogOpen,
|
||||
closeDialog: () => setIsTaskDialogOpen(false),
|
||||
openCreateTask: handleCreateNewTask
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (projectId) {
|
||||
fetchProject();
|
||||
@@ -178,11 +194,6 @@ export function ProjectTasks() {
|
||||
setIsTaskDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleCreateNewTask = () => {
|
||||
setEditingTask(null);
|
||||
setIsTaskDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleViewTaskDetails = (task: Task) => {
|
||||
navigate(`/projects/${projectId}/tasks/${task.id}`);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { ProjectList } from '@/components/projects/project-list'
|
||||
import { ProjectDetail } from '@/components/projects/project-detail'
|
||||
import { useKeyboardShortcuts } from '@/lib/keyboard-shortcuts'
|
||||
|
||||
export function Projects() {
|
||||
const { projectId } = useParams<{ projectId: string }>()
|
||||
@@ -10,6 +11,15 @@ export function Projects() {
|
||||
navigate('/projects')
|
||||
}
|
||||
|
||||
// Setup keyboard shortcuts (only Esc for back navigation, no task creation here)
|
||||
useKeyboardShortcuts({
|
||||
navigate,
|
||||
currentPath: projectId ? `/projects/${projectId}` : '/projects',
|
||||
hasOpenDialog: false,
|
||||
closeDialog: () => {},
|
||||
openCreateTask: () => {} // No-op for projects page
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
return (
|
||||
<ProjectDetail
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Separator } from "@/components/ui/separator";
|
||||
import { ArrowLeft, FileText, Code } from "lucide-react";
|
||||
import { makeRequest } from "@/lib/api";
|
||||
import { TaskFormDialog } from "@/components/tasks/TaskFormDialog";
|
||||
import { useKeyboardShortcuts } from "@/lib/keyboard-shortcuts";
|
||||
import type {
|
||||
TaskStatus,
|
||||
TaskAttempt,
|
||||
@@ -93,6 +94,15 @@ export function TaskDetailsPage() {
|
||||
|
||||
const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false);
|
||||
|
||||
// Setup keyboard shortcuts
|
||||
useKeyboardShortcuts({
|
||||
navigate,
|
||||
currentPath: `/projects/${projectId}/tasks/${taskId}`,
|
||||
hasOpenDialog: isTaskDialogOpen,
|
||||
closeDialog: () => setIsTaskDialogOpen(false),
|
||||
openCreateTask: () => {} // No task creation on task details page
|
||||
});
|
||||
|
||||
// Check if the selected attempt is active (not in a final state)
|
||||
const isAttemptRunning =
|
||||
selectedAttempt &&
|
||||
|
||||
Reference in New Issue
Block a user