mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: Remove GA code from binding (#9042)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
31e8476c78
commit
0ffeaeaecf
44 changed files with 2112 additions and 1832 deletions
|
@ -1633,18 +1633,16 @@ export const actionChangeArrowType = register({
|
|||
|
||||
const finalStartPoint = startHoveredElement
|
||||
? bindPointToSnapToElementOutline(
|
||||
startGlobalPoint,
|
||||
endGlobalPoint,
|
||||
newElement,
|
||||
startHoveredElement,
|
||||
elementsMap,
|
||||
"start",
|
||||
)
|
||||
: startGlobalPoint;
|
||||
const finalEndPoint = endHoveredElement
|
||||
? bindPointToSnapToElementOutline(
|
||||
endGlobalPoint,
|
||||
startGlobalPoint,
|
||||
newElement,
|
||||
endHoveredElement,
|
||||
elementsMap,
|
||||
"end",
|
||||
)
|
||||
: endGlobalPoint;
|
||||
|
||||
|
|
|
@ -188,7 +188,7 @@ export const DEFAULT_TRANSFORM_HANDLE_SPACING = 2;
|
|||
export const SIDE_RESIZING_THRESHOLD = 2 * DEFAULT_TRANSFORM_HANDLE_SPACING;
|
||||
// a small epsilon to make side resizing always take precedence
|
||||
// (avoids an increase in renders and changes to tests)
|
||||
const EPSILON = 0.00001;
|
||||
export const EPSILON = 0.00001;
|
||||
export const DEFAULT_COLLISION_THRESHOLD =
|
||||
2 * SIDE_RESIZING_THRESHOLD - EPSILON;
|
||||
|
||||
|
|
|
@ -88,9 +88,8 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "ellipse-1",
|
||||
"fixedPoint": null,
|
||||
"focus": -0.008153707962747813,
|
||||
"gap": 1,
|
||||
"focus": -0.007519379844961235,
|
||||
"gap": 11.562288374879595,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
|
@ -119,8 +118,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id49",
|
||||
"fixedPoint": null,
|
||||
"focus": -0.08139534883720931,
|
||||
"focus": -0.0813953488372095,
|
||||
"gap": 1,
|
||||
},
|
||||
"strokeColor": "#1864ab",
|
||||
|
@ -146,9 +144,8 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "ellipse-1",
|
||||
"fixedPoint": null,
|
||||
"focus": 0.10666666666666667,
|
||||
"gap": 3.834326468444573,
|
||||
"gap": 3.8343264684446097,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
|
@ -177,9 +174,8 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "diamond-1",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
"gap": 4.545343408287929,
|
||||
},
|
||||
"strokeColor": "#e67700",
|
||||
"strokeStyle": "solid",
|
||||
|
@ -338,7 +334,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "text-2",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 14,
|
||||
},
|
||||
|
@ -369,7 +364,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "text-1",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -442,8 +436,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id42",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -473,7 +466,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id41",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -620,8 +612,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id46",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -651,7 +642,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id45",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -1484,8 +1474,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "Alice",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 5.299874999999986,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -1517,7 +1506,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "Bob",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -1549,9 +1537,8 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "B",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
"gap": 14,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
|
@ -1578,7 +1565,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "Bob",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
|
|
@ -434,7 +434,7 @@ describe("Test Transform", () => {
|
|||
},
|
||||
endBinding: {
|
||||
elementId: ellipse.id,
|
||||
focus: 0,
|
||||
focus: -0,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -519,7 +519,7 @@ describe("Test Transform", () => {
|
|||
},
|
||||
endBinding: {
|
||||
elementId: text3.id,
|
||||
focus: 0,
|
||||
focus: -0,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -777,8 +777,7 @@ describe("Test Transform", () => {
|
|||
const [arrow, rect] = excalidrawElements;
|
||||
expect((arrow as ExcalidrawArrowElement).endBinding).toStrictEqual({
|
||||
elementId: "rect-1",
|
||||
fixedPoint: null,
|
||||
focus: 0,
|
||||
focus: -0,
|
||||
gap: 14,
|
||||
});
|
||||
expect(rect.boundElements).toStrictEqual([
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,10 @@
|
|||
import type {
|
||||
ElementsMap,
|
||||
ExcalidrawDiamondElement,
|
||||
ExcalidrawElement,
|
||||
ExcalidrawEllipseElement,
|
||||
ExcalidrawRectangleElement,
|
||||
ExcalidrawRectanguloidElement,
|
||||
} from "./types";
|
||||
import { getElementBounds } from "./bounds";
|
||||
import type { FrameNameBounds } from "../types";
|
||||
|
@ -16,8 +19,28 @@ import {
|
|||
isTextElement,
|
||||
} from "./typeChecks";
|
||||
import { getBoundTextShape, isPathALoop } from "../shapes";
|
||||
import type { GlobalPoint, LocalPoint, Polygon } from "../../math";
|
||||
import { isPointWithinBounds, pointFrom } from "../../math";
|
||||
import type {
|
||||
GlobalPoint,
|
||||
LineSegment,
|
||||
LocalPoint,
|
||||
Polygon,
|
||||
Radians,
|
||||
} from "../../math";
|
||||
import {
|
||||
curveIntersectLineSegment,
|
||||
isPointWithinBounds,
|
||||
line,
|
||||
lineSegment,
|
||||
lineSegmentIntersectionPoints,
|
||||
pointFrom,
|
||||
pointRotateRads,
|
||||
pointsEqual,
|
||||
} from "../../math";
|
||||
import { ellipse, ellipseLineIntersectionPoints } from "../../math/ellipse";
|
||||
import {
|
||||
deconstructDiamondElement,
|
||||
deconstructRectanguloidElement,
|
||||
} from "./utils";
|
||||
|
||||
export const shouldTestInside = (element: ExcalidrawElement) => {
|
||||
if (element.type === "arrow") {
|
||||
|
@ -121,3 +144,166 @@ export const hitElementBoundText = <Point extends GlobalPoint | LocalPoint>(
|
|||
): boolean => {
|
||||
return !!textShape && isPointInShape(pointFrom(x, y), textShape);
|
||||
};
|
||||
|
||||
/**
|
||||
* Intersect a line with an element for binding test
|
||||
*
|
||||
* @param element
|
||||
* @param line
|
||||
* @param offset
|
||||
* @returns
|
||||
*/
|
||||
export const intersectElementWithLineSegment = (
|
||||
element: ExcalidrawElement,
|
||||
line: LineSegment<GlobalPoint>,
|
||||
offset: number = 0,
|
||||
): GlobalPoint[] => {
|
||||
switch (element.type) {
|
||||
case "rectangle":
|
||||
case "image":
|
||||
case "text":
|
||||
case "iframe":
|
||||
case "embeddable":
|
||||
case "frame":
|
||||
case "magicframe":
|
||||
return intersectRectanguloidWithLineSegment(element, line, offset);
|
||||
case "diamond":
|
||||
return intersectDiamondWithLineSegment(element, line, offset);
|
||||
case "ellipse":
|
||||
return intersectEllipseWithLineSegment(element, line, offset);
|
||||
default:
|
||||
throw new Error(`Unimplemented element type '${element.type}'`);
|
||||
}
|
||||
};
|
||||
|
||||
const intersectRectanguloidWithLineSegment = (
|
||||
element: ExcalidrawRectanguloidElement,
|
||||
l: LineSegment<GlobalPoint>,
|
||||
offset: number = 0,
|
||||
): GlobalPoint[] => {
|
||||
const center = pointFrom<GlobalPoint>(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
);
|
||||
// To emulate a rotated rectangle we rotate the point in the inverse angle
|
||||
// instead. It's all the same distance-wise.
|
||||
const rotatedA = pointRotateRads<GlobalPoint>(
|
||||
l[0],
|
||||
center,
|
||||
-element.angle as Radians,
|
||||
);
|
||||
const rotatedB = pointRotateRads<GlobalPoint>(
|
||||
l[1],
|
||||
center,
|
||||
-element.angle as Radians,
|
||||
);
|
||||
|
||||
// Get the element's building components we can test against
|
||||
const [sides, corners] = deconstructRectanguloidElement(element, offset);
|
||||
|
||||
return (
|
||||
[
|
||||
// Test intersection against the sides, keep only the valid
|
||||
// intersection points and rotate them back to scene space
|
||||
...sides
|
||||
.map((s) =>
|
||||
lineSegmentIntersectionPoints(
|
||||
lineSegment<GlobalPoint>(rotatedA, rotatedB),
|
||||
s,
|
||||
),
|
||||
)
|
||||
.filter((x) => x != null)
|
||||
.map((j) => pointRotateRads<GlobalPoint>(j!, center, element.angle)),
|
||||
// Test intersection against the corners which are cubic bezier curves,
|
||||
// keep only the valid intersection points and rotate them back to scene
|
||||
// space
|
||||
...corners
|
||||
.flatMap((t) =>
|
||||
curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)),
|
||||
)
|
||||
.filter((i) => i != null)
|
||||
.map((j) => pointRotateRads(j, center, element.angle)),
|
||||
]
|
||||
// Remove duplicates
|
||||
.filter(
|
||||
(p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx,
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param element
|
||||
* @param a
|
||||
* @param b
|
||||
* @returns
|
||||
*/
|
||||
const intersectDiamondWithLineSegment = (
|
||||
element: ExcalidrawDiamondElement,
|
||||
l: LineSegment<GlobalPoint>,
|
||||
offset: number = 0,
|
||||
): GlobalPoint[] => {
|
||||
const center = pointFrom<GlobalPoint>(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
);
|
||||
|
||||
// Rotate the point to the inverse direction to simulate the rotated diamond
|
||||
// points. It's all the same distance-wise.
|
||||
const rotatedA = pointRotateRads(l[0], center, -element.angle as Radians);
|
||||
const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians);
|
||||
|
||||
const [sides, curves] = deconstructDiamondElement(element, offset);
|
||||
|
||||
return (
|
||||
[
|
||||
...sides
|
||||
.map((s) =>
|
||||
lineSegmentIntersectionPoints(
|
||||
lineSegment<GlobalPoint>(rotatedA, rotatedB),
|
||||
s,
|
||||
),
|
||||
)
|
||||
.filter((p): p is GlobalPoint => p != null)
|
||||
// Rotate back intersection points
|
||||
.map((p) => pointRotateRads<GlobalPoint>(p!, center, element.angle)),
|
||||
...curves
|
||||
.flatMap((p) =>
|
||||
curveIntersectLineSegment(p, lineSegment(rotatedA, rotatedB)),
|
||||
)
|
||||
.filter((p) => p != null)
|
||||
// Rotate back intersection points
|
||||
.map((p) => pointRotateRads(p, center, element.angle)),
|
||||
]
|
||||
// Remove duplicates
|
||||
.filter(
|
||||
(p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx,
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param element
|
||||
* @param a
|
||||
* @param b
|
||||
* @returns
|
||||
*/
|
||||
const intersectEllipseWithLineSegment = (
|
||||
element: ExcalidrawEllipseElement,
|
||||
l: LineSegment<GlobalPoint>,
|
||||
offset: number = 0,
|
||||
): GlobalPoint[] => {
|
||||
const center = pointFrom<GlobalPoint>(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
);
|
||||
|
||||
const rotatedA = pointRotateRads(l[0], center, -element.angle as Radians);
|
||||
const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians);
|
||||
|
||||
return ellipseLineIntersectionPoints(
|
||||
ellipse(center, element.width / 2 + offset, element.height / 2 + offset),
|
||||
line(rotatedA, rotatedB),
|
||||
).map((p) => pointRotateRads(p, center, element.angle));
|
||||
};
|
||||
|
|
123
packages/excalidraw/element/distance.ts
Normal file
123
packages/excalidraw/element/distance.ts
Normal file
|
@ -0,0 +1,123 @@
|
|||
import type { GlobalPoint, Radians } from "../../math";
|
||||
import {
|
||||
curvePointDistance,
|
||||
distanceToLineSegment,
|
||||
pointFrom,
|
||||
pointRotateRads,
|
||||
} from "../../math";
|
||||
import { ellipse, ellipseDistanceFromPoint } from "../../math/ellipse";
|
||||
import type {
|
||||
ExcalidrawBindableElement,
|
||||
ExcalidrawDiamondElement,
|
||||
ExcalidrawEllipseElement,
|
||||
ExcalidrawRectanguloidElement,
|
||||
} from "./types";
|
||||
import {
|
||||
deconstructDiamondElement,
|
||||
deconstructRectanguloidElement,
|
||||
} from "./utils";
|
||||
|
||||
export const distanceToBindableElement = (
|
||||
element: ExcalidrawBindableElement,
|
||||
p: GlobalPoint,
|
||||
): number => {
|
||||
switch (element.type) {
|
||||
case "rectangle":
|
||||
case "image":
|
||||
case "text":
|
||||
case "iframe":
|
||||
case "embeddable":
|
||||
case "frame":
|
||||
case "magicframe":
|
||||
return distanceToRectanguloidElement(element, p);
|
||||
case "diamond":
|
||||
return distanceToDiamondElement(element, p);
|
||||
case "ellipse":
|
||||
return distanceToEllipseElement(element, p);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the distance of a point and the provided rectangular-shaped element,
|
||||
* accounting for roundness and rotation
|
||||
*
|
||||
* @param element The rectanguloid element
|
||||
* @param p The point to consider
|
||||
* @returns The eucledian distance to the outline of the rectanguloid element
|
||||
*/
|
||||
const distanceToRectanguloidElement = (
|
||||
element: ExcalidrawRectanguloidElement,
|
||||
p: GlobalPoint,
|
||||
) => {
|
||||
const center = pointFrom<GlobalPoint>(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
);
|
||||
// To emulate a rotated rectangle we rotate the point in the inverse angle
|
||||
// instead. It's all the same distance-wise.
|
||||
const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
||||
|
||||
// Get the element's building components we can test against
|
||||
const [sides, corners] = deconstructRectanguloidElement(element);
|
||||
|
||||
return Math.min(
|
||||
...sides.map((s) => distanceToLineSegment(rotatedPoint, s)),
|
||||
...corners
|
||||
.map((a) => curvePointDistance(a, rotatedPoint))
|
||||
.filter((d): d is number => d !== null),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the distance of a point and the provided diamond element, accounting
|
||||
* for roundness and rotation
|
||||
*
|
||||
* @param element The diamond element
|
||||
* @param p The point to consider
|
||||
* @returns The eucledian distance to the outline of the diamond
|
||||
*/
|
||||
const distanceToDiamondElement = (
|
||||
element: ExcalidrawDiamondElement,
|
||||
p: GlobalPoint,
|
||||
): number => {
|
||||
const center = pointFrom<GlobalPoint>(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
);
|
||||
|
||||
// Rotate the point to the inverse direction to simulate the rotated diamond
|
||||
// points. It's all the same distance-wise.
|
||||
const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
||||
|
||||
const [sides, curves] = deconstructDiamondElement(element);
|
||||
|
||||
return Math.min(
|
||||
...sides.map((s) => distanceToLineSegment(rotatedPoint, s)),
|
||||
...curves
|
||||
.map((a) => curvePointDistance(a, rotatedPoint))
|
||||
.filter((d): d is number => d !== null),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the distance of a point and the provided ellipse element, accounting
|
||||
* for roundness and rotation
|
||||
*
|
||||
* @param element The ellipse element
|
||||
* @param p The point to consider
|
||||
* @returns The eucledian distance to the outline of the ellipse
|
||||
*/
|
||||
const distanceToEllipseElement = (
|
||||
element: ExcalidrawEllipseElement,
|
||||
p: GlobalPoint,
|
||||
): number => {
|
||||
const center = pointFrom(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
);
|
||||
return ellipseDistanceFromPoint(
|
||||
// Instead of rotating the ellipse, rotate the point to the inverse angle
|
||||
pointRotateRads(p, center, -element.angle as Radians),
|
||||
ellipse(center, element.width / 2, element.height / 2),
|
||||
);
|
||||
};
|
|
@ -18,6 +18,7 @@ import type {
|
|||
import { ARROW_TYPE } from "../constants";
|
||||
import type { LocalPoint } from "../../math";
|
||||
import { pointFrom } from "../../math";
|
||||
import "../../utils/test-utils";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
|
|
|
@ -19,8 +19,6 @@ import { invariant, isAnyTrue, toBrandedType, tupleToCoors } from "../utils";
|
|||
import type { AppState } from "../types";
|
||||
import {
|
||||
bindPointToSnapToElementOutline,
|
||||
distanceToBindableElement,
|
||||
avoidRectangularCorner,
|
||||
FIXED_BINDING_DISTANCE,
|
||||
getHeadingForElbowArrowSnap,
|
||||
getGlobalFixedPointForBindableElement,
|
||||
|
@ -42,7 +40,7 @@ import {
|
|||
headingForPoint,
|
||||
} from "./heading";
|
||||
import { type ElementUpdate } from "./mutateElement";
|
||||
import { isBindableElement, isRectanguloidElement } from "./typeChecks";
|
||||
import { isBindableElement } from "./typeChecks";
|
||||
import {
|
||||
type ExcalidrawElbowArrowElement,
|
||||
type NonDeletedSceneElementsMap,
|
||||
|
@ -55,6 +53,7 @@ import type {
|
|||
FixedPointBinding,
|
||||
FixedSegment,
|
||||
} from "./types";
|
||||
import { distanceToBindableElement } from "./distance";
|
||||
|
||||
type GridAddress = [number, number] & { _brand: "gridaddress" };
|
||||
|
||||
|
@ -1177,19 +1176,27 @@ const getElbowArrowData = (
|
|||
)
|
||||
: [startElement, endElement];
|
||||
const startGlobalPoint = getGlobalPoint(
|
||||
{
|
||||
...arrow,
|
||||
elbowed: true,
|
||||
points: nextPoints,
|
||||
} as ExcalidrawElbowArrowElement,
|
||||
"start",
|
||||
arrow.startBinding?.fixedPoint,
|
||||
origStartGlobalPoint,
|
||||
origEndGlobalPoint,
|
||||
elementsMap,
|
||||
startElement,
|
||||
hoveredStartElement,
|
||||
options?.isDragging,
|
||||
);
|
||||
const endGlobalPoint = getGlobalPoint(
|
||||
{
|
||||
...arrow,
|
||||
elbowed: true,
|
||||
points: nextPoints,
|
||||
} as ExcalidrawElbowArrowElement,
|
||||
"end",
|
||||
arrow.endBinding?.fixedPoint,
|
||||
origEndGlobalPoint,
|
||||
origStartGlobalPoint,
|
||||
elementsMap,
|
||||
endElement,
|
||||
hoveredEndElement,
|
||||
options?.isDragging,
|
||||
|
@ -2133,21 +2140,20 @@ const neighborIndexToHeading = (idx: number): Heading => {
|
|||
};
|
||||
|
||||
const getGlobalPoint = (
|
||||
arrow: ExcalidrawElbowArrowElement,
|
||||
startOrEnd: "start" | "end",
|
||||
fixedPointRatio: [number, number] | undefined | null,
|
||||
initialPoint: GlobalPoint,
|
||||
otherPoint: GlobalPoint,
|
||||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
|
||||
boundElement?: ExcalidrawBindableElement | null,
|
||||
hoveredElement?: ExcalidrawBindableElement | null,
|
||||
isDragging?: boolean,
|
||||
): GlobalPoint => {
|
||||
if (isDragging) {
|
||||
if (hoveredElement) {
|
||||
const snapPoint = getSnapPoint(
|
||||
initialPoint,
|
||||
otherPoint,
|
||||
const snapPoint = bindPointToSnapToElementOutline(
|
||||
arrow,
|
||||
hoveredElement,
|
||||
elementsMap,
|
||||
startOrEnd,
|
||||
);
|
||||
|
||||
return snapToMid(hoveredElement, snapPoint);
|
||||
|
@ -2164,29 +2170,16 @@ const getGlobalPoint = (
|
|||
|
||||
// NOTE: Resize scales the binding position point too, so we need to update it
|
||||
return Math.abs(
|
||||
distanceToBindableElement(boundElement, fixedGlobalPoint, elementsMap) -
|
||||
distanceToBindableElement(boundElement, fixedGlobalPoint) -
|
||||
FIXED_BINDING_DISTANCE,
|
||||
) > 0.01
|
||||
? getSnapPoint(initialPoint, otherPoint, boundElement, elementsMap)
|
||||
? bindPointToSnapToElementOutline(arrow, boundElement, startOrEnd)
|
||||
: fixedGlobalPoint;
|
||||
}
|
||||
|
||||
return initialPoint;
|
||||
};
|
||||
|
||||
const getSnapPoint = (
|
||||
p: GlobalPoint,
|
||||
otherPoint: GlobalPoint,
|
||||
element: ExcalidrawBindableElement,
|
||||
elementsMap: ElementsMap,
|
||||
) =>
|
||||
bindPointToSnapToElementOutline(
|
||||
isRectanguloidElement(element) ? avoidRectangularCorner(element, p) : p,
|
||||
otherPoint,
|
||||
element,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
const getBindPointHeading = (
|
||||
p: GlobalPoint,
|
||||
otherPoint: GlobalPoint,
|
||||
|
@ -2201,9 +2194,12 @@ const getBindPointHeading = (
|
|||
hoveredElement &&
|
||||
aabbForElement(
|
||||
hoveredElement,
|
||||
Array(4).fill(
|
||||
distanceToBindableElement(hoveredElement, p, elementsMap),
|
||||
) as [number, number, number, number],
|
||||
Array(4).fill(distanceToBindableElement(hoveredElement, p)) as [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
],
|
||||
),
|
||||
elementsMap,
|
||||
origPoint,
|
||||
|
|
355
packages/excalidraw/element/utils.ts
Normal file
355
packages/excalidraw/element/utils.ts
Normal file
|
@ -0,0 +1,355 @@
|
|||
import { getDiamondPoints } from ".";
|
||||
import type { Curve, LineSegment } from "../../math";
|
||||
import {
|
||||
curve,
|
||||
lineSegment,
|
||||
pointFrom,
|
||||
pointFromVector,
|
||||
rectangle,
|
||||
vectorFromPoint,
|
||||
vectorNormalize,
|
||||
vectorScale,
|
||||
type GlobalPoint,
|
||||
} from "../../math";
|
||||
import { getCornerRadius } from "../shapes";
|
||||
import type {
|
||||
ExcalidrawDiamondElement,
|
||||
ExcalidrawRectanguloidElement,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Get the building components of a rectanguloid element in the form of
|
||||
* line segments and curves.
|
||||
*
|
||||
* @param element Target rectanguloid element
|
||||
* @param offset Optional offset to expand the rectanguloid shape
|
||||
* @returns Tuple of line segments (0) and curves (1)
|
||||
*/
|
||||
export function deconstructRectanguloidElement(
|
||||
element: ExcalidrawRectanguloidElement,
|
||||
offset: number = 0,
|
||||
): [LineSegment<GlobalPoint>[], Curve<GlobalPoint>[]] {
|
||||
const roundness = getCornerRadius(
|
||||
Math.min(element.width, element.height),
|
||||
element,
|
||||
);
|
||||
|
||||
if (roundness <= 0) {
|
||||
const r = rectangle(
|
||||
pointFrom(element.x - offset, element.y - offset),
|
||||
pointFrom(
|
||||
element.x + element.width + offset,
|
||||
element.y + element.height + offset,
|
||||
),
|
||||
);
|
||||
|
||||
const top = lineSegment<GlobalPoint>(
|
||||
pointFrom<GlobalPoint>(r[0][0] + roundness, r[0][1]),
|
||||
pointFrom<GlobalPoint>(r[1][0] - roundness, r[0][1]),
|
||||
);
|
||||
const right = lineSegment<GlobalPoint>(
|
||||
pointFrom<GlobalPoint>(r[1][0], r[0][1] + roundness),
|
||||
pointFrom<GlobalPoint>(r[1][0], r[1][1] - roundness),
|
||||
);
|
||||
const bottom = lineSegment<GlobalPoint>(
|
||||
pointFrom<GlobalPoint>(r[0][0] + roundness, r[1][1]),
|
||||
pointFrom<GlobalPoint>(r[1][0] - roundness, r[1][1]),
|
||||
);
|
||||
const left = lineSegment<GlobalPoint>(
|
||||
pointFrom<GlobalPoint>(r[0][0], r[1][1] - roundness),
|
||||
pointFrom<GlobalPoint>(r[0][0], r[0][1] + roundness),
|
||||
);
|
||||
const sides = [top, right, bottom, left];
|
||||
|
||||
return [sides, []];
|
||||
}
|
||||
|
||||
const center = pointFrom<GlobalPoint>(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
);
|
||||
|
||||
const r = rectangle(
|
||||
pointFrom(element.x, element.y),
|
||||
pointFrom(element.x + element.width, element.y + element.height),
|
||||
);
|
||||
|
||||
const top = lineSegment<GlobalPoint>(
|
||||
pointFrom<GlobalPoint>(r[0][0] + roundness, r[0][1]),
|
||||
pointFrom<GlobalPoint>(r[1][0] - roundness, r[0][1]),
|
||||
);
|
||||
const right = lineSegment<GlobalPoint>(
|
||||
pointFrom<GlobalPoint>(r[1][0], r[0][1] + roundness),
|
||||
pointFrom<GlobalPoint>(r[1][0], r[1][1] - roundness),
|
||||
);
|
||||
const bottom = lineSegment<GlobalPoint>(
|
||||
pointFrom<GlobalPoint>(r[0][0] + roundness, r[1][1]),
|
||||
pointFrom<GlobalPoint>(r[1][0] - roundness, r[1][1]),
|
||||
);
|
||||
const left = lineSegment<GlobalPoint>(
|
||||
pointFrom<GlobalPoint>(r[0][0], r[1][1] - roundness),
|
||||
pointFrom<GlobalPoint>(r[0][0], r[0][1] + roundness),
|
||||
);
|
||||
|
||||
const offsets = [
|
||||
vectorScale(
|
||||
vectorNormalize(
|
||||
vectorFromPoint(pointFrom(r[0][0] - offset, r[0][1] - offset), center),
|
||||
),
|
||||
offset,
|
||||
), // TOP LEFT
|
||||
vectorScale(
|
||||
vectorNormalize(
|
||||
vectorFromPoint(pointFrom(r[1][0] + offset, r[0][1] - offset), center),
|
||||
),
|
||||
offset,
|
||||
), //TOP RIGHT
|
||||
vectorScale(
|
||||
vectorNormalize(
|
||||
vectorFromPoint(pointFrom(r[1][0] + offset, r[1][1] + offset), center),
|
||||
),
|
||||
offset,
|
||||
), // BOTTOM RIGHT
|
||||
vectorScale(
|
||||
vectorNormalize(
|
||||
vectorFromPoint(pointFrom(r[0][0] - offset, r[1][1] + offset), center),
|
||||
),
|
||||
offset,
|
||||
), // BOTTOM LEFT
|
||||
];
|
||||
|
||||
const corners = [
|
||||
curve(
|
||||
pointFromVector(offsets[0], left[1]),
|
||||
pointFromVector(
|
||||
offsets[0],
|
||||
pointFrom<GlobalPoint>(
|
||||
left[1][0] + (2 / 3) * (r[0][0] - left[1][0]),
|
||||
left[1][1] + (2 / 3) * (r[0][1] - left[1][1]),
|
||||
),
|
||||
),
|
||||
pointFromVector(
|
||||
offsets[0],
|
||||
pointFrom<GlobalPoint>(
|
||||
top[0][0] + (2 / 3) * (r[0][0] - top[0][0]),
|
||||
top[0][1] + (2 / 3) * (r[0][1] - top[0][1]),
|
||||
),
|
||||
),
|
||||
pointFromVector(offsets[0], top[0]),
|
||||
), // TOP LEFT
|
||||
curve(
|
||||
pointFromVector(offsets[1], top[1]),
|
||||
pointFromVector(
|
||||
offsets[1],
|
||||
pointFrom<GlobalPoint>(
|
||||
top[1][0] + (2 / 3) * (r[1][0] - top[1][0]),
|
||||
top[1][1] + (2 / 3) * (r[0][1] - top[1][1]),
|
||||
),
|
||||
),
|
||||
pointFromVector(
|
||||
offsets[1],
|
||||
pointFrom<GlobalPoint>(
|
||||
right[0][0] + (2 / 3) * (r[1][0] - right[0][0]),
|
||||
right[0][1] + (2 / 3) * (r[0][1] - right[0][1]),
|
||||
),
|
||||
),
|
||||
pointFromVector(offsets[1], right[0]),
|
||||
), // TOP RIGHT
|
||||
curve(
|
||||
pointFromVector(offsets[2], right[1]),
|
||||
pointFromVector(
|
||||
offsets[2],
|
||||
pointFrom<GlobalPoint>(
|
||||
right[1][0] + (2 / 3) * (r[1][0] - right[1][0]),
|
||||
right[1][1] + (2 / 3) * (r[1][1] - right[1][1]),
|
||||
),
|
||||
),
|
||||
pointFromVector(
|
||||
offsets[2],
|
||||
pointFrom<GlobalPoint>(
|
||||
bottom[1][0] + (2 / 3) * (r[1][0] - bottom[1][0]),
|
||||
bottom[1][1] + (2 / 3) * (r[1][1] - bottom[1][1]),
|
||||
),
|
||||
),
|
||||
pointFromVector(offsets[2], bottom[1]),
|
||||
), // BOTTOM RIGHT
|
||||
curve(
|
||||
pointFromVector(offsets[3], bottom[0]),
|
||||
pointFromVector(
|
||||
offsets[3],
|
||||
pointFrom<GlobalPoint>(
|
||||
bottom[0][0] + (2 / 3) * (r[0][0] - bottom[0][0]),
|
||||
bottom[0][1] + (2 / 3) * (r[1][1] - bottom[0][1]),
|
||||
),
|
||||
),
|
||||
pointFromVector(
|
||||
offsets[3],
|
||||
pointFrom<GlobalPoint>(
|
||||
left[0][0] + (2 / 3) * (r[0][0] - left[0][0]),
|
||||
left[0][1] + (2 / 3) * (r[1][1] - left[0][1]),
|
||||
),
|
||||
),
|
||||
pointFromVector(offsets[3], left[0]),
|
||||
), // BOTTOM LEFT
|
||||
];
|
||||
|
||||
const sides = [
|
||||
lineSegment<GlobalPoint>(corners[0][3], corners[1][0]),
|
||||
lineSegment<GlobalPoint>(corners[1][3], corners[2][0]),
|
||||
lineSegment<GlobalPoint>(corners[2][3], corners[3][0]),
|
||||
lineSegment<GlobalPoint>(corners[3][3], corners[0][0]),
|
||||
];
|
||||
|
||||
return [sides, corners];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the building components of a diamond element in the form of
|
||||
* line segments and curves as a tuple, in this order.
|
||||
*
|
||||
* @param element The element to deconstruct
|
||||
* @param offset An optional offset
|
||||
* @returns Tuple of line segments (0) and curves (1)
|
||||
*/
|
||||
export function deconstructDiamondElement(
|
||||
element: ExcalidrawDiamondElement,
|
||||
offset: number = 0,
|
||||
): [LineSegment<GlobalPoint>[], Curve<GlobalPoint>[]] {
|
||||
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
||||
getDiamondPoints(element);
|
||||
const verticalRadius = getCornerRadius(Math.abs(topX - leftX), element);
|
||||
const horizontalRadius = getCornerRadius(Math.abs(rightY - topY), element);
|
||||
|
||||
if (element.roundness?.type == null) {
|
||||
const [top, right, bottom, left]: GlobalPoint[] = [
|
||||
pointFrom(element.x + topX, element.y + topY - offset),
|
||||
pointFrom(element.x + rightX + offset, element.y + rightY),
|
||||
pointFrom(element.x + bottomX, element.y + bottomY + offset),
|
||||
pointFrom(element.x + leftX - offset, element.y + leftY),
|
||||
];
|
||||
|
||||
// Create the line segment parts of the diamond
|
||||
// NOTE: Horizontal and vertical seems to be flipped here
|
||||
const topRight = lineSegment<GlobalPoint>(
|
||||
pointFrom(top[0] + verticalRadius, top[1] + horizontalRadius),
|
||||
pointFrom(right[0] - verticalRadius, right[1] - horizontalRadius),
|
||||
);
|
||||
const bottomRight = lineSegment<GlobalPoint>(
|
||||
pointFrom(right[0] - verticalRadius, right[1] + horizontalRadius),
|
||||
pointFrom(bottom[0] + verticalRadius, bottom[1] - horizontalRadius),
|
||||
);
|
||||
const bottomLeft = lineSegment<GlobalPoint>(
|
||||
pointFrom(bottom[0] - verticalRadius, bottom[1] - horizontalRadius),
|
||||
pointFrom(left[0] + verticalRadius, left[1] + horizontalRadius),
|
||||
);
|
||||
const topLeft = lineSegment<GlobalPoint>(
|
||||
pointFrom(left[0] + verticalRadius, left[1] - horizontalRadius),
|
||||
pointFrom(top[0] - verticalRadius, top[1] + horizontalRadius),
|
||||
);
|
||||
|
||||
return [[topRight, bottomRight, bottomLeft, topLeft], []];
|
||||
}
|
||||
|
||||
const center = pointFrom<GlobalPoint>(
|
||||
element.x + element.width / 2,
|
||||
element.y + element.height / 2,
|
||||
);
|
||||
|
||||
const [top, right, bottom, left]: GlobalPoint[] = [
|
||||
pointFrom(element.x + topX, element.y + topY),
|
||||
pointFrom(element.x + rightX, element.y + rightY),
|
||||
pointFrom(element.x + bottomX, element.y + bottomY),
|
||||
pointFrom(element.x + leftX, element.y + leftY),
|
||||
];
|
||||
|
||||
const offsets = [
|
||||
vectorScale(vectorNormalize(vectorFromPoint(right, center)), offset), // RIGHT
|
||||
vectorScale(vectorNormalize(vectorFromPoint(bottom, center)), offset), // BOTTOM
|
||||
vectorScale(vectorNormalize(vectorFromPoint(left, center)), offset), // LEFT
|
||||
vectorScale(vectorNormalize(vectorFromPoint(top, center)), offset), // TOP
|
||||
];
|
||||
|
||||
const corners = [
|
||||
curve(
|
||||
pointFromVector(
|
||||
offsets[0],
|
||||
pointFrom<GlobalPoint>(
|
||||
right[0] - verticalRadius,
|
||||
right[1] - horizontalRadius,
|
||||
),
|
||||
),
|
||||
pointFromVector(offsets[0], right),
|
||||
pointFromVector(offsets[0], right),
|
||||
pointFromVector(
|
||||
offsets[0],
|
||||
pointFrom<GlobalPoint>(
|
||||
right[0] - verticalRadius,
|
||||
right[1] + horizontalRadius,
|
||||
),
|
||||
),
|
||||
), // RIGHT
|
||||
curve(
|
||||
pointFromVector(
|
||||
offsets[1],
|
||||
pointFrom<GlobalPoint>(
|
||||
bottom[0] + verticalRadius,
|
||||
bottom[1] - horizontalRadius,
|
||||
),
|
||||
),
|
||||
pointFromVector(offsets[1], bottom),
|
||||
pointFromVector(offsets[1], bottom),
|
||||
pointFromVector(
|
||||
offsets[1],
|
||||
pointFrom<GlobalPoint>(
|
||||
bottom[0] - verticalRadius,
|
||||
bottom[1] - horizontalRadius,
|
||||
),
|
||||
),
|
||||
), // BOTTOM
|
||||
curve(
|
||||
pointFromVector(
|
||||
offsets[2],
|
||||
pointFrom<GlobalPoint>(
|
||||
left[0] + verticalRadius,
|
||||
left[1] + horizontalRadius,
|
||||
),
|
||||
),
|
||||
pointFromVector(offsets[2], left),
|
||||
pointFromVector(offsets[2], left),
|
||||
pointFromVector(
|
||||
offsets[2],
|
||||
pointFrom<GlobalPoint>(
|
||||
left[0] + verticalRadius,
|
||||
left[1] - horizontalRadius,
|
||||
),
|
||||
),
|
||||
), // LEFT
|
||||
curve(
|
||||
pointFromVector(
|
||||
offsets[3],
|
||||
pointFrom<GlobalPoint>(
|
||||
top[0] - verticalRadius,
|
||||
top[1] + horizontalRadius,
|
||||
),
|
||||
),
|
||||
pointFromVector(offsets[3], top),
|
||||
pointFromVector(offsets[3], top),
|
||||
pointFromVector(
|
||||
offsets[3],
|
||||
pointFrom<GlobalPoint>(
|
||||
top[0] + verticalRadius,
|
||||
top[1] + horizontalRadius,
|
||||
),
|
||||
),
|
||||
), // TOP
|
||||
];
|
||||
|
||||
const sides = [
|
||||
lineSegment<GlobalPoint>(corners[0][3], corners[1][0]),
|
||||
lineSegment<GlobalPoint>(corners[1][3], corners[2][0]),
|
||||
lineSegment<GlobalPoint>(corners[2][3], corners[3][0]),
|
||||
lineSegment<GlobalPoint>(corners[3][3], corners[0][0]),
|
||||
];
|
||||
|
||||
return [sides, corners];
|
||||
}
|
|
@ -197,7 +197,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 99,
|
||||
"height": "102.35417",
|
||||
"id": "id172",
|
||||
"index": "a2",
|
||||
"isDeleted": false,
|
||||
|
@ -211,8 +211,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
0,
|
||||
],
|
||||
[
|
||||
"98.20800",
|
||||
99,
|
||||
"101.77517",
|
||||
"102.35417",
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
|
@ -227,8 +227,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 40,
|
||||
"width": "98.20800",
|
||||
"x": 1,
|
||||
"width": "101.77517",
|
||||
"x": "0.70711",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
@ -294,24 +294,22 @@ History {
|
|||
"deleted": {
|
||||
"endBinding": {
|
||||
"elementId": "id171",
|
||||
"fixedPoint": null,
|
||||
"focus": "0.00990",
|
||||
"gap": 1,
|
||||
},
|
||||
"height": "0.98017",
|
||||
"height": "0.98597",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
98,
|
||||
"-0.98017",
|
||||
"98.58579",
|
||||
"-0.98597",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id170",
|
||||
"fixedPoint": null,
|
||||
"focus": "0.02970",
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -319,24 +317,22 @@ History {
|
|||
"inserted": {
|
||||
"endBinding": {
|
||||
"elementId": "id171",
|
||||
"fixedPoint": null,
|
||||
"focus": "-0.02000",
|
||||
"gap": 1,
|
||||
},
|
||||
"height": "0.00169",
|
||||
"height": "0.00119",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
98,
|
||||
"0.00169",
|
||||
"98.58579",
|
||||
"0.00119",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id170",
|
||||
"fixedPoint": null,
|
||||
"focus": "0.02000",
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -393,15 +389,15 @@ History {
|
|||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
"height": 99,
|
||||
"height": "102.35417",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
"98.20800",
|
||||
99,
|
||||
"101.77517",
|
||||
"102.35417",
|
||||
],
|
||||
],
|
||||
"startBinding": null,
|
||||
|
@ -410,28 +406,26 @@ History {
|
|||
"inserted": {
|
||||
"endBinding": {
|
||||
"elementId": "id171",
|
||||
"fixedPoint": null,
|
||||
"focus": "0.00990",
|
||||
"gap": 1,
|
||||
},
|
||||
"height": "0.98161",
|
||||
"height": "0.98700",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
98,
|
||||
"-0.98161",
|
||||
"98.58579",
|
||||
"-0.98700",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id170",
|
||||
"fixedPoint": null,
|
||||
"focus": "0.02970",
|
||||
"gap": 1,
|
||||
},
|
||||
"y": "0.99245",
|
||||
"y": "0.99465",
|
||||
},
|
||||
},
|
||||
"id175" => Delta {
|
||||
|
@ -824,7 +818,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 30,
|
||||
"width": 0,
|
||||
"width": 50,
|
||||
"x": 200,
|
||||
"y": 0,
|
||||
}
|
||||
|
@ -858,7 +852,7 @@ History {
|
|||
0,
|
||||
],
|
||||
[
|
||||
0,
|
||||
50,
|
||||
0,
|
||||
],
|
||||
],
|
||||
|
@ -934,8 +928,7 @@ History {
|
|||
"inserted": {
|
||||
"endBinding": {
|
||||
"elementId": "id166",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"points": [
|
||||
|
@ -944,13 +937,12 @@ History {
|
|||
0,
|
||||
],
|
||||
[
|
||||
0,
|
||||
50,
|
||||
0,
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id165",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -1246,7 +1238,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": "2.61991",
|
||||
"height": "2.52823",
|
||||
"id": "id178",
|
||||
"index": "Zz",
|
||||
"isDeleted": false,
|
||||
|
@ -1260,8 +1252,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
0,
|
||||
],
|
||||
[
|
||||
"98.00000",
|
||||
"-2.61991",
|
||||
"98.58579",
|
||||
"-2.52823",
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
|
@ -1284,9 +1276,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 11,
|
||||
"width": "98.00000",
|
||||
"x": "1.00000",
|
||||
"y": "3.98333",
|
||||
"width": "98.58579",
|
||||
"x": "0.70711",
|
||||
"y": "3.82861",
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -1617,7 +1609,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": "2.61991",
|
||||
"height": "2.52823",
|
||||
"id": "id181",
|
||||
"index": "a0",
|
||||
"isDeleted": false,
|
||||
|
@ -1631,8 +1623,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
0,
|
||||
],
|
||||
[
|
||||
"98.00000",
|
||||
"-2.61991",
|
||||
"98.58579",
|
||||
"-2.52823",
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
|
@ -1655,9 +1647,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 11,
|
||||
"width": "98.00000",
|
||||
"x": "1.00000",
|
||||
"y": "3.98333",
|
||||
"width": "98.58579",
|
||||
"x": "0.70711",
|
||||
"y": "3.82861",
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -1775,7 +1767,7 @@ History {
|
|||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": "22.36242",
|
||||
"height": "22.07000",
|
||||
"index": "a0",
|
||||
"isDeleted": false,
|
||||
"lastCommittedPoint": null,
|
||||
|
@ -1788,8 +1780,8 @@ History {
|
|||
0,
|
||||
],
|
||||
[
|
||||
"98.00000",
|
||||
"-22.36242",
|
||||
"99.27949",
|
||||
"-22.07000",
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
|
@ -1810,9 +1802,9 @@ History {
|
|||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"y": 34,
|
||||
"width": "99.27949",
|
||||
"x": "0.01341",
|
||||
"y": "33.34227",
|
||||
},
|
||||
"inserted": {
|
||||
"isDeleted": true,
|
||||
|
@ -2322,14 +2314,13 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id185",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": "408.19672",
|
||||
"height": "410.63965",
|
||||
"id": "id186",
|
||||
"index": "a2",
|
||||
"isDeleted": false,
|
||||
|
@ -2343,8 +2334,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
0,
|
||||
],
|
||||
[
|
||||
498,
|
||||
"-408.19672",
|
||||
"501.24760",
|
||||
"-410.63965",
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
|
@ -2354,7 +2345,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id184",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -2364,8 +2354,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"width": 498,
|
||||
"x": 1,
|
||||
"width": "501.24760",
|
||||
"x": "0.70711",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
@ -2484,8 +2474,7 @@ History {
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id185",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -2515,7 +2504,6 @@ History {
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id184",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -15122,8 +15110,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id58",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -15143,7 +15130,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
0,
|
||||
],
|
||||
[
|
||||
"98.00000",
|
||||
"98.58579",
|
||||
0,
|
||||
],
|
||||
],
|
||||
|
@ -15154,7 +15141,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id56",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -15164,8 +15150,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"width": "98.58579",
|
||||
"x": "0.70711",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
@ -15493,8 +15479,7 @@ History {
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id58",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -15524,7 +15509,6 @@ History {
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id56",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -15821,8 +15805,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id52",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -15842,7 +15825,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
0,
|
||||
],
|
||||
[
|
||||
"98.00000",
|
||||
"98.58579",
|
||||
0,
|
||||
],
|
||||
],
|
||||
|
@ -15853,7 +15836,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id50",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -15863,8 +15845,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"width": "98.58579",
|
||||
"x": "0.70711",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
@ -16116,8 +16098,7 @@ History {
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id52",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -16147,7 +16128,6 @@ History {
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id50",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -16444,8 +16424,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id64",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -16465,7 +16444,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
0,
|
||||
],
|
||||
[
|
||||
"98.00000",
|
||||
"98.58579",
|
||||
0,
|
||||
],
|
||||
],
|
||||
|
@ -16476,7 +16455,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id62",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -16486,8 +16464,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"width": "98.58579",
|
||||
"x": "0.70711",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
@ -16739,8 +16717,7 @@ History {
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id64",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -16770,7 +16747,6 @@ History {
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id62",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -17065,8 +17041,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id70",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -17086,7 +17061,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
0,
|
||||
],
|
||||
[
|
||||
"98.00000",
|
||||
"98.58579",
|
||||
0,
|
||||
],
|
||||
],
|
||||
|
@ -17097,7 +17072,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id68",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -17107,8 +17081,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"width": "98.58579",
|
||||
"x": "0.70711",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
@ -17170,7 +17144,6 @@ History {
|
|||
],
|
||||
"startBinding": {
|
||||
"elementId": "id68",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -17431,8 +17404,7 @@ History {
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id70",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -17462,7 +17434,6 @@ History {
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id68",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -17783,8 +17754,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id77",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -17804,7 +17774,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
0,
|
||||
],
|
||||
[
|
||||
"98.00000",
|
||||
"98.58579",
|
||||
0,
|
||||
],
|
||||
],
|
||||
|
@ -17815,7 +17785,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id75",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -17825,8 +17794,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 11,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"width": "98.58579",
|
||||
"x": "0.70711",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
@ -17887,8 +17856,7 @@ History {
|
|||
"deleted": {
|
||||
"endBinding": {
|
||||
"elementId": "id77",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"points": [
|
||||
|
@ -17903,7 +17871,6 @@ History {
|
|||
],
|
||||
"startBinding": {
|
||||
"elementId": "id75",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
@ -18165,8 +18132,7 @@ History {
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id77",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": -0,
|
||||
"gap": 1,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
|
@ -18196,7 +18162,6 @@ History {
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id75",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
|
|
|
@ -190,14 +190,13 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||
"endArrowhead": "arrow",
|
||||
"endBinding": {
|
||||
"elementId": "id1",
|
||||
"fixedPoint": null,
|
||||
"focus": "-0.46667",
|
||||
"gap": 10,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": "81.47368",
|
||||
"height": "84.41974",
|
||||
"id": "id2",
|
||||
"index": "a2",
|
||||
"isDeleted": false,
|
||||
|
@ -211,8 +210,8 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||
0,
|
||||
],
|
||||
[
|
||||
81,
|
||||
"81.47368",
|
||||
"83.92893",
|
||||
"84.41974",
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
|
@ -223,7 +222,6 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": null,
|
||||
"focus": "-0.60000",
|
||||
"gap": 10,
|
||||
},
|
||||
|
@ -234,7 +232,7 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||
"updated": 1,
|
||||
"version": 11,
|
||||
"versionNonce": 1051383431,
|
||||
"width": 81,
|
||||
"width": "83.92893",
|
||||
"x": 110,
|
||||
"y": 50,
|
||||
}
|
||||
|
|
|
@ -64,7 +64,6 @@ describe("element binding", () => {
|
|||
|
||||
expect(arrow.startBinding).toEqual({
|
||||
elementId: rect.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
|
@ -77,13 +76,11 @@ describe("element binding", () => {
|
|||
// Both the start and the end points should be bound
|
||||
expect(arrow.startBinding).toEqual({
|
||||
elementId: rect.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
expect(arrow.endBinding).toEqual({
|
||||
elementId: rect.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
togglePopover,
|
||||
getCloneByOrigId,
|
||||
} from "./test-utils";
|
||||
import "../../utils/test-utils";
|
||||
import { Excalidraw } from "../index";
|
||||
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||
import { API } from "./helpers/api";
|
||||
|
@ -1321,13 +1322,11 @@ describe("history", () => {
|
|||
expect(API.getUndoStack().length).toBe(5);
|
||||
expect(arrow.startBinding).toEqual({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
expect(arrow.endBinding).toEqual({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
|
@ -1346,13 +1345,11 @@ describe("history", () => {
|
|||
expect(API.getRedoStack().length).toBe(1);
|
||||
expect(arrow.startBinding).toEqual({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
expect(arrow.endBinding).toEqual({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
|
@ -1371,13 +1368,11 @@ describe("history", () => {
|
|||
expect(API.getRedoStack().length).toBe(0);
|
||||
expect(arrow.startBinding).toEqual({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
expect(arrow.endBinding).toEqual({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
|
@ -1404,13 +1399,11 @@ describe("history", () => {
|
|||
expect(API.getRedoStack().length).toBe(0);
|
||||
expect(arrow.startBinding).toEqual({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
expect(arrow.endBinding).toEqual({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
|
@ -1429,13 +1422,11 @@ describe("history", () => {
|
|||
expect(API.getRedoStack().length).toBe(1);
|
||||
expect(arrow.startBinding).toEqual({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
expect(arrow.endBinding).toEqual({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
|
@ -1486,13 +1477,11 @@ describe("history", () => {
|
|||
id: arrow.id,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
|
@ -1533,13 +1522,11 @@ describe("history", () => {
|
|||
id: arrow.id,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
|
@ -1614,13 +1601,11 @@ describe("history", () => {
|
|||
id: arrow.id,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
|
@ -1689,13 +1674,11 @@ describe("history", () => {
|
|||
id: arrow.id,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
|
@ -4276,13 +4259,11 @@ describe("history", () => {
|
|||
id: arrowId,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
|
@ -4347,13 +4328,11 @@ describe("history", () => {
|
|||
id: arrowId,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
|
@ -4414,13 +4393,11 @@ describe("history", () => {
|
|||
id: arrowId,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
|
@ -4489,14 +4466,12 @@ describe("history", () => {
|
|||
id: arrowId,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
// rebound with previous rectangle
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
|
@ -4788,14 +4763,12 @@ describe("history", () => {
|
|||
id: arrowId,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: 0,
|
||||
gap: 1,
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: 0,
|
||||
focus: -0,
|
||||
gap: 1,
|
||||
}),
|
||||
isDeleted: true,
|
||||
|
@ -4838,13 +4811,11 @@ describe("history", () => {
|
|||
id: arrowId,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
fixedPoint: null,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
}),
|
||||
|
|
|
@ -1238,7 +1238,7 @@ describe("Test Linear Elements", () => {
|
|||
mouse.downAt(rect.x, rect.y);
|
||||
mouse.moveTo(200, 0);
|
||||
mouse.upAt(200, 0);
|
||||
expect(arrow.width).toBe(200);
|
||||
expect(arrow.width).toBeCloseTo(204, 0);
|
||||
expect(rect.x).toBe(200);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(handleBindTextResizeSpy).toHaveBeenCalledWith(
|
||||
|
|
|
@ -123,7 +123,7 @@ describe("move element", () => {
|
|||
expect([rectB.x, rectB.y]).toEqual([201, 2]);
|
||||
expect([Math.round(arrow.x), Math.round(arrow.y)]).toEqual([110, 50]);
|
||||
expect([Math.round(arrow.width), Math.round(arrow.height)]).toEqual([
|
||||
81, 81,
|
||||
84, 84,
|
||||
]);
|
||||
|
||||
h.elements.forEach((element) => expect(element).toMatchSnapshot());
|
||||
|
|
|
@ -181,12 +181,12 @@ describe("generic element", () => {
|
|||
|
||||
UI.resize(rectangle, "e", [40, 0]);
|
||||
|
||||
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(30);
|
||||
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(30, 0);
|
||||
|
||||
UI.resize(rectangle, "w", [50, 0]);
|
||||
|
||||
expect(arrow.endBinding?.elementId).toEqual(rectangle.id);
|
||||
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(80);
|
||||
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(80, 0);
|
||||
});
|
||||
|
||||
it("resizes with a label", async () => {
|
||||
|
@ -501,12 +501,12 @@ describe("arrow element", () => {
|
|||
h.state,
|
||||
)[0] as ExcalidrawElbowArrowElement;
|
||||
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05);
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1);
|
||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75);
|
||||
|
||||
UI.resize(rectangle, "se", [-200, -150]);
|
||||
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05);
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1);
|
||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75);
|
||||
});
|
||||
|
||||
|
@ -529,13 +529,13 @@ describe("arrow element", () => {
|
|||
h.state,
|
||||
)[0] as ExcalidrawElbowArrowElement;
|
||||
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05);
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1);
|
||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75);
|
||||
|
||||
UI.resize([rectangle, arrow], "nw", [300, 350]);
|
||||
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(-0.144);
|
||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.25);
|
||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(-0.13);
|
||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.11);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -811,15 +811,16 @@ describe("image element", () => {
|
|||
|
||||
UI.resize(image, "ne", [40, 0]);
|
||||
|
||||
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(30);
|
||||
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(31, 0);
|
||||
|
||||
const imageWidth = image.width;
|
||||
const scale = 20 / image.height;
|
||||
UI.resize(image, "nw", [50, 20]);
|
||||
|
||||
expect(arrow.endBinding?.elementId).toEqual(image.id);
|
||||
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(
|
||||
expect(Math.floor(arrow.width + arrow.endBinding!.gap)).toBeCloseTo(
|
||||
30 + imageWidth * scale,
|
||||
0,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1024,7 +1025,7 @@ describe("multiple selection", () => {
|
|||
|
||||
expect(leftBoundArrow.x).toBeCloseTo(-110);
|
||||
expect(leftBoundArrow.y).toBeCloseTo(50);
|
||||
expect(leftBoundArrow.width).toBeCloseTo(140, 0);
|
||||
expect(leftBoundArrow.width).toBeCloseTo(143, 0);
|
||||
expect(leftBoundArrow.height).toBeCloseTo(7, 0);
|
||||
expect(leftBoundArrow.angle).toEqual(0);
|
||||
expect(leftBoundArrow.startBinding).toBeNull();
|
||||
|
@ -1046,7 +1047,9 @@ describe("multiple selection", () => {
|
|||
expect(rightBoundArrow.endBinding?.elementId).toBe(
|
||||
rightArrowBinding.elementId,
|
||||
);
|
||||
expect(rightBoundArrow.endBinding?.focus).toBe(rightArrowBinding.focus);
|
||||
expect(rightBoundArrow.endBinding?.focus).toBeCloseTo(
|
||||
rightArrowBinding.focus!,
|
||||
);
|
||||
});
|
||||
|
||||
it("resizes with labeled arrows", async () => {
|
||||
|
|
|
@ -32,7 +32,7 @@ test("unselected bound arrow updates when rotating its target element", async ()
|
|||
expect(arrow.endBinding?.elementId).toEqual(rectangle.id);
|
||||
expect(arrow.x).toBeCloseTo(-80);
|
||||
expect(arrow.y).toBeCloseTo(50);
|
||||
expect(arrow.width).toBeCloseTo(110.7, 1);
|
||||
expect(arrow.width).toBeCloseTo(116.7, 1);
|
||||
expect(arrow.height).toBeCloseTo(0);
|
||||
});
|
||||
|
||||
|
@ -69,8 +69,8 @@ test("unselected bound arrows update when rotating their target elements", async
|
|||
expect(ellipseArrow.x).toEqual(0);
|
||||
expect(ellipseArrow.y).toEqual(0);
|
||||
expect(ellipseArrow.points[0]).toEqual([0, 0]);
|
||||
expect(ellipseArrow.points[1][0]).toBeCloseTo(48.5, 1);
|
||||
expect(ellipseArrow.points[1][1]).toBeCloseTo(126.5, 1);
|
||||
expect(ellipseArrow.points[1][0]).toBeCloseTo(48.98, 1);
|
||||
expect(ellipseArrow.points[1][1]).toBeCloseTo(125.79, 1);
|
||||
|
||||
expect(textArrow.endBinding?.elementId).toEqual(text.id);
|
||||
expect(textArrow.x).toEqual(360);
|
||||
|
|
|
@ -16,7 +16,6 @@ import { STORAGE_KEYS } from "../../../excalidraw-app/app_constants";
|
|||
import { getSelectedElements } from "../scene/selection";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import { UI } from "./helpers/ui";
|
||||
import { diffStringsUnified } from "jest-diff";
|
||||
import ansi from "ansicolor";
|
||||
import { ORIG_ID } from "../constants";
|
||||
import { arrayToMap } from "../utils";
|
||||
|
@ -259,36 +258,6 @@ expect.extend({
|
|||
pass: false,
|
||||
};
|
||||
},
|
||||
|
||||
toCloselyEqualPoints(received, expected, precision) {
|
||||
if (!Array.isArray(received) || !Array.isArray(expected)) {
|
||||
throw new Error("expected and received are not point arrays");
|
||||
}
|
||||
|
||||
const COMPARE = 1 / Math.pow(10, precision || 2);
|
||||
const pass = received.every(
|
||||
(point, idx) =>
|
||||
Math.abs(expected[idx]?.[0] - point[0]) < COMPARE &&
|
||||
Math.abs(expected[idx]?.[1] - point[1]) < COMPARE,
|
||||
);
|
||||
|
||||
if (!pass) {
|
||||
return {
|
||||
message: () => ` The provided array of points are not close enough.
|
||||
|
||||
${diffStringsUnified(
|
||||
JSON.stringify(expected, undefined, 2),
|
||||
JSON.stringify(received, undefined, 2),
|
||||
)}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
message: () => `expected ${received} to not be close to ${expected}`,
|
||||
pass: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { Curve } from "../math";
|
||||
import {
|
||||
isLineSegment,
|
||||
lineSegment,
|
||||
|
@ -6,7 +7,7 @@ import {
|
|||
type LocalPoint,
|
||||
} from "../math";
|
||||
import type { LineSegment } from "../utils";
|
||||
import type { BoundingBox, Bounds } from "./element/bounds";
|
||||
import type { Bounds } from "./element/bounds";
|
||||
import { isBounds } from "./element/typeChecks";
|
||||
|
||||
// The global data holder to collect the debug operations
|
||||
|
@ -16,17 +17,29 @@ declare global {
|
|||
data: DebugElement[][];
|
||||
currentFrame?: number;
|
||||
};
|
||||
debugDrawPoint: typeof debugDrawPoint;
|
||||
debugDrawLine: typeof debugDrawLine;
|
||||
}
|
||||
}
|
||||
|
||||
export type DebugElement = {
|
||||
color: string;
|
||||
data: LineSegment<GlobalPoint>;
|
||||
data: LineSegment<GlobalPoint> | Curve<GlobalPoint>;
|
||||
permanent: boolean;
|
||||
};
|
||||
|
||||
export const debugDrawCubicBezier = (
|
||||
c: Curve<GlobalPoint>,
|
||||
opts?: {
|
||||
color?: string;
|
||||
permanent?: boolean;
|
||||
},
|
||||
) => {
|
||||
addToCurrentFrame({
|
||||
color: opts?.color ?? "purple",
|
||||
permanent: !!opts?.permanent,
|
||||
data: c,
|
||||
});
|
||||
};
|
||||
|
||||
export const debugDrawLine = (
|
||||
segment: LineSegment<GlobalPoint> | LineSegment<GlobalPoint>[],
|
||||
opts?: {
|
||||
|
@ -80,41 +93,6 @@ export const debugDrawPoint = (
|
|||
);
|
||||
};
|
||||
|
||||
export const debugDrawBoundingBox = (
|
||||
box: BoundingBox | BoundingBox[],
|
||||
opts?: {
|
||||
color?: string;
|
||||
permanent?: boolean;
|
||||
},
|
||||
) => {
|
||||
(Array.isArray(box) ? box : [box]).forEach((bbox) =>
|
||||
debugDrawLine(
|
||||
[
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(bbox.minX, bbox.minY),
|
||||
pointFrom<GlobalPoint>(bbox.maxX, bbox.minY),
|
||||
),
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(bbox.maxX, bbox.minY),
|
||||
pointFrom<GlobalPoint>(bbox.maxX, bbox.maxY),
|
||||
),
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(bbox.maxX, bbox.maxY),
|
||||
pointFrom<GlobalPoint>(bbox.minX, bbox.maxY),
|
||||
),
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(bbox.minX, bbox.maxY),
|
||||
pointFrom<GlobalPoint>(bbox.minX, bbox.minY),
|
||||
),
|
||||
],
|
||||
{
|
||||
color: opts?.color ?? "cyan",
|
||||
permanent: opts?.permanent,
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const debugDrawBounds = (
|
||||
box: Bounds | Bounds[],
|
||||
opts?: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue