"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 ? (
) : (
Record
)}
);
};
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 (
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 ? (
<>
Save
Cancel
>
) : (
<>
handleEditShortcut(shortcut)}
>
Edit
handleRemoveShortcut(shortcut)}
disabled={shortcut.keys.length === 0}
>
Remove
>
)}
))}
Reset Keyboard Shortcuts?
This will reset all keyboard shortcuts to their default values.
Any custom keybindings will be lost.
Cancel
Reset to Defaults
);
};