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; +};