mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): refactor DeleteImageModal
- refactor the component - use translations - add config for systems where deleted images are not sent to bin (only changes the messaging)
This commit is contained in:
parent
0cc739afc8
commit
99392debe8
@ -63,7 +63,7 @@
|
|||||||
"postProcessDesc3": "The Invoke AI Command Line Interface offers various other features including Embiggen.",
|
"postProcessDesc3": "The Invoke AI Command Line Interface offers various other features including Embiggen.",
|
||||||
"training": "Training",
|
"training": "Training",
|
||||||
"trainingDesc1": "A dedicated workflow for training your own embeddings and checkpoints using Textual Inversion and Dreambooth from the web interface.",
|
"trainingDesc1": "A dedicated workflow for training your own embeddings and checkpoints using Textual Inversion and Dreambooth from the web interface.",
|
||||||
"trainingDesc2": "InvokeAI already supports training custom embeddings using Textual Inversion using the main script.",
|
"trainingDesc2": "InvokeAI already supports training custom embeddourings using Textual Inversion using the main script.",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
@ -100,7 +100,9 @@
|
|||||||
"loadingInvokeAI": "Loading Invoke AI",
|
"loadingInvokeAI": "Loading Invoke AI",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
"generate": "Generate",
|
"generate": "Generate",
|
||||||
"openInNewTab": "Open in New Tab"
|
"openInNewTab": "Open in New Tab",
|
||||||
|
"dontAskMeAgain": "Don't ask me again",
|
||||||
|
"areYouSure": "Are you sure?"
|
||||||
},
|
},
|
||||||
"gallery": {
|
"gallery": {
|
||||||
"generations": "Generations",
|
"generations": "Generations",
|
||||||
@ -116,7 +118,10 @@
|
|||||||
"pinGallery": "Pin Gallery",
|
"pinGallery": "Pin Gallery",
|
||||||
"allImagesLoaded": "All Images Loaded",
|
"allImagesLoaded": "All Images Loaded",
|
||||||
"loadMore": "Load More",
|
"loadMore": "Load More",
|
||||||
"noImagesInGallery": "No Images In Gallery"
|
"noImagesInGallery": "No Images In Gallery",
|
||||||
|
"deleteImage": "Delete Image",
|
||||||
|
"deleteImageBin": "Deleted images will be sent to your operating system's Bin.",
|
||||||
|
"deleteImagePermanent": "Deleted images cannot be restored."
|
||||||
},
|
},
|
||||||
"hotkeys": {
|
"hotkeys": {
|
||||||
"keyboardShortcuts": "Keyboard Shortcuts",
|
"keyboardShortcuts": "Keyboard Shortcuts",
|
||||||
@ -508,7 +513,6 @@
|
|||||||
"useAll": "Use All",
|
"useAll": "Use All",
|
||||||
"useInitImg": "Use Initial Image",
|
"useInitImg": "Use Initial Image",
|
||||||
"info": "Info",
|
"info": "Info",
|
||||||
"deleteImage": "Delete Image",
|
|
||||||
"initialImage": "Initial Image",
|
"initialImage": "Initial Image",
|
||||||
"showOptionsPanel": "Show Options Panel",
|
"showOptionsPanel": "Show Options Panel",
|
||||||
"hidePreview": "Hide Preview",
|
"hidePreview": "Hide Preview",
|
||||||
|
1
invokeai/frontend/web/src/app/invokeai.d.ts
vendored
1
invokeai/frontend/web/src/app/invokeai.d.ts
vendored
@ -375,6 +375,7 @@ export declare type AppConfig = {
|
|||||||
shouldFetchImages: boolean;
|
shouldFetchImages: boolean;
|
||||||
disabledTabs: InvokeTabName[];
|
disabledTabs: InvokeTabName[];
|
||||||
disabledFeatures: AppFeature[];
|
disabledFeatures: AppFeature[];
|
||||||
|
canRestoreDeletedImagesFromBin: boolean;
|
||||||
sd: {
|
sd: {
|
||||||
iterations: {
|
iterations: {
|
||||||
initial: number;
|
initial: number;
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
FlexProps,
|
FlexProps,
|
||||||
FormControl,
|
FormControl,
|
||||||
Link,
|
Link,
|
||||||
|
useDisclosure,
|
||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { runESRGAN, runFacetool } from 'app/socketio/actions';
|
import { runESRGAN, runFacetool } from 'app/socketio/actions';
|
||||||
@ -66,6 +67,7 @@ import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
|
|||||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
import { useGetUrl } from 'common/util/getUrl';
|
import { useGetUrl } from 'common/util/getUrl';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
import { imageDeleted } from 'services/thunks/image';
|
||||||
|
|
||||||
const currentImageButtonsSelector = createSelector(
|
const currentImageButtonsSelector = createSelector(
|
||||||
[
|
[
|
||||||
@ -77,17 +79,14 @@ const currentImageButtonsSelector = createSelector(
|
|||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
selectedImageSelector,
|
selectedImageSelector,
|
||||||
],
|
],
|
||||||
(
|
(system, gallery, postprocessing, ui, lightbox, activeTabName, image) => {
|
||||||
system,
|
const {
|
||||||
gallery,
|
isProcessing,
|
||||||
postprocessing,
|
isConnected,
|
||||||
ui,
|
isGFPGANAvailable,
|
||||||
lightbox,
|
isESRGANAvailable,
|
||||||
activeTabName,
|
shouldConfirmOnDelete,
|
||||||
selectedImage
|
} = system;
|
||||||
) => {
|
|
||||||
const { isProcessing, isConnected, isGFPGANAvailable, isESRGANAvailable } =
|
|
||||||
system;
|
|
||||||
|
|
||||||
const { upscalingLevel, facetoolStrength } = postprocessing;
|
const { upscalingLevel, facetoolStrength } = postprocessing;
|
||||||
|
|
||||||
@ -98,6 +97,8 @@ const currentImageButtonsSelector = createSelector(
|
|||||||
const { intermediateImage, currentImage } = gallery;
|
const { intermediateImage, currentImage } = gallery;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
canDeleteImage: isConnected && !isProcessing,
|
||||||
|
shouldConfirmOnDelete,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
isConnected,
|
isConnected,
|
||||||
isGFPGANAvailable,
|
isGFPGANAvailable,
|
||||||
@ -110,7 +111,7 @@ const currentImageButtonsSelector = createSelector(
|
|||||||
activeTabName,
|
activeTabName,
|
||||||
isLightboxOpen,
|
isLightboxOpen,
|
||||||
shouldHidePreview,
|
shouldHidePreview,
|
||||||
selectedImage,
|
image,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -141,7 +142,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
isLightboxOpen,
|
isLightboxOpen,
|
||||||
activeTabName,
|
activeTabName,
|
||||||
shouldHidePreview,
|
shouldHidePreview,
|
||||||
selectedImage,
|
image,
|
||||||
|
canDeleteImage,
|
||||||
|
shouldConfirmOnDelete,
|
||||||
} = useAppSelector(currentImageButtonsSelector);
|
} = useAppSelector(currentImageButtonsSelector);
|
||||||
|
|
||||||
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||||
@ -150,25 +153,31 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
|
|
||||||
const { getUrl, shouldTransformUrls } = useGetUrl();
|
const { getUrl, shouldTransformUrls } = useGetUrl();
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOpen: isDeleteDialogOpen,
|
||||||
|
onOpen: onDeleteDialogOpen,
|
||||||
|
onClose: onDeleteDialogClose,
|
||||||
|
} = useDisclosure();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const setBothPrompts = useSetBothPrompts();
|
const setBothPrompts = useSetBothPrompts();
|
||||||
|
|
||||||
const handleClickUseAsInitialImage = () => {
|
const handleClickUseAsInitialImage = () => {
|
||||||
if (!selectedImage) return;
|
if (!image) return;
|
||||||
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
|
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
|
||||||
dispatch(initialImageSelected(selectedImage.name));
|
dispatch(initialImageSelected(image.name));
|
||||||
// dispatch(setInitialImage(currentImage));
|
// dispatch(setInitialImage(currentImage));
|
||||||
|
|
||||||
// dispatch(setActiveTab('img2img'));
|
// dispatch(setActiveTab('img2img'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyImage = async () => {
|
const handleCopyImage = async () => {
|
||||||
if (!selectedImage?.url) {
|
if (!image?.url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = getUrl(selectedImage.url);
|
const url = getUrl(image.url);
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return;
|
return;
|
||||||
@ -188,10 +197,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyImageLink = () => {
|
const handleCopyImageLink = () => {
|
||||||
const url = selectedImage
|
const url = image
|
||||||
? shouldTransformUrls
|
? shouldTransformUrls
|
||||||
? getUrl(selectedImage.url)
|
? getUrl(image.url)
|
||||||
: window.location.toString() + selectedImage.url
|
: window.location.toString() + image.url
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
@ -211,7 +220,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'shift+i',
|
'shift+i',
|
||||||
() => {
|
() => {
|
||||||
if (selectedImage) {
|
if (image) {
|
||||||
handleClickUseAsInitialImage();
|
handleClickUseAsInitialImage();
|
||||||
toast({
|
toast({
|
||||||
title: t('toast.sentToImageToImage'),
|
title: t('toast.sentToImageToImage'),
|
||||||
@ -229,7 +238,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedImage]
|
[image]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePreviewVisibility = () => {
|
const handlePreviewVisibility = () => {
|
||||||
@ -237,7 +246,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClickUseAllParameters = () => {
|
const handleClickUseAllParameters = () => {
|
||||||
if (!selectedImage) return;
|
if (!image) return;
|
||||||
// selectedImage.metadata &&
|
// selectedImage.metadata &&
|
||||||
// dispatch(setAllParameters(selectedImage.metadata));
|
// dispatch(setAllParameters(selectedImage.metadata));
|
||||||
// if (selectedImage.metadata?.image.type === 'img2img') {
|
// if (selectedImage.metadata?.image.type === 'img2img') {
|
||||||
@ -250,11 +259,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'a',
|
'a',
|
||||||
() => {
|
() => {
|
||||||
if (
|
if (['txt2img', 'img2img'].includes(image?.metadata?.sd_metadata?.type)) {
|
||||||
['txt2img', 'img2img'].includes(
|
|
||||||
selectedImage?.metadata?.sd_metadata?.type
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
handleClickUseAllParameters();
|
handleClickUseAllParameters();
|
||||||
toast({
|
toast({
|
||||||
title: t('toast.parametersSet'),
|
title: t('toast.parametersSet'),
|
||||||
@ -272,18 +277,17 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedImage]
|
[image]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClickUseSeed = () => {
|
const handleClickUseSeed = () => {
|
||||||
selectedImage?.metadata &&
|
image?.metadata && dispatch(setSeed(image.metadata.sd_metadata.seed));
|
||||||
dispatch(setSeed(selectedImage.metadata.sd_metadata.seed));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
's',
|
's',
|
||||||
() => {
|
() => {
|
||||||
if (selectedImage?.metadata?.sd_metadata?.seed) {
|
if (image?.metadata?.sd_metadata?.seed) {
|
||||||
handleClickUseSeed();
|
handleClickUseSeed();
|
||||||
toast({
|
toast({
|
||||||
title: t('toast.seedSet'),
|
title: t('toast.seedSet'),
|
||||||
@ -301,19 +305,19 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedImage]
|
[image]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClickUsePrompt = useCallback(() => {
|
const handleClickUsePrompt = useCallback(() => {
|
||||||
if (selectedImage?.metadata?.sd_metadata?.prompt) {
|
if (image?.metadata?.sd_metadata?.prompt) {
|
||||||
setBothPrompts(selectedImage?.metadata?.sd_metadata?.prompt);
|
setBothPrompts(image?.metadata?.sd_metadata?.prompt);
|
||||||
}
|
}
|
||||||
}, [selectedImage?.metadata?.sd_metadata?.prompt, setBothPrompts]);
|
}, [image?.metadata?.sd_metadata?.prompt, setBothPrompts]);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'p',
|
'p',
|
||||||
() => {
|
() => {
|
||||||
if (selectedImage?.metadata?.sd_metadata?.prompt) {
|
if (image?.metadata?.sd_metadata?.prompt) {
|
||||||
handleClickUsePrompt();
|
handleClickUsePrompt();
|
||||||
toast({
|
toast({
|
||||||
title: t('toast.promptSet'),
|
title: t('toast.promptSet'),
|
||||||
@ -331,7 +335,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedImage]
|
[image]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClickUpscale = () => {
|
const handleClickUpscale = () => {
|
||||||
@ -356,7 +360,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
isUpscalingEnabled,
|
isUpscalingEnabled,
|
||||||
selectedImage,
|
image,
|
||||||
isESRGANAvailable,
|
isESRGANAvailable,
|
||||||
shouldDisableToolbarButtons,
|
shouldDisableToolbarButtons,
|
||||||
isConnected,
|
isConnected,
|
||||||
@ -388,7 +392,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
|
|
||||||
[
|
[
|
||||||
isFaceRestoreEnabled,
|
isFaceRestoreEnabled,
|
||||||
selectedImage,
|
image,
|
||||||
isGFPGANAvailable,
|
isGFPGANAvailable,
|
||||||
shouldDisableToolbarButtons,
|
shouldDisableToolbarButtons,
|
||||||
isConnected,
|
isConnected,
|
||||||
@ -401,7 +405,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
dispatch(setShouldShowImageDetails(!shouldShowImageDetails));
|
dispatch(setShouldShowImageDetails(!shouldShowImageDetails));
|
||||||
|
|
||||||
const handleSendToCanvas = () => {
|
const handleSendToCanvas = () => {
|
||||||
if (!selectedImage) return;
|
if (!image) return;
|
||||||
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
|
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
|
||||||
|
|
||||||
// dispatch(setInitialCanvasImage(selectedImage));
|
// dispatch(setInitialCanvasImage(selectedImage));
|
||||||
@ -422,7 +426,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'i',
|
'i',
|
||||||
() => {
|
() => {
|
||||||
if (selectedImage) {
|
if (image) {
|
||||||
handleClickShowImageDetails();
|
handleClickShowImageDetails();
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
@ -433,226 +437,255 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedImage, shouldShowImageDetails]
|
[image, shouldShowImageDetails]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleInitiateDelete = () => {
|
||||||
|
if (shouldConfirmOnDelete) {
|
||||||
|
onDeleteDialogOpen();
|
||||||
|
} else {
|
||||||
|
handleDelete();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (canDeleteImage && image) {
|
||||||
|
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useHotkeys('delete', handleInitiateDelete, [
|
||||||
|
image,
|
||||||
|
shouldConfirmOnDelete,
|
||||||
|
isConnected,
|
||||||
|
isProcessing,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleLightBox = () => {
|
const handleLightBox = () => {
|
||||||
dispatch(setIsLightboxOpen(!isLightboxOpen));
|
dispatch(setIsLightboxOpen(!isLightboxOpen));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<>
|
||||||
sx={{
|
<Flex
|
||||||
flexWrap: 'wrap',
|
sx={{
|
||||||
justifyContent: 'center',
|
flexWrap: 'wrap',
|
||||||
alignItems: 'center',
|
justifyContent: 'center',
|
||||||
gap: 2,
|
alignItems: 'center',
|
||||||
}}
|
gap: 2,
|
||||||
{...props}
|
}}
|
||||||
>
|
{...props}
|
||||||
<ButtonGroup isAttached={true}>
|
>
|
||||||
<IAIPopover
|
<ButtonGroup isAttached={true}>
|
||||||
triggerComponent={
|
<IAIPopover
|
||||||
<IAIIconButton
|
triggerComponent={
|
||||||
isDisabled={!selectedImage}
|
<IAIIconButton
|
||||||
aria-label={`${t('parameters.sendTo')}...`}
|
isDisabled={!image}
|
||||||
icon={<FaShareAlt />}
|
aria-label={`${t('parameters.sendTo')}...`}
|
||||||
/>
|
icon={<FaShareAlt />}
|
||||||
}
|
/>
|
||||||
>
|
}
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
flexDirection: 'column',
|
|
||||||
rowGap: 2,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<IAIButton
|
<Flex
|
||||||
size="sm"
|
sx={{
|
||||||
onClick={handleClickUseAsInitialImage}
|
flexDirection: 'column',
|
||||||
leftIcon={<FaShare />}
|
rowGap: 2,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t('parameters.sendToImg2Img')}
|
<IAIButton
|
||||||
</IAIButton>
|
size="sm"
|
||||||
<IAIButton
|
onClick={handleClickUseAsInitialImage}
|
||||||
size="sm"
|
leftIcon={<FaShare />}
|
||||||
onClick={handleSendToCanvas}
|
>
|
||||||
leftIcon={<FaShare />}
|
{t('parameters.sendToImg2Img')}
|
||||||
>
|
|
||||||
{t('parameters.sendToUnifiedCanvas')}
|
|
||||||
</IAIButton>
|
|
||||||
|
|
||||||
<IAIButton
|
|
||||||
size="sm"
|
|
||||||
onClick={handleCopyImage}
|
|
||||||
leftIcon={<FaCopy />}
|
|
||||||
>
|
|
||||||
{t('parameters.copyImage')}
|
|
||||||
</IAIButton>
|
|
||||||
<IAIButton
|
|
||||||
size="sm"
|
|
||||||
onClick={handleCopyImageLink}
|
|
||||||
leftIcon={<FaCopy />}
|
|
||||||
>
|
|
||||||
{t('parameters.copyImageToLink')}
|
|
||||||
</IAIButton>
|
|
||||||
|
|
||||||
<Link download={true} href={getUrl(selectedImage?.url ?? '')}>
|
|
||||||
<IAIButton leftIcon={<FaDownload />} size="sm" w="100%">
|
|
||||||
{t('parameters.downloadImage')}
|
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
</Link>
|
<IAIButton
|
||||||
</Flex>
|
size="sm"
|
||||||
</IAIPopover>
|
onClick={handleSendToCanvas}
|
||||||
<IAIIconButton
|
leftIcon={<FaShare />}
|
||||||
icon={shouldHidePreview ? <FaEyeSlash /> : <FaEye />}
|
>
|
||||||
tooltip={
|
{t('parameters.sendToUnifiedCanvas')}
|
||||||
!shouldHidePreview
|
</IAIButton>
|
||||||
? t('parameters.hidePreview')
|
|
||||||
: t('parameters.showPreview')
|
<IAIButton
|
||||||
}
|
size="sm"
|
||||||
aria-label={
|
onClick={handleCopyImage}
|
||||||
!shouldHidePreview
|
leftIcon={<FaCopy />}
|
||||||
? t('parameters.hidePreview')
|
>
|
||||||
: t('parameters.showPreview')
|
{t('parameters.copyImage')}
|
||||||
}
|
</IAIButton>
|
||||||
isChecked={shouldHidePreview}
|
<IAIButton
|
||||||
onClick={handlePreviewVisibility}
|
size="sm"
|
||||||
/>
|
onClick={handleCopyImageLink}
|
||||||
{isLightboxEnabled && (
|
leftIcon={<FaCopy />}
|
||||||
|
>
|
||||||
|
{t('parameters.copyImageToLink')}
|
||||||
|
</IAIButton>
|
||||||
|
|
||||||
|
<Link download={true} href={getUrl(image?.url ?? '')}>
|
||||||
|
<IAIButton leftIcon={<FaDownload />} size="sm" w="100%">
|
||||||
|
{t('parameters.downloadImage')}
|
||||||
|
</IAIButton>
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
|
</IAIPopover>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<FaExpand />}
|
icon={shouldHidePreview ? <FaEyeSlash /> : <FaEye />}
|
||||||
tooltip={
|
tooltip={
|
||||||
!isLightboxOpen
|
!shouldHidePreview
|
||||||
? `${t('parameters.openInViewer')} (Z)`
|
? t('parameters.hidePreview')
|
||||||
: `${t('parameters.closeViewer')} (Z)`
|
: t('parameters.showPreview')
|
||||||
}
|
}
|
||||||
aria-label={
|
aria-label={
|
||||||
!isLightboxOpen
|
!shouldHidePreview
|
||||||
? `${t('parameters.openInViewer')} (Z)`
|
? t('parameters.hidePreview')
|
||||||
: `${t('parameters.closeViewer')} (Z)`
|
: t('parameters.showPreview')
|
||||||
}
|
}
|
||||||
isChecked={isLightboxOpen}
|
isChecked={shouldHidePreview}
|
||||||
onClick={handleLightBox}
|
onClick={handlePreviewVisibility}
|
||||||
/>
|
/>
|
||||||
)}
|
{isLightboxEnabled && (
|
||||||
</ButtonGroup>
|
<IAIIconButton
|
||||||
|
icon={<FaExpand />}
|
||||||
<ButtonGroup isAttached={true}>
|
tooltip={
|
||||||
<IAIIconButton
|
!isLightboxOpen
|
||||||
icon={<FaQuoteRight />}
|
? `${t('parameters.openInViewer')} (Z)`
|
||||||
tooltip={`${t('parameters.usePrompt')} (P)`}
|
: `${t('parameters.closeViewer')} (Z)`
|
||||||
aria-label={`${t('parameters.usePrompt')} (P)`}
|
|
||||||
isDisabled={!selectedImage?.metadata?.sd_metadata?.prompt}
|
|
||||||
onClick={handleClickUsePrompt}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IAIIconButton
|
|
||||||
icon={<FaSeedling />}
|
|
||||||
tooltip={`${t('parameters.useSeed')} (S)`}
|
|
||||||
aria-label={`${t('parameters.useSeed')} (S)`}
|
|
||||||
isDisabled={!selectedImage?.metadata?.sd_metadata?.seed}
|
|
||||||
onClick={handleClickUseSeed}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IAIIconButton
|
|
||||||
icon={<FaAsterisk />}
|
|
||||||
tooltip={`${t('parameters.useAll')} (A)`}
|
|
||||||
aria-label={`${t('parameters.useAll')} (A)`}
|
|
||||||
isDisabled={
|
|
||||||
!['txt2img', 'img2img'].includes(
|
|
||||||
selectedImage?.metadata?.sd_metadata?.type
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={handleClickUseAllParameters}
|
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
{(isUpscalingEnabled || isFaceRestoreEnabled) && (
|
|
||||||
<ButtonGroup isAttached={true}>
|
|
||||||
{isFaceRestoreEnabled && (
|
|
||||||
<IAIPopover
|
|
||||||
triggerComponent={
|
|
||||||
<IAIIconButton
|
|
||||||
icon={<FaGrinStars />}
|
|
||||||
aria-label={t('parameters.restoreFaces')}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
>
|
aria-label={
|
||||||
<Flex
|
!isLightboxOpen
|
||||||
sx={{
|
? `${t('parameters.openInViewer')} (Z)`
|
||||||
flexDirection: 'column',
|
: `${t('parameters.closeViewer')} (Z)`
|
||||||
rowGap: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FaceRestoreSettings />
|
|
||||||
<IAIButton
|
|
||||||
isDisabled={
|
|
||||||
!isGFPGANAvailable ||
|
|
||||||
!selectedImage ||
|
|
||||||
!(isConnected && !isProcessing) ||
|
|
||||||
!facetoolStrength
|
|
||||||
}
|
|
||||||
onClick={handleClickFixFaces}
|
|
||||||
>
|
|
||||||
{t('parameters.restoreFaces')}
|
|
||||||
</IAIButton>
|
|
||||||
</Flex>
|
|
||||||
</IAIPopover>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isUpscalingEnabled && (
|
|
||||||
<IAIPopover
|
|
||||||
triggerComponent={
|
|
||||||
<IAIIconButton
|
|
||||||
icon={<FaExpandArrowsAlt />}
|
|
||||||
aria-label={t('parameters.upscale')}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
>
|
isChecked={isLightboxOpen}
|
||||||
<Flex
|
onClick={handleLightBox}
|
||||||
sx={{
|
/>
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<UpscaleSettings />
|
|
||||||
<IAIButton
|
|
||||||
isDisabled={
|
|
||||||
!isESRGANAvailable ||
|
|
||||||
!selectedImage ||
|
|
||||||
!(isConnected && !isProcessing) ||
|
|
||||||
!upscalingLevel
|
|
||||||
}
|
|
||||||
onClick={handleClickUpscale}
|
|
||||||
>
|
|
||||||
{t('parameters.upscaleImage')}
|
|
||||||
</IAIButton>
|
|
||||||
</Flex>
|
|
||||||
</IAIPopover>
|
|
||||||
)}
|
)}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
)}
|
|
||||||
|
|
||||||
<ButtonGroup isAttached={true}>
|
<ButtonGroup isAttached={true}>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<FaCode />}
|
icon={<FaQuoteRight />}
|
||||||
tooltip={`${t('parameters.info')} (I)`}
|
tooltip={`${t('parameters.usePrompt')} (P)`}
|
||||||
aria-label={`${t('parameters.info')} (I)`}
|
aria-label={`${t('parameters.usePrompt')} (P)`}
|
||||||
isChecked={shouldShowImageDetails}
|
isDisabled={!image?.metadata?.sd_metadata?.prompt}
|
||||||
onClick={handleClickShowImageDetails}
|
onClick={handleClickUsePrompt}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
|
||||||
|
<IAIIconButton
|
||||||
|
icon={<FaSeedling />}
|
||||||
|
tooltip={`${t('parameters.useSeed')} (S)`}
|
||||||
|
aria-label={`${t('parameters.useSeed')} (S)`}
|
||||||
|
isDisabled={!image?.metadata?.sd_metadata?.seed}
|
||||||
|
onClick={handleClickUseSeed}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IAIIconButton
|
||||||
|
icon={<FaAsterisk />}
|
||||||
|
tooltip={`${t('parameters.useAll')} (A)`}
|
||||||
|
aria-label={`${t('parameters.useAll')} (A)`}
|
||||||
|
isDisabled={
|
||||||
|
!['txt2img', 'img2img'].includes(
|
||||||
|
image?.metadata?.sd_metadata?.type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={handleClickUseAllParameters}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
{(isUpscalingEnabled || isFaceRestoreEnabled) && (
|
||||||
|
<ButtonGroup isAttached={true}>
|
||||||
|
{isFaceRestoreEnabled && (
|
||||||
|
<IAIPopover
|
||||||
|
triggerComponent={
|
||||||
|
<IAIIconButton
|
||||||
|
icon={<FaGrinStars />}
|
||||||
|
aria-label={t('parameters.restoreFaces')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flexDirection: 'column',
|
||||||
|
rowGap: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FaceRestoreSettings />
|
||||||
|
<IAIButton
|
||||||
|
isDisabled={
|
||||||
|
!isGFPGANAvailable ||
|
||||||
|
!image ||
|
||||||
|
!(isConnected && !isProcessing) ||
|
||||||
|
!facetoolStrength
|
||||||
|
}
|
||||||
|
onClick={handleClickFixFaces}
|
||||||
|
>
|
||||||
|
{t('parameters.restoreFaces')}
|
||||||
|
</IAIButton>
|
||||||
|
</Flex>
|
||||||
|
</IAIPopover>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isUpscalingEnabled && (
|
||||||
|
<IAIPopover
|
||||||
|
triggerComponent={
|
||||||
|
<IAIIconButton
|
||||||
|
icon={<FaExpandArrowsAlt />}
|
||||||
|
aria-label={t('parameters.upscale')}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UpscaleSettings />
|
||||||
|
<IAIButton
|
||||||
|
isDisabled={
|
||||||
|
!isESRGANAvailable ||
|
||||||
|
!image ||
|
||||||
|
!(isConnected && !isProcessing) ||
|
||||||
|
!upscalingLevel
|
||||||
|
}
|
||||||
|
onClick={handleClickUpscale}
|
||||||
|
>
|
||||||
|
{t('parameters.upscaleImage')}
|
||||||
|
</IAIButton>
|
||||||
|
</Flex>
|
||||||
|
</IAIPopover>
|
||||||
|
)}
|
||||||
|
</ButtonGroup>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ButtonGroup isAttached={true}>
|
||||||
|
<IAIIconButton
|
||||||
|
icon={<FaCode />}
|
||||||
|
tooltip={`${t('parameters.info')} (I)`}
|
||||||
|
aria-label={`${t('parameters.info')} (I)`}
|
||||||
|
isChecked={shouldShowImageDetails}
|
||||||
|
onClick={handleClickShowImageDetails}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
<DeleteImageModal image={selectedImage}>
|
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
|
onClick={handleInitiateDelete}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
tooltip={`${t('parameters.deleteImage')} (Del)`}
|
tooltip={`${t('gallery.deleteImage')} (Del)`}
|
||||||
aria-label={`${t('parameters.deleteImage')} (Del)`}
|
aria-label={`${t('gallery.deleteImage')} (Del)`}
|
||||||
isDisabled={!selectedImage || !isConnected}
|
isDisabled={!image || !isConnected}
|
||||||
colorScheme="error"
|
colorScheme="error"
|
||||||
/>
|
/>
|
||||||
</DeleteImageModal>
|
</Flex>
|
||||||
</Flex>
|
{image && (
|
||||||
|
<DeleteImageModal
|
||||||
|
isOpen={isDeleteDialogOpen}
|
||||||
|
onClose={onDeleteDialogClose}
|
||||||
|
handleDelete={handleDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,39 +5,27 @@ import {
|
|||||||
AlertDialogFooter,
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogOverlay,
|
AlertDialogOverlay,
|
||||||
forwardRef,
|
|
||||||
Flex,
|
Flex,
|
||||||
Text,
|
Text,
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import * as InvokeAI from 'app/invokeai';
|
|
||||||
import { deleteImage } from 'app/socketio/actions';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
import {
|
import { setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
|
||||||
setShouldConfirmOnDelete,
|
|
||||||
SystemState,
|
|
||||||
} from 'features/system/store/systemSlice';
|
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import {
|
import { ChangeEvent, memo, useCallback, useRef } from 'react';
|
||||||
ChangeEvent,
|
import { useTranslation } from 'react-i18next';
|
||||||
cloneElement,
|
|
||||||
ReactElement,
|
|
||||||
SyntheticEvent,
|
|
||||||
useRef,
|
|
||||||
} from 'react';
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
import { imageDeleted } from 'services/thunks/image';
|
|
||||||
|
|
||||||
const deleteImageModalSelector = createSelector(
|
const selector = createSelector(
|
||||||
systemSelector,
|
[systemSelector, configSelector],
|
||||||
(system: SystemState) => {
|
(system, config) => {
|
||||||
const { shouldConfirmOnDelete, isConnected, isProcessing } = system;
|
const { shouldConfirmOnDelete } = system;
|
||||||
return { shouldConfirmOnDelete, isConnected, isProcessing };
|
const { canRestoreDeletedImagesFromBin } = config;
|
||||||
|
return { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin };
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
memoizeOptions: {
|
memoizeOptions: {
|
||||||
@ -45,106 +33,77 @@ const deleteImageModalSelector = createSelector(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
interface DeleteImageModalProps {
|
interface DeleteImageModalProps {
|
||||||
/**
|
isOpen: boolean;
|
||||||
* Component which, on click, should delete the image/open the modal.
|
onClose: () => void;
|
||||||
*/
|
handleDelete: () => void;
|
||||||
children: ReactElement;
|
|
||||||
/**
|
|
||||||
* The image to delete.
|
|
||||||
*/
|
|
||||||
image?: InvokeAI.Image;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const DeleteImageModal = ({
|
||||||
* Needs a child, which will act as the button to delete an image.
|
isOpen,
|
||||||
* If system.shouldConfirmOnDelete is true, a confirmation modal is displayed.
|
onClose,
|
||||||
* If it is false, the image is deleted immediately.
|
handleDelete,
|
||||||
* The confirmation modal has a "Don't ask me again" switch to set the boolean.
|
}: DeleteImageModalProps) => {
|
||||||
*/
|
const dispatch = useAppDispatch();
|
||||||
const DeleteImageModal = forwardRef(
|
const { t } = useTranslation();
|
||||||
({ image, children }: DeleteImageModalProps, ref) => {
|
const { shouldConfirmOnDelete, canRestoreDeletedImagesFromBin } =
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
useAppSelector(selector);
|
||||||
const dispatch = useAppDispatch();
|
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||||
const { shouldConfirmOnDelete, isConnected, isProcessing } = useAppSelector(
|
|
||||||
deleteImageModalSelector
|
|
||||||
);
|
|
||||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
|
||||||
|
|
||||||
const handleClickDelete = (e: SyntheticEvent) => {
|
const handleChangeShouldConfirmOnDelete = useCallback(
|
||||||
e.stopPropagation();
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
shouldConfirmOnDelete ? onOpen() : handleDelete();
|
dispatch(setShouldConfirmOnDelete(!e.target.checked)),
|
||||||
};
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleClickDelete = useCallback(() => {
|
||||||
if (isConnected && !isProcessing && image) {
|
handleDelete();
|
||||||
dispatch(
|
onClose();
|
||||||
imageDeleted({ imageType: image.type, imageName: image.name })
|
}, [handleDelete, onClose]);
|
||||||
);
|
|
||||||
}
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
useHotkeys(
|
return (
|
||||||
'delete',
|
<AlertDialog
|
||||||
() => {
|
isOpen={isOpen}
|
||||||
shouldConfirmOnDelete ? onOpen() : handleDelete();
|
leastDestructiveRef={cancelRef}
|
||||||
},
|
onClose={onClose}
|
||||||
[image, shouldConfirmOnDelete, isConnected, isProcessing]
|
isCentered
|
||||||
);
|
>
|
||||||
|
<AlertDialogOverlay>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||||
|
{t('gallery.deleteImage')}
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
const handleChangeShouldConfirmOnDelete = (
|
<AlertDialogBody>
|
||||||
e: ChangeEvent<HTMLInputElement>
|
<Flex direction="column" gap={5}>
|
||||||
) => dispatch(setShouldConfirmOnDelete(!e.target.checked));
|
<Flex direction="column" gap={2}>
|
||||||
|
<Text>{t('common.areYouSure')}</Text>
|
||||||
|
<Text>
|
||||||
|
{canRestoreDeletedImagesFromBin
|
||||||
|
? t('gallery.deleteImageBin')
|
||||||
|
: t('gallery.deleteImagePermanent')}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<IAISwitch
|
||||||
|
label={t('common.dontAskMeAgain')}
|
||||||
|
isChecked={!shouldConfirmOnDelete}
|
||||||
|
onChange={handleChangeShouldConfirmOnDelete}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</AlertDialogBody>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<IAIButton ref={cancelRef} onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</IAIButton>
|
||||||
|
<IAIButton colorScheme="error" onClick={handleClickDelete} ml={3}>
|
||||||
|
Delete
|
||||||
|
</IAIButton>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogOverlay>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
export default memo(DeleteImageModal);
|
||||||
<>
|
|
||||||
{cloneElement(children, {
|
|
||||||
// TODO: This feels wrong.
|
|
||||||
onClick: image ? handleClickDelete : undefined,
|
|
||||||
ref: ref,
|
|
||||||
})}
|
|
||||||
|
|
||||||
<AlertDialog
|
|
||||||
isOpen={isOpen}
|
|
||||||
leastDestructiveRef={cancelRef}
|
|
||||||
onClose={onClose}
|
|
||||||
>
|
|
||||||
<AlertDialogOverlay>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
|
||||||
Delete image
|
|
||||||
</AlertDialogHeader>
|
|
||||||
|
|
||||||
<AlertDialogBody>
|
|
||||||
<Flex direction="column" gap={5}>
|
|
||||||
<Text>
|
|
||||||
Are you sure? Deleted images will be sent to the Bin. You
|
|
||||||
can restore from there if you wish to.
|
|
||||||
</Text>
|
|
||||||
<IAISwitch
|
|
||||||
label="Don't ask me again"
|
|
||||||
isChecked={!shouldConfirmOnDelete}
|
|
||||||
onChange={handleChangeShouldConfirmOnDelete}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</AlertDialogBody>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<IAIButton ref={cancelRef} onClick={onClose}>
|
|
||||||
Cancel
|
|
||||||
</IAIButton>
|
|
||||||
<IAIButton colorScheme="error" onClick={handleDelete} ml={3}>
|
|
||||||
Delete
|
|
||||||
</IAIButton>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialogOverlay>
|
|
||||||
</AlertDialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
DeleteImageModal.displayName = 'DeleteImageModal';
|
|
||||||
|
|
||||||
export default DeleteImageModal;
|
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
MenuList,
|
MenuList,
|
||||||
Text,
|
Text,
|
||||||
|
useDisclosure,
|
||||||
useTheme,
|
useTheme,
|
||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
@ -36,7 +37,7 @@ import {
|
|||||||
resizeAndScaleCanvas,
|
resizeAndScaleCanvas,
|
||||||
setInitialCanvasImage,
|
setInitialCanvasImage,
|
||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { hoverableImageSelector } from 'features/gallery/store/gallerySelectors';
|
import { gallerySelector } from 'features/gallery/store/gallerySelectors';
|
||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
|
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
|
||||||
@ -46,6 +47,50 @@ import { useGetUrl } from 'common/util/getUrl';
|
|||||||
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||||
import { BiZoomIn } from 'react-icons/bi';
|
import { BiZoomIn } from 'react-icons/bi';
|
||||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||||
|
import { imageDeleted } from 'services/thunks/image';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
|
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
||||||
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
|
export const selector = createSelector(
|
||||||
|
[
|
||||||
|
gallerySelector,
|
||||||
|
systemSelector,
|
||||||
|
configSelector,
|
||||||
|
lightboxSelector,
|
||||||
|
activeTabNameSelector,
|
||||||
|
],
|
||||||
|
(gallery, system, config, lightbox, activeTabName) => {
|
||||||
|
const {
|
||||||
|
galleryImageObjectFit,
|
||||||
|
galleryImageMinimumWidth,
|
||||||
|
shouldUseSingleGalleryColumn,
|
||||||
|
} = gallery;
|
||||||
|
|
||||||
|
const { isLightboxOpen } = lightbox;
|
||||||
|
const { disabledFeatures } = config;
|
||||||
|
const { isConnected, isProcessing, shouldConfirmOnDelete } = system;
|
||||||
|
|
||||||
|
return {
|
||||||
|
canDeleteImage: isConnected && !isProcessing,
|
||||||
|
shouldConfirmOnDelete,
|
||||||
|
galleryImageObjectFit,
|
||||||
|
galleryImageMinimumWidth,
|
||||||
|
shouldUseSingleGalleryColumn,
|
||||||
|
activeTabName,
|
||||||
|
isLightboxOpen,
|
||||||
|
disabledFeatures,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
interface HoverableImageProps {
|
interface HoverableImageProps {
|
||||||
image: InvokeAI.Image;
|
image: InvokeAI.Image;
|
||||||
@ -66,10 +111,16 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
activeTabName,
|
activeTabName,
|
||||||
galleryImageObjectFit,
|
galleryImageObjectFit,
|
||||||
galleryImageMinimumWidth,
|
galleryImageMinimumWidth,
|
||||||
mayDeleteImage,
|
canDeleteImage,
|
||||||
shouldUseSingleGalleryColumn,
|
shouldUseSingleGalleryColumn,
|
||||||
disabledFeatures,
|
disabledFeatures,
|
||||||
} = useAppSelector(hoverableImageSelector);
|
shouldConfirmOnDelete,
|
||||||
|
} = useAppSelector(selector);
|
||||||
|
const {
|
||||||
|
isOpen: isDeleteDialogOpen,
|
||||||
|
onOpen: onDeleteDialogOpen,
|
||||||
|
onClose: onDeleteDialogClose,
|
||||||
|
} = useDisclosure();
|
||||||
const { image, isSelected } = props;
|
const { image, isSelected } = props;
|
||||||
const { url, thumbnail, name, metadata } = image;
|
const { url, thumbnail, name, metadata } = image;
|
||||||
const { getUrl } = useGetUrl();
|
const { getUrl } = useGetUrl();
|
||||||
@ -85,6 +136,20 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
|
|
||||||
const handleMouseOut = () => setIsHovered(false);
|
const handleMouseOut = () => setIsHovered(false);
|
||||||
|
|
||||||
|
const handleInitiateDelete = () => {
|
||||||
|
if (shouldConfirmOnDelete) {
|
||||||
|
onDeleteDialogOpen();
|
||||||
|
} else {
|
||||||
|
handleDelete();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (canDeleteImage && image) {
|
||||||
|
dispatch(imageDeleted({ imageType: image.type, imageName: image.name }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleUsePrompt = () => {
|
const handleUsePrompt = () => {
|
||||||
if (image.metadata?.sd_metadata?.prompt) {
|
if (image.metadata?.sd_metadata?.prompt) {
|
||||||
setBothPrompts(image.metadata?.sd_metadata?.prompt);
|
setBothPrompts(image.metadata?.sd_metadata?.prompt);
|
||||||
@ -184,159 +249,167 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu<HTMLDivElement>
|
<>
|
||||||
menuProps={{ size: 'sm', isLazy: true }}
|
<ContextMenu<HTMLDivElement>
|
||||||
renderMenu={() => (
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
<MenuList>
|
renderMenu={() => (
|
||||||
<MenuItem
|
<MenuList>
|
||||||
icon={<ExternalLinkIcon />}
|
<MenuItem
|
||||||
onClickCapture={handleOpenInNewTab}
|
icon={<ExternalLinkIcon />}
|
||||||
>
|
onClickCapture={handleOpenInNewTab}
|
||||||
{t('common.openInNewTab')}
|
>
|
||||||
</MenuItem>
|
{t('common.openInNewTab')}
|
||||||
{!disabledFeatures.includes('lightbox') && (
|
|
||||||
<MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}>
|
|
||||||
{t('parameters.openInViewer')}
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
{!disabledFeatures.includes('lightbox') && (
|
||||||
<MenuItem
|
<MenuItem icon={<FaExpand />} onClickCapture={handleLightBox}>
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
{t('parameters.openInViewer')}
|
||||||
onClickCapture={handleUsePrompt}
|
</MenuItem>
|
||||||
isDisabled={image?.metadata?.sd_metadata?.prompt === undefined}
|
|
||||||
>
|
|
||||||
{t('parameters.usePrompt')}
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
<MenuItem
|
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
|
||||||
onClickCapture={handleUseSeed}
|
|
||||||
isDisabled={image?.metadata?.sd_metadata?.seed === undefined}
|
|
||||||
>
|
|
||||||
{t('parameters.useSeed')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
|
||||||
onClickCapture={handleUseInitialImage}
|
|
||||||
isDisabled={image?.metadata?.sd_metadata?.type !== 'img2img'}
|
|
||||||
>
|
|
||||||
{t('parameters.useInitImg')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
|
||||||
onClickCapture={handleUseAllParameters}
|
|
||||||
isDisabled={
|
|
||||||
!['txt2img', 'img2img'].includes(
|
|
||||||
image?.metadata?.sd_metadata?.type
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t('parameters.useAll')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon={<FaShare />}
|
|
||||||
onClickCapture={handleSendToImageToImage}
|
|
||||||
>
|
|
||||||
{t('parameters.sendToImg2Img')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<FaShare />} onClickCapture={handleSendToCanvas}>
|
|
||||||
{t('parameters.sendToUnifiedCanvas')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<FaTrash />}>
|
|
||||||
<DeleteImageModal image={image}>
|
|
||||||
<Text>{t('parameters.deleteImage')}</Text>
|
|
||||||
</DeleteImageModal>
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{(ref) => (
|
|
||||||
<Box
|
|
||||||
position="relative"
|
|
||||||
key={name}
|
|
||||||
onMouseOver={handleMouseOver}
|
|
||||||
onMouseOut={handleMouseOut}
|
|
||||||
userSelect="none"
|
|
||||||
draggable={true}
|
|
||||||
onDragStart={handleDragStart}
|
|
||||||
ref={ref}
|
|
||||||
sx={{
|
|
||||||
padding: 2,
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
transition: 'transform 0.2s ease-out',
|
|
||||||
_hover: {
|
|
||||||
cursor: 'pointer',
|
|
||||||
|
|
||||||
zIndex: 2,
|
|
||||||
},
|
|
||||||
_before: { content: '""', display: 'block', paddingBottom: '100%' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
objectFit={
|
|
||||||
shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
|
|
||||||
}
|
|
||||||
rounded="md"
|
|
||||||
src={getUrl(thumbnail || url)}
|
|
||||||
loading="lazy"
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
maxWidth: '100%',
|
|
||||||
maxHeight: '100%',
|
|
||||||
top: '50%',
|
|
||||||
transform: 'translate(-50%,-50%)',
|
|
||||||
...(direction === 'rtl'
|
|
||||||
? { insetInlineEnd: '50%' }
|
|
||||||
: { insetInlineStart: '50%' }),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Flex
|
|
||||||
onClick={handleSelectImage}
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '0',
|
|
||||||
insetInlineStart: '0',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isSelected && (
|
|
||||||
<Icon
|
|
||||||
as={FaCheck}
|
|
||||||
sx={{
|
|
||||||
width: '50%',
|
|
||||||
height: '50%',
|
|
||||||
fill: 'ok.500',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Flex>
|
<MenuItem
|
||||||
{isHovered && galleryImageMinimumWidth >= 64 && (
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
<Box
|
onClickCapture={handleUsePrompt}
|
||||||
|
isDisabled={image?.metadata?.sd_metadata?.prompt === undefined}
|
||||||
|
>
|
||||||
|
{t('parameters.usePrompt')}
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
|
onClickCapture={handleUseSeed}
|
||||||
|
isDisabled={image?.metadata?.sd_metadata?.seed === undefined}
|
||||||
|
>
|
||||||
|
{t('parameters.useSeed')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
|
onClickCapture={handleUseInitialImage}
|
||||||
|
isDisabled={image?.metadata?.sd_metadata?.type !== 'img2img'}
|
||||||
|
>
|
||||||
|
{t('parameters.useInitImg')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
|
onClickCapture={handleUseAllParameters}
|
||||||
|
isDisabled={
|
||||||
|
!['txt2img', 'img2img'].includes(
|
||||||
|
image?.metadata?.sd_metadata?.type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('parameters.useAll')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={<FaShare />}
|
||||||
|
onClickCapture={handleSendToImageToImage}
|
||||||
|
>
|
||||||
|
{t('parameters.sendToImg2Img')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<FaShare />} onClickCapture={handleSendToCanvas}>
|
||||||
|
{t('parameters.sendToUnifiedCanvas')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<FaTrash />} onClickCapture={onDeleteDialogOpen}>
|
||||||
|
{t('gallery.deleteImage')}
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{(ref) => (
|
||||||
|
<Box
|
||||||
|
position="relative"
|
||||||
|
key={name}
|
||||||
|
onMouseOver={handleMouseOver}
|
||||||
|
onMouseOut={handleMouseOut}
|
||||||
|
userSelect="none"
|
||||||
|
draggable={true}
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
ref={ref}
|
||||||
|
sx={{
|
||||||
|
padding: 2,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
transition: 'transform 0.2s ease-out',
|
||||||
|
_hover: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
_before: {
|
||||||
|
content: '""',
|
||||||
|
display: 'block',
|
||||||
|
paddingBottom: '100%',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
objectFit={
|
||||||
|
shouldUseSingleGalleryColumn ? 'contain' : galleryImageObjectFit
|
||||||
|
}
|
||||||
|
rounded="md"
|
||||||
|
src={getUrl(thumbnail || url)}
|
||||||
|
loading="lazy"
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 1,
|
width: '100%',
|
||||||
insetInlineEnd: 1,
|
height: '100%',
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '100%',
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translate(-50%,-50%)',
|
||||||
|
...(direction === 'rtl'
|
||||||
|
? { insetInlineEnd: '50%' }
|
||||||
|
: { insetInlineStart: '50%' }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Flex
|
||||||
|
onClick={handleSelectImage}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '0',
|
||||||
|
insetInlineStart: '0',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeleteImageModal image={image}>
|
{isSelected && (
|
||||||
|
<Icon
|
||||||
|
as={FaCheck}
|
||||||
|
sx={{
|
||||||
|
width: '50%',
|
||||||
|
height: '50%',
|
||||||
|
fill: 'ok.500',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
{isHovered && galleryImageMinimumWidth >= 64 && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 1,
|
||||||
|
insetInlineEnd: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('parameters.deleteImage')}
|
onClickCapture={handleInitiateDelete}
|
||||||
|
aria-label={t('gallery.deleteImage')}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
size="xs"
|
size="xs"
|
||||||
fontSize={14}
|
fontSize={14}
|
||||||
isDisabled={!mayDeleteImage}
|
isDisabled={!canDeleteImage}
|
||||||
/>
|
/>
|
||||||
</DeleteImageModal>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
)}
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
)}
|
</ContextMenu>
|
||||||
</ContextMenu>
|
<DeleteImageModal
|
||||||
|
isOpen={isDeleteDialogOpen}
|
||||||
|
onClose={onDeleteDialogClose}
|
||||||
|
handleDelete={handleDelete}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}, memoEqualityCheck);
|
}, memoEqualityCheck);
|
||||||
|
|
||||||
|
@ -68,32 +68,6 @@ export const imageGallerySelector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const hoverableImageSelector = createSelector(
|
|
||||||
[
|
|
||||||
gallerySelector,
|
|
||||||
systemSelector,
|
|
||||||
configSelector,
|
|
||||||
lightboxSelector,
|
|
||||||
activeTabNameSelector,
|
|
||||||
],
|
|
||||||
(gallery, system, config, lightbox, activeTabName) => {
|
|
||||||
return {
|
|
||||||
mayDeleteImage: system.isConnected && !system.isProcessing,
|
|
||||||
galleryImageObjectFit: gallery.galleryImageObjectFit,
|
|
||||||
galleryImageMinimumWidth: gallery.galleryImageMinimumWidth,
|
|
||||||
shouldUseSingleGalleryColumn: gallery.shouldUseSingleGalleryColumn,
|
|
||||||
activeTabName,
|
|
||||||
isLightboxOpen: lightbox.isLightboxOpen,
|
|
||||||
disabledFeatures: config.disabledFeatures,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
memoizeOptions: {
|
|
||||||
resultEqualityCheck: isEqual,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectedImageSelector = createSelector(
|
export const selectedImageSelector = createSelector(
|
||||||
[gallerySelector, selectResultsEntities, selectUploadsEntities],
|
[gallerySelector, selectResultsEntities, selectUploadsEntities],
|
||||||
(gallery, allResults, allUploads) => {
|
(gallery, allResults, allUploads) => {
|
||||||
|
@ -8,6 +8,7 @@ const initialConfigState: AppConfig = {
|
|||||||
shouldFetchImages: false,
|
shouldFetchImages: false,
|
||||||
disabledTabs: [],
|
disabledTabs: [],
|
||||||
disabledFeatures: [],
|
disabledFeatures: [],
|
||||||
|
canRestoreDeletedImagesFromBin: true,
|
||||||
sd: {
|
sd: {
|
||||||
iterations: {
|
iterations: {
|
||||||
initial: 1,
|
initial: 1,
|
||||||
|
Loading…
Reference in New Issue
Block a user