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 (
-
-
- }
- >
- {sendToCanvas ? t('controlLayers.sendingToCanvas') : t('controlLayers.sendingToGallery')}
-
-
-
-
-
-
-
-
-
-
-
-
- );
-});
-
-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(() => {
}>
-
+
- }
+
+ >
+ {t('gallery.exitCompare')}
+
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 (
-
+
diff --git a/invokeai/frontend/web/src/features/queue/components/SendToToggle.tsx b/invokeai/frontend/web/src/features/queue/components/SendToToggle.tsx
new file mode 100644
index 00000000000..17ca5e8f14d
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/SendToToggle.tsx
@@ -0,0 +1,145 @@
+import type { SystemStyleObject } from '@invoke-ai/ui-library';
+import { Box, Flex, IconButton, Text, Tooltip, useToken } from '@invoke-ai/ui-library';
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+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';
+
+const TooltipSendToGallery = memo(() => {
+ const { t } = useTranslation();
+
+ return (
+
+ {t('controlLayers.sendToGallery')}
+ {t('controlLayers.sendToGalleryDesc')}
+
+ );
+});
+
+TooltipSendToGallery.displayName = 'TooltipSendToGallery';
+
+const TooltipSendToCanvas = memo(() => {
+ const { t } = useTranslation();
+
+ return (
+
+ {t('controlLayers.sendToCanvas')}
+ {t('controlLayers.sendToCanvasDesc')}
+
+ );
+});
+
+TooltipSendToCanvas.displayName = 'TooltipSendToCanvas';
+
+const selectSendToCanvas = createSelector(selectCanvasSettingsSlice, (canvasSettings) => canvasSettings.sendToCanvas);
+
+const getSx = (padding: string | number): SystemStyleObject => ({
+ transition: 'left 0.1s ease-in-out, transform 0.1s ease-in-out',
+ '&[data-checked="true"]': {
+ left: `calc(100% - ${padding})`,
+ transform: 'translateX(-100%)',
+ },
+ '&[data-checked="false"]': {
+ left: padding,
+ transform: 'translateX(0)',
+ },
+});
+
+export const SendToToggle = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const sendToCanvas = useAppSelector(selectSendToCanvas);
+
+ const gap = useToken('space', 1.5);
+ const sx = useMemo(() => getSx(gap), [gap]);
+
+ const onClickSendToGallery = useCallback(() => {
+ if (!sendToCanvas) {
+ return;
+ }
+ toast({
+ id: 'sendToCanvas',
+ title: t('controlLayers.sendingToGallery'),
+ description: t('controlLayers.sendToGalleryDesc'),
+ status: 'info',
+ withCount: false,
+ });
+ dispatch(settingsSendToCanvasChanged(false));
+ }, [dispatch, sendToCanvas, t]);
+
+ 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]);
+
+ return (
+
+
+ }>
+ }
+ onClick={onClickSendToGallery}
+ variant={!sendToCanvas ? 'solid' : 'ghost'}
+ colorScheme={!sendToCanvas ? 'invokeBlue' : 'base'}
+ aria-label={t('controlLayers.sendToGallery')}
+ data-checked={!sendToCanvas}
+ w={12}
+ alignSelf="stretch"
+ h="auto"
+ />
+
+ }>
+ }
+ onClick={onClickSendToCanvas}
+ variant={sendToCanvas ? 'solid' : 'ghost'}
+ colorScheme={sendToCanvas ? 'invokeGreen' : 'base'}
+ aria-label={t('controlLayers.sendToCanvas')}
+ data-checked={sendToCanvas}
+ w={12}
+ alignSelf="stretch"
+ h="auto"
+ />
+
+
+ );
+});
+
+SendToToggle.displayName = 'CanvasSendToToggle';
diff --git a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx
index 06c7e70c7f5..6e24ecb6480 100644
--- a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx
+++ b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx
@@ -3,12 +3,13 @@ import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
+import { useGetCurrentQueueItemQuery, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
import { $lastProgressEvent } from 'services/events/setEventListeners';
const ProgressBar = () => {
const { t } = useTranslation();
const { data: queueStatus } = useGetQueueStatusQuery();
+ const currentQueueItem = useGetCurrentQueueItemQuery().data;
const isConnected = useStore($isConnected);
const lastProgressEvent = useStore($lastProgressEvent);
const value = useMemo(() => {
@@ -25,7 +26,7 @@ const ProgressBar = () => {
isIndeterminate={isConnected && Boolean(queueStatus?.queue.in_progress) && !lastProgressEvent}
h={2}
w="full"
- colorScheme="invokeBlue"
+ colorScheme={currentQueueItem && currentQueueItem.destination === 'canvas' ? 'invokeGreen' : 'invokeBlue'}
/>
);
};
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 07d633ddca2..4837c7ad6f6 100644
--- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
+++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
@@ -24,6 +24,8 @@ 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 { useClearIntermediates } from 'features/system/components/SettingsModal/useClearIntermediates';
import { StickyScrollable } from 'features/system/components/StickyScrollable';
import {
@@ -175,6 +177,8 @@ const SettingsModal = ({ config = defaultConfig }: SettingsModalProps) => {
{t('settings.confirmOnDelete')}
+
+
diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToAlerts.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToAlerts.tsx
new file mode 100644
index 00000000000..c9f6f26dd21
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToAlerts.tsx
@@ -0,0 +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 type { ChangeEvent } from 'react';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+
+export const SettingsShowSendToToasts = memo(() => {
+ const dispatch = useAppDispatch();
+ const { t } = useTranslation();
+
+ const isChecked = useAppSelector(selectShowSendToToasts);
+ const onChange = useCallback(
+ (e: ChangeEvent) => {
+ dispatch(showSendToToastsChanged(e.target.checked));
+ },
+ [dispatch]
+ );
+
+ return (
+
+ {t('settings.showAlertsIfLost')}
+
+
+ );
+});
+
+SettingsShowSendToToasts.displayName = 'SettingsShowSendToToasts';
diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToToasts.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToToasts.tsx
new file mode 100644
index 00000000000..42abc1cbab6
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsShowSendToToasts.tsx
@@ -0,0 +1,28 @@
+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/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts
index 854d5816e82..a31a84f3dea 100644
--- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts
+++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts
@@ -19,6 +19,8 @@ const initialSystemState: SystemState = {
logIsEnabled: true,
logLevel: 'debug',
logNamespaces: [...zLogNamespace.options],
+ showSendToAlerts: true,
+ showSendToToasts: true,
};
export const systemSlice = createSlice({
@@ -56,6 +58,12 @@ 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;
+ },
},
});
@@ -69,6 +77,8 @@ export const {
shouldUseNSFWCheckerChanged,
shouldUseWatermarkerChanged,
setShouldEnableInformationalPopovers,
+ showSendToAlertsChanged,
+ showSendToToastsChanged,
} = systemSlice.actions;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
@@ -103,3 +113,5 @@ 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);
diff --git a/invokeai/frontend/web/src/features/system/store/types.ts b/invokeai/frontend/web/src/features/system/store/types.ts
index 10d559690d3..5860a56770e 100644
--- a/invokeai/frontend/web/src/features/system/store/types.ts
+++ b/invokeai/frontend/web/src/features/system/store/types.ts
@@ -39,4 +39,6 @@ export interface SystemState {
logIsEnabled: boolean;
logLevel: LogLevel;
logNamespaces: LogNamespace[];
+ showSendToAlerts: boolean;
+ showSendToToasts: boolean;
}
diff --git a/invokeai/frontend/web/src/features/ui/components/AppContent.tsx b/invokeai/frontend/web/src/features/ui/components/AppContent.tsx
index 46d91013520..1a711a0f2c9 100644
--- a/invokeai/frontend/web/src/features/ui/components/AppContent.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/AppContent.tsx
@@ -1,8 +1,8 @@
import { Box, Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
-import { CanvasRightPanelContent } from 'features/controlLayers/components/CanvasRightPanel';
-import { CanvasTabContent } from 'features/controlLayers/components/CanvasTabContent';
+import { CanvasMainPanelContent } from 'features/controlLayers/components/CanvasMainPanelContent';
+import { CanvasRightPanel } from 'features/controlLayers/components/CanvasRightPanel';
import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import NodeEditorPanelGroup from 'features/nodes/components/sidePanel/NodeEditorPanelGroup';
@@ -157,10 +157,10 @@ const RightPanelContent = memo(() => {
const tab = useAppSelector(selectActiveTab);
if (tab === 'generation') {
- return ;
+ return ;
}
- if (tab === 'upscaling' || tab === 'workflows' || tab === 'gallery') {
+ if (tab === 'upscaling' || tab === 'workflows') {
return ;
}
@@ -189,7 +189,7 @@ LeftPanelContent.displayName = 'LeftPanelContent';
const MainPanelContent = memo(() => {
const tab = useAppSelector(selectActiveTab);
if (tab === 'generation') {
- return ;
+ return ;
}
if (tab === 'upscaling') {
return ;
@@ -197,9 +197,6 @@ const MainPanelContent = memo(() => {
if (tab === 'workflows') {
return ;
}
- if (tab === 'gallery') {
- return ;
- }
if (tab === 'models') {
return ;
}
diff --git a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx b/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx
index cdf78f44697..28168b563e6 100644
--- a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx
@@ -8,7 +8,7 @@ import { TabMountGate } from 'features/ui/components/TabMountGate';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { MdZoomOutMap } from 'react-icons/md';
-import { PiFlowArrowBold, PiImageBold } from 'react-icons/pi';
+import { PiFlowArrowBold } from 'react-icons/pi';
import { RiBox2Line, RiInputMethodLine, RiPlayList2Fill } from 'react-icons/ri';
import { TabButton } from './TabButton';
@@ -36,9 +36,6 @@ export const VerticalNavBar = memo(() => {
} label={t('ui.tabs.queue')} />
-
- } label={t('ui.tabs.gallery')} />
-
diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
index 1bd2ed064f0..ac1c5a52e3e 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
@@ -79,7 +79,7 @@ const TABS_WITH_LEFT_PANEL: TabName[] = ['generation', 'upscaling', 'workflows']
export const $isLeftPanelOpen = atom(true);
export const selectWithLeftPanel = createSelector(selectUiSlice, (ui) => TABS_WITH_LEFT_PANEL.includes(ui.activeTab));
-const TABS_WITH_RIGHT_PANEL: TabName[] = ['generation', 'upscaling', 'workflows', 'gallery'] as const;
+const TABS_WITH_RIGHT_PANEL: TabName[] = ['generation', 'upscaling', 'workflows'] as const;
export const RIGHT_PANEL_MIN_SIZE_PX = 390;
export const RIGHT_PANEL_MIN_SIZE_PCT = 20;
export const $isRightPanelOpen = atom(true);
diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
index 7306a85b324..3e6eb5a0624 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
@@ -1,4 +1,4 @@
-export type TabName = 'generation' | 'upscaling' | 'workflows' | 'models' | 'queue' | 'gallery';
+export type TabName = 'generation' | 'upscaling' | 'workflows' | 'models' | 'queue';
export interface UIState {
/**
diff --git a/invokeai/frontend/web/src/services/api/endpoints/queue.ts b/invokeai/frontend/web/src/services/api/endpoints/queue.ts
index 911154377e0..377671c745d 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/queue.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/queue.ts
@@ -368,6 +368,7 @@ export const {
useListQueueItemsQuery,
useCancelQueueItemMutation,
useGetBatchStatusQuery,
+ useGetCurrentQueueItemQuery,
} = queueApi;
export const selectQueueStatus = queueApi.endpoints.getQueueStatus.select();
diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.ts b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
similarity index 69%
rename from invokeai/frontend/web/src/services/events/onInvocationComplete.ts
rename to invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
index ec74bb79dce..e554c966168 100644
--- a/invokeai/frontend/web/src/services/events/onInvocationComplete.ts
+++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
@@ -1,11 +1,19 @@
+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';
@@ -17,6 +25,22 @@ 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,
@@ -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' && }