From fbccce7573d3d44c9e4c0e102495588b5b6808ce Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:12:57 +1000 Subject: [PATCH] feat(ui): staging area toolbar enhancements - Current image number & total are displayed - Left/right wrap around instead of stopping on first/last image - Disable the left/right/number buttons when showing base layer - improved translations --- invokeai/frontend/web/public/locales/en.json | 2 + .../IAICanvasStagingAreaToolbar.tsx | 124 +++++++++--------- .../src/features/canvas/store/canvasSlice.ts | 19 ++- 3 files changed, 74 insertions(+), 71 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 6e783b0567..fc9dd0cc5f 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1444,6 +1444,8 @@ "showCanvasDebugInfo": "Show Additional Canvas Info", "showGrid": "Show Grid", "showHide": "Show/Hide", + "showResultsOn": "Show Results (On)", + "showResultsOff": "Show Results (Off)", "showIntermediates": "Show Intermediates", "snapToGrid": "Snap to Grid", "undo": "Undo" diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingAreaToolbar.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingAreaToolbar.tsx index 3e617f8767..17e76f84b4 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingAreaToolbar.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasStagingAreaToolbar.tsx @@ -14,6 +14,7 @@ import { import { skipToken } from '@reduxjs/toolkit/dist/query'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import IAIButton from 'common/components/IAIButton'; import { memo, useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; @@ -23,8 +24,8 @@ import { FaCheck, FaEye, FaEyeSlash, - FaPlus, FaSave, + FaTimes, } from 'react-icons/fa'; import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { stagingAreaImageSaved } from '../store/actions'; @@ -41,10 +42,10 @@ const selector = createSelector( } = canvas; return { + currentIndex: selectedImageIndex, + total: images.length, currentStagingAreaImage: images.length > 0 ? images[selectedImageIndex] : undefined, - isOnFirstImage: selectedImageIndex === 0, - isOnLastImage: selectedImageIndex === images.length - 1, shouldShowStagingImage, shouldShowStagingOutline, }; @@ -55,10 +56,10 @@ const selector = createSelector( const IAICanvasStagingAreaToolbar = () => { const dispatch = useAppDispatch(); const { - isOnFirstImage, - isOnLastImage, currentStagingAreaImage, shouldShowStagingImage, + currentIndex, + total, } = useAppSelector(selector); const { t } = useTranslation(); @@ -71,39 +72,6 @@ const IAICanvasStagingAreaToolbar = () => { dispatch(setShouldShowStagingOutline(false)); }, [dispatch]); - useHotkeys( - ['left'], - () => { - handlePrevImage(); - }, - { - enabled: () => true, - preventDefault: true, - } - ); - - useHotkeys( - ['right'], - () => { - handleNextImage(); - }, - { - enabled: () => true, - preventDefault: true, - } - ); - - useHotkeys( - ['enter'], - () => { - handleAccept(); - }, - { - enabled: () => true, - preventDefault: true, - } - ); - const handlePrevImage = useCallback( () => dispatch(prevStagingAreaImage()), [dispatch] @@ -119,10 +87,45 @@ const IAICanvasStagingAreaToolbar = () => { [dispatch] ); + useHotkeys(['left'], handlePrevImage, { + enabled: () => true, + preventDefault: true, + }); + + useHotkeys(['right'], handleNextImage, { + enabled: () => true, + preventDefault: true, + }); + + useHotkeys(['enter'], () => handleAccept, { + enabled: () => true, + preventDefault: true, + }); + const { data: imageDTO } = useGetImageDTOQuery( currentStagingAreaImage?.imageName ?? skipToken ); + const handleToggleShouldShowStagingImage = useCallback(() => { + dispatch(setShouldShowStagingImage(!shouldShowStagingImage)); + }, [dispatch, shouldShowStagingImage]); + + const handleSaveToGallery = useCallback(() => { + if (!imageDTO) { + return; + } + + dispatch( + stagingAreaImageSaved({ + imageDTO, + }) + ); + }, [dispatch, imageDTO]); + + const handleDiscardStagingArea = useCallback(() => { + dispatch(discardStagedImages()); + }, [dispatch]); + if (!currentStagingAreaImage) { return null; } @@ -134,8 +137,8 @@ const IAICanvasStagingAreaToolbar = () => { w="100%" align="center" justify="center" - onMouseOver={handleMouseOver} - onMouseOut={handleMouseOut} + onMouseEnter={handleMouseOver} + onMouseLeave={handleMouseOut} > { icon={} onClick={handlePrevImage} colorScheme="accent" - isDisabled={isOnFirstImage} + isDisabled={!shouldShowStagingImage} /> + {`${currentIndex + 1}/${total}`} } onClick={handleNextImage} colorScheme="accent" - isDisabled={isOnLastImage} + isDisabled={!shouldShowStagingImage} /> { colorScheme="accent" /> : } - onClick={() => - dispatch(setShouldShowStagingImage(!shouldShowStagingImage)) - } + onClick={handleToggleShouldShowStagingImage} colorScheme="accent" /> { aria-label={t('unifiedCanvas.saveToGallery')} isDisabled={!imageDTO || !imageDTO.is_intermediate} icon={} - onClick={() => { - if (!imageDTO) { - return; - } - - dispatch( - stagingAreaImageSaved({ - imageDTO, - }) - ); - }} + onClick={handleSaveToGallery} colorScheme="accent" /> } - onClick={() => dispatch(discardStagedImages())} + icon={} + onClick={handleDiscardStagingArea} colorScheme="error" fontSize={20} /> diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 0016ae6599..df601e9e67 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -621,25 +621,22 @@ export const canvasSlice = createSlice({ return; } - const currentIndex = state.layerState.stagingArea.selectedImageIndex; - const length = state.layerState.stagingArea.images.length; + const nextIndex = state.layerState.stagingArea.selectedImageIndex + 1; + const lastIndex = state.layerState.stagingArea.images.length - 1; - state.layerState.stagingArea.selectedImageIndex = Math.min( - currentIndex + 1, - length - 1 - ); + state.layerState.stagingArea.selectedImageIndex = + nextIndex > lastIndex ? 0 : nextIndex; }, prevStagingAreaImage: (state) => { if (!state.layerState.stagingArea.images.length) { return; } - const currentIndex = state.layerState.stagingArea.selectedImageIndex; + const prevIndex = state.layerState.stagingArea.selectedImageIndex - 1; + const lastIndex = state.layerState.stagingArea.images.length - 1; - state.layerState.stagingArea.selectedImageIndex = Math.max( - currentIndex - 1, - 0 - ); + state.layerState.stagingArea.selectedImageIndex = + prevIndex < 0 ? lastIndex : prevIndex; }, commitStagingAreaImage: (state) => { if (!state.layerState.stagingArea.images.length) {