mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): dnd image into layer
This commit is contained in:
parent
c988c58c63
commit
0b5f4cac57
@ -5,9 +5,10 @@ import { parseify } from 'common/util/serialize';
|
|||||||
import {
|
import {
|
||||||
caImageChanged,
|
caImageChanged,
|
||||||
ipaImageChanged,
|
ipaImageChanged,
|
||||||
layerImageAdded,
|
layerAddedFromImage,
|
||||||
rgIPAdapterImageChanged,
|
rgIPAdapterImageChanged,
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
||||||
import {
|
import {
|
||||||
@ -28,7 +29,7 @@ export const dndDropped = createAction<{
|
|||||||
export const addImageDroppedListener = (startAppListening: AppStartListening) => {
|
export const addImageDroppedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: dndDropped,
|
actionCreator: dndDropped,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const log = logger('dnd');
|
const log = logger('dnd');
|
||||||
const { activeData, overData } = action.payload;
|
const { activeData, overData } = action.payload;
|
||||||
if (!isValidDrop(overData, activeData)) {
|
if (!isValidDrop(overData, activeData)) {
|
||||||
@ -101,12 +102,11 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
* Image dropped on Raster layer
|
* Image dropped on Raster layer
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'ADD_LAYER_IMAGE' &&
|
overData.actionType === 'ADD_LAYER_FROM_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
const { id } = overData.context;
|
dispatch(layerAddedFromImage({ imageObject: imageDTOToImageObject(activeData.payload.imageDTO) }));
|
||||||
dispatch(layerImageAdded({ id, imageDTO: activeData.payload.imageDTO }));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
|
<Flex position="absolute" top={0} right={0} bottom={0} left={0} gap={2} pointerEvents="none">
|
||||||
|
<IAIDroppable dropLabel="Create Layer" data={addLayerFromImageDropData} />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CanvasDropArea.displayName = 'CanvasDropArea';
|
@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable i18next/no-literal-string */
|
/* eslint-disable i18next/no-literal-string */
|
||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
|
import { CanvasDropArea } from 'features/controlLayers/components/CanvasDropArea';
|
||||||
import { ControlLayersToolbar } from 'features/controlLayers/components/ControlLayersToolbar';
|
import { ControlLayersToolbar } from 'features/controlLayers/components/ControlLayersToolbar';
|
||||||
import { StageComponent } from 'features/controlLayers/components/StageComponent';
|
import { StageComponent } from 'features/controlLayers/components/StageComponent';
|
||||||
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
||||||
@ -24,6 +25,7 @@ export const ControlLayersEditor = memo(() => {
|
|||||||
{/* <Flex position="absolute" top={0} right={0} bottom={0} left={0} align="center" justify="center">
|
{/* <Flex position="absolute" top={0} right={0} bottom={0} left={0} align="center" justify="center">
|
||||||
<CanvasResizer />
|
<CanvasResizer />
|
||||||
</Flex> */}
|
</Flex> */}
|
||||||
|
<CanvasDropArea />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -9,7 +9,7 @@ import { LayerActionsMenu } from 'features/controlLayers/components/Layer/LayerA
|
|||||||
import { LayerSettings } from 'features/controlLayers/components/Layer/LayerSettings';
|
import { LayerSettings } from 'features/controlLayers/components/Layer/LayerSettings';
|
||||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
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 { memo, useMemo } from 'react';
|
||||||
|
|
||||||
import { LayerOpacity } from './LayerOpacity';
|
import { LayerOpacity } from './LayerOpacity';
|
||||||
@ -21,7 +21,7 @@ type Props = {
|
|||||||
export const Layer = memo(({ id }: Props) => {
|
export const Layer = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'layer' }), [id]);
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'layer' }), [id]);
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
|
||||||
const droppableData = useMemo<LayerImageDropData>(
|
const droppableData = useMemo<AddLayerFromImageDropData>(
|
||||||
() => ({ id, actionType: 'ADD_LAYER_IMAGE', context: { id } }),
|
() => ({ id, actionType: 'ADD_LAYER_IMAGE', context: { id } }),
|
||||||
[id]
|
[id]
|
||||||
);
|
);
|
||||||
|
@ -97,12 +97,12 @@ type EntityStateAndAdapter =
|
|||||||
state: CanvasInpaintMaskState;
|
state: CanvasInpaintMaskState;
|
||||||
adapter: CanvasMaskAdapter;
|
adapter: CanvasMaskAdapter;
|
||||||
}
|
}
|
||||||
| {
|
// | {
|
||||||
id: string;
|
// id: string;
|
||||||
type: CanvasControlAdapterState['type'];
|
// type: CanvasControlAdapterState['type'];
|
||||||
state: CanvasControlAdapterState;
|
// state: CanvasControlAdapterState;
|
||||||
adapter: CanvasControlAdapter;
|
// adapter: CanvasControlAdapter;
|
||||||
}
|
// }
|
||||||
| {
|
| {
|
||||||
id: string;
|
id: string;
|
||||||
type: CanvasRegionalGuidanceState['type'];
|
type: CanvasRegionalGuidanceState['type'];
|
||||||
|
@ -411,9 +411,9 @@ export const {
|
|||||||
bboxSizeOptimized,
|
bboxSizeOptimized,
|
||||||
// layers
|
// layers
|
||||||
layerAdded,
|
layerAdded,
|
||||||
|
layerAddedFromImage,
|
||||||
layerRecalled,
|
layerRecalled,
|
||||||
layerOpacityChanged,
|
layerOpacityChanged,
|
||||||
layerImageAdded,
|
|
||||||
layerAllDeleted,
|
layerAllDeleted,
|
||||||
layerImageCacheChanged,
|
layerImageCacheChanged,
|
||||||
// IP Adapters
|
// IP Adapters
|
||||||
|
@ -4,8 +4,8 @@ import { merge } from 'lodash-es';
|
|||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
import type { CanvasLayerState, CanvasV2State, ImageObjectAddedArg } from './types';
|
import type { CanvasImageState, CanvasLayerState, CanvasV2State } from './types';
|
||||||
import { imageDTOToImageObject, imageDTOToImageWithDims } from './types';
|
import { imageDTOToImageWithDims } from './types';
|
||||||
|
|
||||||
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
|
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
|
||||||
export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
|
export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
|
||||||
@ -25,6 +25,7 @@ export const layersReducers = {
|
|||||||
objects: [],
|
objects: [],
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
|
imageCache: null,
|
||||||
};
|
};
|
||||||
merge(layer, action.payload.overrides);
|
merge(layer, action.payload.overrides);
|
||||||
state.layers.entities.push(layer);
|
state.layers.entities.push(layer);
|
||||||
@ -41,6 +42,26 @@ export const layersReducers = {
|
|||||||
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
|
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
|
||||||
state.layers.imageCache = null;
|
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) => {
|
layerAllDeleted: (state) => {
|
||||||
state.layers.entities = [];
|
state.layers.entities = [];
|
||||||
state.layers.imageCache = null;
|
state.layers.imageCache = null;
|
||||||
@ -54,23 +75,6 @@ export const layersReducers = {
|
|||||||
layer.opacity = opacity;
|
layer.opacity = opacity;
|
||||||
state.layers.imageCache = null;
|
state.layers.imageCache = null;
|
||||||
},
|
},
|
||||||
layerImageAdded: (
|
|
||||||
state,
|
|
||||||
action: PayloadAction<ImageObjectAddedArg & { objectId: string; pos?: { x: number; y: number } }>
|
|
||||||
) => {
|
|
||||||
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 }>) => {
|
layerImageCacheChanged: (state, action: PayloadAction<{ imageDTO: ImageDTO | null }>) => {
|
||||||
const { imageDTO } = action.payload;
|
const { imageDTO } = action.payload;
|
||||||
state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
|
@ -44,11 +44,8 @@ export type RGIPAdapterImageDropData = BaseDropData & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LayerImageDropData = BaseDropData & {
|
export type AddLayerFromImageDropData = BaseDropData & {
|
||||||
actionType: 'ADD_LAYER_IMAGE';
|
actionType: 'ADD_LAYER_FROM_IMAGE';
|
||||||
context: {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type UpscaleInitialImageDropData = BaseDropData & {
|
type UpscaleInitialImageDropData = BaseDropData & {
|
||||||
@ -94,7 +91,7 @@ export type TypesafeDroppableData =
|
|||||||
| RGIPAdapterImageDropData
|
| RGIPAdapterImageDropData
|
||||||
| SelectForCompareDropData
|
| SelectForCompareDropData
|
||||||
| UpscaleInitialImageDropData
|
| UpscaleInitialImageDropData
|
||||||
| LayerImageDropData;
|
| AddLayerFromImageDropData;
|
||||||
|
|
||||||
type BaseDragData = {
|
type BaseDragData = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -21,7 +21,7 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData?
|
|||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'SET_RG_IP_ADAPTER_IMAGE':
|
case 'SET_RG_IP_ADAPTER_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'ADD_LAYER_IMAGE':
|
case 'ADD_LAYER_FROM_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'SET_UPSCALE_INITIAL_IMAGE':
|
case 'SET_UPSCALE_INITIAL_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
|
@ -10,8 +10,12 @@ const TextToImageTab = () => {
|
|||||||
return (
|
return (
|
||||||
<Box layerStyle="first" position="relative" w="full" h="full" p={2} borderRadius="base">
|
<Box layerStyle="first" position="relative" w="full" h="full" p={2} borderRadius="base">
|
||||||
<ControlLayersEditor />
|
<ControlLayersEditor />
|
||||||
{imageViewer.isOpen && <ImageViewer />}
|
{imageViewer.isOpen && (
|
||||||
<ImageComparisonDroppable />
|
<>
|
||||||
|
<ImageViewer />
|
||||||
|
<ImageComparisonDroppable />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user