summaryrefslogtreecommitdiff
path: root/apps/web/src/components/keyboard-shortcuts-help.tsx
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-07-18 11:51:47 +0200
committerMaze Winther <mazewinther@gmail.com>2025-07-18 11:51:47 +0200
commitf8ccb673bfd327e9001d5b7ba1ff77f303c2ec49 (patch)
tree2352bfdb3598c88c9e1ffcbcc04a213dcd73854c /apps/web/src/components/keyboard-shortcuts-help.tsx
parent74e7d427aaed3517ce578c2192f783dacffc02fc (diff)
downloadopencut-f8ccb673bfd327e9001d5b7ba1ff77f303c2ec49.tar.gz
style: match editing experience to match capcut
Diffstat (limited to 'apps/web/src/components/keyboard-shortcuts-help.tsx')
-rw-r--r--apps/web/src/components/keyboard-shortcuts-help.tsx187
1 files changed, 131 insertions, 56 deletions
diff --git a/apps/web/src/components/keyboard-shortcuts-help.tsx b/apps/web/src/components/keyboard-shortcuts-help.tsx
index 0481b1dd..91cfb01a 100644
--- a/apps/web/src/components/keyboard-shortcuts-help.tsx
+++ b/apps/web/src/components/keyboard-shortcuts-help.tsx
@@ -1,7 +1,7 @@
"use client";
import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts";
-import { useState } from "react";
+import { useState, useEffect } from "react";
import { Button } from "./ui/button";
import {
Dialog,
@@ -12,13 +12,13 @@ import {
DialogTrigger,
} from "./ui/dialog";
import { getPlatformSpecialKey } from "@/lib/utils";
-import { Badge } from "./ui/badge";
-import { Keyboard, Settings } from "lucide-react";
+import { Keyboard } from "lucide-react";
import {
useKeyboardShortcutsHelp,
KeyboardShortcut,
} from "@/hooks/use-keyboard-shortcuts-help";
-import { KeybindingEditor } from "./keybinding-editor";
+import { useKeybindingsStore } from "@/stores/keybindings-store";
+import { toast } from "sonner";
const modifier: {
[key: string]: string;
@@ -37,7 +37,15 @@ function getKeyWithModifier(key: string) {
return modifier[key] || key;
}
-const ShortcutItem = ({ shortcut }: { shortcut: KeyboardShortcut }) => {
+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 (
@@ -61,11 +69,21 @@ const ShortcutItem = ({ shortcut }: { shortcut: KeyboardShortcut }) => {
{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) => (
- <ShortcutKey key={partIndex}>
- {getKeyWithModifier(keyPart)}
- </ShortcutKey>
- ))}
+ {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>
@@ -77,33 +95,110 @@ const ShortcutItem = ({ shortcut }: { shortcut: KeyboardShortcut }) => {
);
};
+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();
+ };
+
+ 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 [showEditor, setShowEditor] = useState(false);
+ const [recordingKey, setRecordingKey] = useState<string | null>(null);
+ const [recordingShortcut, setRecordingShortcut] = useState<KeyboardShortcut | null>(null);
+
+ const {
+ updateKeybinding,
+ removeKeybinding,
+ getKeybindingString,
+ validateKeybinding,
+ getKeybindingsForAction,
+ } = useKeybindingsStore();
// Get shortcuts from centralized hook
const { shortcuts } = useKeyboardShortcutsHelp();
const categories = Array.from(new Set(shortcuts.map((s) => s.category)));
- 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>
- );
- }
+ useEffect(() => {
+ if (!recordingKey || !recordingShortcut) return;
+
+ 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;
+ }
+
+ // 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);
+ }
+ };
+
+ const handleClickOutside = (e: MouseEvent) => {
+ 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 handleStartRecording = (keyId: string, shortcut: KeyboardShortcut) => {
+ setRecordingKey(keyId);
+ setRecordingShortcut(shortcut);
+ };
return (
<Dialog open={open} onOpenChange={setOpen}>
@@ -121,21 +216,10 @@ export const KeyboardShortcutsHelp = () => {
</DialogTitle>
<DialogDescription>
Speed up your video editing workflow with these keyboard shortcuts.
- Most shortcuts work when the timeline is focused.
+ Click any shortcut key to edit it.
</DialogDescription>
</DialogHeader>
- <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>
-
<div className="space-y-6">
{categories.map((category) => (
<div key={category} className="flex flex-col gap-1">
@@ -146,7 +230,12 @@ export const KeyboardShortcutsHelp = () => {
{shortcuts
.filter((shortcut) => shortcut.category === category)
.map((shortcut, index) => (
- <ShortcutItem key={index} shortcut={shortcut} />
+ <ShortcutItem
+ key={index}
+ shortcut={shortcut}
+ recordingKey={recordingKey}
+ onStartRecording={handleStartRecording}
+ />
))}
</div>
</div>
@@ -156,17 +245,3 @@ export const KeyboardShortcutsHelp = () => {
</Dialog>
);
};
-
-function ShortcutKey({ children }: { children: React.ReactNode }) {
- 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"
- style={{
- backgroundColor: "rgba(0, 0, 0, 0.2)",
- borderColor: "rgba(255, 255, 255, 0.1)",
- }}
- >
- {children}
- </kbd>
- );
-}