From d1db6198b5c8536d488832571fb39da48c8971be Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Fri, 12 Apr 2024 20:14:33 +1000
Subject: [PATCH] perf(ui): memoize & otherwise optimize regional prompts ui
---
.../components/AddLayerButton.tsx | 8 ++-
.../components/BrushPreview.tsx | 45 +++++-------
.../regionalPrompts/components/BrushSize.tsx | 8 ++-
.../components/LayerBoundingBox.tsx | 13 ++--
.../components/LayerColorPicker.tsx | 31 ++++++---
.../components/LayerComponent.tsx | 68 +++++++++----------
.../components/LayerListItem.tsx | 8 ++-
.../regionalPrompts/components/LayerMenu.tsx | 13 ++--
.../components/LayerVisibilityToggle.tsx | 8 ++-
.../components/LineComponent.tsx | 7 +-
.../components/PromptLayerOpacity.tsx | 8 ++-
.../components/RectComponent.tsx | 17 ++++-
.../components/RegionalPromptsEditor.tsx | 11 +--
.../components/RegionalPromptsStage.tsx | 4 +-
.../store/regionalPromptsSlice.ts | 6 +-
15 files changed, 144 insertions(+), 111 deletions(-)
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx
index f39db803c7..01107489b7 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx
@@ -1,10 +1,10 @@
import { Button } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { layerAdded } from 'features/regionalPrompts/store/regionalPromptsSlice';
-import { useCallback } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-export const AddLayerButton = () => {
+export const AddLayerButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const onClick = useCallback(() => {
@@ -12,4 +12,6 @@ export const AddLayerButton = () => {
}, [dispatch]);
return ;
-};
+});
+
+AddLayerButton.displayName = 'AddLayerButton';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/BrushPreview.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/BrushPreview.tsx
index 2792d4ae81..1c625625cf 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/BrushPreview.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/BrushPreview.tsx
@@ -1,49 +1,38 @@
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
-import { rgbColorToString } from 'features/canvas/util/colorToString';
+import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { $cursorPosition } from 'features/regionalPrompts/store/regionalPromptsSlice';
+import { memo } from 'react';
import { Circle, Group } from 'react-konva';
-const useBrushData = () => {
+export const BrushPreviewOutline = memo(() => {
const brushSize = useAppSelector((s) => s.regionalPrompts.brushSize);
const tool = useAppSelector((s) => s.regionalPrompts.tool);
+ const a = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
const color = useAppSelector((s) => {
const _color = s.regionalPrompts.layers.find((l) => l.id === s.regionalPrompts.selectedLayer)?.color;
if (!_color) {
return null;
}
- return rgbColorToString(_color);
+ return rgbaColorToString({ ..._color, a });
});
+
const pos = useStore($cursorPosition);
- return { brushSize, tool, color, pos };
-};
-
-export const BrushPreviewFill = () => {
- const { brushSize, tool, color, pos } = useBrushData();
if (!brushSize || !color || !pos || tool === 'move') {
return null;
}
return (
-
- );
-};
-
-export const BrushPreviewOutline = () => {
- const { brushSize, tool, color, pos } = useBrushData();
- if (!brushSize || !color || !pos || tool === 'move') {
- return null;
- }
-
- return (
-
+
+
{
/>
);
-};
+});
+
+BrushPreviewOutline.displayName = 'BrushPreviewOutline';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx
index 67871381cb..a1a4bb1cfc 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/BrushSize.tsx
@@ -1,10 +1,10 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { brushSizeChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
-import { useCallback } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-export const BrushSize = () => {
+export const BrushSize = memo(() => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const brushSize = useAppSelector((s) => s.regionalPrompts.brushSize);
@@ -21,4 +21,6 @@ export const BrushSize = () => {
);
-};
+});
+
+BrushSize.displayName = 'BrushSize';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerBoundingBox.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerBoundingBox.tsx
index e9d4514876..31e09d1e05 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerBoundingBox.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerBoundingBox.tsx
@@ -1,14 +1,14 @@
-import { createSelector } from '@reduxjs/toolkit';
+import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { layerSelected, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
-import { useCallback, useMemo } from 'react';
+import { memo, useCallback, useMemo } from 'react';
import { Rect as KonvaRect } from 'react-konva';
type Props = {
layerId: string;
};
-export const LayerBoundingBox = ({ layerId }: Props) => {
+export const LayerBoundingBox = memo(({ layerId }: Props) => {
const dispatch = useAppDispatch();
const tool = useAppSelector((s) => s.regionalPrompts.tool);
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
@@ -19,7 +19,7 @@ export const LayerBoundingBox = ({ layerId }: Props) => {
const selectBbox = useMemo(
() =>
- createSelector(
+ createMemoizedSelector(
selectRegionalPromptsSlice,
(regionalPrompts) => regionalPrompts.layers.find((layer) => layer.id === layerId)?.bbox ?? null
),
@@ -33,6 +33,7 @@ export const LayerBoundingBox = ({ layerId }: Props) => {
return (
{
listening={tool === 'move'}
/>
);
-};
+});
+
+LayerBoundingBox.displayName = 'LayerBoundingBox';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerColorPicker.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerColorPicker.tsx
index aabea569fd..8208912239 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerColorPicker.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerColorPicker.tsx
@@ -1,18 +1,31 @@
import { IconButton, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
-import { useAppDispatch } from 'app/store/storeHooks';
+import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import RgbColorPicker from 'common/components/RgbColorPicker';
-import { useLayer } from 'features/regionalPrompts/hooks/layerStateHooks';
-import { promptRegionLayerColorChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
-import { useCallback } from 'react';
+import {
+ promptRegionLayerColorChanged,
+ selectRegionalPromptsSlice,
+} from 'features/regionalPrompts/store/regionalPromptsSlice';
+import { memo, useCallback, useMemo } from 'react';
import type { RgbColor } from 'react-colorful';
import { PiEyedropperBold } from 'react-icons/pi';
+import { assert } from 'tsafe';
type Props = {
id: string;
};
-export const LayerColorPicker = ({ id }: Props) => {
- const layer = useLayer(id);
+export const LayerColorPicker = memo(({ id }: Props) => {
+ const selectColor = useMemo(
+ () =>
+ createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
+ const layer = regionalPrompts.layers.find((l) => l.id === id);
+ assert(layer);
+ return layer.color;
+ }),
+ [id]
+ );
+ const color = useAppSelector(selectColor);
const dispatch = useAppDispatch();
const onColorChange = useCallback(
(color: RgbColor) => {
@@ -27,9 +40,11 @@ export const LayerColorPicker = ({ id }: Props) => {
-
+
);
-};
+});
+
+LayerColorPicker.displayName = 'LayerColorPicker';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerComponent.tsx
index 9e4596fa30..c0a104b9de 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerComponent.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerComponent.tsx
@@ -1,6 +1,5 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
-import { BrushPreviewFill } from 'features/regionalPrompts/components/BrushPreview';
import { LayerBoundingBox } from 'features/regionalPrompts/components/LayerBoundingBox';
import { LineComponent } from 'features/regionalPrompts/components/LineComponent';
import { RectComponent } from 'features/regionalPrompts/components/RectComponent';
@@ -17,8 +16,7 @@ import type { Group as KonvaGroupType } from 'konva/lib/Group';
import type { Layer as KonvaLayerType } from 'konva/lib/Layer';
import type { KonvaEventObject, Node as KonvaNodeType, NodeConfig as KonvaNodeConfigType } from 'konva/lib/Node';
import type { IRect, Vector2d } from 'konva/lib/types';
-import type React from 'react';
-import { useCallback, useEffect, useRef } from 'react';
+import { memo, useCallback, useEffect, useRef } from 'react';
import { Group as KonvaGroup, Layer as KonvaLayer } from 'react-konva';
type Props = {
@@ -28,15 +26,13 @@ type Props = {
export const selectPromptLayerObjectGroup = (item: KonvaNodeType) =>
item.name() !== REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME;
-export const LayerComponent: React.FC = ({ id }) => {
+export const LayerComponent = memo(({ id }: Props) => {
const dispatch = useAppDispatch();
const layer = useLayer(id);
- const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
const promptLayerOpacity = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
const tool = useAppSelector((s) => s.regionalPrompts.tool);
const layerRef = useRef(null);
const groupRef = useRef(null);
-
const onChangeBbox = useCallback(
(bbox: IRect | null) => {
dispatch(layerBboxChanged({ layerId: layer.id, bbox }));
@@ -94,39 +90,39 @@ export const LayerComponent: React.FC = ({ id }) => {
}
// Caching the group allows its opacity to apply to all shapes at once. We should cache only when the layer's
// objects or attributes with a visual effect (e.g. color) change.
+ // TODO: Figure out a more efficient way to handle opacity - maybe a separate rect w/ globalCompositeOperation...
groupRef.current.cache();
}, [layer.objects, layer.color, layer.isVisible]);
return (
- <>
-
+
-
- {layer.objects.map((obj) => {
- if (obj.kind === 'line') {
- return ;
- }
- if (obj.kind === 'fillRect') {
- return ;
- }
- })}
-
-
-
- {layer.id === selectedLayer && }
- >
+ {layer.objects.map((obj) => {
+ if (obj.kind === 'line') {
+ return ;
+ }
+ if (obj.kind === 'fillRect') {
+ return ;
+ }
+ })}
+
+
+
);
-};
+});
+
+LayerComponent.displayName = 'LayerComponent';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerListItem.tsx
index 36b34d642c..39a7efd758 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerListItem.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerListItem.tsx
@@ -6,14 +6,14 @@ import { LayerMenu } from 'features/regionalPrompts/components/LayerMenu';
import { LayerVisibilityToggle } from 'features/regionalPrompts/components/LayerVisibilityToggle';
import { RegionalPromptsPrompt } from 'features/regionalPrompts/components/RegionalPromptsPrompt';
import { layerSelected } from 'features/regionalPrompts/store/regionalPromptsSlice';
-import { useCallback } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
id: string;
};
-export const LayerListItem = ({ id }: Props) => {
+export const LayerListItem = memo(({ id }: Props) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
@@ -46,4 +46,6 @@ export const LayerListItem = ({ id }: Props) => {
);
-};
+});
+
+LayerListItem.displayName = 'LayerListItem';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerMenu.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerMenu.tsx
index 2317eda26a..bfe85de446 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerMenu.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerMenu.tsx
@@ -1,5 +1,5 @@
import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
-import { createSelector } from '@reduxjs/toolkit';
+import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
layerDeleted,
@@ -10,8 +10,7 @@ import {
layerReset,
selectRegionalPromptsSlice,
} from 'features/regionalPrompts/store/regionalPromptsSlice';
-import type React from 'react';
-import { useCallback, useMemo } from 'react';
+import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
PiArrowCounterClockwiseBold,
@@ -25,12 +24,12 @@ import {
type Props = { id: string };
-export const LayerMenu: React.FC = ({ id }) => {
+export const LayerMenu = memo(({ id }: Props) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const selectValidActions = useMemo(
() =>
- createSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
+ createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layerIndex = regionalPrompts.layers.findIndex((l) => l.id === id);
const layerCount = regionalPrompts.layers.length;
return {
@@ -87,4 +86,6 @@ export const LayerMenu: React.FC = ({ id }) => {
);
-};
+});
+
+LayerMenu.displayName = 'LayerMenu';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerVisibilityToggle.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerVisibilityToggle.tsx
index bb636e8338..2a004f262a 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/LayerVisibilityToggle.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LayerVisibilityToggle.tsx
@@ -2,14 +2,14 @@ import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useLayerIsVisible } from 'features/regionalPrompts/hooks/layerStateHooks';
import { layerIsVisibleToggled } from 'features/regionalPrompts/store/regionalPromptsSlice';
-import { useCallback } from 'react';
+import { memo, useCallback } from 'react';
import { PiEyeBold, PiEyeClosedBold } from 'react-icons/pi';
type Props = {
id: string;
};
-export const LayerVisibilityToggle = ({ id }: Props) => {
+export const LayerVisibilityToggle = memo(({ id }: Props) => {
const dispatch = useAppDispatch();
const isVisible = useLayerIsVisible(id);
const onClick = useCallback(() => {
@@ -25,4 +25,6 @@ export const LayerVisibilityToggle = ({ id }: Props) => {
onClick={onClick}
/>
);
-};
+});
+
+LayerVisibilityToggle.displayName = 'LayerVisibilityToggle';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/LineComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/LineComponent.tsx
index a0250ab6da..5b6796b895 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/LineComponent.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/LineComponent.tsx
@@ -1,5 +1,6 @@
import { rgbColorToString } from 'features/canvas/util/colorToString';
import type { LineObject } from 'features/regionalPrompts/store/regionalPromptsSlice';
+import { memo } from 'react';
import type { RgbColor } from 'react-colorful';
import { Line } from 'react-konva';
@@ -9,7 +10,7 @@ type Props = {
color: RgbColor;
};
-export const LineComponent = ({ layerId, line, color }: Props) => {
+export const LineComponent = memo(({ layerId, line, color }: Props) => {
return (
{
listening={false}
/>
);
-};
+});
+
+LineComponent.displayName = 'LineComponent';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/PromptLayerOpacity.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/PromptLayerOpacity.tsx
index 164505b086..1ae4ecc524 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/PromptLayerOpacity.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/PromptLayerOpacity.tsx
@@ -1,10 +1,10 @@
import { CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { promptLayerOpacityChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
-import { useCallback } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-export const PromptLayerOpacity = () => {
+export const PromptLayerOpacity = memo(() => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const promptLayerOpacity = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
@@ -20,4 +20,6 @@ export const PromptLayerOpacity = () => {
);
-};
+});
+
+PromptLayerOpacity.displayName = 'PromptLayerOpacity';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RectComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RectComponent.tsx
index a8248e1301..882c5823a5 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/RectComponent.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RectComponent.tsx
@@ -1,5 +1,6 @@
import { rgbColorToString } from 'features/canvas/util/colorToString';
import type { FillRectObject } from 'features/regionalPrompts/store/regionalPromptsSlice';
+import { memo } from 'react';
import type { RgbColor } from 'react-colorful';
import { Rect } from 'react-konva';
@@ -8,8 +9,18 @@ type Props = {
color: RgbColor;
};
-export const RectComponent = ({ rect, color }: Props) => {
+export const RectComponent = memo(({ rect, color }: Props) => {
return (
-
+
);
-};
+});
+
+RectComponent.displayName = 'RectComponent';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx
index efc77796c3..22a6d4f223 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx
@@ -1,6 +1,6 @@
/* eslint-disable i18next/no-literal-string */
import { Button, Flex } from '@invoke-ai/ui-library';
-import { createSelector } from '@reduxjs/toolkit';
+import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton';
import { BrushSize } from 'features/regionalPrompts/components/BrushSize';
@@ -11,8 +11,9 @@ import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs';
import { ImageSizeLinear } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSizeLinear';
+import { memo } from 'react';
-const selectLayerIdsReversed = createSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
+const selectLayerIdsReversed = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
regionalPrompts.layers.map((l) => l.id).reverse()
);
@@ -20,7 +21,7 @@ const debugBlobs = () => {
getRegionalPromptLayerBlobs(true);
};
-export const RegionalPromptsEditor = () => {
+export const RegionalPromptsEditor = memo(() => {
const layerIdsReversed = useAppSelector(selectLayerIdsReversed);
return (
@@ -40,4 +41,6 @@ export const RegionalPromptsEditor = () => {
);
-};
+});
+
+RegionalPromptsEditor.displayName = 'RegionalPromptsEditor';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsStage.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsStage.tsx
index 1936a13e17..9a55b0d4e1 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsStage.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsStage.tsx
@@ -1,5 +1,5 @@
import { chakra } from '@invoke-ai/ui-library';
-import { createSelector } from '@reduxjs/toolkit';
+import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { BrushPreviewOutline } from 'features/regionalPrompts/components/BrushPreview';
import { LayerComponent } from 'features/regionalPrompts/components/LayerComponent';
@@ -15,7 +15,7 @@ import type Konva from 'konva';
import { memo, useCallback, useMemo, useRef } from 'react';
import { Layer, Stage } from 'react-konva';
-const selectLayerIds = createSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
+const selectLayerIds = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
regionalPrompts.layers.map((l) => l.id)
);
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts
index 8d96bcdf30..44ceb46b4b 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts
+++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts
@@ -2,7 +2,7 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
-import type Konva from 'konva';
+import type { Stage } from 'konva/lib/Stage';
import type { IRect, Vector2d } from 'konva/lib/types';
import { atom } from 'nanostores';
import type { RgbColor } from 'react-colorful';
@@ -270,8 +270,8 @@ export const regionalPromptsPersistConfig: PersistConfig =
export const $isMouseDown = atom(false);
export const $isMouseOver = atom(false);
export const $cursorPosition = atom(null);
-export const $stage = atom(null);
-export const getStage = (): Konva.Stage => {
+export const $stage = atom(null);
+export const getStage = (): Stage => {
const stage = $stage.get();
assert(stage);
return stage;