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:
psychedelicious
2023-07-04 00:09:18 +10:00
parent fa169b5517
commit 90aa97edd4
100 changed files with 3476 additions and 2075 deletions

View File

@ -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>
);
};