feat(ui): move canvas stage and base layer to nanostores

This commit is contained in:
psychedelicious 2024-01-09 18:24:50 +11:00 committed by Kent Keirsey
parent 21ab650ac0
commit 68a231afea
11 changed files with 67 additions and 94 deletions

View File

@ -1,8 +1,8 @@
import { $logger } from 'app/logging/logger'; import { $logger } from 'app/logging/logger';
import { canvasMerged } from 'features/canvas/store/actions'; import { canvasMerged } from 'features/canvas/store/actions';
import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore';
import { setMergedCanvas } from 'features/canvas/store/canvasSlice'; import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob'; import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
@ -30,7 +30,7 @@ export const addCanvasMergedListener = () => {
return; return;
} }
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = $canvasBaseLayer.get();
if (!canvasBaseLayer) { if (!canvasBaseLayer) {
moduleLog.error('Problem getting canvas base layer'); moduleLog.error('Problem getting canvas base layer');

View File

@ -10,21 +10,16 @@ import useCanvasMouseOut from 'features/canvas/hooks/useCanvasMouseOut';
import useCanvasMouseUp from 'features/canvas/hooks/useCanvasMouseUp'; import useCanvasMouseUp from 'features/canvas/hooks/useCanvasMouseUp';
import useCanvasWheel from 'features/canvas/hooks/useCanvasZoom'; import useCanvasWheel from 'features/canvas/hooks/useCanvasZoom';
import { import {
$isModifyingBoundingBox, $canvasBaseLayer, $canvasStage,$isModifyingBoundingBox,
$isMouseOverBoundingBox, $isMouseOverBoundingBox,
$isMovingStage, $isMovingStage,
$isTransformingBoundingBox, $isTransformingBoundingBox,
$tool, $tool } from 'features/canvas/store/canvasNanostore';
} from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
canvasResized, canvasResized,
selectCanvasSlice, selectCanvasSlice,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import {
setCanvasBaseLayer,
setCanvasStage,
} from 'features/canvas/util/konvaInstanceProvider';
import type Konva from 'konva'; import type Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node'; import type { KonvaEventObject } from 'konva/lib/Node';
import type { Vector2d } from 'konva/lib/types'; import type { Vector2d } from 'konva/lib/types';
@ -80,9 +75,9 @@ const IAICanvas = () => {
const isMouseOverBoundingBox = useStore($isMouseOverBoundingBox); const isMouseOverBoundingBox = useStore($isMouseOverBoundingBox);
const tool = useStore($tool); const tool = useStore($tool);
useCanvasHotkeys(); useCanvasHotkeys();
const canvasStageRefCallback = useCallback((el: Konva.Stage) => { const canvasStageRefCallback = useCallback((stageElement: Konva.Stage) => {
setCanvasStage(el as Konva.Stage); $canvasStage.set(stageElement);
stageRef.current = el; stageRef.current = stageElement;
}, []); }, []);
const stageCursor = useMemo(() => { const stageCursor = useMemo(() => {
if (tool === 'move' || isStaging) { if (tool === 'move' || isStaging) {
@ -105,10 +100,14 @@ const IAICanvas = () => {
shouldRestrictStrokesToBox, shouldRestrictStrokesToBox,
tool, tool,
]); ]);
const canvasBaseLayerRefCallback = useCallback((el: Konva.Layer) => {
setCanvasBaseLayer(el as Konva.Layer); const canvasBaseLayerRefCallback = useCallback(
canvasBaseLayerRef.current = el; (layerElement: Konva.Layer) => {
}, []); $canvasBaseLayer.set(layerElement);
canvasBaseLayerRef.current = layerElement;
},
[]
);
const lastCursorPositionRef = useRef<Vector2d>({ x: 0, y: 0 }); const lastCursorPositionRef = useRef<Vector2d>({ x: 0, y: 0 });

View File

@ -5,6 +5,7 @@ import {
$cursorPosition, $cursorPosition,
$isMovingBoundingBox, $isMovingBoundingBox,
$isTransformingBoundingBox, $isTransformingBoundingBox,
$tool,
} from 'features/canvas/store/canvasNanostore'; } from 'features/canvas/store/canvasNanostore';
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice'; import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { rgbaColorToString } from 'features/canvas/util/colorToString';
@ -89,7 +90,7 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
const maskColorString = useAppSelector((s) => const maskColorString = useAppSelector((s) =>
rgbaColorToString({ ...s.canvas.maskColor, a: 0.5 }) rgbaColorToString({ ...s.canvas.maskColor, a: 0.5 })
); );
const tool = useAppSelector((s) => s.canvas.tool); const tool = useStore($tool);
const layer = useAppSelector((s) => s.canvas.layer); const layer = useAppSelector((s) => s.canvas.layer);
const dotRadius = useAppSelector((s) => 1.5 / s.canvas.stageScale); const dotRadius = useAppSelector((s) => 1.5 / s.canvas.stageScale);
const strokeWidth = useAppSelector((s) => 1.5 / s.canvas.stageScale); const strokeWidth = useAppSelector((s) => 1.5 / s.canvas.stageScale);

View File

@ -15,7 +15,7 @@ import {
canvasMerged, canvasMerged,
canvasSavedToGallery, canvasSavedToGallery,
} from 'features/canvas/store/actions'; } from 'features/canvas/store/actions';
import { $tool } from 'features/canvas/store/canvasNanostore'; import { $canvasBaseLayer,$tool } from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
resetCanvas, resetCanvas,
@ -25,7 +25,6 @@ import {
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import type { CanvasLayer } from 'features/canvas/store/canvasTypes'; import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes'; import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { InvIconButton } from 'index'; import { InvIconButton } from 'index';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
@ -53,7 +52,6 @@ const IAICanvasToolbar = () => {
const layer = useAppSelector((s) => s.canvas.layer); const layer = useAppSelector((s) => s.canvas.layer);
const tool = useStore($tool); const tool = useStore($tool);
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
const canvasBaseLayer = getCanvasBaseLayer();
const { t } = useTranslation(); const { t } = useTranslation();
const { isClipboardAPIAvailable } = useCopyImageToClipboard(); const { isClipboardAPIAvailable } = useCopyImageToClipboard();
@ -82,7 +80,7 @@ const IAICanvasToolbar = () => {
enabled: () => true, enabled: () => true,
preventDefault: true, preventDefault: true,
}, },
[canvasBaseLayer] []
); );
useHotkeys( useHotkeys(
@ -94,7 +92,7 @@ const IAICanvasToolbar = () => {
enabled: () => !isStaging, enabled: () => !isStaging,
preventDefault: true, preventDefault: true,
}, },
[canvasBaseLayer] []
); );
useHotkeys( useHotkeys(
@ -106,7 +104,7 @@ const IAICanvasToolbar = () => {
enabled: () => !isStaging, enabled: () => !isStaging,
preventDefault: true, preventDefault: true,
}, },
[canvasBaseLayer] []
); );
useHotkeys( useHotkeys(
@ -118,7 +116,7 @@ const IAICanvasToolbar = () => {
enabled: () => !isStaging && isClipboardAPIAvailable, enabled: () => !isStaging && isClipboardAPIAvailable,
preventDefault: true, preventDefault: true,
}, },
[canvasBaseLayer, isClipboardAPIAvailable] [isClipboardAPIAvailable]
); );
useHotkeys( useHotkeys(
@ -130,7 +128,7 @@ const IAICanvasToolbar = () => {
enabled: () => !isStaging, enabled: () => !isStaging,
preventDefault: true, preventDefault: true,
}, },
[canvasBaseLayer] []
); );
const handleSelectMoveTool = useCallback(() => { const handleSelectMoveTool = useCallback(() => {
@ -143,7 +141,7 @@ const IAICanvasToolbar = () => {
); );
const handleResetCanvasView = (shouldScaleTo1 = false) => { const handleResetCanvasView = (shouldScaleTo1 = false) => {
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = $canvasBaseLayer.get();
if (!canvasBaseLayer) { if (!canvasBaseLayer) {
return; return;
} }

View File

@ -1,10 +1,9 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
$tool, $canvasStage, $tool,
$toolStash, $toolStash,
resetCanvasInteractionState, resetCanvasInteractionState,
resetToolInteractionState, resetToolInteractionState } from 'features/canvas/store/canvasNanostore';
} from 'features/canvas/store/canvasNanostore';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { import {
clearMask, clearMask,
@ -12,7 +11,6 @@ import {
setShouldShowBoundingBox, setShouldShowBoundingBox,
setShouldSnapToGrid, setShouldSnapToGrid,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { getCanvasStage } from 'features/canvas/util/konvaInstanceProvider';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
@ -26,7 +24,6 @@ const useInpaintingCanvasHotkeys = () => {
const isStaging = useAppSelector(isStagingSelector); const isStaging = useAppSelector(isStagingSelector);
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled); const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid); const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
const canvasStage = getCanvasStage();
// Beta Keys // Beta Keys
const handleClearMask = useCallback(() => dispatch(clearMask()), [dispatch]); const handleClearMask = useCallback(() => dispatch(clearMask()), [dispatch]);
@ -102,35 +99,29 @@ const useInpaintingCanvasHotkeys = () => {
}); });
}, []); }, []);
const onKeyDown = useCallback( const onKeyDown = useCallback((e: KeyboardEvent) => {
(e: KeyboardEvent) => { if (e.repeat || e.key !== ' ') {
if (e.repeat || e.key !== ' ') { return;
return; }
} if ($toolStash.get() || $tool.get() === 'move') {
if ($toolStash.get() || $tool.get() === 'move') { return;
return; }
} $canvasStage.get()?.container().focus();
canvasStage?.container().focus(); $toolStash.set($tool.get());
$toolStash.set($tool.get()); $tool.set('move');
$tool.set('move'); resetToolInteractionState();
resetToolInteractionState(); }, []);
}, const onKeyUp = useCallback((e: KeyboardEvent) => {
[canvasStage] if (e.repeat || e.key !== ' ') {
); return;
const onKeyUp = useCallback( }
(e: KeyboardEvent) => { if (!$toolStash.get() || $tool.get() !== 'move') {
if (e.repeat || e.key !== ' ') { return;
return; }
} $canvasStage.get()?.container().focus();
if (!$toolStash.get() || $tool.get() !== 'move') { $tool.set($toolStash.get() ?? 'move');
return; $toolStash.set(null);
} }, []);
canvasStage?.container().focus();
$tool.set($toolStash.get() ?? 'move');
$toolStash.set(null);
},
[canvasStage]
);
useEffect(() => { useEffect(() => {
window.addEventListener('keydown', onKeyDown); window.addEventListener('keydown', onKeyDown);

View File

@ -1,22 +1,18 @@
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { $tool } from 'features/canvas/store/canvasNanostore'; import { $canvasBaseLayer,$canvasStage,$tool } from 'features/canvas/store/canvasNanostore';
import { import {
commitColorPickerColor, commitColorPickerColor,
setColorPickerColor, setColorPickerColor,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import {
getCanvasBaseLayer,
getCanvasStage,
} from 'features/canvas/util/konvaInstanceProvider';
import Konva from 'konva'; import Konva from 'konva';
import { useCallback } from 'react'; import { useCallback } from 'react';
const useColorPicker = () => { const useColorPicker = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const canvasBaseLayer = getCanvasBaseLayer();
const stage = getCanvasStage();
const updateColorUnderCursor = useCallback(() => { const updateColorUnderCursor = useCallback(() => {
const stage = $canvasStage.get();
const canvasBaseLayer = $canvasBaseLayer.get();
if (!stage || !canvasBaseLayer) { if (!stage || !canvasBaseLayer) {
return; return;
} }
@ -48,11 +44,11 @@ const useColorPicker = () => {
} }
dispatch(setColorPickerColor({ r, g, b, a })); dispatch(setColorPickerColor({ r, g, b, a }));
}, [canvasBaseLayer, dispatch, stage]); }, [dispatch]);
const commitColorUnderCursor = useCallback(() => { const commitColorUnderCursor = useCallback(() => {
dispatch(commitColorPickerColor()); dispatch(commitColorPickerColor());
$tool.set('brush') $tool.set('brush');
}, [dispatch]); }, [dispatch]);
return { updateColorUnderCursor, commitColorUnderCursor }; return { updateColorUnderCursor, commitColorUnderCursor };

View File

@ -1,4 +1,5 @@
import type { CanvasTool } from 'features/canvas/store/canvasTypes'; import type { CanvasTool } from 'features/canvas/store/canvasTypes';
import type Konva from "konva";
import type { Vector2d } from 'konva/lib/types'; import type { Vector2d } from 'konva/lib/types';
import { atom, computed } from 'nanostores'; import { atom, computed } from 'nanostores';
@ -38,4 +39,6 @@ export const resetToolInteractionState = () => {
export const setCanvasInteractionStateMouseOut = () => { export const setCanvasInteractionStateMouseOut = () => {
$cursorPosition.set(null); $cursorPosition.set(null);
}; };export const $canvasBaseLayer = atom<Konva.Layer | null>(null);
export const $canvasStage = atom<Konva.Stage | null>(null);

View File

@ -1,6 +1,6 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore';
import { getCanvasBaseLayer } from './konvaInstanceProvider';
import { konvaNodeToBlob } from './konvaNodeToBlob'; import { konvaNodeToBlob } from './konvaNodeToBlob';
/** /**
@ -10,7 +10,7 @@ export const getBaseLayerBlob = async (
state: RootState, state: RootState,
alwaysUseBoundingBox: boolean = false alwaysUseBoundingBox: boolean = false
) => { ) => {
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = $canvasBaseLayer.get();
if (!canvasBaseLayer) { if (!canvasBaseLayer) {
throw new Error('Problem getting base layer blob'); throw new Error('Problem getting base layer blob');

View File

@ -1,15 +1,15 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { $canvasBaseLayer , $canvasStage } from 'features/canvas/store/canvasNanostore';
import type { import type {
CanvasLayerState, CanvasLayerState,
Dimensions, Dimensions,
} from 'features/canvas/store/canvasTypes'; } from 'features/canvas/store/canvasTypes';
import { isCanvasMaskLine } from 'features/canvas/store/canvasTypes'; import { isCanvasMaskLine } from 'features/canvas/store/canvasTypes';
import { konvaNodeToImageData } from 'features/canvas/util/konvaNodeToImageData';
import type { Vector2d } from 'konva/lib/types'; import type { Vector2d } from 'konva/lib/types';
import createMaskStage from './createMaskStage'; import createMaskStage from './createMaskStage';
import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider';
import { konvaNodeToBlob } from './konvaNodeToBlob'; import { konvaNodeToBlob } from './konvaNodeToBlob';
import { konvaNodeToImageData } from './konvaNodeToImageData';
/** /**
* Gets Blob and ImageData objects for the base and mask layers * Gets Blob and ImageData objects for the base and mask layers
@ -23,8 +23,8 @@ export const getCanvasData = async (
) => { ) => {
const log = logger('canvas'); const log = logger('canvas');
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = $canvasBaseLayer.get();
const canvasStage = getCanvasStage(); const canvasStage = $canvasStage.get();
if (!canvasBaseLayer || !canvasStage) { if (!canvasBaseLayer || !canvasStage) {
log.error('Unable to find canvas / stage'); log.error('Unable to find canvas / stage');

View File

@ -1,11 +1,12 @@
import { getCanvasBaseLayer } from './konvaInstanceProvider'; import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore';
import { konvaNodeToBlob } from './konvaNodeToBlob'; import { konvaNodeToBlob } from './konvaNodeToBlob';
/** /**
* Gets the canvas base layer blob, without bounding box * Gets the canvas base layer blob, without bounding box
*/ */
export const getFullBaseLayerBlob = async () => { export const getFullBaseLayerBlob = async () => {
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = $canvasBaseLayer.get();
if (!canvasBaseLayer) { if (!canvasBaseLayer) {
return; return;

View File

@ -1,16 +0,0 @@
import type Konva from 'konva';
let canvasBaseLayer: Konva.Layer | null = null;
let canvasStage: Konva.Stage | null = null;
export const setCanvasBaseLayer = (layer: Konva.Layer) => {
canvasBaseLayer = layer;
};
export const getCanvasBaseLayer = () => canvasBaseLayer;
export const setCanvasStage = (stage: Konva.Stage) => {
canvasStage = stage;
};
export const getCanvasStage = () => canvasStage;