feat(ui): reworked hotkeys modal

- Displays all as list
- Uses chakra `Kbd` component for keys
- Provides search box
This commit is contained in:
psychedelicious 2024-01-03 21:18:51 +11:00 committed by Kent Keirsey
parent 6bfe994622
commit 7caaf40835
5 changed files with 530 additions and 428 deletions

View File

@ -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"

View File

@ -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 (
<Flex flexDir="column" gap={2} px={2}>
<Flex lineHeight={1} gap={1} alignItems="center">
<InvText fontWeight="semibold">{title}</InvText>
<Spacer />
{hotkeys.map((hotkey, index) => {
return (
<>
{hotkey.map((key, index) => (
<>
<Kbd
textTransform="lowercase"
key={`${hotkey}-${key}-${index}`}
>
{key}
</Kbd>
{index !== hotkey.length - 1 && (
<InvText as="span" fontWeight="semibold">
+
</InvText>
)}
</>
))}
{index !== hotkeys.length - 1 && (
<InvText as="span" px={2} variant='subtext' fontWeight="semibold">
or
</InvText>
)}
</>
);
})}
</Flex>
<InvText variant="subtext">{description}</InvText>
</Flex>
);
};
export default memo(HotkeyListItem);

View File

@ -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[]) => (
<Flex flexDir="column" gap={4}>
{hotkeys.map((hotkey, i) => (
<Flex flexDir="column" px={2} gap={4} key={i}>
<HotkeysModalItem
title={hotkey.title}
description={hotkey.desc}
hotkey={hotkey.hotkey}
/>
{i < hotkeys.length - 1 && <Divider />}
</Flex>
))}
</Flex>
);
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<ChangeEventHandler<HTMLInputElement>>(
(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,
})}
<InvModal isOpen={isHotkeyModalOpen} onClose={onHotkeysModalClose}>
<InvModal
isOpen={isHotkeyModalOpen}
onClose={onHotkeysModalClose}
isCentered
size="2xl"
>
<InvModalOverlay />
<InvModalContent>
<InvModalContent maxH="80vh" h="80vh">
<InvModalHeader>{t('hotkeys.keyboardShortcuts')}</InvModalHeader>
<InvModalCloseButton />
<InvModalBody>
<InvAccordion allowMultiple>
<Flex flexDir="column" gap={2}>
<InvAccordionItem>
<InvAccordionButton>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
<h2>{t('hotkeys.appHotkeys')}</h2>
<InvAccordionIcon />
</Flex>
</InvAccordionButton>
<InvAccordionPanel>
{renderHotkeyModalItems(appHotkeys)}
</InvAccordionPanel>
</InvAccordionItem>
<InvModalBody display="flex" flexDir="column" gap={4}>
<InputGroup>
<InvInput
placeholder={t('hotkeys.searchHotkeys')}
value={hotkeyFilter}
onChange={onChange}
/>
{hotkeyFilter.length && (
<InputRightElement h="full" pe={2}>
<InvIconButton
onClick={clearHotkeyFilter}
size="sm"
variant="ghost"
aria-label={t('hotkeys.clearSearch')}
icon={<CloseIcon boxSize={3} />}
/>
</InputRightElement>
)}
</InputGroup>
<InvAccordionItem>
<InvAccordionButton>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
<h2>{t('hotkeys.generalHotkeys')}</h2>
<InvAccordionIcon />
</Flex>
</InvAccordionButton>
<InvAccordionPanel>
{renderHotkeyModalItems(generalHotkeys)}
</InvAccordionPanel>
</InvAccordionItem>
<InvAccordionItem>
<InvAccordionButton>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
<h2>{t('hotkeys.galleryHotkeys')}</h2>
<InvAccordionIcon />
</Flex>
</InvAccordionButton>
<InvAccordionPanel>
{renderHotkeyModalItems(galleryHotkeys)}
</InvAccordionPanel>
</InvAccordionItem>
<InvAccordionItem>
<InvAccordionButton>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
<h2>{t('hotkeys.unifiedCanvasHotkeys')}</h2>
<InvAccordionIcon />
</Flex>
</InvAccordionButton>
<InvAccordionPanel>
{renderHotkeyModalItems(unifiedCanvasHotkeys)}
</InvAccordionPanel>
</InvAccordionItem>
<InvAccordionItem>
<InvAccordionButton>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
<h2>{t('hotkeys.nodesHotkeys')}</h2>
<InvAccordionIcon />
</Flex>
</InvAccordionButton>
<InvAccordionPanel>
{renderHotkeyModalItems(nodesHotkeys)}
</InvAccordionPanel>
</InvAccordionItem>
</Flex>
</InvAccordion>
<ScrollableContent>
{filteredHotkeyGroups.map((group) => (
<Flex key={group.title} pb={4} flexDir="column">
<Flex
ps={2}
pb={4}
position="sticky"
zIndex={1}
top={0}
bg="base.800"
>
<InvHeading size="sm">{group.title}</InvHeading>
</Flex>
<Flex
key={group.title}
p={4}
borderRadius="base"
bg="base.750"
flexDir="column"
gap={4}
>
{group.hotkeyListItems.map((hotkey, i) => (
<Fragment key={i}>
<HotkeyListItem
title={hotkey.title}
description={hotkey.desc}
hotkeys={hotkey.hotkeys}
/>
{i < group.hotkeyListItems.length - 1 && <Divider />}
</Fragment>
))}
</Flex>
</Flex>
))}
{!filteredHotkeyGroups.length && (
<IAINoContentFallback
label={t('hotkeys.noHotkeysFound')}
icon={null}
/>
)}
</ScrollableContent>
</InvModalBody>
<InvModalFooter />
</InvModalContent>

View File

@ -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 (
<Grid
gridTemplateColumns="auto max-content"
justifyContent="space-between"
alignItems="center"
>
<Grid>
<InvText fontWeight="semibold">{title}</InvText>
{description && <InvText variant="subtext">{description}</InvText>}
</Grid>
<Box fontSize="sm" fontWeight="semibold" px={2} py={1}>
{hotkey}
</Box>
</Grid>
);
};
export default memo(HotkeysModalItem);

View File

@ -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<HotkeyGroup>(
() => ({
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<HotkeyGroup>(
() => ({
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<HotkeyGroup>(
() => ({
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<HotkeyGroup>(
() => ({
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<HotkeyGroup>(
() => ({
title: t('hotkeys.nodesHotkeys'),
hotkeyListItems: [
{
title: t('hotkeys.addNodes.title'),
desc: t('hotkeys.addNodes.desc'),
hotkeys: [['Shift', 'A'], ['Space']],
},
],
}),
[t]
);
const hotkeyGroups = useMemo<HotkeyGroup[]>(
() => [
appHotkeys,
generalHotkeys,
galleryHotkeys,
unifiedCanvasHotkeys,
nodesHotkeys,
],
[
appHotkeys,
generalHotkeys,
galleryHotkeys,
unifiedCanvasHotkeys,
nodesHotkeys,
]
);
return hotkeyGroups;
};