mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Revert to master
This commit is contained in:
parent
5947af5b50
commit
b33cc74183
25 changed files with 410 additions and 577 deletions
|
@ -13,8 +13,6 @@ import { useCallback, useImperativeHandle, useRef } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isLineSegment,
|
isLineSegment,
|
||||||
isCurve,
|
|
||||||
type Curve,
|
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
type LineSegment,
|
type LineSegment,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
|
@ -462,10 +462,23 @@ export const maybeBindLinearElement = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizePointBinding = (binding: { focus: number; gap: number }) => {
|
const normalizePointBinding = (
|
||||||
|
binding: { focus: number; gap: number },
|
||||||
|
hoveredElement: ExcalidrawBindableElement,
|
||||||
|
) => {
|
||||||
|
let gap = binding.gap;
|
||||||
|
const maxGap = maxBindingGap(
|
||||||
|
hoveredElement,
|
||||||
|
hoveredElement.width,
|
||||||
|
hoveredElement.height,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (gap > maxGap) {
|
||||||
|
gap = BINDING_HIGHLIGHT_THICKNESS + BINDING_HIGHLIGHT_OFFSET;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...binding,
|
...binding,
|
||||||
gap: FIXED_BINDING_DISTANCE,
|
gap,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -715,7 +728,7 @@ const calculateFocusAndGap = (
|
||||||
|
|
||||||
return {
|
return {
|
||||||
focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint),
|
focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint),
|
||||||
gap: FIXED_BINDING_DISTANCE,
|
gap: Math.max(1, distanceToBindableElement(hoveredElement, edgePoint)),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -733,7 +746,7 @@ export const updateBoundElements = (
|
||||||
changedElements?: Map<string, OrderedExcalidrawElement>;
|
changedElements?: Map<string, OrderedExcalidrawElement>;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const { simultaneouslyUpdated } = options ?? {};
|
const { newSize, simultaneouslyUpdated } = options ?? {};
|
||||||
const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
|
const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
|
||||||
simultaneouslyUpdated,
|
simultaneouslyUpdated,
|
||||||
);
|
);
|
||||||
|
@ -766,13 +779,23 @@ export const updateBoundElements = (
|
||||||
startBounds = getElementBounds(startBindingElement, elementsMap);
|
startBounds = getElementBounds(startBindingElement, elementsMap);
|
||||||
endBounds = getElementBounds(endBindingElement, elementsMap);
|
endBounds = getElementBounds(endBindingElement, elementsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bindings = {
|
||||||
|
startBinding: maybeCalculateNewGapWhenScaling(
|
||||||
|
changedElement,
|
||||||
|
element.startBinding,
|
||||||
|
newSize,
|
||||||
|
),
|
||||||
|
endBinding: maybeCalculateNewGapWhenScaling(
|
||||||
|
changedElement,
|
||||||
|
element.endBinding,
|
||||||
|
newSize,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
// `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(
|
mutateElement(element, bindings, true);
|
||||||
element,
|
|
||||||
{ startBinding: element.startBinding, endBinding: element.endBinding },
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -794,9 +817,7 @@ export const updateBoundElements = (
|
||||||
const point = updateBoundPoint(
|
const point = updateBoundPoint(
|
||||||
element,
|
element,
|
||||||
bindingProp,
|
bindingProp,
|
||||||
bindingProp === "startBinding"
|
bindings[bindingProp],
|
||||||
? element.startBinding
|
|
||||||
: element.endBinding,
|
|
||||||
bindableElement,
|
bindableElement,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
@ -916,7 +937,6 @@ export const bindPointToSnapToElementOutline = (
|
||||||
arrow: ExcalidrawElbowArrowElement,
|
arrow: ExcalidrawElbowArrowElement,
|
||||||
bindableElement: ExcalidrawBindableElement,
|
bindableElement: ExcalidrawBindableElement,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
elementsMap: ElementsMap,
|
|
||||||
): GlobalPoint => {
|
): GlobalPoint => {
|
||||||
if (isDevEnv() || isTestEnv()) {
|
if (isDevEnv() || isTestEnv()) {
|
||||||
invariant(arrow.points.length > 1, "Arrow should have at least 2 points");
|
invariant(arrow.points.length > 1, "Arrow should have at least 2 points");
|
||||||
|
@ -924,12 +944,10 @@ export const bindPointToSnapToElementOutline = (
|
||||||
|
|
||||||
const aabb = aabbForElement(bindableElement);
|
const aabb = aabbForElement(bindableElement);
|
||||||
const localP =
|
const localP =
|
||||||
linearElement.points[
|
arrow.points[startOrEnd === "start" ? 0 : arrow.points.length - 1];
|
||||||
startOrEnd === "start" ? 0 : linearElement.points.length - 1
|
|
||||||
];
|
|
||||||
const globalP = pointFrom<GlobalPoint>(
|
const globalP = pointFrom<GlobalPoint>(
|
||||||
linearElement.x + localP[0],
|
arrow.x + localP[0],
|
||||||
linearElement.y + localP[1],
|
arrow.y + localP[1],
|
||||||
);
|
);
|
||||||
const edgePoint = isRectanguloidElement(bindableElement)
|
const edgePoint = isRectanguloidElement(bindableElement)
|
||||||
? avoidRectangularCorner(bindableElement, globalP)
|
? avoidRectangularCorner(bindableElement, globalP)
|
||||||
|
@ -966,11 +984,7 @@ export const bindPointToSnapToElementOutline = (
|
||||||
),
|
),
|
||||||
otherPoint,
|
otherPoint,
|
||||||
),
|
),
|
||||||
adjacentPoint,
|
|
||||||
),
|
),
|
||||||
).sort(
|
|
||||||
(g, h) =>
|
|
||||||
pointDistanceSq(g, adjacentPoint) - pointDistanceSq(h, adjacentPoint),
|
|
||||||
)[0];
|
)[0];
|
||||||
} else {
|
} else {
|
||||||
intersection = intersectElementWithLineSegment(
|
intersection = intersectElementWithLineSegment(
|
||||||
|
@ -1018,20 +1032,6 @@ export const bindPointToSnapToElementOutline = (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentDistance = pointDistance(edgePoint, center);
|
|
||||||
const fullDistance = Math.max(
|
|
||||||
pointDistance(intersection ?? edgePoint, center),
|
|
||||||
1e-5, // Avoid division by zero
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isInside) {
|
|
||||||
return intersection;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elbowed) {
|
|
||||||
return headingToMidBindPoint(edgePoint, bindableElement, aabb);
|
|
||||||
}
|
|
||||||
|
|
||||||
return edgePoint;
|
return edgePoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1039,7 +1039,10 @@ export const avoidRectangularCorner = (
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
p: GlobalPoint,
|
p: GlobalPoint,
|
||||||
): GlobalPoint => {
|
): GlobalPoint => {
|
||||||
const center = elementCenterPoint(element);
|
const center = pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
);
|
||||||
const nonRotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
const nonRotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
||||||
|
|
||||||
if (nonRotatedPoint[0] < element.x && nonRotatedPoint[1] < element.y) {
|
if (nonRotatedPoint[0] < element.x && nonRotatedPoint[1] < element.y) {
|
||||||
|
@ -1309,24 +1312,6 @@ const updateBoundPoint = (
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// debugClear();
|
|
||||||
// intersections.forEach((intersection) => {
|
|
||||||
// debugDrawPoint(intersection, { permanent: true, color: "red" });
|
|
||||||
// });
|
|
||||||
// debugDrawLine(
|
|
||||||
// lineSegment<GlobalPoint>(
|
|
||||||
// adjacentPoint,
|
|
||||||
// pointFromVector(
|
|
||||||
// vectorScale(
|
|
||||||
// vectorNormalize(vectorFromPoint(focusPointAbsolute, adjacentPoint)),
|
|
||||||
// interceptorLength,
|
|
||||||
// ),
|
|
||||||
// adjacentPoint,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// { permanent: true, color: "green" },
|
|
||||||
// );
|
|
||||||
|
|
||||||
if (intersections.length > 1) {
|
if (intersections.length > 1) {
|
||||||
// The adjacent point is outside the shape (+ gap)
|
// The adjacent point is outside the shape (+ gap)
|
||||||
newEdgePoint = intersections[0];
|
newEdgePoint = intersections[0];
|
||||||
|
@ -1362,7 +1347,6 @@ export const calculateFixedPointForElbowArrowBinding = (
|
||||||
linearElement,
|
linearElement,
|
||||||
hoveredElement,
|
hoveredElement,
|
||||||
startOrEnd,
|
startOrEnd,
|
||||||
elementsMap,
|
|
||||||
);
|
);
|
||||||
const globalMidPoint = pointFrom(
|
const globalMidPoint = pointFrom(
|
||||||
bounds[0] + (bounds[2] - bounds[0]) / 2,
|
bounds[0] + (bounds[2] - bounds[0]) / 2,
|
||||||
|
@ -1384,6 +1368,28 @@ export const calculateFixedPointForElbowArrowBinding = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const maybeCalculateNewGapWhenScaling = (
|
||||||
|
changedElement: ExcalidrawBindableElement,
|
||||||
|
currentBinding: PointBinding | null | undefined,
|
||||||
|
newSize: { width: number; height: number } | undefined,
|
||||||
|
): PointBinding | null | undefined => {
|
||||||
|
if (currentBinding == null || newSize == null) {
|
||||||
|
return currentBinding;
|
||||||
|
}
|
||||||
|
const { width: newWidth, height: newHeight } = newSize;
|
||||||
|
const { width, height } = changedElement;
|
||||||
|
const newGap = Math.max(
|
||||||
|
1,
|
||||||
|
Math.min(
|
||||||
|
maxBindingGap(changedElement, newWidth, newHeight),
|
||||||
|
currentBinding.gap *
|
||||||
|
(newWidth < newHeight ? newWidth / width : newHeight / height),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...currentBinding, gap: newGap };
|
||||||
|
};
|
||||||
|
|
||||||
const getElligibleElementForBindingElement = (
|
const getElligibleElementForBindingElement = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
|
@ -1574,7 +1580,10 @@ const determineFocusDistance = (
|
||||||
// Another point on the line, in absolute coordinates (closer to element)
|
// Another point on the line, in absolute coordinates (closer to element)
|
||||||
b: GlobalPoint,
|
b: GlobalPoint,
|
||||||
): number => {
|
): number => {
|
||||||
const center = elementCenterPoint(element);
|
const center = pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
);
|
||||||
|
|
||||||
if (pointsEqual(a, b)) {
|
if (pointsEqual(a, b)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1704,7 +1713,10 @@ const determineFocusPoint = (
|
||||||
focus: number,
|
focus: number,
|
||||||
adjacentPoint: GlobalPoint,
|
adjacentPoint: GlobalPoint,
|
||||||
): GlobalPoint => {
|
): GlobalPoint => {
|
||||||
const center = elementCenterPoint(element);
|
const center = pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
);
|
||||||
|
|
||||||
if (focus === 0) {
|
if (focus === 0) {
|
||||||
return center;
|
return center;
|
||||||
|
@ -2135,7 +2147,10 @@ export const getGlobalFixedPointForBindableElement = (
|
||||||
element.x + element.width * fixedX,
|
element.x + element.width * fixedX,
|
||||||
element.y + element.height * fixedY,
|
element.y + element.height * fixedY,
|
||||||
),
|
),
|
||||||
elementCenterPoint(element),
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,7 +41,6 @@ import {
|
||||||
import {
|
import {
|
||||||
deconstructDiamondElement,
|
deconstructDiamondElement,
|
||||||
deconstructRectanguloidElement,
|
deconstructRectanguloidElement,
|
||||||
elementCenterPoint,
|
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
@ -192,7 +191,10 @@ const intersectRectanguloidWithLineSegment = (
|
||||||
l: LineSegment<GlobalPoint>,
|
l: LineSegment<GlobalPoint>,
|
||||||
offset: number = 0,
|
offset: number = 0,
|
||||||
): GlobalPoint[] => {
|
): GlobalPoint[] => {
|
||||||
const center = elementCenterPoint(element);
|
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
|
// To emulate a rotated rectangle we rotate the point in the inverse angle
|
||||||
// instead. It's all the same distance-wise.
|
// instead. It's all the same distance-wise.
|
||||||
const rotatedA = pointRotateRads<GlobalPoint>(
|
const rotatedA = pointRotateRads<GlobalPoint>(
|
||||||
|
@ -210,32 +212,31 @@ const intersectRectanguloidWithLineSegment = (
|
||||||
const [sides, corners] = deconstructRectanguloidElement(element, offset);
|
const [sides, corners] = deconstructRectanguloidElement(element, offset);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Test intersection against the sides, keep only the valid
|
[
|
||||||
// intersection points and rotate them back to scene space
|
// Test intersection against the sides, keep only the valid
|
||||||
sides
|
// intersection points and rotate them back to scene space
|
||||||
.map((s) =>
|
...sides
|
||||||
lineSegmentIntersectionPoints(
|
.map((s) =>
|
||||||
lineSegment<GlobalPoint>(rotatedA, rotatedB),
|
lineSegmentIntersectionPoints(
|
||||||
s,
|
lineSegment<GlobalPoint>(rotatedA, rotatedB),
|
||||||
),
|
s,
|
||||||
)
|
),
|
||||||
.filter((x) => x != null)
|
)
|
||||||
.map((j) => pointRotateRads<GlobalPoint>(j!, center, element.angle))
|
.filter((x) => x != null)
|
||||||
|
.map((j) => pointRotateRads<GlobalPoint>(j!, center, element.angle)),
|
||||||
// Test intersection against the corners which are cubic bezier curves,
|
// Test intersection against the corners which are cubic bezier curves,
|
||||||
// keep only the valid intersection points and rotate them back to scene
|
// keep only the valid intersection points and rotate them back to scene
|
||||||
// space
|
// space
|
||||||
.concat(
|
...corners
|
||||||
corners
|
.flatMap((t) =>
|
||||||
.flatMap((t) =>
|
curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)),
|
||||||
curveIntersectLineSegment(t, lineSegment(rotatedA, rotatedB)),
|
)
|
||||||
)
|
.filter((i) => i != null)
|
||||||
.filter((i) => i != null)
|
.map((j) => pointRotateRads(j, center, element.angle)),
|
||||||
.map((j) => pointRotateRads(j, center, element.angle)),
|
]
|
||||||
)
|
|
||||||
// Remove duplicates
|
// Remove duplicates
|
||||||
.filter(
|
.filter(
|
||||||
(p, idx, points) =>
|
(p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx,
|
||||||
points.findIndex((d) => pointsEqual(p, d, 1e-3)) === idx,
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -252,7 +253,10 @@ const intersectDiamondWithLineSegment = (
|
||||||
l: LineSegment<GlobalPoint>,
|
l: LineSegment<GlobalPoint>,
|
||||||
offset: number = 0,
|
offset: number = 0,
|
||||||
): GlobalPoint[] => {
|
): GlobalPoint[] => {
|
||||||
const center = elementCenterPoint(element);
|
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
|
// Rotate the point to the inverse direction to simulate the rotated diamond
|
||||||
// points. It's all the same distance-wise.
|
// points. It's all the same distance-wise.
|
||||||
|
@ -262,29 +266,28 @@ const intersectDiamondWithLineSegment = (
|
||||||
const [sides, curves] = deconstructDiamondElement(element, offset);
|
const [sides, curves] = deconstructDiamondElement(element, offset);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
sides
|
[
|
||||||
.map((s) =>
|
...sides
|
||||||
lineSegmentIntersectionPoints(
|
.map((s) =>
|
||||||
lineSegment<GlobalPoint>(rotatedA, rotatedB),
|
lineSegmentIntersectionPoints(
|
||||||
s,
|
lineSegment<GlobalPoint>(rotatedA, rotatedB),
|
||||||
),
|
s,
|
||||||
)
|
),
|
||||||
.filter((p): p is GlobalPoint => p != null)
|
)
|
||||||
// Rotate back intersection points
|
.filter((p): p is GlobalPoint => p != null)
|
||||||
.map((p) => pointRotateRads<GlobalPoint>(p!, center, element.angle))
|
// Rotate back intersection points
|
||||||
.concat(
|
.map((p) => pointRotateRads<GlobalPoint>(p!, center, element.angle)),
|
||||||
curves
|
...curves
|
||||||
.flatMap((p) =>
|
.flatMap((p) =>
|
||||||
curveIntersectLineSegment(p, lineSegment(rotatedA, rotatedB)),
|
curveIntersectLineSegment(p, lineSegment(rotatedA, rotatedB)),
|
||||||
)
|
)
|
||||||
.filter((p) => p != null)
|
.filter((p) => p != null)
|
||||||
// Rotate back intersection points
|
// Rotate back intersection points
|
||||||
.map((p) => pointRotateRads(p, center, element.angle)),
|
.map((p) => pointRotateRads(p, center, element.angle)),
|
||||||
)
|
]
|
||||||
// Remove duplicates
|
// Remove duplicates
|
||||||
.filter(
|
.filter(
|
||||||
(p, idx, points) =>
|
(p, idx, points) => points.findIndex((d) => pointsEqual(p, d)) === idx,
|
||||||
points.findIndex((d) => pointsEqual(p, d, 1e-3)) === idx,
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -301,7 +304,10 @@ const intersectEllipseWithLineSegment = (
|
||||||
l: LineSegment<GlobalPoint>,
|
l: LineSegment<GlobalPoint>,
|
||||||
offset: number = 0,
|
offset: number = 0,
|
||||||
): GlobalPoint[] => {
|
): GlobalPoint[] => {
|
||||||
const center = elementCenterPoint(element);
|
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 rotatedA = pointRotateRads(l[0], center, -element.angle as Radians);
|
||||||
const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians);
|
const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians);
|
||||||
|
|
|
@ -61,7 +61,7 @@ export const cropElement = (
|
||||||
|
|
||||||
const rotatedPointer = pointRotateRads(
|
const rotatedPointer = pointRotateRads(
|
||||||
pointFrom(pointerX, pointerY),
|
pointFrom(pointerX, pointerY),
|
||||||
elementCenterPoint(element),
|
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
|
||||||
-element.angle as Radians,
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
curvePointDistance,
|
curvePointDistance,
|
||||||
distanceToLineSegment,
|
distanceToLineSegment,
|
||||||
|
pointFrom,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
|
@ -52,7 +53,10 @@ const distanceToRectanguloidElement = (
|
||||||
element: ExcalidrawRectanguloidElement,
|
element: ExcalidrawRectanguloidElement,
|
||||||
p: GlobalPoint,
|
p: GlobalPoint,
|
||||||
) => {
|
) => {
|
||||||
const center = elementCenterPoint(element);
|
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
|
// To emulate a rotated rectangle we rotate the point in the inverse angle
|
||||||
// instead. It's all the same distance-wise.
|
// instead. It's all the same distance-wise.
|
||||||
const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians);
|
||||||
|
@ -80,7 +84,10 @@ const distanceToDiamondElement = (
|
||||||
element: ExcalidrawDiamondElement,
|
element: ExcalidrawDiamondElement,
|
||||||
p: GlobalPoint,
|
p: GlobalPoint,
|
||||||
): number => {
|
): number => {
|
||||||
const center = elementCenterPoint(element);
|
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
|
// Rotate the point to the inverse direction to simulate the rotated diamond
|
||||||
// points. It's all the same distance-wise.
|
// points. It's all the same distance-wise.
|
||||||
|
@ -108,7 +115,10 @@ const distanceToEllipseElement = (
|
||||||
element: ExcalidrawEllipseElement,
|
element: ExcalidrawEllipseElement,
|
||||||
p: GlobalPoint,
|
p: GlobalPoint,
|
||||||
): number => {
|
): number => {
|
||||||
const center = elementCenterPoint(element);
|
const center = pointFrom(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
);
|
||||||
return ellipseDistanceFromPoint(
|
return ellipseDistanceFromPoint(
|
||||||
// Instead of rotating the ellipse, rotate the point to the inverse angle
|
// Instead of rotating the ellipse, rotate the point to the inverse angle
|
||||||
pointRotateRads(p, center, -element.angle as Radians),
|
pointRotateRads(p, center, -element.angle as Radians),
|
||||||
|
|
|
@ -1249,7 +1249,6 @@ const getElbowArrowData = (
|
||||||
...arrow,
|
...arrow,
|
||||||
type: "arrow",
|
type: "arrow",
|
||||||
elbowed: true,
|
elbowed: true,
|
||||||
type: "arrow",
|
|
||||||
points: nextPoints,
|
points: nextPoints,
|
||||||
} as ExcalidrawElbowArrowElement,
|
} as ExcalidrawElbowArrowElement,
|
||||||
"start",
|
"start",
|
||||||
|
@ -1263,7 +1262,6 @@ const getElbowArrowData = (
|
||||||
...arrow,
|
...arrow,
|
||||||
type: "arrow",
|
type: "arrow",
|
||||||
elbowed: true,
|
elbowed: true,
|
||||||
type: "arrow",
|
|
||||||
points: nextPoints,
|
points: nextPoints,
|
||||||
} as ExcalidrawElbowArrowElement,
|
} as ExcalidrawElbowArrowElement,
|
||||||
"end",
|
"end",
|
||||||
|
@ -2223,7 +2221,6 @@ const getGlobalPoint = (
|
||||||
arrow,
|
arrow,
|
||||||
element,
|
element,
|
||||||
startOrEnd,
|
startOrEnd,
|
||||||
elementsMap,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return snapToMid(element, snapPoint);
|
return snapToMid(element, snapPoint);
|
||||||
|
|
|
@ -238,43 +238,6 @@ export class LinearElementEditor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static getOutlineAvoidingPoint(
|
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
|
||||||
coords: GlobalPoint,
|
|
||||||
pointIndex: number,
|
|
||||||
app: AppClassProperties,
|
|
||||||
fallback?: GlobalPoint,
|
|
||||||
): GlobalPoint {
|
|
||||||
const hoveredElement = getHoveredElementForBinding(
|
|
||||||
{ x: coords[0], y: coords[1] },
|
|
||||||
app.scene.getNonDeletedElements(),
|
|
||||||
app.scene.getNonDeletedElementsMap(),
|
|
||||||
app.state.zoom,
|
|
||||||
true,
|
|
||||||
isElbowArrow(element),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hoveredElement) {
|
|
||||||
const newPoints = Array.from(element.points);
|
|
||||||
newPoints[pointIndex] = pointFrom<LocalPoint>(
|
|
||||||
coords[0] - element.x,
|
|
||||||
coords[1] - element.y,
|
|
||||||
);
|
|
||||||
|
|
||||||
return bindPointToSnapToElementOutline(
|
|
||||||
{
|
|
||||||
...element,
|
|
||||||
points: newPoints,
|
|
||||||
},
|
|
||||||
hoveredElement,
|
|
||||||
pointIndex === 0 ? "start" : "end",
|
|
||||||
app.scene.getNonDeletedElementsMap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fallback ?? coords;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns whether point was dragged
|
* @returns whether point was dragged
|
||||||
*/
|
*/
|
||||||
|
@ -294,16 +257,14 @@ export class LinearElementEditor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { elementId } = linearElementEditor;
|
const { elementId } = linearElementEditor;
|
||||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
const elementsMap = scene.getNonDeletedElementsMap();
|
||||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elbowed = isElbowArrow(element);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
elbowed &&
|
isElbowArrow(element) &&
|
||||||
!linearElementEditor.pointerDownState.lastClickedIsEndPoint &&
|
!linearElementEditor.pointerDownState.lastClickedIsEndPoint &&
|
||||||
linearElementEditor.pointerDownState.lastClickedPoint !== 0
|
linearElementEditor.pointerDownState.lastClickedPoint !== 0
|
||||||
) {
|
) {
|
||||||
|
@ -320,7 +281,7 @@ export class LinearElementEditor {
|
||||||
: undefined,
|
: undefined,
|
||||||
].filter((idx): idx is number => idx !== undefined)
|
].filter((idx): idx is number => idx !== undefined)
|
||||||
: linearElementEditor.selectedPointsIndices;
|
: linearElementEditor.selectedPointsIndices;
|
||||||
const lastClickedPoint = elbowed
|
const lastClickedPoint = isElbowArrow(element)
|
||||||
? linearElementEditor.pointerDownState.lastClickedPoint > 0
|
? linearElementEditor.pointerDownState.lastClickedPoint > 0
|
||||||
? element.points.length - 1
|
? element.points.length - 1
|
||||||
: 0
|
: 0
|
||||||
|
@ -372,43 +333,19 @@ export class LinearElementEditor {
|
||||||
LinearElementEditor.movePoints(
|
LinearElementEditor.movePoints(
|
||||||
element,
|
element,
|
||||||
selectedPointsIndices.map((pointIndex) => {
|
selectedPointsIndices.map((pointIndex) => {
|
||||||
let newPointPosition = pointFrom<LocalPoint>(
|
const newPointPosition: LocalPoint =
|
||||||
element.points[pointIndex][0] + deltaX,
|
pointIndex === lastClickedPoint
|
||||||
element.points[pointIndex][1] + deltaY,
|
? LinearElementEditor.createPointAt(
|
||||||
);
|
|
||||||
|
|
||||||
// Check if point dragging is happening
|
|
||||||
if (pointIndex === lastClickedPoint) {
|
|
||||||
let globalNewPointPosition = pointFrom<GlobalPoint>(
|
|
||||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
|
||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
pointIndex === 0 ||
|
|
||||||
pointIndex === element.points.length - 1
|
|
||||||
) {
|
|
||||||
globalNewPointPosition =
|
|
||||||
LinearElementEditor.getOutlineAvoidingPoint(
|
|
||||||
element,
|
element,
|
||||||
pointFrom<GlobalPoint>(
|
elementsMap,
|
||||||
element.x + element.points[pointIndex][0] + deltaX,
|
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||||
element.y + element.points[pointIndex][1] + deltaY,
|
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||||
),
|
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||||
pointIndex,
|
)
|
||||||
app,
|
: pointFrom(
|
||||||
|
element.points[pointIndex][0] + deltaX,
|
||||||
|
element.points[pointIndex][1] + deltaY,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
newPointPosition = LinearElementEditor.createPointAt(
|
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
globalNewPointPosition[0],
|
|
||||||
globalNewPointPosition[1],
|
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
index: pointIndex,
|
index: pointIndex,
|
||||||
point: newPointPosition,
|
point: newPointPosition,
|
||||||
|
|
|
@ -18,7 +18,6 @@ import { getDiamondPoints } from "./bounds";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
ExcalidrawElement,
|
|
||||||
ExcalidrawRectanguloidElement,
|
ExcalidrawRectanguloidElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
|
@ -69,7 +68,10 @@ export function deconstructRectanguloidElement(
|
||||||
return [sides, []];
|
return [sides, []];
|
||||||
}
|
}
|
||||||
|
|
||||||
const center = elementCenterPoint(element);
|
const center = pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
);
|
||||||
|
|
||||||
const r = rectangle(
|
const r = rectangle(
|
||||||
pointFrom(element.x, element.y),
|
pointFrom(element.x, element.y),
|
||||||
|
@ -252,7 +254,10 @@ export function deconstructDiamondElement(
|
||||||
return [[topRight, bottomRight, bottomLeft, topLeft], []];
|
return [[topRight, bottomRight, bottomLeft, topLeft], []];
|
||||||
}
|
}
|
||||||
|
|
||||||
const center = elementCenterPoint(element);
|
const center = pointFrom<GlobalPoint>(
|
||||||
|
element.x + element.width / 2,
|
||||||
|
element.y + element.height / 2,
|
||||||
|
);
|
||||||
|
|
||||||
const [top, right, bottom, left]: GlobalPoint[] = [
|
const [top, right, bottom, left]: GlobalPoint[] = [
|
||||||
pointFrom(element.x + topX, element.y + topY),
|
pointFrom(element.x + topX, element.y + topY),
|
||||||
|
@ -352,10 +357,3 @@ export function deconstructDiamondElement(
|
||||||
|
|
||||||
return [sides, corners];
|
return [sides, corners];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function elementCenterPoint(element: ExcalidrawElement) {
|
|
||||||
return pointFrom<GlobalPoint>(
|
|
||||||
element.x + element.width / 2,
|
|
||||||
element.y + element.height / 2,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -190,18 +190,7 @@ describe("element binding", () => {
|
||||||
|
|
||||||
// Sever connection
|
// Sever connection
|
||||||
expect(API.getSelectedElement().type).toBe("arrow");
|
expect(API.getSelectedElement().type).toBe("arrow");
|
||||||
Keyboard.withModifierKeys({ shift: true }, () => {
|
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
||||||
// We have to move a significant distance to get out of the binding zone
|
|
||||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
|
||||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
|
||||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
|
||||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
|
||||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
|
||||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
|
||||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
|
||||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
|
||||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
|
||||||
});
|
|
||||||
expect(arrow.endBinding).toBe(null);
|
expect(arrow.endBinding).toBe(null);
|
||||||
Keyboard.keyPress(KEYS.ARROW_RIGHT);
|
Keyboard.keyPress(KEYS.ARROW_RIGHT);
|
||||||
expect(arrow.endBinding).toBe(null);
|
expect(arrow.endBinding).toBe(null);
|
||||||
|
|
|
@ -77,9 +77,9 @@ describe("elbow arrow segment move", () => {
|
||||||
|
|
||||||
expect(arrow.points).toCloselyEqualPoints([
|
expect(arrow.points).toCloselyEqualPoints([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[107.93, 0],
|
[110, 0],
|
||||||
[107.93, 185.86],
|
[110, 200],
|
||||||
[185.86, 185.86],
|
[190, 200],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mouse.reset();
|
mouse.reset();
|
||||||
|
@ -88,9 +88,9 @@ describe("elbow arrow segment move", () => {
|
||||||
|
|
||||||
expect(arrow.points).toCloselyEqualPoints([
|
expect(arrow.points).toCloselyEqualPoints([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[107.93, 0],
|
[110, 0],
|
||||||
[107.93, 185.86],
|
[110, 200],
|
||||||
[185.86, 185.86],
|
[190, 200],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -198,11 +198,11 @@ describe("elbow arrow routing", () => {
|
||||||
points: [pointFrom<LocalPoint>(0, 0), pointFrom<LocalPoint>(90, 200)],
|
points: [pointFrom<LocalPoint>(0, 0), pointFrom<LocalPoint>(90, 200)],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(arrow.points).toCloselyEqualPoints([
|
expect(arrow.points).toEqual([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[42.93, 0],
|
[45, 0],
|
||||||
[42.93, 195.7],
|
[45, 200],
|
||||||
[85.86, 195.7],
|
[90, 200],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -241,9 +241,9 @@ describe("elbow arrow ui", () => {
|
||||||
expect(h.state.currentItemArrowType).toBe(ARROW_TYPE.elbow);
|
expect(h.state.currentItemArrowType).toBe(ARROW_TYPE.elbow);
|
||||||
|
|
||||||
mouse.reset();
|
mouse.reset();
|
||||||
mouse.moveTo(-50, -100);
|
mouse.moveTo(-43, -99);
|
||||||
mouse.click();
|
mouse.click();
|
||||||
mouse.moveTo(50, 100);
|
mouse.moveTo(43, 99);
|
||||||
mouse.click();
|
mouse.click();
|
||||||
|
|
||||||
const arrow = h.scene.getSelectedElements(
|
const arrow = h.scene.getSelectedElements(
|
||||||
|
@ -252,11 +252,11 @@ describe("elbow arrow ui", () => {
|
||||||
|
|
||||||
expect(arrow.type).toBe("arrow");
|
expect(arrow.type).toBe("arrow");
|
||||||
expect(arrow.elbowed).toBe(true);
|
expect(arrow.elbowed).toBe(true);
|
||||||
expect(arrow.points).toCloselyEqualPoints([
|
expect(arrow.points).toEqual([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[42.93, 0],
|
[45, 0],
|
||||||
[42.93, 153.48],
|
[45, 200],
|
||||||
[85.86, 153.48],
|
[90, 200],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -296,8 +296,9 @@ describe("elbow arrow ui", () => {
|
||||||
|
|
||||||
expect(arrow.points.map((point) => point.map(Math.round))).toEqual([
|
expect(arrow.points.map((point) => point.map(Math.round))).toEqual([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[129, 0],
|
[35, 0],
|
||||||
[129, 131],
|
[35, 165],
|
||||||
|
[103, 165],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,7 @@ describe("generic element", () => {
|
||||||
UI.resize(rectangle, "w", [50, 0]);
|
UI.resize(rectangle, "w", [50, 0]);
|
||||||
|
|
||||||
expect(arrow.endBinding?.elementId).toEqual(rectangle.id);
|
expect(arrow.endBinding?.elementId).toEqual(rectangle.id);
|
||||||
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(80.62, 0);
|
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(80, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resizes with a label", async () => {
|
it("resizes with a label", async () => {
|
||||||
|
@ -510,13 +510,13 @@ describe("arrow element", () => {
|
||||||
h.state,
|
h.state,
|
||||||
)[0] as ExcalidrawElbowArrowElement;
|
)[0] as ExcalidrawElbowArrowElement;
|
||||||
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.07);
|
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1);
|
||||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.86);
|
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75);
|
||||||
|
|
||||||
UI.resize(rectangle, "se", [-200, -150]);
|
UI.resize(rectangle, "se", [-200, -150]);
|
||||||
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.07);
|
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1);
|
||||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.86);
|
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("flips the fixed point binding on negative resize for group selection", () => {
|
it("flips the fixed point binding on negative resize for group selection", () => {
|
||||||
|
@ -538,8 +538,8 @@ describe("arrow element", () => {
|
||||||
h.state,
|
h.state,
|
||||||
)[0] as ExcalidrawElbowArrowElement;
|
)[0] as ExcalidrawElbowArrowElement;
|
||||||
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.07);
|
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1);
|
||||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.86);
|
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75);
|
||||||
|
|
||||||
UI.resize([rectangle, arrow], "nw", [300, 350]);
|
UI.resize([rectangle, arrow], "nw", [300, 350]);
|
||||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(0);
|
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(0);
|
||||||
|
@ -809,7 +809,7 @@ describe("image element", () => {
|
||||||
});
|
});
|
||||||
API.setElements([image]);
|
API.setElements([image]);
|
||||||
const arrow = UI.createElement("arrow", {
|
const arrow = UI.createElement("arrow", {
|
||||||
x: -29,
|
x: -30,
|
||||||
y: 50,
|
y: 50,
|
||||||
width: 28,
|
width: 28,
|
||||||
height: 5,
|
height: 5,
|
||||||
|
@ -819,14 +819,14 @@ describe("image element", () => {
|
||||||
|
|
||||||
UI.resize(image, "ne", [40, 0]);
|
UI.resize(image, "ne", [40, 0]);
|
||||||
|
|
||||||
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(30, 0);
|
expect(arrow.width + arrow.endBinding!.gap).toBeCloseTo(31, 0);
|
||||||
|
|
||||||
const imageWidth = image.width;
|
const imageWidth = image.width;
|
||||||
const scale = 20 / image.height;
|
const scale = 20 / image.height;
|
||||||
UI.resize(image, "nw", [50, 20]);
|
UI.resize(image, "nw", [50, 20]);
|
||||||
|
|
||||||
expect(arrow.endBinding?.elementId).toEqual(image.id);
|
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,
|
30 + imageWidth * scale,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
@ -1033,11 +1033,11 @@ describe("multiple selection", () => {
|
||||||
|
|
||||||
expect(leftBoundArrow.x).toBeCloseTo(-110);
|
expect(leftBoundArrow.x).toBeCloseTo(-110);
|
||||||
expect(leftBoundArrow.y).toBeCloseTo(50);
|
expect(leftBoundArrow.y).toBeCloseTo(50);
|
||||||
expect(leftBoundArrow.width).toBeCloseTo(146.46, 0);
|
expect(leftBoundArrow.width).toBeCloseTo(143, 0);
|
||||||
expect(leftBoundArrow.height).toBeCloseTo(7, 0);
|
expect(leftBoundArrow.height).toBeCloseTo(7, 0);
|
||||||
expect(leftBoundArrow.angle).toEqual(0);
|
expect(leftBoundArrow.angle).toEqual(0);
|
||||||
expect(leftBoundArrow.startBinding).toBeNull();
|
expect(leftBoundArrow.startBinding).toBeNull();
|
||||||
expect(leftBoundArrow.endBinding?.gap).toEqual(FIXED_BINDING_DISTANCE);
|
expect(leftBoundArrow.endBinding?.gap).toBeCloseTo(10);
|
||||||
expect(leftBoundArrow.endBinding?.elementId).toBe(
|
expect(leftBoundArrow.endBinding?.elementId).toBe(
|
||||||
leftArrowBinding.elementId,
|
leftArrowBinding.elementId,
|
||||||
);
|
);
|
||||||
|
@ -1051,7 +1051,7 @@ describe("multiple selection", () => {
|
||||||
expect(rightBoundArrow.height).toBeCloseTo(0);
|
expect(rightBoundArrow.height).toBeCloseTo(0);
|
||||||
expect(rightBoundArrow.angle).toEqual(0);
|
expect(rightBoundArrow.angle).toEqual(0);
|
||||||
expect(rightBoundArrow.startBinding).toBeNull();
|
expect(rightBoundArrow.startBinding).toBeNull();
|
||||||
expect(rightBoundArrow.endBinding?.gap).toEqual(FIXED_BINDING_DISTANCE);
|
expect(rightBoundArrow.endBinding?.gap).toBeCloseTo(8.0952);
|
||||||
expect(rightBoundArrow.endBinding?.elementId).toBe(
|
expect(rightBoundArrow.endBinding?.elementId).toBe(
|
||||||
rightArrowBinding.elementId,
|
rightArrowBinding.elementId,
|
||||||
);
|
);
|
||||||
|
|
|
@ -91,26 +91,10 @@ export const actionFinalize = register({
|
||||||
multiPointElement.type !== "freedraw" &&
|
multiPointElement.type !== "freedraw" &&
|
||||||
appState.lastPointerDownWith !== "touch"
|
appState.lastPointerDownWith !== "touch"
|
||||||
) {
|
) {
|
||||||
const { x: rx, y: ry, points, lastCommittedPoint } = multiPointElement;
|
const { points, lastCommittedPoint } = multiPointElement;
|
||||||
const lastGlobalPoint = pointFrom<GlobalPoint>(
|
|
||||||
rx + points[points.length - 1][0],
|
|
||||||
ry + points[points.length - 1][1],
|
|
||||||
);
|
|
||||||
const hoveredElementForBinding = getHoveredElementForBinding(
|
|
||||||
{
|
|
||||||
x: lastGlobalPoint[0],
|
|
||||||
y: lastGlobalPoint[1],
|
|
||||||
},
|
|
||||||
elements,
|
|
||||||
elementsMap,
|
|
||||||
app.state.zoom,
|
|
||||||
true,
|
|
||||||
isElbowArrow(multiPointElement),
|
|
||||||
);
|
|
||||||
if (
|
if (
|
||||||
!hoveredElementForBinding &&
|
!lastCommittedPoint ||
|
||||||
(!lastCommittedPoint ||
|
points[points.length - 1] !== lastCommittedPoint
|
||||||
points[points.length - 1] !== lastCommittedPoint)
|
|
||||||
) {
|
) {
|
||||||
mutateElement(multiPointElement, {
|
mutateElement(multiPointElement, {
|
||||||
points: multiPointElement.points.slice(0, -1),
|
points: multiPointElement.points.slice(0, -1),
|
||||||
|
|
|
@ -1655,7 +1655,6 @@ export const actionChangeArrowType = register({
|
||||||
newElement,
|
newElement,
|
||||||
startHoveredElement,
|
startHoveredElement,
|
||||||
"start",
|
"start",
|
||||||
elementsMap,
|
|
||||||
)
|
)
|
||||||
: startGlobalPoint;
|
: startGlobalPoint;
|
||||||
const finalEndPoint = endHoveredElement
|
const finalEndPoint = endHoveredElement
|
||||||
|
@ -1663,7 +1662,6 @@ export const actionChangeArrowType = register({
|
||||||
newElement,
|
newElement,
|
||||||
endHoveredElement,
|
endHoveredElement,
|
||||||
"end",
|
"end",
|
||||||
elementsMap,
|
|
||||||
)
|
)
|
||||||
: endGlobalPoint;
|
: endGlobalPoint;
|
||||||
|
|
||||||
|
|
|
@ -5965,25 +5965,15 @@ 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
|
// update last uncommitted point
|
||||||
mutateElement(
|
mutateElement(
|
||||||
multiElement,
|
multiElement,
|
||||||
{
|
{
|
||||||
points: [
|
points: [
|
||||||
...points.slice(0, -1),
|
...points.slice(0, -1),
|
||||||
pointTranslate<GlobalPoint, LocalPoint>(
|
pointFrom<LocalPoint>(
|
||||||
LinearElementEditor.getOutlineAvoidingPoint(
|
lastCommittedX + dxFromLastCommitted,
|
||||||
multiElement,
|
lastCommittedY + dyFromLastCommitted,
|
||||||
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
|
|
||||||
multiElement.points.length - 1,
|
|
||||||
this,
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
multiElement.x + lastCommittedX + dxFromLastCommitted,
|
|
||||||
multiElement.y + lastCommittedY + dyFromLastCommitted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
vector(-multiElement.x, -multiElement.y),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -7725,34 +7715,18 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { x: rx, y: ry, lastCommittedPoint } = multiElement;
|
const { x: rx, y: ry, lastCommittedPoint } = multiElement;
|
||||||
const lastGlobalPoint = pointFrom<GlobalPoint>(
|
|
||||||
rx + multiElement.points[multiElement.points.length - 1][0],
|
|
||||||
ry + multiElement.points[multiElement.points.length - 1][1],
|
|
||||||
);
|
|
||||||
const hoveredElementForBinding = getHoveredElementForBinding(
|
|
||||||
{
|
|
||||||
x: lastGlobalPoint[0],
|
|
||||||
y: lastGlobalPoint[1],
|
|
||||||
},
|
|
||||||
this.scene.getNonDeletedElements(),
|
|
||||||
this.scene.getNonDeletedElementsMap(),
|
|
||||||
this.state.zoom,
|
|
||||||
true,
|
|
||||||
isElbowArrow(multiElement),
|
|
||||||
);
|
|
||||||
|
|
||||||
// clicking inside commit zone → finalize arrow
|
// clicking inside commit zone → finalize arrow
|
||||||
if (
|
if (
|
||||||
!!hoveredElementForBinding ||
|
multiElement.points.length > 1 &&
|
||||||
(multiElement.points.length > 1 &&
|
lastCommittedPoint &&
|
||||||
lastCommittedPoint &&
|
pointDistance(
|
||||||
pointDistance(
|
pointFrom(
|
||||||
pointFrom(
|
pointerDownState.origin.x - rx,
|
||||||
pointerDownState.origin.x - rx,
|
pointerDownState.origin.y - ry,
|
||||||
pointerDownState.origin.y - ry,
|
),
|
||||||
),
|
lastCommittedPoint,
|
||||||
lastCommittedPoint,
|
) < LINE_CONFIRM_THRESHOLD
|
||||||
) < LINE_CONFIRM_THRESHOLD)
|
|
||||||
) {
|
) {
|
||||||
this.actionManager.executeAction(actionFinalize);
|
this.actionManager.executeAction(actionFinalize);
|
||||||
return;
|
return;
|
||||||
|
@ -7796,93 +7770,53 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
? [currentItemStartArrowhead, currentItemEndArrowhead]
|
? [currentItemStartArrowhead, currentItemEndArrowhead]
|
||||||
: [null, null];
|
: [null, null];
|
||||||
|
|
||||||
let element: NonDeleted<ExcalidrawLinearElement>;
|
const element =
|
||||||
if (elementType === "arrow") {
|
elementType === "arrow"
|
||||||
const arrow: Mutable<NonDeleted<ExcalidrawArrowElement>> =
|
? newArrowElement({
|
||||||
newArrowElement({
|
type: elementType,
|
||||||
type: "arrow",
|
x: gridX,
|
||||||
x: gridX,
|
y: gridY,
|
||||||
y: gridY,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
strokeColor: this.state.currentItemStrokeColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeWidth: this.state.currentItemStrokeWidth,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
roughness: this.state.currentItemRoughness,
|
||||||
roughness: this.state.currentItemRoughness,
|
opacity: this.state.currentItemOpacity,
|
||||||
opacity: this.state.currentItemOpacity,
|
roundness:
|
||||||
roundness:
|
this.state.currentItemArrowType === ARROW_TYPE.round
|
||||||
this.state.currentItemArrowType === ARROW_TYPE.round
|
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
||||||
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
: // note, roundness doesn't have any effect for elbow arrows,
|
||||||
: // note, roundness doesn't have any effect for elbow arrows,
|
// but it's best to set it to null as well
|
||||||
// but it's best to set it to null as well
|
null,
|
||||||
null,
|
startArrowhead,
|
||||||
startArrowhead,
|
endArrowhead,
|
||||||
endArrowhead,
|
locked: false,
|
||||||
locked: false,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
|
||||||
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
|
fixedSegments:
|
||||||
fixedSegments:
|
this.state.currentItemArrowType === ARROW_TYPE.elbow
|
||||||
this.state.currentItemArrowType === ARROW_TYPE.elbow ? [] : null,
|
? []
|
||||||
});
|
: null,
|
||||||
|
})
|
||||||
const hoveredElement = getHoveredElementForBinding(
|
: newLinearElement({
|
||||||
{ x: gridX, y: gridY },
|
type: elementType,
|
||||||
this.scene.getNonDeletedElements(),
|
x: gridX,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
y: gridY,
|
||||||
this.state.zoom,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
true,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
this.state.currentItemArrowType === ARROW_TYPE.elbow,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
);
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
if (hoveredElement) {
|
roughness: this.state.currentItemRoughness,
|
||||||
[arrow.x, arrow.y] =
|
opacity: this.state.currentItemOpacity,
|
||||||
intersectElementWithLineSegment(
|
roundness:
|
||||||
hoveredElement,
|
this.state.currentItemRoundness === "round"
|
||||||
lineSegment(
|
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
||||||
pointFrom<GlobalPoint>(gridX, gridY),
|
: null,
|
||||||
pointFrom<GlobalPoint>(
|
locked: false,
|
||||||
gridX,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
hoveredElement.y + hoveredElement.height / 2,
|
});
|
||||||
),
|
|
||||||
),
|
|
||||||
2 * FIXED_BINDING_DISTANCE,
|
|
||||||
)[0] ??
|
|
||||||
intersectElementWithLineSegment(
|
|
||||||
hoveredElement,
|
|
||||||
lineSegment(
|
|
||||||
pointFrom<GlobalPoint>(gridX, gridY),
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
hoveredElement.x + hoveredElement.width / 2,
|
|
||||||
gridY,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
2 * FIXED_BINDING_DISTANCE,
|
|
||||||
)[0] ??
|
|
||||||
pointFrom<GlobalPoint>(gridX, gridY);
|
|
||||||
}
|
|
||||||
|
|
||||||
element = arrow;
|
|
||||||
} else {
|
|
||||||
element = newLinearElement({
|
|
||||||
type: elementType,
|
|
||||||
x: gridX,
|
|
||||||
y: gridY,
|
|
||||||
strokeColor: this.state.currentItemStrokeColor,
|
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
|
||||||
strokeWidth: this.state.currentItemStrokeWidth,
|
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
|
||||||
roughness: this.state.currentItemRoughness,
|
|
||||||
opacity: this.state.currentItemOpacity,
|
|
||||||
roundness:
|
|
||||||
this.state.currentItemRoundness === "round"
|
|
||||||
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
|
||||||
: null,
|
|
||||||
locked: false,
|
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
const nextSelectedElementIds = {
|
const nextSelectedElementIds = {
|
||||||
...prevState.selectedElementIds,
|
...prevState.selectedElementIds,
|
||||||
|
@ -8197,6 +8131,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
|
this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [gridX, gridY] = getGridPoint(
|
||||||
|
pointerCoords.x,
|
||||||
|
pointerCoords.y,
|
||||||
|
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
|
||||||
|
);
|
||||||
|
|
||||||
// for arrows/lines, don't start dragging until a given threshold
|
// for arrows/lines, don't start dragging until a given threshold
|
||||||
// to ensure we don't create a 2-point arrow by mistake when
|
// to ensure we don't create a 2-point arrow by mistake when
|
||||||
// user clicks mouse in a way that it moves a tiny bit (thus
|
// user clicks mouse in a way that it moves a tiny bit (thus
|
||||||
|
@ -8297,6 +8237,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
linearElementEditor,
|
linearElementEditor,
|
||||||
|
this.scene,
|
||||||
);
|
);
|
||||||
if (newLinearElementEditor) {
|
if (newLinearElementEditor) {
|
||||||
pointerDownState.lastCoords.x = pointerCoords.x;
|
pointerDownState.lastCoords.x = pointerCoords.x;
|
||||||
|
@ -8638,11 +8579,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
} else if (isLinearElement(newElement)) {
|
} else if (isLinearElement(newElement)) {
|
||||||
pointerDownState.drag.hasOccurred = true;
|
pointerDownState.drag.hasOccurred = true;
|
||||||
const points = newElement.points;
|
const points = newElement.points;
|
||||||
const [gridX, gridY] = getGridPoint(
|
|
||||||
pointerCoords.x,
|
|
||||||
pointerCoords.y,
|
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
|
|
||||||
);
|
|
||||||
let dx = gridX - newElement.x;
|
let dx = gridX - newElement.x;
|
||||||
let dy = gridY - newElement.y;
|
let dy = gridY - newElement.y;
|
||||||
|
|
||||||
|
@ -8659,22 +8595,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
mutateElement(
|
mutateElement(
|
||||||
newElement,
|
newElement,
|
||||||
{
|
{
|
||||||
points: [
|
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
||||||
...points,
|
|
||||||
pointTranslate<GlobalPoint, LocalPoint>(
|
|
||||||
LinearElementEditor.getOutlineAvoidingPoint(
|
|
||||||
newElement,
|
|
||||||
pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
|
|
||||||
newElement.points.length - 1,
|
|
||||||
this,
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
newElement.x + dx,
|
|
||||||
newElement.y + dy,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
vector(-newElement.x, -newElement.y),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
@ -8685,22 +8606,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
mutateElement(
|
mutateElement(
|
||||||
newElement,
|
newElement,
|
||||||
{
|
{
|
||||||
points: [
|
points: [...points.slice(0, -1), pointFrom<LocalPoint>(dx, dy)],
|
||||||
...points.slice(0, -1),
|
|
||||||
pointTranslate<GlobalPoint, LocalPoint>(
|
|
||||||
LinearElementEditor.getOutlineAvoidingPoint(
|
|
||||||
newElement,
|
|
||||||
pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
|
|
||||||
newElement.points.length - 1,
|
|
||||||
this,
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
newElement.x + dx,
|
|
||||||
newElement.y + dy,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
vector(-newElement.x, -newElement.y),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
{ isDragging: true },
|
{ isDragging: true },
|
||||||
|
|
|
@ -89,7 +89,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "ellipse-1",
|
"elementId": "ellipse-1",
|
||||||
"focus": -0.007519379844961235,
|
"focus": -0.007519379844961235,
|
||||||
"gap": 5,
|
"gap": 11.562288374879595,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -119,7 +119,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id49",
|
"elementId": "id49",
|
||||||
"focus": -0.0813953488372095,
|
"focus": -0.0813953488372095,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1864ab",
|
"strokeColor": "#1864ab",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -145,7 +145,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "ellipse-1",
|
"elementId": "ellipse-1",
|
||||||
"focus": 0.10666666666666667,
|
"focus": 0.10666666666666667,
|
||||||
"gap": 5,
|
"gap": 3.8343264684446097,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -175,7 +175,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "diamond-1",
|
"elementId": "diamond-1",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 4.545343408287929,
|
||||||
},
|
},
|
||||||
"strokeColor": "#e67700",
|
"strokeColor": "#e67700",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -335,7 +335,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "text-2",
|
"elementId": "text-2",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 14,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -365,7 +365,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "text-1",
|
"elementId": "text-1",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -437,7 +437,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id42",
|
"elementId": "id42",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -467,7 +467,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id41",
|
"elementId": "id41",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -613,7 +613,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id46",
|
"elementId": "id46",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -643,7 +643,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id45",
|
"elementId": "id45",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -1475,7 +1475,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "Alice",
|
"elementId": "Alice",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 5.299874999999986,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -1507,7 +1507,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "Bob",
|
"elementId": "Bob",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -1538,7 +1538,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "B",
|
"elementId": "B",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 14,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -1566,7 +1566,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "Bob",
|
"elementId": "Bob",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
|
|
@ -433,7 +433,7 @@ describe("Test Transform", () => {
|
||||||
startBinding: {
|
startBinding: {
|
||||||
elementId: rectangle.id,
|
elementId: rectangle.id,
|
||||||
focus: 0,
|
focus: 0,
|
||||||
gap: FIXED_BINDING_DISTANCE,
|
gap: 1,
|
||||||
},
|
},
|
||||||
endBinding: {
|
endBinding: {
|
||||||
elementId: ellipse.id,
|
elementId: ellipse.id,
|
||||||
|
@ -518,7 +518,7 @@ describe("Test Transform", () => {
|
||||||
startBinding: {
|
startBinding: {
|
||||||
elementId: text2.id,
|
elementId: text2.id,
|
||||||
focus: 0,
|
focus: 0,
|
||||||
gap: FIXED_BINDING_DISTANCE,
|
gap: 1,
|
||||||
},
|
},
|
||||||
endBinding: {
|
endBinding: {
|
||||||
elementId: text3.id,
|
elementId: text3.id,
|
||||||
|
@ -781,7 +781,7 @@ describe("Test Transform", () => {
|
||||||
expect((arrow as ExcalidrawArrowElement).endBinding).toStrictEqual({
|
expect((arrow as ExcalidrawArrowElement).endBinding).toStrictEqual({
|
||||||
elementId: "rect-1",
|
elementId: "rect-1",
|
||||||
focus: -0,
|
focus: -0,
|
||||||
gap: FIXED_BINDING_DISTANCE,
|
gap: 14,
|
||||||
});
|
});
|
||||||
expect(rect.boundElements).toStrictEqual([
|
expect(rect.boundElements).toStrictEqual([
|
||||||
{
|
{
|
||||||
|
|
|
@ -197,7 +197,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": "99.58947",
|
"height": "102.35417",
|
||||||
"id": "id172",
|
"id": "id172",
|
||||||
"index": "a2",
|
"index": "a2",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
@ -211,8 +211,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"99.58947",
|
"101.77517",
|
||||||
"99.58947",
|
"102.35417",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
|
@ -227,8 +227,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 40,
|
"version": 40,
|
||||||
"width": "99.58947",
|
"width": "101.77517",
|
||||||
"x": 0,
|
"x": "0.70711",
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -295,49 +295,47 @@ History {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id171",
|
"elementId": "id171",
|
||||||
"focus": "0.00990",
|
"focus": "0.00990",
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"height": "0.92929",
|
"height": "0.98586",
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
"-0.92929",
|
"-0.98586",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id170",
|
"elementId": "id170",
|
||||||
"focus": "0.02970",
|
"focus": "0.02970",
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"width": "92.92893",
|
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id171",
|
"elementId": "id171",
|
||||||
"focus": "-0.02075",
|
"focus": "-0.02000",
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"height": "0.07074",
|
"height": "0.00000",
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
"0.07074",
|
"0.00000",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id170",
|
"elementId": "id170",
|
||||||
"focus": "0.01770",
|
"focus": "0.02000",
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"width": "92.92893",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -391,47 +389,43 @@ History {
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"height": "99.58947",
|
"height": "102.35417",
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"99.58947",
|
"101.77517",
|
||||||
"99.58947",
|
"102.35417",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
"width": "99.58947",
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
"y": 0,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id171",
|
"elementId": "id171",
|
||||||
"focus": "0.00990",
|
"focus": "0.00990",
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"height": "0.92929",
|
"height": "0.98586",
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
"-0.92929",
|
"-0.98586",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id170",
|
"elementId": "id170",
|
||||||
"focus": "0.02970",
|
"focus": "0.02970",
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"width": "92.92893",
|
"y": "0.99364",
|
||||||
"x": "3.53553",
|
|
||||||
"y": "0.96033",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id175" => Delta {
|
"id175" => Delta {
|
||||||
|
@ -862,7 +856,6 @@ History {
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"width": 0,
|
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"points": [
|
"points": [
|
||||||
|
@ -871,11 +864,10 @@ History {
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"85.85786",
|
100,
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"width": "85.85786",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -932,14 +924,12 @@ History {
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
"width": 100,
|
|
||||||
"x": 150,
|
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id166",
|
"elementId": "id166",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
|
@ -954,10 +944,8 @@ History {
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id165",
|
"elementId": "id165",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"width": 0,
|
|
||||||
"x": "146.46447",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1250,7 +1238,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": "1.71911",
|
"height": "1.30038",
|
||||||
"id": "id178",
|
"id": "id178",
|
||||||
"index": "Zz",
|
"index": "Zz",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
@ -1264,8 +1252,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
"1.71911",
|
"1.30038",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
|
@ -1288,8 +1276,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 11,
|
"version": 11,
|
||||||
"width": "92.92893",
|
"width": "98.58579",
|
||||||
"x": "3.53553",
|
"x": "0.70711",
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1621,7 +1609,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": "1.71911",
|
"height": "1.30038",
|
||||||
"id": "id181",
|
"id": "id181",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
@ -1635,8 +1623,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
"1.71911",
|
"1.30038",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
|
@ -1659,8 +1647,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 11,
|
"version": 11,
|
||||||
"width": "92.92893",
|
"width": "98.58579",
|
||||||
"x": "3.53553",
|
"x": "0.70711",
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1779,7 +1767,7 @@ History {
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": "12.86717",
|
"height": "11.27227",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
|
@ -1792,8 +1780,8 @@ History {
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
"12.86717",
|
"11.27227",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
|
@ -1814,8 +1802,8 @@ History {
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"width": "92.92893",
|
"width": "98.58579",
|
||||||
"x": "3.53553",
|
"x": "0.70711",
|
||||||
"y": 0,
|
"y": 0,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
|
@ -2327,12 +2315,12 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id185",
|
"elementId": "id185",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": "369.21589",
|
"height": "374.05754",
|
||||||
"id": "id186",
|
"id": "id186",
|
||||||
"index": "a2",
|
"index": "a2",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
@ -2346,8 +2334,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"496.84035",
|
"502.78936",
|
||||||
"-369.21589",
|
"-374.05754",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
|
@ -2358,7 +2346,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id184",
|
"elementId": "id184",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -2366,9 +2354,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 10,
|
"version": 10,
|
||||||
"width": "496.84035",
|
"width": "502.78936",
|
||||||
"x": "2.18463",
|
"x": "-0.83465",
|
||||||
"y": "-38.80748",
|
"y": "-36.58211",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -2487,7 +2475,7 @@ History {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id185",
|
"elementId": "id185",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -2517,7 +2505,7 @@ History {
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id184",
|
"elementId": "id184",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -15123,7 +15111,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id58",
|
"elementId": "id58",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -15142,7 +15130,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -15154,7 +15142,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id56",
|
"elementId": "id56",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -15162,8 +15150,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 10,
|
"version": 10,
|
||||||
"width": "92.92893",
|
"width": "98.58579",
|
||||||
"x": "3.53553",
|
"x": "0.70711",
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -15494,7 +15482,7 @@ History {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id58",
|
"elementId": "id58",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -15524,7 +15512,7 @@ History {
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id56",
|
"elementId": "id56",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -15820,7 +15808,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id52",
|
"elementId": "id52",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -15839,7 +15827,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -15851,7 +15839,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id50",
|
"elementId": "id50",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -15859,8 +15847,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 10,
|
"version": 10,
|
||||||
"width": "92.92893",
|
"width": "98.58579",
|
||||||
"x": "3.53553",
|
"x": "0.70711",
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -16113,7 +16101,7 @@ History {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id52",
|
"elementId": "id52",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -16143,7 +16131,7 @@ History {
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id50",
|
"elementId": "id50",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -16439,7 +16427,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id64",
|
"elementId": "id64",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -16458,7 +16446,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -16470,7 +16458,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id62",
|
"elementId": "id62",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -16478,8 +16466,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 10,
|
"version": 10,
|
||||||
"width": "92.92893",
|
"width": "98.58579",
|
||||||
"x": "3.53553",
|
"x": "0.70711",
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -16732,7 +16720,7 @@ History {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id64",
|
"elementId": "id64",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -16762,7 +16750,7 @@ History {
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id62",
|
"elementId": "id62",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -17056,7 +17044,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id70",
|
"elementId": "id70",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -17075,7 +17063,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -17087,7 +17075,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id68",
|
"elementId": "id68",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -17095,8 +17083,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 10,
|
"version": 10,
|
||||||
"width": "92.92893",
|
"width": "98.58579",
|
||||||
"x": "3.53553",
|
"x": "0.70711",
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -17159,7 +17147,7 @@ History {
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id68",
|
"elementId": "id68",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
|
@ -17419,7 +17407,7 @@ History {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id70",
|
"elementId": "id70",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -17449,7 +17437,7 @@ History {
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id68",
|
"elementId": "id68",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -17769,7 +17757,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id77",
|
"elementId": "id77",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -17788,7 +17776,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"92.92893",
|
"98.58579",
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -17800,7 +17788,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id75",
|
"elementId": "id75",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -17808,8 +17796,8 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 11,
|
"version": 11,
|
||||||
"width": "92.92893",
|
"width": "98.58579",
|
||||||
"x": "3.53553",
|
"x": "0.70711",
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -17871,7 +17859,7 @@ History {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id77",
|
"elementId": "id77",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
|
@ -17886,7 +17874,7 @@ History {
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id75",
|
"elementId": "id75",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
|
@ -18147,7 +18135,7 @@ History {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id77",
|
"elementId": "id77",
|
||||||
"focus": -0,
|
"focus": -0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
|
@ -18177,7 +18165,7 @@ History {
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id75",
|
"elementId": "id75",
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 5,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
|
|
@ -191,12 +191,12 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id1",
|
"elementId": "id1",
|
||||||
"focus": "-0.46667",
|
"focus": "-0.46667",
|
||||||
"gap": 5,
|
"gap": 10,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": "94.40997",
|
"height": "87.29887",
|
||||||
"id": "id2",
|
"id": "id2",
|
||||||
"index": "a2",
|
"index": "a2",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
|
@ -210,8 +210,8 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"93.92893",
|
"86.85786",
|
||||||
"94.40997",
|
"87.29887",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
|
@ -223,7 +223,7 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id0",
|
"elementId": "id0",
|
||||||
"focus": "-0.60000",
|
"focus": "-0.60000",
|
||||||
"gap": 5,
|
"gap": 10,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
@ -232,8 +232,8 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 11,
|
"version": 11,
|
||||||
"versionNonce": 1051383431,
|
"versionNonce": 1051383431,
|
||||||
"width": "93.92893",
|
"width": "86.85786",
|
||||||
"x": "103.53553",
|
"x": "107.07107",
|
||||||
"y": "43.53553",
|
"y": "47.07107",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -4779,12 +4779,12 @@ describe("history", () => {
|
||||||
startBinding: expect.objectContaining({
|
startBinding: expect.objectContaining({
|
||||||
elementId: rect1.id,
|
elementId: rect1.id,
|
||||||
focus: 0,
|
focus: 0,
|
||||||
gap: FIXED_BINDING_DISTANCE,
|
gap: 1,
|
||||||
}),
|
}),
|
||||||
endBinding: expect.objectContaining({
|
endBinding: expect.objectContaining({
|
||||||
elementId: rect2.id,
|
elementId: rect2.id,
|
||||||
focus: -0,
|
focus: -0,
|
||||||
gap: FIXED_BINDING_DISTANCE,
|
gap: 1,
|
||||||
}),
|
}),
|
||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1246,7 +1246,7 @@ describe("Test Linear Elements", () => {
|
||||||
mouse.downAt(rect.x, rect.y);
|
mouse.downAt(rect.x, rect.y);
|
||||||
mouse.moveTo(200, 0);
|
mouse.moveTo(200, 0);
|
||||||
mouse.upAt(200, 0);
|
mouse.upAt(200, 0);
|
||||||
expect(arrow.width).toBeCloseTo(206.86, 0);
|
expect(arrow.width).toBeCloseTo(204, 0);
|
||||||
expect(rect.x).toBe(200);
|
expect(rect.x).toBe(200);
|
||||||
expect(rect.y).toBe(0);
|
expect(rect.y).toBe(0);
|
||||||
expect(handleBindTextResizeSpy).toHaveBeenCalledWith(
|
expect(handleBindTextResizeSpy).toHaveBeenCalledWith(
|
||||||
|
|
|
@ -128,10 +128,8 @@ describe("move element", () => {
|
||||||
expect(h.state.selectedElementIds[rectB.id]).toBeTruthy();
|
expect(h.state.selectedElementIds[rectB.id]).toBeTruthy();
|
||||||
expect([rectA.x, rectA.y]).toEqual([0, 0]);
|
expect([rectA.x, rectA.y]).toEqual([0, 0]);
|
||||||
expect([rectB.x, rectB.y]).toEqual([201, 2]);
|
expect([rectB.x, rectB.y]).toEqual([201, 2]);
|
||||||
expect([[arrow.x, arrow.y]]).toCloselyEqualPoints([[103.54, 43.53]]);
|
expect([[arrow.x, arrow.y]]).toCloselyEqualPoints([[107.07, 47.07]]);
|
||||||
expect([[arrow.width, arrow.height]]).toCloselyEqualPoints([
|
expect([[arrow.width, arrow.height]]).toCloselyEqualPoints([[86.86, 87.3]]);
|
||||||
[93.93, 94.41],
|
|
||||||
]);
|
|
||||||
|
|
||||||
h.elements.forEach((element) => expect(element).toMatchSnapshot());
|
h.elements.forEach((element) => expect(element).toMatchSnapshot());
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,7 +35,7 @@ test("unselected bound arrow updates when rotating its target element", async ()
|
||||||
expect(arrow.endBinding?.elementId).toEqual(rectangle.id);
|
expect(arrow.endBinding?.elementId).toEqual(rectangle.id);
|
||||||
expect(arrow.x).toBeCloseTo(-80);
|
expect(arrow.x).toBeCloseTo(-80);
|
||||||
expect(arrow.y).toBeCloseTo(50);
|
expect(arrow.y).toBeCloseTo(50);
|
||||||
expect(arrow.width).toBeCloseTo(119.58, 1);
|
expect(arrow.width).toBeCloseTo(116.7, 1);
|
||||||
expect(arrow.height).toBeCloseTo(0);
|
expect(arrow.height).toBeCloseTo(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,13 +72,13 @@ test("unselected bound arrows update when rotating their target elements", async
|
||||||
expect(ellipseArrow.x).toEqual(0);
|
expect(ellipseArrow.x).toEqual(0);
|
||||||
expect(ellipseArrow.y).toEqual(0);
|
expect(ellipseArrow.y).toEqual(0);
|
||||||
expect(ellipseArrow.points[0]).toEqual([0, 0]);
|
expect(ellipseArrow.points[0]).toEqual([0, 0]);
|
||||||
expect(ellipseArrow.points[1][0]).toBeCloseTo(54.36, 1);
|
expect(ellipseArrow.points[1][0]).toBeCloseTo(48.98, 1);
|
||||||
expect(ellipseArrow.points[1][1]).toBeCloseTo(139.61, 1);
|
expect(ellipseArrow.points[1][1]).toBeCloseTo(125.79, 1);
|
||||||
|
|
||||||
expect(textArrow.endBinding?.elementId).toEqual(text.id);
|
expect(textArrow.endBinding?.elementId).toEqual(text.id);
|
||||||
expect(textArrow.x).toEqual(360);
|
expect(textArrow.x).toEqual(360);
|
||||||
expect(textArrow.y).toEqual(300);
|
expect(textArrow.y).toEqual(300);
|
||||||
expect(textArrow.points[0]).toEqual([0, 0]);
|
expect(textArrow.points[0]).toEqual([0, 0]);
|
||||||
expect(textArrow.points[1][0]).toBeCloseTo(-100.12, 0);
|
expect(textArrow.points[1][0]).toBeCloseTo(-94, 0);
|
||||||
expect(textArrow.points[1][1]).toBeCloseTo(-123.63, 0);
|
expect(textArrow.points[1][1]).toBeCloseTo(-116.1, 0);
|
||||||
});
|
});
|
||||||
|
|
|
@ -157,13 +157,22 @@ export function curveIntersectLineSegment<
|
||||||
return bezierEquation(c, t);
|
return bezierEquation(c, t);
|
||||||
};
|
};
|
||||||
|
|
||||||
const solutions = [
|
let solution = calculate(initial_guesses[0]);
|
||||||
calculate(initial_guesses[0]),
|
if (solution) {
|
||||||
calculate(initial_guesses[1]),
|
return [solution];
|
||||||
calculate(initial_guesses[2]),
|
}
|
||||||
].filter((x, i, a): x is Point => x !== null && a.indexOf(x) === i);
|
|
||||||
|
|
||||||
return solutions;
|
solution = calculate(initial_guesses[1]);
|
||||||
|
if (solution) {
|
||||||
|
return [solution];
|
||||||
|
}
|
||||||
|
|
||||||
|
solution = calculate(initial_guesses[2]);
|
||||||
|
if (solution) {
|
||||||
|
return [solution];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -91,10 +91,9 @@ export function isPoint(p: unknown): p is LocalPoint | GlobalPoint {
|
||||||
export function pointsEqual<Point extends GlobalPoint | LocalPoint>(
|
export function pointsEqual<Point extends GlobalPoint | LocalPoint>(
|
||||||
a: Point,
|
a: Point,
|
||||||
b: Point,
|
b: Point,
|
||||||
precision = PRECISION,
|
|
||||||
): boolean {
|
): boolean {
|
||||||
const abs = Math.abs;
|
const abs = Math.abs;
|
||||||
return abs(a[0] - b[0]) < precision && abs(a[1] - b[1]) < precision;
|
return abs(a[0] - b[0]) < PRECISION && abs(a[1] - b[1]) < PRECISION;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const clamp = (value: number, min: number, max: number) => {
|
||||||
|
|
||||||
export const round = (
|
export const round = (
|
||||||
value: number,
|
value: number,
|
||||||
precision: number = (Math.log(1 / PRECISION) * Math.LOG10E + 1) | 0,
|
precision: number,
|
||||||
func: "round" | "floor" | "ceil" = "round",
|
func: "round" | "floor" | "ceil" = "round",
|
||||||
) => {
|
) => {
|
||||||
const multiplier = Math.pow(10, precision);
|
const multiplier = Math.pow(10, precision);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue