diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | 6ec259a0e71174651bae95d4628138bf6fd68742 (patch) | |
| tree | 5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/components/Stats/Position.tsx | |
| parent | 16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff) | |
refactor: packages/
Diffstat (limited to 'packages/excalidraw/components/Stats/Position.tsx')
| -rw-r--r-- | packages/excalidraw/components/Stats/Position.tsx | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/packages/excalidraw/components/Stats/Position.tsx b/packages/excalidraw/components/Stats/Position.tsx new file mode 100644 index 0000000..038e58e --- /dev/null +++ b/packages/excalidraw/components/Stats/Position.tsx @@ -0,0 +1,214 @@ +import type { ElementsMap, ExcalidrawElement } from "../../element/types"; +import StatsDragInput from "./DragInput"; +import type { DragInputCallbackType } from "./DragInput"; +import { getStepSizedValue, moveElement } from "./utils"; +import type Scene from "../../scene/Scene"; +import type { AppState } from "../../types"; +import { clamp, pointFrom, pointRotateRads, round } from "@excalidraw/math"; +import { isImageElement } from "../../element/typeChecks"; +import { + getFlipAdjustedCropPosition, + getUncroppedWidthAndHeight, +} from "../../element/cropElement"; +import { mutateElement } from "../../element/mutateElement"; + +interface PositionProps { + property: "x" | "y"; + element: ExcalidrawElement; + elementsMap: ElementsMap; + scene: Scene; + appState: AppState; +} + +const STEP_SIZE = 10; + +const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({ + accumulatedChange, + instantChange, + originalElements, + originalElementsMap, + shouldChangeByStepSize, + nextValue, + property, + scene, + originalAppState, +}) => { + const elementsMap = scene.getNonDeletedElementsMap(); + const elements = scene.getNonDeletedElements(); + 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, + ), + }; + } + + 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), + }; + + mutateElement(element, { + crop: nextCrop, + }); + + return; + } + + if (nextValue !== undefined) { + const newTopLeftX = property === "x" ? nextValue : topLeftX; + const newTopLeftY = property === "y" ? nextValue : topLeftY; + moveElement( + newTopLeftX, + newTopLeftY, + origElement, + elementsMap, + elements, + 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, + elementsMap, + elements, + scene, + originalElementsMap, + ); +}; + +const Position = ({ + property, + element, + elementsMap, + scene, + appState, +}: PositionProps) => { + const [topLeftX, topLeftY] = pointRotateRads( + pointFrom(element.x, element.y), + pointFrom(element.x + element.width / 2, element.y + element.height / 2), + element.angle, + ); + let value = round(property === "x" ? topLeftX : topLeftY, 2); + + if ( + appState.croppingElementId === element.id && + isImageElement(element) && + element.crop + ) { + const flipAdjustedPosition = getFlipAdjustedCropPosition(element); + + if (flipAdjustedPosition) { + value = round( + property === "x" ? flipAdjustedPosition.x : flipAdjustedPosition.y, + 2, + ); + } + } + + return ( + <StatsDragInput + label={property === "x" ? "X" : "Y"} + elements={[element]} + dragInputCallback={handlePositionChange} + scene={scene} + value={value} + property={property} + appState={appState} + /> + ); +}; + +export default Position; |
