Skip to content

feat: configurable advanced keyboard shortcuts system#343

Closed
anwarulislam wants to merge 15 commits into
OpenCut-app:stagingfrom
anwarulislam:feat/shortcut-actions
Closed

feat: configurable advanced keyboard shortcuts system#343
anwarulislam wants to merge 15 commits into
OpenCut-app:stagingfrom
anwarulislam:feat/shortcut-actions

Conversation

@anwarulislam
Copy link
Copy Markdown
Contributor

@anwarulislam anwarulislam commented Jul 18, 2025

Keyboard Shortcuts System

This document describes the configurable keyboard shortcuts system in OpenCut.

Overview

The keyboard shortcuts system is built with the following components:

  1. Actions System (/src/constants/actions.ts) - Defines all available actions
  2. Keybindings Store (/src/stores/keybindings-store.ts) - Manages keybinding configuration
  3. Keybindings Handler (/src/hooks/use-keybindings.ts) - Handles keyboard events
  4. Action Handlers (/src/hooks/use-editor-actions.ts) - Implements action behavior
  5. UI Components - For viewing and editing shortcuts

Architecture

Actions System

Actions are the core verbs that can be performed in the editor:

export type Action = "toggle-play" | "seek-forward" | "seek-backward";
// ... more actions

Actions can have optional arguments:

type ActionArgsMap = {
  "seek-forward": { seconds: number } | undefined;
  "seek-backward": { seconds: number } | undefined;
  // ...
};

Keybindings Store

The store manages:

  • Current keybinding configuration
  • Default keybindings
  • Validation and conflict detection
  • Import/export functionality
  • Persistence to storage

Key Features

  1. Configurable Shortcuts: Users can customize any keyboard shortcut
  2. Conflict Detection: Prevents duplicate key assignments
  3. Import/Export: Save and share keybinding configurations
  4. Reset to Defaults: Restore original keybindings
  5. Persistence: Settings are saved across sessions
  6. Real-time Updates: Changes apply immediately

Usage

Basic Usage

  1. View Shortcuts: Click the "Shortcuts" button in the header
  2. Customize: Click "Customize" in the shortcuts dialog
  3. Edit Binding: Click "Edit" next to any shortcut
  4. Record Keys: Click "Record" and press your desired key combination
  5. Save: Click "Save" to apply the new binding

Advanced Features

Export Keybindings

const { exportKeybindings } = useKeybindingsStore();
const config = exportKeybindings();
// Save to file or share

Import Keybindings

const { importKeybindings } = useKeybindingsStore();
importKeybindings(configObject);

Programmatic Updates

const { updateKeybinding, removeKeybinding } = useKeybindingsStore();
updateKeybinding("ctrl-s", "save-project");
removeKeybinding("old-key");

Adding New Actions

  1. Define Action: Add to Action type in actions.ts
  2. Add Arguments (if needed): Update ActionArgsMap
  3. Implement Handler: Add to useEditorActions hook
  4. Add Description: Update actionDescriptions in help hook
  5. Set Default Binding: Add to defaultKeybindings in store

Example:

// 1. In actions.ts
export type Action = "existing-action" | "new-action"; // Add this

// 2. In use-editor-actions.ts
useActionHandler("new-action", () => {
  // Implement action behavior
});

// 3. In keybindings-store.ts
export const defaultKeybindings = {
  // existing bindings...
  "ctrl-n": "new-action", // Add default binding
};

// 4. In use-keyboard-shortcuts-help.ts
const actionDescriptions = {
  // existing descriptions...
  "new-action": { description: "Create new item", category: "File" },
};

Key Format

Keys are represented as strings with modifiers separated by dashes:

  • "a" - Simple key
  • "ctrl-a" - Ctrl+A (Cmd+A on macOS)
  • "ctrl-shift-a" - Ctrl+Shift+A
  • "alt-left" - Alt+Left Arrow

Supported modifiers:

  • ctrl - Control key (Command on macOS)
  • alt - Alt key (Option on macOS)
  • shift - Shift key

Validation

The system includes validation to prevent:

  • Duplicate key assignments
  • Invalid key combinations
  • Conflicts with system shortcuts

Persistence

Keybindings are automatically saved to localStorage with the key opencut-keybindings.

