From 407c6d7c827537d404054b16f6aadf589a6b06bc Mon Sep 17 00:00:00 2001 From: Junaid Khan Date: Sat, 18 Apr 2026 19:23:26 -0400 Subject: [PATCH 1/3] added shortcut keys to tooltips in editing category --- .../panels/timeline/timeline-toolbar.tsx | 143 +++++++++++------- bun.lock | 11 +- package.json | 3 +- 3 files changed, 97 insertions(+), 60 deletions(-) diff --git a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx b/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx index 80ea10d0a..e34821812 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx +++ b/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx @@ -49,6 +49,7 @@ import { OcRippleIcon } from "@/components/icons"; import { GraphEditorPopover } from "./graph-editor/popover"; import { PopoverTrigger } from "@/components/ui/popover"; import { useGraphEditorController } from "./graph-editor/use-controller"; +import { useKeyboardShortcutsHelp } from "@/hooks/use-keyboard-shortcuts-help"; export function TimelineToolbar({ zoomLevel, @@ -95,6 +96,13 @@ function ToolbarLeftSection() { const isCurrentlyBookmarked = useEditor((e) => e.scenes.isBookmarked({ time: e.playback.getCurrentTime() }), ); + const { shortcuts } = useKeyboardShortcutsHelp(); + const shortcutMap = Object.fromEntries( + shortcuts.map(s => [s.action, s]) + ); + + + const selectedElement = selectedElements.length === 1 ? (editor.timeline.getElementsWithTracks({ @@ -128,6 +136,80 @@ function ToolbarLeftSection() { element: selectedElement.element, }); + const ToolTipShortcuts = { + "split": { + icon: , + tooltip: `Split element (${shortcutMap["split"]?.keys[0]})`, + disabled: false, + onClick: ({ event }) => + handleAction({ action: "split", event }), + }, + + "split-left": { + icon: , + tooltip: `Split left (${shortcutMap["split-left"]?.keys[0]})`, + disabled: false, + onClick: ({ event }) => + handleAction({ action: "split-left", event }), + }, + + "split-right": { + icon: , + tooltip: `Split right (${shortcutMap["split-right"]?.keys[0]})`, + disabled: false, + onClick: ({ event }) => + handleAction({ action: "split-right", event }), + }, + + "toggle-source-audio": { + icon: ( + + ), + tooltip: sourceAudioLabel, + disabled: !canToggleSelectedSourceAudio, + onClick: ({ event }) => + handleAction({ + action: "toggle-source-audio", + event + }), + }, + + "duplicate-selected": { + icon: , + tooltip: "Duplicate element", + disabled: false, + onClick: ({ event }) => + handleAction({ + action: "duplicate-selected", + event + }), + }, + + "freeze-frame": { + icon: , + tooltip: "Freeze frame (coming soon)", + disabled: true, + onClick: ({ event: _event }) => {}, + }, + + "delete-selected": { + icon: , + tooltip: `Delete element (${shortcutMap["delete-selected"]?.keys.join(" / ")})`, + disabled: false, + onClick: ({ event }) => + handleAction({ + action: "delete-selected", + event + }), + }, + }; + const handleAction = ({ action, event, @@ -142,61 +224,14 @@ function ToolbarLeftSection() { return (
- } - tooltip="Split element" - onClick={({ event }) => handleAction({ action: "split", event })} - /> - - } - tooltip="Split left" - onClick={({ event }) => handleAction({ action: "split-left", event })} - /> - - } - tooltip="Split right" - onClick={({ event }) => - handleAction({ action: "split-right", event }) - } - /> - - ( + - } - tooltip={sourceAudioLabel} - disabled={!canToggleSelectedSourceAudio} - onClick={({ event }) => - handleAction({ action: "toggle-source-audio", event }) - } - /> - - } - tooltip="Duplicate element" - onClick={({ event }) => - handleAction({ action: "duplicate-selected", event }) - } - /> - - } - tooltip="Freeze frame (coming soon)" - disabled={true} - onClick={({ event: _event }) => {}} - /> - - } - tooltip="Delete element" - onClick={({ event }) => - handleAction({ action: "delete-selected", event }) - } - /> + ))}
diff --git a/bun.lock b/bun.lock index acb2f6e81..666822033 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "@types/react-dom": "^19.2.3", "better-auth": "^1.4.15", "next": "^16.1.3", + "opencut-wasm": "^0.2.5", }, "devDependencies": { "turbo": "^2.8.20", @@ -54,7 +55,7 @@ "nanoid": "^5.1.5", "next": "16.1.3", "next-themes": "^0.4.4", - "opencut-wasm": "file:../../rust/wasm/pkg", + "opencut-wasm": "^0.2.5", "pg": "^8.16.2", "postgres": "^3.4.5", "radix-ui": "^1.4.3", @@ -785,7 +786,7 @@ "@turbo/windows-arm64": ["@turbo/windows-arm64@2.8.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-voicVULvUV5yaGXo0Iue13BcHGYW3u0VgqSbfQwBaHbpj1zLjYV4KIe+7fYIo6DO8FVUJzxFps3ODCQG/Wy2Qw=="], - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + "@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], "@types/culori": ["@types/culori@4.0.1", "", {}, "sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ=="], @@ -875,7 +876,7 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -1359,6 +1360,8 @@ "onnxruntime-web": ["onnxruntime-web@1.22.0-dev.20250409-89f8206ba4", "", { "dependencies": { "flatbuffers": "^25.1.24", "guid-typescript": "^1.0.9", "long": "^5.2.3", "onnxruntime-common": "1.22.0-dev.20250409-89f8206ba4", "platform": "^1.3.6", "protobufjs": "^7.2.4" } }, "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ=="], + "opencut-wasm": ["opencut-wasm@0.2.5", "", {}, "sha512-tgUeCesdt6DcB2ILzDkAJR09/FL8XeXntWVioudg1PYO0zopsBkMj9yq8boxAMQPrljlVZwSL8dTF2Px0Dn/NA=="], + "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], @@ -1721,8 +1724,6 @@ "@node-minify/core/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - "@opencut/web/opencut-wasm": ["opencut-wasm@file:rust/wasm/pkg", {}], - "@opencut/web/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "@opennextjs/aws/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], diff --git a/package.json b/package.json index 22504650f..4c40532ea 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "@types/react": "^19.2.10", "@types/react-dom": "^19.2.3", "better-auth": "^1.4.15", - "next": "^16.1.3" + "next": "^16.1.3", + "opencut-wasm": "^0.2.5" }, "devDependencies": { "turbo": "^2.8.20", From 9c84e55e01d1d23e93e96bdc74287d3ea599fb0e Mon Sep 17 00:00:00 2001 From: Junaid Khan Date: Sat, 18 Apr 2026 21:11:27 -0400 Subject: [PATCH 2/3] altered shortcuts to kbd tag format --- .../editor/panels/preview/toolbar.tsx | 38 +++- .../panels/timeline/timeline-toolbar.tsx | 168 +++++++++++------- apps/web/src/components/ui/kbd.tsx | 10 ++ 3 files changed, 138 insertions(+), 78 deletions(-) create mode 100644 apps/web/src/components/ui/kbd.tsx diff --git a/apps/web/src/components/editor/panels/preview/toolbar.tsx b/apps/web/src/components/editor/panels/preview/toolbar.tsx index 9ee6c822a..b3bc649b9 100644 --- a/apps/web/src/components/editor/panels/preview/toolbar.tsx +++ b/apps/web/src/components/editor/panels/preview/toolbar.tsx @@ -26,7 +26,9 @@ import { PREVIEW_ZOOM_PRESETS } from "@/lib/preview/zoom"; import { usePreviewViewport } from "./preview-viewport"; import { GridPopover } from "./guide-popover"; import { usePreviewStore } from "@/stores/preview-store"; - +import { useKeyboardShortcutsHelp } from "@/hooks/use-keyboard-shortcuts-help"; +import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; +import { Kbd } from "@/components/ui/kbd"; export function PreviewToolbar({ onToggleFullscreen, }: { @@ -135,14 +137,32 @@ function ZoomSelect() { function PlayPauseButton() { const isPlaying = useEditor((e) => e.playback.getIsPlaying()); - + const { shortcuts } = useKeyboardShortcutsHelp(); + const togglePlay = shortcuts.find( + item => item.action === "toggle-play" + ); return ( - + + + + + + + + {togglePlay?.description} +
+ + {togglePlay?.keys?.join(" / ")} + +
+
+
+
); } diff --git a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx b/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx index e34821812..0362c3305 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx +++ b/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx @@ -50,7 +50,7 @@ import { GraphEditorPopover } from "./graph-editor/popover"; import { PopoverTrigger } from "@/components/ui/popover"; import { useGraphEditorController } from "./graph-editor/use-controller"; import { useKeyboardShortcutsHelp } from "@/hooks/use-keyboard-shortcuts-help"; - +import { Kbd } from "@/components/ui/kbd"; export function TimelineToolbar({ zoomLevel, minZoom, @@ -100,7 +100,8 @@ function ToolbarLeftSection() { const shortcutMap = Object.fromEntries( shortcuts.map(s => [s.action, s]) ); - + console.log(shortcutMap); + const selectedElement = @@ -137,78 +138,107 @@ function ToolbarLeftSection() { }); const ToolTipShortcuts = { - "split": { - icon: , - tooltip: `Split element (${shortcutMap["split"]?.keys[0]})`, - disabled: false, - onClick: ({ event }) => - handleAction({ action: "split", event }), - }, + split: { + icon: , + tooltip: ( +
+ Split element + {shortcutMap["split"]?.keys?.[0]} +
+ ), + disabled: false, + onClick: ({ event }) => + handleAction({ action: "split", event }), + }, - "split-left": { - icon: , - tooltip: `Split left (${shortcutMap["split-left"]?.keys[0]})`, - disabled: false, - onClick: ({ event }) => - handleAction({ action: "split-left", event }), - }, + "split-left": { + icon: , + tooltip: ( +
+ Split left + {shortcutMap["split-left"]?.keys?.[0]} +
+ ), + disabled: false, + onClick: ({ event }) => + handleAction({ action: "split-left", event }), + }, - "split-right": { - icon: , - tooltip: `Split right (${shortcutMap["split-right"]?.keys[0]})`, - disabled: false, - onClick: ({ event }) => - handleAction({ action: "split-right", event }), - }, + "split-right": { + icon: , + tooltip: ( +
+ Split right + {shortcutMap["split-right"]?.keys?.[0]} +
+ ), + disabled: false, + onClick: ({ event }) => + handleAction({ action: "split-right", event }), + }, - "toggle-source-audio": { - icon: ( - - ), - tooltip: sourceAudioLabel, - disabled: !canToggleSelectedSourceAudio, - onClick: ({ event }) => - handleAction({ - action: "toggle-source-audio", - event - }), - }, + "toggle-source-audio": { + icon: ( + + ), + tooltip: sourceAudioLabel, + disabled: !canToggleSelectedSourceAudio, + onClick: ({ event }) => + handleAction({ + action: "toggle-source-audio", + event, + }), + }, - "duplicate-selected": { - icon: , - tooltip: "Duplicate element", - disabled: false, - onClick: ({ event }) => - handleAction({ - action: "duplicate-selected", - event - }), - }, + "duplicate-selected": { + icon: , + tooltip: ( +
+ Duplicate element + + {shortcutMap["duplicate-selected"]?.keys?.[0]} + +
+ ), + disabled: false, + onClick: ({ event }) => + handleAction({ + action: "duplicate-selected", + event, + }), + }, - "freeze-frame": { - icon: , - tooltip: "Freeze frame (coming soon)", - disabled: true, - onClick: ({ event: _event }) => {}, - }, + "freeze-frame": { + icon: , + tooltip: "Freeze frame (coming soon)", + disabled: true, + onClick: () => {}, + }, - "delete-selected": { - icon: , - tooltip: `Delete element (${shortcutMap["delete-selected"]?.keys.join(" / ")})`, - disabled: false, - onClick: ({ event }) => - handleAction({ - action: "delete-selected", - event - }), - }, - }; + "delete-selected": { + icon: , + tooltip: ( +
+ Delete element + + {shortcutMap["delete-selected"]?.keys?.join(" / ")} + +
+ ), + disabled: false, + onClick: ({ event }) => + handleAction({ + action: "delete-selected", + event, + }), + }, +}; const handleAction = ({ action, @@ -374,7 +404,7 @@ function ToolbarButton({ buttonWrapper, }: { icon: React.ReactNode; - tooltip: string; + tooltip: React.ReactNode | string; onClick?: ({ event }: { event: React.MouseEvent }) => void; disabled?: boolean; isActive?: boolean; diff --git a/apps/web/src/components/ui/kbd.tsx b/apps/web/src/components/ui/kbd.tsx new file mode 100644 index 000000000..2f83e804f --- /dev/null +++ b/apps/web/src/components/ui/kbd.tsx @@ -0,0 +1,10 @@ +"use client" +import * as React from "react"; + +export const Kbd = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); +} \ No newline at end of file From 30ae63bdf0b3f9995987d8717d6eece679ecb7b8 Mon Sep 17 00:00:00 2001 From: Junaid Khan Date: Sat, 18 Apr 2026 21:29:53 -0400 Subject: [PATCH 3/3] added cn to join classNames in kbd component --- .../editor/panels/timeline/timeline-toolbar.tsx | 4 +--- apps/web/src/components/ui/kbd.tsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx b/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx index 0362c3305..6db57bbee 100644 --- a/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx +++ b/apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx @@ -99,9 +99,7 @@ function ToolbarLeftSection() { const { shortcuts } = useKeyboardShortcutsHelp(); const shortcutMap = Object.fromEntries( shortcuts.map(s => [s.action, s]) - ); - console.log(shortcutMap); - + ); const selectedElement = diff --git a/apps/web/src/components/ui/kbd.tsx b/apps/web/src/components/ui/kbd.tsx index 2f83e804f..77a4052d8 100644 --- a/apps/web/src/components/ui/kbd.tsx +++ b/apps/web/src/components/ui/kbd.tsx @@ -1,9 +1,18 @@ "use client" import * as React from "react"; +import { cn } from "@/utils/ui"; +type KbdProps = { + children: React.ReactNode; + className?: string; +} +export const Kbd = ( + { + children, + className -export const Kbd = ({ children }: { children: React.ReactNode }) => { + }: KbdProps) => { return ( - + {children} );