mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
wip: Fix frame children coords when dragging position stats coords
This commit is contained in:
parent
a18b139a60
commit
7343285694
11 changed files with 246 additions and 368 deletions
|
@ -89,6 +89,12 @@ export const isFrameLikeElement = (
|
|||
);
|
||||
};
|
||||
|
||||
export const isFrameChildElement = (
|
||||
element: ExcalidrawElement | null,
|
||||
): element is ExcalidrawElement & { frameId: string } => {
|
||||
return element !== null && element.frameId !== null;
|
||||
};
|
||||
|
||||
export const isFreeDrawElement = (
|
||||
element?: ExcalidrawElement | null,
|
||||
): element is ExcalidrawFreeDrawElement => {
|
||||
|
|
|
@ -12,9 +12,8 @@ import type Scene from "@excalidraw/element/Scene";
|
|||
import { angleIcon } from "../icons";
|
||||
|
||||
import DragInput from "./DragInput";
|
||||
import { getStepSizedValue, isPropertyEditable, updateBindings } from "./utils";
|
||||
import { DragInputCallbackType, getStepSizedValue, isPropertyEditable, updateBindings } from "./utils";
|
||||
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import type { AppState } from "../../types";
|
||||
|
||||
interface AngleProps {
|
||||
|
|
|
@ -13,9 +13,8 @@ import type { ExcalidrawElement } from "@excalidraw/element/types";
|
|||
import type Scene from "@excalidraw/element/Scene";
|
||||
|
||||
import DragInput from "./DragInput";
|
||||
import { getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
import { DragInputCallbackType, getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import type { AppState } from "../../types";
|
||||
|
||||
interface DimensionDragInputProps {
|
||||
|
|
|
@ -13,30 +13,13 @@ import { CaptureUpdateAction } from "../../store";
|
|||
import { useApp } from "../App";
|
||||
import { InlineIcon } from "../InlineIcon";
|
||||
|
||||
import { SMALLEST_DELTA } from "./utils";
|
||||
import { DragInputCallbackType, SMALLEST_DELTA } from "./utils";
|
||||
|
||||
import "./DragInput.scss";
|
||||
|
||||
import type { StatsInputProperty } from "./utils";
|
||||
import type { AppState } from "../../types";
|
||||
|
||||
export type DragInputCallbackType<
|
||||
P extends StatsInputProperty,
|
||||
E = ExcalidrawElement,
|
||||
> = (props: {
|
||||
accumulatedChange: number;
|
||||
instantChange: number;
|
||||
originalElements: readonly E[];
|
||||
originalElementsMap: ElementsMap;
|
||||
shouldKeepAspectRatio: boolean;
|
||||
shouldChangeByStepSize: boolean;
|
||||
scene: Scene;
|
||||
nextValue?: number;
|
||||
property: P;
|
||||
originalAppState: AppState;
|
||||
setInputValue: (value: number) => void;
|
||||
}) => void;
|
||||
|
||||
interface StatsDragInputProps<
|
||||
T extends StatsInputProperty,
|
||||
E = ExcalidrawElement,
|
||||
|
|
|
@ -17,9 +17,8 @@ import type Scene from "@excalidraw/element/Scene";
|
|||
import { fontSizeIcon } from "../icons";
|
||||
|
||||
import StatsDragInput from "./DragInput";
|
||||
import { getStepSizedValue } from "./utils";
|
||||
import { DragInputCallbackType, getStepSizedValue } from "./utils";
|
||||
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import type { AppState } from "../../types";
|
||||
|
||||
interface FontSizeProps {
|
||||
|
|
|
@ -14,9 +14,8 @@ import type Scene from "@excalidraw/element/Scene";
|
|||
import { angleIcon } from "../icons";
|
||||
|
||||
import DragInput from "./DragInput";
|
||||
import { getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
import { DragInputCallbackType, getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import type { AppState } from "../../types";
|
||||
|
||||
interface MultiAngleProps {
|
||||
|
|
|
@ -26,9 +26,8 @@ import type Scene from "@excalidraw/element/Scene";
|
|||
|
||||
import DragInput from "./DragInput";
|
||||
import { getAtomicUnits, getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
import { getElementsInAtomicUnit } from "./utils";
|
||||
import { DragInputCallbackType, getElementsInAtomicUnit } from "./utils";
|
||||
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import type { AtomicUnit } from "./utils";
|
||||
import type { AppState } from "../../types";
|
||||
|
||||
|
|
|
@ -20,9 +20,8 @@ import type Scene from "@excalidraw/element/Scene";
|
|||
import { fontSizeIcon } from "../icons";
|
||||
|
||||
import StatsDragInput from "./DragInput";
|
||||
import { getStepSizedValue } from "./utils";
|
||||
import { DragInputCallbackType, getStepSizedValue } from "./utils";
|
||||
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import type { AppState } from "../../types";
|
||||
|
||||
interface MultiFontSizeProps {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { pointFrom, pointRotateRads } from "@excalidraw/math";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { isTextElement } from "@excalidraw/element/typeChecks";
|
||||
|
||||
import { getCommonBounds } from "@excalidraw/element/bounds";
|
||||
|
||||
import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
@ -10,12 +8,12 @@ import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
|
|||
import type Scene from "@excalidraw/element/Scene";
|
||||
|
||||
import StatsDragInput from "./DragInput";
|
||||
import { getAtomicUnits, getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
import { getElementsInAtomicUnit, moveElement } from "./utils";
|
||||
import { handlePositionChange } from "./utils";
|
||||
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import type { AtomicUnit } from "./utils";
|
||||
import type { AppState } from "../../types";
|
||||
import { getFrameChildren } from "@excalidraw/element/frame";
|
||||
import { isFrameLikeElement } from "@excalidraw/element/typeChecks";
|
||||
|
||||
interface MultiPositionProps {
|
||||
property: "x" | "y";
|
||||
|
@ -26,185 +24,6 @@ interface MultiPositionProps {
|
|||
appState: AppState;
|
||||
}
|
||||
|
||||
const STEP_SIZE = 10;
|
||||
|
||||
const moveElements = (
|
||||
property: MultiPositionProps["property"],
|
||||
changeInTopX: number,
|
||||
changeInTopY: number,
|
||||
originalElements: readonly ExcalidrawElement[],
|
||||
originalElementsMap: ElementsMap,
|
||||
scene: Scene,
|
||||
) => {
|
||||
for (let i = 0; i < originalElements.length; i++) {
|
||||
const origElement = originalElements[i];
|
||||
|
||||
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" ? Math.round(topLeftX + changeInTopX) : topLeftX;
|
||||
|
||||
const newTopLeftY =
|
||||
property === "y" ? Math.round(topLeftY + changeInTopY) : topLeftY;
|
||||
|
||||
moveElement(
|
||||
newTopLeftX,
|
||||
newTopLeftY,
|
||||
origElement,
|
||||
scene,
|
||||
originalElementsMap,
|
||||
false,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const moveGroupTo = (
|
||||
nextX: number,
|
||||
nextY: number,
|
||||
originalElements: ExcalidrawElement[],
|
||||
originalElementsMap: ElementsMap,
|
||||
scene: Scene,
|
||||
) => {
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
const [x1, y1, ,] = getCommonBounds(originalElements);
|
||||
const offsetX = nextX - x1;
|
||||
const offsetY = nextY - y1;
|
||||
|
||||
for (let i = 0; i < originalElements.length; i++) {
|
||||
const origElement = originalElements[i];
|
||||
|
||||
const latestElement = elementsMap.get(origElement.id);
|
||||
if (!latestElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// bound texts are moved with their containers
|
||||
if (!isTextElement(latestElement) || !latestElement.containerId) {
|
||||
const [cx, cy] = [
|
||||
latestElement.x + latestElement.width / 2,
|
||||
latestElement.y + latestElement.height / 2,
|
||||
];
|
||||
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
pointFrom(latestElement.x, latestElement.y),
|
||||
pointFrom(cx, cy),
|
||||
latestElement.angle,
|
||||
);
|
||||
|
||||
moveElement(
|
||||
topLeftX + offsetX,
|
||||
topLeftY + offsetY,
|
||||
origElement,
|
||||
scene,
|
||||
originalElementsMap,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
|
@ -239,13 +58,17 @@ const MultiPosition = ({
|
|||
}),
|
||||
[atomicUnits, elementsMap, property],
|
||||
);
|
||||
const elementsWithFramesChildren = elements.reduce((accumulator: ExcalidrawElement[], element: ExcalidrawElement) => {
|
||||
if (!isFrameLikeElement(element)) return [...accumulator, element];
|
||||
return [...accumulator, element, ...getFrameChildren(elementsMap, element.id)];
|
||||
}, []);
|
||||
|
||||
const value = new Set(positions).size === 1 ? positions[0] : "Mixed";
|
||||
|
||||
return (
|
||||
<StatsDragInput
|
||||
label={property === "x" ? "X" : "Y"}
|
||||
elements={elements}
|
||||
elements={elementsWithFramesChildren}
|
||||
dragInputCallback={handlePositionChange}
|
||||
value={value}
|
||||
property={property}
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
import { clamp, pointFrom, pointRotateRads, round } from "@excalidraw/math";
|
||||
import { pointFrom, pointRotateRads, round } from "@excalidraw/math";
|
||||
|
||||
import {
|
||||
getFlipAdjustedCropPosition,
|
||||
getUncroppedWidthAndHeight,
|
||||
} from "@excalidraw/element/cropElement";
|
||||
import { isImageElement } from "@excalidraw/element/typeChecks";
|
||||
import { isFrameLikeElement, isImageElement } from "@excalidraw/element/typeChecks";
|
||||
|
||||
import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import type Scene from "@excalidraw/element/Scene";
|
||||
|
||||
import StatsDragInput from "./DragInput";
|
||||
import { getStepSizedValue, moveElement } from "./utils";
|
||||
import { handlePositionChange } from "./utils";
|
||||
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import type { AppState } from "../../types";
|
||||
import { getFrameChildren } from "@excalidraw/element/frame";
|
||||
|
||||
interface PositionProps {
|
||||
property: "x" | "y";
|
||||
|
@ -24,150 +23,6 @@ interface PositionProps {
|
|||
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 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,
|
||||
|
@ -180,6 +35,8 @@ const Position = ({
|
|||
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
|
||||
element.angle,
|
||||
);
|
||||
const children = isFrameLikeElement(element) ? getFrameChildren(elementsMap, element.id) : [];
|
||||
const elements = children.length > 0 ? [element, ...children] : [element];
|
||||
let value = round(property === "x" ? topLeftX : topLeftY, 2);
|
||||
|
||||
if (
|
||||
|
@ -200,7 +57,7 @@ const Position = ({
|
|||
return (
|
||||
<StatsDragInput
|
||||
label={property === "x" ? "X" : "Y"}
|
||||
elements={[element]}
|
||||
elements={elements}
|
||||
dragInputCallback={handlePositionChange}
|
||||
scene={scene}
|
||||
value={value}
|
||||
|
|
|
@ -5,7 +5,9 @@ import {
|
|||
updateBoundElements,
|
||||
} from "@excalidraw/element/binding";
|
||||
import { getBoundTextElement } from "@excalidraw/element/textElement";
|
||||
import { getCommonBounds } from "@excalidraw/element/bounds";
|
||||
import {
|
||||
isFrameChildElement,
|
||||
isFrameLikeElement,
|
||||
isLinearElement,
|
||||
isTextElement,
|
||||
|
@ -38,6 +40,23 @@ export type StatsInputProperty =
|
|||
| "fontSize"
|
||||
| "gridStep";
|
||||
|
||||
export type DragInputCallbackType<
|
||||
P extends StatsInputProperty,
|
||||
E = ExcalidrawElement,
|
||||
> = (props: {
|
||||
accumulatedChange: number;
|
||||
instantChange: number;
|
||||
originalElements: readonly E[];
|
||||
originalElementsMap: ElementsMap;
|
||||
shouldKeepAspectRatio: boolean;
|
||||
shouldChangeByStepSize: boolean;
|
||||
scene: Scene;
|
||||
nextValue?: number;
|
||||
property: P;
|
||||
originalAppState: AppState;
|
||||
setInputValue: (value: number) => void;
|
||||
}) => void;
|
||||
|
||||
export const SMALLEST_DELTA = 0.01;
|
||||
|
||||
export const isPropertyEditable = (
|
||||
|
@ -73,9 +92,9 @@ export const getElementsInAtomicUnit = (
|
|||
latest: elementsMap.get(id),
|
||||
}))
|
||||
.filter((el) => el.original !== undefined && el.latest !== undefined) as {
|
||||
original: NonDeletedExcalidrawElement;
|
||||
latest: NonDeletedExcalidrawElement;
|
||||
}[];
|
||||
original: NonDeletedExcalidrawElement;
|
||||
latest: NonDeletedExcalidrawElement;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const newOrigin = (
|
||||
|
@ -174,6 +193,89 @@ export const moveElement = (
|
|||
}
|
||||
};
|
||||
|
||||
export const moveElements = (
|
||||
property: "x" | "y",
|
||||
changeInTopX: number,
|
||||
changeInTopY: number,
|
||||
originalElements: readonly ExcalidrawElement[],
|
||||
originalElementsMap: ElementsMap,
|
||||
scene: Scene,
|
||||
) => {
|
||||
for (let i = 0; i < originalElements.length; i++) {
|
||||
const origElement = originalElements[i];
|
||||
|
||||
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" ? Math.round(topLeftX + changeInTopX) : topLeftX;
|
||||
|
||||
const newTopLeftY =
|
||||
property === "y" ? Math.round(topLeftY + changeInTopY) : topLeftY;
|
||||
|
||||
moveElement(
|
||||
newTopLeftX,
|
||||
newTopLeftY,
|
||||
origElement,
|
||||
scene,
|
||||
originalElementsMap,
|
||||
false,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const moveGroup = (
|
||||
nextX: number,
|
||||
nextY: number,
|
||||
originalElements: ExcalidrawElement[],
|
||||
originalElementsMap: ElementsMap,
|
||||
scene: Scene,
|
||||
) => {
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
const [x1, y1, ,] = getCommonBounds(originalElements);
|
||||
const offsetX = nextX - x1;
|
||||
const offsetY = nextY - y1;
|
||||
|
||||
for (let i = 0; i < originalElements.length; i++) {
|
||||
const origElement = originalElements[i];
|
||||
|
||||
const latestElement = elementsMap.get(origElement.id);
|
||||
if (!latestElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// bound texts are moved with their containers
|
||||
if (!isTextElement(latestElement) || !latestElement.containerId) {
|
||||
const [cx, cy] = [
|
||||
latestElement.x + latestElement.width / 2,
|
||||
latestElement.y + latestElement.height / 2,
|
||||
];
|
||||
|
||||
const [topLeftX, topLeftY] = pointRotateRads(
|
||||
pointFrom(latestElement.x, latestElement.y),
|
||||
pointFrom(cx, cy),
|
||||
latestElement.angle,
|
||||
);
|
||||
|
||||
moveElement(
|
||||
topLeftX + offsetX,
|
||||
topLeftY + offsetY,
|
||||
origElement,
|
||||
scene,
|
||||
originalElementsMap,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getAtomicUnits = (
|
||||
targetElements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
|
@ -210,3 +312,116 @@ 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,
|
||||
);
|
||||
|
||||
if (isFrameChildElement(origElement)) {
|
||||
const childNewTopLeftX = property === "x" ? nextValue + Math.abs(topLeftX) : topLeftX;
|
||||
const childNewTopLeftY = property === "y" ? nextValue + Math.abs(topLeftY) : topLeftY;
|
||||
|
||||
moveElement(
|
||||
childNewTopLeftX,
|
||||
childNewTopLeftY,
|
||||
origElement,
|
||||
scene,
|
||||
originalElementsMap,
|
||||
false,
|
||||
);
|
||||
|
||||
scene.triggerUpdate();
|
||||
return;
|
||||
} else {
|
||||
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();
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue