2025-07-15 12:36:53 -04:00
|
|
|
"use client";
|
|
|
|
|
|
2025-07-18 06:22:49 +06:00
|
|
|
import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts";
|
2025-07-15 12:36:53 -04:00
|
|
|
import { useState } from "react";
|
|
|
|
|
import { Button } from "./ui/button";
|
|
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogTrigger,
|
|
|
|
|
} from "./ui/dialog";
|
2025-07-18 06:22:49 +06:00
|
|
|
import { getPlatformSpecialKey } from "@/lib/utils";
|
2025-07-15 12:36:53 -04:00
|
|
|
import { Badge } from "./ui/badge";
|
2025-07-18 07:38:32 +06:00
|
|
|
import { Keyboard, Settings } from "lucide-react";
|
2025-07-18 07:09:05 +06:00
|
|
|
import {
|
|
|
|
|
useKeyboardShortcutsHelp,
|
|
|
|
|
KeyboardShortcut,
|
|
|
|
|
} from "@/hooks/use-keyboard-shortcuts-help";
|
2025-07-18 07:38:32 +06:00
|
|
|
import { KeybindingEditor } from "./keybinding-editor";
|
2025-07-15 12:36:53 -04:00
|
|
|
|
2025-07-18 06:22:49 +06:00
|
|
|
const modifier: {
|
|
|
|
|
[key: string]: string;
|
|
|
|
|
} = {
|
|
|
|
|
Shift: "Shift",
|
|
|
|
|
Alt: "Alt",
|
|
|
|
|
ArrowLeft: "←",
|
|
|
|
|
ArrowRight: "→",
|
|
|
|
|
ArrowUp: "↑",
|
|
|
|
|
ArrowDown: "↓",
|
|
|
|
|
Space: "Space",
|
2025-07-15 12:36:53 -04:00
|
|
|
};
|
|
|
|
|
|
2025-07-18 06:22:49 +06:00
|
|
|
function getKeyWithModifier(key: string) {
|
|
|
|
|
if (key === "Ctrl") return getPlatformSpecialKey();
|
|
|
|
|
return modifier[key] || key;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-18 07:09:05 +06:00
|
|
|
const ShortcutItem = ({ shortcut }: { shortcut: KeyboardShortcut }) => {
|
2025-07-16 16:05:30 +02:00
|
|
|
// Filter out lowercase duplicates for display - if both "j" and "J" exist, only show "J"
|
|
|
|
|
const displayKeys = shortcut.keys.filter((key: string) => {
|
|
|
|
|
if (
|
2025-07-18 06:22:49 +06:00
|
|
|
key.includes("Cmd") &&
|
|
|
|
|
shortcut.keys.includes(key.replace("Cmd", "Ctrl"))
|
|
|
|
|
)
|
2025-07-16 16:05:30 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
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) => (
|
2025-07-18 11:26:49 +02:00
|
|
|
<ShortcutKey key={partIndex}>
|
2025-07-18 06:22:49 +06:00
|
|
|
{getKeyWithModifier(keyPart)}
|
2025-07-18 11:26:49 +02:00
|
|
|
</ShortcutKey>
|
2025-07-16 16:05:30 +02:00
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
{index < displayKeys.length - 1 && (
|
|
|
|
|
<span className="text-xs text-muted-foreground">or</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2025-07-15 12:36:53 -04:00
|
|
|
</div>
|
2025-07-16 16:05:30 +02:00
|
|
|
);
|
|
|
|
|
};
|
2025-07-15 12:36:53 -04:00
|
|
|
|
|
|
|
|
export const KeyboardShortcutsHelp = () => {
|
|
|
|
|
const [open, setOpen] = useState(false);
|
2025-07-18 07:38:32 +06:00
|
|
|
const [showEditor, setShowEditor] = useState(false);
|
2025-07-15 12:36:53 -04:00
|
|
|
|
2025-07-18 07:09:05 +06:00
|
|
|
// Get shortcuts from centralized hook
|
|
|
|
|
const { shortcuts } = useKeyboardShortcutsHelp();
|
2025-07-16 14:37:08 +02:00
|
|
|
|
|
|
|
|
const categories = Array.from(new Set(shortcuts.map((s) => s.category)));
|
2025-07-15 12:36:53 -04:00
|
|
|
|
2025-07-18 07:38:32 +06:00
|
|
|
if (showEditor) {
|
|
|
|
|
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-6xl max-h-[90vh] overflow-y-auto">
|
|
|
|
|
<KeybindingEditor
|
|
|
|
|
shortcuts={shortcuts}
|
|
|
|
|
onClose={() => setShowEditor(false)}
|
|
|
|
|
/>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 12:36:53 -04:00
|
|
|
return (
|
|
|
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
|
|
|
<DialogTrigger asChild>
|
2025-07-15 12:58:04 -04:00
|
|
|
<Button variant="text" size="sm" className="gap-2">
|
2025-07-15 12:36:53 -04:00
|
|
|
<Keyboard className="w-4 h-4" />
|
|
|
|
|
Shortcuts
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogTrigger>
|
2025-07-18 07:38:32 +06:00
|
|
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
2025-07-15 12:36:53 -04:00
|
|
|
<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.
|
|
|
|
|
Most shortcuts work when the timeline is focused.
|
|
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
2025-07-16 14:37:08 +02:00
|
|
|
|
2025-07-18 07:38:32 +06:00
|
|
|
<div className="flex justify-end mb-4">
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => setShowEditor(true)}
|
|
|
|
|
>
|
|
|
|
|
<Settings className="w-4 h-4 mr-2" />
|
|
|
|
|
Customize
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-07-15 12:36:53 -04:00
|
|
|
<div className="space-y-6">
|
2025-07-16 14:37:08 +02:00
|
|
|
{categories.map((category) => (
|
2025-07-16 15:52:25 +02:00
|
|
|
<div key={category} className="flex flex-col gap-1">
|
|
|
|
|
<h3 className="text-xs text-muted-foreground uppercase tracking-wide font-medium">
|
2025-07-15 12:36:53 -04:00
|
|
|
{category}
|
|
|
|
|
</h3>
|
2025-07-16 15:52:25 +02:00
|
|
|
<div className="space-y-0.5">
|
2025-07-15 12:36:53 -04:00
|
|
|
{shortcuts
|
2025-07-16 14:37:08 +02:00
|
|
|
.filter((shortcut) => shortcut.category === category)
|
2025-07-15 12:36:53 -04:00
|
|
|
.map((shortcut, index) => (
|
|
|
|
|
<ShortcutItem key={index} shortcut={shortcut} />
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
2025-07-16 14:37:08 +02:00
|
|
|
};
|
2025-07-18 11:26:49 +02:00
|
|
|
|
|
|
|
|
function ShortcutKey({ children }: { children: React.ReactNode }) {
|
|
|
|
|
return (
|
2025-07-18 11:37:48 +02:00
|
|
|
<kbd
|
2025-07-18 11:26:49 +02:00
|
|
|
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"
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: "rgba(0, 0, 0, 0.2)",
|
2025-07-18 11:37:48 +02:00
|
|
|
borderColor: "rgba(255, 255, 255, 0.1)",
|
2025-07-18 11:26:49 +02:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</kbd>
|
|
|
|
|
);
|
2025-07-18 11:37:48 +02:00
|
|
|
}
|