mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
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
This commit is contained in:
parent
fa338ddb6a
commit
3d249c4fa3
@ -21,6 +21,7 @@ import { ReactNode, memo, useCallback, useEffect, useState } from 'react';
|
|||||||
import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants';
|
import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants';
|
||||||
import GlobalHotkeys from './GlobalHotkeys';
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
import Toaster from './Toaster';
|
import Toaster from './Toaster';
|
||||||
|
import DeleteModal from 'features/gallery/components/DeleteModal';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
@ -133,6 +134,7 @@ const App = ({
|
|||||||
<FloatingGalleryButton />
|
<FloatingGalleryButton />
|
||||||
</Portal>
|
</Portal>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<DeleteModal />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<GlobalHotkeys />
|
<GlobalHotkeys />
|
||||||
</>
|
</>
|
||||||
|
@ -17,6 +17,10 @@ import '../../i18n';
|
|||||||
import { socketMiddleware } from 'services/events/middleware';
|
import { socketMiddleware } from 'services/events/middleware';
|
||||||
import { Middleware } from '@reduxjs/toolkit';
|
import { Middleware } from '@reduxjs/toolkit';
|
||||||
import ImageDndContext from './ImageDnd/ImageDndContext';
|
import ImageDndContext from './ImageDnd/ImageDndContext';
|
||||||
|
import {
|
||||||
|
DeleteImageContext,
|
||||||
|
DeleteImageContextProvider,
|
||||||
|
} from 'app/contexts/DeleteImageContext';
|
||||||
|
|
||||||
const App = lazy(() => import('./App'));
|
const App = lazy(() => import('./App'));
|
||||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||||
@ -71,11 +75,13 @@ const InvokeAIUI = ({
|
|||||||
<React.Suspense fallback={<Loading />}>
|
<React.Suspense fallback={<Loading />}>
|
||||||
<ThemeLocaleProvider>
|
<ThemeLocaleProvider>
|
||||||
<ImageDndContext>
|
<ImageDndContext>
|
||||||
<App
|
<DeleteImageContextProvider>
|
||||||
config={config}
|
<App
|
||||||
headerComponent={headerComponent}
|
config={config}
|
||||||
setIsReady={setIsReady}
|
headerComponent={headerComponent}
|
||||||
/>
|
setIsReady={setIsReady}
|
||||||
|
/>
|
||||||
|
</DeleteImageContextProvider>
|
||||||
</ImageDndContext>
|
</ImageDndContext>
|
||||||
</ThemeLocaleProvider>
|
</ThemeLocaleProvider>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
|
107
invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx
Normal file
107
invokeai/frontend/web/src/app/contexts/DeleteImageContext.tsx
Normal file
@ -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<DeleteImageContextValue>({
|
||||||
|
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<ImageDTO>();
|
||||||
|
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 (
|
||||||
|
<DeleteImageContext.Provider
|
||||||
|
value={{
|
||||||
|
isOpen,
|
||||||
|
onClose: closeAndClearImageToDelete,
|
||||||
|
onDelete,
|
||||||
|
onImmediatelyDelete,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</DeleteImageContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -1,13 +1,7 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import {
|
import { ButtonGroup, Flex, FlexProps, Link } from '@chakra-ui/react';
|
||||||
ButtonGroup,
|
|
||||||
Flex,
|
|
||||||
FlexProps,
|
|
||||||
Link,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
// import { runESRGAN, runFacetool } from 'app/socketio/actions';
|
// import { runESRGAN, runFacetool } from 'app/socketio/actions';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
@ -45,21 +39,18 @@ import {
|
|||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { gallerySelector } from '../store/gallerySelectors';
|
import { gallerySelector } from '../store/gallerySelectors';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||||
import {
|
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
||||||
requestedImageDeletion,
|
|
||||||
sentImageToCanvas,
|
|
||||||
sentImageToImg2Img,
|
|
||||||
} from '../store/actions';
|
|
||||||
import FaceRestoreSettings from 'features/parameters/components/Parameters/FaceRestore/FaceRestoreSettings';
|
import FaceRestoreSettings from 'features/parameters/components/Parameters/FaceRestore/FaceRestoreSettings';
|
||||||
import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/UpscaleSettings';
|
import UpscaleSettings from 'features/parameters/components/Parameters/Upscale/UpscaleSettings';
|
||||||
import DeleteImageButton from './ImageActionButtons/DeleteImageButton';
|
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
||||||
|
import { DeleteImageButton } from './DeleteModal';
|
||||||
|
|
||||||
const currentImageButtonsSelector = createSelector(
|
const currentImageButtonsSelector = createSelector(
|
||||||
[
|
[
|
||||||
@ -122,10 +113,6 @@ const currentImageButtonsSelector = createSelector(
|
|||||||
|
|
||||||
type CurrentImageButtonsProps = FlexProps;
|
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 CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const {
|
const {
|
||||||
@ -137,13 +124,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
facetoolStrength,
|
facetoolStrength,
|
||||||
shouldDisableToolbarButtons,
|
shouldDisableToolbarButtons,
|
||||||
shouldShowImageDetails,
|
shouldShowImageDetails,
|
||||||
// currentImage,
|
|
||||||
isLightboxOpen,
|
isLightboxOpen,
|
||||||
activeTabName,
|
activeTabName,
|
||||||
shouldHidePreview,
|
shouldHidePreview,
|
||||||
image,
|
image,
|
||||||
canDeleteImage,
|
|
||||||
shouldConfirmOnDelete,
|
|
||||||
shouldShowProgressInViewer,
|
shouldShowProgressInViewer,
|
||||||
} = useAppSelector(currentImageButtonsSelector);
|
} = useAppSelector(currentImageButtonsSelector);
|
||||||
|
|
||||||
@ -152,18 +136,14 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
|
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
|
||||||
const isFaceRestoreEnabled = useFeatureStatus('faceRestore').isFeatureEnabled;
|
const isFaceRestoreEnabled = useFeatureStatus('faceRestore').isFeatureEnabled;
|
||||||
|
|
||||||
const {
|
|
||||||
isOpen: isDeleteDialogOpen,
|
|
||||||
onOpen: onDeleteDialogOpen,
|
|
||||||
onClose: onDeleteDialogClose,
|
|
||||||
} = useDisclosure();
|
|
||||||
|
|
||||||
const toaster = useAppToaster();
|
const toaster = useAppToaster();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||||
useRecallParameters();
|
useRecallParameters();
|
||||||
|
|
||||||
|
const { onDelete } = useContext(DeleteImageContext);
|
||||||
|
|
||||||
// const handleCopyImage = useCallback(async () => {
|
// const handleCopyImage = useCallback(async () => {
|
||||||
// if (!image?.url) {
|
// if (!image?.url) {
|
||||||
// return;
|
// return;
|
||||||
@ -262,6 +242,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
// selectedImage && dispatch(runESRGAN(selectedImage));
|
// selectedImage && dispatch(runESRGAN(selectedImage));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
onDelete(image);
|
||||||
|
}, [image, onDelete]);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'Shift+U',
|
'Shift+U',
|
||||||
() => {
|
() => {
|
||||||
@ -363,31 +347,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
[image, shouldShowImageDetails, toaster]
|
[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(() => {
|
const handleClickProgressImagesToggle = useCallback(() => {
|
||||||
dispatch(setShouldShowProgressInViewer(!shouldShowProgressInViewer));
|
dispatch(setShouldShowProgressInViewer(!shouldShowProgressInViewer));
|
||||||
}, [dispatch, shouldShowProgressInViewer]);
|
}, [dispatch, shouldShowProgressInViewer]);
|
||||||
|
|
||||||
useHotkeys('delete', handleInitiateDelete, [
|
|
||||||
image,
|
|
||||||
shouldConfirmOnDelete,
|
|
||||||
isConnected,
|
|
||||||
isProcessing,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleLightBox = useCallback(() => {
|
const handleLightBox = useCallback(() => {
|
||||||
dispatch(setIsLightboxOpen(!isLightboxOpen));
|
dispatch(setIsLightboxOpen(!isLightboxOpen));
|
||||||
}, [dispatch, isLightboxOpen]);
|
}, [dispatch, isLightboxOpen]);
|
||||||
@ -596,7 +559,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
<ButtonGroup isAttached={true}>
|
<ButtonGroup isAttached={true}>
|
||||||
<DeleteImageButton image={image} />
|
<DeleteImageButton onClick={handleDelete} />
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
|
@ -15,6 +15,7 @@ import { imageSelected } from '../store/gallerySlice';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import { useGetIsImageInUse } from 'common/hooks/useGetIsImageInUse';
|
||||||
|
|
||||||
export const imagesSelector = createSelector(
|
export const imagesSelector = createSelector(
|
||||||
[uiSelector, gallerySelector, systemSelector],
|
[uiSelector, gallerySelector, systemSelector],
|
||||||
@ -54,6 +55,8 @@ const CurrentImagePreview = () => {
|
|||||||
const toaster = useAppToaster();
|
const toaster = useAppToaster();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const isImageInUse = useGetIsImageInUse(image?.image_name);
|
||||||
|
console.log(isImageInUse);
|
||||||
const handleError = useCallback(() => {
|
const handleError = useCallback(() => {
|
||||||
dispatch(imageSelected());
|
dispatch(imageSelected());
|
||||||
if (shouldFetchImages) {
|
if (shouldFetchImages) {
|
||||||
|
@ -9,16 +9,19 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
import { configSelector } from 'features/system/store/configSelectors';
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
||||||
import { isEqual } from 'lodash-es';
|
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 { useTranslation } from 'react-i18next';
|
||||||
|
import { FaTrash } from 'react-icons/fa';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[systemSelector, configSelector],
|
[systemSelector, configSelector],
|
||||||
@ -34,22 +37,12 @@ const selector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
interface DeleteImageModalProps {
|
const DeleteImageModal = () => {
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
handleDelete: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DeleteImageModal = ({
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
handleDelete,
|
|
||||||
}: DeleteImageModalProps) => {
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin } =
|
const { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin } =
|
||||||
useAppSelector(selector);
|
useAppSelector(selector);
|
||||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
|
||||||
|
|
||||||
const handleChangeShouldConfirmOnDelete = useCallback(
|
const handleChangeShouldConfirmOnDelete = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
@ -57,10 +50,10 @@ const DeleteImageModal = ({
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClickDelete = useCallback(() => {
|
const { isOpen, onClose, onImmediatelyDelete } =
|
||||||
handleDelete();
|
useContext(DeleteImageContext);
|
||||||
onClose();
|
|
||||||
}, [handleDelete, onClose]);
|
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
@ -96,7 +89,7 @@ const DeleteImageModal = ({
|
|||||||
<IAIButton ref={cancelRef} onClick={onClose}>
|
<IAIButton ref={cancelRef} onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton colorScheme="error" onClick={handleClickDelete} ml={3}>
|
<IAIButton colorScheme="error" onClick={onImmediatelyDelete} ml={3}>
|
||||||
Delete
|
Delete
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
@ -107,3 +100,33 @@ const DeleteImageModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(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 (
|
||||||
|
<IAIIconButton
|
||||||
|
onClick={onClick}
|
||||||
|
icon={<FaTrash />}
|
||||||
|
tooltip={`${t('gallery.deleteImage')} (Del)`}
|
||||||
|
aria-label={`${t('gallery.deleteImage')} (Del)`}
|
||||||
|
isDisabled={!canDeleteImage}
|
||||||
|
colorScheme="error"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -1,17 +1,8 @@
|
|||||||
import {
|
import { Box, Flex, Icon, Image, MenuItem, MenuList } from '@chakra-ui/react';
|
||||||
Box,
|
|
||||||
Flex,
|
|
||||||
Icon,
|
|
||||||
Image,
|
|
||||||
MenuItem,
|
|
||||||
MenuList,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
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 { FaCheck, FaExpand, FaImage, FaShare, FaTrash } from 'react-icons/fa';
|
||||||
import DeleteImageModal from './DeleteImageModal';
|
|
||||||
import { ContextMenu } from 'chakra-ui-contextmenu';
|
import { ContextMenu } from 'chakra-ui-contextmenu';
|
||||||
import {
|
import {
|
||||||
resizeAndScaleCanvas,
|
resizeAndScaleCanvas,
|
||||||
@ -31,14 +22,11 @@ import { isEqual } from 'lodash-es';
|
|||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||||
import {
|
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
||||||
requestedImageDeletion,
|
|
||||||
sentImageToCanvas,
|
|
||||||
sentImageToImg2Img,
|
|
||||||
} from '../store/actions';
|
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
import { useDraggable } from '@dnd-kit/core';
|
import { useDraggable } from '@dnd-kit/core';
|
||||||
|
import { DeleteImageContext } from 'app/contexts/DeleteImageContext';
|
||||||
|
|
||||||
export const selector = createSelector(
|
export const selector = createSelector(
|
||||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||||
@ -92,27 +80,22 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
galleryImageMinimumWidth,
|
galleryImageMinimumWidth,
|
||||||
canDeleteImage,
|
canDeleteImage,
|
||||||
shouldUseSingleGalleryColumn,
|
shouldUseSingleGalleryColumn,
|
||||||
shouldConfirmOnDelete,
|
|
||||||
} = useAppSelector(selector);
|
} = useAppSelector(selector);
|
||||||
|
|
||||||
const {
|
|
||||||
isOpen: isDeleteDialogOpen,
|
|
||||||
onOpen: onDeleteDialogOpen,
|
|
||||||
onClose: onDeleteDialogClose,
|
|
||||||
} = useDisclosure();
|
|
||||||
|
|
||||||
const { image, isSelected } = props;
|
const { image, isSelected } = props;
|
||||||
const { image_url, thumbnail_url, image_name } = image;
|
const { image_url, thumbnail_url, image_name } = image;
|
||||||
|
|
||||||
const [isHovered, setIsHovered] = useState<boolean>(false);
|
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||||
|
|
||||||
const toaster = useAppToaster();
|
const toaster = useAppToaster();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||||
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
|
||||||
|
|
||||||
|
const { onDelete } = useContext(DeleteImageContext);
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
onDelete(image);
|
||||||
|
}, [image, onDelete]);
|
||||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||||
useRecallParameters();
|
useRecallParameters();
|
||||||
|
|
||||||
@ -126,26 +109,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
const handleMouseOver = () => setIsHovered(true);
|
const handleMouseOver = () => setIsHovered(true);
|
||||||
const handleMouseOut = () => setIsHovered(false);
|
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(() => {
|
const handleSelectImage = useCallback(() => {
|
||||||
dispatch(imageSelected(image));
|
dispatch(imageSelected(image));
|
||||||
}, [image, dispatch]);
|
}, [image, dispatch]);
|
||||||
@ -281,7 +244,11 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
{t('parameters.sendToUnifiedCanvas')}
|
{t('parameters.sendToUnifiedCanvas')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem icon={<FaTrash />} onClickCapture={onDeleteDialogOpen}>
|
<MenuItem
|
||||||
|
sx={{ color: 'error.300' }}
|
||||||
|
icon={<FaTrash />}
|
||||||
|
onClickCapture={handleDelete}
|
||||||
|
>
|
||||||
{t('gallery.deleteImage')}
|
{t('gallery.deleteImage')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
@ -357,7 +324,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
onClickCapture={handleInitiateDelete}
|
onClickCapture={handleDelete}
|
||||||
aria-label={t('gallery.deleteImage')}
|
aria-label={t('gallery.deleteImage')}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
size="xs"
|
size="xs"
|
||||||
@ -369,11 +336,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
<DeleteImageModal
|
|
||||||
isOpen={isDeleteDialogOpen}
|
|
||||||
onClose={onDeleteDialogClose}
|
|
||||||
handleDelete={handleDelete}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}, memoEqualityCheck);
|
}, memoEqualityCheck);
|
||||||
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<IAIIconButton
|
|
||||||
onClick={handleInitiateDelete}
|
|
||||||
icon={<FaTrash />}
|
|
||||||
tooltip={`${t('gallery.deleteImage')} (Del)`}
|
|
||||||
aria-label={`${t('gallery.deleteImage')} (Del)`}
|
|
||||||
isDisabled={!image || !isConnected}
|
|
||||||
colorScheme="error"
|
|
||||||
/>
|
|
||||||
{image && (
|
|
||||||
<DeleteImageModal
|
|
||||||
isOpen={isDeleteDialogOpen}
|
|
||||||
onClose={onDeleteDialogClose}
|
|
||||||
handleDelete={handleDelete}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(DeleteImageButton);
|
|
@ -14,6 +14,7 @@ import { useAppToaster } from 'app/components/Toaster';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
import { IAIImageFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import { useGetIsImageInUse } from 'common/hooks/useGetIsImageInUse';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[generationSelector],
|
[generationSelector],
|
||||||
|
Loading…
Reference in New Issue
Block a user