diff --git a/invokeai/frontend/web/src/common/components/ImageUploader.tsx b/invokeai/frontend/web/src/common/components/ImageUploader.tsx index b3d24c97bb..2b0ca04303 100644 --- a/invokeai/frontend/web/src/common/components/ImageUploader.tsx +++ b/invokeai/frontend/web/src/common/components/ImageUploader.tsx @@ -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 ( - + - {children} + {props.children} {isDragActive && isHandlingUpload && ( system.shouldEnableInformationalPopovers -); - export const InvLabel = memo( forwardRef( ( { 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 ( diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasStatusText/IAICanvasStatusTextCursorPos.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasStatusText/IAICanvasStatusTextCursorPos.tsx index 4e2a984d65..0b3df801ee 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasStatusText/IAICanvasStatusTextCursorPos.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasStatusText/IAICanvasStatusTextCursorPos.tsx @@ -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 ( diff --git a/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx b/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx index 58d577f292..597d06a1ba 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx @@ -68,7 +68,7 @@ const GallerySettingsPopover = () => { ); return ( - + { ? 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 ( ); } - return ; + return ( + + ); }; 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 ( @@ -73,20 +78,20 @@ const Content = memo(({ node, template }: ContentProps) => { p={1} gap={2} > - + - {template.title} + {props.templateTitle} - {node.data.version} + {props.nodeVersion} - + diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx index 86aada12da..a868c328e7 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx @@ -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 ( ); } - if (nes.outputs.length === 0) { + if (data.outputs.length === 0) { return ( ); @@ -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) => ( )) ) : ( - + )} diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useNodeNeedsUpdate.ts b/invokeai/frontend/web/src/features/nodes/hooks/useNodeNeedsUpdate.ts index 057b28ee37..da69150411 100644 --- a/invokeai/frontend/web/src/features/nodes/hooks/useNodeNeedsUpdate.ts +++ b/invokeai/frontend/web/src/features/nodes/hooks/useNodeNeedsUpdate.ts @@ -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; }; 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 aa5c3188d4..a33df20278 100644 --- a/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/HotkeysModal/HotkeysModal.tsx @@ -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[]) => ( + + {hotkeys.map((hotkey, i) => ( + + + {i < hotkeys.length - 1 && } + + ))} + +); + 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[]) => ( - - {hotkeys.map((hotkey, i) => ( - - - {i < hotkeys.length - 1 && } - - ))} - + const nodesHotkeys = useMemo( + () => [ + { + title: t('hotkeys.addNodes.title'), + desc: t('hotkeys.addNodes.desc'), + hotkey: 'Shift + A / Space', + }, + ], + [t] ); return (