fix(editor): Hardened fixed point and bound element parsing in restore (#10816)
* fix: Reinforce fixedPoint restore Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Even more hardened boundElement in restore Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Extract constant Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Remove superfluous check from restore Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * chore: Remove non-needed code path Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: More robust number test for fixedPoint parsing Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Validate bindings for element being parsed Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * unrelated type safety --------- Signed-off-by: Mark Tolmacs <mark@lazycat.hu> Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
@@ -2447,21 +2447,37 @@ export const getArrowLocalFixedPoints = (
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const normalizeFixedPoint = <T extends FixedPoint | null>(
|
export const isFixedPoint = (
|
||||||
|
fixedPoint: any,
|
||||||
|
): fixedPoint is FixedPointBinding["fixedPoint"] => {
|
||||||
|
return (
|
||||||
|
Array.isArray(fixedPoint) &&
|
||||||
|
fixedPoint.length === 2 &&
|
||||||
|
fixedPoint.every((coord) => Number.isFinite(coord))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const normalizeFixedPoint = <T extends FixedPoint>(
|
||||||
fixedPoint: T,
|
fixedPoint: T,
|
||||||
): T extends null ? null : FixedPoint => {
|
): FixedPoint => {
|
||||||
|
if (!isFixedPoint(fixedPoint)) {
|
||||||
|
return [0.5001, 0.5001];
|
||||||
|
}
|
||||||
|
|
||||||
|
const EPSILON = 0.0001;
|
||||||
|
|
||||||
// Do not allow a precise 0.5 for fixed point ratio
|
// Do not allow a precise 0.5 for fixed point ratio
|
||||||
// to avoid jumping arrow heading due to floating point imprecision
|
// to avoid jumping arrow heading due to floating point imprecision
|
||||||
if (
|
if (
|
||||||
fixedPoint &&
|
Math.abs(fixedPoint[0] - 0.5) < EPSILON ||
|
||||||
(Math.abs(fixedPoint[0] - 0.5) < 0.0001 ||
|
Math.abs(fixedPoint[1] - 0.5) < EPSILON
|
||||||
Math.abs(fixedPoint[1] - 0.5) < 0.0001)
|
|
||||||
) {
|
) {
|
||||||
return fixedPoint.map((ratio) =>
|
return fixedPoint.map((ratio) =>
|
||||||
Math.abs(ratio - 0.5) < 0.0001 ? 0.5001 : ratio,
|
Math.abs(ratio - 0.5) < EPSILON ? 0.5001 : ratio,
|
||||||
) as T extends null ? null : FixedPoint;
|
) as FixedPoint;
|
||||||
}
|
}
|
||||||
return fixedPoint as any as T extends null ? null : FixedPoint;
|
|
||||||
|
return fixedPoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Side =
|
type Side =
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
|||||||
| ExcalidrawElbowArrowElement["startBinding"]
|
| ExcalidrawElbowArrowElement["startBinding"]
|
||||||
| ExcalidrawElbowArrowElement["endBinding"] = {
|
| ExcalidrawElbowArrowElement["endBinding"] = {
|
||||||
...binding,
|
...binding,
|
||||||
fixedPoint: normalizeFixedPoint(binding.fixedPoint ?? [0, 0]),
|
fixedPoint: normalizeFixedPoint(binding.fixedPoint),
|
||||||
mode: binding.mode || "orbit",
|
mode: binding.mode || "orbit",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
|||||||
return {
|
return {
|
||||||
elementId: binding.elementId,
|
elementId: binding.elementId,
|
||||||
mode: binding.mode,
|
mode: binding.mode,
|
||||||
fixedPoint: normalizeFixedPoint(binding.fixedPoint || [0.5, 0.5]),
|
fixedPoint: normalizeFixedPoint(binding.fixedPoint),
|
||||||
} as FixedPointBinding | null;
|
} as FixedPointBinding | null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -185,15 +185,14 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
|||||||
// binding schema v1 (legacy) -> attempt to migrate to v2
|
// binding schema v1 (legacy) -> attempt to migrate to v2
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const targetBoundElement =
|
const targetBoundElement = targetElementsMap.get(binding.elementId) as
|
||||||
(targetElementsMap.get(binding.elementId) as ExcalidrawBindableElement) ||
|
| ExcalidrawBindableElement
|
||||||
undefined;
|
| undefined;
|
||||||
const boundElement =
|
const boundElement =
|
||||||
targetBoundElement ||
|
targetBoundElement ||
|
||||||
(existingElementsMap?.get(
|
(existingElementsMap?.get(binding.elementId) as
|
||||||
binding.elementId,
|
| ExcalidrawBindableElement
|
||||||
) as ExcalidrawBindableElement) ||
|
| undefined);
|
||||||
undefined;
|
|
||||||
const elementsMap = targetBoundElement
|
const elementsMap = targetBoundElement
|
||||||
? targetElementsMap
|
? targetElementsMap
|
||||||
: existingElementsMap;
|
: existingElementsMap;
|
||||||
@@ -208,11 +207,28 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
|||||||
const mode = isPointInElement(p, boundElement, elementsMap)
|
const mode = isPointInElement(p, boundElement, elementsMap)
|
||||||
? "inside"
|
? "inside"
|
||||||
: "orbit";
|
: "orbit";
|
||||||
|
const safeElement = {
|
||||||
|
...element,
|
||||||
|
startBinding: element.startBinding?.elementId
|
||||||
|
? {
|
||||||
|
...element.startBinding,
|
||||||
|
mode,
|
||||||
|
fixedPoint: normalizeFixedPoint(element.startBinding.fixedPoint),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
endBinding: element.endBinding?.elementId
|
||||||
|
? {
|
||||||
|
...element.endBinding,
|
||||||
|
mode,
|
||||||
|
fixedPoint: normalizeFixedPoint(element.endBinding.fixedPoint),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
};
|
||||||
const focusPoint =
|
const focusPoint =
|
||||||
mode === "inside"
|
mode === "inside"
|
||||||
? p
|
? p
|
||||||
: projectFixedPointOntoDiagonal(
|
: projectFixedPointOntoDiagonal(
|
||||||
element,
|
safeElement,
|
||||||
p,
|
p,
|
||||||
boundElement,
|
boundElement,
|
||||||
startOrEnd,
|
startOrEnd,
|
||||||
@@ -220,7 +236,7 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
|||||||
{ value: 1 as NormalizedZoomValue },
|
{ value: 1 as NormalizedZoomValue },
|
||||||
) || p;
|
) || p;
|
||||||
const { fixedPoint } = calculateFixedPointForNonElbowArrowBinding(
|
const { fixedPoint } = calculateFixedPointForNonElbowArrowBinding(
|
||||||
element,
|
safeElement,
|
||||||
boundElement,
|
boundElement,
|
||||||
startOrEnd,
|
startOrEnd,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
|
|||||||
Reference in New Issue
Block a user