From 9f2c815e13dd6cde6bc6bb489a0bcc47e1864da3 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Thu, 15 Aug 2024 11:29:19 +1000
Subject: [PATCH] feat(ui): revise entity menus
---
invokeai/frontend/web/public/locales/en.json | 7 +-
.../components/AddLayerButton.tsx | 30 ++-
.../components/AddPromptButtons.tsx | 19 +-
.../components/ControlLayer/ControlLayer.tsx | 2 -
.../ControlLayer/ControlLayerActionsMenu.tsx | 17 --
.../ControlLayerControlAdapter.tsx | 8 +-
...ControlLayerControlAdapterControlMode.tsx} | 4 +-
.../ControlLayerControlAdapterModel.tsx} | 4 +-
.../ControlLayer/ControlLayerMenuItems.tsx | 23 ++
.../ControlLayerMenuItemsControlToRaster.tsx | 25 +++
.../components/ControlLayersPanelContent.tsx | 4 +-
.../components/InpaintMask/InpaintMask.tsx | 2 -
.../InpaintMask/InpaintMaskActionsMenu.tsx | 17 --
.../InpaintMask/InpaintMaskMenuItems.tsx | 12 ++
.../components/RasterLayer/RasterLayer.tsx | 2 -
.../RasterLayer/RasterLayerActionsMenu.tsx | 17 --
.../RasterLayer/RasterLayerMenuItems.tsx | 23 ++
.../RasterLayerMenuItemsRasterToControl.tsx | 28 +++
.../RegionalGuidance/RegionalGuidance.tsx | 2 -
.../RegionalGuidanceActionsMenu.tsx | 62 ------
.../RegionalGuidanceMenuItems.tsx | 21 ++
...uidanceMenuItemsAddPromptsAndIPAdapter.tsx | 58 +++++
...sButton.tsx => ResetAllEntitiesButton.tsx} | 17 +-
.../common/CanvasEntityActionMenuItems.tsx | 200 ------------------
.../components/common/CanvasEntityHeader.tsx | 52 ++++-
.../common/CanvasEntityMenuItemsArrange.tsx | 95 +++++++++
.../common/CanvasEntityMenuItemsDelete.tsx | 25 +++
.../common/CanvasEntityMenuItemsFilter.tsx | 22 ++
.../common/CanvasEntityMenuItemsReset.tsx | 25 +++
.../controlLayers/hooks/addLayerHooks.ts | 89 --------
.../hooks/useLayerControlAdapter.ts | 24 ++-
.../controlLayers/store/canvasV2Slice.ts | 12 +-
32 files changed, 475 insertions(+), 473 deletions(-)
delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerActionsMenu.tsx
rename invokeai/frontend/web/src/features/controlLayers/components/{ControlAdapter/ControlAdapterControlModeSelect.tsx => ControlLayer/ControlLayerControlAdapterControlMode.tsx} (90%)
rename invokeai/frontend/web/src/features/controlLayers/components/{ControlAdapter/ControlAdapterModel.tsx => ControlLayer/ControlLayerControlAdapterModel.tsx} (91%)
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItems.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster.tsx
delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskActionsMenu.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMenuItems.tsx
delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerActionsMenu.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItems.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl.tsx
delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceActionsMenu.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItems.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.tsx
rename invokeai/frontend/web/src/features/controlLayers/components/{DeleteAllLayersButton.tsx => ResetAllEntitiesButton.tsx} (56%)
delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityActionMenuItems.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsArrange.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDelete.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsFilter.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsReset.tsx
delete mode 100644 invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 42b7f80fc8..b137b45f1a 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -1646,7 +1646,7 @@
"storeNotInitialized": "Store is not initialized"
},
"controlLayers": {
- "deleteAll": "Delete All",
+ "resetAll": "Reset All",
"addLayer": "Add Layer",
"moveToFront": "Move to Front",
"moveToBack": "Move to Back",
@@ -1694,7 +1694,10 @@
"layers_other": "Layers",
"objects_zero": "empty",
"objects_one": "{{count}} object",
- "objects_other": "{{count}} objects"
+ "objects_other": "{{count}} objects",
+ "filter": "Filter",
+ "convertToControlLayer": "Convert to Control Layer",
+ "convertToRasterLayer": "Convert to Raster Layer"
},
"upscaling": {
"upscale": "Upscale",
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx
index 0234f49ab3..433faf4c69 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/AddLayerButton.tsx
@@ -1,7 +1,7 @@
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
-import { useAddCALayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
-import { rasterLayerAdded, rgAdded } from 'features/controlLayers/store/canvasV2Slice';
+import { useDefaultControlAdapter, useDefaultIPAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
+import { controlLayerAdded, ipaAdded, rasterLayerAdded, rgAdded } from 'features/controlLayers/store/canvasV2Slice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi';
@@ -9,14 +9,20 @@ import { PiPlusBold } from 'react-icons/pi';
export const AddLayerButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const [addCALayer, isAddCALayerDisabled] = useAddCALayer();
- const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
+ const defaultControlAdapter = useDefaultControlAdapter();
+ const defaultIPAdapter = useDefaultIPAdapter();
const addRGLayer = useCallback(() => {
dispatch(rgAdded());
}, [dispatch]);
const addRasterLayer = useCallback(() => {
dispatch(rasterLayerAdded({ isSelected: true }));
}, [dispatch]);
+ const addControlLayer = useCallback(() => {
+ dispatch(controlLayerAdded({ isSelected: true, overrides: { controlAdapter: defaultControlAdapter } }));
+ }, [defaultControlAdapter, dispatch]);
+ const addIPAdapter = useCallback(() => {
+ dispatch(ipaAdded({ config: defaultIPAdapter }));
+ }, [defaultIPAdapter, dispatch]);
return (
);
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx
index 7256d6d9e0..4b0804c982 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/AddPromptButtons.tsx
@@ -1,8 +1,10 @@
import { Button, Flex } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
+import { useDefaultIPAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
+import { nanoid } from 'features/controlLayers/konva/util';
import {
+ rgIPAdapterAdded,
rgNegativePromptChanged,
rgPositivePromptChanged,
selectCanvasV2Slice,
@@ -18,7 +20,7 @@ type AddPromptButtonProps = {
export const AddPromptButtons = ({ id }: AddPromptButtonProps) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
+ const defaultIPAdapter = useDefaultIPAdapter();
const selectValidActions = useMemo(
() =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
@@ -37,6 +39,11 @@ export const AddPromptButtons = ({ id }: AddPromptButtonProps) => {
const addNegativePrompt = useCallback(() => {
dispatch(rgNegativePromptChanged({ id, prompt: '' }));
}, [dispatch, id]);
+ const addIPAdapter = useCallback(() => {
+ dispatch(
+ rgIPAdapterAdded({ id, ipAdapter: { ...defaultIPAdapter, id: nanoid(), type: 'ip_adapter', isEnabled: true } })
+ );
+ }, [defaultIPAdapter, dispatch, id]);
return (
@@ -58,13 +65,7 @@ export const AddPromptButtons = ({ id }: AddPromptButtonProps) => {
>
{t('common.negativePrompt')}
- }
- onClick={addIPAdapter}
- isDisabled={isAddIPAdapterDisabled}
- >
+ } onClick={addIPAdapter}>
{t('common.ipAdapter')}
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayer.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayer.tsx
index 046d43f46e..af47649a20 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayer.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayer.tsx
@@ -5,7 +5,6 @@ import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/com
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
-import { ControlLayerActionsMenu } from 'features/controlLayers/components/ControlLayer/ControlLayerActionsMenu';
import { ControlLayerControlAdapter } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapter';
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
@@ -25,7 +24,6 @@ export const ControlLayer = memo(({ id }: Props) => {
-
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerActionsMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerActionsMenu.tsx
deleted file mode 100644
index 3f2fa45e06..0000000000
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerActionsMenu.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Menu, MenuList } from '@invoke-ai/ui-library';
-import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
-import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
-import { memo } from 'react';
-
-export const ControlLayerActionsMenu = memo(() => {
- return (
-
- );
-});
-
-ControlLayerActionsMenu.displayName = 'ControlLayerActionsMenu';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx
index 3e5d1d7fee..435e90cdd4 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx
@@ -2,8 +2,8 @@ import { Flex } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
import { Weight } from 'features/controlLayers/components/common/Weight';
-import { ControlAdapterControlModeSelect } from 'features/controlLayers/components/ControlAdapter/ControlAdapterControlModeSelect';
-import { ControlAdapterModel } from 'features/controlLayers/components/ControlAdapter/ControlAdapterModel';
+import { ControlLayerControlAdapterControlMode } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterControlMode';
+import { ControlLayerControlAdapterModel } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterModel';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useControlLayerControlAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
import {
@@ -51,11 +51,11 @@ export const ControlLayerControlAdapter = memo(() => {
return (
-
+
{controlAdapter.type === 'controlnet' && (
-
+
)}
);
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterControlModeSelect.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapterControlMode.tsx
similarity index 90%
rename from invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterControlModeSelect.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapterControlMode.tsx
index e3f03b6d48..e0f57e6df8 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterControlModeSelect.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapterControlMode.tsx
@@ -12,7 +12,7 @@ type Props = {
onChange: (controlMode: ControlModeV2) => void;
};
-export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }: Props) => {
+export const ControlLayerControlAdapterControlMode = memo(({ controlMode, onChange }: Props) => {
const { t } = useTranslation();
const CONTROL_MODE_DATA = useMemo(
() => [
@@ -57,4 +57,4 @@ export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }:
);
});
-ControlAdapterControlModeSelect.displayName = 'ControlAdapterControlModeSelect';
+ControlLayerControlAdapterControlMode.displayName = 'ControlLayerControlAdapterControlMode';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterModel.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapterModel.tsx
similarity index 91%
rename from invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterModel.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapterModel.tsx
index 13e4b17e90..f50218e4aa 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterModel.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapterModel.tsx
@@ -11,7 +11,7 @@ type Props = {
onChange: (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => void;
};
-export const ControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }: Props) => {
+export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }: Props) => {
const { t } = useTranslation();
const currentBaseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
@@ -60,4 +60,4 @@ export const ControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }:
);
});
-ControlAdapterModel.displayName = 'ControlAdapterModel';
+ControlLayerControlAdapterModel.displayName = 'ControlLayerControlAdapterModel';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItems.tsx
new file mode 100644
index 0000000000..49f2558906
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItems.tsx
@@ -0,0 +1,23 @@
+import { MenuDivider } from '@invoke-ai/ui-library';
+import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
+import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
+import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
+import { CanvasEntityMenuItemsReset } from 'features/controlLayers/components/common/CanvasEntityMenuItemsReset';
+import { ControlLayerMenuItemsControlToRaster } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster';
+import { memo } from 'react';
+
+export const ControlLayerMenuItems = memo(() => {
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+});
+
+ControlLayerMenuItems.displayName = 'ControlLayerMenuItems';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster.tsx
new file mode 100644
index 0000000000..e64df4e9e5
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster.tsx
@@ -0,0 +1,25 @@
+import { MenuItem } from '@invoke-ai/ui-library';
+import { useAppDispatch } from 'app/store/storeHooks';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
+import { controlLayerConvertedToRasterLayer } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiLightningBold } from 'react-icons/pi';
+
+export const ControlLayerMenuItemsControlToRaster = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const entityIdentifier = useEntityIdentifierContext();
+
+ const convertControlLayerToRasterLayer = useCallback(() => {
+ dispatch(controlLayerConvertedToRasterLayer({ id: entityIdentifier.id }));
+ }, [dispatch, entityIdentifier.id]);
+
+ return (
+ }>
+ {t('controlLayers.convertToRasterLayer')}
+
+ );
+});
+
+ControlLayerMenuItemsControlToRaster.displayName = 'ControlLayerMenuItemsControlToRaster';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx
index ddaa009055..195d774707 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersPanelContent.tsx
@@ -3,8 +3,8 @@ import { Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList';
-import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
import { Filter } from 'features/controlLayers/components/Filters/Filter';
+import { ResetAllEntitiesButton } from 'features/controlLayers/components/ResetAllEntitiesButton';
import { $filteringEntity } from 'features/controlLayers/store/canvasV2Slice';
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
import { memo } from 'react';
@@ -18,7 +18,7 @@ export const ControlLayersPanelContent = memo(() => {
-
+
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMask.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMask.tsx
index 04b4f88ee6..ba3c3dee82 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMask.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMask.tsx
@@ -5,7 +5,6 @@ import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/com
import { CanvasEntityGroupTitle } from 'features/controlLayers/components/common/CanvasEntityGroupTitle';
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
-import { InpaintMaskActionsMenu } from 'features/controlLayers/components/InpaintMask/InpaintMaskActionsMenu';
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { memo, useMemo } from 'react';
@@ -28,7 +27,6 @@ export const InpaintMask = memo(() => {
-
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskActionsMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskActionsMenu.tsx
deleted file mode 100644
index 5ce4024195..0000000000
--- a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskActionsMenu.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Menu, MenuList } from '@invoke-ai/ui-library';
-import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
-import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
-import { memo } from 'react';
-
-export const InpaintMaskActionsMenu = memo(() => {
- return (
-
- );
-});
-
-InpaintMaskActionsMenu.displayName = 'InpaintMaskActionsMenu';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMenuItems.tsx
new file mode 100644
index 0000000000..d0d1a7dc10
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMenuItems.tsx
@@ -0,0 +1,12 @@
+import { CanvasEntityMenuItemsReset } from 'features/controlLayers/components/common/CanvasEntityMenuItemsReset';
+import { memo } from 'react';
+
+export const InpaintMaskMenuItems = memo(() => {
+ return (
+ <>
+
+ >
+ );
+});
+
+InpaintMaskMenuItems.displayName = 'InpaintMaskMenuItems';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayer.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayer.tsx
index e12ce65b4a..303d4191ed 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayer.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayer.tsx
@@ -4,7 +4,6 @@ import { CanvasEntityDeleteButton } from 'features/controlLayers/components/comm
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
-import { RasterLayerActionsMenu } from 'features/controlLayers/components/RasterLayer/RasterLayerActionsMenu';
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { memo, useMemo } from 'react';
@@ -23,7 +22,6 @@ export const RasterLayer = memo(({ id }: Props) => {
-
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerActionsMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerActionsMenu.tsx
deleted file mode 100644
index 576c939ad2..0000000000
--- a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerActionsMenu.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Menu, MenuList } from '@invoke-ai/ui-library';
-import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
-import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
-import { memo } from 'react';
-
-export const RasterLayerActionsMenu = memo(() => {
- return (
-
- );
-});
-
-RasterLayerActionsMenu.displayName = 'RasterLayerActionsMenu';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItems.tsx
new file mode 100644
index 0000000000..2ad6a48f10
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItems.tsx
@@ -0,0 +1,23 @@
+import { MenuDivider } from '@invoke-ai/ui-library';
+import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
+import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
+import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
+import { CanvasEntityMenuItemsReset } from 'features/controlLayers/components/common/CanvasEntityMenuItemsReset';
+import { RasterLayerMenuItemsRasterToControl } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl';
+import { memo } from 'react';
+
+export const RasterLayerMenuItems = memo(() => {
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+});
+
+RasterLayerMenuItems.displayName = 'RasterLayerMenuItems';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl.tsx
new file mode 100644
index 0000000000..9513f0409c
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl.tsx
@@ -0,0 +1,28 @@
+import { MenuItem } from '@invoke-ai/ui-library';
+import { useAppDispatch } from 'app/store/storeHooks';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
+import { useDefaultControlAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
+import { rasterLayerConvertedToControlLayer } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiLightningBold } from 'react-icons/pi';
+
+export const RasterLayerMenuItemsRasterToControl = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const entityIdentifier = useEntityIdentifierContext();
+
+ const defaultControlAdapter = useDefaultControlAdapter();
+
+ const convertRasterLayerToControlLayer = useCallback(() => {
+ dispatch(rasterLayerConvertedToControlLayer({ id: entityIdentifier.id, controlAdapter: defaultControlAdapter }));
+ }, [dispatch, defaultControlAdapter, entityIdentifier.id]);
+
+ return (
+ }>
+ {t('controlLayers.convertToControlLayer')}
+
+ );
+});
+
+RasterLayerMenuItemsRasterToControl.displayName = 'RasterLayerMenuItemsRasterToControl';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidance.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidance.tsx
index eeed5126a9..fd368e53e1 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidance.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidance.tsx
@@ -4,7 +4,6 @@ import { CanvasEntityDeleteButton } from 'features/controlLayers/components/comm
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
-import { RegionalGuidanceActionsMenu } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceActionsMenu';
import { RegionalGuidanceBadges } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges';
import { RegionalGuidanceSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings';
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
@@ -30,7 +29,6 @@ export const RegionalGuidance = memo(({ id }: Props) => {
-
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceActionsMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceActionsMenu.tsx
deleted file mode 100644
index e8d9db8885..0000000000
--- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceActionsMenu.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Menu, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
-import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
-import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
-import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
-import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
-import {
- rgNegativePromptChanged,
- rgPositivePromptChanged,
- selectCanvasV2Slice,
-} from 'features/controlLayers/store/canvasV2Slice';
-import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
-import { memo, useCallback, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiPlusBold } from 'react-icons/pi';
-
-export const RegionalGuidanceActionsMenu = memo(() => {
- const { id } = useEntityIdentifierContext();
- const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const [onAddIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
- const selectActionsValidity = useMemo(
- () =>
- createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
- const rg = selectRGOrThrow(canvasV2, id);
- return {
- isAddPositivePromptDisabled: rg.positivePrompt === null,
- isAddNegativePromptDisabled: rg.negativePrompt === null,
- };
- }),
- [id]
- );
- const actions = useAppSelector(selectActionsValidity);
- const onAddPositivePrompt = useCallback(() => {
- dispatch(rgPositivePromptChanged({ id: id, prompt: '' }));
- }, [dispatch, id]);
- const onAddNegativePrompt = useCallback(() => {
- dispatch(rgNegativePromptChanged({ id: id, prompt: '' }));
- }, [dispatch, id]);
-
- return (
-
- );
-});
-
-RegionalGuidanceActionsMenu.displayName = 'RegionalGuidanceActionsMenu';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItems.tsx
new file mode 100644
index 0000000000..9c495bb6db
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItems.tsx
@@ -0,0 +1,21 @@
+import { MenuDivider } from '@invoke-ai/ui-library';
+import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
+import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
+import { CanvasEntityMenuItemsReset } from 'features/controlLayers/components/common/CanvasEntityMenuItemsReset';
+import { RegionalGuidanceMenuItemsAddPromptsAndIPAdapter } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter';
+import { memo } from 'react';
+
+export const RegionalGuidanceMenuItems = memo(() => {
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+});
+
+RegionalGuidanceMenuItems.displayName = 'RegionalGuidanceMenuItems';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.tsx
new file mode 100644
index 0000000000..66bcee04e9
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.tsx
@@ -0,0 +1,58 @@
+import { MenuItem } from '@invoke-ai/ui-library';
+import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
+import { useDefaultIPAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
+import { nanoid } from 'features/controlLayers/konva/util';
+import {
+ rgIPAdapterAdded,
+ rgNegativePromptChanged,
+ rgPositivePromptChanged,
+ selectCanvasV2Slice,
+} from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => {
+ const { id } = useEntityIdentifierContext();
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const defaultIPAdapter = useDefaultIPAdapter();
+ const selectValidActions = useMemo(
+ () =>
+ createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
+ const rg = canvasV2.regions.entities.find((rg) => rg.id === id);
+ return {
+ canAddPositivePrompt: rg?.positivePrompt === null,
+ canAddNegativePrompt: rg?.negativePrompt === null,
+ };
+ }),
+ [id]
+ );
+ const validActions = useAppSelector(selectValidActions);
+ const addPositivePrompt = useCallback(() => {
+ dispatch(rgPositivePromptChanged({ id: id, prompt: '' }));
+ }, [dispatch, id]);
+ const addNegativePrompt = useCallback(() => {
+ dispatch(rgNegativePromptChanged({ id: id, prompt: '' }));
+ }, [dispatch, id]);
+ const addIPAdapter = useCallback(() => {
+ dispatch(
+ rgIPAdapterAdded({ id, ipAdapter: { ...defaultIPAdapter, id: nanoid(), type: 'ip_adapter', isEnabled: true } })
+ );
+ }, [defaultIPAdapter, dispatch, id]);
+
+ return (
+ <>
+
+
+
+ >
+ );
+});
+
+RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.displayName = 'RegionalGuidanceMenuItemsExtra';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ResetAllEntitiesButton.tsx
similarity index 56%
rename from invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx
rename to invokeai/frontend/web/src/features/controlLayers/components/ResetAllEntitiesButton.tsx
index b6dc6b4df0..6851f7c5ab 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ResetAllEntitiesButton.tsx
@@ -1,21 +1,13 @@
import { Button } from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useAppDispatch } from 'app/store/storeHooks';
import { allEntitiesDeleted } from 'features/controlLayers/store/canvasV2Slice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiTrashSimpleBold } from 'react-icons/pi';
-export const DeleteAllLayersButton = memo(() => {
+export const ResetAllEntitiesButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const entityCount = useAppSelector((s) => {
- return (
- s.canvasV2.regions.entities.length +
- // s.canvasV2.controlAdapters.entities.length +
- s.canvasV2.ipAdapters.entities.length +
- s.canvasV2.rasterLayers.entities.length
- );
- });
const onClick = useCallback(() => {
dispatch(allEntitiesDeleted());
}, [dispatch]);
@@ -26,12 +18,11 @@ export const DeleteAllLayersButton = memo(() => {
leftIcon={}
variant="ghost"
colorScheme="error"
- isDisabled={entityCount === 0}
data-testid="control-layers-delete-all-layers-button"
>
- {t('controlLayers.deleteAll')}
+ {t('controlLayers.resetAll')}
);
});
-DeleteAllLayersButton.displayName = 'DeleteAllLayersButton';
+ResetAllEntitiesButton.displayName = 'ResetAllEntitiesButton';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityActionMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityActionMenuItems.tsx
deleted file mode 100644
index 1c8b61c01e..0000000000
--- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityActionMenuItems.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-import { MenuDivider, MenuItem } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
-import { useDefaultControlAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
-import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
-import {
- $filteringEntity,
- controlLayerConvertedToRasterLayer,
- entityArrangedBackwardOne,
- entityArrangedForwardOne,
- entityArrangedToBack,
- entityArrangedToFront,
- entityDeleted,
- entityReset,
- rasterLayerConvertedToControlLayer,
- selectCanvasV2Slice,
-} from 'features/controlLayers/store/canvasV2Slice';
-import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types';
-import { memo, useCallback, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-import {
- PiArrowCounterClockwiseBold,
- PiArrowDownBold,
- PiArrowLineDownBold,
- PiArrowLineUpBold,
- PiArrowUpBold,
- PiCheckBold,
- PiQuestionMarkBold,
- PiStarHalfBold,
- PiTrashSimpleBold,
-} from 'react-icons/pi';
-
-const getIndexAndCount = (
- canvasV2: CanvasV2State,
- { id, type }: CanvasEntityIdentifier
-): { index: number; count: number } => {
- if (type === 'raster_layer') {
- return {
- index: canvasV2.rasterLayers.entities.findIndex((entity) => entity.id === id),
- count: canvasV2.rasterLayers.entities.length,
- };
- } else if (type === 'control_layer') {
- return {
- index: canvasV2.controlLayers.entities.findIndex((entity) => entity.id === id),
- count: canvasV2.controlLayers.entities.length,
- };
- } else if (type === 'regional_guidance') {
- return {
- index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
- count: canvasV2.regions.entities.length,
- };
- } else {
- return {
- index: -1,
- count: 0,
- };
- }
-};
-
-export const CanvasEntityActionMenuItems = memo(() => {
- const { t } = useTranslation();
- const canvasManager = useStore($canvasManager);
- const dispatch = useAppDispatch();
- const entityIdentifier = useEntityIdentifierContext();
- const selectValidActions = useMemo(
- () =>
- createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
- const { index, count } = getIndexAndCount(canvasV2, entityIdentifier);
- return {
- canMoveForwardOne: index < count - 1,
- canMoveBackwardOne: index > 0,
- canMoveToFront: index < count - 1,
- canMoveToBack: index > 0,
- };
- }),
- [entityIdentifier]
- );
-
- const validActions = useAppSelector(selectValidActions);
-
- const isArrangeable = useMemo(
- () =>
- entityIdentifier.type === 'raster_layer' ||
- entityIdentifier.type === 'control_layer' ||
- entityIdentifier.type === 'regional_guidance',
- [entityIdentifier.type]
- );
-
- const isDeleteable = useMemo(
- () =>
- entityIdentifier.type === 'raster_layer' ||
- entityIdentifier.type === 'control_layer' ||
- entityIdentifier.type === 'regional_guidance',
- [entityIdentifier.type]
- );
-
- const isFilterable = useMemo(
- () => entityIdentifier.type === 'raster_layer' || entityIdentifier.type === 'control_layer',
- [entityIdentifier.type]
- );
-
- const isRasterLayer = useMemo(() => entityIdentifier.type === 'raster_layer', [entityIdentifier.type]);
-
- const isControlLayer = useMemo(() => entityIdentifier.type === 'control_layer', [entityIdentifier.type]);
-
- const defaultControlAdapter = useDefaultControlAdapter();
-
- const convertRasterLayerToControlLayer = useCallback(() => {
- dispatch(rasterLayerConvertedToControlLayer({ id: entityIdentifier.id, controlAdapter: defaultControlAdapter }));
- }, [dispatch, defaultControlAdapter, entityIdentifier.id]);
-
- const convertControlLayerToRasterLayer = useCallback(() => {
- dispatch(controlLayerConvertedToRasterLayer({ id: entityIdentifier.id }));
- }, [dispatch, entityIdentifier.id]);
-
- const deleteEntity = useCallback(() => {
- dispatch(entityDeleted({ entityIdentifier }));
- }, [dispatch, entityIdentifier]);
- const resetEntity = useCallback(() => {
- dispatch(entityReset({ entityIdentifier }));
- }, [dispatch, entityIdentifier]);
- const moveForwardOne = useCallback(() => {
- dispatch(entityArrangedForwardOne({ entityIdentifier }));
- }, [dispatch, entityIdentifier]);
- const moveToFront = useCallback(() => {
- dispatch(entityArrangedToFront({ entityIdentifier }));
- }, [dispatch, entityIdentifier]);
- const moveBackwardOne = useCallback(() => {
- dispatch(entityArrangedBackwardOne({ entityIdentifier }));
- }, [dispatch, entityIdentifier]);
- const moveToBack = useCallback(() => {
- dispatch(entityArrangedToBack({ entityIdentifier }));
- }, [dispatch, entityIdentifier]);
- const filter = useCallback(() => {
- $filteringEntity.set(entityIdentifier);
- }, [entityIdentifier]);
- const debug = useCallback(() => {
- if (!canvasManager) {
- return;
- }
- const entity = canvasManager.stateApi.getEntity(entityIdentifier);
- if (!entity) {
- return;
- }
- console.debug(entity);
- }, [canvasManager, entityIdentifier]);
-
- return (
- <>
- {isArrangeable && (
- <>
- }>
- {t('controlLayers.moveToFront')}
-
- }>
- {t('controlLayers.moveForward')}
-
- }>
- {t('controlLayers.moveBackward')}
-
- }>
- {t('controlLayers.moveToBack')}
-
- >
- )}
- {isFilterable && (
- }>
- {t('common.filter')}
-
- )}
- {isRasterLayer && (
- }>
- {t('common.convertToControlLayer')}
-
- )}
- {isControlLayer && (
- }>
- {t('common.convertToRasterLayer')}
-
- )}
-
- }>
- {t('accessibility.reset')}
-
- {isDeleteable && (
- } color="error.300">
- {t('common.delete')}
-
- )}
-
- } color="warn.300">
- {t('common.debug')}
-
- >
- );
-});
-
-CanvasEntityActionMenuItems.displayName = 'CanvasEntityActionMenuItems';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx
index 5fe0203861..0521ab803d 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx
@@ -1,16 +1,54 @@
import type { FlexProps } from '@invoke-ai/ui-library';
import { ContextMenu, Flex, MenuList } from '@invoke-ai/ui-library';
-import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
+import { ControlLayerMenuItems } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItems';
+import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintMask/InpaintMaskMenuItems';
+import { RasterLayerMenuItems } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItems';
+import { RegionalGuidanceMenuItems } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItems';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { memo, useCallback } from 'react';
+import { assert } from 'tsafe';
export const CanvasEntityHeader = memo(({ children, ...rest }: FlexProps) => {
+ const entityIdentifier = useEntityIdentifierContext();
const renderMenu = useCallback(() => {
- return (
-
-
-
- );
- }, []);
+ if (entityIdentifier.type === 'regional_guidance') {
+ return (
+
+
+
+ );
+ }
+
+ if (entityIdentifier.type === 'inpaint_mask') {
+ return (
+
+
+
+ );
+ }
+
+ if (entityIdentifier.type === 'raster_layer') {
+ return (
+
+
+
+ );
+ }
+
+ if (entityIdentifier.type === 'control_layer') {
+ return (
+
+
+
+ );
+ }
+
+ if (entityIdentifier.type === 'ip_adapter') {
+ return {/* */};
+ }
+
+ assert(false, 'Unhandled entity type');
+ }, [entityIdentifier]);
return (
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsArrange.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsArrange.tsx
new file mode 100644
index 0000000000..8bcb88f623
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsArrange.tsx
@@ -0,0 +1,95 @@
+import { MenuItem } from '@invoke-ai/ui-library';
+import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
+import {
+ entityArrangedBackwardOne,
+ entityArrangedForwardOne,
+ entityArrangedToBack,
+ entityArrangedToFront,
+ selectCanvasV2Slice,
+} from 'features/controlLayers/store/canvasV2Slice';
+import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types';
+import { memo, useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiArrowDownBold, PiArrowLineDownBold, PiArrowLineUpBold, PiArrowUpBold } from 'react-icons/pi';
+
+const getIndexAndCount = (
+ canvasV2: CanvasV2State,
+ { id, type }: CanvasEntityIdentifier
+): { index: number; count: number } => {
+ if (type === 'raster_layer') {
+ return {
+ index: canvasV2.rasterLayers.entities.findIndex((entity) => entity.id === id),
+ count: canvasV2.rasterLayers.entities.length,
+ };
+ } else if (type === 'control_layer') {
+ return {
+ index: canvasV2.controlLayers.entities.findIndex((entity) => entity.id === id),
+ count: canvasV2.controlLayers.entities.length,
+ };
+ } else if (type === 'regional_guidance') {
+ return {
+ index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
+ count: canvasV2.regions.entities.length,
+ };
+ } else {
+ return {
+ index: -1,
+ count: 0,
+ };
+ }
+};
+
+export const CanvasEntityMenuItemsArrange = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const entityIdentifier = useEntityIdentifierContext();
+ const selectValidActions = useMemo(
+ () =>
+ createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
+ const { index, count } = getIndexAndCount(canvasV2, entityIdentifier);
+ return {
+ canMoveForwardOne: index < count - 1,
+ canMoveBackwardOne: index > 0,
+ canMoveToFront: index < count - 1,
+ canMoveToBack: index > 0,
+ };
+ }),
+ [entityIdentifier]
+ );
+
+ const validActions = useAppSelector(selectValidActions);
+
+ const moveForwardOne = useCallback(() => {
+ dispatch(entityArrangedForwardOne({ entityIdentifier }));
+ }, [dispatch, entityIdentifier]);
+ const moveToFront = useCallback(() => {
+ dispatch(entityArrangedToFront({ entityIdentifier }));
+ }, [dispatch, entityIdentifier]);
+ const moveBackwardOne = useCallback(() => {
+ dispatch(entityArrangedBackwardOne({ entityIdentifier }));
+ }, [dispatch, entityIdentifier]);
+ const moveToBack = useCallback(() => {
+ dispatch(entityArrangedToBack({ entityIdentifier }));
+ }, [dispatch, entityIdentifier]);
+
+ return (
+ <>
+ }>
+ {t('controlLayers.moveToFront')}
+
+ }>
+ {t('controlLayers.moveForward')}
+
+ }>
+ {t('controlLayers.moveBackward')}
+
+ }>
+ {t('controlLayers.moveToBack')}
+
+ >
+ );
+});
+
+CanvasEntityMenuItemsArrange.displayName = 'CanvasEntityArrangeMenuItems';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDelete.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDelete.tsx
new file mode 100644
index 0000000000..24d9a32682
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsDelete.tsx
@@ -0,0 +1,25 @@
+import { MenuItem } from '@invoke-ai/ui-library';
+import { useAppDispatch } from 'app/store/storeHooks';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
+import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiTrashSimpleBold } from 'react-icons/pi';
+
+export const CanvasEntityMenuItemsDelete = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const entityIdentifier = useEntityIdentifierContext();
+
+ const deleteEntity = useCallback(() => {
+ dispatch(entityDeleted({ entityIdentifier }));
+ }, [dispatch, entityIdentifier]);
+
+ return (
+ } color="error.300">
+ {t('common.delete')}
+
+ );
+});
+
+CanvasEntityMenuItemsDelete.displayName = 'CanvasEntityMenuItemsDelete';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsFilter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsFilter.tsx
new file mode 100644
index 0000000000..5e15543e5c
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsFilter.tsx
@@ -0,0 +1,22 @@
+import { MenuItem } from '@invoke-ai/ui-library';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
+import { $filteringEntity } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiShootingStarBold } from 'react-icons/pi';
+
+export const CanvasEntityMenuItemsFilter = memo(() => {
+ const { t } = useTranslation();
+ const entityIdentifier = useEntityIdentifierContext();
+ const filter = useCallback(() => {
+ $filteringEntity.set(entityIdentifier);
+ }, [entityIdentifier]);
+
+ return (
+ }>
+ {t('controlLayers.filter')}
+
+ );
+});
+
+CanvasEntityMenuItemsFilter.displayName = 'CanvasEntityMenuItemsFilter';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsReset.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsReset.tsx
new file mode 100644
index 0000000000..3a2387bc86
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsReset.tsx
@@ -0,0 +1,25 @@
+import { MenuItem } from '@invoke-ai/ui-library';
+import { useAppDispatch } from 'app/store/storeHooks';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
+import { entityReset } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
+
+export const CanvasEntityMenuItemsReset = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const entityIdentifier = useEntityIdentifierContext();
+
+ const resetEntity = useCallback(() => {
+ dispatch(entityReset({ entityIdentifier }));
+ }, [dispatch, entityIdentifier]);
+
+ return (
+ }>
+ {t('accessibility.reset')}
+
+ );
+});
+
+CanvasEntityMenuItemsReset.displayName = 'CanvasEntityMenuItemsReset';
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
deleted file mode 100644
index c7fd177d75..0000000000
--- a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { deepClone } from 'common/util/deepClone';
-import { ipaAdded, rgIPAdapterAdded } from 'features/controlLayers/store/canvasV2Slice';
-import {
- IMAGE_FILTERS,
- initialControlNetV2,
- initialIPAdapterV2,
- initialT2IAdapterV2,
- isFilterType,
-} from 'features/controlLayers/store/types';
-import { zModelIdentifierField } from 'features/nodes/types/common';
-import { useCallback, useMemo } from 'react';
-import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType';
-import type { ControlNetModelConfig, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
-import { v4 as uuidv4 } from 'uuid';
-
-export const useAddCALayer = () => {
- const dispatch = useAppDispatch();
- const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
- const [modelConfigs] = useControlNetAndT2IAdapterModels();
- const model: ControlNetModelConfig | T2IAdapterModelConfig | null = useMemo(() => {
- // prefer to use a model that matches the base model
- const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
- return compatibleModels[0] ?? modelConfigs[0] ?? null;
- }, [baseModel, modelConfigs]);
- const isDisabled = useMemo(() => !model, [model]);
- const addCALayer = useCallback(() => {
- if (!model) {
- return;
- }
-
- const defaultPreprocessor = model.default_settings?.preprocessor;
- const processorConfig = isFilterType(defaultPreprocessor)
- ? IMAGE_FILTERS[defaultPreprocessor].buildDefaults(baseModel)
- : null;
-
- const initialConfig = deepClone(model.type === 'controlnet' ? initialControlNetV2 : initialT2IAdapterV2);
- const config = { ...initialConfig, model: zModelIdentifierField.parse(model), processorConfig };
-
- // dispatch(caAdded({ config }));
- }, [dispatch, model, baseModel]);
-
- return [addCALayer, isDisabled] as const;
-};
-
-export const useAddIPALayer = () => {
- const dispatch = useAppDispatch();
- const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
- const [modelConfigs] = useIPAdapterModels();
- const model: IPAdapterModelConfig | null = useMemo(() => {
- // prefer to use a model that matches the base model
- const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
- return compatibleModels[0] ?? modelConfigs[0] ?? null;
- }, [baseModel, modelConfigs]);
- const isDisabled = useMemo(() => !model, [model]);
- const addIPALayer = useCallback(() => {
- if (!model) {
- return;
- }
-
- const initialConfig = deepClone(initialIPAdapterV2);
- const config = { ...initialConfig, model: zModelIdentifierField.parse(model) };
- dispatch(ipaAdded({ config }));
- }, [dispatch, model]);
-
- return [addIPALayer, isDisabled] as const;
-};
-
-export const useAddIPAdapterToRGLayer = (id: string) => {
- const dispatch = useAppDispatch();
- const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
- const [modelConfigs] = useIPAdapterModels();
- const model: IPAdapterModelConfig | null = useMemo(() => {
- // prefer to use a model that matches the base model
- const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
- return compatibleModels[0] ?? modelConfigs[0] ?? null;
- }, [baseModel, modelConfigs]);
- const isDisabled = useMemo(() => !model, [model]);
- const addIPAdapter = useCallback(() => {
- if (!model) {
- return;
- }
- const initialConfig = deepClone(initialIPAdapterV2);
- const config = { ...initialConfig, model: zModelIdentifierField.parse(model) };
- dispatch(rgIPAdapterAdded({ id, ipAdapter: { ...config, id: uuidv4(), type: 'ip_adapter', isEnabled: true } }));
- }, [model, dispatch, id]);
-
- return [addIPAdapter, isDisabled] as const;
-};
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useLayerControlAdapter.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useLayerControlAdapter.ts
index 9c91bca847..cc174e8771 100644
--- a/invokeai/frontend/web/src/features/controlLayers/hooks/useLayerControlAdapter.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useLayerControlAdapter.ts
@@ -4,10 +4,10 @@ import { deepClone } from 'common/util/deepClone';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
import { selectControlLayerOrThrow } from 'features/controlLayers/store/controlLayersReducers';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
-import { initialControlNetV2, initialT2IAdapterV2 } from 'features/controlLayers/store/types';
+import { initialControlNetV2, initialIPAdapterV2, initialT2IAdapterV2 } from 'features/controlLayers/store/types';
import { zModelIdentifierField } from 'features/nodes/types/common';
import { useMemo } from 'react';
-import { useControlNetAndT2IAdapterModels } from 'services/api/hooks/modelsByType';
+import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType';
export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier) => {
const selectControlAdapter = useMemo(
@@ -42,3 +42,23 @@ export const useDefaultControlAdapter = () => {
return defaultControlAdapter;
};
+
+export const useDefaultIPAdapter = () => {
+ const [modelConfigs] = useIPAdapterModels();
+
+ const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
+
+ const defaultControlAdapter = useMemo(() => {
+ const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
+ const model = compatibleModels[0] ?? modelConfigs[0] ?? null;
+ const ipAdapter = deepClone(initialIPAdapterV2);
+
+ if (model) {
+ ipAdapter.model = zModelIdentifierField.parse(model);
+ }
+
+ return ipAdapter;
+ }, [baseModel, modelConfigs]);
+
+ return defaultControlAdapter;
+};
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts
index cc70237b85..c3818f3703 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts
@@ -44,7 +44,7 @@ import { IMAGE_FILTERS, isDrawableEntity, RGBA_RED } from './types';
const initialState: CanvasV2State = {
_version: 3,
- selectedEntityIdentifier: null,
+ selectedEntityIdentifier: { id: 'inpaint_mask', type: 'inpaint_mask' },
rasterLayers: { entities: [], compositeRasterizationCache: [] },
controlLayers: { entities: [] },
ipAdapters: { entities: [] },
@@ -385,10 +385,13 @@ export const canvasV2Slice = createSlice({
}
},
allEntitiesDeleted: (state) => {
- state.regions.entities = [];
- state.rasterLayers.entities = [];
+ state.ipAdapters = deepClone(initialState.ipAdapters);
+ state.rasterLayers = deepClone(initialState.rasterLayers);
state.rasterLayers.compositeRasterizationCache = [];
- state.ipAdapters.entities = [];
+ state.controlLayers = deepClone(initialState.controlLayers);
+ state.regions = deepClone(initialState.regions);
+ state.inpaintMask = deepClone(initialState.inpaintMask);
+ state.selectedEntityIdentifier = deepClone(initialState.selectedEntityIdentifier);
},
filterSelected: (state, action: PayloadAction<{ type: FilterConfig['type'] }>) => {
state.filter.config = IMAGE_FILTERS[action.payload.type].buildDefaults();
@@ -423,6 +426,7 @@ export const canvasV2Slice = createSlice({
state.ipAdapters = deepClone(initialState.ipAdapters);
state.rasterLayers = deepClone(initialState.rasterLayers);
+ state.rasterLayers.compositeRasterizationCache = [];
state.controlLayers = deepClone(initialState.controlLayers);
state.regions = deepClone(initialState.regions);
state.selectedEntityIdentifier = deepClone(initialState.selectedEntityIdentifier);