mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-18 20:54:31 -05: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:
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user