"use client"; import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { useKeybindingsStore } from "@/stores/keybindings-store"; import { Action } from "@/constants/actions"; import { KeyboardShortcut } from "@/hooks/use-keyboard-shortcuts-help"; import { Settings, RotateCcw, Download, Upload, X, Check, AlertTriangle, } from "lucide-react"; import { toast } from "sonner"; interface KeybindingEditorProps { shortcuts: KeyboardShortcut[]; onClose: () => void; } interface KeyRecorderProps { value: string; onValueChange: (value: string) => void; onCancel: () => void; } const KeyRecorder = ({ value, onValueChange, onCancel }: KeyRecorderProps) => { const [isRecording, setIsRecording] = useState(false); const [recordedKey, setRecordedKey] = useState(""); const { getKeybindingString } = useKeybindingsStore(); useEffect(() => { if (!isRecording) return; const handleKeyDown = (e: KeyboardEvent) => { e.preventDefault(); const keyString = getKeybindingString(e); if (keyString) { setRecordedKey(keyString); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [isRecording, getKeybindingString]); const handleStartRecording = () => { setIsRecording(true); setRecordedKey(""); }; const handleConfirm = () => { onValueChange(recordedKey); setIsRecording(false); setRecordedKey(""); }; const handleCancel = () => { setIsRecording(false); setRecordedKey(""); onCancel(); }; const displayKey = recordedKey || value; return (
{isRecording ? (
) : ( )}
); }; export const KeybindingEditor = ({ shortcuts, onClose, }: KeybindingEditorProps) => { const { keybindings, updateKeybinding, removeKeybinding, resetToDefaults, isCustomized, validateKeybinding, getKeybindingsForAction, exportKeybindings, importKeybindings, } = useKeybindingsStore(); const [editingShortcut, setEditingShortcut] = useState(null); const [newKeyBinding, setNewKeyBinding] = useState(""); const [showResetDialog, setShowResetDialog] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [selectedCategory, setSelectedCategory] = useState("all"); const categories = [ "all", ...Array.from(new Set(shortcuts.map((s) => s.category))), ]; const filteredShortcuts = shortcuts.filter((shortcut) => { const matchesSearch = shortcut.description.toLowerCase().includes(searchTerm.toLowerCase()) || shortcut.keys.some((key) => key.toLowerCase().includes(searchTerm.toLowerCase()) ); const matchesCategory = selectedCategory === "all" || shortcut.category === selectedCategory; return matchesSearch && matchesCategory; }); const handleEditShortcut = (shortcut: KeyboardShortcut) => { setEditingShortcut(shortcut.id); setNewKeyBinding(shortcut.keys[0] || ""); }; const handleSaveShortcut = () => { if (!editingShortcut || !newKeyBinding) return; const shortcut = shortcuts.find((s) => s.id === editingShortcut); if (!shortcut) return; // Validate the new keybinding const conflict = validateKeybinding(newKeyBinding, shortcut.action); if (conflict) { toast.error( `Key "${newKeyBinding}" is already bound to "${conflict.existingAction}"` ); return; } // Remove old keybindings for this action const oldKeys = getKeybindingsForAction(shortcut.action); oldKeys.forEach((key) => removeKeybinding(key)); // Add new keybinding updateKeybinding(newKeyBinding, shortcut.action); setEditingShortcut(null); setNewKeyBinding(""); toast.success("Keybinding updated successfully"); }; const handleCancelEdit = () => { setEditingShortcut(null); setNewKeyBinding(""); }; const handleRemoveShortcut = (shortcut: KeyboardShortcut) => { const keys = getKeybindingsForAction(shortcut.action); keys.forEach((key) => removeKeybinding(key)); toast.success("Keybinding removed"); }; const handleResetToDefaults = () => { resetToDefaults(); setShowResetDialog(false); toast.success("Keybindings reset to defaults"); }; const handleExportKeybindings = () => { const config = exportKeybindings(); const blob = new Blob([JSON.stringify(config, null, 2)], { type: "application/json", }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "opencut-keybindings.json"; a.click(); URL.revokeObjectURL(url); toast.success("Keybindings exported"); }; const handleImportKeybindings = ( event: React.ChangeEvent ) => { const file = event.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const config = JSON.parse(e.target?.result as string); // Validate config structure if (!config || typeof config !== "object") { throw new Error("Invalid configuration format"); } // Validate each keybinding for (const [key, action] of Object.entries(config)) { if (typeof key !== "string" || typeof action !== "string") { throw new Error(`Invalid keybinding: ${key} -> ${action}`); } } importKeybindings(config); toast.success("Keybindings imported successfully"); } catch (error) { toast.error(`Failed to import keybindings: ${error}`); } }; reader.readAsText(file); }; return (

Customize Keyboard Shortcuts

{isCustomized && ( Modified )}
setSearchTerm(e.target.value)} className="flex-1" /> {categories.map((category) => ( {category === "all" ? "All" : category} ))}
{filteredShortcuts.map((shortcut) => (

{shortcut.description}

{shortcut.category}

{editingShortcut === shortcut.id ? ( ) : (
{shortcut.keys.map((key, index) => ( {key} ))} {shortcut.keys.length === 0 && ( No binding )}
)}
{editingShortcut === shortcut.id ? ( <> ) : ( <> )}
))}
Reset Keyboard Shortcuts? This will reset all keyboard shortcuts to their default values. Any custom keybindings will be lost. Cancel Reset to Defaults
); };