From e4376e21dda4b1302c14f02b7c5bab63b25083d2 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Mon, 8 Jul 2024 17:08:59 +1000
Subject: [PATCH] feat(ui): split up tool chooser buttons
Prep for distinct toolbars for generation vs canvas modes
---
.../components/BboxToolButton.tsx | 33 +++
.../components/BrushToolButton.tsx | 39 ++++
.../components/EraserToolButton.tsx | 39 ++++
.../components/MoveToolButton.tsx | 35 +++
.../components/RectToolButton.tsx | 39 ++++
.../controlLayers/components/ToolChooser.tsx | 208 ++----------------
.../components/ViewToolButton.tsx | 32 +++
.../hooks/useCanvasDeleteLayerHotkey.ts | 50 +++++
.../hooks/useCanvasResetLayerHotkey.ts | 53 +++++
.../src/features/controlLayers/store/types.ts | 8 +-
10 files changed, 344 insertions(+), 192 deletions(-)
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/BboxToolButton.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/BrushToolButton.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/EraserToolButton.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/MoveToolButton.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/RectToolButton.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/ViewToolButton.tsx
create mode 100644 invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts
create mode 100644 invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasResetLayerHotkey.ts
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/BboxToolButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/BboxToolButton.tsx
new file mode 100644
index 0000000000..70440f10ac
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/BboxToolButton.tsx
@@ -0,0 +1,33 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
+import { useTranslation } from 'react-i18next';
+import { PiBoundingBoxBold } from 'react-icons/pi';
+
+export const BboxToolButton = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging);
+ const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'bbox');
+
+ const onClick = useCallback(() => {
+ dispatch(toolChanged('bbox'));
+ }, [dispatch]);
+
+ useHotkeys('q', onClick, [onClick]);
+
+ return (
+ }
+ variant={isSelected ? 'solid' : 'outline'}
+ onClick={onClick}
+ isDisabled={isDisabled}
+ />
+ );
+});
+
+BboxToolButton.displayName = 'BboxToolButton';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/BrushToolButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/BrushToolButton.tsx
new file mode 100644
index 0000000000..0dcaa7fa7c
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/BrushToolButton.tsx
@@ -0,0 +1,39 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
+import { isDrawableEntityType } from 'features/controlLayers/store/types';
+import { memo, useCallback } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
+import { useTranslation } from 'react-i18next';
+import { PiPaintBrushBold } from 'react-icons/pi';
+
+export const BrushToolButton = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'brush');
+ const isDisabled = useAppSelector((s) => {
+ const entityType = s.canvasV2.selectedEntityIdentifier?.type;
+ const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
+ const isStaging = s.canvasV2.session.isStaging;
+ return !isDrawingToolAllowed || isStaging;
+ });
+
+ const onClick = useCallback(() => {
+ dispatch(toolChanged('brush'));
+ }, [dispatch]);
+
+ useHotkeys('b', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
+
+ return (
+ }
+ variant={isSelected ? 'solid' : 'outline'}
+ onClick={onClick}
+ isDisabled={isDisabled}
+ />
+ );
+});
+
+BrushToolButton.displayName = 'BrushToolButton';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/EraserToolButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/EraserToolButton.tsx
new file mode 100644
index 0000000000..698b37c81f
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/EraserToolButton.tsx
@@ -0,0 +1,39 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
+import { isDrawableEntityType } from 'features/controlLayers/store/types';
+import { memo, useCallback } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
+import { useTranslation } from 'react-i18next';
+import { PiEraserBold } from 'react-icons/pi';
+
+export const EraserToolButton = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'eraser');
+ const isDisabled = useAppSelector((s) => {
+ const entityType = s.canvasV2.selectedEntityIdentifier?.type;
+ const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
+ const isStaging = s.canvasV2.session.isStaging;
+ return !isDrawingToolAllowed || isStaging;
+ });
+
+ const onClick = useCallback(() => {
+ dispatch(toolChanged('eraser'));
+ }, [dispatch]);
+
+ useHotkeys('e', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
+
+ return (
+ }
+ variant={isSelected ? 'solid' : 'outline'}
+ onClick={onClick}
+ isDisabled={isDisabled}
+ />
+ );
+});
+
+EraserToolButton.displayName = 'EraserToolButton';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/MoveToolButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/MoveToolButton.tsx
new file mode 100644
index 0000000000..48dcfeb247
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/MoveToolButton.tsx
@@ -0,0 +1,35 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
+import { useTranslation } from 'react-i18next';
+import { PiCursorBold } from 'react-icons/pi';
+
+export const MoveToolButton = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'move');
+ const isDisabled = useAppSelector(
+ (s) => s.canvasV2.selectedEntityIdentifier === null || s.canvasV2.session.isStaging
+ );
+
+ const onClick = useCallback(() => {
+ dispatch(toolChanged('move'));
+ }, [dispatch]);
+
+ useHotkeys('v', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
+
+ return (
+ }
+ variant={isSelected ? 'solid' : 'outline'}
+ onClick={onClick}
+ isDisabled={isDisabled}
+ />
+ );
+});
+
+MoveToolButton.displayName = 'MoveToolButton';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RectToolButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RectToolButton.tsx
new file mode 100644
index 0000000000..4a8ccadd09
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RectToolButton.tsx
@@ -0,0 +1,39 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
+import { isDrawableEntityType } from 'features/controlLayers/store/types';
+import { memo, useCallback } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
+import { useTranslation } from 'react-i18next';
+import { PiRectangleBold } from 'react-icons/pi';
+
+export const RectToolButton = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'rect');
+ const isDisabled = useAppSelector((s) => {
+ const entityType = s.canvasV2.selectedEntityIdentifier?.type;
+ const isDrawingToolAllowed = entityType ? isDrawableEntityType(entityType) : false;
+ const isStaging = s.canvasV2.session.isStaging;
+ return !isDrawingToolAllowed || isStaging;
+ });
+
+ const onClick = useCallback(() => {
+ dispatch(toolChanged('rect'));
+ }, [dispatch]);
+
+ useHotkeys('u', onClick, { enabled: !isDisabled }, [isDisabled, onClick]);
+
+ return (
+ }
+ variant={isSelected ? 'solid' : 'outline'}
+ onClick={onClick}
+ isDisabled={isDisabled}
+ />
+ );
+});
+
+RectToolButton.displayName = 'RectToolButton';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx
index dc07df3581..e1bfe85a1f 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ToolChooser.tsx
@@ -1,199 +1,25 @@
-import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
-import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import {
- caDeleted,
- imReset,
- ipaDeleted,
- layerDeleted,
- layerReset,
- rgDeleted,
- rgReset,
- selectCanvasV2Slice,
- toolChanged,
-} from 'features/controlLayers/store/canvasV2Slice';
-import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
-import { useCallback, useMemo } from 'react';
-import { useHotkeys } from 'react-hotkeys-hook';
-import { useTranslation } from 'react-i18next';
-import {
- PiBoundingBoxBold,
- PiCursorBold,
- PiEraserBold,
- PiHandBold,
- PiPaintBrushBold,
- PiRectangleBold,
-} from 'react-icons/pi';
-
-const DRAWING_TOOL_TYPES = ['layer', 'regional_guidance', 'inpaint_mask'];
-
-const getIsDrawingToolEnabled = (entityIdentifier: CanvasEntityIdentifier | null) => {
- if (!entityIdentifier) {
- return false;
- }
- return DRAWING_TOOL_TYPES.includes(entityIdentifier.type);
-};
-
-const selectSelectedEntityIdentifier = createMemoizedSelector(
- selectCanvasV2Slice,
- (canvasV2State) => canvasV2State.selectedEntityIdentifier
-);
+import { ButtonGroup } from '@invoke-ai/ui-library';
+import { BboxToolButton } from 'features/controlLayers/components/BboxToolButton';
+import { BrushToolButton } from 'features/controlLayers/components/BrushToolButton';
+import { EraserToolButton } from 'features/controlLayers/components/EraserToolButton';
+import { MoveToolButton } from 'features/controlLayers/components/MoveToolButton';
+import { RectToolButton } from 'features/controlLayers/components/RectToolButton';
+import { ViewToolButton } from 'features/controlLayers/components/ViewToolButton';
+import { useCanvasDeleteLayerHotkey } from 'features/controlLayers/hooks/useCanvasDeleteLayerHotkey';
+import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey';
export const ToolChooser: React.FC = () => {
- const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
- const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
- const isDrawingToolDisabled = useMemo(
- () => !getIsDrawingToolEnabled(selectedEntityIdentifier),
- [selectedEntityIdentifier]
- );
- const isMoveToolDisabled = useMemo(() => selectedEntityIdentifier === null, [selectedEntityIdentifier]);
- const tool = useAppSelector((s) => s.canvasV2.tool.selected);
-
- const setToolToBrush = useCallback(() => {
- dispatch(toolChanged('brush'));
- }, [dispatch]);
- useHotkeys('b', setToolToBrush, { enabled: !isDrawingToolDisabled && !isStaging }, [
- isDrawingToolDisabled,
- isStaging,
- setToolToBrush,
- ]);
- const setToolToEraser = useCallback(() => {
- dispatch(toolChanged('eraser'));
- }, [dispatch]);
- useHotkeys('e', setToolToEraser, { enabled: !isDrawingToolDisabled && !isStaging }, [
- isDrawingToolDisabled,
- isStaging,
- setToolToEraser,
- ]);
- const setToolToRect = useCallback(() => {
- dispatch(toolChanged('rect'));
- }, [dispatch]);
- useHotkeys('u', setToolToRect, { enabled: !isDrawingToolDisabled && !isStaging }, [
- isDrawingToolDisabled,
- isStaging,
- setToolToRect,
- ]);
- const setToolToMove = useCallback(() => {
- dispatch(toolChanged('move'));
- }, [dispatch]);
- useHotkeys('v', setToolToMove, { enabled: !isMoveToolDisabled && !isStaging }, [
- isMoveToolDisabled,
- isStaging,
- setToolToMove,
- ]);
- const setToolToView = useCallback(() => {
- dispatch(toolChanged('view'));
- }, [dispatch]);
- useHotkeys('h', setToolToView, [setToolToView]);
- const setToolToBbox = useCallback(() => {
- dispatch(toolChanged('bbox'));
- }, [dispatch]);
- useHotkeys('q', setToolToBbox, [setToolToBbox]);
-
- const resetSelectedLayer = useCallback(() => {
- if (selectedEntityIdentifier === null) {
- return;
- }
- const { type, id } = selectedEntityIdentifier;
- if (type === 'layer') {
- dispatch(layerReset({ id }));
- }
- if (type === 'regional_guidance') {
- dispatch(rgReset({ id }));
- }
- if (type === 'inpaint_mask') {
- dispatch(imReset());
- }
- }, [dispatch, selectedEntityIdentifier]);
- const isResetEnabled = useMemo(
- () =>
- (!isStaging && selectedEntityIdentifier?.type === 'layer') ||
- selectedEntityIdentifier?.type === 'regional_guidance' ||
- selectedEntityIdentifier?.type === 'inpaint_mask',
- [isStaging, selectedEntityIdentifier?.type]
- );
- useHotkeys('shift+c', resetSelectedLayer, { enabled: isResetEnabled }, [
- isResetEnabled,
- isStaging,
- resetSelectedLayer,
- ]);
-
- const deleteSelectedLayer = useCallback(() => {
- if (selectedEntityIdentifier === null) {
- return;
- }
- const { type, id } = selectedEntityIdentifier;
- if (type === 'layer') {
- dispatch(layerDeleted({ id }));
- }
- if (type === 'regional_guidance') {
- dispatch(rgDeleted({ id }));
- }
- if (type === 'control_adapter') {
- dispatch(caDeleted({ id }));
- }
- if (type === 'ip_adapter') {
- dispatch(ipaDeleted({ id }));
- }
- }, [dispatch, selectedEntityIdentifier]);
- const isDeleteEnabled = useMemo(
- () => selectedEntityIdentifier !== null && !isStaging,
- [selectedEntityIdentifier, isStaging]
- );
- useHotkeys('shift+d', deleteSelectedLayer, { enabled: isDeleteEnabled }, [isDeleteEnabled, deleteSelectedLayer]);
+ useCanvasResetLayerHotkey();
+ useCanvasDeleteLayerHotkey();
return (
- }
- variant={tool === 'brush' ? 'solid' : 'outline'}
- onClick={setToolToBrush}
- isDisabled={isDrawingToolDisabled || isStaging}
- />
- }
- variant={tool === 'eraser' ? 'solid' : 'outline'}
- onClick={setToolToEraser}
- isDisabled={isDrawingToolDisabled || isStaging}
- />
- }
- variant={tool === 'rect' ? 'solid' : 'outline'}
- onClick={setToolToRect}
- isDisabled={isDrawingToolDisabled || isStaging}
- />
- }
- variant={tool === 'move' ? 'solid' : 'outline'}
- onClick={setToolToMove}
- isDisabled={isMoveToolDisabled || isStaging}
- />
- }
- variant={tool === 'view' ? 'solid' : 'outline'}
- onClick={setToolToView}
- isDisabled={isStaging}
- />
- }
- variant={tool === 'bbox' ? 'solid' : 'outline'}
- onClick={setToolToBbox}
- isDisabled={isStaging}
- />
+
+
+
+
+
+
);
};
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ViewToolButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ViewToolButton.tsx
new file mode 100644
index 0000000000..b9f6b1691d
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ViewToolButton.tsx
@@ -0,0 +1,32 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { toolChanged } from 'features/controlLayers/store/canvasV2Slice';
+import { memo, useCallback } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
+import { useTranslation } from 'react-i18next';
+import { PiHandBold } from 'react-icons/pi';
+
+export const ViewToolButton = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const isSelected = useAppSelector((s) => s.canvasV2.tool.selected === 'view');
+ const isDisabled = useAppSelector((s) => s.canvasV2.session.isStaging);
+ const onClick = useCallback(() => {
+ dispatch(toolChanged('view'));
+ }, [dispatch]);
+
+ useHotkeys('h', onClick, [onClick]);
+
+ return (
+ }
+ variant={isSelected ? 'solid' : 'outline'}
+ onClick={onClick}
+ isDisabled={isDisabled}
+ />
+ );
+});
+
+ViewToolButton.displayName = 'ViewToolButton';
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts
new file mode 100644
index 0000000000..1e2fb57901
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasDeleteLayerHotkey.ts
@@ -0,0 +1,50 @@
+import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
+import {
+ caDeleted,
+ ipaDeleted,
+ layerDeleted,
+ rgDeleted,
+ selectCanvasV2Slice,
+} from 'features/controlLayers/store/canvasV2Slice';
+import { useCallback, useMemo } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
+
+const selectSelectedEntityIdentifier = createMemoizedSelector(
+ selectCanvasV2Slice,
+ (canvasV2State) => canvasV2State.selectedEntityIdentifier
+);
+
+export function useCanvasDeleteLayerHotkey() {
+ useAssertSingleton(useCanvasDeleteLayerHotkey.name);
+ const dispatch = useAppDispatch();
+ const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
+ const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
+
+ const deleteSelectedLayer = useCallback(() => {
+ if (selectedEntityIdentifier === null) {
+ return;
+ }
+ const { type, id } = selectedEntityIdentifier;
+ if (type === 'layer') {
+ dispatch(layerDeleted({ id }));
+ }
+ if (type === 'regional_guidance') {
+ dispatch(rgDeleted({ id }));
+ }
+ if (type === 'control_adapter') {
+ dispatch(caDeleted({ id }));
+ }
+ if (type === 'ip_adapter') {
+ dispatch(ipaDeleted({ id }));
+ }
+ }, [dispatch, selectedEntityIdentifier]);
+
+ const isDeleteEnabled = useMemo(
+ () => selectedEntityIdentifier !== null && !isStaging,
+ [selectedEntityIdentifier, isStaging]
+ );
+
+ useHotkeys('shift+d', deleteSelectedLayer, { enabled: isDeleteEnabled }, [isDeleteEnabled, deleteSelectedLayer]);
+}
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasResetLayerHotkey.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasResetLayerHotkey.ts
new file mode 100644
index 0000000000..2d1c3c74f0
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasResetLayerHotkey.ts
@@ -0,0 +1,53 @@
+import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
+import {
+ imReset,
+ layerReset,
+ rgReset,
+ selectCanvasV2Slice,
+} from 'features/controlLayers/store/canvasV2Slice';
+import { useCallback, useMemo } from 'react';
+import { useHotkeys } from 'react-hotkeys-hook';
+
+const selectSelectedEntityIdentifier = createMemoizedSelector(
+ selectCanvasV2Slice,
+ (canvasV2State) => canvasV2State.selectedEntityIdentifier
+);
+
+export function useCanvasResetLayerHotkey() {
+ useAssertSingleton(useCanvasResetLayerHotkey.name);
+ const dispatch = useAppDispatch();
+ const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
+ const isStaging = useAppSelector((s) => s.canvasV2.session.isStaging);
+
+ const resetSelectedLayer = useCallback(() => {
+ if (selectedEntityIdentifier === null) {
+ return;
+ }
+ const { type, id } = selectedEntityIdentifier;
+ if (type === 'layer') {
+ dispatch(layerReset({ id }));
+ }
+ if (type === 'regional_guidance') {
+ dispatch(rgReset({ id }));
+ }
+ if (type === 'inpaint_mask') {
+ dispatch(imReset());
+ }
+ }, [dispatch, selectedEntityIdentifier]);
+
+ const isResetEnabled = useMemo(
+ () =>
+ (!isStaging && selectedEntityIdentifier?.type === 'layer') ||
+ selectedEntityIdentifier?.type === 'regional_guidance' ||
+ selectedEntityIdentifier?.type === 'inpaint_mask',
+ [isStaging, selectedEntityIdentifier?.type]
+ );
+
+ useHotkeys('shift+c', resetSelectedLayer, { enabled: isResetEnabled }, [
+ isResetEnabled,
+ isStaging,
+ resetSelectedLayer,
+ ]);
+}
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
index 18c8928940..f31ce9e998 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
@@ -655,7 +655,7 @@ const zImageFill = z.object({
});
const zFill = z.discriminatedUnion('type', [zColorFill, zImageFill]);
const zInpaintMaskEntity = z.object({
- id: zId,
+ id: z.literal('inpaint_mask'),
type: z.literal('inpaint_mask'),
isEnabled: z.boolean(),
x: z.number(),
@@ -945,3 +945,9 @@ export function isDrawableEntityAdapter(
): adapter is CanvasLayer | CanvasRegion | CanvasInpaintMask {
return adapter instanceof CanvasLayer || adapter instanceof CanvasRegion || adapter instanceof CanvasInpaintMask;
}
+
+export function isDrawableEntityType(
+ entityType: CanvasEntity['type']
+): entityType is 'layer' | 'regional_guidance' | 'inpaint_mask' {
+ return entityType === 'layer' || entityType === 'regional_guidance' || entityType === 'inpaint_mask';
+}