import { useState, useCallback, useId, memo } from "react"; import { formatTime, frameToSeconds } from "../lib/time"; import { Tooltip } from "../../components/ui"; import { useContextMenuDismiss } from "../../hooks/useContextMenuDismiss"; const SHORTCUT_SECTIONS = [ { title: "Playback", hints: [ { key: "Space", label: "Play / Pause" }, { key: "J", label: "Play backward" }, { key: "K", label: "Stop" }, { key: "L", label: "Play forward" }, { key: "M", label: "Toggle mute" }, { key: "⇧L", label: "Toggle loop" }, { key: "←/→", label: "Step 1 frame" }, { key: "⇧←/⇧→", label: "Step 10 frames" }, { key: "F", label: "Toggle fullscreen" }, ], }, { title: "Keyframes", hints: [ { key: "K", label: "Add keyframe at playhead" }, { key: "Del", label: "Delete selected keyframe" }, { key: "H", label: "Toggle hold / bezier" }, { key: "U", label: "Expand / collapse properties" }, { key: "R", label: "Record gesture" }, ], }, { title: "Editing", hints: [ { key: "⌘Z", label: "Undo" }, { key: "⌘⇧Z", label: "Redo" }, { key: "⌘C", label: "Copy element" }, { key: "⌘V", label: "Paste element" }, { key: "⌘X", label: "Cut element" }, { key: "S", label: "Split clip at playhead" }, { key: "⌘G", label: "Group elements" }, { key: "⌘⇧G", label: "Ungroup" }, { key: "Del", label: "Delete selected element" }, ], }, { title: "Gesture recording modifiers", hints: [ { key: "Drag", label: "Record x / y position" }, { key: "Scroll", label: "Record z depth" }, { key: "⇧ Drag", label: "Record rotationX / rotationY" }, { key: "⌥ Drag", label: "Record rotation" }, { key: "⌘ Drag↕", label: "Record opacity" }, { key: "⌘ Scroll", label: "Record scale" }, ], }, { title: "Canvas", hints: [ { key: "Drag", label: "Move element / add keyframe" }, { key: "⌥ Drag", label: "Move entire animation path" }, { key: "⇧ Drag", label: "Uniform resize" }, ], }, { title: "Crop", hints: [ { key: "Drag edge", label: "Crop a side" }, { key: "Drag center", label: "Reposition the crop" }, ], }, { title: "Panels", hints: [ { key: "⌘1", label: "Compositions tab" }, { key: "⌘2", label: "Assets tab" }, ], }, { title: "Work area", hints: [ { key: "I", label: "Set in-point" }, { key: "⇧I", label: "Clear in-point" }, { key: "O", label: "Set out-point" }, { key: "⇧O", label: "Clear out-point" }, { key: "A", label: "Jump to in-point" }, { key: "E", label: "Jump to out-point" }, ], }, ] as const; interface ShortcutsPanelProps { disabled: boolean; duration: number; inPoint: number | null; outPoint: number | null; setInPoint: (v: number | null) => void; setOutPoint: (v: number | null) => void; onSeek: (time: number) => void; } export const ShortcutsPanel = memo(function ShortcutsPanel({ disabled, duration, inPoint, outPoint, setInPoint, setOutPoint, onSeek, }: ShortcutsPanelProps) { const [showShortcuts, setShowShortcuts] = useState(false); const [jumpFrame, setJumpFrame] = useState(""); const shortcutsPanelId = useId(); const closeShortcuts = useCallback(() => setShowShortcuts(false), []); const shortcutsPanelRef = useContextMenuDismiss(closeShortcuts); const commitJumpFrame = useCallback(() => { if (disabled) return; const frame = Number.parseInt(jumpFrame, 10); if (!Number.isFinite(frame) || duration <= 0) return; onSeek(Math.min(duration, frameToSeconds(Math.max(0, frame)))); }, [disabled, duration, jumpFrame, onSeek]); const handleJumpSubmit = useCallback( (e: React.FormEvent) => { e.preventDefault(); commitJumpFrame(); }, [commitJumpFrame], ); const handleJumpKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key !== "Enter") return; e.preventDefault(); commitJumpFrame(); }, [commitJumpFrame], ); return (
{showShortcuts && (