mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): misc perf/rerender improvements
More efficient selectors, memoized/stable references to objects, lazy popover/menu rendering.
This commit is contained in:
parent
2ba505cce9
commit
539887b215
@ -6,9 +6,9 @@ import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import type { AnimationProps } from 'framer-motion';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import type { KeyboardEvent, ReactNode } from 'react';
|
||||
import type { KeyboardEvent, PropsWithChildren } from 'react';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import type { FileRejection } from 'react-dropzone';
|
||||
import type { Accept, FileRejection } from 'react-dropzone';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useUploadImageMutation } from 'services/api/endpoints/images';
|
||||
@ -16,6 +16,13 @@ import type { PostUploadAction } from 'services/api/types';
|
||||
|
||||
import ImageUploadOverlay from './ImageUploadOverlay';
|
||||
|
||||
const accept: Accept = {
|
||||
'image/png': ['.png'],
|
||||
'image/jpeg': ['.jpg', '.jpeg', '.png'],
|
||||
};
|
||||
|
||||
const dropzoneRootProps = { style: {} };
|
||||
|
||||
const selector = createMemoizedSelector(
|
||||
[stateSelector, activeTabNameSelector],
|
||||
({ gallery }, activeTabName) => {
|
||||
@ -38,12 +45,7 @@ const selector = createMemoizedSelector(
|
||||
}
|
||||
);
|
||||
|
||||
type ImageUploaderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const ImageUploader = (props: ImageUploaderProps) => {
|
||||
const { children } = props;
|
||||
const ImageUploader = (props: PropsWithChildren) => {
|
||||
const { autoAddBoardId, postUploadAction } = useAppSelector(selector);
|
||||
const toaster = useAppToaster();
|
||||
const { t } = useTranslation();
|
||||
@ -111,7 +113,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
||||
isDragActive,
|
||||
inputRef,
|
||||
} = useDropzone({
|
||||
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
||||
accept,
|
||||
noClick: true,
|
||||
onDrop,
|
||||
onDragOver,
|
||||
@ -149,9 +151,9 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box {...getRootProps({ style: {} })} onKeyDown={handleKeyDown}>
|
||||
<Box {...getRootProps(dropzoneRootProps)} onKeyDown={handleKeyDown}>
|
||||
<input {...getInputProps()} />
|
||||
{children}
|
||||
{props.children}
|
||||
<AnimatePresence>
|
||||
{isDragActive && isHandlingUpload && (
|
||||
<motion.div
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { Flex, FormLabel, forwardRef } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||
import { InvControlGroupContext } from 'common/components/InvControl/InvControlGroup';
|
||||
@ -8,18 +6,16 @@ import { memo, useContext } from 'react';
|
||||
|
||||
import type { InvLabelProps } from './types';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
({ system }) => system.shouldEnableInformationalPopovers
|
||||
);
|
||||
|
||||
export const InvLabel = memo(
|
||||
forwardRef<InvLabelProps, typeof FormLabel>(
|
||||
(
|
||||
{ feature, renderInfoPopoverInPortal, children, ...rest }: InvLabelProps,
|
||||
ref
|
||||
) => {
|
||||
const shouldEnableInformationalPopovers = useAppSelector(selector);
|
||||
const shouldEnableInformationalPopovers = useAppSelector(
|
||||
(state) => state.system.shouldEnableInformationalPopovers
|
||||
);
|
||||
|
||||
const ctx = useContext(InvControlGroupContext);
|
||||
if (feature && shouldEnableInformationalPopovers) {
|
||||
return (
|
||||
|
@ -1,30 +1,23 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import roundToHundreth from 'features/canvas/util/roundToHundreth';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const cursorPositionSelector = createMemoizedSelector(
|
||||
[stateSelector],
|
||||
({ canvas }) => {
|
||||
const { cursorPosition } = canvas;
|
||||
const cursorPositionSelector = createSelector([stateSelector], ({ canvas }) => {
|
||||
const { cursorPosition } = canvas;
|
||||
|
||||
const { cursorX, cursorY } = cursorPosition
|
||||
? { cursorX: cursorPosition.x, cursorY: cursorPosition.y }
|
||||
: { cursorX: -1, cursorY: -1 };
|
||||
const { cursorX, cursorY } = cursorPosition
|
||||
? { cursorX: cursorPosition.x, cursorY: cursorPosition.y }
|
||||
: { cursorX: -1, cursorY: -1 };
|
||||
|
||||
return {
|
||||
cursorCoordinatesString: `(${roundToHundreth(cursorX)}, ${roundToHundreth(
|
||||
cursorY
|
||||
)})`,
|
||||
};
|
||||
}
|
||||
);
|
||||
return `(${roundToHundreth(cursorX)}, ${roundToHundreth(cursorY)})`;
|
||||
});
|
||||
|
||||
const IAICanvasStatusTextCursorPos = () => {
|
||||
const { cursorCoordinatesString } = useAppSelector(cursorPositionSelector);
|
||||
const cursorCoordinatesString = useAppSelector(cursorPositionSelector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
|
@ -68,7 +68,7 @@ const GallerySettingsPopover = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<InvPopover>
|
||||
<InvPopover isLazy>
|
||||
<InvPopoverTrigger>
|
||||
<InvIconButton
|
||||
tooltip={t('gallery.gallerySettings')}
|
||||
|
@ -7,13 +7,9 @@ import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import NotesTextarea from 'features/nodes/components/flow/nodes/Invocation/NotesTextarea';
|
||||
import type {
|
||||
InvocationNode,
|
||||
InvocationTemplate,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import EditableNodeTitle from './details/EditableNodeTitle';
|
||||
@ -30,38 +26,47 @@ const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
|
||||
? nodes.nodeTemplates[lastSelectedNode.data.type]
|
||||
: undefined;
|
||||
|
||||
if (!isInvocationNode(lastSelectedNode) || !lastSelectedNodeTemplate) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
node: lastSelectedNode,
|
||||
template: lastSelectedNodeTemplate,
|
||||
nodeId: lastSelectedNode.data.id,
|
||||
nodeVersion: lastSelectedNode.data.version,
|
||||
templateTitle: lastSelectedNodeTemplate.title,
|
||||
};
|
||||
});
|
||||
|
||||
const InspectorDetailsTab = () => {
|
||||
const { node, template } = useAppSelector(selector);
|
||||
const data = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!template || !isInvocationNode(node)) {
|
||||
if (!data) {
|
||||
return (
|
||||
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||
);
|
||||
}
|
||||
|
||||
return <Content node={node} template={template} />;
|
||||
return (
|
||||
<Content
|
||||
nodeId={data.nodeId}
|
||||
nodeVersion={data.nodeVersion}
|
||||
templateTitle={data.templateTitle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InspectorDetailsTab);
|
||||
|
||||
type ContentProps = {
|
||||
node: InvocationNode;
|
||||
template: InvocationTemplate;
|
||||
nodeId: string;
|
||||
nodeVersion: string;
|
||||
templateTitle: string;
|
||||
};
|
||||
|
||||
const Content = memo(({ node, template }: ContentProps) => {
|
||||
const Content = memo((props: ContentProps) => {
|
||||
const { t } = useTranslation();
|
||||
const needsUpdate = useMemo(
|
||||
() => getNeedsUpdate(node, template),
|
||||
[node, template]
|
||||
);
|
||||
const needsUpdate = useNodeNeedsUpdate(props.nodeId);
|
||||
return (
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ScrollableContent>
|
||||
@ -73,20 +78,20 @@ const Content = memo(({ node, template }: ContentProps) => {
|
||||
p={1}
|
||||
gap={2}
|
||||
>
|
||||
<EditableNodeTitle nodeId={node.data.id} />
|
||||
<EditableNodeTitle nodeId={props.nodeId} />
|
||||
<HStack>
|
||||
<InvControl label={t('nodes.nodeType')}>
|
||||
<InvText fontSize="sm" fontWeight="semibold">
|
||||
{template.title}
|
||||
{props.templateTitle}
|
||||
</InvText>
|
||||
</InvControl>
|
||||
<InvControl label={t('nodes.nodeVersion')} isInvalid={needsUpdate}>
|
||||
<InvText fontSize="sm" fontWeight="semibold">
|
||||
{node.data.version}
|
||||
{props.nodeVersion}
|
||||
</InvText>
|
||||
</InvControl>
|
||||
</HStack>
|
||||
<NotesTextarea nodeId={node.data.id} />
|
||||
<NotesTextarea nodeId={props.nodeId} />
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
</Box>
|
||||
|
@ -28,24 +28,31 @@ const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
|
||||
const nes =
|
||||
nodes.nodeExecutionStates[lastSelectedNodeId ?? '__UNKNOWN_NODE__'];
|
||||
|
||||
if (
|
||||
!isInvocationNode(lastSelectedNode) ||
|
||||
!nes ||
|
||||
!lastSelectedNodeTemplate
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
node: lastSelectedNode,
|
||||
template: lastSelectedNodeTemplate,
|
||||
nes,
|
||||
outputs: nes.outputs,
|
||||
outputType: lastSelectedNodeTemplate.outputType,
|
||||
};
|
||||
});
|
||||
|
||||
const InspectorOutputsTab = () => {
|
||||
const { node, template, nes } = useAppSelector(selector);
|
||||
const data = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!node || !nes || !isInvocationNode(node)) {
|
||||
if (!data) {
|
||||
return (
|
||||
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||
);
|
||||
}
|
||||
|
||||
if (nes.outputs.length === 0) {
|
||||
if (data.outputs.length === 0) {
|
||||
return (
|
||||
<IAINoContentFallback label={t('nodes.noOutputRecorded')} icon={null} />
|
||||
);
|
||||
@ -63,15 +70,15 @@ const InspectorOutputsTab = () => {
|
||||
h="full"
|
||||
w="full"
|
||||
>
|
||||
{template?.outputType === 'image_output' ? (
|
||||
nes.outputs.map((result, i) => (
|
||||
{data.outputType === 'image_output' ? (
|
||||
data.outputs.map((result, i) => (
|
||||
<ImageOutputPreview
|
||||
key={getKey(result, i)}
|
||||
output={result as ImageOutput}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<DataViewer data={nes.outputs} label={t('nodes.nodeOutputs')} />
|
||||
<DataViewer data={data.outputs} label={t('nodes.nodeOutputs')} />
|
||||
)}
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
|
@ -11,20 +11,15 @@ export const useNodeNeedsUpdate = (nodeId: string) => {
|
||||
createMemoizedSelector(stateSelector, ({ nodes }) => {
|
||||
const node = nodes.nodes.find((node) => node.id === nodeId);
|
||||
const template = nodes.nodeTemplates[node?.data.type ?? ''];
|
||||
return { node, template };
|
||||
if (isInvocationNode(node) && template) {
|
||||
return getNeedsUpdate(node, template);
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
[nodeId]
|
||||
);
|
||||
|
||||
const { node, template } = useAppSelector(selector);
|
||||
|
||||
const needsUpdate = useMemo(
|
||||
() =>
|
||||
isInvocationNode(node) && template
|
||||
? getNeedsUpdate(node, template)
|
||||
: false,
|
||||
[node, template]
|
||||
);
|
||||
const needsUpdate = useAppSelector(selector);
|
||||
|
||||
return needsUpdate;
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
InvModalOverlay,
|
||||
} from 'common/components/InvModal/wrapper';
|
||||
import type { ReactElement } from 'react';
|
||||
import { cloneElement, memo } from 'react';
|
||||
import { cloneElement, memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import HotkeysModalItem from './HotkeysModalItem';
|
||||
@ -32,6 +32,21 @@ type HotkeyList = {
|
||||
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,
|
||||
@ -41,264 +56,264 @@ const HotkeysModal = ({ children }: HotkeysModalProps) => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const appHotkeys = [
|
||||
{
|
||||
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.focusPrompt.title'),
|
||||
desc: t('hotkeys.focusPrompt.desc'),
|
||||
hotkey: 'Alt+A',
|
||||
},
|
||||
{
|
||||
title: t('hotkeys.toggleOptions.title'),
|
||||
desc: t('hotkeys.toggleOptions.desc'),
|
||||
hotkey: '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',
|
||||
},
|
||||
];
|
||||
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.focusPrompt.title'),
|
||||
desc: t('hotkeys.focusPrompt.desc'),
|
||||
hotkey: 'Alt+A',
|
||||
},
|
||||
{
|
||||
title: t('hotkeys.toggleOptions.title'),
|
||||
desc: t('hotkeys.toggleOptions.desc'),
|
||||
hotkey: '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 = [
|
||||
{
|
||||
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',
|
||||
},
|
||||
];
|
||||
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 = [
|
||||
{
|
||||
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',
|
||||
},
|
||||
];
|
||||
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 = [
|
||||
{
|
||||
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',
|
||||
},
|
||||
];
|
||||
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 = [
|
||||
{
|
||||
title: t('hotkeys.addNodes.title'),
|
||||
desc: t('hotkeys.addNodes.desc'),
|
||||
hotkey: 'Shift + A / Space',
|
||||
},
|
||||
];
|
||||
|
||||
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 nodesHotkeys = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t('hotkeys.addNodes.title'),
|
||||
desc: t('hotkeys.addNodes.desc'),
|
||||
hotkey: 'Shift + A / Space',
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user