mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Separate mutation of elbow arrows from mutateElement
This commit is contained in:
parent
6fc85022ae
commit
703a8f0e78
11 changed files with 293 additions and 126 deletions
|
@ -66,7 +66,7 @@ import {
|
|||
} from "./typeChecks";
|
||||
|
||||
import { aabbForElement, getElementShape, pointInsideBounds } from "./shapes";
|
||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
||||
import { mutateElbowArrow, updateElbowArrowPoints } from "./elbowArrow";
|
||||
|
||||
import type { Bounds } from "./bounds";
|
||||
import type { ElementUpdate } from "./mutateElement";
|
||||
|
@ -796,7 +796,20 @@ export const updateBoundElements = (
|
|||
|
||||
// `linearElement` is being moved/scaled already, just update the binding
|
||||
if (simultaneouslyUpdatedElementIds.has(element.id)) {
|
||||
mutateElement(element, bindings, true);
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
bindings as {
|
||||
startBinding: FixedPointBinding;
|
||||
endBinding: FixedPointBinding;
|
||||
},
|
||||
true,
|
||||
elementsMap,
|
||||
);
|
||||
} else {
|
||||
mutateElement(element, bindings, true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ import {
|
|||
isDevEnv,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import type { Radians } from "@excalidraw/math";
|
||||
|
||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import {
|
||||
|
@ -45,8 +47,8 @@ import {
|
|||
vectorToHeading,
|
||||
headingForPoint,
|
||||
} from "./heading";
|
||||
import { type ElementUpdate } from "./mutateElement";
|
||||
import { isBindableElement } from "./typeChecks";
|
||||
import { mutateElement, type ElementUpdate } from "./mutateElement";
|
||||
import { isBindableElement, isElbowArrow } from "./typeChecks";
|
||||
import {
|
||||
type ExcalidrawElbowArrowElement,
|
||||
type NonDeletedSceneElementsMap,
|
||||
|
@ -61,6 +63,7 @@ import type {
|
|||
Arrowhead,
|
||||
ElementsMap,
|
||||
ExcalidrawBindableElement,
|
||||
ExcalidrawElement,
|
||||
FixedPointBinding,
|
||||
FixedSegment,
|
||||
NonDeletedExcalidrawElement,
|
||||
|
@ -879,6 +882,64 @@ const handleEndpointDrag = (
|
|||
|
||||
const MAX_POS = 1e6;
|
||||
|
||||
export const elbowArrowNeedsToGetNormalized = (
|
||||
element: Readonly<ExcalidrawElement>,
|
||||
updates: {
|
||||
points?: readonly LocalPoint[];
|
||||
fixedSegments?: readonly FixedSegment[] | null;
|
||||
startBinding?: FixedPointBinding | null;
|
||||
endBinding?: FixedPointBinding | null;
|
||||
},
|
||||
) => {
|
||||
const { points, fixedSegments, startBinding, endBinding } = updates;
|
||||
|
||||
return (
|
||||
isElbowArrow(element) &&
|
||||
(Object.keys(updates).length === 0 || // normalization case
|
||||
typeof points !== "undefined" || // repositioning
|
||||
typeof fixedSegments !== "undefined" || // segment fixing
|
||||
typeof startBinding !== "undefined" ||
|
||||
typeof endBinding !== "undefined") // manual binding to element
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutates an elbow arrow element and renormalizes it's properties if necessary.
|
||||
*/
|
||||
export const mutateElbowArrow = (
|
||||
element: Readonly<ExcalidrawElbowArrowElement>,
|
||||
updates: ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||
informMutation: boolean = true,
|
||||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap | ElementsMap,
|
||||
options?: {
|
||||
isDragging?: boolean;
|
||||
},
|
||||
): ElementUpdate<ExcalidrawElbowArrowElement> => {
|
||||
invariant(
|
||||
!isElbowArrow(element),
|
||||
`Element "${element.type}" is not an elbow arrow! Use \`mutateElement\` instead`,
|
||||
);
|
||||
|
||||
if (!elbowArrowNeedsToGetNormalized(element, updates)) {
|
||||
return mutateElement(element, updates, informMutation);
|
||||
}
|
||||
|
||||
return mutateElement(
|
||||
element,
|
||||
{
|
||||
...updates,
|
||||
angle: 0 as Radians,
|
||||
...updateElbowArrowPoints(
|
||||
element,
|
||||
elementsMap as NonDeletedSceneElementsMap,
|
||||
updates,
|
||||
options,
|
||||
),
|
||||
},
|
||||
informMutation,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -887,7 +948,7 @@ export const updateElbowArrowPoints = (
|
|||
elementsMap: NonDeletedSceneElementsMap,
|
||||
updates: {
|
||||
points?: readonly LocalPoint[];
|
||||
fixedSegments?: FixedSegment[] | null;
|
||||
fixedSegments?: readonly FixedSegment[] | null;
|
||||
startBinding?: FixedPointBinding | null;
|
||||
endBinding?: FixedPointBinding | null;
|
||||
},
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
|
||||
// TODO: remove direct dependency on the scene, should be passed in or injected instead
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||
import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||
|
||||
import type { Store } from "@excalidraw/excalidraw/store";
|
||||
|
||||
|
@ -50,7 +50,7 @@ import {
|
|||
getMinMaxXYFromCurvePathOps,
|
||||
} from "./bounds";
|
||||
|
||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
||||
import { mutateElbowArrow, updateElbowArrowPoints } from "./elbowArrow";
|
||||
|
||||
import { headingIsHorizontal, vectorToHeading } from "./heading";
|
||||
import { bumpVersion, mutateElement } from "./mutateElement";
|
||||
|
@ -794,7 +794,10 @@ export class LinearElementEditor {
|
|||
elementsMap,
|
||||
);
|
||||
} else if (event.altKey && appState.editingLinearElement) {
|
||||
if (linearElementEditor.lastUncommittedPoint == null) {
|
||||
if (
|
||||
linearElementEditor.lastUncommittedPoint == null &&
|
||||
!isElbowArrow(element)
|
||||
) {
|
||||
mutateElement(element, {
|
||||
points: [
|
||||
...element.points,
|
||||
|
@ -1219,7 +1222,12 @@ export class LinearElementEditor {
|
|||
return acc;
|
||||
}, []);
|
||||
|
||||
mutateElement(element, { points: nextPoints });
|
||||
const updates = { points: nextPoints };
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(element, updates, true, elementsMap);
|
||||
} else {
|
||||
mutateElement(element, updates);
|
||||
}
|
||||
|
||||
// temp hack to ensure the line doesn't move when adding point to the end,
|
||||
// potentially expanding the bounding box
|
||||
|
@ -1425,9 +1433,12 @@ export class LinearElementEditor {
|
|||
...element.points.slice(segmentMidpoint.index!),
|
||||
];
|
||||
|
||||
mutateElement(element, {
|
||||
points,
|
||||
});
|
||||
const updates = { points };
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(element, updates, true, elementsMap);
|
||||
} else {
|
||||
mutateElement(element, updates);
|
||||
}
|
||||
|
||||
ret.pointerDownState = {
|
||||
...linearElementEditor.pointerDownState,
|
||||
|
@ -1479,8 +1490,8 @@ export class LinearElementEditor {
|
|||
|
||||
updates.points = Array.from(nextPoints);
|
||||
|
||||
if (!options?.sceneElementsMap || Scene.getScene(element)) {
|
||||
mutateElement(element, updates, true, {
|
||||
if (!options?.sceneElementsMap) {
|
||||
mutateElbowArrow(element, updates, true, options?.sceneElementsMap!, {
|
||||
isDragging: options?.isDragging,
|
||||
});
|
||||
} else {
|
||||
|
@ -1825,9 +1836,14 @@ export class LinearElementEditor {
|
|||
.map((segment) => segment.index)
|
||||
.reduce((count, idx) => (idx < index ? count + 1 : count), 0);
|
||||
|
||||
mutateElement(element, {
|
||||
fixedSegments: nextFixedSegments,
|
||||
});
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
{
|
||||
fixedSegments: nextFixedSegments,
|
||||
},
|
||||
true,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
const point = pointFrom<GlobalPoint>(
|
||||
element.x +
|
||||
|
@ -1859,14 +1875,19 @@ export class LinearElementEditor {
|
|||
|
||||
static deleteFixedSegment(
|
||||
element: ExcalidrawElbowArrowElement,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
index: number,
|
||||
): void {
|
||||
mutateElement(element, {
|
||||
fixedSegments: element.fixedSegments?.filter(
|
||||
(segment) => segment.index !== index,
|
||||
),
|
||||
});
|
||||
mutateElement(element, {}, true);
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
{
|
||||
fixedSegments: element.fixedSegments?.filter(
|
||||
(segment) => segment.index !== index,
|
||||
),
|
||||
},
|
||||
true,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,23 +2,20 @@ import {
|
|||
getSizeFromPoints,
|
||||
randomInteger,
|
||||
getUpdatedTimestamp,
|
||||
toBrandedType,
|
||||
invariant,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
// TODO: remove direct dependency on the scene, should be passed in or injected instead
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||
|
||||
import type { Radians } from "@excalidraw/math";
|
||||
|
||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { ShapeCache } from "./ShapeCache";
|
||||
|
||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
||||
import { isElbowArrow } from "./typeChecks";
|
||||
import { elbowArrowNeedsToGetNormalized } from "./elbowArrow";
|
||||
|
||||
import type { ExcalidrawElement, NonDeletedSceneElementsMap } from "./types";
|
||||
import type { ExcalidrawElement } from "./types";
|
||||
|
||||
export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
|
||||
Partial<TElement>,
|
||||
|
@ -33,54 +30,25 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||
element: TElement,
|
||||
updates: ElementUpdate<TElement>,
|
||||
informMutation = true,
|
||||
options?: {
|
||||
// Currently only for elbow arrows.
|
||||
// If true, the elbow arrow tries to bind to the nearest element. If false
|
||||
// it tries to keep the same bound element, if any.
|
||||
isDragging?: boolean;
|
||||
},
|
||||
): TElement => {
|
||||
let didChange = false;
|
||||
|
||||
// casting to any because can't use `in` operator
|
||||
// (see https://github.com/microsoft/TypeScript/issues/21732)
|
||||
const { points, fixedSegments, fileId, startBinding, endBinding } =
|
||||
const { points, fileId, fixedSegments, startBinding, endBinding } =
|
||||
updates as any;
|
||||
|
||||
if (
|
||||
isElbowArrow(element) &&
|
||||
(Object.keys(updates).length === 0 || // normalization case
|
||||
typeof points !== "undefined" || // repositioning
|
||||
typeof fixedSegments !== "undefined" || // segment fixing
|
||||
typeof startBinding !== "undefined" ||
|
||||
typeof endBinding !== "undefined") // manual binding to element
|
||||
) {
|
||||
const elementsMap = toBrandedType<NonDeletedSceneElementsMap>(
|
||||
Scene.getScene(element)?.getNonDeletedElementsMap() ?? new Map(),
|
||||
);
|
||||
invariant(
|
||||
elbowArrowNeedsToGetNormalized(element, {
|
||||
points,
|
||||
fixedSegments,
|
||||
startBinding,
|
||||
endBinding,
|
||||
}),
|
||||
"Elbow arrow should get normalized! Use `mutateElbowArrow` instead.",
|
||||
);
|
||||
|
||||
updates = {
|
||||
...updates,
|
||||
angle: 0 as Radians,
|
||||
...updateElbowArrowPoints(
|
||||
{
|
||||
...element,
|
||||
x: updates.x || element.x,
|
||||
y: updates.y || element.y,
|
||||
},
|
||||
elementsMap,
|
||||
{
|
||||
fixedSegments,
|
||||
points,
|
||||
startBinding,
|
||||
endBinding,
|
||||
},
|
||||
{
|
||||
isDragging: options?.isDragging,
|
||||
},
|
||||
),
|
||||
};
|
||||
} else if (typeof points !== "undefined") {
|
||||
if (typeof points !== "undefined") {
|
||||
updates = { ...getSizeFromPoints(points), ...updates };
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ import {
|
|||
|
||||
import { isInGroup } from "./groups";
|
||||
|
||||
import { mutateElbowArrow } from "./elbowArrow";
|
||||
|
||||
import type { BoundingBox } from "./bounds";
|
||||
import type {
|
||||
MaybeTransformHandleType,
|
||||
|
@ -545,9 +547,14 @@ const rotateMultipleElements = (
|
|||
|
||||
if (isElbowArrow(element)) {
|
||||
// Needed to re-route the arrow
|
||||
mutateElement(element, {
|
||||
points: getArrowLocalFixedPoints(element, elementsMap),
|
||||
});
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
{
|
||||
points: getArrowLocalFixedPoints(element, elementsMap),
|
||||
},
|
||||
false,
|
||||
elementsMap,
|
||||
);
|
||||
} else {
|
||||
mutateElement(
|
||||
element,
|
||||
|
@ -1527,10 +1534,14 @@ export const resizeMultipleElements = (
|
|||
} of elementsAndUpdates) {
|
||||
const { width, height, angle } = update;
|
||||
|
||||
mutateElement(element, update, false, {
|
||||
// needed for the fixed binding point udpate to take effect
|
||||
isDragging: true,
|
||||
});
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(element, update, false, elementsMap, {
|
||||
// needed for the fixed binding point udpate to take effect
|
||||
isDragging: true,
|
||||
});
|
||||
} else {
|
||||
mutateElement(element, update, false);
|
||||
}
|
||||
|
||||
updateBoundElements(element, elementsMap as SceneElementsMap, {
|
||||
simultaneouslyUpdated: elementsToUpdate,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue