mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): dnd layer reordering (wip)
This commit is contained in:
@@ -6,7 +6,7 @@ import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHook
|
||||
import { CanvasLayersPanelContent } from 'features/controlLayers/components/CanvasLayersPanelContent';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectEntityCountActive } from 'features/controlLayers/store/selectors';
|
||||
import type { Dnd } from 'features/dnd/dnd';
|
||||
import { Dnd } from 'features/dnd/dnd';
|
||||
import { DndDropOverlay } from 'features/dnd/DndDropOverlay';
|
||||
import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent';
|
||||
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
@@ -195,11 +195,6 @@ const PanelTabs = memo(() => {
|
||||
}
|
||||
};
|
||||
|
||||
const canMonitor = () => {
|
||||
// Only monitor if we are not already on the gallery tab
|
||||
return selectActiveTabCanvasRightPanel(store.getState()) !== 'gallery';
|
||||
};
|
||||
|
||||
const onDragStart = () => {
|
||||
// Set the state to pending when a drag starts
|
||||
setGalleryTabDndState('potential');
|
||||
@@ -212,7 +207,13 @@ const PanelTabs = memo(() => {
|
||||
onDragLeave,
|
||||
}),
|
||||
monitorForElements({
|
||||
canMonitor,
|
||||
canMonitor: ({ source }) => {
|
||||
if (!Dnd.Source.singleImage.typeGuard(source.data) || !Dnd.Source.multipleImage.typeGuard(source.data)) {
|
||||
return false;
|
||||
}
|
||||
// Only monitor if we are not already on the gallery tab
|
||||
return selectActiveTabCanvasRightPanel(store.getState()) !== 'gallery';
|
||||
},
|
||||
onDragStart,
|
||||
}),
|
||||
dropTargetForExternal({
|
||||
@@ -221,7 +222,7 @@ const PanelTabs = memo(() => {
|
||||
onDragLeave,
|
||||
}),
|
||||
monitorForExternal({
|
||||
canMonitor,
|
||||
canMonitor: () => selectActiveTabCanvasRightPanel(store.getState()) !== 'gallery',
|
||||
onDragStart,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -3,12 +3,12 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
|
||||
import { ControlLayer } from 'features/controlLayers/components/ControlLayer/ControlLayer';
|
||||
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.controlLayers.entities.map(mapId).reverse();
|
||||
const selectEntityIdentifiers = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
return canvas.controlLayers.entities.map(getEntityIdentifier).toReversed();
|
||||
});
|
||||
|
||||
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
|
||||
@@ -17,17 +17,17 @@ const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selecte
|
||||
|
||||
export const ControlLayerEntityList = memo(() => {
|
||||
const isSelected = useAppSelector(selectIsSelected);
|
||||
const layerIds = useAppSelector(selectEntityIds);
|
||||
const entityIdentifiers = useAppSelector(selectEntityIdentifiers);
|
||||
|
||||
if (layerIds.length === 0) {
|
||||
if (entityIdentifiers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (layerIds.length > 0) {
|
||||
if (entityIdentifiers.length > 0) {
|
||||
return (
|
||||
<CanvasEntityGroupList type="control_layer" isSelected={isSelected}>
|
||||
{layerIds.map((id) => (
|
||||
<ControlLayer key={id} id={id} />
|
||||
<CanvasEntityGroupList type="control_layer" isSelected={isSelected} entityIdentifiers={entityIdentifiers}>
|
||||
{entityIdentifiers.map((entityIdentifier) => (
|
||||
<ControlLayer key={entityIdentifier.id} id={entityIdentifier.id} />
|
||||
))}
|
||||
</CanvasEntityGroupList>
|
||||
);
|
||||
|
||||
@@ -3,12 +3,12 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
|
||||
import { InpaintMask } from 'features/controlLayers/components/InpaintMask/InpaintMask';
|
||||
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.inpaintMasks.entities.map(mapId).reverse();
|
||||
const selectEntityIdentifiers = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
return canvas.inpaintMasks.entities.map(getEntityIdentifier).toReversed();
|
||||
});
|
||||
|
||||
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
|
||||
@@ -17,17 +17,17 @@ const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selecte
|
||||
|
||||
export const InpaintMaskList = memo(() => {
|
||||
const isSelected = useAppSelector(selectIsSelected);
|
||||
const entityIds = useAppSelector(selectEntityIds);
|
||||
const entityIdentifiers = useAppSelector(selectEntityIdentifiers);
|
||||
|
||||
if (entityIds.length === 0) {
|
||||
if (entityIdentifiers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entityIds.length > 0) {
|
||||
if (entityIdentifiers.length > 0) {
|
||||
return (
|
||||
<CanvasEntityGroupList type="inpaint_mask" isSelected={isSelected}>
|
||||
{entityIds.map((id) => (
|
||||
<InpaintMask key={id} id={id} />
|
||||
<CanvasEntityGroupList type="inpaint_mask" isSelected={isSelected} entityIdentifiers={entityIdentifiers}>
|
||||
{entityIdentifiers.map((entityIdentifier) => (
|
||||
<InpaintMask key={entityIdentifier.id} id={entityIdentifier.id} />
|
||||
))}
|
||||
</CanvasEntityGroupList>
|
||||
);
|
||||
|
||||
@@ -3,12 +3,12 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
|
||||
import { RasterLayer } from 'features/controlLayers/components/RasterLayer/RasterLayer';
|
||||
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.rasterLayers.entities.map(mapId).reverse();
|
||||
const selectEntityIdentifiers = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
return canvas.rasterLayers.entities.map(getEntityIdentifier).toReversed();
|
||||
});
|
||||
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
|
||||
return selectedEntityIdentifier?.type === 'raster_layer';
|
||||
@@ -16,17 +16,17 @@ const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selecte
|
||||
|
||||
export const RasterLayerEntityList = memo(() => {
|
||||
const isSelected = useAppSelector(selectIsSelected);
|
||||
const layerIds = useAppSelector(selectEntityIds);
|
||||
const entityIdentifiers = useAppSelector(selectEntityIdentifiers);
|
||||
|
||||
if (layerIds.length === 0) {
|
||||
if (entityIdentifiers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (layerIds.length > 0) {
|
||||
if (entityIdentifiers.length > 0) {
|
||||
return (
|
||||
<CanvasEntityGroupList type="raster_layer" isSelected={isSelected}>
|
||||
{layerIds.map((id) => (
|
||||
<RasterLayer key={id} id={id} />
|
||||
<CanvasEntityGroupList type="raster_layer" isSelected={isSelected} entityIdentifiers={entityIdentifiers}>
|
||||
{entityIdentifiers.map((entityIdentifier) => (
|
||||
<RasterLayer key={entityIdentifier.id} id={entityIdentifier.id} />
|
||||
))}
|
||||
</CanvasEntityGroupList>
|
||||
);
|
||||
|
||||
@@ -3,12 +3,12 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
|
||||
import { RegionalGuidance } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidance';
|
||||
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.regionalGuidance.entities.map(mapId).reverse();
|
||||
const selectEntityIdentifiers = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
return canvas.regionalGuidance.entities.map(getEntityIdentifier).toReversed();
|
||||
});
|
||||
const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selectedEntityIdentifier) => {
|
||||
return selectedEntityIdentifier?.type === 'regional_guidance';
|
||||
@@ -16,17 +16,17 @@ const selectIsSelected = createSelector(selectSelectedEntityIdentifier, (selecte
|
||||
|
||||
export const RegionalGuidanceEntityList = memo(() => {
|
||||
const isSelected = useAppSelector(selectIsSelected);
|
||||
const rgIds = useAppSelector(selectEntityIds);
|
||||
const entityIdentifiers = useAppSelector(selectEntityIdentifiers);
|
||||
|
||||
if (rgIds.length === 0) {
|
||||
if (entityIdentifiers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rgIds.length > 0) {
|
||||
if (entityIdentifiers.length > 0) {
|
||||
return (
|
||||
<CanvasEntityGroupList type="regional_guidance" isSelected={isSelected}>
|
||||
{rgIds.map((id) => (
|
||||
<RegionalGuidance key={id} id={id} />
|
||||
<CanvasEntityGroupList type="regional_guidance" isSelected={isSelected} entityIdentifiers={entityIdentifiers}>
|
||||
{entityIdentifiers.map((entityIdentifier) => (
|
||||
<RegionalGuidance key={entityIdentifier.id} id={entityIdentifier.id} />
|
||||
))}
|
||||
</CanvasEntityGroupList>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
||||
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||
// import { pointerOutsideOfPreview } from '@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview';
|
||||
// import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
||||
import type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
||||
import { attachClosestEdge, extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
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 { Dnd } from 'features/dnd/dnd';
|
||||
import DropIndicator from 'features/dnd/DndDropIndicator';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
type DndState =
|
||||
| {
|
||||
type: 'idle';
|
||||
}
|
||||
| {
|
||||
type: 'preview';
|
||||
container: HTMLElement;
|
||||
}
|
||||
| {
|
||||
type: 'is-dragging';
|
||||
}
|
||||
| {
|
||||
type: 'is-dragging-over';
|
||||
closestEdge: Edge | null;
|
||||
};
|
||||
|
||||
const idle: DndState = { type: 'idle' };
|
||||
|
||||
export const CanvasEntityContainer = memo((props: PropsWithChildren) => {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -18,9 +44,95 @@ export const CanvasEntityContainer = memo((props: PropsWithChildren) => {
|
||||
}
|
||||
dispatch(entitySelected({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier, isSelected]);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [dndState, setDndState] = useState<DndState>(idle);
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
return combine(
|
||||
draggable({
|
||||
element,
|
||||
getInitialData() {
|
||||
return Dnd.Source.singleCanvasEntity.getData({ entityIdentifier });
|
||||
},
|
||||
// onGenerateDragPreview({ nativeSetDragImage }) {
|
||||
// setCustomNativeDragPreview({
|
||||
// nativeSetDragImage,
|
||||
// getOffset: pointerOutsideOfPreview({
|
||||
// x: '16px',
|
||||
// y: '8px',
|
||||
// }),
|
||||
// render({ container }) {
|
||||
// setState({ type: 'preview', container });
|
||||
// },
|
||||
// });
|
||||
// },
|
||||
onDragStart() {
|
||||
setDndState({ type: 'is-dragging' });
|
||||
},
|
||||
onDrop() {
|
||||
setDndState(idle);
|
||||
},
|
||||
}),
|
||||
dropTargetForElements({
|
||||
element,
|
||||
canDrop({ source }) {
|
||||
// not allowing dropping on yourself
|
||||
if (source.element === element) {
|
||||
return false;
|
||||
}
|
||||
// only allowing tasks to be dropped on me
|
||||
if (!Dnd.Source.singleCanvasEntity.typeGuard(source.data)) {
|
||||
return false;
|
||||
}
|
||||
if (source.data.payload.entityIdentifier.type !== entityIdentifier.type) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getData({ input }) {
|
||||
const data = Dnd.Source.singleCanvasEntity.getData({ entityIdentifier });
|
||||
return attachClosestEdge(data, {
|
||||
element,
|
||||
input,
|
||||
allowedEdges: ['top', 'bottom'],
|
||||
});
|
||||
},
|
||||
getIsSticky() {
|
||||
return true;
|
||||
},
|
||||
onDragEnter({ self }) {
|
||||
const closestEdge = extractClosestEdge(self.data);
|
||||
setDndState({ 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) => {
|
||||
if (current.type === 'is-dragging-over' && current.closestEdge === closestEdge) {
|
||||
return current;
|
||||
}
|
||||
return { type: 'is-dragging-over', closestEdge };
|
||||
});
|
||||
},
|
||||
onDragLeave() {
|
||||
setDndState(idle);
|
||||
},
|
||||
onDrop() {
|
||||
setDndState(idle);
|
||||
},
|
||||
})
|
||||
);
|
||||
}, [entityIdentifier]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={ref}
|
||||
position="relative"
|
||||
flexDir="column"
|
||||
w="full"
|
||||
@@ -31,6 +143,9 @@ export const CanvasEntityContainer = memo((props: PropsWithChildren) => {
|
||||
borderRadius="base"
|
||||
>
|
||||
{props.children}
|
||||
{dndState.type === 'is-dragging-over' && dndState.closestEdge ? (
|
||||
<DropIndicator edge={dndState.closestEdge} gap="8px" />
|
||||
) : null}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||
import { extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
||||
import { reorderWithEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/util/reorder-with-edge';
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Button, Collapse, Flex, Icon, Spacer, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { useBoolean } from 'common/hooks/useBoolean';
|
||||
import { fixTooltipCloseOnScrollStyles } from 'common/util/fixTooltipCloseOnScrollStyles';
|
||||
@@ -8,24 +12,97 @@ import { CanvasEntityMergeVisibleButton } from 'features/controlLayers/component
|
||||
import { CanvasEntityTypeIsHiddenToggle } from 'features/controlLayers/components/common/CanvasEntityTypeIsHiddenToggle';
|
||||
import { useEntityTypeInformationalPopover } from 'features/controlLayers/hooks/useEntityTypeInformationalPopover';
|
||||
import { useEntityTypeTitle } from 'features/controlLayers/hooks/useEntityTypeTitle';
|
||||
import { type CanvasEntityIdentifier, isRenderableEntityType } from 'features/controlLayers/store/types';
|
||||
import { entitiesReordered } from 'features/controlLayers/store/canvasSlice';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { isRenderableEntityType } from 'features/controlLayers/store/types';
|
||||
import { Dnd } from 'features/dnd/dnd';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { memo, useEffect } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { PiCaretDownBold } from 'react-icons/pi';
|
||||
|
||||
type Props = PropsWithChildren<{
|
||||
isSelected: boolean;
|
||||
type: CanvasEntityIdentifier['type'];
|
||||
entityIdentifiers: CanvasEntityIdentifier[];
|
||||
}>;
|
||||
|
||||
const _hover: SystemStyleObject = {
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
export const CanvasEntityGroupList = memo(({ isSelected, type, children }: Props) => {
|
||||
export const CanvasEntityGroupList = memo(({ isSelected, type, children, entityIdentifiers }: Props) => {
|
||||
const title = useEntityTypeTitle(type);
|
||||
const informationalPopoverFeature = useEntityTypeInformationalPopover(type);
|
||||
const collapse = useBoolean(true);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
return monitorForElements({
|
||||
canMonitor({ source }) {
|
||||
if (!Dnd.Source.singleCanvasEntity.typeGuard(source.data)) {
|
||||
return false;
|
||||
}
|
||||
if (source.data.payload.entityIdentifier.type !== type) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
onDrop({ location, source }) {
|
||||
const target = location.current.dropTargets[0];
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceData = source.data;
|
||||
const targetData = target.data;
|
||||
|
||||
if (
|
||||
!Dnd.Source.singleCanvasEntity.typeGuard(sourceData) ||
|
||||
!Dnd.Source.singleCanvasEntity.typeGuard(targetData)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexOfSource = entityIdentifiers.findIndex(
|
||||
(entityIdentifier) => entityIdentifier.id === sourceData.payload.entityIdentifier.id
|
||||
);
|
||||
const indexOfTarget = entityIdentifiers.findIndex(
|
||||
(entityIdentifier) => entityIdentifier.id === targetData.payload.entityIdentifier.id
|
||||
);
|
||||
|
||||
if (indexOfTarget < 0 || indexOfSource < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const closestEdgeOfTarget = extractClosestEdge(targetData);
|
||||
|
||||
// Using `flushSync` so we can query the DOM straight after this line
|
||||
flushSync(() => {
|
||||
dispatch(
|
||||
entitiesReordered({
|
||||
type,
|
||||
entityIdentifiers: reorderWithEdge({
|
||||
list: entityIdentifiers,
|
||||
startIndex: indexOfSource,
|
||||
indexOfTarget,
|
||||
closestEdgeOfTarget,
|
||||
axis: 'vertical',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
// // Being simple and just querying for the task after the drop.
|
||||
// // We could use react context to register the element in a lookup,
|
||||
// // and then we could retrieve that element after the drop and use
|
||||
// // `triggerPostMoveFlash`. But this gets the job done.
|
||||
// const element = document.querySelector(`[data-task-id="${sourceData.taskId}"]`);
|
||||
// if (element instanceof HTMLElement) {
|
||||
// triggerPostMoveFlash(element);
|
||||
// }
|
||||
},
|
||||
});
|
||||
}, [dispatch, entityIdentifiers, type]);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" w="full">
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
selectRegionalGuidanceReferenceImage,
|
||||
} from 'features/controlLayers/store/selectors';
|
||||
import type {
|
||||
CanvasEntityStateFromType,
|
||||
CanvasEntityType,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasMetadata,
|
||||
FillStyle,
|
||||
@@ -1345,6 +1347,46 @@ export const canvasSlice = createSlice({
|
||||
}
|
||||
moveToStart(selectAllEntitiesOfType(state, entity.type), entity);
|
||||
},
|
||||
entitiesReordered: <T extends CanvasEntityType>(
|
||||
state: CanvasState,
|
||||
action: PayloadAction<{ type: T; entityIdentifiers: CanvasEntityIdentifier<T>[] }>
|
||||
) => {
|
||||
const { type, entityIdentifiers } = action.payload;
|
||||
|
||||
switch (type) {
|
||||
case 'raster_layer': {
|
||||
state.rasterLayers.entities = reorderEntities(
|
||||
state.rasterLayers.entities,
|
||||
entityIdentifiers as CanvasEntityIdentifier<'raster_layer'>[]
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'control_layer':
|
||||
state.controlLayers.entities = reorderEntities(
|
||||
state.controlLayers.entities,
|
||||
entityIdentifiers as CanvasEntityIdentifier<'control_layer'>[]
|
||||
);
|
||||
break;
|
||||
case 'inpaint_mask':
|
||||
state.inpaintMasks.entities = reorderEntities(
|
||||
state.inpaintMasks.entities,
|
||||
entityIdentifiers as CanvasEntityIdentifier<'inpaint_mask'>[]
|
||||
);
|
||||
break;
|
||||
case 'regional_guidance':
|
||||
state.regionalGuidance.entities = reorderEntities(
|
||||
state.regionalGuidance.entities,
|
||||
entityIdentifiers as CanvasEntityIdentifier<'regional_guidance'>[]
|
||||
);
|
||||
break;
|
||||
case 'reference_image':
|
||||
state.referenceImages.entities = reorderEntities(
|
||||
state.referenceImages.entities,
|
||||
entityIdentifiers as CanvasEntityIdentifier<'reference_image'>[]
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
entityOpacityChanged: (state, action: PayloadAction<EntityIdentifierPayload<{ opacity: number }>>) => {
|
||||
const { entityIdentifier, opacity } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
@@ -1471,6 +1513,7 @@ export const {
|
||||
entityArrangedBackwardOne,
|
||||
entityArrangedToBack,
|
||||
entityOpacityChanged,
|
||||
entitiesReordered,
|
||||
// allEntitiesDeleted, // currently unused
|
||||
allEntitiesOfTypeIsHiddenToggled,
|
||||
// bbox
|
||||
@@ -1604,3 +1647,17 @@ function actionsThrottlingFilter(action: UnknownAction) {
|
||||
}, THROTTLE_MS);
|
||||
return true;
|
||||
}
|
||||
|
||||
const reorderEntities = <T extends CanvasEntityType>(
|
||||
entities: CanvasEntityStateFromType<T>[],
|
||||
sortedEntityIdentifiers: CanvasEntityIdentifier<T>[]
|
||||
) => {
|
||||
const sortedEntities: CanvasEntityStateFromType<T>[] = [];
|
||||
for (const { id } of sortedEntityIdentifiers.toReversed()) {
|
||||
const entity = entities.find((entity) => entity.id === id);
|
||||
if (entity) {
|
||||
sortedEntities.push(entity);
|
||||
}
|
||||
}
|
||||
return sortedEntities;
|
||||
};
|
||||
|
||||
@@ -466,6 +466,8 @@ export type EntityRasterizedPayload = EntityIdentifierPayload<{
|
||||
|
||||
export type GenerationMode = 'txt2img' | 'img2img' | 'inpaint' | 'outpaint';
|
||||
|
||||
export type CanvasEntityStateFromType<T extends CanvasEntityType> = Extract<CanvasEntityState, { type: T }>;
|
||||
|
||||
export function isRenderableEntityType(
|
||||
entityType: CanvasEntityState['type']
|
||||
): entityType is CanvasRenderableEntityState['type'] {
|
||||
|
||||
Reference in New Issue
Block a user