feat: extend rundown shortcuts

This commit is contained in:
Carlos Valente
2026-01-16 16:45:54 +01:00
committed by Carlos Valente
parent d7af73d9ed
commit 967e92e2c8
27 changed files with 726 additions and 307 deletions
-165
View File
@@ -1,165 +0,0 @@
# Implementation Plan: Enhanced Rundown Keyboard Shortcuts
This document outlines the plan to extend the keyboard shortcuts for the Rundown feature, including functionality for Duplicate, Delete, Cut, and improved navigation (Home/End, PageUp/PageDown).
## Files to Modify
1. `apps/client/src/features/rundown/hooks/useRundownCommands.ts`
2. `apps/client/src/features/rundown/hooks/useRundownKeyboard.ts`
## Step 1: Logic Implementation (`useRundownCommands.ts`)
We need to add the underlying logic for the new actions.
### 1. `cloneEntry`
Create a function to clone the currently selected entry.
* **Action**: Use `entryActions.clone`.
* **Logic**:
* If no cursor, return.
* Call `clone(cursor, { after: cursor })`.
### 2. `selectEdge`
Create a function to jump to the top or bottom of the list.
* **Arguments**: `direction: 'top' | 'bottom'`.
* **Logic**:
* Use `getFirstNormal(entries, order)` for `'top'`.
* Use `getLastNormal(entries, order)` for `'bottom'`.
* Call `setSelectedEvents` with the result.
### 3. `selectPage`
Create a function to move selection by a "page" (e.g., 10 items).
* **Arguments**: `cursor: string | null`, `direction: 'up' | 'down'`.
* **Constant**: `PAGE_SIZE = 10`.
* **Logic**:
* Use `getNextNormal` / `getPreviousNormal` in a loop (up to `PAGE_SIZE` times) to find the target entry.
* Call `setSelectedEvents` with the result.
### 4. `cutEntry`
While "Cut" is often a compound action in the keyboard hook (Copy + Delete), implementing a helper here allows for cleaner handling if additional logic is needed later.
* **Logic**:
* Set copy ID (requires access to `entryCopyStore` or passed via props, but current pattern passes `setEntryCopyId` to `useRundownKeyboard` separately).
* *Note*: Since `setEntryCopyId` is separate, we can handle "Cut" composition in `useRundownKeyboard.ts` or add a `cut` command here that combines them if we bring `setEntryCopyId` into commands. *Recommendation: Handle composition in `useRundownKeyboard.ts` to matching existing patterns, or add a specific `cutEntry` if complex.*
**Update Return Interface**: Ensure `selectEdge`, `selectPage`, and `cloneEntry` are returned from the hook.
## Step 2: Keyboard Mapping (`useRundownKeyboard.ts`)
Update the `UseRundownKeyboardOptions` interface and the `useHotkeys` hook configuration.
### 1. Update Interface
Update `UseRundownKeyboardOptions['commands']` to include:
```typescript
interface UseRundownKeyboardOptions {
// ... existing
commands: {
// ... existing
cloneEntry: (cursor: EntryId | null) => void;
selectEdge: (direction: 'top' | 'bottom') => void;
selectPage: (cursor: EntryId | null, direction: 'up' | 'down') => void;
};
// ... existing
}
```
### 2. Add Hotkeys implementation
Add the following mappings to the `useHotkeys` array:
* **Note**: `mod + D` (Clone) does not collide with `alt + D` (Add Delay).
| Action | Shortcut | Handler Logic |
| :--- | :--- | :--- |
| **Clone** | `mod + D` | `commands.cloneEntry(cursor)` |
| **Delete** | `mod + Delete` | `commands.deleteAtCursor(cursor)` |
| **Cut** | `mod + X` | `() => { setEntryCopyId(cursor); commands.deleteAtCursor(cursor); }` |
| **Home** | `Home` | `commands.selectEdge('top')` |
| **End** | `End` | `commands.selectEdge('bottom')` |
| **Page Up** | `PageUp` | `commands.selectPage(cursor, 'up')` |
| **Page Down** | `PageDown` | `commands.selectPage(cursor, 'down')` |
*Note*: Ensure `{ preventDefault: true, usePhysicalKeys: true }` is used for navigation keys to prevent browser scrolling.
## Step 3: UI Updates for Discoverability
To ensure users can discover these new features, we must update the UI to reflect new shortcuts.
### 1. Update Context Menus
Add "Clone" and "Delete" options (or ensure they use the new keyboard shortcuts in their labels) to the context menus of rundown items.
**Files to Modify**:
* `apps/client/src/features/rundown/rundown-event/RundownEvent.tsx`
* `apps/client/src/features/rundown/rundown-group/RundownGroup.tsx`
* `apps/client/src/features/rundown/rundown-milestone/RundownMilestone.tsx`
**Changes**:
* Locate `useContextMenu` implementation.
* Add/Update `Clone` option with shortcut label `Mod+D`.
* Ensure `Delete` option shows correct `Mod/Del` shortcut.
### 2. Update Empty State Shortcuts
Update the shortcut list displayed when no event is selected.
**File to Modify**:
* `apps/client/src/features/rundown/entry-editor/EventEditorEmpty.tsx`
**Changes**:
* Add rows to the shortcut table for:
* **Clone Entry**: `Mod + D`
* **Delete Entry**: `Mod + Delete` / `Delete`
* **Navigation**: `Home`, `End`, `PgUp`, `PgDn` (Group under "Navigation")
## Step 4: UX Review & Interface Improvements
### 1. `EventEditorEmpty` Redesign
The current table-based layout is rigid. We will refactor `apps/client/src/features/rundown/entry-editor/EventEditorEmpty.tsx` to use a sleek CSS Grid layout.
* **Action**: Replace `<table>` with `div` based grid.
* **Visuals**: Use subtle headers for groups (Navigation, Editing, System).
* **Refinement**: Ensure `<Kbd>` components use a flat, minimal design.
### 2. Global Shortcuts Dialog
* **Decision**: Implement a Global Shortcuts Dialog triggered by `?` (Shift + /).
* **Rationale**: Users lose the `EventEditorEmpty` reference once they add content. A persistent dialog ensures "recognition over recall".
### 3. Context Menu Implementation Guide
We will enhance the context menu to display shortcuts inline.
**A. Update Type Definition**
Modify `apps/client/src/common/components/dropdown-menu/DropdownMenu.tsx`:
```typescript
type DropdownMenuItem = {
// ... existing fields
shortcut?: string; // New field
};
```
**B. Update Component Rendering**
In `apps/client/src/common/components/dropdown-menu/DropdownMenu.tsx`, update the render loop to display the shortcut:
```tsx
<BaseMenu.Item className={style.item}>
<span className={style.labelContainer}>
{item.icon && <item.icon />}
{item.label}
</span>
{item.type === 'item' && item.shortcut && (
<span className={style.shortcut}>{item.shortcut}</span>
)}
</BaseMenu.Item>
```
*Note*: Update `DropdownMenu.module.scss` to use `justify-content: space-between` on the item.
**C. Update Usage in `RundownEvent.tsx`**
Add shortcuts to the context menu options:
```tsx
{
type: 'item',
label: 'Clone',
icon: IoDuplicateOutline,
shortcut: `${deviceMod}+D`,
onClick: () => clone(eventId, { after: eventId }),
},
{
type: 'item',
label: 'Delete',
icon: IoTrash,
shortcut: 'Del',
onClick: () => { /* ... */ },
}
```