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

@@ -32,11 +32,12 @@ import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent
import TextToImageTab from './tabs/TextToImage/TextToImageTab';
import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab';
import NodesTab from './tabs/Nodes/NodesTab';
import { FaFont, FaImage } from 'react-icons/fa';
import { FaFont, FaImage, FaLayerGroup } from 'react-icons/fa';
import ResizeHandle from './tabs/ResizeHandle';
import ImageTab from './tabs/ImageToImage/ImageToImageTab';
import AuxiliaryProgressIndicator from 'app/components/AuxiliaryProgressIndicator';
import { useMinimumPanelSize } from '../hooks/useMinimumPanelSize';
import BatchTab from './tabs/Batch/BatchTab';
export interface InvokeTabInfo {
id: InvokeTabName;
@@ -65,6 +66,11 @@ const tabs: InvokeTabInfo[] = [
icon: <Icon as={MdDeviceHub} sx={{ boxSize: 6, pointerEvents: 'none' }} />,
content: <NodesTab />,
},
{
id: 'batch',
icon: <Icon as={FaLayerGroup} sx={{ boxSize: 6, pointerEvents: 'none' }} />,
content: <BatchTab />,
},
];
const enabledTabsSelector = createSelector(

View File

@@ -71,7 +71,15 @@ const ParametersDrawer = () => {
onClose={handleClosePanel}
>
<Flex
sx={{ flexDir: 'column', h: 'full', w: PARAMETERS_PANEL_WIDTH, gap: 2 }}
sx={{
flexDir: 'column',
h: 'full',
w: PARAMETERS_PANEL_WIDTH,
gap: 2,
position: 'relative',
flexShrink: 0,
overflowY: 'auto',
}}
>
<Flex
paddingTop={1.5}
@@ -82,9 +90,16 @@ const ParametersDrawer = () => {
<InvokeAILogoComponent />
<PinParametersPanelButton />
</Flex>
<OverlayScrollable>
<Flex sx={{ flexDir: 'column', gap: 2 }}>{drawerContent}</Flex>
</OverlayScrollable>
<Flex
sx={{
gap: 2,
flexDirection: 'column',
h: 'full',
w: 'full',
}}
>
{drawerContent}
</Flex>
</Flex>
</ResizableDrawer>
);

View File

@@ -42,18 +42,10 @@ const ParametersPinnedWrapper = (props: ParametersPinnedWrapperProps) => {
h: 'full',
w: 'full',
position: 'absolute',
overflowY: 'auto',
}}
>
<OverlayScrollable>
<Flex
sx={{
flexDirection: 'column',
gap: 2,
}}
>
{props.children}
</Flex>
</OverlayScrollable>
{props.children}
</Flex>
<PinParametersPanelButton

View File

@@ -0,0 +1,43 @@
import { Box, Flex } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import InitialImageDisplay from 'features/parameters/components/Parameters/ImageToImage/InitialImageDisplay';
import { memo, useCallback, useRef } from 'react';
import {
ImperativePanelGroupHandle,
Panel,
PanelGroup,
} from 'react-resizable-panels';
import ResizeHandle from '../ResizeHandle';
import TextToImageTabMain from '../TextToImage/TextToImageTabMain';
import BatchManager from 'features/batch/components/BatchManager';
const ImageToImageTab = () => {
const dispatch = useAppDispatch();
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
const handleDoubleClickHandle = useCallback(() => {
if (!panelGroupRef.current) {
return;
}
panelGroupRef.current.setLayout([50, 50]);
}, []);
return (
<Flex
layerStyle={'first'}
sx={{
gap: 4,
p: 4,
w: 'full',
h: 'full',
borderRadius: 'base',
}}
>
<BatchManager />
</Flex>
);
};
export default memo(ImageToImageTab);

View File

@@ -14,8 +14,12 @@ import UnifiedCanvasToolbarBeta from './UnifiedCanvasBeta/UnifiedCanvasToolbarBe
import UnifiedCanvasToolSettingsBeta from './UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta';
import { ImageDTO } from 'services/api/types';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { useDroppable } from '@dnd-kit/core';
import IAIDropOverlay from 'common/components/IAIDropOverlay';
import {
CanvasInitialImageDropData,
isValidDrop,
useDroppable,
} from 'app/components/ImageDnd/typesafeDnd';
const selector = createSelector(
[canvasSelector, uiSelector],
@@ -30,28 +34,24 @@ const selector = createSelector(
defaultSelectorOptions
);
const droppableData: CanvasInitialImageDropData = {
id: 'canvas-intial-image',
actionType: 'SET_CANVAS_INITIAL_IMAGE',
};
const UnifiedCanvasContent = () => {
const dispatch = useAppDispatch();
const { doesCanvasNeedScaling, shouldUseCanvasBetaLayout } =
useAppSelector(selector);
const onDrop = useCallback(
(droppedImage: ImageDTO) => {
dispatch(setInitialCanvasImage(droppedImage));
},
[dispatch]
);
const {
isOver,
setNodeRef: setDroppableRef,
active,
} = useDroppable({
id: 'unifiedCanvas',
data: {
handleDrop: onDrop,
},
data: droppableData,
});
useLayoutEffect(() => {
@@ -97,7 +97,12 @@ const UnifiedCanvasContent = () => {
<UnifiedCanvasToolSettingsBeta />
<Box sx={{ w: 'full', h: 'full', position: 'relative' }}>
{doesCanvasNeedScaling ? <IAICanvasResizer /> : <IAICanvas />}
{active && <IAIDropOverlay isOver={isOver} />}
{isValidDrop(droppableData, active) && (
<IAIDropOverlay
isOver={isOver}
label="Set Canvas Initial Image"
/>
)}
</Box>
</Flex>
</Flex>
@@ -139,7 +144,12 @@ const UnifiedCanvasContent = () => {
>
<Box sx={{ w: 'full', h: 'full', position: 'relative' }}>
{doesCanvasNeedScaling ? <IAICanvasResizer /> : <IAICanvas />}
{active && <IAIDropOverlay isOver={isOver} />}
{isValidDrop(droppableData, active) && (
<IAIDropOverlay
isOver={isOver}
label="Set Canvas Initial Image"
/>
)}
</Box>
</Flex>
</Flex>