From 3d249c4fa3bebafe744c4e9e52b3159bc3450d5a Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 6 Jun 2023 00:40:40 +1000
Subject: [PATCH] feat(ui): refactor image deletion
Add `DeleteImageContext`:
- provide a single function to delete an image
- opens the modal or immediately deletes, if confirm is off
---
.../frontend/web/src/app/components/App.tsx | 2 +
.../web/src/app/components/InvokeAIUI.tsx | 16 ++-
.../src/app/contexts/DeleteImageContext.tsx | 107 ++++++++++++++++++
.../components/CurrentImageButtons.tsx | 61 ++--------
.../components/CurrentImagePreview.tsx | 3 +
.../{DeleteImageModal.tsx => DeleteModal.tsx} | 59 +++++++---
.../gallery/components/HoverableImage.tsx | 66 +++--------
.../ImageActionButtons/DeleteImageButton.tsx | 92 ---------------
.../ImageToImage/InitialImagePreview.tsx | 1 +
9 files changed, 191 insertions(+), 216 deletions(-)
create mode 100644 invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx
rename invokeai/frontend/web/src/features/gallery/components/{DeleteImageModal.tsx => DeleteModal.tsx} (70%)
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx
diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx
index 21b3945490..67d0091261 100644
--- a/invokeai/frontend/web/src/app/components/App.tsx
+++ b/invokeai/frontend/web/src/app/components/App.tsx
@@ -21,6 +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';
const DEFAULT_CONFIG = {};
@@ -133,6 +134,7 @@ const App = ({
+
>
diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
index c94f7624b2..0537d1de2a 100644
--- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
+++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
@@ -17,6 +17,10 @@ import '../../i18n';
import { socketMiddleware } from 'services/events/middleware';
import { Middleware } from '@reduxjs/toolkit';
import ImageDndContext from './ImageDnd/ImageDndContext';
+import {
+ DeleteImageContext,
+ DeleteImageContextProvider,
+} from 'app/contexts/DeleteImageContext';
const App = lazy(() => import('./App'));
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
@@ -71,11 +75,13 @@ const InvokeAIUI = ({
}>
-
+
+
+
diff --git a/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx
new file mode 100644
index 0000000000..1d129d4e00
--- /dev/null
+++ b/invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx
@@ -0,0 +1,107 @@
+import { useDisclosure } from '@chakra-ui/react';
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
+import { requestedImageDeletion } from 'features/gallery/store/actions';
+import { systemSelector } from 'features/system/store/systemSelectors';
+import { PropsWithChildren, createContext, useCallback, useState } from 'react';
+import { ImageDTO } from 'services/api';
+
+type DeleteImageContextValue = {
+ /**
+ * Whether the delete image dialog is open.
+ */
+ isOpen: boolean;
+ /**
+ * Closes the delete image dialog.
+ */
+ onClose: () => void;
+ /**
+ * 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({
+ isOpen: false,
+ onClose: () => undefined,
+ onImmediatelyDelete: () => undefined,
+ onDelete: () => undefined,
+});
+
+const selector = createSelector(
+ [systemSelector],
+ (system) => {
+ const { isProcessing, isConnected, shouldConfirmOnDelete } = system;
+
+ return {
+ canDeleteImage: isConnected && !isProcessing,
+ shouldConfirmOnDelete,
+ isProcessing,
+ isConnected,
+ };
+ },
+ defaultSelectorOptions
+);
+
+type Props = PropsWithChildren;
+
+export const DeleteImageContextProvider = (props: Props) => {
+ const { canDeleteImage, shouldConfirmOnDelete } = useAppSelector(selector);
+ const [imageToDelete, setImageToDelete] = useState();
+ const dispatch = useAppDispatch();
+ const { isOpen, onOpen, onClose } = useDisclosure();
+
+ const closeAndClearImageToDelete = useCallback(() => {
+ setImageToDelete(undefined);
+ onClose();
+ }, [onClose]);
+
+ const onImmediatelyDelete = useCallback(() => {
+ if (canDeleteImage && imageToDelete) {
+ dispatch(requestedImageDeletion(imageToDelete));
+ }
+ closeAndClearImageToDelete();
+ }, [canDeleteImage, imageToDelete, closeAndClearImageToDelete, dispatch]);
+
+ const handleDelete = useCallback(
+ (image: ImageDTO) => {
+ if (shouldConfirmOnDelete) {
+ onOpen();
+ } else {
+ dispatch(requestedImageDeletion(image));
+ }
+ },
+ [shouldConfirmOnDelete, onOpen, dispatch]
+ );
+
+ const onDelete = useCallback(
+ (image?: ImageDTO) => {
+ if (!image) {
+ return;
+ }
+ setImageToDelete(image);
+ handleDelete(image);
+ },
+ [handleDelete]
+ );
+
+ return (
+
+ {props.children}
+
+ );
+};
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
index 6862b35fb8..333ad516ef 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
@@ -1,13 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash-es';
-import {
- ButtonGroup,
- Flex,
- FlexProps,
- Link,
- useDisclosure,
-} from '@chakra-ui/react';
+import { ButtonGroup, Flex, FlexProps, Link } from '@chakra-ui/react';
// import { runESRGAN, runFacetool } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
@@ -45,21 +39,18 @@ import {
FaShareAlt,
} from 'react-icons/fa';
import { gallerySelector } from '../store/gallerySelectors';
-import { useCallback } from 'react';
+import { useCallback, useContext } from 'react';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { initialImageSelected } from 'features/parameters/store/actions';
-import {
- requestedImageDeletion,
- sentImageToCanvas,
- sentImageToImg2Img,
-} from '../store/actions';
+import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
import FaceRestoreSettings from 'features/parameters/components/Parameters/FaceRestore/FaceRestoreSettings';
import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/UpscaleSettings';
-import DeleteImageButton from './ImageActionButtons/DeleteImageButton';
import { useAppToaster } from 'app/components/Toaster';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
+import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
+import { DeleteImageButton } from './DeleteModal';
const currentImageButtonsSelector = createSelector(
[
@@ -122,10 +113,6 @@ const currentImageButtonsSelector = createSelector(
type CurrentImageButtonsProps = FlexProps;
-/**
- * Row of buttons for common actions:
- * Use as init image, use all params, use seed, upscale, fix faces, details, delete.
- */
const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
const dispatch = useAppDispatch();
const {
@@ -137,13 +124,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
facetoolStrength,
shouldDisableToolbarButtons,
shouldShowImageDetails,
- // currentImage,
isLightboxOpen,
activeTabName,
shouldHidePreview,
image,
- canDeleteImage,
- shouldConfirmOnDelete,
shouldShowProgressInViewer,
} = useAppSelector(currentImageButtonsSelector);
@@ -152,18 +136,14 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
const isFaceRestoreEnabled = useFeatureStatus('faceRestore').isFeatureEnabled;
- const {
- isOpen: isDeleteDialogOpen,
- onOpen: onDeleteDialogOpen,
- onClose: onDeleteDialogClose,
- } = useDisclosure();
-
const toaster = useAppToaster();
const { t } = useTranslation();
const { recallBothPrompts, recallSeed, recallAllParameters } =
useRecallParameters();
+ const { onDelete } = useContext(DeleteImageContext);
+
// const handleCopyImage = useCallback(async () => {
// if (!image?.url) {
// return;
@@ -262,6 +242,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
// selectedImage && dispatch(runESRGAN(selectedImage));
}, []);
+ const handleDelete = useCallback(() => {
+ onDelete(image);
+ }, [image, onDelete]);
+
useHotkeys(
'Shift+U',
() => {
@@ -363,31 +347,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
[image, shouldShowImageDetails, toaster]
);
- const handleDelete = useCallback(() => {
- if (canDeleteImage && image) {
- dispatch(requestedImageDeletion(image));
- }
- }, [image, canDeleteImage, dispatch]);
-
- const handleInitiateDelete = useCallback(() => {
- if (shouldConfirmOnDelete) {
- onDeleteDialogOpen();
- } else {
- handleDelete();
- }
- }, [shouldConfirmOnDelete, onDeleteDialogOpen, handleDelete]);
-
const handleClickProgressImagesToggle = useCallback(() => {
dispatch(setShouldShowProgressInViewer(!shouldShowProgressInViewer));
}, [dispatch, shouldShowProgressInViewer]);
- useHotkeys('delete', handleInitiateDelete, [
- image,
- shouldConfirmOnDelete,
- isConnected,
- isProcessing,
- ]);
-
const handleLightBox = useCallback(() => {
dispatch(setIsLightboxOpen(!isLightboxOpen));
}, [dispatch, isLightboxOpen]);
@@ -596,7 +559,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
-
+
>
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
index 5e210bf4b7..b8d9d6220a 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
@@ -15,6 +15,7 @@ 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],
@@ -54,6 +55,8 @@ 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/DeleteImageModal.tsx b/invokeai/frontend/web/src/features/gallery/components/DeleteModal.tsx
similarity index 70%
rename from invokeai/frontend/web/src/features/gallery/components/DeleteImageModal.tsx
rename to invokeai/frontend/web/src/features/gallery/components/DeleteModal.tsx
index 12038f4179..ca06aa7953 100644
--- a/invokeai/frontend/web/src/features/gallery/components/DeleteImageModal.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/DeleteModal.tsx
@@ -9,16 +9,19 @@ import {
Text,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
+import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
+import IAIIconButton from 'common/components/IAIIconButton';
import IAISwitch from 'common/components/IAISwitch';
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 { ChangeEvent, memo, useCallback, useRef } from 'react';
+import { ChangeEvent, memo, useCallback, useContext, useRef } from 'react';
import { useTranslation } from 'react-i18next';
+import { FaTrash } from 'react-icons/fa';
const selector = createSelector(
[systemSelector, configSelector],
@@ -34,22 +37,12 @@ const selector = createSelector(
}
);
-interface DeleteImageModalProps {
- isOpen: boolean;
- onClose: () => void;
- handleDelete: () => void;
-}
-
-const DeleteImageModal = ({
- isOpen,
- onClose,
- handleDelete,
-}: DeleteImageModalProps) => {
+const DeleteImageModal = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
+
const { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin } =
useAppSelector(selector);
- const cancelRef = useRef(null);
const handleChangeShouldConfirmOnDelete = useCallback(
(e: ChangeEvent) =>
@@ -57,10 +50,10 @@ const DeleteImageModal = ({
[dispatch]
);
- const handleClickDelete = useCallback(() => {
- handleDelete();
- onClose();
- }, [handleDelete, onClose]);
+ const { isOpen, onClose, onImmediatelyDelete } =
+ useContext(DeleteImageContext);
+
+ const cancelRef = useRef(null);
return (
Cancel
-
+
Delete
@@ -107,3 +100,33 @@ const DeleteImageModal = ({
};
export default memo(DeleteImageModal);
+
+const deleteImageButtonsSelector = createSelector(
+ [systemSelector],
+ (system) => {
+ const { isProcessing, isConnected } = system;
+
+ return isConnected && !isProcessing;
+ }
+);
+
+type DeleteImageButtonProps = {
+ onClick: () => void;
+};
+
+export const DeleteImageButton = (props: DeleteImageButtonProps) => {
+ const { onClick } = props;
+ const { t } = useTranslation();
+ const canDeleteImage = useAppSelector(deleteImageButtonsSelector);
+
+ return (
+ }
+ tooltip={`${t('gallery.deleteImage')} (Del)`}
+ aria-label={`${t('gallery.deleteImage')} (Del)`}
+ isDisabled={!canDeleteImage}
+ colorScheme="error"
+ />
+ );
+};
diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx
index ef4ed5be1c..2b8f72101d 100644
--- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx
@@ -1,17 +1,8 @@
-import {
- Box,
- Flex,
- Icon,
- Image,
- MenuItem,
- MenuList,
- useDisclosure,
-} from '@chakra-ui/react';
+import { Box, Flex, Icon, Image, MenuItem, MenuList } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { imageSelected } from 'features/gallery/store/gallerySlice';
-import { DragEvent, MouseEvent, memo, useCallback, useState } from 'react';
+import { memo, useCallback, useContext, useState } from 'react';
import { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa';
-import DeleteImageModal from './DeleteImageModal';
import { ContextMenu } from 'chakra-ui-contextmenu';
import {
resizeAndScaleCanvas,
@@ -31,14 +22,11 @@ import { isEqual } from 'lodash-es';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { initialImageSelected } from 'features/parameters/store/actions';
-import {
- requestedImageDeletion,
- sentImageToCanvas,
- sentImageToImg2Img,
-} from '../store/actions';
+import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
import { useAppToaster } from 'app/components/Toaster';
import { ImageDTO } from 'services/api';
import { useDraggable } from '@dnd-kit/core';
+import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
export const selector = createSelector(
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
@@ -92,27 +80,22 @@ const HoverableImage = memo((props: HoverableImageProps) => {
galleryImageMinimumWidth,
canDeleteImage,
shouldUseSingleGalleryColumn,
- shouldConfirmOnDelete,
} = useAppSelector(selector);
- const {
- isOpen: isDeleteDialogOpen,
- onOpen: onDeleteDialogOpen,
- onClose: onDeleteDialogClose,
- } = useDisclosure();
-
const { image, isSelected } = props;
const { image_url, thumbnail_url, image_name } = image;
const [isHovered, setIsHovered] = useState(false);
-
const toaster = useAppToaster();
const { t } = useTranslation();
-
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
+ const { onDelete } = useContext(DeleteImageContext);
+ const handleDelete = useCallback(() => {
+ onDelete(image);
+ }, [image, onDelete]);
const { recallBothPrompts, recallSeed, recallAllParameters } =
useRecallParameters();
@@ -126,26 +109,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const handleMouseOver = () => setIsHovered(true);
const handleMouseOut = () => setIsHovered(false);
- // Immediately deletes an image
- const handleDelete = useCallback(() => {
- if (canDeleteImage && image) {
- dispatch(requestedImageDeletion(image));
- }
- }, [dispatch, image, canDeleteImage]);
-
- // Opens the alert dialog to check if user is sure they want to delete
- const handleInitiateDelete = useCallback(
- (e: MouseEvent) => {
- e.stopPropagation();
- if (shouldConfirmOnDelete) {
- onDeleteDialogOpen();
- } else {
- handleDelete();
- }
- },
- [handleDelete, onDeleteDialogOpen, shouldConfirmOnDelete]
- );
-
const handleSelectImage = useCallback(() => {
dispatch(imageSelected(image));
}, [image, dispatch]);
@@ -281,7 +244,11 @@ const HoverableImage = memo((props: HoverableImageProps) => {
{t('parameters.sendToUnifiedCanvas')}
)}
- } onClickCapture={onDeleteDialogOpen}>
+ }
+ onClickCapture={handleDelete}
+ >
{t('gallery.deleteImage')}
@@ -357,7 +324,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
}}
>
}
size="xs"
@@ -369,11 +336,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
)}
-
);
}, memoEqualityCheck);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx
deleted file mode 100644
index 4b0f6e60dd..0000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ImageActionButtons/DeleteImageButton.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { createSelector } from '@reduxjs/toolkit';
-
-import { useDisclosure } from '@chakra-ui/react';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import IAIIconButton from 'common/components/IAIIconButton';
-import { systemSelector } from 'features/system/store/systemSelectors';
-
-import { useHotkeys } from 'react-hotkeys-hook';
-import { useTranslation } from 'react-i18next';
-import { FaTrash } from 'react-icons/fa';
-import { memo, useCallback } from 'react';
-import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
-import DeleteImageModal from '../DeleteImageModal';
-import { requestedImageDeletion } from 'features/gallery/store/actions';
-import { ImageDTO } from 'services/api';
-
-const selector = createSelector(
- [systemSelector],
- (system) => {
- const { isProcessing, isConnected, shouldConfirmOnDelete } = system;
-
- return {
- canDeleteImage: isConnected && !isProcessing,
- shouldConfirmOnDelete,
- isProcessing,
- isConnected,
- };
- },
- defaultSelectorOptions
-);
-
-type DeleteImageButtonProps = {
- image: ImageDTO | undefined;
-};
-
-const DeleteImageButton = (props: DeleteImageButtonProps) => {
- const { image } = props;
- const dispatch = useAppDispatch();
- const { isProcessing, isConnected, canDeleteImage, shouldConfirmOnDelete } =
- useAppSelector(selector);
-
- const {
- isOpen: isDeleteDialogOpen,
- onOpen: onDeleteDialogOpen,
- onClose: onDeleteDialogClose,
- } = useDisclosure();
-
- const { t } = useTranslation();
-
- const handleDelete = useCallback(() => {
- if (canDeleteImage && image) {
- dispatch(requestedImageDeletion(image));
- }
- }, [image, canDeleteImage, dispatch]);
-
- const handleInitiateDelete = useCallback(() => {
- if (shouldConfirmOnDelete) {
- onDeleteDialogOpen();
- } else {
- handleDelete();
- }
- }, [shouldConfirmOnDelete, onDeleteDialogOpen, handleDelete]);
-
- useHotkeys('delete', handleInitiateDelete, [
- image,
- shouldConfirmOnDelete,
- isConnected,
- isProcessing,
- ]);
-
- return (
- <>
- }
- tooltip={`${t('gallery.deleteImage')} (Del)`}
- aria-label={`${t('gallery.deleteImage')} (Del)`}
- isDisabled={!image || !isConnected}
- colorScheme="error"
- />
- {image && (
-
- )}
- >
- );
-};
-
-export default memo(DeleteImageButton);
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 c006215256..73efb69728 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,6 +14,7 @@ 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],