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";
|
} from "./typeChecks";
|
||||||
|
|
||||||
import { aabbForElement, getElementShape, pointInsideBounds } from "./shapes";
|
import { aabbForElement, getElementShape, pointInsideBounds } from "./shapes";
|
||||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
import { mutateElbowArrow, updateElbowArrowPoints } from "./elbowArrow";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
import type { Bounds } from "./bounds";
|
||||||
import type { ElementUpdate } from "./mutateElement";
|
import type { ElementUpdate } from "./mutateElement";
|
||||||
|
@ -796,7 +796,20 @@ export const updateBoundElements = (
|
||||||
|
|
||||||
// `linearElement` is being moved/scaled already, just update the binding
|
// `linearElement` is being moved/scaled already, just update the binding
|
||||||
if (simultaneouslyUpdatedElementIds.has(element.id)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ import {
|
||||||
isDevEnv,
|
isDevEnv,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
|
import type { Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -45,8 +47,8 @@ import {
|
||||||
vectorToHeading,
|
vectorToHeading,
|
||||||
headingForPoint,
|
headingForPoint,
|
||||||
} from "./heading";
|
} from "./heading";
|
||||||
import { type ElementUpdate } from "./mutateElement";
|
import { mutateElement, type ElementUpdate } from "./mutateElement";
|
||||||
import { isBindableElement } from "./typeChecks";
|
import { isBindableElement, isElbowArrow } from "./typeChecks";
|
||||||
import {
|
import {
|
||||||
type ExcalidrawElbowArrowElement,
|
type ExcalidrawElbowArrowElement,
|
||||||
type NonDeletedSceneElementsMap,
|
type NonDeletedSceneElementsMap,
|
||||||
|
@ -61,6 +63,7 @@ import type {
|
||||||
Arrowhead,
|
Arrowhead,
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
|
ExcalidrawElement,
|
||||||
FixedPointBinding,
|
FixedPointBinding,
|
||||||
FixedSegment,
|
FixedSegment,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
|
@ -879,6 +882,64 @@ const handleEndpointDrag = (
|
||||||
|
|
||||||
const MAX_POS = 1e6;
|
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,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
updates: {
|
updates: {
|
||||||
points?: readonly LocalPoint[];
|
points?: readonly LocalPoint[];
|
||||||
fixedSegments?: FixedSegment[] | null;
|
fixedSegments?: readonly FixedSegment[] | null;
|
||||||
startBinding?: FixedPointBinding | null;
|
startBinding?: FixedPointBinding | null;
|
||||||
endBinding?: FixedPointBinding | null;
|
endBinding?: FixedPointBinding | null;
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
|
|
||||||
// TODO: remove direct dependency on the scene, should be passed in or injected instead
|
// TODO: remove direct dependency on the scene, should be passed in or injected instead
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// 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";
|
import type { Store } from "@excalidraw/excalidraw/store";
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ import {
|
||||||
getMinMaxXYFromCurvePathOps,
|
getMinMaxXYFromCurvePathOps,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
|
|
||||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
import { mutateElbowArrow, updateElbowArrowPoints } from "./elbowArrow";
|
||||||
|
|
||||||
import { headingIsHorizontal, vectorToHeading } from "./heading";
|
import { headingIsHorizontal, vectorToHeading } from "./heading";
|
||||||
import { bumpVersion, mutateElement } from "./mutateElement";
|
import { bumpVersion, mutateElement } from "./mutateElement";
|
||||||
|
@ -794,7 +794,10 @@ export class LinearElementEditor {
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
} else if (event.altKey && appState.editingLinearElement) {
|
} else if (event.altKey && appState.editingLinearElement) {
|
||||||
if (linearElementEditor.lastUncommittedPoint == null) {
|
if (
|
||||||
|
linearElementEditor.lastUncommittedPoint == null &&
|
||||||
|
!isElbowArrow(element)
|
||||||
|
) {
|
||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
points: [
|
points: [
|
||||||
...element.points,
|
...element.points,
|
||||||
|
@ -1219,7 +1222,12 @@ export class LinearElementEditor {
|
||||||
return acc;
|
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,
|
// temp hack to ensure the line doesn't move when adding point to the end,
|
||||||
// potentially expanding the bounding box
|
// potentially expanding the bounding box
|
||||||
|
@ -1425,9 +1433,12 @@ export class LinearElementEditor {
|
||||||
...element.points.slice(segmentMidpoint.index!),
|
...element.points.slice(segmentMidpoint.index!),
|
||||||
];
|
];
|
||||||
|
|
||||||
mutateElement(element, {
|
const updates = { points };
|
||||||
points,
|
if (isElbowArrow(element)) {
|
||||||
});
|
mutateElbowArrow(element, updates, true, elementsMap);
|
||||||
|
} else {
|
||||||
|
mutateElement(element, updates);
|
||||||
|
}
|
||||||
|
|
||||||
ret.pointerDownState = {
|
ret.pointerDownState = {
|
||||||
...linearElementEditor.pointerDownState,
|
...linearElementEditor.pointerDownState,
|
||||||
|
@ -1479,8 +1490,8 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
updates.points = Array.from(nextPoints);
|
updates.points = Array.from(nextPoints);
|
||||||
|
|
||||||
if (!options?.sceneElementsMap || Scene.getScene(element)) {
|
if (!options?.sceneElementsMap) {
|
||||||
mutateElement(element, updates, true, {
|
mutateElbowArrow(element, updates, true, options?.sceneElementsMap!, {
|
||||||
isDragging: options?.isDragging,
|
isDragging: options?.isDragging,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -1825,9 +1836,14 @@ export class LinearElementEditor {
|
||||||
.map((segment) => segment.index)
|
.map((segment) => segment.index)
|
||||||
.reduce((count, idx) => (idx < index ? count + 1 : count), 0);
|
.reduce((count, idx) => (idx < index ? count + 1 : count), 0);
|
||||||
|
|
||||||
mutateElement(element, {
|
mutateElbowArrow(
|
||||||
fixedSegments: nextFixedSegments,
|
element,
|
||||||
});
|
{
|
||||||
|
fixedSegments: nextFixedSegments,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const point = pointFrom<GlobalPoint>(
|
const point = pointFrom<GlobalPoint>(
|
||||||
element.x +
|
element.x +
|
||||||
|
@ -1859,14 +1875,19 @@ export class LinearElementEditor {
|
||||||
|
|
||||||
static deleteFixedSegment(
|
static deleteFixedSegment(
|
||||||
element: ExcalidrawElbowArrowElement,
|
element: ExcalidrawElbowArrowElement,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
index: number,
|
index: number,
|
||||||
): void {
|
): void {
|
||||||
mutateElement(element, {
|
mutateElbowArrow(
|
||||||
fixedSegments: element.fixedSegments?.filter(
|
element,
|
||||||
(segment) => segment.index !== index,
|
{
|
||||||
),
|
fixedSegments: element.fixedSegments?.filter(
|
||||||
});
|
(segment) => segment.index !== index,
|
||||||
mutateElement(element, {}, true);
|
),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,23 +2,20 @@ import {
|
||||||
getSizeFromPoints,
|
getSizeFromPoints,
|
||||||
randomInteger,
|
randomInteger,
|
||||||
getUpdatedTimestamp,
|
getUpdatedTimestamp,
|
||||||
toBrandedType,
|
invariant,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
// TODO: remove direct dependency on the scene, should be passed in or injected instead
|
// TODO: remove direct dependency on the scene, should be passed in or injected instead
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
import Scene from "@excalidraw/excalidraw/scene/Scene";
|
import Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||||
|
|
||||||
import type { Radians } from "@excalidraw/math";
|
|
||||||
|
|
||||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import { ShapeCache } from "./ShapeCache";
|
import { ShapeCache } from "./ShapeCache";
|
||||||
|
|
||||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
import { elbowArrowNeedsToGetNormalized } from "./elbowArrow";
|
||||||
import { isElbowArrow } from "./typeChecks";
|
|
||||||
|
|
||||||
import type { ExcalidrawElement, NonDeletedSceneElementsMap } from "./types";
|
import type { ExcalidrawElement } from "./types";
|
||||||
|
|
||||||
export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
|
export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
|
||||||
Partial<TElement>,
|
Partial<TElement>,
|
||||||
|
@ -33,54 +30,25 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
||||||
element: TElement,
|
element: TElement,
|
||||||
updates: ElementUpdate<TElement>,
|
updates: ElementUpdate<TElement>,
|
||||||
informMutation = true,
|
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 => {
|
): TElement => {
|
||||||
let didChange = false;
|
let didChange = false;
|
||||||
|
|
||||||
// casting to any because can't use `in` operator
|
// casting to any because can't use `in` operator
|
||||||
// (see https://github.com/microsoft/TypeScript/issues/21732)
|
// (see https://github.com/microsoft/TypeScript/issues/21732)
|
||||||
const { points, fixedSegments, fileId, startBinding, endBinding } =
|
const { points, fileId, fixedSegments, startBinding, endBinding } =
|
||||||
updates as any;
|
updates as any;
|
||||||
|
|
||||||
if (
|
invariant(
|
||||||
isElbowArrow(element) &&
|
elbowArrowNeedsToGetNormalized(element, {
|
||||||
(Object.keys(updates).length === 0 || // normalization case
|
points,
|
||||||
typeof points !== "undefined" || // repositioning
|
fixedSegments,
|
||||||
typeof fixedSegments !== "undefined" || // segment fixing
|
startBinding,
|
||||||
typeof startBinding !== "undefined" ||
|
endBinding,
|
||||||
typeof endBinding !== "undefined") // manual binding to element
|
}),
|
||||||
) {
|
"Elbow arrow should get normalized! Use `mutateElbowArrow` instead.",
|
||||||
const elementsMap = toBrandedType<NonDeletedSceneElementsMap>(
|
);
|
||||||
Scene.getScene(element)?.getNonDeletedElementsMap() ?? new Map(),
|
|
||||||
);
|
|
||||||
|
|
||||||
updates = {
|
if (typeof points !== "undefined") {
|
||||||
...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") {
|
|
||||||
updates = { ...getSizeFromPoints(points), ...updates };
|
updates = { ...getSizeFromPoints(points), ...updates };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,8 @@ import {
|
||||||
|
|
||||||
import { isInGroup } from "./groups";
|
import { isInGroup } from "./groups";
|
||||||
|
|
||||||
|
import { mutateElbowArrow } from "./elbowArrow";
|
||||||
|
|
||||||
import type { BoundingBox } from "./bounds";
|
import type { BoundingBox } from "./bounds";
|
||||||
import type {
|
import type {
|
||||||
MaybeTransformHandleType,
|
MaybeTransformHandleType,
|
||||||
|
@ -545,9 +547,14 @@ const rotateMultipleElements = (
|
||||||
|
|
||||||
if (isElbowArrow(element)) {
|
if (isElbowArrow(element)) {
|
||||||
// Needed to re-route the arrow
|
// Needed to re-route the arrow
|
||||||
mutateElement(element, {
|
mutateElbowArrow(
|
||||||
points: getArrowLocalFixedPoints(element, elementsMap),
|
element,
|
||||||
});
|
{
|
||||||
|
points: getArrowLocalFixedPoints(element, elementsMap),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
mutateElement(
|
mutateElement(
|
||||||
element,
|
element,
|
||||||
|
@ -1527,10 +1534,14 @@ export const resizeMultipleElements = (
|
||||||
} of elementsAndUpdates) {
|
} of elementsAndUpdates) {
|
||||||
const { width, height, angle } = update;
|
const { width, height, angle } = update;
|
||||||
|
|
||||||
mutateElement(element, update, false, {
|
if (isElbowArrow(element)) {
|
||||||
// needed for the fixed binding point udpate to take effect
|
mutateElbowArrow(element, update, false, elementsMap, {
|
||||||
isDragging: true,
|
// needed for the fixed binding point udpate to take effect
|
||||||
});
|
isDragging: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mutateElement(element, update, false);
|
||||||
|
}
|
||||||
|
|
||||||
updateBoundElements(element, elementsMap as SceneElementsMap, {
|
updateBoundElements(element, elementsMap as SceneElementsMap, {
|
||||||
simultaneouslyUpdated: elementsToUpdate,
|
simultaneouslyUpdated: elementsToUpdate,
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
|
isElbowArrow,
|
||||||
isTextBindableContainer,
|
isTextBindableContainer,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
isUsingAdaptiveRadius,
|
isUsingAdaptiveRadius,
|
||||||
|
@ -33,11 +34,14 @@ import { syncMovedIndices } from "@excalidraw/element/fractionalIndex";
|
||||||
|
|
||||||
import { newElement } from "@excalidraw/element/newElement";
|
import { newElement } from "@excalidraw/element/newElement";
|
||||||
|
|
||||||
|
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawTextContainer,
|
ExcalidrawTextContainer,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
|
FixedPointBinding,
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||||
|
@ -297,7 +301,21 @@ export const actionWrapTextInContainer = register({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startBinding || endBinding) {
|
if (startBinding || endBinding) {
|
||||||
mutateElement(ele, { startBinding, endBinding }, false);
|
const updates = { startBinding, endBinding };
|
||||||
|
|
||||||
|
if (isElbowArrow(ele)) {
|
||||||
|
mutateElbowArrow(
|
||||||
|
ele,
|
||||||
|
updates as {
|
||||||
|
startBinding: FixedPointBinding;
|
||||||
|
endBinding: FixedPointBinding;
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
mutateElement(ele, updates, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,7 @@ import { KEYS, updateActiveTool } from "@excalidraw/common";
|
||||||
import { getNonDeletedElements } from "@excalidraw/element";
|
import { getNonDeletedElements } from "@excalidraw/element";
|
||||||
import { fixBindingsAfterDeletion } from "@excalidraw/element/binding";
|
import { fixBindingsAfterDeletion } from "@excalidraw/element/binding";
|
||||||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||||
import {
|
import { newElementWith } from "@excalidraw/element/mutateElement";
|
||||||
mutateElement,
|
|
||||||
newElementWith,
|
|
||||||
} from "@excalidraw/element/mutateElement";
|
|
||||||
import { getContainerElement } from "@excalidraw/element/textElement";
|
import { getContainerElement } from "@excalidraw/element/textElement";
|
||||||
import {
|
import {
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
|
@ -20,6 +17,8 @@ import {
|
||||||
selectGroupsForSelectedElements,
|
selectGroupsForSelectedElements,
|
||||||
} from "@excalidraw/element/groups";
|
} from "@excalidraw/element/groups";
|
||||||
|
|
||||||
|
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||||
|
|
||||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
@ -94,15 +93,21 @@ const deleteSelectedElements = (
|
||||||
el.boundElements.forEach((candidate) => {
|
el.boundElements.forEach((candidate) => {
|
||||||
const bound = app.scene.getNonDeletedElementsMap().get(candidate.id);
|
const bound = app.scene.getNonDeletedElementsMap().get(candidate.id);
|
||||||
if (bound && isElbowArrow(bound)) {
|
if (bound && isElbowArrow(bound)) {
|
||||||
mutateElement(bound, {
|
mutateElbowArrow(
|
||||||
startBinding:
|
bound,
|
||||||
el.id === bound.startBinding?.elementId
|
{
|
||||||
? null
|
startBinding:
|
||||||
: bound.startBinding,
|
el.id === bound.startBinding?.elementId
|
||||||
endBinding:
|
? null
|
||||||
el.id === bound.endBinding?.elementId ? null : bound.endBinding,
|
: bound.startBinding,
|
||||||
});
|
endBinding:
|
||||||
mutateElement(bound, { points: bound.points });
|
el.id === bound.endBinding?.elementId
|
||||||
|
? null
|
||||||
|
: bound.endBinding,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,12 @@ import {
|
||||||
bindOrUnbindLinearElement,
|
bindOrUnbindLinearElement,
|
||||||
} from "@excalidraw/element/binding";
|
} from "@excalidraw/element/binding";
|
||||||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||||
|
|
||||||
import { mutateElement } from "@excalidraw/element/mutateElement";
|
import { mutateElement } from "@excalidraw/element/mutateElement";
|
||||||
|
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||||
import {
|
import {
|
||||||
isBindingElement,
|
isBindingElement,
|
||||||
|
isElbowArrow,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
} from "@excalidraw/element/typeChecks";
|
} from "@excalidraw/element/typeChecks";
|
||||||
|
|
||||||
|
@ -16,6 +19,13 @@ import { isPathALoop } from "@excalidraw/element/shapes";
|
||||||
|
|
||||||
import { isInvisiblySmallElement } from "@excalidraw/element/sizeHelpers";
|
import { isInvisiblySmallElement } from "@excalidraw/element/sizeHelpers";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ExcalidrawElbowArrowElement,
|
||||||
|
ExcalidrawElement,
|
||||||
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
|
import type { ElementUpdate } from "@excalidraw/element/mutateElement";
|
||||||
|
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { resetCursor } from "../cursor";
|
import { resetCursor } from "../cursor";
|
||||||
import { done } from "../components/icons";
|
import { done } from "../components/icons";
|
||||||
|
@ -85,6 +95,16 @@ export const actionFinalize = register({
|
||||||
? appState.newElement
|
? appState.newElement
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const mutate = (updates: ElementUpdate<ExcalidrawElbowArrowElement>) =>
|
||||||
|
isElbowArrow(multiPointElement as ExcalidrawElbowArrowElement)
|
||||||
|
? mutateElbowArrow(
|
||||||
|
multiPointElement as ExcalidrawElbowArrowElement,
|
||||||
|
updates,
|
||||||
|
true,
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
|
: mutateElement(multiPointElement as ExcalidrawElement, updates);
|
||||||
|
|
||||||
if (multiPointElement) {
|
if (multiPointElement) {
|
||||||
// pen and mouse have hover
|
// pen and mouse have hover
|
||||||
if (
|
if (
|
||||||
|
@ -96,7 +116,7 @@ export const actionFinalize = register({
|
||||||
!lastCommittedPoint ||
|
!lastCommittedPoint ||
|
||||||
points[points.length - 1] !== lastCommittedPoint
|
points[points.length - 1] !== lastCommittedPoint
|
||||||
) {
|
) {
|
||||||
mutateElement(multiPointElement, {
|
mutate({
|
||||||
points: multiPointElement.points.slice(0, -1),
|
points: multiPointElement.points.slice(0, -1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -120,7 +140,7 @@ export const actionFinalize = register({
|
||||||
if (isLoop) {
|
if (isLoop) {
|
||||||
const linePoints = multiPointElement.points;
|
const linePoints = multiPointElement.points;
|
||||||
const firstPoint = linePoints[0];
|
const firstPoint = linePoints[0];
|
||||||
mutateElement(multiPointElement, {
|
mutate({
|
||||||
points: linePoints.map((p, index) =>
|
points: linePoints.map((p, index) =>
|
||||||
index === linePoints.length - 1
|
index === linePoints.length - 1
|
||||||
? pointFrom(firstPoint[0], firstPoint[1])
|
? pointFrom(firstPoint[0], firstPoint[1])
|
||||||
|
|
|
@ -302,6 +302,10 @@ import {
|
||||||
|
|
||||||
import { isNonDeletedElement } from "@excalidraw/element";
|
import { isNonDeletedElement } from "@excalidraw/element";
|
||||||
|
|
||||||
|
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||||
|
|
||||||
|
import type { ElementUpdate } from "@excalidraw/element/mutateElement";
|
||||||
|
|
||||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
import type { LocalPoint, Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
@ -327,6 +331,7 @@ import type {
|
||||||
MagicGenerationData,
|
MagicGenerationData,
|
||||||
ExcalidrawNonSelectionElement,
|
ExcalidrawNonSelectionElement,
|
||||||
ExcalidrawArrowElement,
|
ExcalidrawArrowElement,
|
||||||
|
ExcalidrawElbowArrowElement,
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
import type { ValueOf } from "@excalidraw/common/utility-types";
|
import type { ValueOf } from "@excalidraw/common/utility-types";
|
||||||
|
@ -5485,7 +5490,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
|
|
||||||
if (midPoint && midPoint > -1) {
|
if (midPoint && midPoint > -1) {
|
||||||
this.store.shouldCaptureIncrement();
|
this.store.shouldCaptureIncrement();
|
||||||
LinearElementEditor.deleteFixedSegment(selectedElements[0], midPoint);
|
LinearElementEditor.deleteFixedSegment(
|
||||||
|
selectedElements[0],
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
midPoint,
|
||||||
|
);
|
||||||
|
|
||||||
const nextCoords = LinearElementEditor.getSegmentMidpointHitCoords(
|
const nextCoords = LinearElementEditor.getSegmentMidpointHitCoords(
|
||||||
{
|
{
|
||||||
|
@ -5991,23 +6000,30 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
if (isPathALoop(points, this.state.zoom.value)) {
|
if (isPathALoop(points, this.state.zoom.value)) {
|
||||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||||
}
|
}
|
||||||
// update last uncommitted point
|
const updates = {
|
||||||
mutateElement(
|
points: [
|
||||||
multiElement,
|
...points.slice(0, -1),
|
||||||
{
|
pointFrom<LocalPoint>(
|
||||||
points: [
|
lastCommittedX + dxFromLastCommitted,
|
||||||
...points.slice(0, -1),
|
lastCommittedY + dyFromLastCommitted,
|
||||||
pointFrom<LocalPoint>(
|
),
|
||||||
lastCommittedX + dxFromLastCommitted,
|
],
|
||||||
lastCommittedY + dyFromLastCommitted,
|
};
|
||||||
),
|
|
||||||
],
|
if (isElbowArrow(multiElement)) {
|
||||||
},
|
mutateElbowArrow(
|
||||||
false,
|
multiElement,
|
||||||
{
|
updates,
|
||||||
isDragging: true,
|
false,
|
||||||
},
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
{
|
||||||
|
isDragging: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// update last uncommitted point
|
||||||
|
mutateElement(multiElement, updates, false);
|
||||||
|
}
|
||||||
|
|
||||||
// in this path, we're mutating multiElement to reflect
|
// in this path, we're mutating multiElement to reflect
|
||||||
// how it will be after adding pointer position as the next point
|
// how it will be after adding pointer position as the next point
|
||||||
|
@ -8672,25 +8688,33 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mutate = (
|
||||||
|
updates: ElementUpdate<ExcalidrawLinearElement>,
|
||||||
|
options: { isDragging?: boolean } = {},
|
||||||
|
) =>
|
||||||
|
isElbowArrow(newElement)
|
||||||
|
? mutateElbowArrow(
|
||||||
|
newElement,
|
||||||
|
updates as ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||||
|
false,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
: mutateElement(newElement, updates, false);
|
||||||
|
|
||||||
if (points.length === 1) {
|
if (points.length === 1) {
|
||||||
mutateElement(
|
mutate({
|
||||||
newElement,
|
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
||||||
{
|
});
|
||||||
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
} else if (
|
} else if (
|
||||||
points.length === 2 ||
|
points.length === 2 ||
|
||||||
(points.length > 1 && isElbowArrow(newElement))
|
(points.length > 1 && isElbowArrow(newElement))
|
||||||
) {
|
) {
|
||||||
mutateElement(
|
mutate(
|
||||||
newElement,
|
|
||||||
{
|
{
|
||||||
points: [...points.slice(0, -1), pointFrom<LocalPoint>(dx, dy)],
|
points: [...points.slice(0, -1), pointFrom<LocalPoint>(dx, dy)],
|
||||||
},
|
},
|
||||||
false,
|
{ isDragging: true }
|
||||||
{ isDragging: true },
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8937,7 +8961,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
if (element) {
|
if (element) {
|
||||||
mutateElement(element, {}, true);
|
mutateElbowArrow(
|
||||||
|
element as ExcalidrawElbowArrowElement,
|
||||||
|
{},
|
||||||
|
true,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9080,7 +9109,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!pointerDownState.drag.hasOccurred && newElement && !multiElement) {
|
if (!pointerDownState.drag.hasOccurred && newElement && !multiElement) {
|
||||||
mutateElement(newElement, {
|
const updates = {
|
||||||
points: [
|
points: [
|
||||||
...newElement.points,
|
...newElement.points,
|
||||||
pointFrom<LocalPoint>(
|
pointFrom<LocalPoint>(
|
||||||
|
@ -9088,7 +9117,19 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
pointerCoords.y - newElement.y,
|
pointerCoords.y - newElement.y,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (isElbowArrow(newElement)) {
|
||||||
|
mutateElbowArrow(
|
||||||
|
newElement,
|
||||||
|
updates,
|
||||||
|
false,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
mutateElement(newElement, updates, false);
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
multiElement: newElement,
|
multiElement: newElement,
|
||||||
newElement,
|
newElement,
|
||||||
|
|
|
@ -13,10 +13,12 @@ import {
|
||||||
handleBindTextResize,
|
handleBindTextResize,
|
||||||
} from "@excalidraw/element/textElement";
|
} from "@excalidraw/element/textElement";
|
||||||
|
|
||||||
import { isTextElement } from "@excalidraw/element/typeChecks";
|
import { isElbowArrow, isTextElement } from "@excalidraw/element/typeChecks";
|
||||||
|
|
||||||
import { getCommonBounds } from "@excalidraw/utils";
|
import { getCommonBounds } from "@excalidraw/utils";
|
||||||
|
|
||||||
|
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
@ -80,7 +82,12 @@ const resizeElementInGroup = (
|
||||||
) => {
|
) => {
|
||||||
const updates = getResizedUpdates(anchorX, anchorY, scale, origElement);
|
const updates = getResizedUpdates(anchorX, anchorY, scale, origElement);
|
||||||
|
|
||||||
mutateElement(latestElement, updates, false);
|
if (isElbowArrow(latestElement)) {
|
||||||
|
mutateElbowArrow(latestElement, updates, false, elementsMap);
|
||||||
|
} else {
|
||||||
|
mutateElement(latestElement, updates, false);
|
||||||
|
}
|
||||||
|
|
||||||
const boundTextElement = getBoundTextElement(
|
const boundTextElement = getBoundTextElement(
|
||||||
origElement,
|
origElement,
|
||||||
originalElementsMap,
|
originalElementsMap,
|
||||||
|
|
|
@ -264,6 +264,8 @@ export {
|
||||||
bumpVersion,
|
bumpVersion,
|
||||||
} from "@excalidraw/element/mutateElement";
|
} from "@excalidraw/element/mutateElement";
|
||||||
|
|
||||||
|
export { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||||
|
|
||||||
export { CaptureUpdateAction } from "./store";
|
export { CaptureUpdateAction } from "./store";
|
||||||
|
|
||||||
export { parseLibraryTokensFromUrl, useHandleLibrary } from "./data/library";
|
export { parseLibraryTokensFromUrl, useHandleLibrary } from "./data/library";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue