From 73481d4aeca9bb82141fd7cbb45b5a426586f439 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 5 Jan 2024 21:18:12 +1100 Subject: [PATCH] feat(ui): clean up canvas selectors Do not memoize unless absolutely necessary. Minor perf improvement --- .../features/canvas/components/IAICanvas.tsx | 68 ++++-------- .../IAICanvasBoundingBoxOverlay.tsx | 11 +- .../canvas/components/IAICanvasGrid.tsx | 12 +- .../components/IAICanvasIntermediateImage.tsx | 25 ++--- .../components/IAICanvasMaskCompositer.tsx | 105 ++---------------- .../components/IAICanvasToolPreview.tsx | 54 ++++----- .../IAICanvasToolbar/IAICanvasBoundingBox.tsx | 50 ++------- .../IAICanvasToolbar/IAICanvasMaskOptions.tsx | 34 ++---- .../IAICanvasSettingsButtonPopover.tsx | 62 ++++------- .../IAICanvasToolbar/IAICanvasToolbar.tsx | 24 +--- .../features/canvas/hooks/useCanvasHotkeys.ts | 47 ++------ .../canvas/hooks/useCanvasMouseMove.ts | 17 +-- .../features/canvas/util/getColoredMaskSVG.ts | 81 ++++++++++++++ .../web/src/features/ui/store/uiSelectors.ts | 6 +- 14 files changed, 217 insertions(+), 379 deletions(-) create mode 100644 invokeai/frontend/web/src/features/canvas/util/getColoredMaskSVG.ts diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx index 30ebc073f7..7143c75c35 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvas.tsx @@ -42,57 +42,34 @@ import IAICanvasStatusText from './IAICanvasStatusText'; import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox'; import IAICanvasToolPreview from './IAICanvasToolPreview'; -const selector = createMemoizedSelector( - [selectCanvasSlice, isStagingSelector], - (canvas, isStaging) => { - const { - isMaskEnabled, - stageScale, - shouldShowBoundingBox, - stageDimensions, - stageCoordinates, - tool, - shouldShowIntermediates, - shouldRestrictStrokesToBox, - shouldShowGrid, - shouldAntialias, - } = canvas; - - return { - isMaskEnabled, - shouldShowBoundingBox, - shouldShowGrid, - stageCoordinates, - stageDimensions, - stageScale, - tool, - isStaging, - shouldShowIntermediates, - shouldAntialias, - shouldRestrictStrokesToBox, - }; - } -); +const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => { + return { + stageCoordinates: canvas.stageCoordinates, + stageDimensions: canvas.stageDimensions, + }; +}); const ChakraStage = chakra(Stage, { shouldForwardProp: (prop) => !['sx'].includes(prop), }); const IAICanvas = () => { - const { - isMaskEnabled, - shouldShowBoundingBox, - shouldShowGrid, - stageCoordinates, - stageDimensions, - stageScale, - tool, - isStaging, - shouldShowIntermediates, - shouldAntialias, - shouldRestrictStrokesToBox, - } = useAppSelector(selector); - useCanvasHotkeys(); + const isStaging = useAppSelector(isStagingSelector); + const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled); + const shouldShowBoundingBox = useAppSelector( + (s) => s.canvas.shouldShowBoundingBox + ); + const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid); + const stageScale = useAppSelector((s) => s.canvas.stageScale); + const tool = useAppSelector((s) => s.canvas.tool); + const shouldShowIntermediates = useAppSelector( + (s) => s.canvas.shouldShowIntermediates + ); + const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias); + const shouldRestrictStrokesToBox = useAppSelector( + (s) => s.canvas.shouldRestrictStrokesToBox + ); + const { stageCoordinates, stageDimensions } = useAppSelector(selector); const dispatch = useAppDispatch(); const containerRef = useRef(null); const stageRef = useRef(null); @@ -101,6 +78,7 @@ const IAICanvas = () => { const isMovingStage = useStore($isMovingStage); const isTransformingBoundingBox = useStore($isTransformingBoundingBox); const isMouseOverBoundingBox = useStore($isMouseOverBoundingBox); + useCanvasHotkeys(); const canvasStageRefCallback = useCallback((el: Konva.Stage) => { setCanvasStage(el as Konva.Stage); stageRef.current = el; diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasBoundingBoxOverlay.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasBoundingBoxOverlay.tsx index 6b43be9ff1..8d364a91ab 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasBoundingBoxOverlay.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasBoundingBoxOverlay.tsx @@ -9,29 +9,28 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => { boundingBoxCoordinates, boundingBoxDimensions, stageDimensions, - stageScale, - shouldDarkenOutsideBoundingBox, stageCoordinates, } = canvas; return { boundingBoxCoordinates, boundingBoxDimensions, - shouldDarkenOutsideBoundingBox, stageCoordinates, stageDimensions, - stageScale, }; }); + const IAICanvasBoundingBoxOverlay = () => { const { boundingBoxCoordinates, boundingBoxDimensions, - shouldDarkenOutsideBoundingBox, stageCoordinates, stageDimensions, - stageScale, } = useAppSelector(selector); + const shouldDarkenOutsideBoundingBox = useAppSelector( + (s) => s.canvas.shouldDarkenOutsideBoundingBox + ); + const stageScale = useAppSelector((s) => s.canvas.stageScale); return ( diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasGrid.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasGrid.tsx index d1059262ce..69b1751d9f 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasGrid.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasGrid.tsx @@ -7,17 +7,19 @@ import { memo, useCallback, useMemo } from 'react'; import { Group, Line as KonvaLine } from 'react-konva'; import { getArbitraryBaseColor } from 'theme/colors'; -const selector = createMemoizedSelector([selectCanvasSlice], (canvas) => { - const { stageScale, stageCoordinates, stageDimensions } = canvas; - return { stageScale, stageCoordinates, stageDimensions }; +const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => { + return { + stageCoordinates: canvas.stageCoordinates, + stageDimensions: canvas.stageDimensions, + }; }); const baseGridLineColor = getArbitraryBaseColor(27); const fineGridLineColor = getArbitraryBaseColor(18); const IAICanvasGrid = () => { - const { stageScale, stageCoordinates, stageDimensions } = - useAppSelector(selector); + const { stageCoordinates, stageDimensions } = useAppSelector(selector); + const stageScale = useAppSelector((s) => s.canvas.stageScale); const gridSpacing = useMemo(() => { if (stageScale >= 2) { diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasIntermediateImage.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasIntermediateImage.tsx index 8c5c8b844a..966017a814 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasIntermediateImage.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasIntermediateImage.tsx @@ -1,33 +1,28 @@ -import { - createLruSelector, - createMemoizedSelector, -} from 'app/store/createMemoizedSelector'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { selectCanvasSlice } from 'features/canvas/store/canvasSlice'; import { selectSystemSlice } from 'features/system/store/systemSlice'; import { memo, useEffect, useState } from 'react'; import { Image as KonvaImage } from 'react-konva'; -const progressImageSelector = createLruSelector( +const progressImageSelector = createMemoizedSelector( [selectSystemSlice, selectCanvasSlice], (system, canvas) => { const { denoiseProgress } = system; const { batchIds } = canvas; - return denoiseProgress && batchIds.includes(denoiseProgress.batch_id) - ? denoiseProgress.progress_image - : undefined; + return { + progressImage: + denoiseProgress && batchIds.includes(denoiseProgress.batch_id) + ? denoiseProgress.progress_image + : undefined, + boundingBox: canvas.layerState.stagingArea.boundingBox, + }; } ); -const boundingBoxSelector = createMemoizedSelector( - [selectCanvasSlice], - (canvas) => canvas.layerState.stagingArea.boundingBox -); - const IAICanvasIntermediateImage = () => { - const progressImage = useAppSelector(progressImageSelector); - const boundingBox = useAppSelector(boundingBoxSelector); + const { progressImage, boundingBox } = useAppSelector(progressImageSelector); const [loadedImageElement, setLoadedImageElement] = useState(null); diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasMaskCompositer.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasMaskCompositer.tsx index 7ce2e36d67..1a0aaa62d2 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasMaskCompositer.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasMaskCompositer.tsx @@ -2,6 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import { selectCanvasSlice } from 'features/canvas/store/canvasSlice'; import { rgbaColorToString } from 'features/canvas/util/colorToString'; +import { getColoredMaskSVG } from 'features/canvas/util/getColoredMaskSVG'; import type Konva from 'konva'; import type { RectConfig } from 'konva/lib/shapes/Rect'; import { isNumber } from 'lodash-es'; @@ -11,107 +12,25 @@ import { Rect } from 'react-konva'; export const canvasMaskCompositerSelector = createMemoizedSelector( selectCanvasSlice, (canvas) => { - const { maskColor, stageCoordinates, stageDimensions, stageScale } = canvas; - return { - stageCoordinates, - stageDimensions, - stageScale, - maskColorString: rgbaColorToString(maskColor), + stageCoordinates: canvas.stageCoordinates, + stageDimensions: canvas.stageDimensions, }; } ); type IAICanvasMaskCompositerProps = RectConfig; -const getColoredSVG = (color: string) => { - return `data:image/svg+xml;utf8, - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`.replaceAll('black', color); -}; - const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => { const { ...rest } = props; - const { maskColorString, stageCoordinates, stageDimensions, stageScale } = - useAppSelector(canvasMaskCompositerSelector); - + const { stageCoordinates, stageDimensions } = useAppSelector( + canvasMaskCompositerSelector + ); + const stageScale = useAppSelector((s) => s.canvas.stageScale); + const maskColorString = useAppSelector((s) => + rgbaColorToString(s.canvas.maskColor) + ); const [fillPatternImage, setFillPatternImage] = useState(null); @@ -132,14 +51,14 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => { image.onload = () => { setFillPatternImage(image); }; - image.src = getColoredSVG(maskColorString); + image.src = getColoredMaskSVG(maskColorString); }, [fillPatternImage, maskColorString]); useEffect(() => { if (!fillPatternImage) { return; } - fillPatternImage.src = getColoredSVG(maskColorString); + fillPatternImage.src = getColoredMaskSVG(maskColorString); }, [fillPatternImage, maskColorString]); useEffect(() => { diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolPreview.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolPreview.tsx index 1921f08e6d..09e332caa3 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolPreview.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolPreview.tsx @@ -20,13 +20,6 @@ const canvasBrushPreviewSelector = createMemoizedSelector( selectCanvasSlice, (canvas) => { const { - brushSize, - colorPickerColor, - maskColor, - brushColor, - tool, - layer, - stageScale, stageDimensions, boundingBoxCoordinates, boundingBoxDimensions, @@ -82,17 +75,6 @@ const canvasBrushPreviewSelector = createMemoizedSelector( // : undefined; return { - radius: brushSize / 2, - colorPickerOuterRadius: COLOR_PICKER_SIZE / stageScale, - colorPickerInnerRadius: - (COLOR_PICKER_SIZE - COLOR_PICKER_STROKE_RADIUS + 1) / stageScale, - maskColorString: rgbaColorToString({ ...maskColor, a: 0.5 }), - brushColorString: rgbaColorToString(brushColor), - colorPickerColorString: rgbaColorToString(colorPickerColor), - tool, - layer, - strokeWidth: 1.5 / stageScale, - dotRadius: 1.5 / stageScale, clip, stageDimensions, }; @@ -103,20 +85,28 @@ const canvasBrushPreviewSelector = createMemoizedSelector( * Draws a black circle around the canvas brush preview. */ const IAICanvasToolPreview = (props: GroupConfig) => { - const { - radius, - maskColorString, - tool, - layer, - dotRadius, - strokeWidth, - brushColorString, - colorPickerColorString, - colorPickerInnerRadius, - colorPickerOuterRadius, - clip, - stageDimensions, - } = useAppSelector(canvasBrushPreviewSelector); + const radius = useAppSelector((s) => s.canvas.brushSize / 2); + const maskColorString = useAppSelector((s) => + rgbaColorToString({ ...s.canvas.maskColor, a: 0.5 }) + ); + const tool = useAppSelector((s) => s.canvas.tool); + const layer = useAppSelector((s) => s.canvas.layer); + const dotRadius = useAppSelector((s) => 1.5 / s.canvas.stageScale); + const strokeWidth = useAppSelector((s) => 1.5 / s.canvas.stageScale); + const brushColorString = useAppSelector((s) => + rgbaColorToString(s.canvas.brushColor) + ); + const colorPickerColorString = useAppSelector((s) => + rgbaColorToString(s.canvas.colorPickerColor) + ); + const colorPickerInnerRadius = useAppSelector( + (s) => + (COLOR_PICKER_SIZE - COLOR_PICKER_STROKE_RADIUS + 1) / s.canvas.stageScale + ); + const colorPickerOuterRadius = useAppSelector( + (s) => COLOR_PICKER_SIZE / s.canvas.stageScale + ); + const { clip, stageDimensions } = useAppSelector(canvasBrushPreviewSelector); const cursorPosition = useStore($cursorPosition); const isMovingBoundingBox = useStore($isMovingBoundingBox); diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx index caeac04055..b2245519a0 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx @@ -1,5 +1,4 @@ import { useStore } from '@nanostores/react'; -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { $shift } from 'common/hooks/useGlobalModifiers'; import { @@ -17,7 +16,6 @@ import { } from 'features/canvas/store/canvasNanostore'; import { aspectRatioChanged, - selectCanvasSlice, setBoundingBoxCoordinates, setBoundingBoxDimensions, setShouldSnapToGrid, @@ -38,47 +36,23 @@ import { Group, Rect, Transformer } from 'react-konva'; const borderDash = [4, 4]; -const boundingBoxPreviewSelector = createMemoizedSelector( - [selectCanvasSlice, selectOptimalDimension], - (canvas, optimalDimension) => { - const { - boundingBoxCoordinates, - boundingBoxDimensions, - stageScale, - tool, - shouldSnapToGrid, - aspectRatio, - } = canvas; - - return { - boundingBoxCoordinates, - boundingBoxDimensions, - stageScale, - shouldSnapToGrid, - tool, - hitStrokeWidth: 20 / stageScale, - aspectRatio, - optimalDimension, - }; - } -); - type IAICanvasBoundingBoxPreviewProps = GroupConfig; const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => { const { ...rest } = props; const dispatch = useAppDispatch(); - const { - boundingBoxCoordinates, - boundingBoxDimensions, - stageScale, - shouldSnapToGrid, - tool, - hitStrokeWidth, - aspectRatio, - optimalDimension, - } = useAppSelector(boundingBoxPreviewSelector); - + const boundingBoxCoordinates = useAppSelector( + (s) => s.canvas.boundingBoxCoordinates + ); + const boundingBoxDimensions = useAppSelector( + (s) => s.canvas.boundingBoxDimensions + ); + const stageScale = useAppSelector((s) => s.canvas.stageScale); + const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid); + const tool = useAppSelector((s) => s.canvas.tool); + const hitStrokeWidth = useAppSelector((s) => 20 / s.canvas.stageScale); + const aspectRatio = useAppSelector((s) => s.canvas.aspectRatio); + const optimalDimension = useAppSelector(selectOptimalDimension); const transformerRef = useRef(null); const shapeRef = useRef(null); const shift = useStore($shift); diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx index b4f2086284..e237b9e3e1 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx @@ -1,5 +1,4 @@ import { Box, Flex } from '@chakra-ui/react'; -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIColorPicker from 'common/components/IAIColorPicker'; import { InvButton } from 'common/components/InvButton/InvButton'; @@ -16,13 +15,11 @@ import { canvasMaskSavedToGallery } from 'features/canvas/store/actions'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { clearMask, - selectCanvasSlice, setIsMaskEnabled, setLayer, setMaskColor, setShouldPreserveMaskedArea, } from 'features/canvas/store/canvasSlice'; -import { rgbaColorToString } from 'features/canvas/util/colorToString'; import type { ChangeEvent } from 'react'; import { memo, useCallback } from 'react'; import type { RgbaColor } from 'react-colorful'; @@ -30,33 +27,16 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { FaMask, FaSave, FaTrash } from 'react-icons/fa'; -export const selector = createMemoizedSelector( - [selectCanvasSlice, isStagingSelector], - (canvas, isStaging) => { - const { maskColor, layer, isMaskEnabled, shouldPreserveMaskedArea } = - canvas; - - return { - layer, - maskColor, - maskColorString: rgbaColorToString(maskColor), - isMaskEnabled, - shouldPreserveMaskedArea, - isStaging, - }; - } -); const IAICanvasMaskOptions = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - - const { - layer, - maskColor, - isMaskEnabled, - shouldPreserveMaskedArea, - isStaging, - } = useAppSelector(selector); + const layer = useAppSelector((s) => s.canvas.layer); + const maskColor = useAppSelector((s) => s.canvas.maskColor); + const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled); + const shouldPreserveMaskedArea = useAppSelector( + (s) => s.canvas.shouldPreserveMaskedArea + ); + const isStaging = useAppSelector(isStagingSelector); useHotkeys( ['q'], diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx index 8dc28766ff..892ddfe7f3 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx @@ -1,5 +1,4 @@ import { Flex } from '@chakra-ui/react'; -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { InvCheckbox } from 'common/components/InvCheckbox/wrapper'; import { InvControl } from 'common/components/InvControl/InvControl'; @@ -10,7 +9,6 @@ import { } from 'common/components/InvPopover/wrapper'; import ClearCanvasHistoryButtonModal from 'features/canvas/components/ClearCanvasHistoryButtonModal'; import { - selectCanvasSlice, setShouldAntialias, setShouldAutoSave, setShouldCropToBoundingBoxOnSave, @@ -28,50 +26,28 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { FaWrench } from 'react-icons/fa'; -export const canvasControlsSelector = createMemoizedSelector( - [selectCanvasSlice], - (canvas) => { - const { - shouldAutoSave, - shouldCropToBoundingBoxOnSave, - shouldDarkenOutsideBoundingBox, - shouldShowCanvasDebugInfo, - shouldShowGrid, - shouldShowIntermediates, - shouldSnapToGrid, - shouldRestrictStrokesToBox, - shouldAntialias, - } = canvas; - - return { - shouldAutoSave, - shouldCropToBoundingBoxOnSave, - shouldDarkenOutsideBoundingBox, - shouldShowCanvasDebugInfo, - shouldShowGrid, - shouldShowIntermediates, - shouldSnapToGrid, - shouldRestrictStrokesToBox, - shouldAntialias, - }; - } -); - const IAICanvasSettingsButtonPopover = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - - const { - shouldAutoSave, - shouldCropToBoundingBoxOnSave, - shouldDarkenOutsideBoundingBox, - shouldShowCanvasDebugInfo, - shouldShowGrid, - shouldShowIntermediates, - shouldSnapToGrid, - shouldRestrictStrokesToBox, - shouldAntialias, - } = useAppSelector(canvasControlsSelector); + const shouldAutoSave = useAppSelector((s) => s.canvas.shouldAutoSave); + const shouldCropToBoundingBoxOnSave = useAppSelector( + (s) => s.canvas.shouldCropToBoundingBoxOnSave + ); + const shouldDarkenOutsideBoundingBox = useAppSelector( + (s) => s.canvas.shouldDarkenOutsideBoundingBox + ); + const shouldShowCanvasDebugInfo = useAppSelector( + (s) => s.canvas.shouldShowCanvasDebugInfo + ); + const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid); + const shouldShowIntermediates = useAppSelector( + (s) => s.canvas.shouldShowIntermediates + ); + const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid); + const shouldRestrictStrokesToBox = useAppSelector( + (s) => s.canvas.shouldRestrictStrokesToBox + ); + const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias); useHotkeys( ['n'], diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx index c62c00d9a9..bfbd78be60 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx @@ -1,5 +1,4 @@ import { Flex } from '@chakra-ui/react'; -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup'; import { InvControl } from 'common/components/InvControl/InvControl'; @@ -19,7 +18,6 @@ import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { resetCanvas, resetCanvasView, - selectCanvasSlice, setIsMaskEnabled, setLayer, setTool, @@ -48,27 +46,13 @@ import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover'; import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions'; import IAICanvasUndoButton from './IAICanvasUndoButton'; -export const selector = createMemoizedSelector( - [selectCanvasSlice, isStagingSelector], - (canvas, isStaging) => { - const { tool, shouldCropToBoundingBoxOnSave, layer, isMaskEnabled } = - canvas; - - return { - isStaging, - isMaskEnabled, - tool, - layer, - shouldCropToBoundingBoxOnSave, - }; - } -); - const IAICanvasToolbar = () => { const dispatch = useAppDispatch(); - const { isStaging, isMaskEnabled, layer, tool } = useAppSelector(selector); + const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled); + const layer = useAppSelector((s) => s.canvas.layer); + const tool = useAppSelector((s) => s.canvas.tool); + const isStaging = useAppSelector(isStagingSelector); const canvasBaseLayer = getCanvasBaseLayer(); - const { t } = useTranslation(); const { isClipboardAPIAvailable } = useCopyImageToClipboard(); diff --git a/invokeai/frontend/web/src/features/canvas/hooks/useCanvasHotkeys.ts b/invokeai/frontend/web/src/features/canvas/hooks/useCanvasHotkeys.ts index 39a617ffba..508395e439 100644 --- a/invokeai/frontend/web/src/features/canvas/hooks/useCanvasHotkeys.ts +++ b/invokeai/frontend/web/src/features/canvas/hooks/useCanvasHotkeys.ts @@ -1,4 +1,3 @@ -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { resetCanvasInteractionState, @@ -7,7 +6,6 @@ import { import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { clearMask, - selectCanvasSlice, setIsMaskEnabled, setShouldShowBoundingBox, setShouldSnapToGrid, @@ -16,49 +14,24 @@ import { import type { CanvasTool } from 'features/canvas/store/canvasTypes'; import { getCanvasStage } from 'features/canvas/util/konvaInstanceProvider'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; -import { useRef } from 'react'; +import { useCallback, useRef } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; -const selector = createMemoizedSelector( - [selectCanvasSlice, activeTabNameSelector, isStagingSelector], - (canvas, activeTabName, isStaging) => { - const { - shouldLockBoundingBox, - shouldShowBoundingBox, - tool, - isMaskEnabled, - shouldSnapToGrid, - } = canvas; - - return { - activeTabName, - shouldLockBoundingBox, - shouldShowBoundingBox, - tool, - isStaging, - isMaskEnabled, - shouldSnapToGrid, - }; - } -); - const useInpaintingCanvasHotkeys = () => { const dispatch = useAppDispatch(); - const { - activeTabName, - shouldShowBoundingBox, - tool, - isStaging, - isMaskEnabled, - shouldSnapToGrid, - } = useAppSelector(selector); - + const activeTabName = useAppSelector(activeTabNameSelector); + const shouldShowBoundingBox = useAppSelector( + (s) => s.canvas.shouldShowBoundingBox + ); + const tool = useAppSelector((s) => s.canvas.tool); + const isStaging = useAppSelector(isStagingSelector); + const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled); + const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid); const previousToolRef = useRef(null); - const canvasStage = getCanvasStage(); // Beta Keys - const handleClearMask = () => dispatch(clearMask()); + const handleClearMask = useCallback(() => dispatch(clearMask()), [dispatch]); useHotkeys( ['shift+c'], diff --git a/invokeai/frontend/web/src/features/canvas/hooks/useCanvasMouseMove.ts b/invokeai/frontend/web/src/features/canvas/hooks/useCanvasMouseMove.ts index add621d799..b3cf343b14 100644 --- a/invokeai/frontend/web/src/features/canvas/hooks/useCanvasMouseMove.ts +++ b/invokeai/frontend/web/src/features/canvas/hooks/useCanvasMouseMove.ts @@ -1,5 +1,4 @@ import { useStore } from '@nanostores/react'; -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { $isDrawing, @@ -8,10 +7,8 @@ import { import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { addPointToCurrentLine, - selectCanvasSlice, } from 'features/canvas/store/canvasSlice'; import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import type Konva from 'konva'; import type { Vector2d } from 'konva/lib/types'; import type { MutableRefObject } from 'react'; @@ -19,17 +16,6 @@ import { useCallback } from 'react'; import useColorPicker from './useColorUnderCursor'; -const selector = createMemoizedSelector( - [activeTabNameSelector, selectCanvasSlice, isStagingSelector], - (activeTabName, canvas, isStaging) => { - return { - tool: canvas.tool, - activeTabName, - isStaging, - }; - } -); - const useCanvasMouseMove = ( stageRef: MutableRefObject, didMouseMoveRef: MutableRefObject, @@ -37,7 +23,8 @@ const useCanvasMouseMove = ( ) => { const dispatch = useAppDispatch(); const isDrawing = useStore($isDrawing); - const { tool, isStaging } = useAppSelector(selector); + const tool = useAppSelector((s) => s.canvas.tool); + const isStaging = useAppSelector(isStagingSelector); const { updateColorUnderCursor } = useColorPicker(); return useCallback(() => { diff --git a/invokeai/frontend/web/src/features/canvas/util/getColoredMaskSVG.ts b/invokeai/frontend/web/src/features/canvas/util/getColoredMaskSVG.ts new file mode 100644 index 0000000000..47e1100447 --- /dev/null +++ b/invokeai/frontend/web/src/features/canvas/util/getColoredMaskSVG.ts @@ -0,0 +1,81 @@ +export const getColoredMaskSVG = (color: string) => { + return `data:image/svg+xml;utf8, + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`.replaceAll('black', color); +}; diff --git a/invokeai/frontend/web/src/features/ui/store/uiSelectors.ts b/invokeai/frontend/web/src/features/ui/store/uiSelectors.ts index 5a75325327..7556adef79 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSelectors.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSelectors.ts @@ -1,11 +1,11 @@ -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { createSelector } from '@reduxjs/toolkit'; import { selectConfigSlice } from 'features/system/store/configSlice'; import { selectUiSlice } from 'features/ui/store/uiSlice'; import { isString } from 'lodash-es'; import { tabMap } from './tabMap'; -export const activeTabNameSelector = createMemoizedSelector( +export const activeTabNameSelector = createSelector( selectUiSlice, /** * Previously `activeTab` was an integer, but now it's a string. @@ -14,7 +14,7 @@ export const activeTabNameSelector = createMemoizedSelector( (ui) => (isString(ui.activeTab) ? ui.activeTab : 'txt2img') ); -export const activeTabIndexSelector = createMemoizedSelector( +export const activeTabIndexSelector = createSelector( selectUiSlice, selectConfigSlice, (ui, config) => {