mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip again sry lol
This commit is contained in:
parent
2ef5919475
commit
58cb5fefd0
@ -82,7 +82,7 @@ const DragPreview = (props: OverlayDragImageProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (props.dragData.payloadType === 'BATCH_SELECTION') {
|
||||
if (props.dragData.payloadType === 'IMAGE_NAMES') {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
@ -95,26 +95,7 @@ const DragPreview = (props: OverlayDragImageProps) => {
|
||||
...STYLES,
|
||||
}}
|
||||
>
|
||||
<Heading>{batchSelectionCount}</Heading>
|
||||
<Heading size="sm">Images</Heading>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.dragData.payloadType === 'GALLERY_SELECTION') {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
cursor: 'none',
|
||||
userSelect: 'none',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDir: 'column',
|
||||
...STYLES,
|
||||
}}
|
||||
>
|
||||
<Heading>{gallerySelectionCount}</Heading>
|
||||
<Heading>{props.dragData.payload.image_names.length}</Heading>
|
||||
<Heading size="sm">Images</Heading>
|
||||
</Flex>
|
||||
);
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { snapCenterToCursor } from '@dnd-kit/modifiers';
|
||||
import { imageDropped } from 'app/store/middleware/listenerMiddleware/listeners/imageDropped';
|
||||
import { dndDropped } from 'app/store/middleware/listenerMiddleware/listeners/imageDropped';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { PropsWithChildren, memo, useCallback, useState } from 'react';
|
||||
@ -42,7 +42,7 @@ const ImageDndContext = (props: ImageDndContextProps) => {
|
||||
if (!activeData || !overData) {
|
||||
return;
|
||||
}
|
||||
dispatch(imageDropped({ overData, activeData }));
|
||||
dispatch(dndDropped({ overData, activeData }));
|
||||
setActiveDragData(null);
|
||||
},
|
||||
[dispatch]
|
||||
|
@ -77,18 +77,14 @@ export type ImageDraggableData = BaseDragData & {
|
||||
payload: { imageDTO: ImageDTO };
|
||||
};
|
||||
|
||||
export type GallerySelectionDraggableData = BaseDragData & {
|
||||
payloadType: 'GALLERY_SELECTION';
|
||||
};
|
||||
|
||||
export type BatchSelectionDraggableData = BaseDragData & {
|
||||
payloadType: 'BATCH_SELECTION';
|
||||
export type ImageNamesDraggableData = BaseDragData & {
|
||||
payloadType: 'IMAGE_NAMES';
|
||||
payload: { image_names: string[] };
|
||||
};
|
||||
|
||||
export type TypesafeDraggableData =
|
||||
| ImageDraggableData
|
||||
| GallerySelectionDraggableData
|
||||
| BatchSelectionDraggableData;
|
||||
| ImageNamesDraggableData;
|
||||
|
||||
interface UseDroppableTypesafeArguments
|
||||
extends Omit<UseDroppableArguments, 'data'> {
|
||||
@ -159,13 +155,11 @@ export const isValidDrop = (
|
||||
case 'SET_NODES_IMAGE':
|
||||
return payloadType === 'IMAGE_DTO';
|
||||
case 'SET_MULTI_NODES_IMAGE':
|
||||
return payloadType === 'IMAGE_DTO' || 'GALLERY_SELECTION';
|
||||
return payloadType === 'IMAGE_DTO' || 'IMAGE_NAMES';
|
||||
case 'ADD_TO_BATCH':
|
||||
return payloadType === 'IMAGE_DTO' || 'GALLERY_SELECTION';
|
||||
return payloadType === 'IMAGE_DTO' || 'IMAGE_NAMES';
|
||||
case 'MOVE_BOARD':
|
||||
return (
|
||||
payloadType === 'IMAGE_DTO' || 'GALLERY_SELECTION' || 'BATCH_SELECTION'
|
||||
);
|
||||
return payloadType === 'IMAGE_DTO' || 'IMAGE_NAMES';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { imagesAddedToBatch } from 'features/batch/store/batchSlice';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import { boardImageNamesReceived } from 'services/api/thunks/boardImages';
|
||||
import { receivedListOfImages } from 'services/api/thunks/image';
|
||||
import { startAppListening } from '..';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'batch' });
|
||||
@ -27,26 +25,7 @@ export const addAddBoardToBatchListener = () => {
|
||||
action.meta.requestId === requestId
|
||||
);
|
||||
|
||||
moduleLog.debug({ data: { payload } }, 'boardImageNamesReceived');
|
||||
|
||||
const { requestId: requestId2 } = dispatch(
|
||||
receivedListOfImages(payload.image_names)
|
||||
);
|
||||
|
||||
const [{ payload: payload2 }] = await take(
|
||||
(action): action is ReturnType<typeof receivedListOfImages.fulfilled> =>
|
||||
action.meta.requestId === requestId2
|
||||
);
|
||||
|
||||
moduleLog.debug({ data: { payload2 } }, 'receivedListOfImages');
|
||||
|
||||
dispatch(imagesAddedToBatch(payload2.image_dtos));
|
||||
|
||||
payload2.image_dtos.forEach((image) => {
|
||||
dispatch(
|
||||
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image)
|
||||
);
|
||||
});
|
||||
dispatch(imagesAddedToBatch(payload.image_names));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
} from 'features/batch/store/batchSlice';
|
||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||
import { controlNetImageChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { selectSelectedImages } from 'features/gallery/store/gallerySelectors';
|
||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
import {
|
||||
fieldValueChanged,
|
||||
@ -22,15 +21,15 @@ import { startAppListening } from '../';
|
||||
|
||||
const moduleLog = log.child({ namespace: 'dnd' });
|
||||
|
||||
export const imageDropped = createAction<{
|
||||
export const dndDropped = createAction<{
|
||||
overData: TypesafeDroppableData;
|
||||
activeData: TypesafeDraggableData;
|
||||
}>('dnd/imageDropped');
|
||||
}>('dnd/dndDropped');
|
||||
|
||||
export const addImageDroppedListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: imageDropped,
|
||||
effect: (action, { dispatch, getState }) => {
|
||||
actionCreator: dndDropped,
|
||||
effect: async (action, { dispatch, getState, take }) => {
|
||||
const { activeData, overData } = action.payload;
|
||||
const state = getState();
|
||||
|
||||
@ -46,6 +45,7 @@ export const addImageDroppedListener = () => {
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
dispatch(imageSelected(activeData.payload.imageDTO.image_name));
|
||||
return;
|
||||
}
|
||||
|
||||
// set initial image
|
||||
@ -55,6 +55,7 @@ export const addImageDroppedListener = () => {
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
dispatch(initialImageChanged(activeData.payload.imageDTO));
|
||||
return;
|
||||
}
|
||||
|
||||
// add image to batch
|
||||
@ -63,16 +64,18 @@ export const addImageDroppedListener = () => {
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
dispatch(imageAddedToBatch(activeData.payload.imageDTO));
|
||||
dispatch(imageAddedToBatch(activeData.payload.imageDTO.image_name));
|
||||
return;
|
||||
}
|
||||
|
||||
// add multiple images to batch
|
||||
if (
|
||||
overData.actionType === 'ADD_TO_BATCH' &&
|
||||
activeData.payloadType === 'GALLERY_SELECTION'
|
||||
activeData.payloadType === 'IMAGE_NAMES'
|
||||
) {
|
||||
const images = selectSelectedImages(state);
|
||||
dispatch(imagesAddedToBatch(images));
|
||||
dispatch(imagesAddedToBatch(activeData.payload.image_names));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// set control image
|
||||
@ -88,6 +91,7 @@ export const addImageDroppedListener = () => {
|
||||
controlNetId,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// set canvas image
|
||||
@ -97,6 +101,7 @@ export const addImageDroppedListener = () => {
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
dispatch(setInitialCanvasImage(activeData.payload.imageDTO));
|
||||
return;
|
||||
}
|
||||
|
||||
// set nodes image
|
||||
@ -113,6 +118,7 @@ export const addImageDroppedListener = () => {
|
||||
value: activeData.payload.imageDTO,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// set multiple nodes images (single image handler)
|
||||
@ -129,23 +135,25 @@ export const addImageDroppedListener = () => {
|
||||
value: [activeData.payload.imageDTO],
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// set multiple nodes images (multiple images handler)
|
||||
if (
|
||||
overData.actionType === 'SET_MULTI_NODES_IMAGE' &&
|
||||
activeData.payloadType === 'GALLERY_SELECTION'
|
||||
activeData.payloadType === 'IMAGE_NAMES'
|
||||
) {
|
||||
const { fieldName, nodeId } = overData.context;
|
||||
dispatch(
|
||||
imageCollectionFieldValueChanged({
|
||||
nodeId,
|
||||
fieldName,
|
||||
value: state.gallery.selection.map((image_name) => ({
|
||||
value: activeData.payload.image_names.map((image_name) => ({
|
||||
image_name,
|
||||
})),
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// add image to board
|
||||
@ -163,6 +171,7 @@ export const addImageDroppedListener = () => {
|
||||
board_id: boardId,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// remove image from board
|
||||
@ -176,68 +185,69 @@ export const addImageDroppedListener = () => {
|
||||
dispatch(
|
||||
boardImagesApi.endpoints.deleteBoardImage.initiate({ image_name })
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// add gallery selection to board
|
||||
if (
|
||||
overData.actionType === 'MOVE_BOARD' &&
|
||||
activeData.payloadType === 'GALLERY_SELECTION' &&
|
||||
activeData.payloadType === 'IMAGE_NAMES' &&
|
||||
overData.context.boardId
|
||||
) {
|
||||
console.log('adding gallery selection to board');
|
||||
const board_id = overData.context.boardId;
|
||||
const image_names = state.gallery.selection;
|
||||
dispatch(
|
||||
boardImagesApi.endpoints.addManyBoardImages.initiate({
|
||||
board_id,
|
||||
image_names,
|
||||
image_names: activeData.payload.image_names,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// remove gallery selection from board
|
||||
if (
|
||||
overData.actionType === 'MOVE_BOARD' &&
|
||||
activeData.payloadType === 'GALLERY_SELECTION' &&
|
||||
activeData.payloadType === 'IMAGE_NAMES' &&
|
||||
overData.context.boardId === null
|
||||
) {
|
||||
console.log('removing gallery selection to board');
|
||||
const image_names = state.gallery.selection;
|
||||
dispatch(
|
||||
boardImagesApi.endpoints.deleteManyBoardImages.initiate({
|
||||
image_names,
|
||||
image_names: activeData.payload.image_names,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// add batch selection to board
|
||||
if (
|
||||
overData.actionType === 'MOVE_BOARD' &&
|
||||
activeData.payloadType === 'BATCH_SELECTION' &&
|
||||
activeData.payloadType === 'IMAGE_NAMES' &&
|
||||
overData.context.boardId
|
||||
) {
|
||||
const board_id = overData.context.boardId;
|
||||
const image_names = state.batch.selection;
|
||||
dispatch(
|
||||
boardImagesApi.endpoints.addManyBoardImages.initiate({
|
||||
board_id,
|
||||
image_names,
|
||||
image_names: activeData.payload.image_names,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// remove batch selection from board
|
||||
if (
|
||||
overData.actionType === 'MOVE_BOARD' &&
|
||||
activeData.payloadType === 'BATCH_SELECTION' &&
|
||||
activeData.payloadType === 'IMAGE_NAMES' &&
|
||||
overData.context.boardId === null
|
||||
) {
|
||||
const image_names = state.batch.selection;
|
||||
dispatch(
|
||||
boardImagesApi.endpoints.deleteManyBoardImages.initiate({
|
||||
image_names,
|
||||
image_names: activeData.payload.image_names,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { Box, Spinner } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
@ -13,54 +13,57 @@ import {
|
||||
} from 'features/batch/store/batchSlice';
|
||||
import ImageContextMenu from 'features/gallery/components/ImageContextMenu';
|
||||
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
|
||||
const makeSelector = (image_name: string) =>
|
||||
createSelector(
|
||||
[stateSelector],
|
||||
(state) => ({
|
||||
selectionCount: state.batch.selection.length,
|
||||
selection: state.batch.selection,
|
||||
isSelected: state.batch.selection.includes(image_name),
|
||||
}),
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
type BatchImageProps = {
|
||||
imageDTO: ImageDTO;
|
||||
imageName: string;
|
||||
};
|
||||
|
||||
const BatchImage = (props: BatchImageProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { imageDTO } = props;
|
||||
const { image_name } = imageDTO;
|
||||
const { imageName } = props;
|
||||
|
||||
const selector = useMemo(() => makeSelector(image_name), [image_name]);
|
||||
const { currentData: imageDTO } = useGetImageDTOQuery(imageName);
|
||||
|
||||
const { isSelected, selectionCount } = useAppSelector(selector);
|
||||
const selector = useMemo(() => makeSelector(imageName), [imageName]);
|
||||
|
||||
const { isSelected, selectionCount, selection } = useAppSelector(selector);
|
||||
|
||||
const handleClickRemove = useCallback(() => {
|
||||
dispatch(imageRemovedFromBatch(image_name));
|
||||
}, [dispatch, image_name]);
|
||||
dispatch(imageRemovedFromBatch(imageName));
|
||||
}, [dispatch, imageName]);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
if (e.shiftKey) {
|
||||
dispatch(batchImageRangeEndSelected(image_name));
|
||||
dispatch(batchImageRangeEndSelected(imageName));
|
||||
} else if (e.ctrlKey || e.metaKey) {
|
||||
dispatch(batchImageSelectionToggled(image_name));
|
||||
dispatch(batchImageSelectionToggled(imageName));
|
||||
} else {
|
||||
dispatch(batchImageSelected(image_name));
|
||||
dispatch(batchImageSelected(imageName));
|
||||
}
|
||||
},
|
||||
[dispatch, image_name]
|
||||
[dispatch, imageName]
|
||||
);
|
||||
|
||||
const draggableData = useMemo<TypesafeDraggableData | undefined>(() => {
|
||||
if (selectionCount > 1) {
|
||||
return {
|
||||
id: 'batch',
|
||||
payloadType: 'BATCH_SELECTION',
|
||||
payloadType: 'IMAGE_NAMES',
|
||||
payload: { image_names: selection },
|
||||
};
|
||||
}
|
||||
|
||||
@ -71,15 +74,19 @@ const BatchImage = (props: BatchImageProps) => {
|
||||
payload: { imageDTO },
|
||||
};
|
||||
}
|
||||
}, [imageDTO, selectionCount]);
|
||||
}, [imageDTO, selection, selectionCount]);
|
||||
|
||||
if (!imageDTO) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
|
||||
<ImageContextMenu image={imageDTO}>
|
||||
<ImageContextMenu imageDTO={imageDTO}>
|
||||
{(ref) => (
|
||||
<Box
|
||||
position="relative"
|
||||
key={image_name}
|
||||
key={imageName}
|
||||
userSelect="none"
|
||||
ref={ref}
|
||||
sx={{
|
||||
|
@ -1,32 +1,22 @@
|
||||
import {
|
||||
PayloadAction,
|
||||
createEntityAdapter,
|
||||
createSlice,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { dateComparator } from 'common/util/dateComparator';
|
||||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
import { uniq } from 'lodash-es';
|
||||
import { imageDeleted } from 'services/api/thunks/image';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
|
||||
export const batchImagesAdapter = createEntityAdapter<ImageDTO>({
|
||||
selectId: (image) => image.image_name,
|
||||
sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at),
|
||||
});
|
||||
|
||||
type AdditionalBatchState = {
|
||||
type BatchState = {
|
||||
isEnabled: boolean;
|
||||
imageNames: string[];
|
||||
asInitialImage: boolean;
|
||||
controlNets: string[];
|
||||
selection: string[];
|
||||
};
|
||||
|
||||
export const initialBatchState =
|
||||
batchImagesAdapter.getInitialState<AdditionalBatchState>({
|
||||
isEnabled: false,
|
||||
asInitialImage: false,
|
||||
controlNets: [],
|
||||
selection: [],
|
||||
});
|
||||
export const initialBatchState: BatchState = {
|
||||
isEnabled: false,
|
||||
imageNames: [],
|
||||
asInitialImage: false,
|
||||
controlNets: [],
|
||||
selection: [],
|
||||
};
|
||||
|
||||
const batch = createSlice({
|
||||
name: 'batch',
|
||||
@ -35,20 +25,24 @@ const batch = createSlice({
|
||||
isEnabledChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.isEnabled = action.payload;
|
||||
},
|
||||
imageAddedToBatch: (state, action: PayloadAction<ImageDTO>) => {
|
||||
batchImagesAdapter.addOne(state, action.payload);
|
||||
imageAddedToBatch: (state, action: PayloadAction<string>) => {
|
||||
state.imageNames.push(action.payload);
|
||||
},
|
||||
imagesAddedToBatch: (state, action: PayloadAction<ImageDTO[]>) => {
|
||||
batchImagesAdapter.addMany(state, action.payload);
|
||||
imagesAddedToBatch: (state, action: PayloadAction<string[]>) => {
|
||||
state.imageNames = state.imageNames.concat(action.payload);
|
||||
},
|
||||
imageRemovedFromBatch: (state, action: PayloadAction<string>) => {
|
||||
batchImagesAdapter.removeOne(state, action.payload);
|
||||
state.imageNames = state.imageNames.filter(
|
||||
(imageName) => action.payload !== imageName
|
||||
);
|
||||
state.selection = state.selection.filter(
|
||||
(imageName) => action.payload !== imageName
|
||||
);
|
||||
},
|
||||
imagesRemovedFromBatch: (state, action: PayloadAction<string[]>) => {
|
||||
batchImagesAdapter.removeMany(state, action.payload);
|
||||
state.imageNames = state.imageNames.filter(
|
||||
(imageName) => !action.payload.includes(imageName)
|
||||
);
|
||||
state.selection = state.selection.filter(
|
||||
(imageName) => !action.payload.includes(imageName)
|
||||
);
|
||||
@ -57,21 +51,20 @@ const batch = createSlice({
|
||||
const rangeEndImageName = action.payload;
|
||||
const lastSelectedImage = state.selection[state.selection.length - 1];
|
||||
|
||||
const images = batchImagesAdapter.getSelectors().selectAll(state);
|
||||
const lastClickedIndex = images.findIndex(
|
||||
(n) => n.image_name === lastSelectedImage
|
||||
const { imageNames } = state;
|
||||
|
||||
const lastClickedIndex = imageNames.findIndex(
|
||||
(n) => n === lastSelectedImage
|
||||
);
|
||||
const currentClickedIndex = images.findIndex(
|
||||
(n) => n.image_name === rangeEndImageName
|
||||
const currentClickedIndex = imageNames.findIndex(
|
||||
(n) => n === rangeEndImageName
|
||||
);
|
||||
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
|
||||
// We have a valid range!
|
||||
const start = Math.min(lastClickedIndex, currentClickedIndex);
|
||||
const end = Math.max(lastClickedIndex, currentClickedIndex);
|
||||
|
||||
const imagesToSelect = images
|
||||
.slice(start, end + 1)
|
||||
.map((i) => i.image_name);
|
||||
const imagesToSelect = imageNames.slice(start, end + 1);
|
||||
|
||||
state.selection = uniq(state.selection.concat(imagesToSelect));
|
||||
}
|
||||
@ -91,10 +84,10 @@ const batch = createSlice({
|
||||
batchImageSelected: (state, action: PayloadAction<string | null>) => {
|
||||
state.selection = action.payload
|
||||
? [action.payload]
|
||||
: [String(state.ids[0])];
|
||||
: [String(state.imageNames[0])];
|
||||
},
|
||||
batchReset: (state) => {
|
||||
batchImagesAdapter.removeAll(state);
|
||||
state.imageNames = [];
|
||||
state.selection = [];
|
||||
},
|
||||
asInitialImageToggled: (state) => {
|
||||
@ -120,7 +113,9 @@ const batch = createSlice({
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(imageDeleted.fulfilled, (state, action) => {
|
||||
batchImagesAdapter.removeOne(state, action.meta.arg.image_name);
|
||||
state.imageNames = state.imageNames.filter(
|
||||
(imageName) => imageName !== action.meta.arg.image_name
|
||||
);
|
||||
state.selection = state.selection.filter(
|
||||
(imageName) => imageName !== action.meta.arg.image_name
|
||||
);
|
||||
|
@ -11,7 +11,6 @@ import { stateSelector } from 'app/store/store';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import BatchImage from 'features/batch/components/BatchImage';
|
||||
import { batchImagesAdapter } from 'features/batch/store/batchSlice';
|
||||
import { VirtuosoGrid } from 'react-virtuoso';
|
||||
import ItemContainer from './ItemContainer';
|
||||
import ListContainer from './ListContainer';
|
||||
@ -19,9 +18,8 @@ import ListContainer from './ListContainer';
|
||||
const selector = createSelector(
|
||||
[stateSelector],
|
||||
(state) => {
|
||||
const images = batchImagesAdapter.getSelectors().selectAll(state.batch);
|
||||
return {
|
||||
images,
|
||||
imageNames: state.batch.imageNames,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
@ -45,7 +43,7 @@ const BatchGrid = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { images } = useAppSelector(selector);
|
||||
const { imageNames } = useAppSelector(selector);
|
||||
|
||||
useEffect(() => {
|
||||
const { current: root } = rootRef;
|
||||
@ -60,22 +58,19 @@ const BatchGrid = () => {
|
||||
return () => osInstance()?.destroy();
|
||||
}, [scroller, initialize, osInstance]);
|
||||
|
||||
if (images.length) {
|
||||
if (imageNames.length) {
|
||||
return (
|
||||
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||
<VirtuosoGrid
|
||||
style={{ height: '100%' }}
|
||||
data={images}
|
||||
data={imageNames}
|
||||
components={{
|
||||
Item: ItemContainer,
|
||||
List: ListContainer,
|
||||
}}
|
||||
scrollerRef={setScroller}
|
||||
itemContent={(index, item) => (
|
||||
<BatchImage
|
||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||
imageDTO={item}
|
||||
/>
|
||||
itemContent={(index, imageName) => (
|
||||
<BatchImage key={imageName} imageName={imageName} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
|
@ -10,7 +10,7 @@ import GenericBoard from './GenericBoard';
|
||||
|
||||
const selector = createSelector(stateSelector, (state) => {
|
||||
return {
|
||||
count: state.batch.ids.length,
|
||||
count: state.batch.imageNames.length,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -17,6 +17,7 @@ export const makeSelector = (image_name: string) =>
|
||||
({ gallery }) => ({
|
||||
isSelected: gallery.selection.includes(image_name),
|
||||
selectionCount: gallery.selection.length,
|
||||
selection: gallery.selection,
|
||||
}),
|
||||
defaultSelectorOptions
|
||||
);
|
||||
@ -33,7 +34,8 @@ const GalleryImage = (props: HoverableImageProps) => {
|
||||
|
||||
const localSelector = useMemo(() => makeSelector(image_name), [image_name]);
|
||||
|
||||
const { isSelected, selectionCount } = useAppSelector(localSelector);
|
||||
const { isSelected, selectionCount, selection } =
|
||||
useAppSelector(localSelector);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
@ -65,7 +67,8 @@ const GalleryImage = (props: HoverableImageProps) => {
|
||||
if (selectionCount > 1) {
|
||||
return {
|
||||
id: 'gallery-image',
|
||||
payloadType: 'GALLERY_SELECTION',
|
||||
payloadType: 'IMAGE_NAMES',
|
||||
payload: { image_names: selection },
|
||||
};
|
||||
}
|
||||
|
||||
@ -76,11 +79,11 @@ const GalleryImage = (props: HoverableImageProps) => {
|
||||
payload: { imageDTO },
|
||||
};
|
||||
}
|
||||
}, [imageDTO, selectionCount]);
|
||||
}, [imageDTO, selection, selectionCount]);
|
||||
|
||||
return (
|
||||
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
|
||||
<ImageContextMenu image={imageDTO}>
|
||||
<ImageContextMenu imageDTO={imageDTO}>
|
||||
{(ref) => (
|
||||
<Box
|
||||
position="relative"
|
||||
|
@ -40,11 +40,11 @@ import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardCon
|
||||
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
||||
|
||||
type Props = {
|
||||
image: ImageDTO;
|
||||
imageDTO: ImageDTO;
|
||||
children: ContextMenuProps<HTMLDivElement>['children'];
|
||||
};
|
||||
|
||||
const ImageContextMenu = ({ image, children }: Props) => {
|
||||
const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
@ -53,13 +53,13 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
const isBatch = gallery.selectedBoardId === 'batch';
|
||||
|
||||
const selection = isBatch ? batch.selection : gallery.selection;
|
||||
const isInBatch = batch.ids.includes(image.image_name);
|
||||
const isInBatch = batch.imageNames.includes(imageDTO.image_name);
|
||||
|
||||
return { selection, isInBatch };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
),
|
||||
[image.image_name]
|
||||
[imageDTO.image_name]
|
||||
);
|
||||
const { selection, isInBatch } = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
@ -73,11 +73,11 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
if (!image) {
|
||||
if (!imageDTO) {
|
||||
return;
|
||||
}
|
||||
dispatch(imageToDeleteSelected(image));
|
||||
}, [dispatch, image]);
|
||||
dispatch(imageToDeleteSelected(imageDTO));
|
||||
}, [dispatch, imageDTO]);
|
||||
|
||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||
useRecallParameters();
|
||||
@ -89,23 +89,23 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
// Recall parameters handlers
|
||||
const handleRecallPrompt = useCallback(() => {
|
||||
recallBothPrompts(
|
||||
image.metadata?.positive_conditioning,
|
||||
image.metadata?.negative_conditioning
|
||||
imageDTO.metadata?.positive_conditioning,
|
||||
imageDTO.metadata?.negative_conditioning
|
||||
);
|
||||
}, [
|
||||
image.metadata?.negative_conditioning,
|
||||
image.metadata?.positive_conditioning,
|
||||
imageDTO.metadata?.negative_conditioning,
|
||||
imageDTO.metadata?.positive_conditioning,
|
||||
recallBothPrompts,
|
||||
]);
|
||||
|
||||
const handleRecallSeed = useCallback(() => {
|
||||
recallSeed(image.metadata?.seed);
|
||||
}, [image, recallSeed]);
|
||||
recallSeed(imageDTO.metadata?.seed);
|
||||
}, [imageDTO, recallSeed]);
|
||||
|
||||
const handleSendToImageToImage = useCallback(() => {
|
||||
dispatch(sentImageToImg2Img());
|
||||
dispatch(initialImageSelected(image));
|
||||
}, [dispatch, image]);
|
||||
dispatch(initialImageSelected(imageDTO));
|
||||
}, [dispatch, imageDTO]);
|
||||
|
||||
// const handleRecallInitialImage = useCallback(() => {
|
||||
// recallInitialImage(image.metadata.invokeai?.node?.image);
|
||||
@ -113,7 +113,7 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
|
||||
const handleSendToCanvas = () => {
|
||||
dispatch(sentImageToCanvas());
|
||||
dispatch(setInitialCanvasImage(image));
|
||||
dispatch(setInitialCanvasImage(imageDTO));
|
||||
dispatch(resizeAndScaleCanvas());
|
||||
dispatch(setActiveTab('unifiedCanvas'));
|
||||
|
||||
@ -126,8 +126,8 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
};
|
||||
|
||||
const handleUseAllParameters = useCallback(() => {
|
||||
recallAllParameters(image);
|
||||
}, [image, recallAllParameters]);
|
||||
recallAllParameters(imageDTO);
|
||||
}, [imageDTO, recallAllParameters]);
|
||||
|
||||
const handleLightBox = () => {
|
||||
// dispatch(setCurrentImage(image));
|
||||
@ -135,39 +135,43 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
};
|
||||
|
||||
const handleAddToBoard = useCallback(() => {
|
||||
onClickAddToBoard(image);
|
||||
}, [image, onClickAddToBoard]);
|
||||
onClickAddToBoard(imageDTO);
|
||||
}, [imageDTO, onClickAddToBoard]);
|
||||
|
||||
const handleRemoveFromBoard = useCallback(() => {
|
||||
if (!image.board_id) {
|
||||
if (!imageDTO.board_id) {
|
||||
return;
|
||||
}
|
||||
deleteBoardImage({ image_name: image.image_name });
|
||||
}, [deleteBoardImage, image.board_id, image.image_name]);
|
||||
deleteBoardImage({ image_name: imageDTO.image_name });
|
||||
}, [deleteBoardImage, imageDTO.board_id, imageDTO.image_name]);
|
||||
|
||||
const handleAddSelectionToBoard = useCallback(() => {
|
||||
addManyBoardImages({ board_id, image_names: selection });
|
||||
}, [addManyBoardImages, selection]);
|
||||
// addManyBoardImages({ board_id, image_names: selection });
|
||||
}, []);
|
||||
|
||||
const handleRemoveSelectionFromBoard = useCallback(() => {
|
||||
deleteManyBoardImages({ image_names: selection });
|
||||
}, [deleteManyBoardImages, selection]);
|
||||
|
||||
const handleOpenInNewTab = useCallback(() => {
|
||||
window.open(image.image_url, '_blank');
|
||||
}, [image.image_url]);
|
||||
window.open(imageDTO.image_url, '_blank');
|
||||
}, [imageDTO.image_url]);
|
||||
|
||||
const handleAddSelectionToBatch = useCallback(() => {
|
||||
dispatch(selectionAddedToBatch({ images_names: selection }));
|
||||
}, [dispatch, selection]);
|
||||
|
||||
const handleAddToBatch = useCallback(() => {
|
||||
dispatch(imageAddedToBatch(image));
|
||||
}, [dispatch, image]);
|
||||
dispatch(imageAddedToBatch(imageDTO.image_name));
|
||||
}, [dispatch, imageDTO]);
|
||||
|
||||
const handleRemoveFromBatch = useCallback(() => {
|
||||
dispatch(imageRemovedFromBatch(image.image_name));
|
||||
}, [dispatch, image]);
|
||||
dispatch(imageRemovedFromBatch(imageDTO.image_name));
|
||||
}, [dispatch, imageDTO]);
|
||||
|
||||
if (!imageDTO) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenu<HTMLDivElement>
|
||||
@ -191,7 +195,7 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallPrompt}
|
||||
isDisabled={
|
||||
image?.metadata?.positive_conditioning === undefined
|
||||
imageDTO?.metadata?.positive_conditioning === undefined
|
||||
}
|
||||
>
|
||||
{t('parameters.usePrompt')}
|
||||
@ -200,7 +204,7 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
<MenuItem
|
||||
icon={<IoArrowUndoCircleOutline />}
|
||||
onClickCapture={handleRecallSeed}
|
||||
isDisabled={image?.metadata?.seed === undefined}
|
||||
isDisabled={imageDTO?.metadata?.seed === undefined}
|
||||
>
|
||||
{t('parameters.useSeed')}
|
||||
</MenuItem>
|
||||
@ -210,7 +214,7 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
isDisabled={
|
||||
// what should these be
|
||||
!['t2l', 'l2l', 'inpaint'].includes(
|
||||
String(image?.metadata?.type)
|
||||
String(imageDTO?.metadata?.type)
|
||||
)
|
||||
}
|
||||
>
|
||||
@ -241,9 +245,9 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
||||
{isInBatch ? 'Remove from Batch' : 'Add to Batch'}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
||||
{image.board_id ? 'Change Board' : 'Add to Board'}
|
||||
{imageDTO.board_id ? 'Change Board' : 'Add to Board'}
|
||||
</MenuItem>
|
||||
{image.board_id && (
|
||||
{imageDTO.board_id && (
|
||||
<MenuItem
|
||||
icon={<FaFolder />}
|
||||
onClickCapture={handleRemoveFromBoard}
|
||||
|
@ -27,7 +27,7 @@ export const ASSETS_CATEGORIES: ImageCategory[] = [
|
||||
export const INITIAL_IMAGE_LIMIT = 100;
|
||||
export const IMAGE_LIMIT = 20;
|
||||
|
||||
type AdditionaGalleryState = {
|
||||
type AdditionalGalleryState = {
|
||||
offset: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
@ -43,7 +43,7 @@ type AdditionaGalleryState = {
|
||||
};
|
||||
|
||||
export const initialGalleryState =
|
||||
galleryImagesAdapter.getInitialState<AdditionaGalleryState>({
|
||||
galleryImagesAdapter.getInitialState<AdditionalGalleryState>({
|
||||
offset: 0,
|
||||
limit: 0,
|
||||
total: 0,
|
||||
|
Loading…
Reference in New Issue
Block a user