From 0b5f4cac57d3939f0c600295a9d30df402005098 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Wed, 7 Aug 2024 18:37:43 +1000
Subject: [PATCH] feat(ui): dnd image into layer
---
.../listeners/imageDropped.ts | 10 ++---
.../components/CanvasDropArea.tsx | 19 +++++++++
.../components/ControlLayersEditor.tsx | 2 +
.../controlLayers/components/Layer/Layer.tsx | 4 +-
.../controlLayers/konva/CanvasManager.ts | 12 +++---
.../controlLayers/store/canvasV2Slice.ts | 2 +-
.../controlLayers/store/layersReducers.ts | 42 ++++++++++---------
.../web/src/features/dnd/types/index.ts | 9 ++--
.../web/src/features/dnd/util/isValidDrop.ts | 2 +-
.../ui/components/tabs/TextToImageTab.tsx | 8 +++-
10 files changed, 68 insertions(+), 42 deletions(-)
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx
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 8368e42ec3..c551fa88b7 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
@@ -5,9 +5,10 @@ import { parseify } from 'common/util/serialize';
import {
caImageChanged,
ipaImageChanged,
- layerImageAdded,
+ layerAddedFromImage,
rgIPAdapterImageChanged,
} from 'features/controlLayers/store/canvasV2Slice';
+import { imageDTOToImageObject } from 'features/controlLayers/store/types';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { isValidDrop } from 'features/dnd/util/isValidDrop';
import {
@@ -28,7 +29,7 @@ export const dndDropped = createAction<{
export const addImageDroppedListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: dndDropped,
- effect: async (action, { dispatch, getState }) => {
+ effect: (action, { dispatch, getState }) => {
const log = logger('dnd');
const { activeData, overData } = action.payload;
if (!isValidDrop(overData, activeData)) {
@@ -101,12 +102,11 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
* Image dropped on Raster layer
*/
if (
- overData.actionType === 'ADD_LAYER_IMAGE' &&
+ overData.actionType === 'ADD_LAYER_FROM_IMAGE' &&
activeData.payloadType === 'IMAGE_DTO' &&
activeData.payload.imageDTO
) {
- const { id } = overData.context;
- dispatch(layerImageAdded({ id, imageDTO: activeData.payload.imageDTO }));
+ dispatch(layerAddedFromImage({ imageObject: imageDTOToImageObject(activeData.payload.imageDTO) }));
return;
}
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx
new file mode 100644
index 0000000000..b5a1c636a5
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx
@@ -0,0 +1,19 @@
+import { Flex } from '@invoke-ai/ui-library';
+import IAIDroppable from 'common/components/IAIDroppable';
+import type { AddLayerFromImageDropData } from 'features/dnd/types';
+import { memo } from 'react';
+
+const addLayerFromImageDropData: AddLayerFromImageDropData = {
+ id: 'add-layer-from-image-drop-data',
+ actionType: 'ADD_LAYER_FROM_IMAGE',
+};
+
+export const CanvasDropArea = memo(() => {
+ return (
+
+
+
+ );
+});
+
+CanvasDropArea.displayName = 'CanvasDropArea';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx
index a2a4bf9126..71e35263b5 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersEditor.tsx
@@ -1,5 +1,6 @@
/* eslint-disable i18next/no-literal-string */
import { Flex } from '@invoke-ai/ui-library';
+import { CanvasDropArea } from 'features/controlLayers/components/CanvasDropArea';
import { ControlLayersToolbar } from 'features/controlLayers/components/ControlLayersToolbar';
import { StageComponent } from 'features/controlLayers/components/StageComponent';
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
@@ -24,6 +25,7 @@ export const ControlLayersEditor = memo(() => {
{/*
*/}
+
);
});
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx
index ffc6ce9b74..d8f73cbaaa 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx
@@ -9,7 +9,7 @@ import { LayerActionsMenu } from 'features/controlLayers/components/Layer/LayerA
import { LayerSettings } from 'features/controlLayers/components/Layer/LayerSettings';
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
-import type { LayerImageDropData } from 'features/dnd/types';
+import type { AddLayerFromImageDropData } from 'features/dnd/types';
import { memo, useMemo } from 'react';
import { LayerOpacity } from './LayerOpacity';
@@ -21,7 +21,7 @@ type Props = {
export const Layer = memo(({ id }: Props) => {
const entityIdentifier = useMemo(() => ({ id, type: 'layer' }), [id]);
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
- const droppableData = useMemo(
+ const droppableData = useMemo(
() => ({ id, actionType: 'ADD_LAYER_IMAGE', context: { id } }),
[id]
);
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
index 2d91a78577..722c6ce470 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
@@ -97,12 +97,12 @@ type EntityStateAndAdapter =
state: CanvasInpaintMaskState;
adapter: CanvasMaskAdapter;
}
- | {
- id: string;
- type: CanvasControlAdapterState['type'];
- state: CanvasControlAdapterState;
- adapter: CanvasControlAdapter;
- }
+ // | {
+ // id: string;
+ // type: CanvasControlAdapterState['type'];
+ // state: CanvasControlAdapterState;
+ // adapter: CanvasControlAdapter;
+ // }
| {
id: string;
type: CanvasRegionalGuidanceState['type'];
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts
index 4159d9f7da..ca37dc9517 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts
@@ -411,9 +411,9 @@ export const {
bboxSizeOptimized,
// layers
layerAdded,
+ layerAddedFromImage,
layerRecalled,
layerOpacityChanged,
- layerImageAdded,
layerAllDeleted,
layerImageCacheChanged,
// IP Adapters
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts
index 78be3cf6f1..48e01cd1f3 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts
@@ -4,8 +4,8 @@ import { merge } from 'lodash-es';
import type { ImageDTO } from 'services/api/types';
import { assert } from 'tsafe';
-import type { CanvasLayerState, CanvasV2State, ImageObjectAddedArg } from './types';
-import { imageDTOToImageObject, imageDTOToImageWithDims } from './types';
+import type { CanvasImageState, CanvasLayerState, CanvasV2State } from './types';
+import { imageDTOToImageWithDims } from './types';
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
@@ -25,6 +25,7 @@ export const layersReducers = {
objects: [],
opacity: 1,
position: { x: 0, y: 0 },
+ imageCache: null,
};
merge(layer, action.payload.overrides);
state.layers.entities.push(layer);
@@ -41,6 +42,26 @@ export const layersReducers = {
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
state.layers.imageCache = null;
},
+ layerAddedFromImage: {
+ reducer: (state, action: PayloadAction<{ id: string; imageObject: CanvasImageState }>) => {
+ const { id, imageObject } = action.payload;
+ const layer: CanvasLayerState = {
+ id,
+ type: 'layer',
+ isEnabled: true,
+ objects: [imageObject],
+ opacity: 1,
+ position: { x: 0, y: 0 },
+ imageCache: null,
+ };
+ state.layers.entities.push(layer);
+ state.selectedEntityIdentifier = { type: 'layer', id };
+ state.layers.imageCache = null;
+ },
+ prepare: (payload: { imageObject: CanvasImageState }) => ({
+ payload: { ...payload, id: getPrefixedId('layer') },
+ }),
+ },
layerAllDeleted: (state) => {
state.layers.entities = [];
state.layers.imageCache = null;
@@ -54,23 +75,6 @@ export const layersReducers = {
layer.opacity = opacity;
state.layers.imageCache = null;
},
- layerImageAdded: (
- state,
- action: PayloadAction
- ) => {
- const { id, imageDTO, pos } = action.payload;
- const layer = selectLayer(state, id);
- if (!layer) {
- return;
- }
- const imageObject = imageDTOToImageObject(imageDTO);
- if (pos) {
- imageObject.x = pos.x;
- imageObject.y = pos.y;
- }
- layer.objects.push(imageObject);
- state.layers.imageCache = null;
- },
layerImageCacheChanged: (state, action: PayloadAction<{ imageDTO: ImageDTO | null }>) => {
const { imageDTO } = action.payload;
state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
diff --git a/invokeai/frontend/web/src/features/dnd/types/index.ts b/invokeai/frontend/web/src/features/dnd/types/index.ts
index 19dcbf19c0..43e2ca36f2 100644
--- a/invokeai/frontend/web/src/features/dnd/types/index.ts
+++ b/invokeai/frontend/web/src/features/dnd/types/index.ts
@@ -44,11 +44,8 @@ export type RGIPAdapterImageDropData = BaseDropData & {
};
};
-export type LayerImageDropData = BaseDropData & {
- actionType: 'ADD_LAYER_IMAGE';
- context: {
- id: string;
- };
+export type AddLayerFromImageDropData = BaseDropData & {
+ actionType: 'ADD_LAYER_FROM_IMAGE';
};
type UpscaleInitialImageDropData = BaseDropData & {
@@ -94,7 +91,7 @@ export type TypesafeDroppableData =
| RGIPAdapterImageDropData
| SelectForCompareDropData
| UpscaleInitialImageDropData
- | LayerImageDropData;
+ | AddLayerFromImageDropData;
type BaseDragData = {
id: string;
diff --git a/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts b/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts
index 128e5c5d50..0de8d48a94 100644
--- a/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts
+++ b/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts
@@ -21,7 +21,7 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData?
return payloadType === 'IMAGE_DTO';
case 'SET_RG_IP_ADAPTER_IMAGE':
return payloadType === 'IMAGE_DTO';
- case 'ADD_LAYER_IMAGE':
+ case 'ADD_LAYER_FROM_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SET_UPSCALE_INITIAL_IMAGE':
return payloadType === 'IMAGE_DTO';
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx
index 5be0195046..736c5559e0 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImageTab.tsx
@@ -10,8 +10,12 @@ const TextToImageTab = () => {
return (
- {imageViewer.isOpen && }
-
+ {imageViewer.isOpen && (
+ <>
+
+
+ >
+ )}
);
};