diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx
index 2dfd08c1b0..c1308d98ec 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx
@@ -5,70 +5,73 @@ import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
-import { CALayer } from 'features/controlLayers/components/CALayer/CALayer';
+import { CA } from 'features/controlLayers/components/ControlAdapter/CA';
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
-import { IILayer } from 'features/controlLayers/components/IILayer/IILayer';
-import { IPAEntity } from 'features/controlLayers/components/IPALayer/IPALayer';
-import { Layer } from 'features/controlLayers/components/RasterLayer/RasterLayer';
-import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
-import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
-import type { LayerData } from 'features/controlLayers/store/types';
-import { isRenderableLayer } from 'features/controlLayers/store/types';
-import { partition } from 'lodash-es';
-import { memo } from 'react';
+import { IPA } from 'features/controlLayers/components/IPAdapter/IPA';
+import { Layer } from 'features/controlLayers/components/Layer/Layer';
+import { RG } from 'features/controlLayers/components/RegionalGuidance/RG';
+import { mapId } from 'features/controlLayers/konva/util';
+import { selectControlAdaptersV2Slice } from 'features/controlLayers/store/controlAdaptersSlice';
+import { selectIPAdaptersSlice } from 'features/controlLayers/store/ipAdaptersSlice';
+import { selectLayersSlice } from 'features/controlLayers/store/layersSlice';
+import { selectRegionalGuidanceSlice } from 'features/controlLayers/store/regionalGuidanceSlice';
+import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-const selectLayerIdTypePairs = createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
- const [renderableLayers, ipAdapterLayers] = partition(canvasV2.layers, isRenderableLayer);
- return [...ipAdapterLayers, ...renderableLayers].map((l) => ({ id: l.id, type: l.type })).reverse();
+const selectRGIds = createMemoizedSelector(selectRegionalGuidanceSlice, (rgState) => {
+ return rgState.regions.map(mapId).reverse();
+});
+
+const selectCAIds = createMemoizedSelector(selectControlAdaptersV2Slice, (caState) => {
+ return caState.controlAdapters.map(mapId).reverse();
+});
+
+const selectIPAIds = createMemoizedSelector(selectIPAdaptersSlice, (ipaState) => {
+ return ipaState.ipAdapters.map(mapId).reverse();
+});
+
+const selectLayerIds = createMemoizedSelector(selectLayersSlice, (layersState) => {
+ return layersState.layers.map(mapId).reverse();
});
export const ControlLayersPanelContent = memo(() => {
const { t } = useTranslation();
- const layerIdTypePairs = useAppSelector(selectLayerIdTypePairs);
+ const rgIds = useAppSelector(selectRGIds);
+ const caIds = useAppSelector(selectCAIds);
+ const ipaIds = useAppSelector(selectIPAIds);
+ const layerIds = useAppSelector(selectLayerIds);
+ const entityCount = useMemo(
+ () => rgIds.length + caIds.length + ipaIds.length + layerIds.length,
+ [rgIds.length, caIds.length, ipaIds.length, layerIds.length]
+ );
+
return (
- {layerIdTypePairs.length > 0 && (
+ {entityCount > 0 && (
- {layerIdTypePairs.map(({ id, type }) => (
-
+ {rgIds.map((id) => (
+
+ ))}
+ {caIds.map((id) => (
+
+ ))}
+ {ipaIds.map((id) => (
+
+ ))}
+ {layerIds.map((id) => (
+
))}
)}
- {layerIdTypePairs.length === 0 && }
+ {entityCount === 0 && }
);
});
ControlLayersPanelContent.displayName = 'ControlLayersPanelContent';
-
-type LayerWrapperProps = {
- id: string;
- type: LayerData['type'];
-};
-
-const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => {
- if (type === 'regional_guidance_layer') {
- return ;
- }
- if (type === 'control_adapter_layer') {
- return ;
- }
- if (type === 'ip_adapter_layer') {
- return ;
- }
- if (type === 'initial_image_layer') {
- return ;
- }
- if (type === 'raster_layer') {
- return ;
- }
-});
-
-LayerWrapper.displayName = 'LayerWrapper';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx
index 15da096680..df2e911c50 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx
@@ -1,24 +1,18 @@
/* eslint-disable i18next/no-literal-string */
import { Flex } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { BrushColorPicker } from 'features/controlLayers/components/BrushColorPicker';
-import { BrushWidth } from 'features/controlLayers/components/BrushSize';
+import { useAppSelector } from 'app/store/storeHooks';
+import { BrushWidth } from 'features/controlLayers/components/BrushWidth';
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
+import { EraserWidth } from 'features/controlLayers/components/EraserWidth';
+import { FillColorPicker } from 'features/controlLayers/components/FillColorPicker';
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
-import { $tool } from 'features/controlLayers/store/controlLayersSlice';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
-import { memo, useMemo } from 'react';
+import { memo } from 'react';
export const ControlLayersToolbar = memo(() => {
- const tool = useStore($tool);
- const withBrushSize = useMemo(() => {
- return tool === 'brush' || tool === 'eraser';
- }, [tool]);
- const withBrushColor = useMemo(() => {
- return tool === 'brush' || tool === 'rect';
- }, [tool]);
+ const tool = useAppSelector((s) => s.canvasV2.tool.selected);
return (
@@ -28,8 +22,9 @@ export const ControlLayersToolbar = memo(() => {
- {withBrushSize && }
- {withBrushColor && }
+ {tool === 'brush' && }
+ {tool === 'eraser' && }
+
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx
index e69b83fa79..f43ffc7725 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx
@@ -1,6 +1,10 @@
import { Button } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { allLayersDeleted } from 'features/controlLayers/store/controlLayersSlice';
+import { caAllDeleted } from 'features/controlLayers/store/controlAdaptersSlice';
+import { ipaAllDeleted } from 'features/controlLayers/store/ipAdaptersSlice';
+import { layerAllDeleted } from 'features/controlLayers/store/layersSlice';
+import { rgAllDeleted } from 'features/controlLayers/store/regionalGuidanceSlice';
+import { selectEntityCount } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiTrashSimpleBold } from 'react-icons/pi';
@@ -8,9 +12,12 @@ import { PiTrashSimpleBold } from 'react-icons/pi';
export const DeleteAllLayersButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const isDisabled = useAppSelector((s) => s.canvasV2.layers.length === 0);
+ const entityCount = useAppSelector(selectEntityCount);
const onClick = useCallback(() => {
- dispatch(allLayersDeleted());
+ dispatch(caAllDeleted());
+ dispatch(rgAllDeleted());
+ dispatch(ipaAllDeleted());
+ dispatch(layerAllDeleted());
}, [dispatch]);
return (
@@ -19,7 +26,7 @@ export const DeleteAllLayersButton = memo(() => {
leftIcon={}
variant="ghost"
colorScheme="error"
- isDisabled={isDisabled}
+ isDisabled={entityCount > 0}
data-testid="control-layers-delete-all-layers-button"
>
{t('controlLayers.deleteAll')}
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersSlice.ts
index 8faa0a900b..07438575e2 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersSlice.ts
@@ -246,6 +246,9 @@ export const controlAdaptersV2Slice = createSlice({
}
ca.beginEndStepPct = beginEndStepPct;
},
+ caAllDeleted: (state) => {
+ state.controlAdapters = [];
+ },
},
});
@@ -270,6 +273,7 @@ export const {
caProcessorPendingBatchIdChanged,
caWeightChanged,
caBeginEndStepPctChanged,
+ caAllDeleted,
} = controlAdaptersV2Slice.actions;
export const selectControlAdaptersV2Slice = (state: RootState) => state.controlAdaptersV2;
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersSlice.ts
index 9e9a9541ae..cdde6078c8 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/ipAdaptersSlice.ts
@@ -114,6 +114,9 @@ export const ipAdaptersSlice = createSlice({
}
ipa.beginEndStepPct = beginEndStepPct;
},
+ ipaAllDeleted: (state) => {
+ state.ipAdapters = [];
+ },
},
});
@@ -128,6 +131,7 @@ export const {
ipaCLIPVisionModelChanged,
ipaWeightChanged,
ipaBeginEndStepPctChanged,
+ ipaAllDeleted,
} = ipAdaptersSlice.actions;
export const selectIPAdaptersSlice = (state: RootState) => state.ipAdapters;
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/layersSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/layersSlice.ts
index b25a700f3f..66567dc47c 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/layersSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/layersSlice.ts
@@ -234,6 +234,9 @@ export const layersSlice = createSlice({
},
prepare: (payload: ImageObjectAddedArg) => ({ payload: { ...payload, imageId: uuidv4() } }),
},
+ layerAllDeleted: (state) => {
+ state.layers = [];
+ },
},
});
@@ -254,6 +257,7 @@ export const {
layerLinePointAdded,
layerRectAdded,
layerImageAdded,
+ layerAllDeleted,
} = layersSlice.actions;
export const selectLayersSlice = (state: RootState) => state.layers;
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/regionalGuidanceSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/regionalGuidanceSlice.ts
index 2d5c16d421..0b341d9399 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/regionalGuidanceSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/regionalGuidanceSlice.ts
@@ -398,6 +398,9 @@ export const regionalGuidanceSlice = createSlice({
},
prepare: (payload: RectShapeAddedArg) => ({ payload: { ...payload, rectId: uuidv4() } }),
},
+ rgAllDeleted: (state) => {
+ state.regions = [];
+ },
},
});
@@ -431,6 +434,7 @@ export const {
rgEraserLineAdded,
rgLinePointAdded,
rgRectAdded,
+ rgAllDeleted,
} = regionalGuidanceSlice.actions;
export const selectRegionalGuidanceSlice = (state: RootState) => state.regionalGuidance;
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts b/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts
new file mode 100644
index 0000000000..44ac9f32b8
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts
@@ -0,0 +1,17 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { selectControlAdaptersV2Slice } from 'features/controlLayers/store/controlAdaptersSlice';
+import { selectIPAdaptersSlice } from 'features/controlLayers/store/ipAdaptersSlice';
+import { selectLayersSlice } from 'features/controlLayers/store/layersSlice';
+import { selectRegionalGuidanceSlice } from 'features/controlLayers/store/regionalGuidanceSlice';
+
+export const selectEntityCount = createSelector(
+ selectRegionalGuidanceSlice,
+ selectControlAdaptersV2Slice,
+ selectIPAdaptersSlice,
+ selectLayersSlice,
+ (rgState, caState, ipaState, layersState) => {
+ return (
+ rgState.regions.length + caState.controlAdapters.length + ipaState.ipAdapters.length + layersState.layers.length
+ );
+ }
+);