mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
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>
This commit is contained in:
committed by
GitHub
parent
5642099a40
commit
a2e109b3c2
295
docs/contributing/HOTKEYS.md
Normal file
295
docs/contributing/HOTKEYS.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# Hotkeys System
|
||||
|
||||
This document describes the technical implementation of the customizable hotkeys system in InvokeAI.
|
||||
|
||||
> **Note:** For user-facing documentation on how to use customizable hotkeys, see [Hotkeys Feature Documentation](../features/hotkeys.md).
|
||||
|
||||
## Overview
|
||||
|
||||
The hotkeys system allows users to customize keyboard shortcuts throughout the application. All hotkeys are:
|
||||
- Centrally defined and managed
|
||||
- Customizable by users
|
||||
- Persisted across sessions
|
||||
- Type-safe and validated
|
||||
|
||||
## Architecture
|
||||
|
||||
The customizable hotkeys feature is built on top of the existing hotkey system with the following components:
|
||||
|
||||
### 1. Hotkeys State Slice (`hotkeysSlice.ts`)
|
||||
|
||||
Location: `invokeai/frontend/web/src/features/system/store/hotkeysSlice.ts`
|
||||
|
||||
**Responsibilities:**
|
||||
- Stores custom hotkey mappings in Redux state
|
||||
- Persisted to IndexedDB using `redux-remember`
|
||||
- Provides actions to change, reset individual, or reset all hotkeys
|
||||
|
||||
**State Shape:**
|
||||
```typescript
|
||||
{
|
||||
_version: 1,
|
||||
customHotkeys: {
|
||||
'app.invoke': ['mod+enter'],
|
||||
'canvas.undo': ['mod+z'],
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Actions:**
|
||||
- `hotkeyChanged(id, hotkeys)` - Update a single hotkey
|
||||
- `hotkeyReset(id)` - Reset a single hotkey to default
|
||||
- `allHotkeysReset()` - Reset all hotkeys to defaults
|
||||
|
||||
### 2. useHotkeyData Hook (`useHotkeyData.ts`)
|
||||
|
||||
Location: `invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts`
|
||||
|
||||
**Responsibilities:**
|
||||
- Defines all default hotkeys
|
||||
- Merges default hotkeys with custom hotkeys from the store
|
||||
- Returns the effective hotkeys that should be used throughout the app
|
||||
- Provides platform-specific key translations (Ctrl/Cmd, Alt/Option)
|
||||
|
||||
**Key Functions:**
|
||||
- `useHotkeyData()` - Returns all hotkeys organized by category
|
||||
- `useRegisteredHotkeys()` - Hook to register a hotkey in a component
|
||||
|
||||
### 3. HotkeyEditor Component (`HotkeyEditor.tsx`)
|
||||
|
||||
Location: `invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeyEditor.tsx`
|
||||
|
||||
**Features:**
|
||||
- Inline editor with input field
|
||||
- Modifier buttons (Mod, Ctrl, Shift, Alt) for quick insertion
|
||||
- Live preview of hotkey combinations
|
||||
- Validation with visual feedback
|
||||
- Help tooltip with syntax examples
|
||||
- Save/cancel/reset buttons
|
||||
|
||||
**Smart Features:**
|
||||
- Automatic `+` insertion between modifiers
|
||||
- Cursor position preservation
|
||||
- Validation prevents invalid combinations (e.g., modifier-only keys)
|
||||
|
||||
### 4. HotkeysModal Component (`HotkeysModal.tsx`)
|
||||
|
||||
Location: `invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx`
|
||||
|
||||
**Features:**
|
||||
- View Mode / Edit Mode toggle
|
||||
- Search functionality
|
||||
- Category-based organization
|
||||
- Shows HotkeyEditor components when in edit mode
|
||||
- "Reset All to Default" button in edit mode
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. User opens Hotkeys Modal │
|
||||
│ 2. User clicks "Edit Mode" button │
|
||||
│ 3. User clicks edit icon next to a hotkey │
|
||||
│ 4. User enters new hotkey(s) using editor │
|
||||
│ 5. User clicks save or presses Enter │
|
||||
│ 6. Custom hotkey stored via hotkeyChanged() action │
|
||||
│ 7. Redux state persisted to IndexedDB (redux-remember) │
|
||||
│ 8. useHotkeyData() hook picks up the change │
|
||||
│ 9. All components using useRegisteredHotkeys() get update │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Hotkey Format
|
||||
|
||||
Hotkeys use the format from `react-hotkeys-hook` library:
|
||||
|
||||
- **Modifiers:** `mod`, `ctrl`, `shift`, `alt`, `meta`
|
||||
- **Keys:** Letters, numbers, function keys, special keys
|
||||
- **Separator:** `+` between keys in a combination
|
||||
- **Multiple hotkeys:** Comma-separated (e.g., `mod+a, ctrl+b`)
|
||||
|
||||
**Examples:**
|
||||
- `mod+enter` - Mod key + Enter
|
||||
- `shift+x` - Shift + X
|
||||
- `ctrl+shift+a` - Control + Shift + A
|
||||
- `f1, f2` - F1 or F2 (alternatives)
|
||||
|
||||
## Developer Guide
|
||||
|
||||
### Using Hotkeys in Components
|
||||
|
||||
To use a hotkey in a component:
|
||||
|
||||
```tsx
|
||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||
|
||||
const MyComponent = () => {
|
||||
const handleAction = useCallback(() => {
|
||||
// Your action here
|
||||
}, []);
|
||||
|
||||
// This automatically uses custom hotkeys if configured
|
||||
useRegisteredHotkeys({
|
||||
id: 'myAction',
|
||||
category: 'app', // or 'canvas', 'viewer', 'gallery', 'workflows'
|
||||
callback: handleAction,
|
||||
options: { enabled: true, preventDefault: true },
|
||||
dependencies: [handleAction]
|
||||
});
|
||||
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `enabled` - Whether the hotkey is active
|
||||
- `preventDefault` - Prevent default browser behavior
|
||||
- `enableOnFormTags` - Allow hotkey in form elements (default: false)
|
||||
|
||||
### Adding New Hotkeys
|
||||
|
||||
To add a new hotkey to the system:
|
||||
|
||||
#### 1. Add Translation Strings
|
||||
|
||||
In `invokeai/frontend/web/public/locales/en.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"hotkeys": {
|
||||
"app": {
|
||||
"myAction": {
|
||||
"title": "My Action",
|
||||
"desc": "Description of what this hotkey does"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Register the Hotkey
|
||||
|
||||
In `invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts`:
|
||||
|
||||
```typescript
|
||||
// Inside the appropriate category builder function
|
||||
addHotkey('app', 'myAction', ['mod+k']); // Default binding
|
||||
```
|
||||
|
||||
#### 3. Use the Hotkey
|
||||
|
||||
In your component:
|
||||
|
||||
```typescript
|
||||
useRegisteredHotkeys({
|
||||
id: 'myAction',
|
||||
category: 'app',
|
||||
callback: handleMyAction,
|
||||
options: { enabled: true },
|
||||
dependencies: [handleMyAction]
|
||||
});
|
||||
```
|
||||
|
||||
### Hotkey Categories
|
||||
|
||||
Current categories:
|
||||
- **app** - Global application hotkeys
|
||||
- **canvas** - Canvas/drawing operations
|
||||
- **viewer** - Image viewer operations
|
||||
- **gallery** - Gallery/image grid operations
|
||||
- **workflows** - Node workflow editor
|
||||
|
||||
To add a new category, update `useHotkeyData.ts` and add translations.
|
||||
|
||||
## Testing
|
||||
|
||||
Tests are located in `invokeai/frontend/web/src/features/system/store/hotkeysSlice.test.ts`.
|
||||
|
||||
**Test Coverage:**
|
||||
- Adding custom hotkeys
|
||||
- Updating existing custom hotkeys
|
||||
- Resetting individual hotkeys
|
||||
- Resetting all hotkeys
|
||||
- State persistence and migration
|
||||
|
||||
Run tests with:
|
||||
|
||||
```bash
|
||||
cd invokeai/frontend/web
|
||||
pnpm test:no-watch
|
||||
```
|
||||
|
||||
## Persistence
|
||||
|
||||
Custom hotkeys are persisted using the same mechanism as other app settings:
|
||||
|
||||
- Stored in Redux state under the `hotkeys` slice
|
||||
- Persisted to IndexedDB via `redux-remember`
|
||||
- Automatically loaded when the app starts
|
||||
- Survives page refreshes and browser restarts
|
||||
- Includes migration support for state schema changes
|
||||
|
||||
**State Location:**
|
||||
- IndexedDB database: `invoke`
|
||||
- Store key: `hotkeys`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **react-hotkeys-hook** (v4.5.0) - Core hotkey handling
|
||||
- **@reduxjs/toolkit** - State management
|
||||
- **redux-remember** - Persistence
|
||||
- **zod** - State validation
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use `mod` instead of `ctrl`** - Automatically maps to Cmd on Mac, Ctrl elsewhere
|
||||
2. **Provide descriptive translations** - Help users understand what each hotkey does
|
||||
3. **Avoid conflicts** - Check existing hotkeys before adding new ones
|
||||
4. **Use preventDefault** - Prevent browser default behavior when appropriate
|
||||
5. **Check enabled state** - Only activate hotkeys when the action is available
|
||||
6. **Use dependencies correctly** - Ensure callbacks are stable with useCallback
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Conditional Hotkeys
|
||||
|
||||
```typescript
|
||||
useRegisteredHotkeys({
|
||||
id: 'save',
|
||||
category: 'app',
|
||||
callback: handleSave,
|
||||
options: {
|
||||
enabled: hasUnsavedChanges && !isLoading, // Only when valid
|
||||
preventDefault: true
|
||||
},
|
||||
dependencies: [hasUnsavedChanges, isLoading, handleSave]
|
||||
});
|
||||
```
|
||||
|
||||
### Multiple Hotkeys for Same Action
|
||||
|
||||
```typescript
|
||||
// In useHotkeyData.ts
|
||||
addHotkey('canvas', 'redo', ['mod+shift+z', 'mod+y']); // Two alternatives
|
||||
```
|
||||
|
||||
### Focus-Scoped Hotkeys
|
||||
|
||||
```typescript
|
||||
import { useFocusRegion } from 'common/hooks/focus';
|
||||
|
||||
const MyComponent = () => {
|
||||
const focusRegionRef = useFocusRegion('myRegion');
|
||||
|
||||
// Hotkey only works when this region has focus
|
||||
useRegisteredHotkeys({
|
||||
id: 'myAction',
|
||||
category: 'app',
|
||||
callback: handleAction,
|
||||
options: { enabled: true }
|
||||
});
|
||||
|
||||
return <div ref={focusRegionRef}>...</div>;
|
||||
};
|
||||
```
|
||||
80
docs/features/hotkeys.md
Normal file
80
docs/features/hotkeys.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Customizable Hotkeys
|
||||
|
||||
InvokeAI allows you to customize all keyboard shortcuts (hotkeys) to match your workflow preferences.
|
||||
|
||||
## Features
|
||||
|
||||
- **View All Hotkeys**: See all available keyboard shortcuts in one place
|
||||
- **Customize Any Hotkey**: Change any shortcut to your preference
|
||||
- **Multiple Bindings**: Assign multiple key combinations to the same action
|
||||
- **Smart Validation**: Built-in validation prevents invalid combinations
|
||||
- **Persistent Settings**: Your custom hotkeys are saved and restored across sessions
|
||||
- **Easy Reset**: Reset individual hotkeys or all hotkeys back to defaults
|
||||
|
||||
## How to Use
|
||||
|
||||
### Opening the Hotkeys Modal
|
||||
|
||||
Press `Shift+?` or click the keyboard icon in the application to open the Hotkeys Modal.
|
||||
|
||||
### Viewing Hotkeys
|
||||
|
||||
In **View Mode** (default), you can:
|
||||
- Browse all available hotkeys organized by category (App, Canvas, Gallery, Workflows, etc.)
|
||||
- Search for specific hotkeys using the search bar
|
||||
- See the current key combination for each action
|
||||
|
||||
### Customizing Hotkeys
|
||||
|
||||
1. Click the **Edit Mode** button at the bottom of the Hotkeys Modal
|
||||
2. Find the hotkey you want to change
|
||||
3. Click the **pencil icon** next to it
|
||||
4. The editor will appear with:
|
||||
- **Input field**: Enter your new hotkey combination
|
||||
- **Modifier buttons**: Quick-insert Mod, Ctrl, Shift, Alt keys
|
||||
- **Help icon** (?): Shows syntax examples and valid keys
|
||||
- **Live preview**: See how your hotkey will look
|
||||
|
||||
5. Enter your new hotkey using the format:
|
||||
- `mod+a` - Mod key + A (Mod = Ctrl on Windows/Linux, Cmd on Mac)
|
||||
- `ctrl+shift+k` - Multiple modifiers
|
||||
- `f1` - Function keys
|
||||
- `mod+enter, ctrl+enter` - Multiple alternatives (separated by comma)
|
||||
|
||||
6. Click the **checkmark** or press Enter to save
|
||||
7. Click the **X** or press Escape to cancel
|
||||
|
||||
### Resetting Hotkeys
|
||||
|
||||
**Reset a single hotkey:**
|
||||
- Click the counter-clockwise arrow icon that appears next to customized hotkeys
|
||||
|
||||
**Reset all hotkeys:**
|
||||
- In Edit Mode, click the **Reset All to Default** button at the bottom
|
||||
|
||||
### Hotkey Format Reference
|
||||
|
||||
**Valid Modifiers:**
|
||||
- `mod` - Context-aware: Ctrl (Windows/Linux) or Cmd (Mac)
|
||||
- `ctrl` - Control key
|
||||
- `shift` - Shift key
|
||||
- `alt` - Alt key (Option on Mac)
|
||||
|
||||
**Valid Keys:**
|
||||
- Letters: `a-z`
|
||||
- Numbers: `0-9`
|
||||
- Function keys: `f1-f12`
|
||||
- Special keys: `enter`, `space`, `tab`, `backspace`, `delete`, `escape`
|
||||
- Arrow keys: `up`, `down`, `left`, `right`
|
||||
- And more...
|
||||
|
||||
**Examples:**
|
||||
- ✅ `mod+s` - Save action
|
||||
- ✅ `ctrl+shift+p` - Command palette
|
||||
- ✅ `f5, mod+r` - Two alternatives for refresh
|
||||
- ❌ `mod+` - Invalid (no key after modifier)
|
||||
- ❌ `shift+ctrl+` - Invalid (ends with modifier)
|
||||
|
||||
## For Developers
|
||||
|
||||
For technical implementation details, architecture, and how to add new hotkeys to the system, see the [Hotkeys Developer Documentation](../contributing/HOTKEYS.md).
|
||||
Reference in New Issue
Block a user