feat: Better restore for bindings

This commit is contained in:
Mark Tolmacs
2025-11-13 14:15:14 +01:00
parent fe33028617
commit ee33204ed1

View File

@@ -18,7 +18,13 @@ import {
normalizeLink, normalizeLink,
getLineHeight, getLineHeight,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { getNonDeletedElements, isValidPolygon } from "@excalidraw/element"; import {
calculateFixedPointForNonElbowArrowBinding,
getNonDeletedElements,
isPointInElement,
isValidPolygon,
projectFixedPointOntoDiagonal,
} from "@excalidraw/element";
import { normalizeFixedPoint } from "@excalidraw/element"; import { normalizeFixedPoint } from "@excalidraw/element";
import { import {
updateElbowArrowPoints, updateElbowArrowPoints,
@@ -50,9 +56,9 @@ import type { LocalPoint, Radians } from "@excalidraw/math";
import type { import type {
ExcalidrawArrowElement, ExcalidrawArrowElement,
ExcalidrawBindableElement,
ExcalidrawElbowArrowElement, ExcalidrawElbowArrowElement,
ExcalidrawElement, ExcalidrawElement,
ExcalidrawElementType,
ExcalidrawLinearElement, ExcalidrawLinearElement,
ExcalidrawSelectionElement, ExcalidrawSelectionElement,
ExcalidrawTextElement, ExcalidrawTextElement,
@@ -119,9 +125,11 @@ const getFontFamilyByName = (fontFamilyName: string): FontFamilyValues => {
return DEFAULT_FONT_FAMILY; return DEFAULT_FONT_FAMILY;
}; };
const repairBinding = <T extends ExcalidrawLinearElement>( const repairBinding = <T extends ExcalidrawArrowElement>(
element: T, element: T,
binding: FixedPointBinding | null, binding: FixedPointBinding | null,
elements: readonly ExcalidrawElement[],
startOrEnd: "start" | "end",
): FixedPointBinding | null => { ): FixedPointBinding | null => {
if (!binding) { if (!binding) {
return null; return null;
@@ -139,11 +147,54 @@ const repairBinding = <T extends ExcalidrawLinearElement>(
return fixedPointBinding; return fixedPointBinding;
} }
return { const boundElement =
elementId: binding.elementId, (elements.find(
mode: binding.mode || "orbit", (el) => el.id === binding.elementId,
fixedPoint: normalizeFixedPoint(binding.fixedPoint || [0.51, 0.51]), ) as ExcalidrawBindableElement) || undefined;
} as FixedPointBinding | null; if (boundElement) {
if (binding.mode) {
return {
elementId: binding.elementId,
mode: binding.mode || "orbit",
fixedPoint: normalizeFixedPoint(binding.fixedPoint || [0.51, 0.51]),
} as FixedPointBinding | null;
}
const elementsMap = arrayToMap(elements);
const p = LinearElementEditor.getPointAtIndexGlobalCoordinates(
element,
startOrEnd === "start" ? 0 : element.points.length - 1,
elementsMap,
);
const mode = isPointInElement(p, boundElement, elementsMap)
? "inside"
: "orbit";
const focusPoint =
mode === "inside"
? p
: projectFixedPointOntoDiagonal(
element,
p,
boundElement,
startOrEnd,
elementsMap,
) || p;
const { fixedPoint } = calculateFixedPointForNonElbowArrowBinding(
element,
boundElement,
startOrEnd,
elementsMap,
focusPoint,
);
return {
mode,
elementId: binding.elementId,
fixedPoint,
};
}
return null;
}; };
const restoreElementWithProperties = < const restoreElementWithProperties = <
@@ -234,7 +285,10 @@ const restoreElementWithProperties = <
export const restoreElement = ( export const restoreElement = (
element: Exclude<ExcalidrawElement, ExcalidrawSelectionElement>, element: Exclude<ExcalidrawElement, ExcalidrawSelectionElement>,
opts?: { deleteInvisibleElements?: boolean }, elements: readonly ExcalidrawElement[],
opts?: {
deleteInvisibleElements?: boolean;
},
): typeof element | null => { ): typeof element | null => {
element = { ...element }; element = { ...element };
@@ -321,12 +375,9 @@ export const restoreElement = (
} }
return restoreElementWithProperties(element, { return restoreElementWithProperties(element, {
type: type: "line",
(element.type as ExcalidrawElementType | "draw") === "draw" startBinding: null,
? "line" endBinding: null,
: element.type,
startBinding: repairBinding(element, element.startBinding),
endBinding: repairBinding(element, element.endBinding),
startArrowhead, startArrowhead,
endArrowhead, endArrowhead,
points, points,
@@ -357,8 +408,18 @@ export const restoreElement = (
const base = { const base = {
type: element.type, type: element.type,
startBinding: repairBinding(element, element.startBinding), startBinding: repairBinding(
endBinding: repairBinding(element, element.endBinding), element as ExcalidrawArrowElement,
element.startBinding,
elements,
"start",
),
endBinding: repairBinding(
element as ExcalidrawArrowElement,
element.endBinding,
elements,
"end",
),
startArrowhead, startArrowhead,
endArrowhead, endArrowhead,
points, points,
@@ -373,8 +434,6 @@ export const restoreElement = (
? restoreElementWithProperties(element as ExcalidrawElbowArrowElement, { ? restoreElementWithProperties(element as ExcalidrawElbowArrowElement, {
...base, ...base,
elbowed: true, elbowed: true,
startBinding: repairBinding(element, element.startBinding),
endBinding: repairBinding(element, element.endBinding),
fixedSegments: fixedSegments:
element.fixedSegments?.length && base.points.length >= 4 element.fixedSegments?.length && base.points.length >= 4
? element.fixedSegments ? element.fixedSegments
@@ -535,9 +594,13 @@ export const restoreElements = (
return elements; return elements;
} }
let migratedElement: ExcalidrawElement | null = restoreElement(element, { let migratedElement: ExcalidrawElement | null = restoreElement(
deleteInvisibleElements: opts?.deleteInvisibleElements, element,
}); elements,
{
deleteInvisibleElements: opts?.deleteInvisibleElements,
},
);
if (migratedElement) { if (migratedElement) {
const localElement = localElementsMap?.get(element.id); const localElement = localElementsMap?.get(element.id);