mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add multi-select and batch capabilities
This introduces the core functionality for batch operations on images and multiple selection in the gallery/batch manager. A number of other substantial changes are included: - `imagesSlice` is consolidated into `gallerySlice`, allowing for simpler selection of filtered images - `batchSlice` is added to manage the batch - The wonky context pattern for image deletion has been changed, much simpler now using a `imageDeletionSlice` and redux listeners; this needs to be implemented still for the other image modals - Minimum gallery size in px implemented as a hook - Many style fixes & several bug fixes TODO: - The UI and UX need to be figured out, especially for controlnet - Batch processing is not hooked up; generation does not do anything with batch - Routes to support batch image operations, specifically delete and add/remove to/from boards
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import {
|
||||
ControlNetConfig,
|
||||
@ -10,11 +10,16 @@ import { Box, Flex, SystemStyleObject } from '@chakra-ui/react';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback';
|
||||
import { IAILoadingImageFallback } from 'common/components/IAIImageFallback';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { FaUndo } from 'react-icons/fa';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||
import {
|
||||
TypesafeDraggableData,
|
||||
TypesafeDroppableData,
|
||||
} from 'app/components/ImageDnd/typesafeDnd';
|
||||
import { PostUploadAction } from 'services/api/thunks/image';
|
||||
|
||||
const selector = createSelector(
|
||||
controlNetSelector,
|
||||
@ -57,22 +62,6 @@ const ControlNetImagePreview = (props: Props) => {
|
||||
isSuccess: isSuccessProcessedControlImage,
|
||||
} = useGetImageDTOQuery(processedControlImageName ?? skipToken);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(droppedImage: ImageDTO) => {
|
||||
if (controlImageName === droppedImage.image_name) {
|
||||
return;
|
||||
}
|
||||
setIsMouseOverImage(false);
|
||||
dispatch(
|
||||
controlNetImageChanged({
|
||||
controlNetId,
|
||||
controlImage: droppedImage.image_name,
|
||||
})
|
||||
);
|
||||
},
|
||||
[controlImageName, controlNetId, dispatch]
|
||||
);
|
||||
|
||||
const handleResetControlImage = useCallback(() => {
|
||||
dispatch(controlNetImageChanged({ controlNetId, controlImage: null }));
|
||||
}, [controlNetId, dispatch]);
|
||||
@ -84,6 +73,31 @@ const ControlNetImagePreview = (props: Props) => {
|
||||
setIsMouseOverImage(false);
|
||||
}, []);
|
||||
|
||||
const draggableData = useMemo<TypesafeDraggableData | undefined>(() => {
|
||||
if (controlImage) {
|
||||
return {
|
||||
id: controlNetId,
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: controlImage },
|
||||
};
|
||||
}
|
||||
}, [controlImage, controlNetId]);
|
||||
|
||||
const droppableData = useMemo<TypesafeDroppableData | undefined>(() => {
|
||||
if (controlNetId) {
|
||||
return {
|
||||
id: controlNetId,
|
||||
actionType: 'SET_CONTROLNET_IMAGE',
|
||||
context: { controlNetId },
|
||||
};
|
||||
}
|
||||
}, [controlNetId]);
|
||||
|
||||
const postUploadAction = useMemo<PostUploadAction>(
|
||||
() => ({ type: 'SET_CONTROLNET_IMAGE', controlNetId }),
|
||||
[controlNetId]
|
||||
);
|
||||
|
||||
const shouldShowProcessedImage =
|
||||
controlImage &&
|
||||
processedControlImage &&
|
||||
@ -104,14 +118,14 @@ const ControlNetImagePreview = (props: Props) => {
|
||||
}}
|
||||
>
|
||||
<IAIDndImage
|
||||
image={controlImage}
|
||||
onDrop={handleDrop}
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={controlImage}
|
||||
isDropDisabled={shouldShowProcessedImage}
|
||||
postUploadAction={{ type: 'SET_CONTROLNET_IMAGE', controlNetId }}
|
||||
imageSx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
}}
|
||||
onClickReset={handleResetControlImage}
|
||||
postUploadAction={postUploadAction}
|
||||
resetTooltip="Reset Control Image"
|
||||
withResetIcon={Boolean(controlImage)}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
@ -127,14 +141,13 @@ const ControlNetImagePreview = (props: Props) => {
|
||||
}}
|
||||
>
|
||||
<IAIDndImage
|
||||
image={processedControlImage}
|
||||
onDrop={handleDrop}
|
||||
payloadImage={controlImage}
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={processedControlImage}
|
||||
isUploadDisabled={true}
|
||||
imageSx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
}}
|
||||
onClickReset={handleResetControlImage}
|
||||
resetTooltip="Reset Control Image"
|
||||
withResetIcon={Boolean(controlImage)}
|
||||
/>
|
||||
</Box>
|
||||
{pendingControlImages.includes(controlNetId) && (
|
||||
@ -145,27 +158,12 @@ const ControlNetImagePreview = (props: Props) => {
|
||||
insetInlineStart: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
>
|
||||
<IAIImageLoadingFallback />
|
||||
<IAILoadingImageFallback image={controlImage} />
|
||||
</Box>
|
||||
)}
|
||||
{controlImage && (
|
||||
<Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0 }}>
|
||||
<IAIIconButton
|
||||
aria-label="Reset Control Image"
|
||||
tooltip="Reset Control Image"
|
||||
size="sm"
|
||||
onClick={handleResetControlImage}
|
||||
icon={<FaUndo />}
|
||||
variant="link"
|
||||
sx={{
|
||||
p: 2,
|
||||
color: 'base.50',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user