From d9b92d19f95bf05798aec2af8958d9e9f19acf4f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 3 May 2024 12:13:35 +1000 Subject: [PATCH] feat(ui): clearer viewer/editor context switching --- invokeai/frontend/web/public/locales/en.json | 3 +- .../IAICanvasToolbar/IAICanvasToolbar.tsx | 181 ++++++++++-------- .../components/ControlLayersToolbar.tsx | 21 +- .../ImageViewer/BackToEditorButton.tsx | 24 --- .../components/ImageViewer/EditorButton.tsx | 37 ++++ .../components/ImageViewer/ImageViewer.tsx | 7 +- .../components/ImageViewer/ViewerButton.tsx | 17 ++ .../components/ImageViewer/useImageViewer.tsx | 9 - .../flow/panels/TopPanel/TopPanel.tsx | 2 + 9 files changed, 174 insertions(+), 127 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/BackToEditorButton.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/EditorButton.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerButton.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index d122892d39..a6d60fa281 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -88,6 +88,7 @@ "negativePrompt": "Negative Prompt", "discordLabel": "Discord", "dontAskMeAgain": "Don't ask me again", + "editor": "Editor", "error": "Error", "file": "File", "folder": "Folder", @@ -364,7 +365,7 @@ "bulkDownloadFailed": "Download Failed", "problemDeletingImages": "Problem Deleting Images", "problemDeletingImagesDesc": "One or more images could not be deleted", - "backToEditor": "Back to {{tab}} (Esc)" + "switchTo": "Switch to {{ tab }} (Z)" }, "hotkeys": { "searchHotkeys": "Search Hotkeys", diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx index 686577b4a7..15d38b9f76 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx @@ -22,6 +22,7 @@ import { } from 'features/canvas/store/canvasSlice'; import type { CanvasLayer } from 'features/canvas/store/canvasTypes'; import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes'; +import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton'; import { memo, useCallback, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -219,97 +220,107 @@ const IAICanvasToolbar = () => { const value = useMemo(() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0], [layer]); return ( - - - - - - + + + + + + + + + + - - + + - - } - isChecked={tool === 'move' || isStaging} - onClick={handleSelectMoveTool} - /> - : } - onClick={handleSetShouldShowBoundingBox} - isDisabled={isStaging} - /> - } - onClick={handleClickResetCanvasView} - /> - - - - } - onClick={handleMergeVisible} - isDisabled={isStaging} - /> - } - onClick={handleSaveToGallery} - isDisabled={isStaging} - /> - {isClipboardAPIAvailable && ( + } - onClick={handleCopyImageToClipboard} + aria-label={`${t('unifiedCanvas.move')} (V)`} + tooltip={`${t('unifiedCanvas.move')} (V)`} + icon={} + isChecked={tool === 'move' || isStaging} + onClick={handleSelectMoveTool} + /> + : } + onClick={handleSetShouldShowBoundingBox} isDisabled={isStaging} /> - )} - } - onClick={handleDownloadAsImage} - isDisabled={isStaging} - /> - - - - - + } + onClick={handleClickResetCanvasView} + /> + - - } - isDisabled={isStaging} - {...getUploadButtonProps()} - /> - - } - onClick={handleResetCanvas} - colorScheme="error" - isDisabled={isStaging} - /> - - - - + + } + onClick={handleMergeVisible} + isDisabled={isStaging} + /> + } + onClick={handleSaveToGallery} + isDisabled={isStaging} + /> + {isClipboardAPIAvailable && ( + } + onClick={handleCopyImageToClipboard} + isDisabled={isStaging} + /> + )} + } + onClick={handleDownloadAsImage} + isDisabled={isStaging} + /> + + + + + + + + } + isDisabled={isStaging} + {...getUploadButtonProps()} + /> + + } + onClick={handleResetCanvas} + colorScheme="error" + isDisabled={isStaging} + /> + + + + + + + + + + ); }; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx index 15a74a332a..b78910700d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx @@ -4,15 +4,26 @@ import { BrushSize } from 'features/controlLayers/components/BrushSize'; import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover'; import { ToolChooser } from 'features/controlLayers/components/ToolChooser'; import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup'; +import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton'; import { memo } from 'react'; export const ControlLayersToolbar = memo(() => { return ( - - - - - + + + + + + + + + + + + + + + ); }); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/BackToEditorButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/BackToEditorButton.tsx deleted file mode 100644 index 660840b568..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/BackToEditorButton.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Button } from '@invoke-ai/ui-library'; -import { useAppSelector } from 'app/store/storeHooks'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; -import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiArrowLeftBold } from 'react-icons/pi'; - -import { TAB_NAME_TO_TKEY, useImageViewer } from './useImageViewer'; - -export const BackToEditorButton = () => { - const { t } = useTranslation(); - const { onClose } = useImageViewer(); - const activeTabName = useAppSelector(activeTabNameSelector); - const tooltip = useMemo( - () => t('gallery.backToEditor', { tab: t(TAB_NAME_TO_TKEY[activeTabName]) }), - [t, activeTabName] - ); - - return ( - - ); -}; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/EditorButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/EditorButton.tsx new file mode 100644 index 0000000000..d6cbfbb124 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/EditorButton.tsx @@ -0,0 +1,37 @@ +import { Button } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; +import type { InvokeTabName } from 'features/ui/store/tabMap'; +import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useImageViewer } from './useImageViewer'; + +export const TAB_NAME_TO_TKEY: Record = { + generation: 'ui.tabs.generationTab', + canvas: 'ui.tabs.canvasTab', + workflows: 'ui.tabs.workflowsTab', + models: 'ui.tabs.modelsTab', + queue: 'ui.tabs.queueTab', +}; + +export const TAB_NAME_TO_TKEY_SHORT: Record = { + generation: 'ui.tabs.generation', + canvas: 'ui.tabs.canvas', + workflows: 'ui.tabs.workflows', + models: 'ui.tabs.models', + queue: 'ui.tabs.queue', +}; + +export const EditorButton = () => { + const { t } = useTranslation(); + const { onClose } = useImageViewer(); + const activeTabName = useAppSelector(activeTabNameSelector); + const tooltip = useMemo(() => t('gallery.switchTo', { tab: t(TAB_NAME_TO_TKEY[activeTabName]) }), [t, activeTabName]); + + return ( + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx index 9f3e7c5902..874464f938 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx @@ -10,9 +10,9 @@ import { AnimatePresence, motion } from 'framer-motion'; import { memo, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; -import { BackToEditorButton } from './BackToEditorButton'; import CurrentImageButtons from './CurrentImageButtons'; import CurrentImagePreview from './CurrentImagePreview'; +import { EditorButton } from './EditorButton'; const initial: AnimationProps['initial'] = { opacity: 0, @@ -39,13 +39,14 @@ export const ImageViewer = memo(() => { return isOpen; }, [isOpen, isViewerEnabled]); - useHotkeys('shift+s', onToggle, { enabled: isViewerEnabled }, [isViewerEnabled, onToggle]); + useHotkeys('z', onToggle, { enabled: isViewerEnabled }, [isViewerEnabled, onToggle]); useHotkeys('esc', onClose, { enabled: isViewerEnabled }, [isViewerEnabled, onClose]); return ( {shouldShowViewer && ( { - + diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerButton.tsx new file mode 100644 index 0000000000..a57ae9d1ee --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerButton.tsx @@ -0,0 +1,17 @@ +import { Button } from '@invoke-ai/ui-library'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useImageViewer } from './useImageViewer'; + +export const ViewerButton = () => { + const { t } = useTranslation(); + const { onOpen } = useImageViewer(); + const tooltip = useMemo(() => t('gallery.switchTo', { tab: t('common.viewer') }), [t]); + + return ( + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.tsx index 17a9dc9922..57b3697b7e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.tsx @@ -1,16 +1,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice'; -import type { InvokeTabName } from 'features/ui/store/tabMap'; import { useCallback } from 'react'; -export const TAB_NAME_TO_TKEY: Record = { - generation: 'ui.tabs.generation', - canvas: 'ui.tabs.canvas', - workflows: 'ui.tabs.workflows', - models: 'ui.tabs.models', - queue: 'ui.tabs.queue', -}; - export const useImageViewer = () => { const dispatch = useAppDispatch(); const isOpen = useAppSelector((s) => s.gallery.isImageViewerOpen); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopPanel.tsx index 93856a21c4..2a08fb840e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopPanel.tsx @@ -1,5 +1,6 @@ import { Flex, Spacer } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; +import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton'; import AddNodeButton from 'features/nodes/components/flow/panels/TopPanel/AddNodeButton'; import ClearFlowButton from 'features/nodes/components/flow/panels/TopPanel/ClearFlowButton'; import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton'; @@ -22,6 +23,7 @@ const TopCenterPanel = () => { + ); };