feat(ui): clear features if image used by them is deleted

This handles the case when an image is deleted but is still in use in as eg an init image on canvas, or a control image. If we just delete the image, canvas/controlnet/etc may break (the image would just fail to load).

When an image is deleted, the app checks to see if it is in use in:
- Image to Image
- ControlNet
- Unified Canvas
- Node Editor

The delete dialog will always open if the image is in use anywhere, and the user is advised that deleting the image will reset the feature(s).

Even if the user has ticked the box to not confirm on delete, the dialog will still show if the image is in use somewhere.
This commit is contained in:
psychedelicious
2023-06-06 01:30:06 +10:00
parent 3d249c4fa3
commit bf116927e1
9 changed files with 135 additions and 46 deletions

View File

@ -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 = ({
<FloatingGalleryButton />
</Portal>
</Grid>
<DeleteModal />
<DeleteImageModal />
<Toaster />
<GlobalHotkeys />
</>

View File

@ -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<DeleteImageContextValue>({
@ -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<ImageDTO>();
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 (
<DeleteImageContext.Provider
value={{
isOpen,
image: imageToDelete,
onClose: closeAndClearImageToDelete,
onDelete,
onImmediatelyDelete,