feat(ui): add initial image layer to CL

This commit is contained in:
psychedelicious 2024-05-02 21:12:58 +10:00 committed by Kent Keirsey
parent d67480d92c
commit 1d213067e8
16 changed files with 335 additions and 10 deletions

View File

@ -1543,6 +1543,8 @@
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)", "globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
"globalIPAdapter": "Global $t(common.ipAdapter)", "globalIPAdapter": "Global $t(common.ipAdapter)",
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)", "globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
"globalInitialImage": "Global Initial Image",
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
"opacityFilter": "Opacity Filter", "opacityFilter": "Opacity Filter",
"clearProcessor": "Clear Processor", "clearProcessor": "Clear Processor",
"resetProcessor": "Reset Processor to Defaults" "resetProcessor": "Reset Processor to Defaults"

View File

@ -9,6 +9,7 @@ import {
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { import {
caLayerImageChanged, caLayerImageChanged,
iiLayerImageChanged,
ipaLayerImageChanged, ipaLayerImageChanged,
rgLayerIPAdapterImageChanged, rgLayerIPAdapterImageChanged,
} from 'features/controlLayers/store/controlLayersSlice'; } from 'features/controlLayers/store/controlLayersSlice';
@ -143,6 +144,24 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
return; return;
} }
/**
* Image dropped on II Layer Image
*/
if (
overData.actionType === 'SET_II_LAYER_IMAGE' &&
activeData.payloadType === 'IMAGE_DTO' &&
activeData.payload.imageDTO
) {
const { layerId } = overData.context;
dispatch(
iiLayerImageChanged({
layerId,
imageDTO: activeData.payload.imageDTO,
})
);
return;
}
/** /**
* Image dropped on Canvas * Image dropped on Canvas
*/ */

View File

