diff options
Diffstat (limited to 'apps/web/src/components/keyboard-shortcuts-help.tsx')
| -rw-r--r-- | apps/web/src/components/keyboard-shortcuts-help.tsx | 418 |
1 files changed, 216 insertions, 202 deletions
diff --git a/apps/web/src/components/keyboard-shortcuts-help.tsx b/apps/web/src/components/keyboard-shortcuts-help.tsx index 91cfb01a..96deab72 100644 --- a/apps/web/src/components/keyboard-shortcuts-help.tsx +++ b/apps/web/src/components/keyboard-shortcuts-help.tsx @@ -4,244 +4,258 @@ import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts"; import { useState, useEffect } from "react"; import { Button } from "./ui/button"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, } from "./ui/dialog"; import { getPlatformSpecialKey } from "@/lib/utils"; import { Keyboard } from "lucide-react"; import { - useKeyboardShortcutsHelp, - KeyboardShortcut, + useKeyboardShortcutsHelp, + KeyboardShortcut, } from "@/hooks/use-keyboard-shortcuts-help"; import { useKeybindingsStore } from "@/stores/keybindings-store"; import { toast } from "sonner"; const modifier: { - [key: string]: string; + [key: string]: string; } = { - Shift: "Shift", - Alt: "Alt", - ArrowLeft: "←", - ArrowRight: "→", - ArrowUp: "↑", - ArrowDown: "↓", - Space: "Space", + Shift: "Shift", + Alt: "Alt", + ArrowLeft: "←", + ArrowRight: "→", + ArrowUp: "↑", + ArrowDown: "↓", + Space: "Space", }; function getKeyWithModifier(key: string) { - if (key === "Ctrl") return getPlatformSpecialKey(); - return modifier[key] || key; + if (key === "Ctrl") return getPlatformSpecialKey(); + return modifier[key] || key; } -const ShortcutItem = ({ - shortcut, - recordingKey, - onStartRecording -}: { - shortcut: KeyboardShortcut; - recordingKey: string | null; - onStartRecording: (keyId: string, shortcut: KeyboardShortcut) => void; +const ShortcutItem = ({ + shortcut, + recordingKey, + onStartRecording, +}: { + shortcut: KeyboardShortcut; + recordingKey: string | null; + onStartRecording: (keyId: string, shortcut: KeyboardShortcut) => void; }) => { - // Filter out lowercase duplicates for display - if both "j" and "J" exist, only show "J" - const displayKeys = shortcut.keys.filter((key: string) => { - if ( - key.includes("Cmd") && - shortcut.keys.includes(key.replace("Cmd", "Ctrl")) - ) - return false; + // Filter out lowercase duplicates for display - if both "j" and "J" exist, only show "J" + const displayKeys = shortcut.keys.filter((key: string) => { + if ( + key.includes("Cmd") && + shortcut.keys.includes(key.replace("Cmd", "Ctrl")) + ) + return false; - return true; - }); + return true; + }); - return ( - <div className="flex items-center justify-between"> - <div className="flex items-center gap-3"> - {shortcut.icon && ( - <div className="text-muted-foreground">{shortcut.icon}</div> - )} - <span className="text-sm">{shortcut.description}</span> - </div> - <div className="flex items-center gap-1"> - {displayKeys.map((key: string, index: number) => ( - <div key={index} className="flex items-center gap-1"> - <div className="flex items-center"> - {key.split("+").map((keyPart: string, partIndex: number) => { - const keyId = `${shortcut.id}-${index}-${partIndex}`; - return ( - <EditableShortcutKey - key={partIndex} - keyId={keyId} - originalKey={key} - shortcut={shortcut} - isRecording={recordingKey === keyId} - onStartRecording={() => onStartRecording(keyId, shortcut)} - > - {getKeyWithModifier(keyPart)} - </EditableShortcutKey> - ); - })} - </div> - {index < displayKeys.length - 1 && ( - <span className="text-xs text-muted-foreground">or</span> - )} - </div> - ))} - </div> - </div> - ); + return ( + <div className="flex items-center justify-between"> + <div className="flex items-center gap-3"> + {shortcut.icon && ( + <div className="text-muted-foreground">{shortcut.icon}</div> + )} + <span className="text-sm">{shortcut.description}</span> + </div> + <div className="flex items-center gap-1"> + {displayKeys.map((key: string, index: number) => ( + <div key={index} className="flex items-center gap-1"> + <div className="flex items-center"> + {key.split("+").map((keyPart: string, partIndex: number) => { + const keyId = `${shortcut.id}-${index}-${partIndex}`; + return ( + <EditableShortcutKey + key={partIndex} + keyId={keyId} + originalKey={key} + shortcut={shortcut} + isRecording={recordingKey === keyId} + onStartRecording={() => onStartRecording(keyId, shortcut)} + > + {getKeyWithModifier(keyPart)} + </EditableShortcutKey> + ); + })} + </div> + {index < displayKeys.length - 1 && ( + <span className="text-xs text-muted-foreground">or</span> + )} + </div> + ))} + </div> + </div> + ); }; -const EditableShortcutKey = ({ - children, - keyId, - originalKey, - shortcut, - isRecording, - onStartRecording -}: { - children: React.ReactNode; - keyId: string; - originalKey: string; - shortcut: KeyboardShortcut; - isRecording: boolean; - onStartRecording: () => void; +const EditableShortcutKey = ({ + children, + keyId, + originalKey, + shortcut, + isRecording, + onStartRecording, +}: { + children: React.ReactNode; + keyId: string; + originalKey: string; + shortcut: KeyboardShortcut; + isRecording: boolean; + onStartRecording: () => void; }) => { - const handleClick = (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - onStartRecording(); - }; + const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + onStartRecording(); + }; - return ( - <kbd - className={`inline-flex font-sans text-xs rounded px-2 min-w-[1.5rem] min-h-[1.5rem] leading-none items-center justify-center shadow-sm border mr-1 cursor-pointer hover:bg-opacity-80 ${ - isRecording - ? "border-primary bg-primary/10" - : "border-white/10 bg-black/20" - }`} - onClick={handleClick} - title={isRecording ? "Press any key combination..." : "Click to edit shortcut"} - > - {children} - </kbd> - ); + return ( + <kbd + className={`inline-flex font-sans text-xs rounded px-2 min-w-[1.5rem] min-h-[1.5rem] leading-none items-center justify-center shadow-sm border mr-1 cursor-pointer hover:bg-opacity-80 ${ + isRecording + ? "border-primary bg-primary/10" + : "border-white/10 bg-black/20" + }`} + onClick={handleClick} + title={ + isRecording ? "Press any key combination..." : "Click to edit shortcut" + } + > + {children} + </kbd> + ); }; export const KeyboardShortcutsHelp = () => { - const [open, setOpen] = useState(false); - const [recordingKey, setRecordingKey] = useState<string | null>(null); - const [recordingShortcut, setRecordingShortcut] = useState<KeyboardShortcut | null>(null); + const [open, setOpen] = useState(false); + const [recordingKey, setRecordingKey] = useState<string | null>(null); + const [recordingShortcut, setRecordingShortcut] = + useState<KeyboardShortcut | null>(null); - const { - updateKeybinding, - removeKeybinding, - getKeybindingString, - validateKeybinding, - getKeybindingsForAction, - } = useKeybindingsStore(); + const { + updateKeybinding, + removeKeybinding, + getKeybindingString, + validateKeybinding, + getKeybindingsForAction, + } = useKeybindingsStore(); - // Get shortcuts from centralized hook - const { shortcuts } = useKeyboardShortcutsHelp(); + // Get shortcuts from centralized hook + const { shortcuts } = useKeyboardShortcutsHelp(); - const categories = Array.from(new Set(shortcuts.map((s) => s.category))); + const categories = Array.from(new Set(shortcuts.map((s) => s.category))); - useEffect(() => { - if (!recordingKey || !recordingShortcut) return; + useEffect(() => { + if (!recordingKey || !recordingShortcut) return; - const handleKeyDown = (e: KeyboardEvent) => { - e.preventDefault(); - e.stopPropagation(); + const handleKeyDown = (e: KeyboardEvent) => { + e.preventDefault(); + e.stopPropagation(); - const keyString = getKeybindingString(e); - if (keyString) { - // Auto-save the new keybinding - const conflict = validateKeybinding(keyString, recordingShortcut.action); - if (conflict) { - toast.error( - `Key "${keyString}" is already bound to "${conflict.existingAction}"` - ); - setRecordingKey(null); - setRecordingShortcut(null); - return; - } + const keyString = getKeybindingString(e); + if (keyString) { + // Auto-save the new keybinding + const conflict = validateKeybinding( + keyString, + recordingShortcut.action, + ); + if (conflict) { + toast.error( + `Key "${keyString}" is already bound to "${conflict.existingAction}"`, + ); + setRecordingKey(null); + setRecordingShortcut(null); + return; + } - // Remove old keybindings for this action - const oldKeys = getKeybindingsForAction(recordingShortcut.action); - oldKeys.forEach((key) => removeKeybinding(key)); + // Remove old keybindings for this action + const oldKeys = getKeybindingsForAction(recordingShortcut.action); + oldKeys.forEach((key) => removeKeybinding(key)); - // Add new keybinding - updateKeybinding(keyString, recordingShortcut.action); - - setRecordingKey(null); - setRecordingShortcut(null); - } - }; + // Add new keybinding + updateKeybinding(keyString, recordingShortcut.action); - const handleClickOutside = (e: MouseEvent) => { - setRecordingKey(null); - setRecordingShortcut(null); - }; + setRecordingKey(null); + setRecordingShortcut(null); + } + }; - document.addEventListener("keydown", handleKeyDown); - document.addEventListener("click", handleClickOutside); - - return () => { - document.removeEventListener("keydown", handleKeyDown); - document.removeEventListener("click", handleClickOutside); - }; - }, [recordingKey, recordingShortcut, getKeybindingString, updateKeybinding, removeKeybinding, validateKeybinding, getKeybindingsForAction]); + const handleClickOutside = (e: MouseEvent) => { + setRecordingKey(null); + setRecordingShortcut(null); + }; - const handleStartRecording = (keyId: string, shortcut: KeyboardShortcut) => { - setRecordingKey(keyId); - setRecordingShortcut(shortcut); - }; + document.addEventListener("keydown", handleKeyDown); + document.addEventListener("click", handleClickOutside); - return ( - <Dialog open={open} onOpenChange={setOpen}> - <DialogTrigger asChild> - <Button variant="text" size="sm" className="gap-2"> - <Keyboard className="w-4 h-4" /> - Shortcuts - </Button> - </DialogTrigger> - <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto"> - <DialogHeader> - <DialogTitle className="flex items-center gap-2"> - <Keyboard className="w-5 h-5" /> - Keyboard Shortcuts - </DialogTitle> - <DialogDescription> - Speed up your video editing workflow with these keyboard shortcuts. - Click any shortcut key to edit it. - </DialogDescription> - </DialogHeader> + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.removeEventListener("click", handleClickOutside); + }; + }, [ + recordingKey, + recordingShortcut, + getKeybindingString, + updateKeybinding, + removeKeybinding, + validateKeybinding, + getKeybindingsForAction, + ]); - <div className="space-y-6"> - {categories.map((category) => ( - <div key={category} className="flex flex-col gap-1"> - <h3 className="text-xs text-muted-foreground uppercase tracking-wide font-medium"> - {category} - </h3> - <div className="space-y-0.5"> - {shortcuts - .filter((shortcut) => shortcut.category === category) - .map((shortcut, index) => ( - <ShortcutItem - key={index} - shortcut={shortcut} - recordingKey={recordingKey} - onStartRecording={handleStartRecording} - /> - ))} - </div> - </div> - ))} - </div> - </DialogContent> - </Dialog> - ); + const handleStartRecording = (keyId: string, shortcut: KeyboardShortcut) => { + setRecordingKey(keyId); + setRecordingShortcut(shortcut); + }; + + return ( + <Dialog open={open} onOpenChange={setOpen}> + <DialogTrigger asChild> + <Button variant="text" size="sm" className="gap-2"> + <Keyboard className="w-4 h-4" /> + Shortcuts + </Button> + </DialogTrigger> + <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto"> + <DialogHeader> + <DialogTitle className="flex items-center gap-2"> + <Keyboard className="w-5 h-5" /> + Keyboard Shortcuts + </DialogTitle> + <DialogDescription> + Speed up your video editing workflow with these keyboard shortcuts. + Click any shortcut key to edit it. + </DialogDescription> + </DialogHeader> + + <div className="space-y-6"> + {categories.map((category) => ( + <div key={category} className="flex flex-col gap-1"> + <h3 className="text-xs text-muted-foreground uppercase tracking-wide font-medium"> + {category} + </h3> + <div className="space-y-0.5"> + {shortcuts + .filter((shortcut) => shortcut.category === category) + .map((shortcut, index) => ( + <ShortcutItem + key={index} + shortcut={shortcut} + recordingKey={recordingKey} + onStartRecording={handleStartRecording} + /> + ))} + </div> + </div> + ))} + </div> + </DialogContent> + </Dialog> + ); }; |