Summary by CodeRabbit

  • New Features

    • Introduced a customizable keyboard shortcut editor with inline editing, filtering, categorized views, and import/export capabilities.
    • Added keybinding conflict detection and validation to prevent shortcut overlaps and provide user feedback.
    • Enhanced keyboard shortcuts help dialog with a "Customize" button to open the new editor.
    • Implemented platform-aware shortcut display and improved keyboard event handling with enable/disable controls.
    • Added typed action management system with React integration for user commands.
    • Added new editor actions for timeline control, editing, and undo/redo operations.
    • Provided utilities for detecting typable elements and platform-specific keys.
  • Bug Fixes

    • Improved handling to prevent keyboard shortcuts from triggering when focus is on input fields.
  • Chores

    • Refactored keyboard shortcut management for better modularity, maintainability, and explicit control of keybinding states.

@netlify
Copy link
Copy Markdown

netlify Bot commented Jul 18, 2025

👷 Deploy request for appcut pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 23001f8

@vercel
Copy link
Copy Markdown

vercel Bot commented Jul 18, 2025

@anwarulislam is attempting to deploy a commit to the OpenCut OSS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 18, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

"""

Walkthrough

This change refactors keyboard shortcut handling in the web application by removing the old useKeyboardShortcuts hook and introducing a modular system with new hooks, a keybindings store, and an action management framework. It adds a customizable keybinding editor UI, conflict detection, platform-aware utilities, and updates the shortcuts help dialog to support editing.

Changes

File(s) Change Summary
apps/web/src/components/editor-provider.tsx Refactored to use new modular hooks (useEditorActions, useKeybindingsListener, useKeybindingDisabler) for keyboard shortcut management and explicit keybinding enable/disable logic during initialization.
apps/web/src/components/keybinding-editor.tsx Added a new KeybindingEditor React component with UI for customizing, validating, importing/exporting, and resetting keyboard shortcuts, including an inline KeyRecorder subcomponent.
apps/web/src/components/keyboard-shortcuts-help.tsx Enhanced the shortcuts help dialog to include a "Customize" button, conditional rendering of the new keybinding editor, updated shortcut retrieval, and improved scrolling.
apps/web/src/constants/actions.ts Introduced a typed action management system with an ActionEmitter, action binding/invocation logic, and React hooks for action handling and state queries.
apps/web/src/hooks/use-editor-actions.ts Added a hook to register editor-related actions (playback, navigation, editing) via the new action system, handling state and UI feedback.
apps/web/src/hooks/use-keybinding-conflicts.ts Added a hook to detect and query conflicts in keybindings, returning structured conflict information and helper functions.
apps/web/src/hooks/use-keybindings.ts Added hooks for listening to keyboard events (useKeybindingsListener), programmatically enabling/disabling keybindings, and an empty bindings export for compatibility.
apps/web/src/hooks/use-keyboard-shortcuts-help.ts Added a hook to provide a formatted, categorized list of keyboard shortcuts for help UI, mapping keybindings to actions and descriptions.
apps/web/src/hooks/use-keyboard-shortcuts.ts Deleted: Removed the old centralized keyboard shortcut management hook and its associated types and logic.
apps/web/src/lib/utils.ts Reformatted generateUUID and added platform/element detection utilities: isDOMElement, isTypableElement, isAppleDevice, getPlatformSpecialKey, getPlatformAlternateKey.
apps/web/src/stores/keybindings-store.ts Added a Zustand store for managing keybindings, including persistence, updating, removal, import/export, conflict validation, and keyboard event parsing utilities.
apps/web/src/types/keybinding.ts Added TypeScript types defining modifier keys, supported keys, shortcut key formats, and keybinding configuration mapping keys to actions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Keyboard
    participant KeybindingsListener
    participant KeybindingsStore
    participant Actions
    participant Editor

    User->>Keyboard: Presses a key combination
    Keyboard->>KeybindingsListener: keydown event
    KeybindingsListener->>KeybindingsStore: Get keybinding for event
    KeybindingsStore-->>KeybindingsListener: Returns mapped action (if any)
    KeybindingsListener->>Actions: invokeAction(action, args)
    Actions->>Editor: Editor action handler executes logic
Loading
sequenceDiagram
    participant User
    participant KeyboardShortcutsHelp
    participant KeybindingEditor
    participant KeybindingsStore

    User->>KeyboardShortcutsHelp: Opens shortcuts help
    KeyboardShortcutsHelp->>User: Shows list of shortcuts
    User->>KeyboardShortcutsHelp: Clicks "Customize"
    KeyboardShortcutsHelp->>KeybindingEditor: Renders editor with shortcuts
    User->>KeybindingEditor: Edits, adds, removes, imports, or exports shortcuts
    KeybindingEditor->>KeybindingsStore: Updates keybindings
    KeybindingsStore-->>KeybindingEditor: Persists and validates changes
Loading

Possibly related PRs

  • OpenCut-app/OpenCut#284: Introduced the original comprehensive keyboard shortcuts system and related UI components, which are now being refactored and modularized in this PR.

Poem

A rabbit hopped on keys one day,
And found the shortcuts led astray—
Now with editors bright and actions neat,
Custom keys make work a treat!
Conflicts resolved, with hops so fleet,
The bunny codes—no need to cheat!
🐇⌨️✨
"""


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@anwarulislam
Copy link
Copy Markdown
Contributor Author