@ -8,6 +8,7 @@ import {
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { import {
caLayerImageChanged, caLayerImageChanged,
iiLayerImageChanged,
ipaLayerImageChanged, ipaLayerImageChanged,
rgLayerIPAdapterImageChanged, rgLayerIPAdapterImageChanged,
} from 'features/controlLayers/store/controlLayersSlice'; } from 'features/controlLayers/store/controlLayersSlice';
@ -146,6 +147,17 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
); );
} }
if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') {
const { layerId } = postUploadAction;
dispatch(iiLayerImageChanged({ layerId, imageDTO }));
dispatch(
addToast({
...DEFAULT_UPLOADED_TOAST,
description: t('toast.setControlImage'),
})
);
}
if (postUploadAction?.type === 'SET_INITIAL_IMAGE') { if (postUploadAction?.type === 'SET_INITIAL_IMAGE') {
dispatch(initialImageChanged(imageDTO)); dispatch(initialImageChanged(imageDTO));
dispatch( dispatch(

View File

@ -16,7 +16,6 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import i18n from 'i18next'; import i18n from 'i18next';
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
import { getConnectedEdges } from 'reactflow'; import { getConnectedEdges } from 'reactflow';
import { assert } from 'tsafe';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[ [
@ -110,7 +109,7 @@ const selector = createMemoizedSelector(
} else if (l.type === 'regional_guidance_layer') { } else if (l.type === 'regional_guidance_layer') {
return l.ipAdapters; return l.ipAdapters;
} }
assert(false); return [];
}) })
.forEach((ca, i) => { .forEach((ca, i) => {
const hasNoModel = !ca.model; const hasNoModel = !ca.model;

View File

@ -1,6 +1,6 @@
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library'; import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useAddCALayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks'; import { useAddCALayer, useAddIILayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
import { rgLayerAdded } from 'features/controlLayers/store/controlLayersSlice'; import { rgLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -11,6 +11,7 @@ export const AddLayerButton = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [addCALayer, isAddCALayerDisabled] = useAddCALayer(); const [addCALayer, isAddCALayerDisabled] = useAddCALayer();
const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer(); const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
const [addIILayer, isAddIILayerDisabled] = useAddIILayer();
const addRGLayer = useCallback(() => { const addRGLayer = useCallback(() => {
dispatch(rgLayerAdded()); dispatch(rgLayerAdded());
}, [dispatch]); }, [dispatch]);
@ -30,6 +31,9 @@ export const AddLayerButton = memo(() => {
<MenuItem icon={<PiPlusBold />} onClick={addIPALayer} isDisabled={isAddIPALayerDisabled}> <MenuItem icon={<PiPlusBold />} onClick={addIPALayer} isDisabled={isAddIPALayerDisabled}>
{t('controlLayers.globalIPAdapterLayer')} {t('controlLayers.globalIPAdapterLayer')}
</MenuItem> </MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addIILayer} isDisabled={isAddIILayerDisabled}>
{t('controlLayers.globalInitialImageLayer')}
</MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
); );

View File

@ -6,6 +6,7 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton'; import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
import { CALayer } from 'features/controlLayers/components/CALayer/CALayer'; import { CALayer } from 'features/controlLayers/components/CALayer/CALayer';
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton'; import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
import { IILayer } from 'features/controlLayers/components/IILayer/IILayer';
import { IPALayer } from 'features/controlLayers/components/IPALayer/IPALayer'; import { IPALayer } from 'features/controlLayers/components/IPALayer/IPALayer';
import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer'; import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
import { isRenderableLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; import { isRenderableLayer, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
@ -54,6 +55,9 @@ const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => {
if (type === 'ip_adapter_layer') { if (type === 'ip_adapter_layer') {
return <IPALayer key={id} layerId={id} />; return <IPALayer key={id} layerId={id} />;
} }
if (type === 'initial_image_layer') {
return <IILayer key={id} layerId={id} />;
}
}); });
LayerWrapper.displayName = 'LayerWrapper'; LayerWrapper.displayName = 'LayerWrapper';

View File

@ -0,0 +1,81 @@
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InitialImagePreview } from 'features/controlLayers/components/IILayer/InitialImagePreview';
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
import {
iiLayerImageChanged,
layerSelected,
selectIILayerOrThrow,
} from 'features/controlLayers/store/controlLayersSlice';
import type { IILayerImageDropData } from 'features/dnd/types';
import ImageToImageStrength from 'features/parameters/components/ImageToImage/ImageToImageStrength';
import { memo, useCallback, useMemo } from 'react';
import type { IILayerImagePostUploadAction, ImageDTO } from 'services/api/types';
type Props = {
layerId: string;
};
export const IILayer = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch();
const layer = useAppSelector((s) => selectIILayerOrThrow(s.controlLayers.present, layerId));
const onClick = useCallback(() => {
dispatch(layerSelected(layerId));
}, [dispatch, layerId]);
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
const onChangeImage = useCallback(
(imageDTO: ImageDTO | null) => {
dispatch(iiLayerImageChanged({ layerId, imageDTO }));
},
[dispatch, layerId]
);
const droppableData = useMemo<IILayerImageDropData>(
() => ({
actionType: 'SET_II_LAYER_IMAGE',
context: {
layerId,
},
id: layerId,
}),
[layerId]
);
const postUploadAction = useMemo<IILayerImagePostUploadAction>(
() => ({
layerId,
type: 'SET_II_LAYER_IMAGE',
}),
[layerId]
);
return (
<LayerWrapper onClick={onClick} borderColor={layer.isSelected ? 'base.400' : 'base.800'}>
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
<LayerVisibilityToggle layerId={layerId} />
<LayerTitle type="initial_image_layer" />
<Spacer />
<LayerMenu layerId={layerId} />
<LayerDeleteButton layerId={layerId} />
</Flex>
{isOpen && (
<Flex flexDir="column" gap={3} px={3} pb={3}>
<ImageToImageStrength />
<InitialImagePreview
image={layer.image}
onChangeImage={onChangeImage}
droppableData={droppableData}
postUploadAction={postUploadAction}
/>
</Flex>
)}
</LayerWrapper>
);
});
IILayer.displayName = 'IILayer';

View File

@ -0,0 +1,109 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Flex, useShiftModifier } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
import type { ImageWithDims } from 'features/controlLayers/util/controlAdapters';
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { memo, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import type { ImageDTO, PostUploadAction } from 'services/api/types';
type Props = {
image: ImageWithDims | null;
onChangeImage: (imageDTO: ImageDTO | null) => void;
droppableData: TypesafeDroppableData;
postUploadAction: PostUploadAction;
};
export const InitialImagePreview = memo(({ image, onChangeImage, droppableData, postUploadAction }: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isConnected = useAppSelector((s) => s.system.isConnected);
const activeTabName = useAppSelector(activeTabNameSelector);
const optimalDimension = useAppSelector(selectOptimalDimension);
const shift = useShiftModifier();
const { currentData: imageDTO, isError: isErrorControlImage } = useGetImageDTOQuery(image?.imageName ?? skipToken);
const onReset = useCallback(() => {
onChangeImage(null);
}, [onChangeImage]);
const onUseSize = useCallback(() => {
if (!imageDTO) {
return;
}
if (activeTabName === 'unifiedCanvas') {
dispatch(setBoundingBoxDimensions({ width: imageDTO.width, height: imageDTO.height }, optimalDimension));
} else {
const options = { updateAspectRatio: true, clamp: true };
if (shift) {
const { width, height } = imageDTO;
dispatch(widthChanged({ width, ...options }));
dispatch(heightChanged({ height, ...options }));
} else {
const { width, height } = calculateNewSize(
imageDTO.width / imageDTO.height,
optimalDimension * optimalDimension
);
dispatch(widthChanged({ width, ...options }));
dispatch(heightChanged({ height, ...options }));
}
}
}, [imageDTO, activeTabName, dispatch, optimalDimension, shift]);
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
if (imageDTO) {
return {
id: 'initial_image_layer',
payloadType: 'IMAGE_DTO',
payload: { imageDTO: imageDTO },
};
}
}, [imageDTO]);
useEffect(() => {
if (isConnected && isErrorControlImage) {
onReset();
}
}, [onReset, isConnected, isErrorControlImage]);
return (
<Flex position="relative" w="full" h={36} alignItems="center" justifyContent="center">
<IAIDndImage
draggableData={draggableData}
droppableData={droppableData}
imageDTO={imageDTO}
postUploadAction={postUploadAction}
/>
<>
<IAIDndImageIcon
onClick={onReset}
icon={imageDTO ? <PiArrowCounterClockwiseBold size={16} /> : undefined}
tooltip={t('controlnet.resetControlImage')}
/>
<IAIDndImageIcon
onClick={onUseSize}
icon={imageDTO ? <PiRulerBold size={16} /> : undefined}
tooltip={shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')}
styleOverrides={useSizeStyleOverrides}
/>
</>
</Flex>
);
});
InitialImagePreview.displayName = 'InitialImagePreview';
const useSizeStyleOverrides: SystemStyleObject = { mt: 6 };

View File

@ -37,7 +37,9 @@ export const LayerMenu = memo(({ layerId }: Props) => {
<MenuDivider /> <MenuDivider />
</> </>
)} )}
{(layerType === 'regional_guidance_layer' || layerType === 'control_adapter_layer') && ( {(layerType === 'regional_guidance_layer' ||
layerType === 'control_adapter_layer' ||
layerType === 'initial_image_layer') && (
<> <>
<LayerMenuArrangeActions layerId={layerId} /> <LayerMenuArrangeActions layerId={layerId} />
<MenuDivider /> <MenuDivider />

View File

@ -16,6 +16,8 @@ export const LayerTitle = memo(({ type }: Props) => {
return t('controlLayers.globalControlAdapter'); return t('controlLayers.globalControlAdapter');
} else if (type === 'ip_adapter_layer') { } else if (type === 'ip_adapter_layer') {
return t('controlLayers.globalIPAdapter'); return t('controlLayers.globalIPAdapter');
} else if (type === 'initial_image_layer') {
return t('controlLayers.globalInitialImage');
} }
}, [t, type]); }, [t, type]);

