Skip to content

Commit 92f6600

Browse files
refactor(ui): dnd actions to image actions
We don't need a "dnd" image system. We need a "image action" system. We need to execute specific flows with images from various "origins": - internal dnd e.g. from gallery - external dnd e.g. user drags an image file into the browser - direct file upload e.g. user clicks an upload button - some other internal app button e.g. a context menu The actions are now generalized to better support these various use-cases.
1 parent 1afc2cb commit 92f6600

File tree

29 files changed

+726
-923
lines changed

29 files changed

+726
-923
lines changed

invokeai/frontend/web/src/features/controlLayers/components/CanvasDropArea.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { Grid, GridItem } from '@invoke-ai/ui-library';
22
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
3-
import { Dnd } from 'features/dnd/dnd';
43
import { DndDropTarget } from 'features/dnd/DndDropTarget';
54
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
5+
import { newCanvasEntityFromImageActionApi } from 'features/imageActions/actions';
66
import { memo } from 'react';
77
import { useTranslation } from 'react-i18next';
88

9-
const addRasterLayerFromImageDndTargetData = Dnd.Target.newRasterLayerFromImage.getData();
10-
const addControlLayerFromImageDndTargetData = Dnd.Target.newControlLayerFromImage.getData();
11-
const addRegionalGuidanceReferenceImageFromImageDndTargetData =
12-
Dnd.Target.newRegionalGuidanceReferenceImageFromImage.getData();
13-
const addGlobalReferenceImageFromImageDndTargetData = Dnd.Target.newGlobalReferenceImageFromImage.getData();
9+
const addRasterLayerFromImageDndTargetData = newCanvasEntityFromImageActionApi.getData({ type: 'raster_layer' });
10+
const addControlLayerFromImageDndTargetData = newCanvasEntityFromImageActionApi.getData({ type: 'control_layer' });
11+
const addRegionalGuidanceReferenceImageFromImageDndTargetData = newCanvasEntityFromImageActionApi.getData({
12+
type: 'regional_guidance_with_reference_image',
13+
});
14+
const addGlobalReferenceImageFromImageDndTargetData = newCanvasEntityFromImageActionApi.getData({ type: 'reference_image' });
1415

