From 7e14be49418b8d4d8a34ed597cc3cfe6c32484a4 Mon Sep 17 00:00:00 2001 From: Rucha Date: Tue, 19 May 2026 21:44:08 +0530 Subject: [PATCH 1/3] feat(shortcut): add M key to toggle audio mute (#173) - Move M-key handler from AudioSpeedControl into useVideoEditor hook so the shortcut is guarded by ile (only fires when a video is loaded) - Protect against text input focus (INPUT, TEXTAREA, contentEditable) - Also guard against Ctrl/Meta/Alt modifiers to avoid conflicts - Use setRecipe functional update (no stale-closure risk; no extra deps) - Add visible M kbd badge on the mute toggle button for discoverability - Add collapsible KeyboardShortcutsPanel in the right sidebar documenting both shortcuts: M (mute) and Ctrl+Enter (export) Closes #173 --- src/components/AudioSpeedControl.tsx | 41 ++++-------------- src/components/VideoEditor.tsx | 63 ++++++++++++++++++++++++++++ src/hooks/useVideoEditor.ts | 25 +++++++++++ 3 files changed, 96 insertions(+), 33 deletions(-) diff --git a/src/components/AudioSpeedControl.tsx b/src/components/AudioSpeedControl.tsx index 3200f1f5..c6272e05 100644 --- a/src/components/AudioSpeedControl.tsx +++ b/src/components/AudioSpeedControl.tsx @@ -1,5 +1,4 @@ "use client"; -import { useEffect } from "react"; import { EditRecipe } from "@/lib/types" import { SPEED_STEPS } from "@/lib/constants"; @@ -12,36 +11,6 @@ interface Props { } export default function AudioSpeedControl({ recipe, onChange }: Props) { - useEffect(() => { - const handler = (e: KeyboardEvent) => { - const target = e.target as HTMLElement; - - if ( - target.tagName === "INPUT" || - target.tagName === "TEXTAREA" || - target.isContentEditable - ) { - return; - } - - if ( - e.key.toLowerCase() === "m" && - !e.ctrlKey && - !e.metaKey - ) { - onChange({ - keepAudio: !recipe.keepAudio, - }); - } - }; - - document.addEventListener("keydown", handler); - - return () => { - document.removeEventListener("keydown", handler); - }; - }, [recipe.keepAudio, onChange]); - const speedIndex = SPEED_STEPS.indexOf(recipe.speed as (typeof SPEED_STEPS)[number]); const getSpeedDescription = (speed: number) => { @@ -71,7 +40,7 @@ export default function AudioSpeedControl({ recipe, onChange }: Props) {
diff --git a/src/components/VideoEditor.tsx b/src/components/VideoEditor.tsx index d43f5d18..316c45c1 100644 --- a/src/components/VideoEditor.tsx +++ b/src/components/VideoEditor.tsx @@ -48,6 +48,67 @@ function Section({ icon, title, children, delay = 0 }: SectionProps) { ); } +/** Inline keyboard hint badge. */ +function Kbd({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +/** Collapsible panel that lists all keyboard shortcuts. */ +function KeyboardShortcutsPanel() { + const [open, setOpen] = useState(false); + + const shortcuts: { keys: React.ReactNode[]; label: string }[] = [ + { keys: [M], label: "Toggle audio mute" }, + { keys: [Ctrl, +, ], label: "Export video" }, + ]; + + return ( +
+ + + {open && ( +
    + {shortcuts.map(({ keys, label }) => ( +
  • + {label} + {keys} +
  • + ))} +
+ )} +
+ ); +} + export default function VideoEditor() { const { file, duration, recipe, status, progress, @@ -343,6 +404,8 @@ export default function VideoEditor() {
+ +