diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx
index 67d0091261..bb2f140716 100644
--- a/invokeai/frontend/web/src/app/components/App.tsx
+++ b/invokeai/frontend/web/src/app/components/App.tsx
@@ -21,7 +21,7 @@ import { ReactNode, memo, useCallback, useEffect, useState } from 'react';
import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants';
import GlobalHotkeys from './GlobalHotkeys';
import Toaster from './Toaster';
-import DeleteModal from 'features/gallery/components/DeleteModal';
+import DeleteImageModal from 'features/gallery/components/DeleteImageModal';
const DEFAULT_CONFIG = {};
@@ -134,7 +134,7 @@ const App = ({
-
+
>
diff --git a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx
index 1d129d4e00..2f2bc4625b 100644
--- a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx
+++ b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx
@@ -7,6 +7,12 @@ import { systemSelector } from 'features/system/store/systemSelectors';
import { PropsWithChildren, createContext, useCallback, useState } from 'react';
import { ImageDTO } from 'services/api';
+import { useImageUsage } from 'common/hooks/useImageUsage';
+import { resetCanvas } from 'features/canvas/store/canvasSlice';
+import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
+import { clearInitialImage } from 'features/parameters/store/generationSlice';
+import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
+
type DeleteImageContextValue = {
/**
* Whether the delete image dialog is open.
@@ -16,16 +22,20 @@ type DeleteImageContextValue = {
* Closes the delete image dialog.
*/
onClose: () => void;
+ /**
+ * Opens the delete image dialog and handles all deletion-related checks.
+ */
+ onDelete: (image?: ImageDTO) => void;
+ /**
+ * The image pending deletion
+ */
+ image?: ImageDTO;
/**
* Immediately deletes an image.
*
* You probably don't want to use this - use `onDelete` instead.
*/
onImmediatelyDelete: () => void;
- /**
- * Opens the delete image dialog and handles all deletion-related checks.
- */
- onDelete: (image?: ImageDTO) => void;
};
export const DeleteImageContext = createContext({
@@ -43,8 +53,6 @@ const selector = createSelector(
return {
canDeleteImage: isConnected && !isProcessing,
shouldConfirmOnDelete,
- isProcessing,
- isConnected,
};
},
defaultSelectorOptions
@@ -57,6 +65,35 @@ export const DeleteImageContextProvider = (props: Props) => {
const [imageToDelete, setImageToDelete] = useState();
const dispatch = useAppDispatch();
const { isOpen, onOpen, onClose } = useDisclosure();
+ const imageUsage = useImageUsage(imageToDelete?.image_name);
+
+ const handleActualDeletion = useCallback(
+ (image: ImageDTO) => {
+ dispatch(requestedImageDeletion(image));
+
+ if (imageUsage.isCanvasImage) {
+ dispatch(resetCanvas());
+ }
+
+ if (imageUsage.isControlNetImage) {
+ dispatch(controlNetReset());
+ }
+
+ if (imageUsage.isInitialImage) {
+ dispatch(clearInitialImage());
+ }
+
+ if (imageUsage.isControlNetImage) {
+ dispatch(nodeEditorReset());
+ }
+ },
+ [
+ dispatch,
+ imageUsage.isCanvasImage,
+ imageUsage.isControlNetImage,
+ imageUsage.isInitialImage,
+ ]
+ );
const closeAndClearImageToDelete = useCallback(() => {
setImageToDelete(undefined);
@@ -65,20 +102,25 @@ export const DeleteImageContextProvider = (props: Props) => {
const onImmediatelyDelete = useCallback(() => {
if (canDeleteImage && imageToDelete) {
- dispatch(requestedImageDeletion(imageToDelete));
+ handleActualDeletion(imageToDelete);
}
closeAndClearImageToDelete();
- }, [canDeleteImage, imageToDelete, closeAndClearImageToDelete, dispatch]);
+ }, [
+ canDeleteImage,
+ imageToDelete,
+ closeAndClearImageToDelete,
+ handleActualDeletion,
+ ]);
- const handleDelete = useCallback(
+ const handleGatedDeletion = useCallback(
(image: ImageDTO) => {
- if (shouldConfirmOnDelete) {
+ if (shouldConfirmOnDelete || imageUsage) {
onOpen();
} else {
- dispatch(requestedImageDeletion(image));
+ handleActualDeletion(image);
}
},
- [shouldConfirmOnDelete, onOpen, dispatch]
+ [shouldConfirmOnDelete, imageUsage, onOpen, handleActualDeletion]
);
const onDelete = useCallback(
@@ -87,15 +129,16 @@ export const DeleteImageContextProvider = (props: Props) => {
return;
}
setImageToDelete(image);
- handleDelete(image);
+ handleGatedDeletion(image);
},
- [handleDelete]
+ [handleGatedDeletion]
);
return (
image_name,
+ (state: RootState, image_name?: string) => image_name,
],
(generation, canvas, nodes, controlNet, image_name) => {
const isInitialImage = generation.initialImage?.image_name === image_name;
@@ -37,18 +45,22 @@ const selectIsImageInUse = createSelector(
c.processedControlImage?.image_name === image_name
);
- return {
+ const imageUsage: ImageUsage = {
isInitialImage,
isCanvasImage,
isNodesImage,
isControlNetImage,
};
+
+ return imageUsage;
},
defaultSelectorOptions
);
-export const useGetIsImageInUse = (image_name?: string) => {
- const a = useAppSelector((state) => selectIsImageInUse(state, image_name));
+export const useImageUsage = (image_name?: string) => {
+ const imageUsage = useAppSelector((state) =>
+ selectImageUsage(state, image_name)
+ );
- return a;
+ return imageUsage;
};
diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts
index da76ce4a8a..92d6c302e9 100644
--- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts
+++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts
@@ -187,6 +187,9 @@ export const controlNetSlice = createSlice({
processorType
].default as RequiredControlNetProcessorNode;
},
+ controlNetReset: () => {
+ return { ...initialControlNetState };
+ },
},
extraReducers: (builder) => {
builder.addCase(controlNetImageProcessed, (state, action) => {
@@ -243,6 +246,7 @@ export const {
controlNetEndStepPctChanged,
controlNetProcessorParamsChanged,
controlNetProcessorTypeChanged,
+ controlNetReset,
} = controlNetSlice.actions;
export default controlNetSlice.reducer;
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
index 333ad516ef..a5eaeb4c71 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
@@ -50,7 +50,7 @@ import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/U
import { useAppToaster } from 'app/components/Toaster';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
-import { DeleteImageButton } from './DeleteModal';
+import { DeleteImageButton } from './DeleteImageModal';
const currentImageButtonsSelector = createSelector(
[
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
index b8d9d6220a..5e210bf4b7 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
@@ -15,7 +15,6 @@ import { imageSelected } from '../store/gallerySlice';
import IAIDndImage from 'common/components/IAIDndImage';
import { ImageDTO } from 'services/api';
import { IAIImageFallback } from 'common/components/IAIImageFallback';
-import { useGetIsImageInUse } from 'common/hooks/useGetIsImageInUse';
export const imagesSelector = createSelector(
[uiSelector, gallerySelector, systemSelector],
@@ -55,8 +54,6 @@ const CurrentImagePreview = () => {
const toaster = useAppToaster();
const dispatch = useAppDispatch();
- const isImageInUse = useGetIsImageInUse(image?.image_name);
- console.log(isImageInUse);
const handleError = useCallback(() => {
dispatch(imageSelected());
if (shouldFetchImages) {
diff --git a/invokeai/frontend/web/src/features/gallery/components/DeleteModal.tsx b/invokeai/frontend/web/src/features/gallery/components/DeleteImageModal.tsx
similarity index 68%
rename from invokeai/frontend/web/src/features/gallery/components/DeleteModal.tsx
rename to invokeai/frontend/web/src/features/gallery/components/DeleteImageModal.tsx
index ca06aa7953..335944df43 100644
--- a/invokeai/frontend/web/src/features/gallery/components/DeleteModal.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/DeleteImageModal.tsx
@@ -5,19 +5,24 @@ import {
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
+ Divider,
Flex,
+ ListItem,
Text,
+ UnorderedList,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton';
import IAIIconButton from 'common/components/IAIIconButton';
import IAISwitch from 'common/components/IAISwitch';
+import { ImageUsage, useImageUsage } from 'common/hooks/useImageUsage';
import { configSelector } from 'features/system/store/configSelectors';
import { systemSelector } from 'features/system/store/systemSelectors';
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
-import { isEqual } from 'lodash-es';
+import { some } from 'lodash-es';
import { ChangeEvent, memo, useCallback, useContext, useRef } from 'react';
import { useTranslation } from 'react-i18next';
@@ -28,31 +33,56 @@ const selector = createSelector(
(system, config) => {
const { shouldConfirmOnDelete } = system;
const { canRestoreDeletedImagesFromBin } = config;
- return { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin };
+
+ return {
+ shouldConfirmOnDelete,
+ canRestoreDeletedImagesFromBin,
+ };
},
- {
- memoizeOptions: {
- resultEqualityCheck: isEqual,
- },
- }
+ defaultSelectorOptions
);
+const ImageInUseMessage = (props: { imageUsage: ImageUsage }) => {
+ const { imageUsage } = props;
+
+ if (!some(imageUsage)) {
+ return null;
+ }
+
+ return (
+ <>
+ This image is currently in use in the following features:
+
+ {imageUsage.isInitialImage && Image to Image}
+ {imageUsage.isCanvasImage && Unified Canvas}
+ {imageUsage.isControlNetImage && ControlNet}
+ {imageUsage.isNodesImage && Node Editor}
+
+
+ If you delete this image, those features will immediately be reset.
+
+ >
+ );
+};
+
const DeleteImageModal = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
+ const { isOpen, onClose, onImmediatelyDelete, image } =
+ useContext(DeleteImageContext);
+
const { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin } =
useAppSelector(selector);
+ const imageUsage = useImageUsage(image?.image_name);
+
const handleChangeShouldConfirmOnDelete = useCallback(
(e: ChangeEvent) =>
dispatch(setShouldConfirmOnDelete(!e.target.checked)),
[dispatch]
);
- const { isOpen, onClose, onImmediatelyDelete } =
- useContext(DeleteImageContext);
-
const cancelRef = useRef(null);
return (
@@ -69,15 +99,15 @@ const DeleteImageModal = () => {
-
-
- {t('common.areYouSure')}
-
- {canRestoreDeletedImagesFromBin
- ? t('gallery.deleteImageBin')
- : t('gallery.deleteImagePermanent')}
-
-
+
+
+
+
+ {canRestoreDeletedImagesFromBin
+ ? t('gallery.deleteImageBin')
+ : t('gallery.deleteImagePermanent')}
+
+ {t('common.areYouSure')}
{
+ return { ...initialNodesState };
+ },
},
extraReducers(builder) {
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
@@ -127,6 +130,7 @@ export const {
connectionEnded,
shouldShowGraphOverlayChanged,
parsedOpenAPISchema,
+ nodeEditorReset,
} = nodesSlice.actions;
export default nodesSlice.reducer;
diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx
index 73efb69728..c006215256 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx
@@ -14,7 +14,6 @@ import { useAppToaster } from 'app/components/Toaster';
import IAIDndImage from 'common/components/IAIDndImage';
import { ImageDTO } from 'services/api';
import { IAIImageFallback } from 'common/components/IAIImageFallback';
-import { useGetIsImageInUse } from 'common/hooks/useGetIsImageInUse';
const selector = createSelector(
[generationSelector],