1516
export const CanvasDropArea = memo(() => {
1617
const { t } = useTranslation();

invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityGroupList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { useEntityTypeTitle } from 'features/controlLayers/hooks/useEntityTypeTi
1616
import { entitiesReordered } from 'features/controlLayers/store/canvasSlice';
1717
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
1818
import { isRenderableEntityType } from 'features/controlLayers/store/types';
19-
import { triggerPostMoveFlash } from 'features/dnd/dnd';
19+
import { triggerPostMoveFlash } from 'features/dnd/util';
2020
import type { PropsWithChildren } from 'react';
2121
import { memo, useEffect } from 'react';
2222
import { flushSync } from 'react-dom';

invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/useCanvasEntityListDnd.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,26 @@ import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
22
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
33
import { attachClosestEdge, extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
44
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
5-
import type { DndListState } from 'features/dnd/dnd';
6-
import { buildDndSourceApi, idle } from 'features/dnd/dnd';
5+
import { type DndListTargetState, idle } from 'features/dnd/types';
6+
import type { ActionData, ActionSourceApi } from 'features/imageActions/actions';
7+
import { buildGetData, buildTypeAndKey, buildTypeGuard } from 'features/imageActions/actions';
78
import type { RefObject } from 'react';
89
import { useEffect, useState } from 'react';
910

10-
/**
11-
* Dnd source API for a single canvas entity.
12-
*/
13-
export const singleCanvasEntity = buildDndSourceApi<{ entityIdentifier: CanvasEntityIdentifier }>('SingleCanvasEntity');
11+
const _singleCanvasEntity = buildTypeAndKey('single-canvas-entity');
12+
type SingleCanvasEntitySourceData = ActionData<
13+
typeof _singleCanvasEntity.type,
14+
typeof _singleCanvasEntity.key,
15+
{ entityIdentifier: CanvasEntityIdentifier }
16+
>;
17+
export const singleCanvasEntity: ActionSourceApi<SingleCanvasEntitySourceData> = {
18+
..._singleCanvasEntity,
19+
typeGuard: buildTypeGuard(_singleCanvasEntity.key),
20+
getData: buildGetData(_singleCanvasEntity.key, _singleCanvasEntity.type),
21+
};
1422

1523
export const useCanvasEntityListDnd = (ref: RefObject<HTMLElement>, entityIdentifier: CanvasEntityIdentifier) => {
16-
const [dndListState, setDndListState] = useState<DndListState>(idle);
24+
const [dndListState, setDndListState] = useState<DndListTargetState>(idle);
1725
const [isDragging, setIsDragging] = useState(false);
1826

1927
useEffect(() => {

invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ export const CanvasMainPanelContent = memo(() => {
109109
<SelectObject />
110110
</CanvasManagerProviderGate>
111111
</Flex>
112-
<CanvasDropArea />
112+
<CanvasManagerProviderGate>
113+
<CanvasDropArea />
114+
</CanvasManagerProviderGate>
113115
<GatedImageViewer />
114116
</Flex>
115117
);

invokeai/frontend/web/src/features/controlLayers/components/CanvasRightPanel.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHook
66
import { CanvasLayersPanelContent } from 'features/controlLayers/components/CanvasLayersPanelContent';
77
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
88
import { selectEntityCountActive } from 'features/controlLayers/store/selectors';
9-
import { Dnd } from 'features/dnd/dnd';
109
import { DndDropOverlay } from 'features/dnd/DndDropOverlay';
10+
import type { DndTargetState } from 'features/dnd/types';
1111
import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent';
1212
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
13+
import { multipleImageSourceApi, singleImageSourceApi } from 'features/imageActions/actions';
1314
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
1415
import { selectActiveTabCanvasRightPanel } from 'features/ui/store/uiSelectors';
1516
import { activeTabCanvasRightPanelChanged } from 'features/ui/store/uiSlice';
@@ -84,8 +85,8 @@ const PanelTabs = memo(() => {
8485
const { t } = useTranslation();
8586
const store = useAppStore();
8687
const activeEntityCount = useAppSelector(selectEntityCountActive);
87-
const [layersTabDndState, setLayersTabDndState] = useState<Dnd.types['DndState']>('idle');
88-
const [galleryTabDndState, setGalleryTabDndState] = useState<Dnd.types['DndState']>('idle');
88+
const [layersTabDndState, setLayersTabDndState] = useState<DndTargetState>('idle');
89+
const [galleryTabDndState, setGalleryTabDndState] = useState<DndTargetState>('idle');
8990
const layersTabRef = useRef<HTMLDivElement>(null);
9091
const galleryTabRef = useRef<HTMLDivElement>(null);
9192
const timeoutRef = useRef<number | null>(null);
@@ -208,7 +209,7 @@ const PanelTabs = memo(() => {
208209
}),
209210
monitorForElements({
210211
canMonitor: ({ source }) => {
211-
if (!Dnd.Source.singleImage.typeGuard(source.data) || !Dnd.Source.multipleImage.typeGuard(source.data)) {
212+
if (!singleImageSourceApi.typeGuard(source.data) || !multipleImageSourceApi.typeGuard(source.data)) {
212213
return false;
213214
}
214215
// Only monitor if we are not already on the gallery tab

invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayer.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import { ControlLayerAdapterGate } from 'features/controlLayers/contexts/EntityA
1111
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
1212
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
1313
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
14-
import { Dnd } from 'features/dnd/dnd';
1514
import { DndDropTarget } from 'features/dnd/DndDropTarget';
15+
import type { ReplaceCanvasEntityObjectsWithImageActionData} from 'features/imageActions/actions';
16+
import {replaceCanvasEntityObjectsWithImageActionApi } from 'features/imageActions/actions';
1617
import { memo, useMemo } from 'react';
1718
import { useTranslation } from 'react-i18next';
1819

@@ -27,8 +28,8 @@ export const ControlLayer = memo(({ id }: Props) => {
2728
() => ({ id, type: 'control_layer' }),
2829
[id]
2930
);
30-
const targetData = useMemo<Dnd.types['TargetDataTypeMap']['replaceLayerWithImage']>(
31-
() => Dnd.Target.replaceLayerWithImage.getData({ entityIdentifier }, entityIdentifier.id),
31+
const targetData = useMemo<ReplaceCanvasEntityObjectsWithImageActionData>(
32+
() => replaceCanvasEntityObjectsWithImageActionApi.getData({ entityIdentifier }, entityIdentifier.id),
3233
[entityIdentifier]
3334
);
3435

invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterImagePreview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { Flex } from '@invoke-ai/ui-library';
33
import { useStore } from '@nanostores/react';
44
import { skipToken } from '@reduxjs/toolkit/query';
55
import type { ImageWithDims } from 'features/controlLayers/store/types';
6-
import type { Dnd } from 'features/dnd/dnd';
76
import { DndDropTarget } from 'features/dnd/DndDropTarget';
87
import { DndImage } from 'features/dnd/DndImage';
98
import { DndImageIcon } from 'features/dnd/DndImageIcon';
9+
import type { SetGlobalReferenceImageActionData, SetRegionalGuidanceReferenceImageActionData } from 'features/imageActions/actions';
1010
import { memo, useCallback, useEffect } from 'react';
1111
import { useTranslation } from 'react-i18next';
1212
import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
@@ -31,7 +31,7 @@ const sx = {
3131
type Props = {
3232
image: ImageWithDims | null;
3333
onChangeImage: (imageDTO: ImageDTO | null) => void;
34-
targetData: Dnd.types['TargetDataUnion'];
34+
targetData: SetGlobalReferenceImageActionData | SetRegionalGuidanceReferenceImageActionData;
3535
};
3636

3737
export const IPAdapterImagePreview = memo(({ image, onChangeImage, targetData }: Props) => {

invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {
1919
import { selectIsFLUX } from 'features/controlLayers/store/paramsSlice';
2020
import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
2121
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
22-
import { Dnd } from 'features/dnd/dnd';
22+
import type { SetGlobalReferenceImageActionData} from 'features/imageActions/actions';
23+
import {setGlobalReferenceImageActionApi } from 'features/imageActions/actions';
2324
import { memo, useCallback, useMemo } from 'react';
2425
import { useTranslation } from 'react-i18next';
2526
import { PiBoundingBoxBold } from 'react-icons/pi';
@@ -80,8 +81,8 @@ export const IPAdapterSettings = memo(() => {
8081
[dispatch, entityIdentifier]
8182
);
8283

83-
const targetData = useMemo<Dnd.types['TargetDataTypeMap']['setGlobalReferenceImage']>(
84-
() => Dnd.Target.setGlobalReferenceImage.getData({ entityIdentifier }, ipAdapter.image?.image_name),
84+
const targetData = useMemo<SetGlobalReferenceImageActionData>(
85+
() => setGlobalReferenceImageActionApi.getData({ entityIdentifier }, ipAdapter.image?.image_name),
8586
[entityIdentifier, ipAdapter.image?.image_name]
8687
);
8788
const pullBboxIntoIPAdapter = usePullBboxIntoGlobalReferenceImage(entityIdentifier);

invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayer.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import { RasterLayerAdapterGate } from 'features/controlLayers/contexts/EntityAd
88
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
99
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
1010
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
11-
import { Dnd } from 'features/dnd/dnd';
1211
import { DndDropTarget } from 'features/dnd/DndDropTarget';
12+
import type { ReplaceCanvasEntityObjectsWithImageActionData} from 'features/imageActions/actions';
13+
import {replaceCanvasEntityObjectsWithImageActionApi } from 'features/imageActions/actions';
1314
import { memo, useMemo } from 'react';
1415
import { useTranslation } from 'react-i18next';
1516

@@ -21,8 +22,8 @@ export const RasterLayer = memo(({ id }: Props) => {
2122
const { t } = useTranslation();
2223
const isBusy = useCanvasIsBusy();
2324
const entityIdentifier = useMemo<CanvasEntityIdentifier<'raster_layer'>>(() => ({ id, type: 'raster_layer' }), [id]);
24-
const targetData = useMemo<Dnd.types['TargetDataTypeMap']['replaceLayerWithImage']>(
25-
() => Dnd.Target.replaceLayerWithImage.getData({ entityIdentifier }, entityIdentifier.id),
25+
const targetData = useMemo<ReplaceCanvasEntityObjectsWithImageActionData>(
26+
() => replaceCanvasEntityObjectsWithImageActionApi.getData({ entityIdentifier }, entityIdentifier.id),
2627
[entityIdentifier]
2728
);
2829

invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {
2020
} from 'features/controlLayers/store/canvasSlice';
2121
import { selectCanvasSlice, selectRegionalGuidanceReferenceImage } from 'features/controlLayers/store/selectors';
2222
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
23-
import { Dnd } from 'features/dnd/dnd';
23+
import type { SetRegionalGuidanceReferenceImageActionData} from 'features/imageActions/actions';
24+
import {setRegionalGuidanceReferenceImageActionApi } from 'features/imageActions/actions';
2425
import { memo, useCallback, useMemo } from 'react';
2526
import { useTranslation } from 'react-i18next';
2627
import { PiBoundingBoxBold, PiTrashSimpleFill } from 'react-icons/pi';
@@ -91,12 +92,9 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ referenceImageId }: Pro
9192
[dispatch, entityIdentifier, referenceImageId]
9293
);
9394

94-
const targetData = useMemo<Dnd.types['TargetDataTypeMap']['setRegionalGuidanceReferenceImage']>(
95+
const targetData = useMemo<SetRegionalGuidanceReferenceImageActionData>(
9596
() =>
96-
Dnd.Target.setRegionalGuidanceReferenceImage.getData(
97-
{ entityIdentifier, referenceImageId },
98-
ipAdapter.image?.image_name
99-
),
97+
setRegionalGuidanceReferenceImageActionApi.getData({ entityIdentifier, referenceImageId }, ipAdapter.image?.image_name),
10098
[entityIdentifier, ipAdapter.image?.image_name, referenceImageId]
10199
);
102100

0 commit comments

Comments
 (0)