Skip to content

Commit 9e6b60a

Browse files
psychedelicioushipsterusername
authored andcommitted
feat(ui): update hotkey list
- Rework hotkey data to include the keys for each hotkey action. - Add wrapper for `useHotkeys` that accepts a hotkey category and id. Automatically selects the key from the hotkey data. - Add handling for macOS (cmd vs ctrl, option vs alt). - Redo all hotkey descriptions, deleting nonexistant ones. - Some `esc` hotkeys that just close whatever you are currently in are omitted due to their relative simplicity and intuitiveness.
1 parent fdcd26f commit 9e6b60a

31 files changed

+1066
-831
lines changed

invokeai/frontend/web/public/locales/en.json

Lines changed: 259 additions & 217 deletions
Large diffs are not rendered by default.

invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts

Lines changed: 64 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,112 +3,122 @@ import { addScope, removeScope, setScopes } from 'common/hooks/interactionScopes
33
import { useClearQueue } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
44
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
55
import { useInvoke } from 'features/queue/hooks/useInvoke';
6+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
67
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
78
import { setActiveTab } from 'features/ui/store/uiSlice';
8-
import { useHotkeys } from 'react-hotkeys-hook';
99

1010
export const useGlobalHotkeys = () => {
1111
const dispatch = useAppDispatch();
1212
const isModelManagerEnabled = useFeatureStatus('modelManager');
1313
const queue = useInvoke();
1414

15-
useHotkeys(
16-
['ctrl+enter', 'meta+enter'],
17-
queue.queueBack,
18-
{
15+
useRegisteredHotkeys({
16+
id: 'invoke',
17+
category: 'app',
18+
callback: queue.queueBack,
19+
options: {
1920
enabled: !queue.isDisabled && !queue.isLoading,
2021
preventDefault: true,
2122
enableOnFormTags: ['input', 'textarea', 'select'],
2223
},
23-
[queue]
24-
);
24+
dependencies: [queue],
25+
});
2526

26-
useHotkeys(
27-
['ctrl+shift+enter', 'meta+shift+enter'],
28-
queue.queueFront,
29-
{
27+
useRegisteredHotkeys({
28+
id: 'invokeFront',
29+
category: 'app',
30+
callback: queue.queueFront,
31+
options: {
3032
enabled: !queue.isDisabled && !queue.isLoading,
3133
preventDefault: true,
3234
enableOnFormTags: ['input', 'textarea', 'select'],
3335
},
34-
[queue]
35-
);
36+
dependencies: [queue],
37+
});
3638

3739
const {
3840
cancelQueueItem,
3941
isDisabled: isDisabledCancelQueueItem,
4042
isLoading: isLoadingCancelQueueItem,
4143
} = useCancelCurrentQueueItem();
4244

43-
useHotkeys(
44-
['shift+x'],
45-
cancelQueueItem,
46-
{
45+
useRegisteredHotkeys({
46+
id: 'cancelQueueItem',
47+
category: 'app',
48+
callback: cancelQueueItem,
49+
options: {
4750
enabled: !isDisabledCancelQueueItem && !isLoadingCancelQueueItem,
4851
preventDefault: true,
4952
},
50-
[cancelQueueItem, isDisabledCancelQueueItem, isLoadingCancelQueueItem]
51-
);
53+
dependencies: [cancelQueueItem, isDisabledCancelQueueItem, isLoadingCancelQueueItem],
54+
});
5255

5356
const { clearQueue, isDisabled: isDisabledClearQueue, isLoading: isLoadingClearQueue } = useClearQueue();
5457

55-
useHotkeys(
56-
['ctrl+shift+x', 'meta+shift+x'],
57-
clearQueue,
58-
{
58+
useRegisteredHotkeys({
59+
id: 'clearQueue',
60+
category: 'app',
61+
callback: clearQueue,
62+
options: {
5963
enabled: !isDisabledClearQueue && !isLoadingClearQueue,
6064
preventDefault: true,
6165
},
62-
[clearQueue, isDisabledClearQueue, isLoadingClearQueue]
63-
);
66+
dependencies: [clearQueue, isDisabledClearQueue, isLoadingClearQueue],
67+
});
6468

65-
useHotkeys(
66-
'1',
67-
() => {
69+
useRegisteredHotkeys({
70+
id: 'selectCanvasTab',
71+
category: 'app',
72+
callback: () => {
6873
dispatch(setActiveTab('canvas'));
6974
addScope('canvas');
7075
removeScope('workflows');
7176
},
72-
[dispatch]
73-
);
77+
dependencies: [dispatch],
78+
});
7479

75-
useHotkeys(
76-
'2',
77-
() => {
80+
useRegisteredHotkeys({
81+
id: 'selectUpscalingTab',
82+
category: 'app',
83+
callback: () => {
7884
dispatch(setActiveTab('upscaling'));
7985
removeScope('canvas');
8086
removeScope('workflows');
8187
},
82-
[dispatch]
83-
);
88+
dependencies: [dispatch],
89+
});
8490

85-
useHotkeys(
86-
'3',
87-
() => {
91+
useRegisteredHotkeys({
92+
id: 'selectWorkflowsTab',
93+
category: 'app',
94+
callback: () => {
8895
dispatch(setActiveTab('workflows'));
8996
removeScope('canvas');
9097
addScope('workflows');
9198
},
92-
[dispatch]
93-
);
99+
dependencies: [dispatch],
100+
});
94101

95-
useHotkeys(
96-
'4',
97-
() => {
98-
if (isModelManagerEnabled) {
99-
dispatch(setActiveTab('models'));
100-
setScopes([]);
101-
}
102+
useRegisteredHotkeys({
103+
id: 'selectModelsTab',
104+
category: 'app',
105+
callback: () => {
106+
dispatch(setActiveTab('models'));
107+
setScopes([]);
108+
},
109+
options: {
110+
enabled: isModelManagerEnabled,
102111
},
103-
[dispatch, isModelManagerEnabled]
104-
);
112+
dependencies: [dispatch, isModelManagerEnabled],
113+
});
105114

106-
useHotkeys(
107-
isModelManagerEnabled ? '5' : '4',
108-
() => {
115+
useRegisteredHotkeys({
116+
id: 'selectQueueTab',
117+
category: 'app',
118+
callback: () => {
109119
dispatch(setActiveTab('queue'));
110120
setScopes([]);
111121
},
112-
[dispatch, isModelManagerEnabled]
113-
);
122+
dependencies: [dispatch, isModelManagerEnabled],
123+
});
114124
};

invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import {
1212
import { selectEntityCountActive } from 'features/controlLayers/store/selectors';
1313
import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent';
1414
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
15+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
1516
import { memo, useCallback, useMemo, useRef } from 'react';
16-
import { useHotkeys } from 'react-hotkeys-hook';
1717
import { useTranslation } from 'react-i18next';
1818

1919
export const CanvasRightPanel = memo(() => {
@@ -28,7 +28,12 @@ export const CanvasRightPanel = memo(() => {
2828
}
2929
imageViewer.toggle();
3030
}, [imageViewer]);
31-
useHotkeys('z', imageViewer.toggle);
31+
useRegisteredHotkeys({
32+
id: 'toggleViewer',
33+
category: 'viewer',
34+
callback: imageViewer.toggle,
35+
dependencies: [imageViewer],
36+
});
3237

3338
return (
3439
<Tabs index={tabIndex} onChange={$canvasRightPanelTabIndex.set} w="full" h="full" display="flex" flexDir="column">

invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBboxButton.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IconButton } from '@invoke-ai/ui-library';
22
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
3+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
34
import { memo } from 'react';
4-
import { useHotkeys } from 'react-hotkeys-hook';
55
import { useTranslation } from 'react-i18next';
66
import { PiBoundingBoxBold } from 'react-icons/pi';
77

@@ -10,7 +10,13 @@ export const ToolBboxButton = memo(() => {
1010
const selectBbox = useSelectTool('bbox');
1111
const isSelected = useToolIsSelected('bbox');
1212

13-
useHotkeys('c', selectBbox, { enabled: !isSelected }, [selectBbox, isSelected]);
13+
useRegisteredHotkeys({
14+
id: 'selectBboxTool',
15+
category: 'canvas',
16+
callback: selectBbox,
17+
options: { enabled: !isSelected },
18+
dependencies: [selectBbox, isSelected],
19+
});
1420

1521
return (
1622
<IconButton

invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushButton.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IconButton } from '@invoke-ai/ui-library';
22
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
3+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
34
import { memo } from 'react';
4-
import { useHotkeys } from 'react-hotkeys-hook';
55
import { useTranslation } from 'react-i18next';
66
import { PiPaintBrushBold } from 'react-icons/pi';
77

@@ -10,7 +10,13 @@ export const ToolBrushButton = memo(() => {
1010
const isSelected = useToolIsSelected('brush');
1111
const selectBrush = useSelectTool('brush');
1212

13-
useHotkeys('b', selectBrush, { enabled: !isSelected }, [isSelected, selectBrush]);
13+
useRegisteredHotkeys({
14+
id: 'selectBrushTool',
15+
category: 'canvas',
16+
callback: selectBrush,
17+
options: { enabled: !isSelected },
18+
dependencies: [isSelected, selectBrush],
19+
});
1420

1521
return (
1622
<IconButton

invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolBrushWidth.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import { createSelector } from '@reduxjs/toolkit';
1616
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
1717
import { useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
1818
import { selectCanvasSettingsSlice, settingsBrushWidthChanged } from 'features/controlLayers/store/canvasSettingsSlice';
19+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
1920
import { clamp } from 'lodash-es';
2021
import type { KeyboardEvent } from 'react';
2122
import { memo, useCallback, useEffect, useState } from 'react';
22-
import { useHotkeys } from 'react-hotkeys-hook';
2323
import { useTranslation } from 'react-i18next';
2424
import { PiCaretDownBold } from 'react-icons/pi';
2525

@@ -127,8 +127,20 @@ export const ToolBrushWidth = memo(() => {
127127
setLocalValue(width);
128128
}, [width]);
129129

130-
useHotkeys('[', decrement, { enabled: isSelected }, [decrement, isSelected]);
131-
useHotkeys(']', increment, { enabled: isSelected }, [increment, isSelected]);
130+
useRegisteredHotkeys({
131+
id: 'incrementToolWidth',
132+
category: 'canvas',
133+
callback: decrement,
134+
options: { enabled: isSelected },
135+
dependencies: [decrement, isSelected],
136+
});
137+
useRegisteredHotkeys({
138+
id: 'incrementToolWidth',
139+
category: 'canvas',
140+
callback: increment,
141+
options: { enabled: isSelected },
142+
dependencies: [increment, isSelected],
143+
});
132144

133145
return (
134146
<Popover>

invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolColorPickerButton.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IconButton } from '@invoke-ai/ui-library';
22
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
3+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
34
import { memo } from 'react';
4-
import { useHotkeys } from 'react-hotkeys-hook';
55
import { useTranslation } from 'react-i18next';
66
import { PiEyedropperBold } from 'react-icons/pi';
77

@@ -10,7 +10,13 @@ export const ToolColorPickerButton = memo(() => {
1010
const isSelected = useToolIsSelected('colorPicker');
1111
const selectColorPicker = useSelectTool('colorPicker');
1212

13-
useHotkeys('i', selectColorPicker, { enabled: !isSelected }, [selectColorPicker, isSelected]);
13+
useRegisteredHotkeys({
14+
id: 'selectColorPickerTool',
15+
category: 'canvas',
16+
callback: selectColorPicker,
17+
options: { enabled: !isSelected },
18+
dependencies: [selectColorPicker, isSelected],
19+
});
1420

1521
return (
1622
<IconButton

invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserButton.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IconButton } from '@invoke-ai/ui-library';
22
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
3+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
34
import { memo } from 'react';
4-
import { useHotkeys } from 'react-hotkeys-hook';
55
import { useTranslation } from 'react-i18next';
66
import { PiEraserBold } from 'react-icons/pi';
77

@@ -10,7 +10,13 @@ export const ToolEraserButton = memo(() => {
1010
const isSelected = useToolIsSelected('eraser');
1111
const selectEraser = useSelectTool('eraser');
1212

13-
useHotkeys('e', selectEraser, { enabled: !isSelected }, [isSelected, selectEraser]);
13+
useRegisteredHotkeys({
14+
id: 'selectEraserTool',
15+
category: 'canvas',
16+
callback: selectEraser,
17+
options: { enabled: !isSelected },
18+
dependencies: [isSelected, selectEraser],
19+
});
1420

1521
return (
1622
<IconButton

invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolEraserWidth.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import {
1919
selectCanvasSettingsSlice,
2020
settingsEraserWidthChanged,
2121
} from 'features/controlLayers/store/canvasSettingsSlice';
22+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
2223
import { clamp } from 'lodash-es';
2324
import type { KeyboardEvent } from 'react';
2425
import { memo, useCallback, useEffect, useState } from 'react';
25-
import { useHotkeys } from 'react-hotkeys-hook';
2626
import { useTranslation } from 'react-i18next';
2727
import { PiCaretDownBold } from 'react-icons/pi';
2828

@@ -130,8 +130,20 @@ export const ToolEraserWidth = memo(() => {
130130
setLocalValue(width);
131131
}, [width]);
132132

133-
useHotkeys('[', decrement, { enabled: isSelected }, [decrement, isSelected]);
134-
useHotkeys(']', increment, { enabled: isSelected }, [increment, isSelected]);
133+
useRegisteredHotkeys({
134+
id: 'incrementToolWidth',
135+
category: 'canvas',
136+
callback: decrement,
137+
options: { enabled: isSelected },
138+
dependencies: [decrement, isSelected],
139+
});
140+
useRegisteredHotkeys({
141+
id: 'incrementToolWidth',
142+
category: 'canvas',
143+
callback: increment,
144+
options: { enabled: isSelected },
145+
dependencies: [increment, isSelected],
146+
});
135147

136148
return (
137149
<Popover>

invokeai/frontend/web/src/features/controlLayers/components/Tool/ToolMoveButton.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IconButton } from '@invoke-ai/ui-library';
22
import { useSelectTool, useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
3+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
34
import { memo } from 'react';
4-
import { useHotkeys } from 'react-hotkeys-hook';
55
import { useTranslation } from 'react-i18next';
66
import { PiCursorBold } from 'react-icons/pi';
77

@@ -10,7 +10,13 @@ export const ToolMoveButton = memo(() => {
1010
const isSelected = useToolIsSelected('move');
1111
const selectMove = useSelectTool('move');
1212

13-
useHotkeys('v', selectMove, { enabled: !isSelected }, [isSelected, selectMove]);
13+
useRegisteredHotkeys({
14+
id: 'selectMoveTool',
15+
category: 'canvas',
16+
callback: selectMove,
17+
options: { enabled: !isSelected },
18+
dependencies: [isSelected, selectMove],
19+
});
1420

1521
return (
1622
<IconButton

0 commit comments

Comments
 (0)