anwarulislam commented Jul 18, 2025

Hey, @mazeincoding!
I couldn't wait to get this going!

So, why actions? And why complicate things a bit?

It's not really complicated; it just makes it super customizable and easy to maintain in the future.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🔭 Outside diff range comments (1)
apps/web/src/components/keyboard-shortcuts-help.tsx (1)

99-167: Refactor to avoid Dialog duplication and improve UX

The current implementation duplicates the Dialog component and could cause the dialog to close/reopen when toggling between views. This creates unnecessary re-renders and poor UX.

Refactor to use a single Dialog wrapper:

-  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>
-    );
-  }

  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-2xl max-h-[80vh] overflow-y-auto">
+      <DialogContent className={showEditor ? "max-w-6xl max-h-[90vh] overflow-y-auto" : "max-w-2xl max-h-[80vh] overflow-y-auto"}>
+        {showEditor ? (
+          <KeybindingEditor
+            shortcuts={shortcuts}
+            onClose={() => setShowEditor(false)}
+          />
+        ) : (
+          <>
            <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>
            // ... rest of the help content
+          </>
+        )}
      </DialogContent>
    </Dialog>
  );
🧹 Nitpick comments (5)
apps/web/src/lib/utils.ts (2)

47-49: Consider simplifying the instanceof check

The function works correctly but could be simplified since HTMLElement extends Element.

export function isDOMElement(el: any): el is HTMLElement {
-  return !!el && (el instanceof Element || el instanceof HTMLElement);
+  return !!el && el instanceof HTMLElement;
}

Or if you need to support all DOM elements (not just HTML):

export function isDOMElement(el: any): el is HTMLElement {
-  return !!el && (el instanceof Element || el instanceof HTMLElement);
+  return !!el && el instanceof Element;
}

51-65: Consider case-insensitive tagName comparison

The function logic is correct, but tagName is always uppercase in the DOM. Consider using uppercase comparisons for consistency.

