diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/DragPreview.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/DragPreview.tsx
index 5b6142d748..bf66c0ee08 100644
--- a/invokeai/frontend/web/src/app/components/ImageDnd/DragPreview.tsx
+++ b/invokeai/frontend/web/src/app/components/ImageDnd/DragPreview.tsx
@@ -1,4 +1,8 @@
import { Box, ChakraProps, Flex, Heading, Image } from '@chakra-ui/react';
+import { createSelector } from '@reduxjs/toolkit';
+import { stateSelector } from 'app/store/store';
+import { useAppSelector } from 'app/store/storeHooks';
+import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { memo } from 'react';
import { TypesafeDraggableData } from './typesafeDnd';
@@ -28,7 +32,24 @@ const STYLES: ChakraProps['sx'] = {
},
};
+const selector = createSelector(
+ stateSelector,
+ (state) => {
+ const gallerySelectionCount = state.gallery.selection.length;
+ const batchSelectionCount = state.batch.selection.length;
+
+ return {
+ gallerySelectionCount,
+ batchSelectionCount,
+ };
+ },
+ defaultSelectorOptions
+);
+
const DragPreview = (props: OverlayDragImageProps) => {
+ const { gallerySelectionCount, batchSelectionCount } =
+ useAppSelector(selector);
+
if (!props.dragData) {
return;
}
@@ -57,7 +78,7 @@ const DragPreview = (props: OverlayDragImageProps) => {
);
}
- if (props.dragData.payloadType === 'IMAGE_NAMES') {
+ if (props.dragData.payloadType === 'BATCH_SELECTION') {
return (
{
...STYLES,
}}
>
- {props.dragData.payload.imageNames.length}
+ {batchSelectionCount}
+ Images
+
+ );
+ }
+
+ if (props.dragData.payloadType === 'GALLERY_SELECTION') {
+ return (
+
+ {gallerySelectionCount}
Images
);
diff --git a/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx b/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx
index e744a70750..1478ace748 100644
--- a/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx
+++ b/invokeai/frontend/web/src/app/components/ImageDnd/typesafeDnd.tsx
@@ -77,14 +77,18 @@ export type ImageDraggableData = BaseDragData & {
payload: { imageDTO: ImageDTO };
};
-export type ImageNamesDraggableData = BaseDragData & {
- payloadType: 'IMAGE_NAMES';
- payload: { imageNames: string[] };
+export type GallerySelectionDraggableData = BaseDragData & {
+ payloadType: 'GALLERY_SELECTION';
+};
+
+export type BatchSelectionDraggableData = BaseDragData & {
+ payloadType: 'BATCH_SELECTION';
};
export type TypesafeDraggableData =
| ImageDraggableData
- | ImageNamesDraggableData;
+ | GallerySelectionDraggableData
+ | BatchSelectionDraggableData;
interface UseDroppableTypesafeArguments
extends Omit {
@@ -155,11 +159,13 @@ export const isValidDrop = (
case 'SET_NODES_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SET_MULTI_NODES_IMAGE':
- return payloadType === 'IMAGE_DTO' || 'IMAGE_NAMES';
+ return payloadType === 'IMAGE_DTO' || 'GALLERY_SELECTION';
case 'ADD_TO_BATCH':
- return payloadType === 'IMAGE_DTO' || 'IMAGE_NAMES';
+ return payloadType === 'IMAGE_DTO' || 'GALLERY_SELECTION';
case 'MOVE_BOARD':
- return payloadType === 'IMAGE_DTO' || 'IMAGE_NAMES';
+ return (
+ payloadType === 'IMAGE_DTO' || 'GALLERY_SELECTION' || 'BATCH_SELECTION'
+ );
default:
return false;
}
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts
index 56f660a653..24a5bffec7 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts
@@ -1,24 +1,23 @@
import { createAction } from '@reduxjs/toolkit';
-import { startAppListening } from '../';
-import { log } from 'app/logging/useLogger';
import {
TypesafeDraggableData,
TypesafeDroppableData,
} from 'app/components/ImageDnd/typesafeDnd';
-import { imageSelected } from 'features/gallery/store/gallerySlice';
-import { initialImageChanged } from 'features/parameters/store/generationSlice';
+import { log } from 'app/logging/useLogger';
import {
imageAddedToBatch,
imagesAddedToBatch,
} from 'features/batch/store/batchSlice';
-import { controlNetImageChanged } from 'features/controlNet/store/controlNetSlice';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
+import { controlNetImageChanged } from 'features/controlNet/store/controlNetSlice';
+import { imageSelected } from 'features/gallery/store/gallerySlice';
import {
fieldValueChanged,
imageCollectionFieldValueChanged,
} from 'features/nodes/store/nodesSlice';
-import { boardsApi } from 'services/api/endpoints/boards';
+import { initialImageChanged } from 'features/parameters/store/generationSlice';
import { boardImagesApi } from 'services/api/endpoints/boardImages';
+import { startAppListening } from '../';
const moduleLog = log.child({ namespace: 'dnd' });
@@ -33,6 +32,7 @@ export const addImageDroppedListener = () => {
effect: (action, { dispatch, getState }) => {
const { activeData, overData } = action.payload;
const { actionType } = overData;
+ const state = getState();
// set current image
if (
@@ -64,9 +64,9 @@ export const addImageDroppedListener = () => {
// add multiple images to batch
if (
actionType === 'ADD_TO_BATCH' &&
- activeData.payloadType === 'IMAGE_NAMES'
+ activeData.payloadType === 'GALLERY_SELECTION'
) {
- dispatch(imagesAddedToBatch(activeData.payload.imageNames));
+ dispatch(imagesAddedToBatch(state.gallery.selection));
}
// set control image
@@ -128,14 +128,14 @@ export const addImageDroppedListener = () => {
// set multiple nodes images (multiple images handler)
if (
actionType === 'SET_MULTI_NODES_IMAGE' &&
- activeData.payloadType === 'IMAGE_NAMES'
+ activeData.payloadType === 'GALLERY_SELECTION'
) {
const { fieldName, nodeId } = overData.context;
dispatch(
imageCollectionFieldValueChanged({
nodeId,
fieldName,
- value: activeData.payload.imageNames.map((image_name) => ({
+ value: state.gallery.selection.map((image_name) => ({
image_name,
})),
})
diff --git a/invokeai/frontend/web/src/features/batch/components/BatchImage.tsx b/invokeai/frontend/web/src/features/batch/components/BatchImage.tsx
index 3394946972..4a6250f93a 100644
--- a/invokeai/frontend/web/src/features/batch/components/BatchImage.tsx
+++ b/invokeai/frontend/web/src/features/batch/components/BatchImage.tsx
@@ -19,7 +19,7 @@ const makeSelector = (image_name: string) =>
createSelector(
[stateSelector],
(state) => ({
- selection: state.batch.selection,
+ selectionCount: state.batch.selection.length,
isSelected: state.batch.selection.includes(image_name),
}),
defaultSelectorOptions
@@ -43,7 +43,7 @@ const BatchImage = (props: BatchImageProps) => {
[props.imageName]
);
- const { isSelected, selection } = useAppSelector(selector);
+ const { isSelected, selectionCount } = useAppSelector(selector);
const handleClickRemove = useCallback(() => {
dispatch(imageRemovedFromBatch(props.imageName));
@@ -63,13 +63,10 @@ const BatchImage = (props: BatchImageProps) => {
);
const draggableData = useMemo(() => {
- if (selection.length > 1) {
+ if (selectionCount > 1) {
return {
id: 'batch',
- payloadType: 'IMAGE_NAMES',
- payload: {
- imageNames: selection,
- },
+ payloadType: 'BATCH_SELECTION',
};
}
@@ -80,7 +77,7 @@ const BatchImage = (props: BatchImageProps) => {
payload: { imageDTO },
};
}
- }, [imageDTO, selection]);
+ }, [imageDTO, selectionCount]);
if (isError) {
return ;
diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx
index df73f1141d..c0c1030b79 100644
--- a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx
@@ -1,25 +1,22 @@
-import { memo, useCallback, useMemo, useState } from 'react';
-import { ImageDTO } from 'services/api/types';
-import {
- ControlNetConfig,
- controlNetImageChanged,
- controlNetSelector,
-} from '../store/controlNetSlice';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
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 { 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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
+import IAIDndImage from 'common/components/IAIDndImage';
+import { IAILoadingImageFallback } from 'common/components/IAIImageFallback';
+import { memo, useCallback, useMemo, useState } from 'react';
+import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { PostUploadAction } from 'services/api/thunks/image';
+import {
+ ControlNetConfig,
+ controlNetImageChanged,
+ controlNetSelector,
+} from '../store/controlNetSlice';
const selector = createSelector(
controlNetSelector,
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
index 112129ffa2..8018beea9a 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
@@ -1,19 +1,19 @@
import { Box, Flex, Image } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
-import { useAppSelector } from 'app/store/storeHooks';
-import { isEqual } from 'lodash-es';
-import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
-import NextPrevImageButtons from './NextPrevImageButtons';
-import { memo, useMemo } from 'react';
-import IAIDndImage from 'common/components/IAIDndImage';
-import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { skipToken } from '@reduxjs/toolkit/dist/query';
-import { stateSelector } from 'app/store/store';
-import { selectLastSelectedImage } from 'features/gallery/store/gallerySlice';
import {
TypesafeDraggableData,
TypesafeDroppableData,
} from 'app/components/ImageDnd/typesafeDnd';
+import { stateSelector } from 'app/store/store';
+import { useAppSelector } from 'app/store/storeHooks';
+import IAIDndImage from 'common/components/IAIDndImage';
+import { selectLastSelectedImage } from 'features/gallery/store/gallerySlice';
+import { isEqual } from 'lodash-es';
+import { memo, useMemo } from 'react';
+import { useGetImageDTOQuery } from 'services/api/endpoints/images';
+import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
+import NextPrevImageButtons from './NextPrevImageButtons';
export const imagesSelector = createSelector(
[stateSelector, selectLastSelectedImage],
diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx
index 7b2e27ddbe..ea0b3b0fd8 100644
--- a/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx
@@ -22,10 +22,10 @@ export const makeSelector = (image_name: string) =>
[stateSelector],
({ gallery }) => {
const isSelected = gallery.selection.includes(image_name);
- const selection = gallery.selection;
+ const selectionCount = gallery.selection.length;
return {
isSelected,
- selection,
+ selectionCount,
};
},
defaultSelectorOptions
@@ -44,7 +44,7 @@ const GalleryImage = (props: HoverableImageProps) => {
const localSelector = useMemo(() => makeSelector(image_name), [image_name]);
- const { isSelected, selection } = useAppSelector(localSelector);
+ const { isSelected, selectionCount } = useAppSelector(localSelector);
const dispatch = useAppDispatch();
@@ -75,11 +75,10 @@ const GalleryImage = (props: HoverableImageProps) => {
);
const draggableData = useMemo(() => {
- if (selection.length > 1) {
+ if (selectionCount > 1) {
return {
id: 'gallery-image',
- payloadType: 'IMAGE_NAMES',
- payload: { imageNames: selection },
+ payloadType: 'GALLERY_SELECTION',
};
}
@@ -90,7 +89,7 @@ const GalleryImage = (props: HoverableImageProps) => {
payload: { imageDTO },
};
}
- }, [imageDTO, selection]);
+ }, [imageDTO, selectionCount]);
return (
diff --git a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx
index 499946e3af..bfae89e931 100644
--- a/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/fields/ImageInputFieldComponent.tsx
@@ -7,18 +7,17 @@ import {
} from 'features/nodes/types/types';
import { memo, useCallback, useMemo } from 'react';
-import { FieldComponentProps } from './types';
-import IAIDndImage from 'common/components/IAIDndImage';
-import { ImageDTO } from 'services/api/types';
import { Flex } from '@chakra-ui/react';
-import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { skipToken } from '@reduxjs/toolkit/dist/query';
import {
- NodesImageDropData,
TypesafeDraggableData,
TypesafeDroppableData,
} from 'app/components/ImageDnd/typesafeDnd';
+import IAIDndImage from 'common/components/IAIDndImage';
+import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { PostUploadAction } from 'services/api/thunks/image';
+import { ImageDTO } from 'services/api/types';
+import { FieldComponentProps } from './types';
const ImageInputFieldComponent = (
props: FieldComponentProps