View File

@ -1,5 +1,11 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { caLayerAdded, ipaLayerAdded, rgLayerIPAdapterAdded } from 'features/controlLayers/store/controlLayersSlice'; import {
caLayerAdded,
iiLayerAdded,
ipaLayerAdded,
isInitialImageLayer,
rgLayerIPAdapterAdded,
} from 'features/controlLayers/store/controlLayersSlice';
import { import {
buildControlNet, buildControlNet,
buildIPAdapter, buildIPAdapter,
@ -93,3 +99,13 @@ export const useAddIPAdapterToIPALayer = (layerId: string) => {
return [addIPAdapter, isDisabled] as const; return [addIPAdapter, isDisabled] as const;
}; };
export const useAddIILayer = () => {
const dispatch = useAppDispatch();
const isDisabled = useAppSelector((s) => Boolean(s.controlLayers.present.layers.find(isInitialImageLayer)));
const addIILayer = useCallback(() => {
dispatch(iiLayerAdded(null));
}, [dispatch]);
return [addIILayer, isDisabled] as const;
};

View File

@ -39,6 +39,7 @@ import type {
ControlAdapterLayer, ControlAdapterLayer,
ControlLayersState, ControlLayersState,
DrawingTool, DrawingTool,
InitialImageLayer,
IPAdapterLayer, IPAdapterLayer,
Layer, Layer,
RegionalGuidanceLayer, RegionalGuidanceLayer,
@ -71,8 +72,13 @@ export const isRegionalGuidanceLayer = (layer?: Layer): layer is RegionalGuidanc
export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer => export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer =>
layer?.type === 'control_adapter_layer'; layer?.type === 'control_adapter_layer';
export const isIPAdapterLayer = (layer?: Layer): layer is IPAdapterLayer => layer?.type === 'ip_adapter_layer'; export const isIPAdapterLayer = (layer?: Layer): layer is IPAdapterLayer => layer?.type === 'ip_adapter_layer';
export const isRenderableLayer = (layer?: Layer): layer is RegionalGuidanceLayer | ControlAdapterLayer => export const isInitialImageLayer = (layer?: Layer): layer is InitialImageLayer => layer?.type === 'initial_image_layer';
layer?.type === 'regional_guidance_layer' || layer?.type === 'control_adapter_layer'; export const isRenderableLayer = (
layer?: Layer
): layer is RegionalGuidanceLayer | ControlAdapterLayer | InitialImageLayer =>
layer?.type === 'regional_guidance_layer' ||
layer?.type === 'control_adapter_layer' ||
layer?.type === 'initial_image_layer';
const resetLayer = (layer: Layer) => { const resetLayer = (layer: Layer) => {
if (layer.type === 'regional_guidance_layer') { if (layer.type === 'regional_guidance_layer') {
layer.maskObjects = []; layer.maskObjects = [];
@ -94,6 +100,11 @@ export const selectIPALayerOrThrow = (state: ControlLayersState, layerId: string
assert(isIPAdapterLayer(layer)); assert(isIPAdapterLayer(layer));
return layer; return layer;
}; };
export const selectIILayerOrThrow = (state: ControlLayersState, layerId: string): InitialImageLayer => {
const layer = state.layers.find((l) => l.id === layerId);
assert(isInitialImageLayer(layer));
return layer;
};
const selectCAOrIPALayerOrThrow = ( const selectCAOrIPALayerOrThrow = (
state: ControlLayersState, state: ControlLayersState,
layerId: string layerId: string
@ -611,6 +622,45 @@ export const controlLayersSlice = createSlice({
}, },
//#endregion //#endregion
//#region Initial Image Layer
iiLayerAdded: {
reducer: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
const { layerId, imageDTO } = action.payload;
// Highlander! There can be only one!
assert(!state.layers.find(isInitialImageLayer));
const layer: InitialImageLayer = {
id: layerId,
type: 'initial_image_layer',
x: 0,
y: 0,
bbox: null,
bboxNeedsUpdate: false,
isEnabled: true,
image: imageDTO ? imageDTOToImageWithDims(imageDTO) : null,
isSelected: true,
};
state.layers.push(layer);
state.selectedLayerId = layer.id;
for (const layer of state.layers.filter(isRenderableLayer)) {
if (layer.id !== layerId) {
layer.isSelected = false;
}
}
},
prepare: (imageDTO: ImageDTO | null) => ({ payload: { layerId: 'initial_image_layer', imageDTO } }),
},
iiLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
const { layerId, imageDTO } = action.payload;
const layer = selectIILayerOrThrow(state, layerId);
if (layer) {
layer.bbox = null;
layer.bboxNeedsUpdate = true;
layer.isEnabled = true;
layer.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
}
},
//#endregion
//#region Globals //#region Globals
positivePromptChanged: (state, action: PayloadAction<string>) => { positivePromptChanged: (state, action: PayloadAction<string>) => {
state.positivePrompt = action.payload; state.positivePrompt = action.payload;
@ -780,6 +830,9 @@ export const {
rgLayerIPAdapterMethodChanged, rgLayerIPAdapterMethodChanged,
rgLayerIPAdapterModelChanged, rgLayerIPAdapterModelChanged,
rgLayerIPAdapterCLIPVisionModelChanged, rgLayerIPAdapterCLIPVisionModelChanged,
// II Layer
iiLayerAdded,
iiLayerImageChanged,
// Globals // Globals
positivePromptChanged, positivePromptChanged,
negativePromptChanged, negativePromptChanged,

View File

@ -1,5 +1,6 @@
import type { import type {
ControlNetConfigV2, ControlNetConfigV2,
ImageWithDims,
IPAdapterConfigV2, IPAdapterConfigV2,
T2IAdapterConfigV2, T2IAdapterConfigV2,
} from 'features/controlLayers/util/controlAdapters'; } from 'features/controlLayers/util/controlAdapters';
@ -73,7 +74,12 @@ export type RegionalGuidanceLayer = RenderableLayerBase & {
needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object
}; };
export type Layer = RegionalGuidanceLayer | ControlAdapterLayer | IPAdapterLayer; export type InitialImageLayer = RenderableLayerBase & {
type: 'initial_image_layer';
image: ImageWithDims | null;
};
export type Layer = RegionalGuidanceLayer | ControlAdapterLayer | IPAdapterLayer | InitialImageLayer;
export type ControlLayersState = { export type ControlLayersState = {
_version: 1; _version: 1;

View File

@ -55,6 +55,13 @@ export type RGLayerIPAdapterImageDropData = BaseDropData & {
}; };
}; };
export type IILayerImageDropData = BaseDropData & {
actionType: 'SET_II_LAYER_IMAGE';
context: {
layerId: string;
};
};
export type CanvasInitialImageDropData = BaseDropData & { export type CanvasInitialImageDropData = BaseDropData & {
actionType: 'SET_CANVAS_INITIAL_IMAGE'; actionType: 'SET_CANVAS_INITIAL_IMAGE';
}; };
@ -86,7 +93,8 @@ export type TypesafeDroppableData =
| RemoveFromBoardDropData | RemoveFromBoardDropData
| CALayerImageDropData | CALayerImageDropData
| IPALayerImageDropData | IPALayerImageDropData
| RGLayerIPAdapterImageDropData; | RGLayerIPAdapterImageDropData
| IILayerImageDropData;
type BaseDragData = { type BaseDragData = {
id: string; id: string;

View File

@ -25,6 +25,8 @@ export const isValidDrop = (overData: TypesafeDroppableData | undefined, active:
return payloadType === 'IMAGE_DTO'; return payloadType === 'IMAGE_DTO';
case 'SET_RG_LAYER_IP_ADAPTER_IMAGE': case 'SET_RG_LAYER_IP_ADAPTER_IMAGE':
return payloadType === 'IMAGE_DTO'; return payloadType === 'IMAGE_DTO';
case 'SET_II_LAYER_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SET_CANVAS_INITIAL_IMAGE': case 'SET_CANVAS_INITIAL_IMAGE':
return payloadType === 'IMAGE_DTO'; return payloadType === 'IMAGE_DTO';
case 'SET_NODES_IMAGE': case 'SET_NODES_IMAGE':

View File

@ -193,6 +193,11 @@ export type RGLayerIPAdapterImagePostUploadAction = {
ipAdapterId: string; ipAdapterId: string;
}; };
export type IILayerImagePostUploadAction = {
type: 'SET_II_LAYER_IMAGE';
layerId: string;
};
type InitialImageAction = { type InitialImageAction = {
type: 'SET_INITIAL_IMAGE'; type: 'SET_INITIAL_IMAGE';
}; };
@ -225,4 +230,5 @@ export type PostUploadAction =
| AddToBatchAction | AddToBatchAction
| CALayerImagePostUploadAction | CALayerImagePostUploadAction
| IPALayerImagePostUploadAction | IPALayerImagePostUploadAction
| RGLayerIPAdapterImagePostUploadAction; | RGLayerIPAdapterImagePostUploadAction
| IILayerImagePostUploadAction;