From b3946aa3b430e15b8cd9c2a120cfabbee99b2424 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 2 May 2025 14:46:57 +0200 Subject: [PATCH] Further reverse refactor --- .../components/Stats/MultiPosition.tsx | 107 +++++++++++- .../excalidraw/components/Stats/Position.tsx | 152 +++++++++++++++++- packages/excalidraw/components/Stats/utils.ts | 98 +---------- 3 files changed, 256 insertions(+), 101 deletions(-) diff --git a/packages/excalidraw/components/Stats/MultiPosition.tsx b/packages/excalidraw/components/Stats/MultiPosition.tsx index e29070f91..d7f2bbce3 100644 --- a/packages/excalidraw/components/Stats/MultiPosition.tsx +++ b/packages/excalidraw/components/Stats/MultiPosition.tsx @@ -8,9 +8,18 @@ import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; import type Scene from "@excalidraw/element/Scene"; import StatsDragInput from "./DragInput"; -import { handlePositionChange } from "./utils"; +import { + getAtomicUnits, + getStepSizedValue, + isPropertyEditable, + moveElements, + moveGroupTo, + STEP_SIZE, + type AtomicUnit, +} from "./utils"; +import { getElementsInAtomicUnit, moveElement } from "./utils"; -import type { AtomicUnit } from "./utils"; +import type { DragInputCallbackType } from "./DragInput"; import type { AppState } from "../../types"; interface MultiPositionProps { @@ -22,6 +31,100 @@ interface MultiPositionProps { appState: AppState; } +const handlePositionChange: DragInputCallbackType< + MultiPositionProps["property"] +> = ({ + accumulatedChange, + originalElements, + originalElementsMap, + shouldChangeByStepSize, + nextValue, + property, + scene, + originalAppState, +}) => { + const elementsMap = scene.getNonDeletedElementsMap(); + + if (nextValue !== undefined) { + for (const atomicUnit of getAtomicUnits( + originalElements, + originalAppState, + )) { + const elementsInUnit = getElementsInAtomicUnit( + atomicUnit, + elementsMap, + originalElementsMap, + ); + + if (elementsInUnit.length > 1) { + const [x1, y1, ,] = getCommonBounds( + elementsInUnit.map((el) => el.latest!), + ); + const newTopLeftX = property === "x" ? nextValue : x1; + const newTopLeftY = property === "y" ? nextValue : y1; + + moveGroupTo( + newTopLeftX, + newTopLeftY, + elementsInUnit.map((el) => el.original), + originalElementsMap, + scene, + ); + } else { + const origElement = elementsInUnit[0]?.original; + const latestElement = elementsInUnit[0]?.latest; + if ( + origElement && + latestElement && + isPropertyEditable(latestElement, property) + ) { + const [cx, cy] = [ + origElement.x + origElement.width / 2, + origElement.y + origElement.height / 2, + ]; + const [topLeftX, topLeftY] = pointRotateRads( + pointFrom(origElement.x, origElement.y), + pointFrom(cx, cy), + origElement.angle, + ); + + const newTopLeftX = property === "x" ? nextValue : topLeftX; + const newTopLeftY = property === "y" ? nextValue : topLeftY; + moveElement( + newTopLeftX, + newTopLeftY, + origElement, + scene, + originalElementsMap, + false, + ); + } + } + } + + scene.triggerUpdate(); + return; + } + + const change = shouldChangeByStepSize + ? getStepSizedValue(accumulatedChange, STEP_SIZE) + : accumulatedChange; + + const changeInTopX = property === "x" ? change : 0; + const changeInTopY = property === "y" ? change : 0; + + moveElements( + property, + changeInTopX, + changeInTopY, + originalElements, + originalElementsMap, + scene, + ); + + scene.triggerUpdate(); +}; + const MultiPosition = ({ property, elements, diff --git a/packages/excalidraw/components/Stats/Position.tsx b/packages/excalidraw/components/Stats/Position.tsx index 0b2ba2984..7b590cdf7 100644 --- a/packages/excalidraw/components/Stats/Position.tsx +++ b/packages/excalidraw/components/Stats/Position.tsx @@ -1,6 +1,9 @@ -import { pointFrom, pointRotateRads, round } from "@excalidraw/math"; +import { clamp, pointFrom, pointRotateRads, round } from "@excalidraw/math"; -import { getFlipAdjustedCropPosition } from "@excalidraw/element/cropElement"; +import { + getFlipAdjustedCropPosition, + getUncroppedWidthAndHeight, +} from "@excalidraw/element/cropElement"; import { isImageElement } from "@excalidraw/element/typeChecks"; import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; @@ -8,8 +11,9 @@ import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; import type Scene from "@excalidraw/element/Scene"; import StatsDragInput from "./DragInput"; -import { handlePositionChange } from "./utils"; +import { getStepSizedValue, moveElement, STEP_SIZE } from "./utils"; +import type { DragInputCallbackType } from "./DragInput"; import type { AppState } from "../../types"; interface PositionProps { @@ -20,6 +24,148 @@ interface PositionProps { appState: AppState; } +const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({ + accumulatedChange, + instantChange, + originalElements, + originalElementsMap, + shouldChangeByStepSize, + nextValue, + property, + scene, + originalAppState, +}) => { + const elementsMap = scene.getNonDeletedElementsMap(); + const origElement = originalElements[0]; + const [cx, cy] = [ + origElement.x + origElement.width / 2, + origElement.y + origElement.height / 2, + ]; + const [topLeftX, topLeftY] = pointRotateRads( + pointFrom(origElement.x, origElement.y), + pointFrom(cx, cy), + origElement.angle, + ); + + if (originalAppState.croppingElementId === origElement.id) { + const element = elementsMap.get(origElement.id); + + if (!element || !isImageElement(element) || !element.crop) { + return; + } + + const crop = element.crop; + let nextCrop = crop; + const isFlippedByX = element.scale[0] === -1; + const isFlippedByY = element.scale[1] === -1; + const { width: uncroppedWidth, height: uncroppedHeight } = + getUncroppedWidthAndHeight(element); + + if (nextValue !== undefined) { + if (property === "x") { + const nextValueInNatural = + nextValue * (crop.naturalWidth / uncroppedWidth); + + if (isFlippedByX) { + nextCrop = { + ...crop, + x: clamp( + crop.naturalWidth - nextValueInNatural - crop.width, + 0, + crop.naturalWidth - crop.width, + ), + }; + } else { + nextCrop = { + ...crop, + x: clamp( + nextValue * (crop.naturalWidth / uncroppedWidth), + 0, + crop.naturalWidth - crop.width, + ), + }; + } + } + + if (property === "y") { + nextCrop = { + ...crop, + y: clamp( + nextValue * (crop.naturalHeight / uncroppedHeight), + 0, + crop.naturalHeight - crop.height, + ), + }; + } + + scene.mutateElement(element, { + crop: nextCrop, + }); + + return; + } + + const changeInX = + (property === "x" ? instantChange : 0) * (isFlippedByX ? -1 : 1); + const changeInY = + (property === "y" ? instantChange : 0) * (isFlippedByY ? -1 : 1); + + nextCrop = { + ...crop, + x: clamp(crop.x + changeInX, 0, crop.naturalWidth - crop.width), + y: clamp(crop.y + changeInY, 0, crop.naturalHeight - crop.height), + }; + + scene.mutateElement(element, { + crop: nextCrop, + }); + + return; + } + + if (nextValue !== undefined) { + const newTopLeftX = property === "x" ? nextValue : topLeftX; + const newTopLeftY = property === "y" ? nextValue : topLeftY; + moveElement( + newTopLeftX, + newTopLeftY, + origElement, + scene, + originalElementsMap, + ); + return; + } + + const changeInTopX = property === "x" ? accumulatedChange : 0; + const changeInTopY = property === "y" ? accumulatedChange : 0; + + const newTopLeftX = + property === "x" + ? Math.round( + shouldChangeByStepSize + ? getStepSizedValue(origElement.x + changeInTopX, STEP_SIZE) + : topLeftX + changeInTopX, + ) + : topLeftX; + + const newTopLeftY = + property === "y" + ? Math.round( + shouldChangeByStepSize + ? getStepSizedValue(origElement.y + changeInTopY, STEP_SIZE) + : topLeftY + changeInTopY, + ) + : topLeftY; + + moveElement( + newTopLeftX, + newTopLeftY, + origElement, + scene, + originalElementsMap, + ); +}; + const Position = ({ property, element, diff --git a/packages/excalidraw/components/Stats/utils.ts b/packages/excalidraw/components/Stats/utils.ts index ef8a663e3..c35c70c8a 100644 --- a/packages/excalidraw/components/Stats/utils.ts +++ b/packages/excalidraw/components/Stats/utils.ts @@ -31,7 +31,6 @@ import type { import type Scene from "@excalidraw/element/Scene"; import type { AppState } from "../../types"; -import type { DragInputCallbackType } from "./DragInput"; export type StatsInputProperty = | "x" @@ -43,6 +42,7 @@ export type StatsInputProperty = | "gridStep"; export const SMALLEST_DELTA = 0.01; +export const STEP_SIZE = 10; export const isPropertyEditable = ( element: ExcalidrawElement, @@ -274,7 +274,7 @@ export const moveElements = ( } }; -export const moveGroup = ( +export const moveGroupTo = ( nextX: number, nextY: number, originalElements: ExcalidrawElement[], @@ -355,97 +355,3 @@ export const updateBindings = ( updateBoundElements(latestElement, scene, options); } }; - -export const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({ - accumulatedChange, - originalElements, - originalElementsMap, - shouldChangeByStepSize, - nextValue, - property, - scene, - originalAppState, -}) => { - const STEP_SIZE = 10; - const elementsMap = scene.getNonDeletedElementsMap(); - - if (nextValue !== undefined) { - for (const atomicUnit of getAtomicUnits( - originalElements, - originalAppState, - )) { - const elementsInUnit = getElementsInAtomicUnit( - atomicUnit, - elementsMap, - originalElementsMap, - ); - - if (elementsInUnit.length > 1) { - const [x1, y1, ,] = getCommonBounds( - elementsInUnit.map((el) => el.latest!), - ); - const newTopLeftX = property === "x" ? nextValue : x1; - const newTopLeftY = property === "y" ? nextValue : y1; - - moveGroup( - newTopLeftX, - newTopLeftY, - elementsInUnit.map((el) => el.original), - originalElementsMap, - scene, - ); - } else { - const origElement = elementsInUnit[0]?.original; - const latestElement = elementsInUnit[0]?.latest; - if ( - origElement && - latestElement && - isPropertyEditable(latestElement, property) - ) { - const [cx, cy] = [ - origElement.x + origElement.width / 2, - origElement.y + origElement.height / 2, - ]; - const [topLeftX, topLeftY] = pointRotateRads( - pointFrom(origElement.x, origElement.y), - pointFrom(cx, cy), - origElement.angle, - ); - - const newTopLeftX = property === "x" ? nextValue : topLeftX; - const newTopLeftY = property === "y" ? nextValue : topLeftY; - - moveElement( - newTopLeftX, - newTopLeftY, - origElement, - scene, - originalElementsMap, - false, - ); - } - } - } - - scene.triggerUpdate(); - return; - } - - const change = shouldChangeByStepSize - ? getStepSizedValue(accumulatedChange, STEP_SIZE) - : accumulatedChange; - - const changeInTopX = property === "x" ? change : 0; - const changeInTopY = property === "y" ? change : 0; - - moveElements( - property, - changeInTopX, - changeInTopY, - originalElements, - originalElementsMap, - scene, - ); - - scene.triggerUpdate(); -};