From 01f93b3c19a120b7799076188e9f75ddd04be6b1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:03:05 +1000 Subject: [PATCH 01/12] build(ui): bump @invoke-ai/ui-library This gets us access to the Alert component. --- invokeai/frontend/web/package.json | 2 +- invokeai/frontend/web/pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index 0b448b73428..2d90d21312f 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -58,7 +58,7 @@ "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@fontsource-variable/inter": "^5.0.20", - "@invoke-ai/ui-library": "^0.0.34", + "@invoke-ai/ui-library": "^0.0.36", "@nanostores/react": "^0.7.3", "@reduxjs/toolkit": "2.2.3", "@roarr/browser-log-writer": "^1.3.0", diff --git a/invokeai/frontend/web/pnpm-lock.yaml b/invokeai/frontend/web/pnpm-lock.yaml index e6030ac08fa..1c8c30464d9 100644 --- a/invokeai/frontend/web/pnpm-lock.yaml +++ b/invokeai/frontend/web/pnpm-lock.yaml @@ -24,8 +24,8 @@ dependencies: specifier: ^5.0.20 version: 5.0.20 '@invoke-ai/ui-library': - specifier: ^0.0.34 - version: 0.0.34(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1) + specifier: ^0.0.36 + version: 0.0.36(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1) '@nanostores/react': specifier: ^0.7.3 version: 0.7.3(nanostores@0.11.2)(react@18.3.1) @@ -3574,8 +3574,8 @@ packages: prettier: 3.3.3 dev: true - /@invoke-ai/ui-library@0.0.34(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-iDSjFQV2U4LfQ8+UdZ9Uy6J1iKKTSsXM0uhkWrwcIghbgN5QwY3ABVLhqJrSWVTwp7puEDhe/lRQ9QhTZBkVzw==} + /@invoke-ai/ui-library@0.0.36(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-gF/LSzUYFxDmKiUADvl7lsVEbyOQurWVxeCRD4279lBo/RjVcGDwkflLtP8FN//Wv1CqZqn9UnOrJHpUZWl1Rg==} peerDependencies: '@fontsource-variable/inter': ^5.0.16 react: ^18.2.0 From 2677556fd455e0c4dbe3cd093c641ec82612d99a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:14:49 +1000 Subject: [PATCH 02/12] fix(ui): select first image instead of clearing selection fully Fixes an issue where you end up w/ the no image fallback after pressing escape. --- .../components/ImageGrid/GallerySelectionCountTag.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx index bd7faf86e03..cc6872ab7eb 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx @@ -22,8 +22,11 @@ export const GallerySelectionCountTag = () => { const isSelectAllEnabled = useStore($isSelectAllEnabled); const onClearSelection = useCallback(() => { - dispatch(selectionChanged([])); - }, [dispatch]); + const firstImage = selection[0]; + if (firstImage) { + dispatch(selectionChanged([firstImage])); + } + }, [dispatch, selection]); const onSelectPage = useCallback(() => { dispatch(selectionChanged([...selection, ...imageDTOs])); From 66eabc197bc886252f054da155b37a63d51f8a99 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:15:41 +1000 Subject: [PATCH 03/12] feat(ui): use `` for selected entity alerts --- .../HUD/CanvasSelectedEntityStatusAlert.tsx | 71 ++++++++----------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert.tsx index 9042ca3dce8..d510556f898 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert.tsx @@ -1,8 +1,8 @@ -import { Box, Flex, Icon, Text } from '@invoke-ai/ui-library'; +import type { AlertStatus } from '@invoke-ai/ui-library'; +import { Alert, AlertDescription, AlertIcon, AlertTitle } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import type { Property } from 'csstype'; import { useEntityAdapterSafe } from 'features/controlLayers/contexts/EntityAdapterContext'; import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle'; import { useEntityTypeIsHidden } from 'features/controlLayers/hooks/useEntityTypeIsHidden'; @@ -16,7 +16,6 @@ import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types' import { atom } from 'nanostores'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiWarningCircleFill } from 'react-icons/pi'; type ContentProps = { entityIdentifier: CanvasEntityIdentifier; @@ -25,9 +24,10 @@ type ContentProps = { const $isFilteringFallback = atom(false); -type EntityStatus = { - value: string; - color?: Property.Color; +type AlertData = { + status: AlertStatus; + title: string; + description: string; }; const CanvasSelectedEntityStatusAlertContent = memo(({ entityIdentifier, adapter }: ContentProps) => { @@ -47,73 +47,60 @@ const CanvasSelectedEntityStatusAlertContent = memo(({ entityIdentifier, adapter const isFiltering = useStore(adapter.filterer?.$isFiltering ?? $isFilteringFallback); const isTransforming = useStore(adapter.transformer.$isTransforming); - const status = useMemo(() => { + const alert = useMemo(() => { if (isFiltering) { return { - value: t('controlLayers.HUD.entityStatus.isFiltering'), - color: 'invokeBlue.300', + status: 'info', + title, + description: t('controlLayers.HUD.entityStatus.isFiltering'), }; } if (isTransforming) { return { - value: t('controlLayers.HUD.entityStatus.isTransforming'), - color: 'invokeBlue.300', + status: 'info', + title, + description: t('controlLayers.HUD.entityStatus.isTransforming'), }; } if (isHidden) { return { - value: t('controlLayers.HUD.entityStatus.isHidden'), - color: 'invokePurple.300', + status: 'warning', + title, + description: t('controlLayers.HUD.entityStatus.isHidden'), }; } if (isLocked) { return { - value: t('controlLayers.HUD.entityStatus.isLocked'), - color: 'invokeRed.300', + status: 'warning', + title, + description: t('controlLayers.HUD.entityStatus.isLocked'), }; } if (!isEnabled) { return { - value: t('controlLayers.HUD.entityStatus.isDisabled'), - color: 'invokeRed.300', + status: 'warning', + title, + description: t('controlLayers.HUD.entityStatus.isDisabled'), }; } return null; - }, [isFiltering, isTransforming, isHidden, isLocked, isEnabled, t]); + }, [isFiltering, isTransforming, isHidden, isLocked, isEnabled, title, t]); - if (!status) { + if (!alert) { return null; } return ( - - - - - - - {title} - {' '} - {status.value} - - - + + + {alert.title} + {alert.description}. + ); }); From 6ba0deb4cf6387bd520fae880dde065fe96a00d6 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 20:14:56 +1000 Subject: [PATCH 04/12] feat(ui): restore viewer - Remove gallery tab - Restore viewer - Add configurable alerts & toasts when user may be lost --- invokeai/frontend/web/public/locales/en.json | 10 +- .../web/src/common/hooks/useGlobalHotkeys.ts | 9 -- .../components/CanvasDropArea.tsx | 7 + ...ntent.tsx => CanvasLayersPanelContent.tsx} | 4 +- ...Content.tsx => CanvasMainPanelContent.tsx} | 10 +- .../components/CanvasRightPanel.tsx | 58 ++++--- .../components/CanvasSendToToggle.tsx | 77 ---------- .../HUD/CanvasSendingToGalleryAlert.tsx | 130 ++++++++++++++++ .../ImageMenuItemOpenInViewer.tsx | 12 +- .../components/ImageGrid/GalleryImage.tsx | 7 +- .../components/ImageViewer/CompareToolbar.tsx | 12 +- .../components/ImageViewer/ImageViewer.tsx | 65 +++++++- .../components/ImageViewer/ViewerToolbar.tsx | 11 +- .../components/ImageViewer/useImageViewer.ts | 17 ++ .../components/InvokeQueueBackButton.tsx | 2 +- .../queue/components/QueueControls.tsx | 2 + .../queue/components/SendToToggle.tsx | 145 ++++++++++++++++++ .../system/components/ProgressBar.tsx | 5 +- .../SettingsModal/SettingsModal.tsx | 4 + .../SettingsShowSendToAlerts.tsx | 28 ++++ .../SettingsShowSendToToasts.tsx | 28 ++++ .../src/features/system/store/systemSlice.ts | 12 ++ .../web/src/features/system/store/types.ts | 2 + .../src/features/ui/components/AppContent.tsx | 13 +- .../features/ui/components/VerticalNavBar.tsx | 5 +- .../web/src/features/ui/store/uiSlice.ts | 2 +- .../web/src/features/ui/store/uiTypes.ts | 2 +- .../web/src/services/api/endpoints/queue.ts | 1 + ...onComplete.ts => onInvocationComplete.tsx} | 68 +++++++- 29 files changed, 586 insertions(+), 162 deletions(-) rename invokeai/frontend/web/src/features/controlLayers/components/{CanvasPanelContent.tsx => CanvasLayersPanelContent.tsx} (89%) rename invokeai/frontend/web/src/features/controlLayers/components/{CanvasTabContent.tsx => CanvasMainPanelContent.tsx} (88%) delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts create mode 100644 invokeai/frontend/web/src/features/queue/components/SendToToggle.tsx create mode 100644 invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToAlerts.tsx create mode 100644 invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToToasts.tsx rename invokeai/frontend/web/src/services/events/{onInvocationComplete.ts => onInvocationComplete.tsx} (69%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index d6183458c46..d697ff4be80 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -439,7 +439,9 @@ "compareHelp2": "Press M to cycle through comparison modes.", "compareHelp3": "Press C to swap the compared images.", "compareHelp4": "Press Z or Esc to exit.", - "toggleMiniViewer": "Toggle Mini Viewer" + "toggleMiniViewer": "Toggle Mini Viewer", + "openViewer": "Open Viewer", + "closeViewer": "Close Viewer" }, "hotkeys": { "searchHotkeys": "Search Hotkeys", @@ -1723,9 +1725,11 @@ "sendingToCanvas": "Sending to Canvas", "sendingToGallery": "Sending to Gallery", "sendToGallery": "Send To Gallery", - "sendToGalleryDesc": "Generations will be sent to the gallery.", + "sendToGalleryDesc": "Pressing Invoke generates and saves a unique image to your gallery.", "sendToCanvas": "Send To Canvas", - "sendToCanvasDesc": "Generations will be staged onto the canvas.", + "sendToCanvasDesc": "Pressing Invoke stages your work in progress on the canvas.", + "viewGenerationsInImageViewer": "View Generations in the Image Viewer.", + "viewAndStageOnTheCanvas": "View and stage generations on the Canvas.", "rasterLayer_withCount_one": "$t(controlLayers.rasterLayer)", "controlLayer_withCount_one": "$t(controlLayers.controlLayer)", "inpaintMask_withCount_one": "$t(controlLayers.inpaintMask)", diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts index 9a40d55841a..634e2ead39c 100644 --- a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts +++ b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts @@ -114,13 +114,4 @@ export const useGlobalHotkeys = () => { }, [dispatch, isModelManagerEnabled] ); - - useHotkeys( - isModelManagerEnabled ? '6' : '5', - () => { - dispatch(setActiveTab('gallery')); - setScopes([]); - }, - [dispatch, isModelManagerEnabled] - ); }; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx index 499cbcba7d5..15ecd5a956e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx @@ -1,6 +1,7 @@ import { Flex } from '@invoke-ai/ui-library'; import IAIDroppable from 'common/components/IAIDroppable'; import type { AddControlLayerFromImageDropData, AddRasterLayerFromImageDropData } from 'features/dnd/types'; +import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { memo } from 'react'; const addRasterLayerFromImageDropData: AddRasterLayerFromImageDropData = { @@ -14,6 +15,12 @@ const addControlLayerFromImageDropData: AddControlLayerFromImageDropData = { }; export const CanvasDropArea = memo(() => { + const imageViewer = useImageViewer(); + + if (imageViewer.isOpen) { + return null; + } + return ( <> diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasLayersPanelContent.tsx similarity index 89% rename from invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/CanvasLayersPanelContent.tsx index 87971e31f0e..1a4bf4d2734 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasLayersPanelContent.tsx @@ -7,7 +7,7 @@ import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/Canva import { selectHasEntities } from 'features/controlLayers/store/selectors'; import { memo } from 'react'; -export const CanvasPanelContent = memo(() => { +export const CanvasLayersPanelContent = memo(() => { const hasEntities = useAppSelector(selectHasEntities); return ( @@ -22,4 +22,4 @@ export const CanvasPanelContent = memo(() => { ); }); -CanvasPanelContent.displayName = 'CanvasPanelContent'; +CanvasLayersPanelContent.displayName = 'CanvasLayersPanelContent'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasTabContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx similarity index 88% rename from invokeai/frontend/web/src/features/controlLayers/components/CanvasTabContent.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx index c228c743e9d..4b2c548885b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasTabContent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx @@ -6,6 +6,7 @@ import { CanvasDropArea } from 'features/controlLayers/components/CanvasDropArea import { Filter } from 'features/controlLayers/components/Filters/Filter'; import { CanvasHUD } from 'features/controlLayers/components/HUD/CanvasHUD'; import { CanvasSelectedEntityStatusAlert } from 'features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert'; +import { SendingToGalleryAlert } from 'features/controlLayers/components/HUD/CanvasSendingToGalleryAlert'; import { InvokeCanvasComponent } from 'features/controlLayers/components/InvokeCanvasComponent'; import { StagingAreaIsStagingGate } from 'features/controlLayers/components/StagingArea/StagingAreaIsStagingGate'; import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar'; @@ -14,9 +15,10 @@ import { Transform } from 'features/controlLayers/components/Transform/Transform import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; import { TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern'; import { selectDynamicGrid, selectShowHUD } from 'features/controlLayers/store/canvasSettingsSlice'; +import { GatedImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer'; import { memo, useCallback, useRef } from 'react'; -export const CanvasTabContent = memo(() => { +export const CanvasMainPanelContent = memo(() => { const ref = useRef(null); const dynamicGrid = useAppSelector(selectDynamicGrid); const showHUD = useAppSelector(selectShowHUD); @@ -76,8 +78,9 @@ export const CanvasTabContent = memo(() => { )} - + + @@ -97,8 +100,9 @@ export const CanvasTabContent = memo(() => { + ); }); -CanvasTabContent.displayName = 'CanvasTabContent'; +CanvasMainPanelContent.displayName = 'CanvasMainPanelContent'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx index 40bd0add243..af67dc5320c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx @@ -1,30 +1,47 @@ import { useDndContext } from '@dnd-kit/core'; -import { Box, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; +import { Box, Button, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; +import { useStore } from '@nanostores/react'; import { useAppSelector } from 'app/store/storeHooks'; import { useScopeOnFocus } from 'common/hooks/interactionScopes'; -import { CanvasPanelContent } from 'features/controlLayers/components/CanvasPanelContent'; -import { CanvasSendToToggle } from 'features/controlLayers/components/CanvasSendToToggle'; -import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice'; +import { CanvasLayersPanelContent } from 'features/controlLayers/components/CanvasLayersPanelContent'; import { selectEntityCountActive } from 'features/controlLayers/store/selectors'; import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent'; -import { memo, useCallback, useMemo, useRef, useState } from 'react'; +import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; +import { atom } from 'nanostores'; +import { memo, useCallback, useMemo, useRef } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; -export const CanvasRightPanelContent = memo(() => { +const $tabIndex = atom(0); +export const setRightPanelTabToLayers = () => $tabIndex.set(0); +export const setRightPanelTabToGallery = () => $tabIndex.set(1); + +export const CanvasRightPanel = memo(() => { + const { t } = useTranslation(); const ref = useRef(null); - const [tab, setTab] = useState(0); + const tabIndex = useStore($tabIndex); useScopeOnFocus('gallery', ref); + const imageViewer = useImageViewer(); + const onClickViewerToggleButton = useCallback(() => { + if ($tabIndex.get() !== 1) { + $tabIndex.set(1); + } + imageViewer.toggle(); + }, [imageViewer]); + useHotkeys('z', imageViewer.toggle); return ( - + - + - + - + @@ -34,30 +51,29 @@ export const CanvasRightPanelContent = memo(() => { ); }); -CanvasRightPanelContent.displayName = 'CanvasRightPanelContent'; +CanvasRightPanel.displayName = 'CanvasRightPanel'; -const PanelTabs = memo(({ setTab }: { setTab: (val: number) => void }) => { +const PanelTabs = memo(() => { const { t } = useTranslation(); const activeEntityCount = useAppSelector(selectEntityCountActive); - const sendToCanvas = useAppSelector(selectSendToCanvas); const tabTimeout = useRef(null); const dndCtx = useDndContext(); const onOnMouseOverLayersTab = useCallback(() => { tabTimeout.current = window.setTimeout(() => { if (dndCtx.active) { - setTab(0); + setRightPanelTabToLayers(); } }, 300); - }, [dndCtx.active, setTab]); + }, [dndCtx.active]); const onOnMouseOverGalleryTab = useCallback(() => { tabTimeout.current = window.setTimeout(() => { if (dndCtx.active) { - setTab(1); + setRightPanelTabToGallery(); } }, 300); - }, [dndCtx.active, setTab]); + }, [dndCtx.active]); const onMouseOut = useCallback(() => { if (tabTimeout.current) { @@ -78,15 +94,9 @@ const PanelTabs = memo(({ setTab }: { setTab: (val: number) => void }) => { {layersTabLabel} - {sendToCanvas && ( - - )} {t('gallery.gallery')} - {!sendToCanvas && ( - - )} ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx deleted file mode 100644 index 8e44f44a7f4..00000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasSendToToggle.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { - Button, - Flex, - Icon, - Popover, - PopoverArrow, - PopoverBody, - PopoverContent, - PopoverTrigger, - Text, -} from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { selectSendToCanvas, settingsSendToCanvasChanged } from 'features/controlLayers/store/canvasSettingsSlice'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiCaretDownBold, PiCheckBold } from 'react-icons/pi'; - -export const CanvasSendToToggle = memo(() => { - const { t } = useTranslation(); - const sendToCanvas = useAppSelector(selectSendToCanvas); - const dispatch = useAppDispatch(); - - const enableSendToCanvas = useCallback(() => { - dispatch(settingsSendToCanvasChanged(true)); - }, [dispatch]); - - const disableSendToCanvas = useCallback(() => { - dispatch(settingsSendToCanvasChanged(false)); - }, [dispatch]); - - return ( - - - - - - - - - - - - - - - ); -}); - -CanvasSendToToggle.displayName = 'CanvasSendToToggle'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx new file mode 100644 index 00000000000..9f23fc4af07 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx @@ -0,0 +1,130 @@ +import { + Alert, + AlertDescription, + AlertIcon, + AlertTitle, + Button, + Flex, + Icon, + IconButton, + Spacer, +} from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + setRightPanelTabToGallery, + setRightPanelTabToLayers, +} from 'features/controlLayers/components/CanvasRightPanel'; +import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice'; +import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; +import { selectShowSendToAlerts, showSendToAlertsChanged } from 'features/system/store/systemSlice'; +import { setActiveTab } from 'features/ui/store/uiSlice'; +import type { PropsWithChildren } from 'react'; +import { useCallback } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { PiXBold } from 'react-icons/pi'; + +const DontShowMeTheseAgainButton = () => { + const dispatch = useAppDispatch(); + const onClick = useCallback(() => { + dispatch(showSendToAlertsChanged(false)); + }, [dispatch]); + return ( + } + tooltip="Don't show me these again" + aria-label="Don't show me these again" + right={-1} + top={-2} + onClick={onClick} + minW="auto" + /> + ); +}; + +const ActivateImageViewerButton = (props: PropsWithChildren) => { + const imageViewer = useImageViewer(); + const onClick = useCallback(() => { + imageViewer.open(); + setRightPanelTabToGallery(); + }, [imageViewer]); + return ( + + ); +}; + +export const SendingToGalleryAlert = () => { + const { t } = useTranslation(); + const sendToCanvas = useAppSelector(selectSendToCanvas); + const showSendToAlerts = useAppSelector(selectShowSendToAlerts); + + if (!showSendToAlerts) { + return null; + } + + if (sendToCanvas) { + return null; + } + + return ( + + + + {t('controlLayers.sendingToGallery')} + + + + + }} + /> + + + ); +}; + +const ActivateCanvasButton = (props: PropsWithChildren) => { + const dispatch = useAppDispatch(); + const imageViewer = useImageViewer(); + const onClick = useCallback(() => { + dispatch(setActiveTab('generation')); + setRightPanelTabToLayers(); + imageViewer.close(); + }, [dispatch, imageViewer]); + return ( + + ); +}; + +export const SendingToCanvasAlert = () => { + const { t } = useTranslation(); + const sendToCanvas = useAppSelector(selectSendToCanvas); + const showSendToAlerts = useAppSelector(selectShowSendToAlerts); + + if (!showSendToAlerts) { + return null; + } + + if (!sendToCanvas) { + return null; + } + + return ( + + + + {t('controlLayers.sendingToCanvas')} + + + + + }} /> + + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx index 6f4962a3c7f..0ce467d4a47 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx @@ -1,25 +1,25 @@ import { MenuItem } from '@invoke-ai/ui-library'; import { useAppDispatch } from 'app/store/storeHooks'; +import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext'; import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice'; -import { setActiveTab } from 'features/ui/store/uiSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiEyeBold } from 'react-icons/pi'; +import { PiArrowsOutBold } from 'react-icons/pi'; export const ImageMenuItemOpenInViewer = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const imageDTO = useImageDTOContext(); - + const imageViewer = useImageViewer(); const onClick = useCallback(() => { dispatch(imageToCompareChanged(null)); dispatch(imageSelected(imageDTO)); - dispatch(setActiveTab('gallery')); - }, [dispatch, imageDTO]); + imageViewer.open(); + }, [dispatch, imageDTO, imageViewer]); return ( - } onClick={onClick}> + } onClick={onClick}> {t('gallery.openInViewer')} ); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx index 7dca9ecac2f..87915e4928b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx @@ -10,11 +10,11 @@ import IAIFillSkeleton from 'common/components/IAIFillSkeleton'; import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice'; import type { GallerySelectionDraggableData, ImageDraggableData, TypesafeDraggableData } from 'features/dnd/types'; import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId'; +import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { useMultiselect } from 'features/gallery/hooks/useMultiselect'; import { useScrollIntoView } from 'features/gallery/hooks/useScrollIntoView'; import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors'; import { imageToCompareChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice'; -import { setActiveTab } from 'features/ui/store/uiSlice'; import type { MouseEvent, MouseEventHandler } from 'react'; import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -112,10 +112,11 @@ const GalleryImage = ({ index, imageDTO }: HoverableImageProps) => { setIsHovered(true); }, []); + const imageViewer = useImageViewer(); const onDoubleClick = useCallback(() => { - dispatch(setActiveTab('gallery')); + imageViewer.open(); dispatch(imageToCompareChanged(null)); - }, [dispatch]); + }, [dispatch, imageViewer]); const handleMouseOut = useCallback(() => { setIsHovered(false); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx index 637d282e171..e2f2944c314 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CompareToolbar.tsx @@ -21,7 +21,7 @@ import { import { memo, useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { Trans, useTranslation } from 'react-i18next'; -import { PiArrowsOutBold, PiQuestion, PiSwapBold, PiXBold } from 'react-icons/pi'; +import { PiArrowsOutBold, PiQuestion, PiSwapBold } from 'react-icons/pi'; export const CompareToolbar = memo(() => { const { t } = useTranslation(); @@ -104,15 +104,17 @@ export const CompareToolbar = memo(() => { }> - + - } + 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 414539942c1..be247ad41c2 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx @@ -1,22 +1,38 @@ -import { Box, Flex } from '@invoke-ai/ui-library'; +import { Box, Button, Flex } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { useScopeOnFocus, useScopeOnMount } from 'common/hooks/interactionScopes'; +import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; +import { SendingToCanvasAlert } from 'features/controlLayers/components/HUD/CanvasSendingToGalleryAlert'; import { CompareToolbar } from 'features/gallery/components/ImageViewer/CompareToolbar'; import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview'; import { ImageComparison } from 'features/gallery/components/ImageViewer/ImageComparison'; import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewer/ImageComparisonDroppable'; import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar'; import { selectHasImageToCompare } from 'features/gallery/store/gallerySelectors'; -import { memo, useRef } from 'react'; +import type { ReactNode } from 'react'; +import { memo, useEffect, useRef } from 'react'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { useTranslation } from 'react-i18next'; import { useMeasure } from 'react-use'; -export const ImageViewer = memo(() => { +import { useImageViewer } from './useImageViewer'; + +type Props = { + closeButton?: ReactNode; +}; + +export const ImageViewer = memo(({ closeButton }: Props) => { + useAssertSingleton('ImageViewer'); const hasImageToCompare = useAppSelector(selectHasImageToCompare); const [containerRef, containerDims] = useMeasure(); const ref = useRef(null); useScopeOnFocus('imageViewer', ref); useScopeOnMount('imageViewer'); + useEffect(() => { + ref?.current?.focus(); + }, []); + return ( { justifyContent="center" > {hasImageToCompare && } - {!hasImageToCompare && } + {!hasImageToCompare && } {!hasImageToCompare && } {hasImageToCompare && } + + + ); }); ImageViewer.displayName = 'ImageViewer'; + +export const GatedImageViewer = memo(() => { + const imageViewer = useImageViewer(); + + if (!imageViewer.isOpen) { + return null; + } + + return } />; +}); + +GatedImageViewer.displayName = 'GatedImageViewer'; + +const ImageViewerCloseButton = memo(() => { + const { t } = useTranslation(); + const imageViewer = useImageViewer(); + useAssertSingleton('ImageViewerCloseButton'); + useHotkeys('esc', imageViewer.close); + return ( + + ); +}); + +ImageViewerCloseButton.displayName = 'ImageViewerCloseButton'; + +const GatedImageViewerCloseButton = memo(() => { + const imageViewer = useImageViewer(); + + if (!imageViewer.isOpen) { + return null; + } + + return ; +}); + +GatedImageViewerCloseButton.displayName = 'GatedImageViewerCloseButton'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx index 5a66e34039a..690087c6d0e 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ViewerToolbar.tsx @@ -1,11 +1,16 @@ import { Flex } from '@invoke-ai/ui-library'; import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton'; import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton'; +import type { ReactNode } from 'react'; import { memo } from 'react'; import CurrentImageButtons from './CurrentImageButtons'; -export const ViewerToolbar = memo(() => { +type Props = { + closeButton?: ReactNode; +}; + +export const ViewerToolbar = memo(({ closeButton }: Props) => { return ( @@ -18,7 +23,9 @@ export const ViewerToolbar = memo(() => { - + + {closeButton} + ); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts new file mode 100644 index 00000000000..f4aa3be0689 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts @@ -0,0 +1,17 @@ +import { buildUseBoolean } from 'common/hooks/useBoolean'; +import { useMemo } from 'react'; + +const hook = buildUseBoolean(true); +const useImageViewerState = hook[0]; + +export const $imageViewerState = hook[1]; + +export const useImageViewer = () => { + const imageViewerState = useImageViewerState(); + const isOpen = useMemo(() => imageViewerState.isTrue, [imageViewerState]); + const open = useMemo(() => imageViewerState.setTrue, [imageViewerState]); + const close = useMemo(() => imageViewerState.setFalse, [imageViewerState]); + const toggle = useMemo(() => imageViewerState.toggle, [imageViewerState]); + + return { isOpen, open, close, toggle, $state: $imageViewerState }; +}; diff --git a/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx b/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx index 1d58c878cf1..5b29f4b6ee9 100644 --- a/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx +++ b/invokeai/frontend/web/src/features/queue/components/InvokeQueueBackButton.tsx @@ -15,7 +15,7 @@ export const InvokeQueueBackButton = memo(() => { const isLoadingDynamicPrompts = useAppSelector(selectDynamicPromptsIsLoading); return ( - + + ); +}; + export const buildOnInvocationComplete = ( getState: () => RootState, dispatch: AppDispatch, @@ -24,6 +48,32 @@ export const buildOnInvocationComplete = ( setLastProgressEvent: (event: S['InvocationDenoiseProgressEvent'] | null) => void, setLastCanvasProgressEvent: (event: S['InvocationDenoiseProgressEvent'] | null) => void ) => { + const toastIfUserIsLost = (destination: string | null) => { + const state = getState(); + const showToasts = selectShowSendToToasts(state); + if (!showToasts) { + return; + } + const tab = selectActiveTab(state); + if (destination === 'canvas' && ($imageViewerState.get() || tab !== 'generation')) { + toast({ + id: SEND_TO_TOAST_ID, + title: 'Image sent to Canvas', + updateDescription: true, + withCount: false, + description: , + }); + } else if (destination !== 'canvas' && !$imageViewerState.get() && tab === 'generation') { + toast({ + id: SEND_TO_TOAST_ID, + title: 'Image sent to Gallery', + updateDescription: true, + withCount: false, + description: , + }); + } + }; + const addImageToGallery = (imageDTO: ImageDTO) => { if (imageDTO.is_intermediate) { return; @@ -108,12 +158,13 @@ export const buildOnInvocationComplete = ( const imageDTO = await getResultImageDTO(data); - if (imageDTO) { + if (imageDTO && !imageDTO.is_intermediate) { addImageToGallery(imageDTO); + toastIfUserIsLost(data.destination); } }; - const handleOriginCanvas = async (data: S['InvocationCompleteEvent']) => { + const handleOriginGeneration = async (data: S['InvocationCompleteEvent']) => { const imageDTO = await getResultImageDTO(data); if (!imageDTO) { @@ -128,20 +179,23 @@ export const buildOnInvocationComplete = ( } else if (data.result.type === 'image_output') { dispatch(stagingAreaImageStaged({ stagingAreaImage: { imageDTO, offsetX: 0, offsetY: 0 } })); } + addImageToGallery(imageDTO); + toastIfUserIsLost(data.destination); } - } else { + } else if (!imageDTO.is_intermediate) { // session.mode === 'generate' setLastCanvasProgressEvent(null); + addImageToGallery(imageDTO); + toastIfUserIsLost(data.destination); } - - addImageToGallery(imageDTO); }; const handleOriginOther = async (data: S['InvocationCompleteEvent']) => { const imageDTO = await getResultImageDTO(data); - if (imageDTO) { + if (imageDTO && !imageDTO.is_intermediate) { addImageToGallery(imageDTO); + toastIfUserIsLost(data.destination); } }; @@ -155,7 +209,7 @@ export const buildOnInvocationComplete = ( if (data.origin === 'workflows') { await handleOriginWorkflows(data); } else if (data.origin === 'generation') { - await handleOriginCanvas(data); + await handleOriginGeneration(data); } else { await handleOriginOther(data); } From 3cfed9920631ef5cec0d6201f480c20d2e3fb633 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 22:26:40 +1000 Subject: [PATCH 05/12] feat(ui): remove toasts when toggling send to --- .../queue/components/SendToToggle.tsx | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/invokeai/frontend/web/src/features/queue/components/SendToToggle.tsx b/invokeai/frontend/web/src/features/queue/components/SendToToggle.tsx index 17ca5e8f14d..eae5cc7d3fb 100644 --- a/invokeai/frontend/web/src/features/queue/components/SendToToggle.tsx +++ b/invokeai/frontend/web/src/features/queue/components/SendToToggle.tsx @@ -6,7 +6,6 @@ import { selectCanvasSettingsSlice, settingsSendToCanvasChanged, } from 'features/controlLayers/store/canvasSettingsSlice'; -import { toast } from 'features/toast/toast'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { PiImageBold, PiPaintBrushBold } from 'react-icons/pi'; @@ -63,29 +62,15 @@ export const SendToToggle = memo(() => { if (!sendToCanvas) { return; } - toast({ - id: 'sendToCanvas', - title: t('controlLayers.sendingToGallery'), - description: t('controlLayers.sendToGalleryDesc'), - status: 'info', - withCount: false, - }); dispatch(settingsSendToCanvasChanged(false)); - }, [dispatch, sendToCanvas, t]); + }, [dispatch, sendToCanvas]); const onClickSendToCanvas = useCallback(() => { if (sendToCanvas) { return; } - toast({ - id: 'sendToCanvas', - title: t('controlLayers.sendingToCanvas'), - description: t('controlLayers.sendToCanvasDesc'), - status: 'info', - withCount: false, - }); dispatch(settingsSendToCanvasChanged(true)); - }, [dispatch, sendToCanvas, t]); + }, [dispatch, sendToCanvas]); return ( Date: Thu, 12 Sep 2024 22:27:57 +1000 Subject: [PATCH 06/12] feat(ui): alerts display depending on current generation destination --- invokeai/frontend/web/public/locales/en.json | 4 +-- .../HUD/CanvasSendingToGalleryAlert.tsx | 30 +++++++++++-------- .../queue/hooks/useCurrentDestination.ts | 12 ++++++++ .../system/components/ProgressBar.tsx | 7 +++-- 4 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index d697ff4be80..2dcd94211f1 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1728,8 +1728,8 @@ "sendToGalleryDesc": "Pressing Invoke generates and saves a unique image to your gallery.", "sendToCanvas": "Send To Canvas", "sendToCanvasDesc": "Pressing Invoke stages your work in progress on the canvas.", - "viewGenerationsInImageViewer": "View Generations in the Image Viewer.", - "viewAndStageOnTheCanvas": "View and stage generations on the Canvas.", + "viewProgressInViewer": "View progress and outputs in the Image Viewer.", + "viewProgressOnCanvas": "View progress and stage outputs on the Canvas.", "rasterLayer_withCount_one": "$t(controlLayers.rasterLayer)", "controlLayer_withCount_one": "$t(controlLayers.controlLayer)", "inpaintMask_withCount_one": "$t(controlLayers.inpaintMask)", diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx index 9f23fc4af07..c30fc7e6813 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx @@ -14,8 +14,8 @@ import { setRightPanelTabToGallery, setRightPanelTabToLayers, } from 'features/controlLayers/components/CanvasRightPanel'; -import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice'; import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; +import { useCurrentDestination } from 'features/queue/hooks/useCurrentDestination'; import { selectShowSendToAlerts, showSendToAlertsChanged } from 'features/system/store/systemSlice'; import { setActiveTab } from 'features/ui/store/uiSlice'; import type { PropsWithChildren } from 'react'; @@ -24,6 +24,7 @@ import { Trans, useTranslation } from 'react-i18next'; import { PiXBold } from 'react-icons/pi'; const DontShowMeTheseAgainButton = () => { + const { t } = useTranslation(); const dispatch = useAppDispatch(); const onClick = useCallback(() => { dispatch(showSendToAlertsChanged(false)); @@ -32,8 +33,8 @@ const DontShowMeTheseAgainButton = () => { } - tooltip="Don't show me these again" - aria-label="Don't show me these again" + tooltip={t('common.dontShowMeThese')} + aria-label={t('common.dontShowMeThese')} right={-1} top={-2} onClick={onClick} @@ -57,14 +58,18 @@ const ActivateImageViewerButton = (props: PropsWithChildren) => { export const SendingToGalleryAlert = () => { const { t } = useTranslation(); - const sendToCanvas = useAppSelector(selectSendToCanvas); + const destination = useCurrentDestination(); const showSendToAlerts = useAppSelector(selectShowSendToAlerts); if (!showSendToAlerts) { return null; } - if (sendToCanvas) { + if (!destination) { + return null; + } + + if (destination === 'canvas') { return null; } @@ -77,10 +82,7 @@ export const SendingToGalleryAlert = () => { - }} - /> + }} /> ); @@ -103,14 +105,18 @@ const ActivateCanvasButton = (props: PropsWithChildren) => { export const SendingToCanvasAlert = () => { const { t } = useTranslation(); - const sendToCanvas = useAppSelector(selectSendToCanvas); + const destination = useCurrentDestination(); const showSendToAlerts = useAppSelector(selectShowSendToAlerts); if (!showSendToAlerts) { return null; } - if (!sendToCanvas) { + if (!destination) { + return null; + } + + if (destination !== 'canvas') { return null; } @@ -123,7 +129,7 @@ export const SendingToCanvasAlert = () => { - }} /> + }} /> ); diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts b/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts new file mode 100644 index 00000000000..33b37e05777 --- /dev/null +++ b/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts @@ -0,0 +1,12 @@ +import { useGetCurrentQueueItemQuery } from "services/api/endpoints/queue"; + + +export const useCurrentDestination = () => { + const { destination } = useGetCurrentQueueItemQuery(undefined, { + selectFromResult: ({ data }) => ({ + destination: data ? data.destination : null, + }), + }); + + return destination; +}; diff --git a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx index 6e24ecb6480..6cc834c2f89 100644 --- a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx +++ b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx @@ -1,15 +1,16 @@ import { Progress } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { $isConnected } from 'app/hooks/useSocketIO'; +import { useCurrentDestination } from 'features/queue/hooks/useCurrentDestination'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useGetCurrentQueueItemQuery, useGetQueueStatusQuery } from 'services/api/endpoints/queue'; +import { useGetQueueStatusQuery } from 'services/api/endpoints/queue'; import { $lastProgressEvent } from 'services/events/setEventListeners'; const ProgressBar = () => { const { t } = useTranslation(); + const destination = useCurrentDestination(); const { data: queueStatus } = useGetQueueStatusQuery(); - const currentQueueItem = useGetCurrentQueueItemQuery().data; const isConnected = useStore($isConnected); const lastProgressEvent = useStore($lastProgressEvent); const value = useMemo(() => { @@ -26,7 +27,7 @@ const ProgressBar = () => { isIndeterminate={isConnected && Boolean(queueStatus?.queue.in_progress) && !lastProgressEvent} h={2} w="full" - colorScheme={currentQueueItem && currentQueueItem.destination === 'canvas' ? 'invokeGreen' : 'invokeBlue'} + colorScheme={destination === 'canvas' ? 'invokeGreen' : 'invokeBlue'} /> ); }; From 50cddd7b1798d7076ba5d8a61894c14ba3dd92d9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:16:55 +1000 Subject: [PATCH 07/12] feat(ui): animations for send to alerts --- .../HUD/CanvasSendingToGalleryAlert.tsx | 153 ++++++++++-------- 1 file changed, 89 insertions(+), 64 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx index c30fc7e6813..d6149049940 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx @@ -10,6 +10,7 @@ import { Spacer, } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useBoolean } from 'common/hooks/useBoolean'; import { setRightPanelTabToGallery, setRightPanelTabToLayers, @@ -18,31 +19,12 @@ import { useImageViewer } from 'features/gallery/components/ImageViewer/useImage import { useCurrentDestination } from 'features/queue/hooks/useCurrentDestination'; import { selectShowSendToAlerts, showSendToAlertsChanged } from 'features/system/store/systemSlice'; import { setActiveTab } from 'features/ui/store/uiSlice'; -import type { PropsWithChildren } from 'react'; -import { useCallback } from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; +import type { PropsWithChildren, ReactNode } from 'react'; +import { useCallback, useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { PiXBold } from 'react-icons/pi'; -const DontShowMeTheseAgainButton = () => { - const { t } = useTranslation(); - const dispatch = useAppDispatch(); - const onClick = useCallback(() => { - dispatch(showSendToAlertsChanged(false)); - }, [dispatch]); - return ( - } - tooltip={t('common.dontShowMeThese')} - aria-label={t('common.dontShowMeThese')} - right={-1} - top={-2} - onClick={onClick} - minW="auto" - /> - ); -}; - const ActivateImageViewerButton = (props: PropsWithChildren) => { const imageViewer = useImageViewer(); const onClick = useCallback(() => { @@ -59,32 +41,26 @@ const ActivateImageViewerButton = (props: PropsWithChildren) => { export const SendingToGalleryAlert = () => { const { t } = useTranslation(); const destination = useCurrentDestination(); - const showSendToAlerts = useAppSelector(selectShowSendToAlerts); - - if (!showSendToAlerts) { - return null; - } + const isVisible = useMemo(() => { + if (!destination) { + return false; + } - if (!destination) { - return null; - } + if (destination === 'canvas') { + return false; + } - if (destination === 'canvas') { - return null; - } + return true; + }, [destination]); return ( - - - - {t('controlLayers.sendingToGallery')} - - - - + }} /> - - + } + isVisible={isVisible} + /> ); }; @@ -106,31 +82,80 @@ const ActivateCanvasButton = (props: PropsWithChildren) => { export const SendingToCanvasAlert = () => { const { t } = useTranslation(); const destination = useCurrentDestination(); - const showSendToAlerts = useAppSelector(selectShowSendToAlerts); - - if (!showSendToAlerts) { - return null; - } + const isVisible = useMemo(() => { + if (!destination) { + return false; + } - if (!destination) { - return null; - } + if (destination !== 'canvas') { + return false; + } - if (destination !== 'canvas') { - return null; - } + return true; + }, [destination]); return ( - - - - {t('controlLayers.sendingToCanvas')} - - - - + }} /> - - + } + isVisible={isVisible} + /> + ); +}; + +const AlertWrapper = ({ + title, + description, + isVisible, +}: { + title: ReactNode; + description: ReactNode; + isVisible: boolean; +}) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const showSendToAlerts = useAppSelector(selectShowSendToAlerts); + const isHovered = useBoolean(false); + const onClickDontShowMeThese = useCallback(() => { + dispatch(showSendToAlertsChanged(false)); + isHovered.setFalse(); + }, [dispatch, isHovered]); + + return ( + + {(isVisible || isHovered.isTrue) && showSendToAlerts && ( + + + + + {title} + + } + tooltip={t('common.dontShowMeThese')} + aria-label={t('common.dontShowMeThese')} + right={-1} + top={-2} + onClick={onClickDontShowMeThese} + minW="auto" + /> + + {description} + + + )} + ); }; From c0dea2b14492cac42ed762a7f1d80ae7ee9e76dc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:18:26 +1000 Subject: [PATCH 08/12] revert(ui): remove post-generation toasts --- .../services/events/onInvocationComplete.tsx | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx index e554c966168..32720911d5d 100644 --- a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx +++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx @@ -1,19 +1,11 @@ -import { Button } from '@invoke-ai/ui-library'; import { logger } from 'app/logging/logger'; import type { AppDispatch, RootState } from 'app/store/store'; -import { useAppDispatch } from 'app/store/storeHooks'; import type { SerializableObject } from 'common/types'; import { deepClone } from 'common/util/deepClone'; import { stagingAreaImageStaged } from 'features/controlLayers/store/canvasStagingAreaSlice'; -import { $imageViewerState } from 'features/gallery/components/ImageViewer/useImageViewer'; import { boardIdSelected, galleryViewChanged, imageSelected, offsetChanged } from 'features/gallery/store/gallerySlice'; import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState'; import { zNodeStatus } from 'features/nodes/types/invocation'; -import { selectShowSendToToasts, showSendToToastsChanged } from 'features/system/store/systemSlice'; -import { toast } from 'features/toast/toast'; -import { selectActiveTab } from 'features/ui/store/uiSelectors'; -import { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; import { boardsApi } from 'services/api/endpoints/boards'; import { getImageDTO, imagesApi } from 'services/api/endpoints/images'; import type { ImageDTO, S } from 'services/api/types'; @@ -25,22 +17,6 @@ const isCanvasOutputNode = (data: S['InvocationCompleteEvent']) => { return data.invocation_source_id.split(':')[0] === 'canvas_output'; }; -const SEND_TO_TOAST_ID = 'send-to-toast'; - -const DontShowMeTheseToastDescription = () => { - const { t } = useTranslation(); - const dispatch = useAppDispatch(); - const onClick = useCallback(() => { - dispatch(showSendToToastsChanged(false)); - }, [dispatch]); - - return ( - - ); -}; - export const buildOnInvocationComplete = ( getState: () => RootState, dispatch: AppDispatch, @@ -48,32 +24,6 @@ export const buildOnInvocationComplete = ( setLastProgressEvent: (event: S['InvocationDenoiseProgressEvent'] | null) => void, setLastCanvasProgressEvent: (event: S['InvocationDenoiseProgressEvent'] | null) => void ) => { - const toastIfUserIsLost = (destination: string | null) => { - const state = getState(); - const showToasts = selectShowSendToToasts(state); - if (!showToasts) { - return; - } - const tab = selectActiveTab(state); - if (destination === 'canvas' && ($imageViewerState.get() || tab !== 'generation')) { - toast({ - id: SEND_TO_TOAST_ID, - title: 'Image sent to Canvas', - updateDescription: true, - withCount: false, - description: , - }); - } else if (destination !== 'canvas' && !$imageViewerState.get() && tab === 'generation') { - toast({ - id: SEND_TO_TOAST_ID, - title: 'Image sent to Gallery', - updateDescription: true, - withCount: false, - description: , - }); - } - }; - const addImageToGallery = (imageDTO: ImageDTO) => { if (imageDTO.is_intermediate) { return; @@ -160,7 +110,6 @@ export const buildOnInvocationComplete = ( if (imageDTO && !imageDTO.is_intermediate) { addImageToGallery(imageDTO); - toastIfUserIsLost(data.destination); } }; @@ -180,13 +129,11 @@ export const buildOnInvocationComplete = ( dispatch(stagingAreaImageStaged({ stagingAreaImage: { imageDTO, offsetX: 0, offsetY: 0 } })); } addImageToGallery(imageDTO); - toastIfUserIsLost(data.destination); } } else if (!imageDTO.is_intermediate) { // session.mode === 'generate' setLastCanvasProgressEvent(null); addImageToGallery(imageDTO); - toastIfUserIsLost(data.destination); } }; @@ -195,7 +142,6 @@ export const buildOnInvocationComplete = ( if (imageDTO && !imageDTO.is_intermediate) { addImageToGallery(imageDTO); - toastIfUserIsLost(data.destination); } }; From d02e22efed5bf2f6efed6da7640437260aab42d9 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:28:11 +1000 Subject: [PATCH 09/12] fix(ui): remove unused setting, fix missing translation for alerts --- invokeai/frontend/web/public/locales/en.json | 3 +- .../HUD/CanvasSendingToGalleryAlert.tsx | 6 ++-- .../components/ImageViewer/useImageViewer.ts | 5 +--- .../queue/hooks/useCurrentDestination.ts | 3 +- .../SettingsModal/SettingsModal.tsx | 6 ++-- .../SettingsShowSendToToasts.tsx | 28 ------------------- ...tingsShowSendingToDifferentViewAlerts.tsx} | 12 ++++---- .../src/features/system/store/systemSlice.ts | 16 ++++------- .../web/src/features/system/store/types.ts | 3 +- 9 files changed, 21 insertions(+), 61 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToToasts.tsx rename invokeai/frontend/web/src/features/system/components/SettingsModal/{SettingsShowSendToAlerts.tsx => SettingsShowSendingToDifferentViewAlerts.tsx} (55%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 2dcd94211f1..f932e2f8a71 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -2035,6 +2035,7 @@ "events": "Events", "queue": "Queue", "metadata": "Metadata" - } + }, + "showSendingToAlerts": "Alert When Sending to Different View" } } diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx index d6149049940..d38b60c49eb 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/HUD/CanvasSendingToGalleryAlert.tsx @@ -17,7 +17,7 @@ import { } from 'features/controlLayers/components/CanvasRightPanel'; import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; import { useCurrentDestination } from 'features/queue/hooks/useCurrentDestination'; -import { selectShowSendToAlerts, showSendToAlertsChanged } from 'features/system/store/systemSlice'; +import { selectShowSendingToAlerts, showSendingToAlertsChanged } from 'features/system/store/systemSlice'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { AnimatePresence, motion } from 'framer-motion'; import type { PropsWithChildren, ReactNode } from 'react'; @@ -116,10 +116,10 @@ const AlertWrapper = ({ }) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const showSendToAlerts = useAppSelector(selectShowSendToAlerts); + const showSendToAlerts = useAppSelector(selectShowSendingToAlerts); const isHovered = useBoolean(false); const onClickDontShowMeThese = useCallback(() => { - dispatch(showSendToAlertsChanged(false)); + dispatch(showSendingToAlertsChanged(false)); isHovered.setFalse(); }, [dispatch, isHovered]); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts index f4aa3be0689..732a156e978 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/useImageViewer.ts @@ -1,10 +1,7 @@ import { buildUseBoolean } from 'common/hooks/useBoolean'; import { useMemo } from 'react'; -const hook = buildUseBoolean(true); -const useImageViewerState = hook[0]; - -export const $imageViewerState = hook[1]; +const [useImageViewerState, $imageViewerState] = buildUseBoolean(true); export const useImageViewer = () => { const imageViewerState = useImageViewerState(); diff --git a/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts b/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts index 33b37e05777..773d9666340 100644 --- a/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts +++ b/invokeai/frontend/web/src/features/queue/hooks/useCurrentDestination.ts @@ -1,5 +1,4 @@ -import { useGetCurrentQueueItemQuery } from "services/api/endpoints/queue"; - +import { useGetCurrentQueueItemQuery } from 'services/api/endpoints/queue'; export const useCurrentDestination = () => { const { destination } = useGetCurrentQueueItemQuery(undefined, { diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index 4837c7ad6f6..bfb02998c7e 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -24,8 +24,7 @@ import { useRefreshAfterResetModal } from 'features/system/components/SettingsMo import { SettingsDeveloperLogIsEnabled } from 'features/system/components/SettingsModal/SettingsDeveloperLogIsEnabled'; import { SettingsDeveloperLogLevel } from 'features/system/components/SettingsModal/SettingsDeveloperLogLevel'; import { SettingsDeveloperLogNamespaces } from 'features/system/components/SettingsModal/SettingsDeveloperLogNamespaces'; -import { SettingsShowSendToToasts } from 'features/system/components/SettingsModal/SettingsShowSendToAlerts'; -import { SettingsShowSendToAlerts } from 'features/system/components/SettingsModal/SettingsShowSendToToasts'; +import { SettingsShowSendingToDifferentViewAlerts } from 'features/system/components/SettingsModal/SettingsShowSendingToDifferentViewAlerts'; import { useClearIntermediates } from 'features/system/components/SettingsModal/useClearIntermediates'; import { StickyScrollable } from 'features/system/components/StickyScrollable'; import { @@ -177,8 +176,7 @@ const SettingsModal = ({ config = defaultConfig }: SettingsModalProps) => { {t('settings.confirmOnDelete')} - - + diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToToasts.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToToasts.tsx deleted file mode 100644 index 42abc1cbab6..00000000000 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToToasts.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { selectShowSendToAlerts, showSendToAlertsChanged } from 'features/system/store/systemSlice'; -import type { ChangeEvent } from 'react'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -export const SettingsShowSendToAlerts = memo(() => { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const showSendToAlerts = useAppSelector(selectShowSendToAlerts); - const onChange = useCallback( - (e: ChangeEvent) => { - dispatch(showSendToAlertsChanged(e.target.checked)); - }, - [dispatch] - ); - - return ( - - {t('settings.showAlertsIfLost')} - - - ); -}); - -SettingsShowSendToAlerts.displayName = 'SettingsShowSendToAlerts'; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToAlerts.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendingToDifferentViewAlerts.tsx similarity index 55% rename from invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToAlerts.tsx rename to invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendingToDifferentViewAlerts.tsx index c9f6f26dd21..36bb85aee84 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToAlerts.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendingToDifferentViewAlerts.tsx @@ -1,28 +1,28 @@ import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { selectShowSendToToasts, showSendToToastsChanged } from 'features/system/store/systemSlice'; +import { selectShowSendingToAlerts, showSendingToAlertsChanged } from 'features/system/store/systemSlice'; import type { ChangeEvent } from 'react'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -export const SettingsShowSendToToasts = memo(() => { +export const SettingsShowSendingToDifferentViewAlerts = memo(() => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const isChecked = useAppSelector(selectShowSendToToasts); + const isChecked = useAppSelector(selectShowSendingToAlerts); const onChange = useCallback( (e: ChangeEvent) => { - dispatch(showSendToToastsChanged(e.target.checked)); + dispatch(showSendingToAlertsChanged(e.target.checked)); }, [dispatch] ); return ( - {t('settings.showAlertsIfLost')} + {t('system.showSendingToAlerts')} ); }); -SettingsShowSendToToasts.displayName = 'SettingsShowSendToToasts'; +SettingsShowSendingToDifferentViewAlerts.displayName = 'SettingsShowSendingToDifferentViewAlerts'; diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index a31a84f3dea..f229cd649e7 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -19,8 +19,7 @@ const initialSystemState: SystemState = { logIsEnabled: true, logLevel: 'debug', logNamespaces: [...zLogNamespace.options], - showSendToAlerts: true, - showSendToToasts: true, + showSendingToDifferentViewAlerts: true, }; export const systemSlice = createSlice({ @@ -58,11 +57,8 @@ export const systemSlice = createSlice({ setShouldEnableInformationalPopovers(state, action: PayloadAction) { state.shouldEnableInformationalPopovers = action.payload; }, - showSendToAlertsChanged: (state, action: PayloadAction) => { - state.showSendToAlerts = action.payload; - }, - showSendToToastsChanged: (state, action: PayloadAction) => { - state.showSendToToasts = action.payload; + showSendingToAlertsChanged: (state, action: PayloadAction) => { + state.showSendingToDifferentViewAlerts = action.payload; }, }, }); @@ -77,8 +73,7 @@ export const { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged, setShouldEnableInformationalPopovers, - showSendToAlertsChanged, - showSendToToastsChanged, + showSendingToAlertsChanged, } = systemSlice.actions; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ @@ -113,5 +108,4 @@ export const selectSystemShouldAntialiasProgressImage = createSystemSelector( export const selectSystemShouldEnableInformationalPopovers = createSystemSelector( (system) => system.shouldEnableInformationalPopovers ); -export const selectShowSendToAlerts = createSystemSelector((s) => s.showSendToAlerts); -export const selectShowSendToToasts = createSystemSelector((s) => s.showSendToToasts); +export const selectShowSendingToAlerts = createSystemSelector((s) => s.showSendingToDifferentViewAlerts); diff --git a/invokeai/frontend/web/src/features/system/store/types.ts b/invokeai/frontend/web/src/features/system/store/types.ts index 5860a56770e..d783643f568 100644 --- a/invokeai/frontend/web/src/features/system/store/types.ts +++ b/invokeai/frontend/web/src/features/system/store/types.ts @@ -39,6 +39,5 @@ export interface SystemState { logIsEnabled: boolean; logLevel: LogLevel; logNamespaces: LogNamespace[]; - showSendToAlerts: boolean; - showSendToToasts: boolean; + showSendingToDifferentViewAlerts: boolean; } From ed35da428f6339d61679af049433e412eac96568 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:34:29 +1000 Subject: [PATCH 10/12] feat(ui): do not show canvas progress in viewer --- .../gallery/components/ImageViewer/CurrentImagePreview.tsx | 5 +++-- .../gallery/components/ImageViewer/ProgressImage.tsx | 5 +++-- .../frontend/web/src/services/events/setEventListeners.tsx | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx index 193ebec361a..60c4db26e86 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx @@ -15,7 +15,7 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PiImageBold } from 'react-icons/pi'; import { useGetImageDTOQuery } from 'services/api/endpoints/images'; -import { $hasProgress } from 'services/events/setEventListeners'; +import { $hasProgress, $isProgressFromCanvas } from 'services/events/setEventListeners'; import ProgressImage from './ProgressImage'; @@ -24,6 +24,7 @@ const CurrentImagePreview = () => { const shouldShowImageDetails = useAppSelector(selectShouldShowImageDetails); const imageName = useAppSelector(selectLastSelectedImageName); const hasDenoiseProgress = useStore($hasProgress); + const isProgressFromCanvas = useStore($isProgressFromCanvas); const shouldShowProgressInViewer = useAppSelector(selectShouldShowProgressInViewer); const { currentData: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken); @@ -61,7 +62,7 @@ const CurrentImagePreview = () => { justifyContent="center" position="relative" > - {hasDenoiseProgress && shouldShowProgressInViewer ? ( + {hasDenoiseProgress && !isProgressFromCanvas && shouldShowProgressInViewer ? ( ) : ( { const progressImage = useStore($progressImage); + const isProgressFromCanvas = useStore($isProgressFromCanvas); const shouldAntialiasProgressImage = useAppSelector(selectShouldAntialiasProgressImage); const sx = useMemo( @@ -23,7 +24,7 @@ const CurrentImagePreview = () => { [shouldAntialiasProgressImage] ); - if (!progressImage) { + if (!progressImage || isProgressFromCanvas) { return null; } diff --git a/invokeai/frontend/web/src/services/events/setEventListeners.tsx b/invokeai/frontend/web/src/services/events/setEventListeners.tsx index 010d018ddc0..f99365da967 100644 --- a/invokeai/frontend/web/src/services/events/setEventListeners.tsx +++ b/invokeai/frontend/web/src/services/events/setEventListeners.tsx @@ -40,6 +40,7 @@ const nodeTypeDenylist = ['load_image', 'image']; export const $lastProgressEvent = atom(null); export const $hasProgress = computed($lastProgressEvent, (val) => Boolean(val)); export const $progressImage = computed($lastProgressEvent, (val) => val?.progress_image ?? null); +export const $isProgressFromCanvas = computed($lastProgressEvent, (val) => val?.destination === 'canvas'); export const setEventListeners = ({ socket, dispatch, getState, setIsConnected }: SetEventListenersArg) => { socket.on('connect', () => { From 671d71566f7779fc0b7a1142e5aa7b15a84b49d0 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:37:35 +1000 Subject: [PATCH 11/12] revert(ui): miniviewer toodles --- .../features/gallery/components/Gallery.tsx | 58 ++++--------------- .../gallery/store/gallerySelectors.ts | 1 - .../features/gallery/store/gallerySlice.ts | 5 -- .../web/src/features/gallery/store/types.ts | 1 - 4 files changed, 12 insertions(+), 53 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx index fdbe7de50fc..0ef57047ed4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx @@ -14,14 +14,12 @@ import { import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useGallerySearchTerm'; -import CurrentImageButtons from 'features/gallery/components/ImageViewer/CurrentImageButtons'; -import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview'; -import { selectIsMiniViewerOpen, selectSelectedBoardId } from 'features/gallery/store/gallerySelectors'; -import { galleryViewChanged, isMiniViewerOpenToggled, selectGallerySlice } from 'features/gallery/store/gallerySlice'; +import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors'; +import { galleryViewChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice'; import type { CSSProperties } from 'react'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiEyeBold, PiEyeClosedBold, PiMagnifyingGlassBold } from 'react-icons/pi'; +import { PiMagnifyingGlassBold } from 'react-icons/pi'; import { useBoardName } from 'services/api/hooks/useBoardName'; import GalleryImageGrid from './ImageGrid/GalleryImageGrid'; @@ -52,11 +50,6 @@ export const Gallery = () => { const initialSearchTerm = useAppSelector(selectSearchTerm); const searchDisclosure = useDisclosure({ defaultIsOpen: initialSearchTerm.length > 0 }); const [searchTerm, onChangeSearchTerm, onResetSearchTerm] = useGallerySearchTerm(); - const isMiniViewerOpen = useAppSelector(selectIsMiniViewerOpen); - - const toggleMiniViewer = useCallback(() => { - dispatch(isMiniViewerOpenToggled()); - }, [dispatch]); const handleClickImages = useCallback(() => { dispatch(galleryViewChanged('images')); }, [dispatch]); @@ -87,27 +80,15 @@ export const Gallery = () => { {t('gallery.assets')} - - : } - colorScheme={isMiniViewerOpen ? 'invokeBlue' : 'base'} - /> - } - /> - + } + /> @@ -120,21 +101,6 @@ export const Gallery = () => { /> - - - - - - - - diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts index 8f1e8668754..c7b4daa92c9 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts @@ -57,4 +57,3 @@ export const selectImageToCompare = createSelector(selectGallerySlice, (gallery) export const selectHasImageToCompare = createSelector(selectImageToCompare, (imageToCompare) => Boolean(imageToCompare) ); -export const selectIsMiniViewerOpen = createSelector(selectGallerySlice, (gallery) => gallery.isMiniViewerOpen); diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts index 69e8d6ac4c2..a9f380d7eb7 100644 --- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts +++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts @@ -25,7 +25,6 @@ const initialGalleryState: GalleryState = { comparisonMode: 'slider', comparisonFit: 'fill', shouldShowArchivedBoards: false, - isMiniViewerOpen: false, }; export const gallerySlice = createSlice({ @@ -88,9 +87,6 @@ export const gallerySlice = createSlice({ alwaysShowImageSizeBadgeChanged: (state, action: PayloadAction) => { state.alwaysShowImageSizeBadge = action.payload; }, - isMiniViewerOpenToggled: (state) => { - state.isMiniViewerOpen = !state.isMiniViewerOpen; - }, comparedImagesSwapped: (state) => { if (state.imageToCompare) { const oldSelection = state.selection; @@ -146,7 +142,6 @@ export const { starredFirstChanged, shouldShowArchivedBoardsChanged, searchTermChanged, - isMiniViewerOpenToggled, } = gallerySlice.actions; export const selectGallerySlice = (state: RootState) => state.gallery; diff --git a/invokeai/frontend/web/src/features/gallery/store/types.ts b/invokeai/frontend/web/src/features/gallery/store/types.ts index 4ded8c494e4..48bbc8b7be1 100644 --- a/invokeai/frontend/web/src/features/gallery/store/types.ts +++ b/invokeai/frontend/web/src/features/gallery/store/types.ts @@ -28,5 +28,4 @@ export type GalleryState = { comparisonMode: ComparisonMode; comparisonFit: ComparisonFit; shouldShowArchivedBoards: boolean; - isMiniViewerOpen: boolean; }; From 5cf878a5bf7c663b64235dbbe31513fa0581694a Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:39:55 +1000 Subject: [PATCH 12/12] fix(ui): show send to toggle on canvas only --- .../web/src/features/queue/components/QueueControls.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/queue/components/QueueControls.tsx b/invokeai/frontend/web/src/features/queue/components/QueueControls.tsx index 92774133a47..2e6ead68e57 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueControls.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueControls.tsx @@ -1,15 +1,18 @@ import { Flex, Spacer } from '@invoke-ai/ui-library'; +import { useAppSelector } from 'app/store/storeHooks'; import { ClearQueueIconButton } from 'features/queue/components/ClearQueueIconButton'; import QueueFrontButton from 'features/queue/components/QueueFrontButton'; import { SendToToggle } from 'features/queue/components/SendToToggle'; import ProgressBar from 'features/system/components/ProgressBar'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +import { selectActiveTab } from 'features/ui/store/uiSelectors'; import { memo } from 'react'; import { InvokeQueueBackButton } from './InvokeQueueBackButton'; const QueueControls = () => { const isPrependEnabled = useFeatureStatus('prependQueue'); + const tab = useAppSelector(selectActiveTab); return ( @@ -17,7 +20,7 @@ const QueueControls = () => { {isPrependEnabled && } - + {tab === 'generation' && }