export function isTypableElement(el: HTMLElement): boolean {
  // If content editable, then it is editable
  if (el.isContentEditable) return true;

  // If element is an input and the input is enabled, then it is typable
-  if (el.tagName === "INPUT") {
+  if (el.tagName === "INPUT") {
    return !(el as HTMLInputElement).disabled;
  }
  // If element is a textarea and the input is enabled, then it is typable
-  if (el.tagName === "TEXTAREA") {
+  if (el.tagName === "TEXTAREA") {
    return !(el as HTMLTextAreaElement).disabled;
  }

  return false;
}

Actually, the current implementation is fine since tagName is always uppercase in browsers. The function correctly identifies typable elements.

apps/web/src/hooks/use-keybindings.ts (1)

75-76: Consider proper backward compatibility or removal

The empty bindings export doesn't provide meaningful backward compatibility. Code relying on the old bindings will fail silently.

Either implement a proper compatibility layer:

// Provide a compatibility layer that maps to the new system
export const bindings = {
  get: () => {
    console.warn('bindings.get() is deprecated. Use useKeybindingsStore instead.');
    return useKeybindingsStore.getState().keybindings;
  }
};

Or remove it entirely to fail fast and help developers migrate:

-// Export the bindings for backward compatibility
-export const bindings = {};
apps/web/src/components/keybinding-editor.tsx (1)

255-271: Use React ref instead of getElementById

Using getElementById is not idiomatic React. Use a ref for better integration and type safety.

+  const fileInputRef = useRef<HTMLInputElement>(null);

   // ... in the JSX:
   <input
     type="file"
     accept=".json"
     onChange={handleImportKeybindings}
     className="hidden"
-    id="import-keybindings"
+    ref={fileInputRef}
   />
   <Button
     variant="outline"
     size="sm"
-    onClick={() =>
-      document.getElementById("import-keybindings")?.click()
-    }
+    onClick={() => fileInputRef.current?.click()}
   >
apps/web/src/constants/actions.ts (1)

14-27: Consider using Set for listener management

The current array-based implementation works but could be optimized for better performance with many listeners.

 class ActionEmitter {
-  private listeners: Array<(actions: Action[]) => void> = [];
+  private listeners = new Set<(actions: Action[]) => void>();

   subscribe(listener: (actions: Action[]) => void) {
-    this.listeners.push(listener);
+    this.listeners.add(listener);
     return () => {
-      this.listeners = this.listeners.filter((l) => l !== listener);
+      this.listeners.delete(listener);
     };
   }

   emit(actions: Action[]) {
-    this.listeners.forEach((listener) => listener(actions));
+    this.listeners.forEach((listener) => listener(actions));
   }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1b2168 and cdae10c.

📒 Files selected for processing (11)
  • apps/web/src/components/editor-provider.tsx (1 hunks)
  • apps/web/src/components/keybinding-editor.tsx (1 hunks)
  • apps/web/src/components/keyboard-shortcuts-help.tsx (5 hunks)
  • apps/web/src/constants/actions.ts (1 hunks)
  • apps/web/src/hooks/use-editor-actions.ts (1 hunks)
  • apps/web/src/hooks/use-keybinding-conflicts.ts (1 hunks)
  • apps/web/src/hooks/use-keybindings.ts (1 hunks)
  • apps/web/src/hooks/use-keyboard-shortcuts-help.ts (1 hunks)
  • apps/web/src/hooks/use-keyboard-shortcuts.ts (0 hunks)
  • apps/web/src/lib/utils.ts (2 hunks)
  • apps/web/src/stores/keybindings-store.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/src/hooks/use-keyboard-shortcuts.ts
🧰 Additional context used
🧠 Learnings (1)
apps/web/src/hooks/use-editor-actions.ts (1)
Learnt from: simonorzel26
PR: OpenCut-app/OpenCut#324
File: apps/web/src/components/editor/snap-indicator.tsx:43-43
Timestamp: 2025-07-17T08:26:10.929Z
Learning: In the timeline refactor PR #324, the snap indicator component in apps/web/src/components/editor/snap-indicator.tsx requires the hard-coded `ml-48` class in addition to the calculated `leftPosition` for proper alignment. This is intentional and needed for the new timeline layout.
🧬 Code Graph Analysis (4)
apps/web/src/components/editor-provider.tsx (3)
apps/web/src/stores/editor-store.ts (1)
  • useEditorStore (67-105)
apps/web/src/hooks/use-keybindings.ts (2)
  • useKeybindingDisabler (60-73)
  • useKeybindingsListener (17-55)
apps/web/src/hooks/use-editor-actions.ts (1)
  • useEditorActions (10-162)
apps/web/src/hooks/use-keybinding-conflicts.ts (2)
apps/web/src/constants/actions.ts (1)
  • ActionWithOptionalArgs (81-83)
apps/web/src/stores/keybindings-store.ts (1)
  • useKeybindingsStore (65-144)
apps/web/src/stores/keybindings-store.ts (2)
apps/web/src/constants/actions.ts (1)
  • ActionWithOptionalArgs (81-83)
apps/web/src/lib/utils.ts (3)
  • isDOMElement (47-49)
  • isTypableElement (51-65)
  • isAppleDevice (67-69)
apps/web/src/components/keyboard-shortcuts-help.tsx (4)
apps/web/src/hooks/use-keyboard-shortcuts-help.ts (2)
  • KeyboardShortcut (7-14)
  • useKeyboardShortcutsHelp (85-123)
apps/web/src/components/ui/dialog.tsx (3)
  • Dialog (123-123)
  • DialogTrigger (126-126)
  • DialogContent (128-128)
apps/web/src/components/ui/button.tsx (1)
  • Button (59-59)
apps/web/src/components/keybinding-editor.tsx (1)
  • KeybindingEditor (116-414)
🔇 Additional comments (22)
apps/web/src/lib/utils.ts (4)

16-19: LGTM - Improved readability

The multiline formatting of the condition improves code readability without changing the logic.


32-45: LGTM - Consistent formatting

The reformatted UUID string construction maintains the same logic while using consistent double quotes and clear concatenation structure.


71-77: LGTM - Platform-specific key utilities

Both functions correctly return platform-appropriate key symbols for displaying keyboard shortcuts in the UI.


67-69: Consider migrating from deprecated navigator.platform

While the current implementation correctly detects Apple devices, note that navigator.platform is deprecated (still supported broadly but discouraged). When support for navigator.userAgentData improves—today available in Chrome, Edge, and Opera but not in Firefox or Safari (including iOS)—update this function to prefer navigator.userAgentData.platform with a fallback to navigator.platform.

• File: apps/web/src/lib/utils.ts Lines 67–69

Suggested future pattern:

export function isAppleDevice() {
  if ('userAgentData' in navigator && navigator.userAgentData.platform) {
    return /^(macOS|iOS)$/i.test(navigator.userAgentData.platform);
  }
  return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
}
apps/web/src/components/editor-provider.tsx (2)

6-24: LGTM - Well-structured modular approach

The refactoring successfully separates concerns by splitting keyboard shortcut handling into distinct responsibilities: action handling, event listening, and enable/disable control. This improves maintainability and testability.


27-33: LGTM - Proper keybinding lifecycle management

The useEffect correctly manages keybinding state based on editor readiness, preventing keyboard shortcuts from interfering during initialization. The dependency array is complete and the logic aligns with the loading state conditions.

apps/web/src/hooks/use-editor-actions.ts (7)

28-77: LGTM - Comprehensive playback controls

All playback actions are implemented correctly with proper bounds checking, fallback values, and appropriate state management. The frame stepping logic correctly uses project FPS with a sensible default.


79-101: LGTM - Robust split element implementation

The split element action correctly validates selection count, calculates effective element boundaries considering trim values, and ensures the playhead is positioned within the element before splitting. Error handling provides clear user feedback.


103-114: LGTM - Clean delete implementation

The delete action properly validates selection, removes all selected elements, and clears the selection state. The implementation handles the typical delete workflow correctly.


116-124: LGTM - Correct select all implementation

The select all action properly flattens all track elements and maps them to the required selection format. The use of flatMap is appropriate for this nested structure.


126-148: LGTM - Well-designed duplicate functionality

The duplicate action correctly validates single selection, calculates appropriate positioning with a small gap to prevent overlap, and properly handles element ID generation for the new element. The implementation follows good practices for element duplication.


150-152: LGTM - Simple toggle implementation

The snapping toggle correctly delegates to the timeline store method.


154-162: LGTM - Proper history action delegation

Both undo and redo actions correctly delegate to the timeline store methods for history management.

apps/web/src/hooks/use-keyboard-shortcuts-help.ts (2)

85-123: LGTM - Well-structured hook implementation

The hook correctly transforms keybindings store data into a user-friendly format with proper memoization and grouping logic. The implementation efficiently handles the conversion from key-to-action mapping to action-to-keys grouping.


17-65: Double-check completeness of keyboard shortcut help mapping

The last automated check didn’t produce any output to confirm exact coverage. Please manually verify that the actionDescriptions object in
apps/web/src/hooks/use-keyboard-shortcuts-help.ts
includes every action string exported in
apps/web/src/constants/actions.ts

  • Open the Action union in apps/web/src/constants/actions.ts
  • Confirm each action value appears as a key in actionDescriptions
apps/web/src/hooks/use-keybinding-conflicts.ts (2)

16-39: LGTM - Efficient conflict detection algorithm

The conflict detection correctly groups actions by key, identifies unique actions using Set deduplication, and properly filters to return only actual conflicts. The logic is sound and efficient.


41-59: LGTM - Well-designed conflict query API

The helper functions provide intuitive ways to query conflicts by key or action. The return object gives consumers all necessary conflict information with a clean, discoverable API.

apps/web/src/components/keyboard-shortcuts-help.tsx (1)

41-41: Good use of typed interface

The change to use the KeyboardShortcut interface improves type safety.

apps/web/src/components/keybinding-editor.tsx (1)

143-152: Well-implemented search and filter logic

The search and category filtering is cleanly implemented with proper case-insensitive matching.

apps/web/src/constants/actions.ts (2)

61-95: Excellent type safety implementation

The type system effectively handles the complexity of actions with different argument requirements while maintaining strong type safety.


108-116: Clean global state management

The global state management is well-implemented with proper encapsulation and notification mechanisms.

apps/web/src/stores/keybindings-store.ts (1)

209-223: Good platform-specific key handling

The modifier key detection correctly handles platform differences between Apple (meta key) and other platforms (ctrl key).

Comment thread apps/web/src/hooks/use-keyboard-shortcuts-help.ts
Comment thread apps/web/src/hooks/use-keybindings.ts
Comment thread apps/web/src/hooks/use-keybindings.ts Outdated
Comment thread apps/web/src/components/keybinding-editor.tsx
Comment thread apps/web/src/components/keybinding-editor.tsx
Comment thread apps/web/src/constants/actions.ts
Comment thread apps/web/src/stores/keybindings-store.ts
Comment thread apps/web/src/stores/keybindings-store.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/web/src/components/keybinding-editor.tsx (1)

52-59: System shortcuts prevention issue persists.

The handleKeyDown function still calls e.preventDefault() unconditionally, which could interfere with critical system or browser shortcuts like Ctrl+R, Ctrl+W, etc.

🧹 Nitpick comments (1)
apps/web/src/components/keybinding-editor.tsx (1)

217-245: Import validation has been properly implemented.

The import functionality now includes comprehensive validation for config structure and individual keybindings, addressing previous security concerns.

Consider improving the error message display:

-        toast.error(`Failed to import keybindings: ${error}`);
+        toast.error(`Failed to import keybindings: ${error instanceof Error ? error.message : String(error)}`);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdae10c and c3caefc.

📒 Files selected for processing (4)
  • apps/web/src/components/keybinding-editor.tsx (1 hunks)
  • apps/web/src/constants/actions.ts (1 hunks)
  • apps/web/src/hooks/use-keybindings.ts (1 hunks)
  • apps/web/src/stores/keybindings-store.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/hooks/use-keybindings.ts
  • apps/web/src/constants/actions.ts
🔇 Additional comments (13)
apps/web/src/components/keybinding-editor.tsx (7)

1-42: Well-structured imports and type definitions.

The imports are organized logically and the TypeScript interfaces are clean and well-defined.


44-51: Well-implemented key recording functionality.

The KeyRecorder component has solid state management and provides clear user feedback during the recording process.

Also applies to: 60-114


116-141: Clean component setup and state management.

The component properly integrates with the keybindings store and manages local state effectively.


143-152: Effective filtering and search implementation.

The search and category filtering logic is well-implemented with appropriate case-insensitive matching.


154-201: Robust event handling with proper validation.

The shortcut editing handlers include appropriate conflict validation, proper state management, and good user feedback through toast notifications.


203-216: Clean export functionality implementation.

The export feature properly creates a downloadable JSON file with formatted keybindings data.


247-425: Comprehensive and well-structured UI implementation.

The component provides an excellent user experience with intuitive editing controls, proper visual feedback, search/filtering capabilities, and appropriate confirmation dialogs for destructive actions.

apps/web/src/stores/keybindings-store.ts (6)

1-41: Well-defined types and comprehensive default keybindings.

The type definitions are clean and the default keybindings provide a solid foundation with commonly expected shortcuts.


43-66: Comprehensive and well-organized store interface.

The interface provides all necessary methods for keybinding management with clear categorization and typing.


114-126: Import validation has been properly implemented.

The importKeybindings method now includes validation to ensure data integrity, addressing the previous review concern.


68-113: Solid store implementation with proper state management.

The Zustand store is well-implemented with immutable updates, proper persistence configuration, and comprehensive method coverage.

Also applies to: 127-163


207-208: Space key handling has been properly implemented.

The getPressedKey function now correctly handles the space key, addressing the previous review concern.


165-206: Comprehensive and well-implemented utility functions.

The keybinding generation logic properly handles modifier detection, platform differences, and input context awareness. The key detection covers all necessary key types with appropriate validation.

Also applies to: 209-243

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/web/src/stores/keybindings-store.ts (2)

109-121: Validation improvements implemented, but could be more comprehensive.

The validation logic addresses the previous review concern by checking key format. However, the validation could be enhanced to also verify that actions are valid.

Consider adding action validation:

      importKeybindings: (config: KeybindingConfig) => {
        // Validate all keys and actions
        for (const [key, action] of Object.entries(config)) {
          // Validate the key format
          if (typeof key !== "string" || key.length === 0) {
            throw new Error(`Invalid key format: ${key}`);
          }
+         
+         // Validate action is defined
+         if (!action || typeof action !== "string") {
+           throw new Error(`Invalid action for key ${key}: ${action}`);
+         }
        }
        set({
          keybindings: { ...config },
          isCustomized: true,
        });
      },

220-221: Consider expanding special key support.

The current implementation handles common keys well, but could benefit from supporting additional special keys that might be useful in a video editor context.

Consider adding support for additional keys:

  // Check if slash, period or enter
  if (key === "/" || key === "." || key === "enter") return key;
+ 
+ // Add support for additional special keys
+ if (key === "escape" || key === "pageup" || key === "pagedown") return key;
+ if (key === "f1" || key === "f2" || key === "f3" || key === "f4" || 
+     key === "f5" || key === "f6" || key === "f7" || key === "f8" ||
+     key === "f9" || key === "f10" || key === "f11" || key === "f12") return key;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3caefc and 23001f8.

📒 Files selected for processing (2)
  • apps/web/src/stores/keybindings-store.ts (1 hunks)
  • apps/web/src/types/keybinding.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/web/src/types/keybinding.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/src/stores/keybindings-store.ts (3)
apps/web/src/types/keybinding.ts (2)
  • KeybindingConfig (33-35)
  • ShortcutKey (31-31)
apps/web/src/constants/actions.ts (1)
  • ActionWithOptionalArgs (81-83)
apps/web/src/lib/utils.ts (3)
  • isDOMElement (47-49)
  • isTypableElement (51-65)
  • isAppleDevice (67-69)
🔇 Additional comments (5)
apps/web/src/stores/keybindings-store.ts (5)

9-30: Well-structured default keybindings configuration.

The default keybindings provide comprehensive coverage for video editing actions with good platform considerations (ctrl+z/ctrl+y for undo/redo). The configuration is clean and follows consistent naming conventions.


32-61: Comprehensive and well-designed interfaces.

The interfaces provide a clean separation of concerns with proper TypeScript typing. The KeybindingConflict interface and the categorized methods in KeybindingsState demonstrate good architectural planning.


195-225: Space key handling successfully implemented.

The space key handling issue from the previous review has been properly addressed. The function now correctly handles space key events.


227-241: Excellent platform-aware modifier key handling.

The modifier key logic correctly handles platform differences between Apple devices (meta key) and other platforms (ctrl key). The combination logic for multiple modifiers is well-implemented.


1-241: Excellent implementation of the keybindings store system.

This implementation successfully delivers a comprehensive keyboard shortcuts management system as outlined in the PR objectives. The code demonstrates:

  • Proper separation of concerns with validation, conflict detection, and utility methods
  • Good TypeScript typing and Zustand patterns
  • Platform-aware keyboard event handling
  • Proper persistence configuration
  • Resolution of previous review concerns

The store provides a solid foundation for the customizable keyboard shortcuts system in OpenCut.

@mazeincoding mazeincoding changed the base branch from main to staging July 18, 2025 09:30
@anwarulislam
Copy link
Copy Markdown
Contributor Author

Hey @mazeincoding, I'm still making some tweaks. Don’t merge it just yet. I’ll sort out the conflicts!

@mazeincoding
Copy link
Copy Markdown
Collaborator

oh i was actually making a ton of changes, hmm

@mazeincoding
Copy link
Copy Markdown
Collaborator

i had also solved the merge conflicts ^

@anwarulislam anwarulislam force-pushed the feat/shortcut-actions branch from 367a7f9 to e3d935e Compare July 18, 2025 09:52
@anwarulislam
Copy link
Copy Markdown
Contributor Author

anwarulislam commented Jul 18, 2025

@mazeincoding, good to go from my side. But still I see conflict issues. You can pull latest changes.

@anwarulislam anwarulislam deleted the feat/shortcut-actions branch July 20, 2025 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants