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 + ); + } +);