fix: Elbow arrow orthogonality (#9073)

This commit is contained in:
Márk Tolmács 2025-01-31 14:19:07 +01:00 committed by GitHub
parent 6cdb683410
commit 9b6edc767a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 10 deletions

View file

@ -244,6 +244,12 @@ const handleSegmentRenormalization = (
); );
} }
import.meta.env.DEV &&
invariant(
validateElbowPoints(nextPoints),
"Invalid elbow points with fixed segments",
);
return normalizeArrowElementUpdate( return normalizeArrowElementUpdate(
nextPoints, nextPoints,
filteredNextFixedSegments, filteredNextFixedSegments,
@ -912,7 +918,11 @@ export const updateElbowArrowPoints = (
// 0. During all element replacement in the scene, we just need to renormalize // 0. During all element replacement in the scene, we just need to renormalize
// the arrow // the arrow
// TODO (dwelle,mtolmacs): Remove this once Scene.getScene() is removed // TODO (dwelle,mtolmacs): Remove this once Scene.getScene() is removed
if (elementsMap.size === 0 && updates.points) { if (
elementsMap.size === 0 &&
updates.points &&
validateElbowPoints(updates.points)
) {
return normalizeArrowElementUpdate( return normalizeArrowElementUpdate(
updates.points.map((p) => updates.points.map((p) =>
pointFrom<GlobalPoint>(arrow.x + p[0], arrow.y + p[1]), pointFrom<GlobalPoint>(arrow.x + p[0], arrow.y + p[1]),
@ -2123,3 +2133,16 @@ const getHoveredElements = (
const gridAddressesEqual = (a: GridAddress, b: GridAddress): boolean => const gridAddressesEqual = (a: GridAddress, b: GridAddress): boolean =>
a[0] === b[0] && a[1] === b[1]; a[0] === b[0] && a[1] === b[1];
const validateElbowPoints = <P extends GlobalPoint | LocalPoint>(
points: readonly P[],
tolerance: number = DEDUP_TRESHOLD,
) =>
points
.slice(1)
.map(
(p, i) =>
Math.abs(p[0] - points[i][0]) < tolerance ||
Math.abs(p[1] - points[i][1]) < tolerance,
)
.every(Boolean);

View file

@ -10,13 +10,15 @@ import {
import { bindLinearElement } from "./binding"; import { bindLinearElement } from "./binding";
import { LinearElementEditor } from "./linearElementEditor"; import { LinearElementEditor } from "./linearElementEditor";
import { newArrowElement, newElement } from "./newElement"; import { newArrowElement, newElement } from "./newElement";
import type { import type { SceneElementsMap } from "./types";
ElementsMap, import {
ExcalidrawBindableElement, type ElementsMap,
ExcalidrawElement, type ExcalidrawBindableElement,
ExcalidrawFlowchartNodeElement, type ExcalidrawElement,
NonDeletedSceneElementsMap, type ExcalidrawFlowchartNodeElement,
OrderedExcalidrawElement, type NonDeletedSceneElementsMap,
type Ordered,
type OrderedExcalidrawElement,
} from "./types"; } from "./types";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import type { AppState, PendingExcalidrawElements } from "../types"; import type { AppState, PendingExcalidrawElements } from "../types";
@ -28,9 +30,10 @@ import {
isFrameElement, isFrameElement,
isFlowchartNodeElement, isFlowchartNodeElement,
} from "./typeChecks"; } from "./typeChecks";
import { invariant } from "../utils"; import { invariant, toBrandedType } from "../utils";
import { pointFrom, type LocalPoint } from "../../math"; import { pointFrom, type LocalPoint } from "../../math";
import { aabbForElement } from "../shapes"; import { aabbForElement } from "../shapes";
import { updateElbowArrowPoints } from "./elbowArrow";
type LinkDirection = "up" | "right" | "down" | "left"; type LinkDirection = "up" | "right" | "down" | "left";
@ -467,7 +470,23 @@ const createBindingArrow = (
}, },
]); ]);
return bindingArrow; const update = updateElbowArrowPoints(
bindingArrow,
toBrandedType<SceneElementsMap>(
new Map([
...elementsMap.entries(),
[startBindingElement.id, startBindingElement],
[endBindingElement.id, endBindingElement],
[bindingArrow.id, bindingArrow],
] as [string, Ordered<ExcalidrawElement>][]),
),
{ points: bindingArrow.points },
);
return {
...bindingArrow,
...update,
};
}; };
export class FlowChartNavigator { export class FlowChartNavigator {