"use client"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { type KeyboardShortcut, useKeyboardShortcutsHelp, } from "@/actions/use-keyboard-shortcuts-help"; import { useKeybindingsStore } from "@/actions/keybindings-store"; import { Button } from "@/components/ui/button"; import { Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; export function ShortcutsDialog({ isOpen, onOpenChange, }: { isOpen: boolean; onOpenChange: (open: boolean) => void; }) { const [recordingShortcut, setRecordingShortcut] = useState(null); const { updateKeybinding, removeKeybinding, getKeybindingString, validateKeybinding, getKeybindingsForAction, setIsRecording, resetToDefaults, isRecording, } = useKeybindingsStore(); const { shortcuts } = useKeyboardShortcutsHelp(); const categories = Array.from(new Set(shortcuts.map((s) => s.category))); useEffect(() => { if (!isRecording || !recordingShortcut) return; const handleKeyDown = (e: KeyboardEvent) => { e.preventDefault(); e.stopPropagation(); const keyString = getKeybindingString(e); if (keyString) { const conflict = validateKeybinding({ key: keyString, action: recordingShortcut.action, }); if (conflict) { toast.error( `Key "${keyString}" is already bound to "${conflict.existingAction}"`, ); setRecordingShortcut(null); return; } const oldKeys = getKeybindingsForAction(recordingShortcut.action); for (const key of oldKeys) { removeKeybinding(key); } updateKeybinding({ key: keyString, action: recordingShortcut.action, }); setIsRecording(false); setRecordingShortcut(null); } }; const handleClickOutside = () => { setRecordingShortcut(null); setIsRecording(false); }; document.addEventListener("keydown", handleKeyDown); document.addEventListener("click", handleClickOutside); return () => { document.removeEventListener("keydown", handleKeyDown); document.removeEventListener("click", handleClickOutside); }; }, [ recordingShortcut, getKeybindingString, updateKeybinding, removeKeybinding, validateKeybinding, getKeybindingsForAction, setIsRecording, isRecording, ]); const handleStartRecording = (shortcut: KeyboardShortcut) => { setRecordingShortcut(shortcut); setIsRecording(true); }; return ( Keyboard shortcuts
{categories.map((category) => (

{category}

{shortcuts .filter((shortcut) => shortcut.category === category) .map((shortcut) => ( handleStartRecording(shortcut)} /> ))}
))}
); } function ShortcutItem({ shortcut, isRecording, onStartRecording, }: { shortcut: KeyboardShortcut; isRecording: boolean; onStartRecording: (params: { shortcut: KeyboardShortcut }) => void; }) { const displayKeys = shortcut.keys.filter((key: string) => { if ( key.includes("Cmd") && shortcut.keys.includes(key.replace("Cmd", "Ctrl")) ) return false; return true; }); return (
{shortcut.icon && (
{shortcut.icon}
)} {shortcut.description}
{displayKeys.map((key: string, index: number) => (
{key.split("+").map((keyPart: string, partIndex: number) => { const keyId = `${shortcut.id}-${index}-${partIndex}`; return ( onStartRecording({ shortcut })} > {keyPart} ); })}
{index < displayKeys.length - 1 && ( or )}
))}
); } function EditableShortcutKey({ children, isRecording, onStartRecording, }: { children: React.ReactNode; isRecording: boolean; onStartRecording: () => void; }) { const handleClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); onStartRecording(); }; return ( ); }