feat(ui): more dnd cleanup and tidy

This commit is contained in:
psychedelicious
2024-10-31 07:26:22 +10:00
parent f0c80a8d7a
commit ee8359242c
8 changed files with 75 additions and 55 deletions

View File

@@ -1,19 +1,33 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Box, Flex } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useCanvasEntityListDnd } from 'features/controlLayers/components/CanvasEntityList/useCanvasEntityListDnd';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useEntityIsSelected } from 'features/controlLayers/hooks/useEntityIsSelected';
import { useEntitySelectionColor } from 'features/controlLayers/hooks/useEntitySelectionColor';
import { entitySelected } from 'features/controlLayers/store/canvasSlice';
import { DndListDropIndicator } from 'features/dnd/DndListDropIndicator';
import type { PropsWithChildren } from 'react';
import { memo, useCallback, useRef } from 'react';
const sx = {
position: 'relative',
flexDir: 'column',
w: 'full',
bg: 'base.850',
borderRadius: 'base',
'&[data-selected=true]': {
bg: 'base.800',
},
'&[data-is-dragging=true]': {
opacity: 0.3,
},
transitionProperty: 'common',
} satisfies SystemStyleObject;
export const CanvasEntityContainer = memo((props: PropsWithChildren) => {
const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext();
const isSelected = useEntityIsSelected(entityIdentifier);
const selectionColor = useEntitySelectionColor(entityIdentifier);
const onClick = useCallback(() => {
if (isSelected) {
return;
@@ -22,26 +36,22 @@ export const CanvasEntityContainer = memo((props: PropsWithChildren) => {
}, [dispatch, entityIdentifier, isSelected]);
const ref = useRef<HTMLDivElement>(null);
const dndState = useCanvasEntityListDnd(ref, entityIdentifier);
const [dndListState, isDragging] = useCanvasEntityListDnd(ref, entityIdentifier);
return (
<Box position="relative">
<Flex
// This is used to trigger the post-move flash animation
data-entity-id={entityIdentifier.id}
data-selected={isSelected}
data-is-dragging={isDragging}
ref={ref}
position="relative"
flexDir="column"
w="full"
bg={isSelected ? 'base.800' : 'base.850'}
onClick={onClick}
borderInlineStartWidth={5}
borderColor={isSelected ? selectionColor : 'base.800'}
borderRadius="base"
sx={sx}
>
{props.children}
</Flex>
<DndListDropIndicator dndState={dndState} />
<DndListDropIndicator dndState={dndListState} />
</Box>
);
});

View File

@@ -13,7 +13,8 @@ import { useEffect, useState } from 'react';
export const singleCanvasEntity = buildDndSourceApi<{ entityIdentifier: CanvasEntityIdentifier }>('SingleCanvasEntity');
export const useCanvasEntityListDnd = (ref: RefObject<HTMLElement>, entityIdentifier: CanvasEntityIdentifier) => {
const [dndState, setDndState] = useState<DndListState>(idle);
const [dndListState, setDndListState] = useState<DndListState>(idle);
const [isDragging, setIsDragging] = useState(false);
useEffect(() => {
const element = ref.current;
@@ -27,10 +28,12 @@ export const useCanvasEntityListDnd = (ref: RefObject<HTMLElement>, entityIdenti
return singleCanvasEntity.getData({ entityIdentifier });
},
onDragStart() {
setDndState({ type: 'is-dragging' });
setDndListState({ type: 'is-dragging' });
setIsDragging(true);
},
onDrop() {
setDndState(idle);
setDndListState(idle);
setIsDragging(false);
},
}),
dropTargetForElements({
@@ -57,14 +60,14 @@ export const useCanvasEntityListDnd = (ref: RefObject<HTMLElement>, entityIdenti
},
onDragEnter({ self }) {
const closestEdge = extractClosestEdge(self.data);
setDndState({ type: 'is-dragging-over', closestEdge });
setDndListState({ type: 'is-dragging-over', closestEdge });
},
onDrag({ self }) {
const closestEdge = extractClosestEdge(self.data);
// Only need to update react state if nothing has changed.
// Prevents re-rendering.
setDndState((current) => {
setDndListState((current) => {
if (current.type === 'is-dragging-over' && current.closestEdge === closestEdge) {
return current;
}
@@ -72,14 +75,14 @@ export const useCanvasEntityListDnd = (ref: RefObject<HTMLElement>, entityIdenti
});
},
onDragLeave() {
setDndState(idle);
setDndListState(idle);
},
onDrop() {
setDndState(idle);
setDndListState(idle);
},
})
);
}, [entityIdentifier, ref]);
return dndState;
return [dndListState, isDragging] as const;
};

View File

@@ -4,12 +4,12 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityGroupList';
import { IPAdapter } from 'features/controlLayers/components/IPAdapter/IPAdapter';
import { mapId } from 'features/controlLayers/konva/util';
import { selectCanvasSlice, selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { getEntityIdentifier } from 'features/controlLayers/store/types';
import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return canvas.referenceImages.entities.map(mapId).reverse();
const selectEntityIdentifiers = createMemoizedSelector(selectCanvasSlice, (canvas) => {
return canvas.referenceImages.entities.map(getEntityIdentifier).toReversed();
});
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
return selectedEntityIdentifier?.type === 'reference_image';
@@ -17,17 +17,17 @@ const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selecte
export const IPAdapterList = memo(() => {
const isSelected = useAppSelector(selectIsSelected);
const ipaIds = useAppSelector(selectEntityIds);
const entityIdentifiers = useAppSelector(selectEntityIdentifiers);
if (ipaIds.length === 0) {
if (entityIdentifiers.length === 0) {
return null;
}
if (ipaIds.length > 0) {
if (entityIdentifiers.length > 0) {
return (
<CanvasEntityGroupList type="reference_image" isSelected={isSelected}>
{ipaIds.map((id) => (
<IPAdapter key={id} id={id} />
<CanvasEntityGroupList type="reference_image" isSelected={isSelected} entityIdentifiers={entityIdentifiers}>
{entityIdentifiers.map((entityIdentifiers) => (
<IPAdapter key={entityIdentifiers.id} id={entityIdentifiers.id} />
))}
</CanvasEntityGroupList>
);

View File

@@ -85,12 +85,8 @@ export const IPAdapterSettings = memo(() => {
[entityIdentifier.id]
);
const targetData = useMemo<Dnd.types['TargetDataTypeMap']['setGlobalReferenceImage']>(
() =>
Dnd.Target.setGlobalReferenceImage.getData(
{ globalReferenceImageId: entityIdentifier.id },
ipAdapter.image?.image_name
),
[entityIdentifier.id, ipAdapter.image?.image_name]
() => Dnd.Target.setGlobalReferenceImage.getData({ entityIdentifier }, ipAdapter.image?.image_name),
[entityIdentifier, ipAdapter.image?.image_name]
);
const pullBboxIntoIPAdapter = usePullBboxIntoGlobalReferenceImage(entityIdentifier);
const isBusy = useCanvasIsBusy();

View File

@@ -94,10 +94,10 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ referenceImageId }: Pro
const targetData = useMemo<Dnd.types['TargetDataTypeMap']['setRegionalGuidanceReferenceImage']>(
() =>
Dnd.Target.setRegionalGuidanceReferenceImage.getData(
{ regionalGuidanceId: entityIdentifier.id, referenceImageId },
{ entityIdentifier, referenceImageId },
ipAdapter.image?.image_name
),
[entityIdentifier.id, ipAdapter.image?.image_name, referenceImageId]
[entityIdentifier, ipAdapter.image?.image_name, referenceImageId]
);
const postUploadAction = useMemo<RGIPAdapterImagePostUploadAction>(