Skip to content

Commit a2e109b

Browse files
PfannkuchensackblessedcoolantCopilotdunkeronilstein
authored
feat(ui): improve hotkey customization UX with interactive controls and validation (#8649)
* feat: remove the ModelFooter in the ModelView and add the Delete Model Button from the Footer into the View * forget to run pnpm fix * chore(ui): reorder the model view buttons * Initial plan * Add customizable hotkeys infrastructure with UI Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * Fix ESLint issues in HotkeyEditor component Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * Fix knip unused export warning Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * Add tests for hotkeys slice Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * Fix tests to actually call reducer and add documentation Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> * docs: add comprehensive hotkeys system documentation - Created new HOTKEYS.md technical documentation for developers explaining architecture, data flow, and implementation details - Added user-facing hotkeys.md guide with features overview and usage instructions - Removed old CUSTOMIZABLE_HOTKEYS.md in favor of new split documentation - Expanded documentation with detailed sections on: - State management and persistence - Component architecture and responsibilities - Developer integration * Behavior changed to hotkey press instead of input + checking for allready used hotkeys --------- Co-authored-by: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dunkeroni <3298737+dunkeroni@users.noreply.github.com> Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
1 parent 5642099 commit a2e109b

File tree

12 files changed

+997
-24
lines changed

12 files changed

+997
-24
lines changed

docs/contributing/HOTKEYS.md

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
# Hotkeys System
2+
3+
This document describes the technical implementation of the customizable hotkeys system in InvokeAI.
4+
5+
> **Note:** For user-facing documentation on how to use customizable hotkeys, see [Hotkeys Feature Documentation](../features/hotkeys.md).
6+
7+
## Overview
8+
9+
The hotkeys system allows users to customize keyboard shortcuts throughout the application. All hotkeys are:
10+
- Centrally defined and managed
11+
- Customizable by users
12+
- Persisted across sessions
13+
- Type-safe and validated
14+
15+
## Architecture
16+
17+
The customizable hotkeys feature is built on top of the existing hotkey system with the following components:
18+
19+
### 1. Hotkeys State Slice (`hotkeysSlice.ts`)
20+
21+
Location: `invokeai/frontend/web/src/features/system/store/hotkeysSlice.ts`
22+
23+
**Responsibilities:**
24+
- Stores custom hotkey mappings in Redux state
25+
- Persisted to IndexedDB using `redux-remember`
26+
- Provides actions to change, reset individual, or reset all hotkeys
27+
28+
**State Shape:**
29+
```typescript
30+
{
31+
_version: 1,
32+
customHotkeys: {
33+
'app.invoke': ['mod+enter'],
34+
'canvas.undo': ['mod+z'],
35+
// ...
36+
}
37+
}
38+
```
39+
40+
**Actions:**
41+
- `hotkeyChanged(id, hotkeys)` - Update a single hotkey
42+
- `hotkeyReset(id)` - Reset a single hotkey to default
43+
- `allHotkeysReset()` - Reset all hotkeys to defaults
44+
45+
### 2. useHotkeyData Hook (`useHotkeyData.ts`)
46+
47+
Location: `invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts`
48+
49+
**Responsibilities:**
50+
- Defines all default hotkeys
51+
- Merges default hotkeys with custom hotkeys from the store
52+
- Returns the effective hotkeys that should be used throughout the app
53+
- Provides platform-specific key translations (Ctrl/Cmd, Alt/Option)
54+
55+
**Key Functions:**
56+
- `useHotkeyData()` - Returns all hotkeys organized by category
57+
- `useRegisteredHotkeys()` - Hook to register a hotkey in a component
58+
59+
### 3. HotkeyEditor Component (`HotkeyEditor.tsx`)
60+
61+
Location: `invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeyEditor.tsx`
62+
63+
**Features:**
64+
- Inline editor with input field
65+
- Modifier buttons (Mod, Ctrl, Shift, Alt) for quick insertion
66+
- Live preview of hotkey combinations
67+
- Validation with visual feedback
68+
- Help tooltip with syntax examples
69+
- Save/cancel/reset buttons
70+
71+
**Smart Features:**
72+
- Automatic `+` insertion between modifiers
73+
- Cursor position preservation
74+
- Validation prevents invalid combinations (e.g., modifier-only keys)
75+
76+
### 4. HotkeysModal Component (`HotkeysModal.tsx`)
77+
78+
Location: `invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx`
79+
80+
**Features:**
81+
- View Mode / Edit Mode toggle
82+
- Search functionality
83+
- Category-based organization
84+
- Shows HotkeyEditor components when in edit mode
85+
- "Reset All to Default" button in edit mode
86+
87+
## Data Flow
88+
89+
```
90+
┌─────────────────────────────────────────────────────────────┐
91+
│ 1. User opens Hotkeys Modal │
92+
│ 2. User clicks "Edit Mode" button │
93+
│ 3. User clicks edit icon next to a hotkey │
94+
│ 4. User enters new hotkey(s) using editor │
95+
│ 5. User clicks save or presses Enter │
96+
│ 6. Custom hotkey stored via hotkeyChanged() action │
97+
│ 7. Redux state persisted to IndexedDB (redux-remember) │
98+
│ 8. useHotkeyData() hook picks up the change │
99+
│ 9. All components using useRegisteredHotkeys() get update │
100+
└─────────────────────────────────────────────────────────────┘
101+
```
102+
103+
## Hotkey Format
104+
105+
Hotkeys use the format from `react-hotkeys-hook` library:
106+
107+
- **Modifiers:** `mod`, `ctrl`, `shift`, `alt`, `meta`
108+
- **Keys:** Letters, numbers, function keys, special keys
109+
- **Separator:** `+` between keys in a combination
110+
- **Multiple hotkeys:** Comma-separated (e.g., `mod+a, ctrl+b`)
111+
112+
**Examples:**
113+
- `mod+enter` - Mod key + Enter
114+
- `shift+x` - Shift + X
115+
- `ctrl+shift+a` - Control + Shift + A
116+
- `f1, f2` - F1 or F2 (alternatives)
117+
118+
## Developer Guide
119+
120+
### Using Hotkeys in Components
121+
122+
To use a hotkey in a component:
123+
124+
```tsx
125+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
126+
127+
const MyComponent = () => {
128+
const handleAction = useCallback(() => {
129+
// Your action here
130+
}, []);
131+
132+
// This automatically uses custom hotkeys if configured
133+
useRegisteredHotkeys({
134+
id: 'myAction',
135+
category: 'app', // or 'canvas', 'viewer', 'gallery', 'workflows'
136+
callback: handleAction,
137+
options: { enabled: true, preventDefault: true },
138+
dependencies: [handleAction]
139+
});
140+
141+
// ...
142+
};
143+
```
144+
145+
**Options:**
146+
- `enabled` - Whether the hotkey is active
147+
- `preventDefault` - Prevent default browser behavior
148+
- `enableOnFormTags` - Allow hotkey in form elements (default: false)
149+
150+
### Adding New Hotkeys
151+
152+
To add a new hotkey to the system:
153+
154+
#### 1. Add Translation Strings
155+
156+
In `invokeai/frontend/web/public/locales/en.json`:
157+
158+
```json
159+
{
160+
"hotkeys": {
161+
"app": {
162+
"myAction": {
163+
"title": "My Action",
164+
"desc": "Description of what this hotkey does"
165+
}
166+
}
167+
}
168+
}
169+
```
170+
171+
#### 2. Register the Hotkey
172+
173+
In `invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts`:
174+
175+
```typescript
176+
// Inside the appropriate category builder function
177+
addHotkey('app', 'myAction', ['mod+k']); // Default binding
178+
```
179+
180+
#### 3. Use the Hotkey
181+
182+
In your component:
183+
184+
```typescript
185+
useRegisteredHotkeys({
186+
id: 'myAction',
187+
category: 'app',
188+
callback: handleMyAction,
189+
options: { enabled: true },
190+
dependencies: [handleMyAction]
191+
});
192+
```
193+
194+
### Hotkey Categories
195+
196+
Current categories:
197+
- **app** - Global application hotkeys
198+
- **canvas** - Canvas/drawing operations
199+
- **viewer** - Image viewer operations
200+
- **gallery** - Gallery/image grid operations
201+
- **workflows** - Node workflow editor
202+
203+
To add a new category, update `useHotkeyData.ts` and add translations.
204+
205+
## Testing
206+
207+
Tests are located in `invokeai/frontend/web/src/features/system/store/hotkeysSlice.test.ts`.
208+
209+
**Test Coverage:**
210+
- Adding custom hotkeys
211+
- Updating existing custom hotkeys
212+
- Resetting individual hotkeys
213+
- Resetting all hotkeys
214+
- State persistence and migration
215+
216+
Run tests with:
217+
218+
```bash
219+
cd invokeai/frontend/web
220+
pnpm test:no-watch
221+
```
222+
223+
## Persistence
224+
225+
Custom hotkeys are persisted using the same mechanism as other app settings:
226+
227+
- Stored in Redux state under the `hotkeys` slice
228+
- Persisted to IndexedDB via `redux-remember`
229+
- Automatically loaded when the app starts
230+
- Survives page refreshes and browser restarts
231+
- Includes migration support for state schema changes
232+
233+
**State Location:**
234+
- IndexedDB database: `invoke`
235+
- Store key: `hotkeys`
236+
237+
## Dependencies
238+
239+
- **react-hotkeys-hook** (v4.5.0) - Core hotkey handling
240+
- **@reduxjs/toolkit** - State management
241+
- **redux-remember** - Persistence
242+
- **zod** - State validation
243+
244+
## Best Practices
245+
246+
1. **Use `mod` instead of `ctrl`** - Automatically maps to Cmd on Mac, Ctrl elsewhere
247+
2. **Provide descriptive translations** - Help users understand what each hotkey does
248+
3. **Avoid conflicts** - Check existing hotkeys before adding new ones
249+
4. **Use preventDefault** - Prevent browser default behavior when appropriate
250+
5. **Check enabled state** - Only activate hotkeys when the action is available
251+
6. **Use dependencies correctly** - Ensure callbacks are stable with useCallback
252+
253+
## Common Patterns
254+
255+
### Conditional Hotkeys
256+
257+
```typescript
258+
useRegisteredHotkeys({
259+
id: 'save',
260+
category: 'app',
261+
callback: handleSave,
262+
options: {
263+
enabled: hasUnsavedChanges && !isLoading, // Only when valid
264+
preventDefault: true
265+
},
266+
dependencies: [hasUnsavedChanges, isLoading, handleSave]
267+
});
268+
```
269+
270+
### Multiple Hotkeys for Same Action
271+
272+
```typescript
273+
// In useHotkeyData.ts
274+
addHotkey('canvas', 'redo', ['mod+shift+z', 'mod+y']); // Two alternatives
275+
```
276+
277+
### Focus-Scoped Hotkeys
278+
279+
```typescript
280+
import { useFocusRegion } from 'common/hooks/focus';
281+
282+
const MyComponent = () => {
283+
const focusRegionRef = useFocusRegion('myRegion');
284+
285+
// Hotkey only works when this region has focus
286+
useRegisteredHotkeys({
287+
id: 'myAction',
288+
category: 'app',
289+
callback: handleAction,
290+
options: { enabled: true }
291+
});
292+
293+
return <div ref={focusRegionRef}>...</div>;
294+
};
295+
```

docs/features/hotkeys.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Customizable Hotkeys
2+
3+
InvokeAI allows you to customize all keyboard shortcuts (hotkeys) to match your workflow preferences.
4+
5+
## Features
6+
7+
- **View All Hotkeys**: See all available keyboard shortcuts in one place
8+
- **Customize Any Hotkey**: Change any shortcut to your preference
9+
- **Multiple Bindings**: Assign multiple key combinations to the same action
10+
- **Smart Validation**: Built-in validation prevents invalid combinations
11+
- **Persistent Settings**: Your custom hotkeys are saved and restored across sessions
12+
- **Easy Reset**: Reset individual hotkeys or all hotkeys back to defaults
13+
14+
## How to Use
15+
16+
### Opening the Hotkeys Modal
17+
18+
Press `Shift+?` or click the keyboard icon in the application to open the Hotkeys Modal.
19+
20+
### Viewing Hotkeys
21+
22+
In **View Mode** (default), you can:
23+
- Browse all available hotkeys organized by category (App, Canvas, Gallery, Workflows, etc.)
24+
- Search for specific hotkeys using the search bar
25+
- See the current key combination for each action
26+
27+
### Customizing Hotkeys
28+
29+
1. Click the **Edit Mode** button at the bottom of the Hotkeys Modal
30+
2. Find the hotkey you want to change
31+
3. Click the **pencil icon** next to it
32+
4. The editor will appear with:
33+
- **Input field**: Enter your new hotkey combination
34+
- **Modifier buttons**: Quick-insert Mod, Ctrl, Shift, Alt keys
35+
- **Help icon** (?): Shows syntax examples and valid keys
36+
- **Live preview**: See how your hotkey will look
37+
38+
5. Enter your new hotkey using the format:
39+
- `mod+a` - Mod key + A (Mod = Ctrl on Windows/Linux, Cmd on Mac)
40+
- `ctrl+shift+k` - Multiple modifiers
41+
- `f1` - Function keys
42+
- `mod+enter, ctrl+enter` - Multiple alternatives (separated by comma)
43+
44+
6. Click the **checkmark** or press Enter to save
45+
7. Click the **X** or press Escape to cancel
46+
47+
### Resetting Hotkeys
48+
49+
**Reset a single hotkey:**
50+
- Click the counter-clockwise arrow icon that appears next to customized hotkeys
51+
52+
**Reset all hotkeys:**
53+
- In Edit Mode, click the **Reset All to Default** button at the bottom
54+
55+
### Hotkey Format Reference
56+
57+
**Valid Modifiers:**
58+
- `mod` - Context-aware: Ctrl (Windows/Linux) or Cmd (Mac)
59+
- `ctrl` - Control key
60+
- `shift` - Shift key
61+
- `alt` - Alt key (Option on Mac)
62+
63+
**Valid Keys:**
64+
- Letters: `a-z`
65+
- Numbers: `0-9`
66+
- Function keys: `f1-f12`
67+
- Special keys: `enter`, `space`, `tab`, `backspace`, `delete`, `escape`
68+
- Arrow keys: `up`, `down`, `left`, `right`
69+
- And more...
70+
71+
**Examples:**
72+
-`mod+s` - Save action
73+
-`ctrl+shift+p` - Command palette
74+
-`f5, mod+r` - Two alternatives for refresh
75+
-`mod+` - Invalid (no key after modifier)
76+
-`shift+ctrl+` - Invalid (ends with modifier)
77+
78+
## For Developers
79+
80+
For technical implementation details, architecture, and how to add new hotkeys to the system, see the [Hotkeys Developer Documentation](../contributing/HOTKEYS.md).

0 commit comments

Comments
 (0)