From f25e28a933aab2a669f89985ae429f81fa8dde5f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:58:57 +1000 Subject: [PATCH] feat(ui): split up StagingAreaToolbar --- .../StagingArea/StagingAreaToolbar.tsx | 224 ++---------------- .../StagingAreaToolbarAcceptButton.tsx | 57 +++++ .../StagingAreaToolbarDiscardAllButton.tsx | 28 +++ ...tagingAreaToolbarDiscardSelectedButton.tsx | 46 ++++ .../StagingAreaToolbarImageCountButton.tsx | 25 ++ .../StagingAreaToolbarNextButton.tsx | 50 ++++ .../StagingAreaToolbarPrevButton.tsx | 50 ++++ ...AreaToolbarSaveSelectedToGalleryButton.tsx | 50 ++++ ...gingAreaToolbarToggleShowResultsButton.tsx | 30 +++ .../store/canvasStagingAreaSlice.ts | 12 + 10 files changed, 367 insertions(+), 205 deletions(-) create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarImageCountButton.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarNextButton.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarPrevButton.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarSaveSelectedToGalleryButton.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarToggleShowResultsButton.tsx diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbar.tsx index f0844db414..7f22485d48 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbar.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbar.tsx @@ -1,217 +1,31 @@ -import { Button, ButtonGroup, IconButton } from '@invoke-ai/ui-library'; -import { useStore } from '@nanostores/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { INTERACTION_SCOPES, useScopeOnMount } from 'common/hooks/interactionScopes'; -import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; -import { - selectCanvasStagingAreaSlice, - stagingAreaImageAccepted, - stagingAreaNextStagedImageSelected, - stagingAreaPrevStagedImageSelected, - stagingAreaReset, - stagingAreaStagedImageDiscarded, -} from 'features/controlLayers/store/canvasStagingAreaSlice'; -import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors'; -import { memo, useCallback, useMemo } from 'react'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { - PiArrowLeftBold, - PiArrowRightBold, - PiCheckBold, - PiEyeBold, - PiEyeSlashBold, - PiFloppyDiskBold, - PiTrashSimpleBold, - PiXBold, -} from 'react-icons/pi'; -import { useAddImagesToBoardMutation, useChangeImageIsIntermediateMutation } from 'services/api/endpoints/images'; - -const selectStagedImageIndex = createSelector( - selectCanvasStagingAreaSlice, - (stagingArea) => stagingArea.selectedStagedImageIndex -); - -const selectSelectedImage = createSelector( - [selectCanvasStagingAreaSlice, selectStagedImageIndex], - (stagingArea, index) => stagingArea.stagedImages[index] ?? null -); - -const selectImageCount = createSelector(selectCanvasStagingAreaSlice, (stagingArea) => stagingArea.stagedImages.length); +import { ButtonGroup } from '@invoke-ai/ui-library'; +import { useScopeOnMount } from 'common/hooks/interactionScopes'; +import { StagingAreaToolbarAcceptButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton'; +import { StagingAreaToolbarDiscardAllButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton'; +import { StagingAreaToolbarDiscardSelectedButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton'; +import { StagingAreaToolbarImageCountButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarImageCountButton'; +import { StagingAreaToolbarNextButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarNextButton'; +import { StagingAreaToolbarPrevButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarPrevButton'; +import { StagingAreaToolbarSaveSelectedToGalleryButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarSaveSelectedToGalleryButton'; +import { StagingAreaToolbarToggleShowResultsButton } from 'features/controlLayers/components/StagingArea/StagingAreaToolbarToggleShowResultsButton'; +import { memo } from 'react'; export const StagingAreaToolbar = memo(() => { - const dispatch = useAppDispatch(); - const canvasManager = useCanvasManager(); - const autoAddBoardId = useAppSelector(selectAutoAddBoardId); - const index = useAppSelector(selectStagedImageIndex); - const selectedImage = useAppSelector(selectSelectedImage); - const imageCount = useAppSelector(selectImageCount); - const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage); - const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive); - const [addImageToBoard] = useAddImagesToBoardMutation(); - const [changeIsImageIntermediate] = useChangeImageIsIntermediateMutation(); useScopeOnMount('stagingArea'); - const { t } = useTranslation(); - - const onPrev = useCallback(() => { - dispatch(stagingAreaPrevStagedImageSelected()); - }, [dispatch]); - - const onNext = useCallback(() => { - dispatch(stagingAreaNextStagedImageSelected()); - }, [dispatch]); - - const onAccept = useCallback(() => { - if (!selectedImage) { - return; - } - dispatch(stagingAreaImageAccepted({ index })); - }, [dispatch, index, selectedImage]); - - const onDiscardOne = useCallback(() => { - if (!selectedImage) { - return; - } - if (imageCount === 1) { - dispatch(stagingAreaReset()); - } else { - dispatch(stagingAreaStagedImageDiscarded({ index })); - } - }, [selectedImage, imageCount, dispatch, index]); - - const onDiscardAll = useCallback(() => { - dispatch(stagingAreaReset()); - }, [dispatch]); - - const onToggleShouldShowStagedImage = useCallback(() => { - canvasManager.stagingArea.$shouldShowStagedImage.set(!shouldShowStagedImage); - }, [canvasManager.stagingArea.$shouldShowStagedImage, shouldShowStagedImage]); - - const onSaveStagingImage = useCallback(async () => { - if (!selectedImage) { - return; - } - if (autoAddBoardId !== 'none') { - await addImageToBoard({ imageDTOs: [selectedImage.imageDTO], board_id: autoAddBoardId }).unwrap(); - // The changeIsImageIntermediate request will use the board_id on this specific imageDTO object, so we need to - // update it before making the request - else the optimistic board updates will get out of whack. - changeIsImageIntermediate({ - imageDTO: { ...selectedImage.imageDTO, board_id: autoAddBoardId }, - is_intermediate: false, - }); - } else { - changeIsImageIntermediate({ - imageDTO: selectedImage.imageDTO, - is_intermediate: false, - }); - } - }, [addImageToBoard, autoAddBoardId, changeIsImageIntermediate, selectedImage]); - - useHotkeys( - ['left'], - onPrev, - { - preventDefault: true, - enabled: isCanvasActive && shouldShowStagedImage && imageCount > 1, - }, - [isCanvasActive, shouldShowStagedImage, imageCount] - ); - - useHotkeys( - ['right'], - onNext, - { - preventDefault: true, - enabled: isCanvasActive && shouldShowStagedImage && imageCount > 1, - }, - [isCanvasActive, shouldShowStagedImage, imageCount] - ); - - useHotkeys( - ['enter'], - onAccept, - { - preventDefault: true, - enabled: isCanvasActive && shouldShowStagedImage && imageCount > 1, - }, - [isCanvasActive, shouldShowStagedImage, imageCount] - ); - - const counterText = useMemo(() => { - if (imageCount > 0) { - return `${(index ?? 0) + 1} of ${imageCount}`; - } else { - return `0 of 0`; - } - }, [imageCount, index]); - return ( <> - } - onClick={onPrev} - colorScheme="invokeBlue" - isDisabled={imageCount <= 1 || !shouldShowStagedImage} - /> - - } - onClick={onNext} - colorScheme="invokeBlue" - isDisabled={imageCount <= 1 || !shouldShowStagedImage} - /> + + + - } - onClick={onAccept} - colorScheme="invokeBlue" - isDisabled={!selectedImage} - /> - : } - onClick={onToggleShouldShowStagedImage} - colorScheme="invokeBlue" - /> - } - onClick={onSaveStagingImage} - colorScheme="invokeBlue" - isDisabled={!selectedImage || !selectedImage.imageDTO.is_intermediate} - /> - } - onClick={onDiscardOne} - colorScheme="invokeBlue" - fontSize={16} - isDisabled={!selectedImage} - /> - } - onClick={onDiscardAll} - colorScheme="error" - fontSize={16} - /> + + + + + ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton.tsx new file mode 100644 index 0000000000..9c6ddc4c0b --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarAcceptButton.tsx @@ -0,0 +1,57 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes'; +import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; +import { + selectImageCount, + selectSelectedImage, + selectStagedImageIndex, + stagingAreaImageAccepted, +} from 'features/controlLayers/store/canvasStagingAreaSlice'; +import { memo, useCallback } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { useTranslation } from 'react-i18next'; +import { PiCheckBold } from 'react-icons/pi'; + +export const StagingAreaToolbarAcceptButton = memo(() => { + const dispatch = useAppDispatch(); + const canvasManager = useCanvasManager(); + const index = useAppSelector(selectStagedImageIndex); + const selectedImage = useAppSelector(selectSelectedImage); + const imageCount = useAppSelector(selectImageCount); + const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage); + const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive); + + const { t } = useTranslation(); + + const acceptSelected = useCallback(() => { + if (!selectedImage) { + return; + } + dispatch(stagingAreaImageAccepted({ index })); + }, [dispatch, index, selectedImage]); + + useHotkeys( + ['enter'], + acceptSelected, + { + preventDefault: true, + enabled: isCanvasActive && shouldShowStagedImage && imageCount > 1, + }, + [isCanvasActive, shouldShowStagedImage, imageCount] + ); + + return ( + } + onClick={acceptSelected} + colorScheme="invokeBlue" + isDisabled={!selectedImage} + /> + ); +}); + +StagingAreaToolbarAcceptButton.displayName = 'StagingAreaToolbarAcceptButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton.tsx new file mode 100644 index 0000000000..0ebacb1d70 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardAllButton.tsx @@ -0,0 +1,28 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { stagingAreaReset } from 'features/controlLayers/store/canvasStagingAreaSlice'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiTrashSimpleBold } from 'react-icons/pi'; + +export const StagingAreaToolbarDiscardAllButton = memo(() => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const discardAll = useCallback(() => { + dispatch(stagingAreaReset()); + }, [dispatch]); + + return ( + } + onClick={discardAll} + colorScheme="error" + fontSize={16} + /> + ); +}); + +StagingAreaToolbarDiscardAllButton.displayName = 'StagingAreaToolbarDiscardAllButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton.tsx new file mode 100644 index 0000000000..4845e90403 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarDiscardSelectedButton.tsx @@ -0,0 +1,46 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + selectImageCount, + selectSelectedImage, + selectStagedImageIndex, + stagingAreaReset, + stagingAreaStagedImageDiscarded, +} from 'features/controlLayers/store/canvasStagingAreaSlice'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiXBold } from 'react-icons/pi'; + +export const StagingAreaToolbarDiscardSelectedButton = memo(() => { + const dispatch = useAppDispatch(); + const index = useAppSelector(selectStagedImageIndex); + const selectedImage = useAppSelector(selectSelectedImage); + const imageCount = useAppSelector(selectImageCount); + + const { t } = useTranslation(); + + const discardSelected = useCallback(() => { + if (!selectedImage) { + return; + } + if (imageCount === 1) { + dispatch(stagingAreaReset()); + } else { + dispatch(stagingAreaStagedImageDiscarded({ index })); + } + }, [selectedImage, imageCount, dispatch, index]); + + return ( + } + onClick={discardSelected} + colorScheme="invokeBlue" + fontSize={16} + isDisabled={!selectedImage} + /> + ); +}); + +StagingAreaToolbarDiscardSelectedButton.displayName = 'StagingAreaToolbarDiscardSelectedButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarImageCountButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarImageCountButton.tsx new file mode 100644 index 0000000000..d408bf1c90 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarImageCountButton.tsx @@ -0,0 +1,25 @@ +import { Button } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; +import { selectImageCount, selectStagedImageIndex } from 'features/controlLayers/store/canvasStagingAreaSlice'; +import { memo, useMemo } from 'react'; + +export const StagingAreaToolbarImageCountButton = memo(() => { + const index = useAppSelector(selectStagedImageIndex); + const imageCount = useAppSelector(selectImageCount); + + const counterText = useMemo(() => { + if (imageCount > 0) { + return `${(index ?? 0) + 1} of ${imageCount}`; + } else { + return `0 of 0`; + } + }, [imageCount, index]); + + return ( + + ); +}); + +StagingAreaToolbarImageCountButton.displayName = 'StagingAreaToolbarImageCountButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarNextButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarNextButton.tsx new file mode 100644 index 0000000000..609353709c --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarNextButton.tsx @@ -0,0 +1,50 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes'; +import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; +import { + selectImageCount, + stagingAreaNextStagedImageSelected, +} from 'features/controlLayers/store/canvasStagingAreaSlice'; +import { memo, useCallback } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { useTranslation } from 'react-i18next'; +import { PiArrowRightBold } from 'react-icons/pi'; + +export const StagingAreaToolbarNextButton = memo(() => { + const dispatch = useAppDispatch(); + const canvasManager = useCanvasManager(); + const imageCount = useAppSelector(selectImageCount); + const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage); + const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive); + + const { t } = useTranslation(); + + const selectNext = useCallback(() => { + dispatch(stagingAreaNextStagedImageSelected()); + }, [dispatch]); + + useHotkeys( + ['right'], + selectNext, + { + preventDefault: true, + enabled: isCanvasActive && shouldShowStagedImage && imageCount > 1, + }, + [isCanvasActive, shouldShowStagedImage, imageCount] + ); + + return ( + } + onClick={selectNext} + colorScheme="invokeBlue" + isDisabled={imageCount <= 1 || !shouldShowStagedImage} + /> + ); +}); + +StagingAreaToolbarNextButton.displayName = 'StagingAreaToolbarNextButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarPrevButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarPrevButton.tsx new file mode 100644 index 0000000000..23a4387a85 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarPrevButton.tsx @@ -0,0 +1,50 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes'; +import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; +import { + selectImageCount, + stagingAreaPrevStagedImageSelected, +} from 'features/controlLayers/store/canvasStagingAreaSlice'; +import { memo, useCallback } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { useTranslation } from 'react-i18next'; +import { PiArrowLeftBold } from 'react-icons/pi'; + +export const StagingAreaToolbarPrevButton = memo(() => { + const dispatch = useAppDispatch(); + const canvasManager = useCanvasManager(); + const imageCount = useAppSelector(selectImageCount); + const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage); + const isCanvasActive = useStore(INTERACTION_SCOPES.canvas.$isActive); + + const { t } = useTranslation(); + + const selectPrev = useCallback(() => { + dispatch(stagingAreaPrevStagedImageSelected()); + }, [dispatch]); + + useHotkeys( + ['left'], + selectPrev, + { + preventDefault: true, + enabled: isCanvasActive && shouldShowStagedImage && imageCount > 1, + }, + [isCanvasActive, shouldShowStagedImage, imageCount] + ); + + return ( + } + onClick={selectPrev} + colorScheme="invokeBlue" + isDisabled={imageCount <= 1 || !shouldShowStagedImage} + /> + ); +}); + +StagingAreaToolbarPrevButton.displayName = 'StagingAreaToolbarPrevButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarSaveSelectedToGalleryButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarSaveSelectedToGalleryButton.tsx new file mode 100644 index 0000000000..a8ae72c3fb --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarSaveSelectedToGalleryButton.tsx @@ -0,0 +1,50 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; +import { selectSelectedImage } from 'features/controlLayers/store/canvasStagingAreaSlice'; +import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiFloppyDiskBold } from 'react-icons/pi'; +import { useAddImagesToBoardMutation, useChangeImageIsIntermediateMutation } from 'services/api/endpoints/images'; + +export const StagingAreaToolbarSaveSelectedToGalleryButton = memo(() => { + const autoAddBoardId = useAppSelector(selectAutoAddBoardId); + const selectedImage = useAppSelector(selectSelectedImage); + const [addImageToBoard] = useAddImagesToBoardMutation(); + const [changeIsImageIntermediate] = useChangeImageIsIntermediateMutation(); + + const { t } = useTranslation(); + + const saveSelectedImageToGallery = useCallback(async () => { + if (!selectedImage) { + return; + } + if (autoAddBoardId !== 'none') { + await addImageToBoard({ imageDTOs: [selectedImage.imageDTO], board_id: autoAddBoardId }).unwrap(); + // The changeIsImageIntermediate request will use the board_id on this specific imageDTO object, so we need to + // update it before making the request - else the optimistic board updates will get out of whack. + changeIsImageIntermediate({ + imageDTO: { ...selectedImage.imageDTO, board_id: autoAddBoardId }, + is_intermediate: false, + }); + } else { + changeIsImageIntermediate({ + imageDTO: selectedImage.imageDTO, + is_intermediate: false, + }); + } + }, [addImageToBoard, autoAddBoardId, changeIsImageIntermediate, selectedImage]); + + return ( + } + onClick={saveSelectedImageToGallery} + colorScheme="invokeBlue" + isDisabled={!selectedImage || !selectedImage.imageDTO.is_intermediate} + /> + ); +}); + +StagingAreaToolbarSaveSelectedToGalleryButton.displayName = 'StagingAreaToolbarSaveSelectedToGalleryButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarToggleShowResultsButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarToggleShowResultsButton.tsx new file mode 100644 index 0000000000..1814d53470 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaToolbarToggleShowResultsButton.tsx @@ -0,0 +1,30 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; +import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiEyeBold, PiEyeSlashBold } from 'react-icons/pi'; + +export const StagingAreaToolbarToggleShowResultsButton = memo(() => { + const canvasManager = useCanvasManager(); + const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage); + + const { t } = useTranslation(); + + const toggleShowResults = useCallback(() => { + canvasManager.stagingArea.$shouldShowStagedImage.set(!shouldShowStagedImage); + }, [canvasManager.stagingArea.$shouldShowStagedImage, shouldShowStagedImage]); + + return ( + : } + onClick={toggleShowResults} + colorScheme="invokeBlue" + /> + ); +}); + +StagingAreaToolbarToggleShowResultsButton.displayName = 'StagingAreaToolbarToggleShowResultsButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasStagingAreaSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasStagingAreaSlice.ts index f11338c840..dab853c878 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasStagingAreaSlice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasStagingAreaSlice.ts @@ -90,3 +90,15 @@ export const selectIsStaging = createSelector( return data.in_progress > 0 || data.pending > 0; } ); +export const selectStagedImageIndex = createSelector( + selectCanvasStagingAreaSlice, + (stagingArea) => stagingArea.selectedStagedImageIndex +); +export const selectSelectedImage = createSelector( + [selectCanvasStagingAreaSlice, selectStagedImageIndex], + (stagingArea, index) => stagingArea.stagedImages[index] ?? null +); +export const selectImageCount = createSelector( + selectCanvasStagingAreaSlice, + (stagingArea) => stagingArea.stagedImages.length +);