diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index bdcebd77ec..9c21fd40fe 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -426,6 +426,9 @@
"problemDeletingImagesDesc": "One or more images could not be deleted"
},
"hotkeys": {
+ "searchHotkeys": "Search Hotkeys",
+ "clearSearch": "Clear Search",
+ "noHotkeysFound": "No Hotkeys Found",
"acceptStagingImage": {
"desc": "Accept Current Staging Area Image",
"title": "Accept Staging Image"
@@ -434,7 +437,7 @@
"desc": "Opens the add node menu",
"title": "Add Nodes"
},
- "appHotkeys": "App Hotkeys",
+ "appHotkeys": "App",
"cancel": {
"desc": "Cancel current queue item",
"title": "Cancel"
@@ -499,8 +502,8 @@
"desc": "Focus the prompt input area",
"title": "Focus Prompt"
},
- "galleryHotkeys": "Gallery Hotkeys",
- "generalHotkeys": "General Hotkeys",
+ "galleryHotkeys": "Gallery",
+ "generalHotkeys": "General",
"hideMask": {
"desc": "Hide and unhide mask",
"title": "Hide Mask"
@@ -521,7 +524,7 @@
"desc": "Generate an image",
"title": "Invoke"
},
- "keyboardShortcuts": "Keyboard Shortcuts",
+ "keyboardShortcuts": "Hotkeys",
"maximizeWorkSpace": {
"desc": "Close panels and maximize work area",
"title": "Maximize Workspace"
@@ -542,7 +545,7 @@
"desc": "Next Staging Area Image",
"title": "Next Staging Image"
},
- "nodesHotkeys": "Nodes Hotkeys",
+ "nodesHotkeys": "Nodes",
"pinOptions": {
"desc": "Pin the options panel",
"title": "Pin Options"
@@ -611,31 +614,31 @@
"desc": "Open and close the gallery drawer",
"title": "Toggle Gallery"
},
- "toggleGalleryPin": {
- "desc": "Pins and unpins the gallery to the UI",
- "title": "Toggle Gallery Pin"
+ "toggleOptions": {
+ "desc": "Open and close the options panel",
+ "title": "Toggle Options"
+ },
+ "toggleOptionsAndGallery": {
+ "desc": "Open and close the options and gallery panels",
+ "title": "Toggle Options and Gallery"
+ },
+ "resetOptionsAndGallery": {
+ "desc": "Resets the options and gallery panels",
+ "title": "Reset Options and Gallery"
},
"toggleLayer": {
"desc": "Toggles mask/base layer selection",
"title": "Toggle Layer"
},
- "toggleOptions": {
- "desc": "Open and close the options panel",
- "title": "Toggle Options"
- },
"toggleSnap": {
"desc": "Toggles Snap to Grid",
"title": "Toggle Snap"
},
- "toggleViewer": {
- "desc": "Open and close Image Viewer",
- "title": "Toggle Viewer"
- },
"undoStroke": {
"desc": "Undo a brush stroke",
"title": "Undo Stroke"
},
- "unifiedCanvasHotkeys": "Unified Canvas Hotkeys",
+ "unifiedCanvasHotkeys": "Unified Canvas",
"upscale": {
"desc": "Upscale the current image",
"title": "Upscale"
diff --git a/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeyListItem.tsx b/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeyListItem.tsx
new file mode 100644
index 0000000000..f2b5b8506f
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeyListItem.tsx
@@ -0,0 +1,51 @@
+/* eslint-disable i18next/no-literal-string */
+import { Flex, Kbd, Spacer } from '@chakra-ui/react';
+import { InvText } from 'common/components/InvText/wrapper';
+import { memo } from 'react';
+
+interface HotkeysModalProps {
+ hotkeys: string[][];
+ title: string;
+ description: string;
+}
+
+const HotkeyListItem = (props: HotkeysModalProps) => {
+ const { title, hotkeys, description } = props;
+ return (
+
+
+ {title}
+
+ {hotkeys.map((hotkey, index) => {
+ return (
+ <>
+ {hotkey.map((key, index) => (
+ <>
+
+ {key}
+
+ {index !== hotkey.length - 1 && (
+
+ +
+
+ )}
+ >
+ ))}
+ {index !== hotkeys.length - 1 && (
+
+ or
+
+ )}
+ >
+ );
+ })}
+
+ {description}
+
+ );
+};
+
+export default memo(HotkeyListItem);
diff --git a/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx b/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx
index c2977c9982..285e8a76b1 100644
--- a/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx
+++ b/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx
@@ -1,11 +1,15 @@
-import { Divider, Flex, useDisclosure } from '@chakra-ui/react';
-import { InvAccordionButton } from 'common/components/InvAccordion/InvAccordionButton';
+import { CloseIcon } from '@chakra-ui/icons';
import {
- InvAccordion,
- InvAccordionIcon,
- InvAccordionItem,
- InvAccordionPanel,
-} from 'common/components/InvAccordion/wrapper';
+ Divider,
+ Flex,
+ InputGroup,
+ InputRightElement,
+ useDisclosure,
+} from '@chakra-ui/react';
+import { IAINoContentFallback } from 'common/components/IAIImageFallback';
+import { InvHeading } from 'common/components/InvHeading/wrapper';
+import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
+import { InvInput } from 'common/components/InvInput/InvInput';
import {
InvModal,
InvModalBody,
@@ -15,406 +19,148 @@ import {
InvModalHeader,
InvModalOverlay,
} from 'common/components/InvModal/wrapper';
-import type { ReactElement } from 'react';
-import { cloneElement, memo, useMemo } from 'react';
+import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
+import type { HotkeyGroup } from 'features/system/components/HotkeysModal/useHotkeyData';
+import { useHotkeyData } from 'features/system/components/HotkeysModal/useHotkeyData';
+import type { ChangeEventHandler, ReactElement } from 'react';
+import {
+ cloneElement,
+ Fragment,
+ memo,
+ useCallback,
+ useMemo,
+ useState,
+} from 'react';
import { useTranslation } from 'react-i18next';
-import HotkeysModalItem from './HotkeysModalItem';
+import HotkeyListItem from './HotkeyListItem';
type HotkeysModalProps = {
/* The button to open the Settings Modal */
children: ReactElement;
};
-type HotkeyList = {
- title: string;
- desc: string;
- hotkey: string;
-};
-
-const renderHotkeyModalItems = (hotkeys: HotkeyList[]) => (
-
- {hotkeys.map((hotkey, i) => (
-
-
- {i < hotkeys.length - 1 && }
-
- ))}
-
-);
-
const HotkeysModal = ({ children }: HotkeysModalProps) => {
const {
isOpen: isHotkeyModalOpen,
onOpen: onHotkeysModalOpen,
onClose: onHotkeysModalClose,
} = useDisclosure();
-
const { t } = useTranslation();
-
- const appHotkeys = useMemo(
- () => [
- {
- title: t('hotkeys.invoke.title'),
- desc: t('hotkeys.invoke.desc'),
- hotkey: 'Ctrl+Enter',
- },
- {
- title: t('hotkeys.cancel.title'),
- desc: t('hotkeys.cancel.desc'),
- hotkey: 'Shift+X',
- },
- {
- title: t('hotkeys.cancelAndClear.title'),
- desc: t('hotkeys.cancelAndClear.desc'),
- hotkey: 'Shift+Ctrl+X / Shift+Cmd+X',
- },
- {
- title: t('hotkeys.focusPrompt.title'),
- desc: t('hotkeys.focusPrompt.desc'),
- hotkey: 'Alt+A',
- },
- {
- title: t('hotkeys.toggleOptions.title'),
- desc: t('hotkeys.toggleOptions.desc'),
- hotkey: 'T / O',
- },
- {
- title: t('hotkeys.toggleGallery.title'),
- desc: t('hotkeys.toggleGallery.desc'),
- hotkey: 'G',
- },
- {
- title: t('hotkeys.maximizeWorkSpace.title'),
- desc: t('hotkeys.maximizeWorkSpace.desc'),
- hotkey: 'F',
- },
- {
- title: t('hotkeys.changeTabs.title'),
- desc: t('hotkeys.changeTabs.desc'),
- hotkey: '1-5',
- },
- ],
- [t]
- );
-
- const generalHotkeys = useMemo(
- () => [
- {
- title: t('hotkeys.setPrompt.title'),
- desc: t('hotkeys.setPrompt.desc'),
- hotkey: 'P',
- },
- {
- title: t('hotkeys.setSeed.title'),
- desc: t('hotkeys.setSeed.desc'),
- hotkey: 'S',
- },
- {
- title: t('hotkeys.setParameters.title'),
- desc: t('hotkeys.setParameters.desc'),
- hotkey: 'A',
- },
- {
- title: t('hotkeys.upscale.title'),
- desc: t('hotkeys.upscale.desc'),
- hotkey: 'Shift+U',
- },
- {
- title: t('hotkeys.showInfo.title'),
- desc: t('hotkeys.showInfo.desc'),
- hotkey: 'I',
- },
- {
- title: t('hotkeys.sendToImageToImage.title'),
- desc: t('hotkeys.sendToImageToImage.desc'),
- hotkey: 'Shift+I',
- },
- {
- title: t('hotkeys.deleteImage.title'),
- desc: t('hotkeys.deleteImage.desc'),
- hotkey: 'Del',
- },
- {
- title: t('hotkeys.closePanels.title'),
- desc: t('hotkeys.closePanels.desc'),
- hotkey: 'Esc',
- },
- ],
- [t]
- );
-
- const galleryHotkeys = useMemo(
- () => [
- {
- title: t('hotkeys.previousImage.title'),
- desc: t('hotkeys.previousImage.desc'),
- hotkey: 'Arrow Left',
- },
- {
- title: t('hotkeys.nextImage.title'),
- desc: t('hotkeys.nextImage.desc'),
- hotkey: 'Arrow Right',
- },
- {
- title: t('hotkeys.increaseGalleryThumbSize.title'),
- desc: t('hotkeys.increaseGalleryThumbSize.desc'),
- hotkey: 'Shift+Up',
- },
- {
- title: t('hotkeys.decreaseGalleryThumbSize.title'),
- desc: t('hotkeys.decreaseGalleryThumbSize.desc'),
- hotkey: 'Shift+Down',
- },
- ],
- [t]
- );
-
- const unifiedCanvasHotkeys = useMemo(
- () => [
- {
- title: t('hotkeys.selectBrush.title'),
- desc: t('hotkeys.selectBrush.desc'),
- hotkey: 'B',
- },
- {
- title: t('hotkeys.selectEraser.title'),
- desc: t('hotkeys.selectEraser.desc'),
- hotkey: 'E',
- },
- {
- title: t('hotkeys.decreaseBrushSize.title'),
- desc: t('hotkeys.decreaseBrushSize.desc'),
- hotkey: '[',
- },
- {
- title: t('hotkeys.increaseBrushSize.title'),
- desc: t('hotkeys.increaseBrushSize.desc'),
- hotkey: ']',
- },
- {
- title: t('hotkeys.decreaseBrushOpacity.title'),
- desc: t('hotkeys.decreaseBrushOpacity.desc'),
- hotkey: 'Shift + [',
- },
- {
- title: t('hotkeys.increaseBrushOpacity.title'),
- desc: t('hotkeys.increaseBrushOpacity.desc'),
- hotkey: 'Shift + ]',
- },
- {
- title: t('hotkeys.moveTool.title'),
- desc: t('hotkeys.moveTool.desc'),
- hotkey: 'V',
- },
- {
- title: t('hotkeys.fillBoundingBox.title'),
- desc: t('hotkeys.fillBoundingBox.desc'),
- hotkey: 'Shift + F',
- },
- {
- title: t('hotkeys.eraseBoundingBox.title'),
- desc: t('hotkeys.eraseBoundingBox.desc'),
- hotkey: 'Delete / Backspace',
- },
- {
- title: t('hotkeys.colorPicker.title'),
- desc: t('hotkeys.colorPicker.desc'),
- hotkey: 'C',
- },
- {
- title: t('hotkeys.toggleSnap.title'),
- desc: t('hotkeys.toggleSnap.desc'),
- hotkey: 'N',
- },
- {
- title: t('hotkeys.quickToggleMove.title'),
- desc: t('hotkeys.quickToggleMove.desc'),
- hotkey: 'Hold Space',
- },
- {
- title: t('hotkeys.toggleLayer.title'),
- desc: t('hotkeys.toggleLayer.desc'),
- hotkey: 'Q',
- },
- {
- title: t('hotkeys.clearMask.title'),
- desc: t('hotkeys.clearMask.desc'),
- hotkey: 'Shift+C',
- },
- {
- title: t('hotkeys.hideMask.title'),
- desc: t('hotkeys.hideMask.desc'),
- hotkey: 'H',
- },
- {
- title: t('hotkeys.showHideBoundingBox.title'),
- desc: t('hotkeys.showHideBoundingBox.desc'),
- hotkey: 'Shift+H',
- },
- {
- title: t('hotkeys.mergeVisible.title'),
- desc: t('hotkeys.mergeVisible.desc'),
- hotkey: 'Shift+M',
- },
- {
- title: t('hotkeys.saveToGallery.title'),
- desc: t('hotkeys.saveToGallery.desc'),
- hotkey: 'Shift+S',
- },
- {
- title: t('hotkeys.copyToClipboard.title'),
- desc: t('hotkeys.copyToClipboard.desc'),
- hotkey: 'Ctrl+C',
- },
- {
- title: t('hotkeys.downloadImage.title'),
- desc: t('hotkeys.downloadImage.desc'),
- hotkey: 'Shift+D',
- },
- {
- title: t('hotkeys.undoStroke.title'),
- desc: t('hotkeys.undoStroke.desc'),
- hotkey: 'Ctrl+Z',
- },
- {
- title: t('hotkeys.redoStroke.title'),
- desc: t('hotkeys.redoStroke.desc'),
- hotkey: 'Ctrl+Shift+Z, Ctrl+Y',
- },
- {
- title: t('hotkeys.resetView.title'),
- desc: t('hotkeys.resetView.desc'),
- hotkey: 'R',
- },
- {
- title: t('hotkeys.previousStagingImage.title'),
- desc: t('hotkeys.previousStagingImage.desc'),
- hotkey: 'Arrow Left',
- },
- {
- title: t('hotkeys.nextStagingImage.title'),
- desc: t('hotkeys.nextStagingImage.desc'),
- hotkey: 'Arrow Right',
- },
- {
- title: t('hotkeys.acceptStagingImage.title'),
- desc: t('hotkeys.acceptStagingImage.desc'),
- hotkey: 'Enter',
- },
- ],
- [t]
- );
-
- const nodesHotkeys = useMemo(
- () => [
- {
- title: t('hotkeys.addNodes.title'),
- desc: t('hotkeys.addNodes.desc'),
- hotkey: 'Shift + A / Space',
- },
- ],
- [t]
+ const [hotkeyFilter, setHotkeyFilter] = useState('');
+ const clearHotkeyFilter = useCallback(() => setHotkeyFilter(''), []);
+ const onChange = useCallback>(
+ (e) => setHotkeyFilter(e.target.value),
+ []
);
+ const hotkeyGroups = useHotkeyData();
+ const filteredHotkeyGroups = useMemo(() => {
+ if (!hotkeyFilter.trim().length) {
+ return hotkeyGroups;
+ }
+ const trimmedHotkeyFilter = hotkeyFilter.trim().toLowerCase();
+ const filteredGroups: HotkeyGroup[] = [];
+ hotkeyGroups.forEach((group) => {
+ const filteredGroup: HotkeyGroup = {
+ title: group.title,
+ hotkeyListItems: [],
+ };
+ group.hotkeyListItems.forEach((item) => {
+ if (
+ item.title.toLowerCase().includes(trimmedHotkeyFilter) ||
+ item.desc.toLowerCase().includes(trimmedHotkeyFilter) ||
+ item.hotkeys.some((hotkey) =>
+ hotkey.some((key) =>
+ key.toLowerCase().includes(trimmedHotkeyFilter)
+ )
+ )
+ ) {
+ filteredGroup.hotkeyListItems.push(item);
+ }
+ });
+ if (filteredGroup.hotkeyListItems.length) {
+ filteredGroups.push(filteredGroup);
+ }
+ });
+ return filteredGroups;
+ }, [hotkeyGroups, hotkeyFilter]);
return (
<>
{cloneElement(children, {
onClick: onHotkeysModalOpen,
})}
-
+
-
+
{t('hotkeys.keyboardShortcuts')}
-
-
-
-
-
-
- {t('hotkeys.appHotkeys')}
-
-
-
-
- {renderHotkeyModalItems(appHotkeys)}
-
-
+
+
+
+ {hotkeyFilter.length && (
+
+ }
+ />
+
+ )}
+
-
-
-
- {t('hotkeys.generalHotkeys')}
-
-
-
-
- {renderHotkeyModalItems(generalHotkeys)}
-
-
-
-
-
-
- {t('hotkeys.galleryHotkeys')}
-
-
-
-
- {renderHotkeyModalItems(galleryHotkeys)}
-
-
-
-
-
-
- {t('hotkeys.unifiedCanvasHotkeys')}
-
-
-
-
- {renderHotkeyModalItems(unifiedCanvasHotkeys)}
-
-
-
-
-
-
- {t('hotkeys.nodesHotkeys')}
-
-
-
-
- {renderHotkeyModalItems(nodesHotkeys)}
-
-
-
-
+
+ {filteredHotkeyGroups.map((group) => (
+
+
+ {group.title}
+
+
+ {group.hotkeyListItems.map((hotkey, i) => (
+
+
+ {i < group.hotkeyListItems.length - 1 && }
+
+ ))}
+
+
+ ))}
+ {!filteredHotkeyGroups.length && (
+
+ )}
+
diff --git a/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModalItem.tsx b/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModalItem.tsx
deleted file mode 100644
index 43efe820bd..0000000000
--- a/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModalItem.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Box, Grid } from '@chakra-ui/react';
-import { InvText } from 'common/components/InvText/wrapper';
-import { memo } from 'react';
-
-interface HotkeysModalProps {
- hotkey: string;
- title: string;
- description?: string;
-}
-
-const HotkeysModalItem = (props: HotkeysModalProps) => {
- const { title, hotkey, description } = props;
- return (
-
-
- {title}
- {description && {description}}
-
-
- {hotkey}
-
-
- );
-};
-
-export default memo(HotkeysModalItem);
diff --git a/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts b/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts
new file mode 100644
index 0000000000..4aa0264e35
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts
@@ -0,0 +1,332 @@
+import { useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+export type HotkeyListItem = {
+ title: string;
+ desc: string;
+ hotkeys: string[][];
+};
+
+export type HotkeyGroup = {
+ title: string;
+ hotkeyListItems: HotkeyListItem[];
+};
+
+export const useHotkeyData = (): HotkeyGroup[] => {
+ const { t } = useTranslation();
+
+ const appHotkeys = useMemo(
+ () => ({
+ title: t('hotkeys.appHotkeys'),
+ hotkeyListItems: [
+ {
+ title: t('hotkeys.invoke.title'),
+ desc: t('hotkeys.invoke.desc'),
+ hotkeys: [['Ctrl', 'Enter']],
+ },
+ {
+ title: t('hotkeys.cancel.title'),
+ desc: t('hotkeys.cancel.desc'),
+ hotkeys: [['Shift', 'X']],
+ },
+ {
+ title: t('hotkeys.cancelAndClear.title'),
+ desc: t('hotkeys.cancelAndClear.desc'),
+ hotkeys: [
+ ['Shift', 'Ctrl', 'X'],
+ ['Shift', 'Cmd', 'X'],
+ ],
+ },
+ {
+ title: t('hotkeys.focusPrompt.title'),
+ desc: t('hotkeys.focusPrompt.desc'),
+ hotkeys: [['Alt', 'A']],
+ },
+ {
+ title: t('hotkeys.toggleOptions.title'),
+ desc: t('hotkeys.toggleOptions.desc'),
+ hotkeys: [['T'], ['O']],
+ },
+ {
+ title: t('hotkeys.toggleGallery.title'),
+ desc: t('hotkeys.toggleGallery.desc'),
+ hotkeys: [['G']],
+ },
+ {
+ title: t('hotkeys.toggleOptionsAndGallery.title'),
+ desc: t('hotkeys.toggleOptionsAndGallery.desc'),
+ hotkeys: [['F']],
+ },
+ {
+ title: t('hotkeys.resetOptionsAndGallery.title'),
+ desc: t('hotkeys.resetOptionsAndGallery.desc'),
+ hotkeys: [['Shift', 'R']],
+ },
+ {
+ title: t('hotkeys.maximizeWorkSpace.title'),
+ desc: t('hotkeys.maximizeWorkSpace.desc'),
+ hotkeys: [['F']],
+ },
+ {
+ title: t('hotkeys.changeTabs.title'),
+ desc: t('hotkeys.changeTabs.desc'),
+ hotkeys: [['1 - 6']],
+ },
+ ],
+ }),
+ [t]
+ );
+
+ const generalHotkeys = useMemo(
+ () => ({
+ title: t('hotkeys.generalHotkeys'),
+ hotkeyListItems: [
+ {
+ title: t('hotkeys.setPrompt.title'),
+ desc: t('hotkeys.setPrompt.desc'),
+ hotkeys: [['P']],
+ },
+ {
+ title: t('hotkeys.setSeed.title'),
+ desc: t('hotkeys.setSeed.desc'),
+ hotkeys: [['S']],
+ },
+ {
+ title: t('hotkeys.setParameters.title'),
+ desc: t('hotkeys.setParameters.desc'),
+ hotkeys: [['A']],
+ },
+ {
+ title: t('hotkeys.upscale.title'),
+ desc: t('hotkeys.upscale.desc'),
+ hotkeys: [['Shift', 'U']],
+ },
+ {
+ title: t('hotkeys.showInfo.title'),
+ desc: t('hotkeys.showInfo.desc'),
+ hotkeys: [['I']],
+ },
+ {
+ title: t('hotkeys.sendToImageToImage.title'),
+ desc: t('hotkeys.sendToImageToImage.desc'),
+ hotkeys: [['Shift', 'I']],
+ },
+ {
+ title: t('hotkeys.deleteImage.title'),
+ desc: t('hotkeys.deleteImage.desc'),
+ hotkeys: [['Del']],
+ },
+ {
+ title: t('hotkeys.closePanels.title'),
+ desc: t('hotkeys.closePanels.desc'),
+ hotkeys: [['Esc']],
+ },
+ ],
+ }),
+ [t]
+ );
+
+ const galleryHotkeys = useMemo(
+ () => ({
+ title: t('hotkeys.galleryHotkeys'),
+ hotkeyListItems: [
+ {
+ title: t('hotkeys.previousImage.title'),
+ desc: t('hotkeys.previousImage.desc'),
+ hotkeys: [['Arrow Left']],
+ },
+ {
+ title: t('hotkeys.nextImage.title'),
+ desc: t('hotkeys.nextImage.desc'),
+ hotkeys: [['Arrow Right']],
+ },
+ {
+ title: t('hotkeys.increaseGalleryThumbSize.title'),
+ desc: t('hotkeys.increaseGalleryThumbSize.desc'),
+ hotkeys: [['Shift', 'Up']],
+ },
+ {
+ title: t('hotkeys.decreaseGalleryThumbSize.title'),
+ desc: t('hotkeys.decreaseGalleryThumbSize.desc'),
+ hotkeys: [['Shift', 'Down']],
+ },
+ ],
+ }),
+ [t]
+ );
+
+ const unifiedCanvasHotkeys = useMemo(
+ () => ({
+ title: t('hotkeys.unifiedCanvasHotkeys'),
+ hotkeyListItems: [
+ {
+ title: t('hotkeys.selectBrush.title'),
+ desc: t('hotkeys.selectBrush.desc'),
+ hotkeys: [['B']],
+ },
+ {
+ title: t('hotkeys.selectEraser.title'),
+ desc: t('hotkeys.selectEraser.desc'),
+ hotkeys: [['E']],
+ },
+ {
+ title: t('hotkeys.decreaseBrushSize.title'),
+ desc: t('hotkeys.decreaseBrushSize.desc'),
+ hotkeys: [['[']],
+ },
+ {
+ title: t('hotkeys.increaseBrushSize.title'),
+ desc: t('hotkeys.increaseBrushSize.desc'),
+ hotkeys: [[']']],
+ },
+ {
+ title: t('hotkeys.decreaseBrushOpacity.title'),
+ desc: t('hotkeys.decreaseBrushOpacity.desc'),
+ hotkeys: [['Shift', '[']],
+ },
+ {
+ title: t('hotkeys.increaseBrushOpacity.title'),
+ desc: t('hotkeys.increaseBrushOpacity.desc'),
+ hotkeys: [['Shift', ']']],
+ },
+ {
+ title: t('hotkeys.moveTool.title'),
+ desc: t('hotkeys.moveTool.desc'),
+ hotkeys: [['V']],
+ },
+ {
+ title: t('hotkeys.fillBoundingBox.title'),
+ desc: t('hotkeys.fillBoundingBox.desc'),
+ hotkeys: [['Shift', 'F']],
+ },
+ {
+ title: t('hotkeys.eraseBoundingBox.title'),
+ desc: t('hotkeys.eraseBoundingBox.desc'),
+ hotkeys: [['Delete', 'Backspace']],
+ },
+ {
+ title: t('hotkeys.colorPicker.title'),
+ desc: t('hotkeys.colorPicker.desc'),
+ hotkeys: [['C']],
+ },
+ {
+ title: t('hotkeys.toggleSnap.title'),
+ desc: t('hotkeys.toggleSnap.desc'),
+ hotkeys: [['N']],
+ },
+ {
+ title: t('hotkeys.quickToggleMove.title'),
+ desc: t('hotkeys.quickToggleMove.desc'),
+ hotkeys: [['Hold Space']],
+ },
+ {
+ title: t('hotkeys.toggleLayer.title'),
+ desc: t('hotkeys.toggleLayer.desc'),
+ hotkeys: [['Q']],
+ },
+ {
+ title: t('hotkeys.clearMask.title'),
+ desc: t('hotkeys.clearMask.desc'),
+ hotkeys: [['Shift', 'C']],
+ },
+ {
+ title: t('hotkeys.hideMask.title'),
+ desc: t('hotkeys.hideMask.desc'),
+ hotkeys: [['H']],
+ },
+ {
+ title: t('hotkeys.showHideBoundingBox.title'),
+ desc: t('hotkeys.showHideBoundingBox.desc'),
+ hotkeys: [['Shift', 'H']],
+ },
+ {
+ title: t('hotkeys.mergeVisible.title'),
+ desc: t('hotkeys.mergeVisible.desc'),
+ hotkeys: [['Shift', 'M']],
+ },
+ {
+ title: t('hotkeys.saveToGallery.title'),
+ desc: t('hotkeys.saveToGallery.desc'),
+ hotkeys: [['Shift', 'S']],
+ },
+ {
+ title: t('hotkeys.copyToClipboard.title'),
+ desc: t('hotkeys.copyToClipboard.desc'),
+ hotkeys: [['Ctrl', 'C']],
+ },
+ {
+ title: t('hotkeys.downloadImage.title'),
+ desc: t('hotkeys.downloadImage.desc'),
+ hotkeys: [['Shift', 'D']],
+ },
+ {
+ title: t('hotkeys.undoStroke.title'),
+ desc: t('hotkeys.undoStroke.desc'),
+ hotkeys: [['Ctrl', 'Z']],
+ },
+ {
+ title: t('hotkeys.redoStroke.title'),
+ desc: t('hotkeys.redoStroke.desc'),
+ hotkeys: [
+ ['Ctrl', 'Shift', 'Z'],
+ ['Ctrl', 'Y'],
+ ],
+ },
+ {
+ title: t('hotkeys.resetView.title'),
+ desc: t('hotkeys.resetView.desc'),
+ hotkeys: [['R']],
+ },
+ {
+ title: t('hotkeys.previousStagingImage.title'),
+ desc: t('hotkeys.previousStagingImage.desc'),
+ hotkeys: [['Arrow Left']],
+ },
+ {
+ title: t('hotkeys.nextStagingImage.title'),
+ desc: t('hotkeys.nextStagingImage.desc'),
+ hotkeys: [['Arrow Right']],
+ },
+ {
+ title: t('hotkeys.acceptStagingImage.title'),
+ desc: t('hotkeys.acceptStagingImage.desc'),
+ hotkeys: [['Enter']],
+ },
+ ],
+ }),
+ [t]
+ );
+
+ const nodesHotkeys = useMemo(
+ () => ({
+ title: t('hotkeys.nodesHotkeys'),
+ hotkeyListItems: [
+ {
+ title: t('hotkeys.addNodes.title'),
+ desc: t('hotkeys.addNodes.desc'),
+ hotkeys: [['Shift', 'A'], ['Space']],
+ },
+ ],
+ }),
+ [t]
+ );
+
+ const hotkeyGroups = useMemo(
+ () => [
+ appHotkeys,
+ generalHotkeys,
+ galleryHotkeys,
+ unifiedCanvasHotkeys,
+ nodesHotkeys,
+ ],
+ [
+ appHotkeys,
+ generalHotkeys,
+ galleryHotkeys,
+ unifiedCanvasHotkeys,
+ nodesHotkeys,
+ ]
+ );
+
+ return hotkeyGroups;
+};