mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-09-27 03:09:57 +02:00
Fix all tests
Signed-off-by: Mark Tolmacs <mark@lazycat.hu> fix(transform): Fix group resize and rotate fix(binding): Harmonize binding param usage fix: Center focus point Signed-off-by: Mark Tolmacs <mark@lazycat.hu> chore: Trigger build Remove binding gap Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
@@ -35,10 +35,8 @@ import {
|
||||
import {
|
||||
bindingBorderTest,
|
||||
getHoveredElementForBinding,
|
||||
getHoveredElementForBindingAndIfItsPrecise,
|
||||
hitElementItself,
|
||||
intersectElementWithLineSegment,
|
||||
maxBindingDistanceFromOutline,
|
||||
} from "./collision";
|
||||
import { distanceToElement } from "./distance";
|
||||
import {
|
||||
@@ -114,7 +112,6 @@ export type BindingStrategy =
|
||||
};
|
||||
|
||||
export const FIXED_BINDING_DISTANCE = 5;
|
||||
export const BINDING_HIGHLIGHT_THICKNESS = 10;
|
||||
|
||||
export const shouldEnableBindingForPointerEvent = (
|
||||
event: React.PointerEvent<HTMLElement>,
|
||||
@@ -202,7 +199,6 @@ const bindOrUnbindBindingElementEdge = (
|
||||
const getOriginalBindingsIfStillCloseToBindingEnds = (
|
||||
linearElement: NonDeleted<ExcalidrawArrowElement>,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
zoom?: AppState["zoom"],
|
||||
): (NonDeleted<ExcalidrawElement> | null)[] =>
|
||||
(["start", "end"] as const).map((edge) => {
|
||||
const coors = tupleToCoors(
|
||||
@@ -224,7 +220,6 @@ const getOriginalBindingsIfStillCloseToBindingEnds = (
|
||||
element,
|
||||
pointFrom<GlobalPoint>(coors.x, coors.y),
|
||||
elementsMap,
|
||||
zoom,
|
||||
)
|
||||
) {
|
||||
return element;
|
||||
@@ -328,30 +323,16 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = (
|
||||
draggingPoints.get(startDragged ? startIdx : endIdx)!.point,
|
||||
elementsMap,
|
||||
);
|
||||
const { hovered, hit } = getHoveredElementForBindingAndIfItsPrecise(
|
||||
point,
|
||||
elements,
|
||||
elementsMap,
|
||||
appState.zoom,
|
||||
true,
|
||||
);
|
||||
const hit = getHoveredElementForBinding(point, elements, elementsMap);
|
||||
|
||||
// With new arrows this handles the binding at arrow creation
|
||||
if (startDragged) {
|
||||
if (hovered) {
|
||||
if (hit) {
|
||||
start = {
|
||||
element: hovered,
|
||||
mode: "inside",
|
||||
focusPoint: point,
|
||||
};
|
||||
} else {
|
||||
start = {
|
||||
element: hovered,
|
||||
mode: "orbit",
|
||||
focusPoint: point,
|
||||
};
|
||||
}
|
||||
if (hit) {
|
||||
start = {
|
||||
element: hit,
|
||||
mode: "inside",
|
||||
focusPoint: point,
|
||||
};
|
||||
} else {
|
||||
start = { mode: null };
|
||||
}
|
||||
@@ -365,41 +346,24 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = (
|
||||
appState?.selectedLinearElement?.pointerDownState.arrowOriginalStartPoint;
|
||||
|
||||
// Inside -> inside binding
|
||||
if (hovered && hit && arrow.startBinding?.elementId === hovered.id) {
|
||||
if (hit && arrow.startBinding?.elementId === hit.id) {
|
||||
const center = pointFrom<GlobalPoint>(
|
||||
hovered.x + hovered.width / 2,
|
||||
hovered.y + hovered.height / 2,
|
||||
hit.x + hit.width / 2,
|
||||
hit.y + hit.height / 2,
|
||||
);
|
||||
|
||||
return {
|
||||
start: {
|
||||
mode: "inside",
|
||||
element: hovered,
|
||||
element: hit,
|
||||
focusPoint: arrowOriginalStartPoint ?? center,
|
||||
},
|
||||
end: { mode: "inside", element: hovered, focusPoint: point },
|
||||
};
|
||||
}
|
||||
|
||||
// Inside -> orbit binding
|
||||
if (hovered && !hit && arrow.startBinding?.elementId === hovered.id) {
|
||||
const center = pointFrom<GlobalPoint>(
|
||||
hovered.x + hovered.width / 2,
|
||||
hovered.y + hovered.height / 2,
|
||||
);
|
||||
|
||||
return {
|
||||
start: {
|
||||
mode: globalBindMode === "inside" ? "inside" : "orbit",
|
||||
element: hovered,
|
||||
focusPoint: arrowOriginalStartPoint ?? center,
|
||||
},
|
||||
end: { mode: null },
|
||||
end: { mode: "inside", element: hit, focusPoint: point },
|
||||
};
|
||||
}
|
||||
|
||||
// Inside -> outside binding
|
||||
if (arrow.startBinding && arrow.startBinding.elementId !== hovered?.id) {
|
||||
if (arrow.startBinding && arrow.startBinding.elementId !== hit?.id) {
|
||||
const otherElement = elementsMap.get(
|
||||
arrow.startBinding.elementId,
|
||||
) as ExcalidrawBindableElement;
|
||||
@@ -411,21 +375,26 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = (
|
||||
const other: BindingStrategy = {
|
||||
mode: otherIsInsideBinding ? "inside" : "orbit",
|
||||
element: otherElement,
|
||||
focusPoint: snapToCenter(
|
||||
otherElement,
|
||||
elementsMap,
|
||||
arrowOriginalStartPoint ?? pointFrom<GlobalPoint>(arrow.x, arrow.y),
|
||||
),
|
||||
focusPoint: otherIsInsideBinding
|
||||
? arrowOriginalStartPoint ?? pointFrom<GlobalPoint>(arrow.x, arrow.y)
|
||||
: snapToCenter(
|
||||
otherElement,
|
||||
elementsMap,
|
||||
arrowOriginalStartPoint ??
|
||||
pointFrom<GlobalPoint>(arrow.x, arrow.y),
|
||||
),
|
||||
};
|
||||
|
||||
// We are hovering another element with the end point
|
||||
let current: BindingStrategy;
|
||||
if (hovered) {
|
||||
if (hit) {
|
||||
const isInsideBinding = globalBindMode === "inside";
|
||||
current = {
|
||||
mode: isInsideBinding ? "inside" : "orbit",
|
||||
element: hovered,
|
||||
focusPoint: snapToCenter(hovered, elementsMap, point),
|
||||
element: hit,
|
||||
focusPoint: isInsideBinding
|
||||
? point
|
||||
: snapToCenter(hit, elementsMap, point),
|
||||
};
|
||||
} else {
|
||||
current = { mode: null };
|
||||
@@ -439,13 +408,13 @@ const bindingStrategyForNewSimpleArrowEndpointDragging = (
|
||||
|
||||
// No start binding
|
||||
if (!arrow.startBinding) {
|
||||
if (hovered) {
|
||||
if (hit) {
|
||||
const isInsideBinding =
|
||||
globalBindMode === "inside" || isAlwaysInsideBinding(hovered);
|
||||
globalBindMode === "inside" || isAlwaysInsideBinding(hit);
|
||||
|
||||
end = {
|
||||
mode: isInsideBinding ? "inside" : "orbit",
|
||||
element: hovered,
|
||||
element: hit,
|
||||
focusPoint: point,
|
||||
};
|
||||
} else {
|
||||
@@ -466,7 +435,6 @@ const bindingStrategyForSimpleArrowEndpointDragging = (
|
||||
oppositeBinding: FixedPointBinding | null,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||
zoom: AppState["zoom"],
|
||||
globalBindMode?: AppState["bindMode"],
|
||||
opts?: {
|
||||
newArrow?: boolean;
|
||||
@@ -476,23 +444,14 @@ const bindingStrategyForSimpleArrowEndpointDragging = (
|
||||
let current: BindingStrategy = { mode: undefined };
|
||||
let other: BindingStrategy = { mode: undefined };
|
||||
|
||||
const { hovered, hit } = getHoveredElementForBindingAndIfItsPrecise(
|
||||
point,
|
||||
elements,
|
||||
elementsMap,
|
||||
zoom,
|
||||
true,
|
||||
);
|
||||
const hit = getHoveredElementForBinding(point, elements, elementsMap);
|
||||
|
||||
// If the global bind mode is in free binding mode, just bind
|
||||
// where the pointer is and keep the other end intact
|
||||
if (
|
||||
globalBindMode === "inside" ||
|
||||
(hovered && isAlwaysInsideBinding(hovered))
|
||||
) {
|
||||
current = hovered
|
||||
if (globalBindMode === "inside" || (hit && isAlwaysInsideBinding(hit))) {
|
||||
current = hit
|
||||
? {
|
||||
element: hovered,
|
||||
element: hit,
|
||||
focusPoint: point,
|
||||
mode: "inside",
|
||||
}
|
||||
@@ -503,90 +462,58 @@ const bindingStrategyForSimpleArrowEndpointDragging = (
|
||||
|
||||
// Dragged point is outside of any bindable element
|
||||
// so we break any existing binding
|
||||
if (!hovered) {
|
||||
if (!hit) {
|
||||
return { current: { mode: null }, other };
|
||||
}
|
||||
|
||||
// Dragged point is on the binding gap of a bindable element
|
||||
if (!hit) {
|
||||
// If the opposite binding (if exists) is on the same element
|
||||
if (oppositeBinding) {
|
||||
if (oppositeBinding.elementId === hovered.id) {
|
||||
return { current: { mode: null }, other };
|
||||
}
|
||||
// The opposite binding is on a different element
|
||||
// eslint-disable-next-line no-else-return
|
||||
else {
|
||||
current = {
|
||||
element: hovered,
|
||||
mode: "orbit",
|
||||
focusPoint: opts?.newArrow
|
||||
? pointFrom<GlobalPoint>(
|
||||
hovered.x + hovered.width / 2,
|
||||
hovered.y + hovered.height / 2,
|
||||
)
|
||||
: point,
|
||||
};
|
||||
|
||||
return { current, other };
|
||||
}
|
||||
}
|
||||
|
||||
// No opposite binding or the opposite binding is on a different element
|
||||
current = { element: hovered, mode: "orbit", focusPoint: point };
|
||||
}
|
||||
// The dragged point is inside the hovered bindable element
|
||||
else {
|
||||
// The opposite binding is on the same element
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (oppositeBinding) {
|
||||
if (oppositeBinding.elementId === hovered.id) {
|
||||
// The opposite binding is on the binding gap of the same element
|
||||
if (oppositeBinding.mode !== "inside") {
|
||||
current = { element: hovered, mode: "orbit", focusPoint: point };
|
||||
other = { mode: null };
|
||||
|
||||
return { current, other };
|
||||
}
|
||||
// The opposite binding is inside the same element
|
||||
// eslint-disable-next-line no-else-return
|
||||
else {
|
||||
current = { element: hovered, mode: "inside", focusPoint: point };
|
||||
// The opposite binding is on the same element
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (oppositeBinding) {
|
||||
if (oppositeBinding.elementId === hit.id) {
|
||||
// The opposite binding is on the binding gap of the same element
|
||||
if (oppositeBinding.mode !== "inside") {
|
||||
current = { element: hit, mode: "orbit", focusPoint: point };
|
||||
other = { mode: null };
|
||||
|
||||
return { current, other };
|
||||
}
|
||||
return { current, other };
|
||||
}
|
||||
// The opposite binding is on a different element
|
||||
// The opposite binding is inside the same element
|
||||
// eslint-disable-next-line no-else-return
|
||||
else {
|
||||
current = {
|
||||
element: hovered,
|
||||
mode: "orbit",
|
||||
focusPoint: opts?.newArrow
|
||||
? pointFrom<GlobalPoint>(
|
||||
hovered.x + hovered.width / 2,
|
||||
hovered.y + hovered.height / 2,
|
||||
)
|
||||
: point,
|
||||
};
|
||||
current = { element: hit, mode: "inside", focusPoint: point };
|
||||
|
||||
return { current, other };
|
||||
}
|
||||
}
|
||||
// The opposite binding is on a different element or no binding
|
||||
// The opposite binding is on a different element
|
||||
// eslint-disable-next-line no-else-return
|
||||
else {
|
||||
current = {
|
||||
element: hovered,
|
||||
element: hit,
|
||||
mode: "orbit",
|
||||
focusPoint: opts?.newArrow
|
||||
? pointFrom<GlobalPoint>(
|
||||
hovered.x + hovered.width / 2,
|
||||
hovered.y + hovered.height / 2,
|
||||
hit.x + hit.width / 2,
|
||||
hit.y + hit.height / 2,
|
||||
)
|
||||
: point,
|
||||
};
|
||||
|
||||
return { current, other };
|
||||
}
|
||||
}
|
||||
// The opposite binding is on a different element or no binding
|
||||
else {
|
||||
current = {
|
||||
element: hit,
|
||||
mode: "orbit",
|
||||
focusPoint: opts?.newArrow
|
||||
? pointFrom<GlobalPoint>(hit.x + hit.width / 2, hit.y + hit.height / 2)
|
||||
: point,
|
||||
};
|
||||
}
|
||||
|
||||
// Must return as only one endpoint is dragged, therefore
|
||||
// the end binding strategy might accidentally gets overriden
|
||||
@@ -649,7 +576,6 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
||||
p,
|
||||
elements,
|
||||
elementsMap,
|
||||
appState.zoom,
|
||||
);
|
||||
const current: BindingStrategy = hoveredElement
|
||||
? {
|
||||
@@ -697,7 +623,6 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
||||
arrow.endBinding,
|
||||
elementsMap,
|
||||
elements,
|
||||
appState.zoom,
|
||||
globalBindMode,
|
||||
opts,
|
||||
);
|
||||
@@ -719,7 +644,6 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
||||
arrow.startBinding,
|
||||
elementsMap,
|
||||
elements,
|
||||
appState.zoom,
|
||||
globalBindMode,
|
||||
opts,
|
||||
);
|
||||
@@ -748,7 +672,6 @@ export const bindOrUnbindBindingElements = (
|
||||
export const getSuggestedBindingsForBindingElements = (
|
||||
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
zoom: AppState["zoom"],
|
||||
): SuggestedBinding[] => {
|
||||
// HOT PATH: Bail out if selected elements list is too large
|
||||
if (selectedElements.length > 50) {
|
||||
@@ -759,11 +682,7 @@ export const getSuggestedBindingsForBindingElements = (
|
||||
selectedElements
|
||||
.filter(isArrowElement)
|
||||
.flatMap((element) =>
|
||||
getOriginalBindingsIfStillCloseToBindingEnds(
|
||||
element,
|
||||
elementsMap,
|
||||
zoom,
|
||||
),
|
||||
getOriginalBindingsIfStillCloseToBindingEnds(element, elementsMap),
|
||||
)
|
||||
.filter(
|
||||
(element): element is NonDeleted<ExcalidrawBindableElement> =>
|
||||
@@ -785,7 +704,6 @@ export const maybeSuggestBindingsForBindingElementAtCoords = (
|
||||
linearElement: NonDeleted<ExcalidrawArrowElement>,
|
||||
startOrEndOrBoth: "start" | "end" | "both",
|
||||
scene: Scene,
|
||||
zoom: AppState["zoom"],
|
||||
): ExcalidrawBindableElement[] => {
|
||||
const startCoords = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
linearElement,
|
||||
@@ -801,13 +719,11 @@ export const maybeSuggestBindingsForBindingElementAtCoords = (
|
||||
startCoords,
|
||||
scene.getNonDeletedElements(),
|
||||
scene.getNonDeletedElementsMap(),
|
||||
zoom,
|
||||
);
|
||||
const endHovered = getHoveredElementForBinding(
|
||||
endCoords,
|
||||
scene.getNonDeletedElements(),
|
||||
scene.getNonDeletedElementsMap(),
|
||||
zoom,
|
||||
);
|
||||
|
||||
const suggestedBindings = [];
|
||||
@@ -1075,7 +991,6 @@ export const getHeadingForElbowArrowSnap = (
|
||||
aabb: Bounds | undefined | null,
|
||||
origPoint: GlobalPoint,
|
||||
elementsMap: ElementsMap,
|
||||
zoom?: AppState["zoom"],
|
||||
): Heading => {
|
||||
const otherPointHeading = vectorToHeading(vectorFromPoint(otherPoint, p));
|
||||
|
||||
@@ -1084,14 +999,8 @@ export const getHeadingForElbowArrowSnap = (
|
||||
}
|
||||
|
||||
const d = distanceToElement(bindableElement, elementsMap, origPoint);
|
||||
const bindDistance = maxBindingDistanceFromOutline(
|
||||
bindableElement,
|
||||
bindableElement.width,
|
||||
bindableElement.height,
|
||||
zoom,
|
||||
);
|
||||
|
||||
const distance = d > bindDistance ? null : d;
|
||||
const distance = d > 0 ? null : d;
|
||||
|
||||
if (!distance) {
|
||||
return vectorToHeading(
|
||||
|
@@ -25,7 +25,7 @@ import type {
|
||||
Radians,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import type { AppState, FrameNameBounds } from "@excalidraw/excalidraw/types";
|
||||
import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { isPathALoop } from "./utils";
|
||||
import {
|
||||
@@ -58,8 +58,6 @@ import { LinearElementEditor } from "./linearElementEditor";
|
||||
|
||||
import { distanceToElement } from "./distance";
|
||||
|
||||
import { BINDING_HIGHLIGHT_THICKNESS, FIXED_BINDING_DISTANCE } from "./binding";
|
||||
|
||||
import type {
|
||||
ElementsMap,
|
||||
ExcalidrawBindableElement,
|
||||
@@ -206,40 +204,12 @@ export const hitElementBoundText = (
|
||||
return isPointInElement(point, boundTextElement, elementsMap);
|
||||
};
|
||||
|
||||
export const maxBindingDistanceFromOutline = (
|
||||
element: ExcalidrawElement,
|
||||
elementWidth: number,
|
||||
elementHeight: number,
|
||||
zoom?: AppState["zoom"],
|
||||
): number => {
|
||||
const zoomValue = zoom?.value && zoom.value < 1 ? zoom.value : 1;
|
||||
|
||||
// Aligns diamonds with rectangles
|
||||
const shapeRatio = element.type === "diamond" ? 1 / Math.sqrt(2) : 1;
|
||||
const smallerDimension = shapeRatio * Math.min(elementWidth, elementHeight);
|
||||
|
||||
return Math.max(
|
||||
16,
|
||||
// bigger bindable boundary for bigger elements
|
||||
Math.min(0.25 * smallerDimension, 32),
|
||||
// keep in sync with the zoomed highlight
|
||||
BINDING_HIGHLIGHT_THICKNESS / zoomValue + FIXED_BINDING_DISTANCE,
|
||||
);
|
||||
};
|
||||
|
||||
export const bindingBorderTest = (
|
||||
element: NonDeleted<ExcalidrawBindableElement>,
|
||||
[x, y]: Readonly<GlobalPoint>,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
zoom?: AppState["zoom"],
|
||||
): boolean => {
|
||||
const p = pointFrom<GlobalPoint>(x, y);
|
||||
const threshold = maxBindingDistanceFromOutline(
|
||||
element,
|
||||
element.width,
|
||||
element.height,
|
||||
zoom,
|
||||
);
|
||||
const shouldTestInside =
|
||||
// disable fullshape snapping for frame elements so we
|
||||
// can bind to frame children
|
||||
@@ -247,12 +217,7 @@ export const bindingBorderTest = (
|
||||
|
||||
// PERF: Run a cheap test to see if the binding element
|
||||
// is even close to the element
|
||||
const bounds = [
|
||||
x - threshold,
|
||||
y - threshold,
|
||||
x + threshold,
|
||||
y + threshold,
|
||||
] as Bounds;
|
||||
const bounds = [x - 1, y - 1, x + 1, y + 1] as Bounds;
|
||||
const elementBounds = getElementBounds(element, elementsMap);
|
||||
if (!doBoundsIntersect(bounds, elementBounds)) {
|
||||
return false;
|
||||
@@ -267,15 +232,14 @@ export const bindingBorderTest = (
|
||||
const distance = distanceToElement(element, elementsMap, p);
|
||||
|
||||
return shouldTestInside
|
||||
? intersections.length === 0 || distance <= threshold
|
||||
: intersections.length > 0 && distance <= threshold;
|
||||
? intersections.length === 0
|
||||
: intersections.length > 0 && distance <= 1;
|
||||
};
|
||||
|
||||
export const getHoveredElementForBinding = (
|
||||
point: Readonly<GlobalPoint>,
|
||||
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
zoom?: AppState["zoom"],
|
||||
): NonDeleted<ExcalidrawBindableElement> | null => {
|
||||
const candidateElements: NonDeleted<ExcalidrawBindableElement>[] = [];
|
||||
// We need to to hit testing from front (end of the array) to back (beginning of the array)
|
||||
@@ -291,7 +255,7 @@ export const getHoveredElementForBinding = (
|
||||
|
||||
if (
|
||||
isBindableElement(element, false) &&
|
||||
bindingBorderTest(element, point, elementsMap, zoom)
|
||||
bindingBorderTest(element, point, elementsMap)
|
||||
) {
|
||||
candidateElements.push(element);
|
||||
}
|
||||
@@ -313,36 +277,6 @@ export const getHoveredElementForBinding = (
|
||||
.pop() as NonDeleted<ExcalidrawBindableElement>;
|
||||
};
|
||||
|
||||
export const getHoveredElementForBindingAndIfItsPrecise = (
|
||||
point: GlobalPoint,
|
||||
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
zoom: AppState["zoom"],
|
||||
shouldTestInside: boolean = true,
|
||||
): {
|
||||
hovered: NonDeleted<ExcalidrawBindableElement> | null;
|
||||
hit: boolean;
|
||||
} => {
|
||||
const hoveredElement = getHoveredElementForBinding(
|
||||
point,
|
||||
elements,
|
||||
elementsMap,
|
||||
zoom,
|
||||
);
|
||||
// TODO: Optimize this to avoid recalculating the point - element distance
|
||||
const hit =
|
||||
!!hoveredElement &&
|
||||
hitElementItself({
|
||||
element: hoveredElement,
|
||||
elementsMap,
|
||||
point,
|
||||
threshold: 0,
|
||||
overrideShouldTestInside: shouldTestInside,
|
||||
});
|
||||
|
||||
return { hovered: hoveredElement, hit };
|
||||
};
|
||||
|
||||
/**
|
||||
* Intersect a line with an element for binding test
|
||||
*
|
||||
|
@@ -1216,19 +1216,9 @@ const getElbowArrowData = (
|
||||
if (options?.isDragging) {
|
||||
const elements = Array.from(elementsMap.values());
|
||||
hoveredStartElement =
|
||||
getHoveredElement(
|
||||
origStartGlobalPoint,
|
||||
elementsMap,
|
||||
elements,
|
||||
options?.zoom,
|
||||
) || null;
|
||||
getHoveredElement(origStartGlobalPoint, elementsMap, elements) || null;
|
||||
hoveredEndElement =
|
||||
getHoveredElement(
|
||||
origEndGlobalPoint,
|
||||
elementsMap,
|
||||
elements,
|
||||
options?.zoom,
|
||||
) || null;
|
||||
getHoveredElement(origEndGlobalPoint, elementsMap, elements) || null;
|
||||
} else {
|
||||
hoveredStartElement = arrow.startBinding
|
||||
? getBindableElementForId(arrow.startBinding.elementId, elementsMap) ||
|
||||
@@ -2262,9 +2252,8 @@ const getHoveredElement = (
|
||||
origPoint: GlobalPoint,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||
zoom?: AppState["zoom"],
|
||||
) => {
|
||||
return getHoveredElementForBinding(origPoint, elements, elementsMap, zoom);
|
||||
return getHoveredElementForBinding(origPoint, elements, elementsMap);
|
||||
};
|
||||
|
||||
const gridAddressesEqual = (a: GridAddress, b: GridAddress): boolean =>
|
||||
|
@@ -465,7 +465,6 @@ export class LinearElementEditor {
|
||||
? "start"
|
||||
: "end",
|
||||
app.scene,
|
||||
app.state.zoom,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2001,7 +2000,6 @@ const pointDraggingUpdates = (
|
||||
newGlobalPointPosition,
|
||||
elements,
|
||||
elementsMap,
|
||||
app.state.zoom,
|
||||
);
|
||||
const otherGlobalPoint =
|
||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
@@ -2011,12 +2009,7 @@ const pointDraggingUpdates = (
|
||||
);
|
||||
const otherPointInsideElement =
|
||||
!!hoveredElement &&
|
||||
!!bindingBorderTest(
|
||||
hoveredElement,
|
||||
otherGlobalPoint,
|
||||
elementsMap,
|
||||
app.state.zoom,
|
||||
);
|
||||
!!bindingBorderTest(hoveredElement, otherGlobalPoint, elementsMap);
|
||||
|
||||
if (
|
||||
isBindingEnabled(app.state) &&
|
||||
|
@@ -20,7 +20,11 @@ import type { PointerDownState } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { getArrowLocalFixedPoints, updateBoundElements } from "./binding";
|
||||
import {
|
||||
getArrowLocalFixedPoints,
|
||||
unbindBindingElement,
|
||||
updateBoundElements,
|
||||
} from "./binding";
|
||||
import {
|
||||
getElementAbsoluteCoords,
|
||||
getCommonBounds,
|
||||
@@ -229,12 +233,18 @@ const rotateSingleElement = (
|
||||
if (isBindingElement(element)) {
|
||||
update = {
|
||||
...update,
|
||||
startBinding: null,
|
||||
endBinding: null,
|
||||
} as ElementUpdate<ExcalidrawArrowElement>;
|
||||
|
||||
if (element.startBinding) {
|
||||
unbindBindingElement(element, "start", scene);
|
||||
}
|
||||
if (element.endBinding) {
|
||||
unbindBindingElement(element, "end", scene);
|
||||
}
|
||||
}
|
||||
|
||||
scene.mutateElement(element, update);
|
||||
|
||||
if (boundTextElementId) {
|
||||
const textElement =
|
||||
scene.getElement<ExcalidrawTextElementWithContainer>(boundTextElementId);
|
||||
@@ -399,6 +409,11 @@ const rotateMultipleElements = (
|
||||
centerAngle -= centerAngle % SHIFT_LOCKING_ANGLE;
|
||||
}
|
||||
|
||||
const rotatedElementsMap = new Map<
|
||||
ExcalidrawElement["id"],
|
||||
NonDeletedExcalidrawElement
|
||||
>(elements.map((element) => [element.id, element]));
|
||||
|
||||
for (const element of elements) {
|
||||
if (!isFrameLikeElement(element)) {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
@@ -429,6 +444,19 @@ const rotateMultipleElements = (
|
||||
simultaneouslyUpdated: elements,
|
||||
});
|
||||
|
||||
if (isBindingElement(element)) {
|
||||
if (element.startBinding) {
|
||||
if (!rotatedElementsMap.has(element.startBinding.elementId)) {
|
||||
unbindBindingElement(element, "start", scene);
|
||||
}
|
||||
}
|
||||
if (element.endBinding) {
|
||||
if (!rotatedElementsMap.has(element.endBinding.elementId)) {
|
||||
unbindBindingElement(element, "end", scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const boundText = getBoundTextElement(element, elementsMap);
|
||||
if (boundText && !isArrowElement(element)) {
|
||||
scene.mutateElement(boundText, {
|
||||
@@ -845,8 +873,11 @@ export const resizeSingleElement = (
|
||||
if (latestElement.startBinding) {
|
||||
updates = {
|
||||
...updates,
|
||||
startBinding: null,
|
||||
} as ElementUpdate<ExcalidrawArrowElement>;
|
||||
|
||||
if (latestElement.startBinding) {
|
||||
unbindBindingElement(latestElement, "start", scene);
|
||||
}
|
||||
}
|
||||
|
||||
if (latestElement.endBinding) {
|
||||
@@ -1408,6 +1439,10 @@ export const resizeMultipleElements = (
|
||||
}
|
||||
|
||||
const elementsToUpdate = elementsAndUpdates.map(({ element }) => element);
|
||||
const resizedElementsMap = new Map<
|
||||
ExcalidrawElement["id"],
|
||||
NonDeletedExcalidrawElement
|
||||
>(elementsAndUpdates.map(({ element }) => [element.id, element]));
|
||||
|
||||
for (const {
|
||||
element,
|
||||
@@ -1421,6 +1456,19 @@ export const resizeMultipleElements = (
|
||||
simultaneouslyUpdated: elementsToUpdate,
|
||||
});
|
||||
|
||||
if (isBindingElement(element)) {
|
||||
if (element.startBinding) {
|
||||
if (!resizedElementsMap.has(element.startBinding.elementId)) {
|
||||
unbindBindingElement(element, "start", scene);
|
||||
}
|
||||
}
|
||||
if (element.endBinding) {
|
||||
if (!resizedElementsMap.has(element.endBinding.elementId)) {
|
||||
unbindBindingElement(element, "end", scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement && boundTextFontSize) {
|
||||
scene.mutateElement(boundTextElement, {
|
||||
|
@@ -4537,7 +4537,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
(element) => element.id !== elbowArrow?.id || step !== 0,
|
||||
),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state.zoom,
|
||||
),
|
||||
});
|
||||
|
||||
@@ -4766,7 +4765,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointFrom<GlobalPoint>(scenePointer.x, scenePointer.y),
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state.zoom,
|
||||
);
|
||||
|
||||
if (hoveredElement && !this.bindModeHandler) {
|
||||
@@ -6098,7 +6096,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
newElement,
|
||||
"end",
|
||||
this.scene,
|
||||
this.state.zoom,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
@@ -6203,7 +6200,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
|
||||
this.scene.getNonDeletedElements(),
|
||||
elementsMap,
|
||||
this.state.zoom,
|
||||
);
|
||||
|
||||
// Timed bind mode handler for arrow elements
|
||||
@@ -7991,7 +7987,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
),
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state.zoom,
|
||||
);
|
||||
|
||||
this.setState({
|
||||
@@ -8197,7 +8192,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
lastGlobalPoint,
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state.zoom,
|
||||
);
|
||||
|
||||
// clicking inside commit zone → finalize arrow
|
||||
@@ -8318,7 +8312,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
point,
|
||||
this.scene.getNonDeletedElements(),
|
||||
elementsMap,
|
||||
this.state.zoom,
|
||||
);
|
||||
|
||||
this.scene.mutateElement(element, {
|
||||
@@ -8764,7 +8757,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
|
||||
this.scene.getNonDeletedElements(),
|
||||
elementsMap,
|
||||
this.state.zoom,
|
||||
);
|
||||
|
||||
// Timed bind mode handler for arrow elements
|
||||
@@ -9141,7 +9133,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
suggestedBindings: getSuggestedBindingsForBindingElements(
|
||||
selectedElements,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state.zoom,
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -9451,7 +9442,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
newElement,
|
||||
"end",
|
||||
this.scene,
|
||||
this.state.zoom,
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -11002,7 +10992,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state.zoom,
|
||||
);
|
||||
this.setState({
|
||||
suggestedBindings:
|
||||
@@ -11625,7 +11614,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const suggestedBindings = getSuggestedBindingsForBindingElements(
|
||||
selectedElements,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state.zoom,
|
||||
);
|
||||
|
||||
const elementsToHighlight = new Set<ExcalidrawElement>();
|
||||
|
@@ -16,10 +16,7 @@ import {
|
||||
throttleRAF,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
FIXED_BINDING_DISTANCE,
|
||||
maxBindingDistanceFromOutline,
|
||||
} from "@excalidraw/element";
|
||||
import { FIXED_BINDING_DISTANCE } from "@excalidraw/element";
|
||||
import { LinearElementEditor } from "@excalidraw/element";
|
||||
import {
|
||||
getOmitSidesForDevice,
|
||||
@@ -197,12 +194,7 @@ const renderBindingHighlightForBindableElement = (
|
||||
elementsMap: ElementsMap,
|
||||
zoom: InteractiveCanvasAppState["zoom"],
|
||||
) => {
|
||||
const padding = maxBindingDistanceFromOutline(
|
||||
element,
|
||||
element.width,
|
||||
element.height,
|
||||
zoom,
|
||||
);
|
||||
const padding = 5;
|
||||
|
||||
context.fillStyle = "rgba(0,0,0,.05)";
|
||||
|
||||
@@ -251,14 +243,9 @@ const renderBindingHighlightForSuggestedPointBinding = (
|
||||
elementsMap: ElementsMap,
|
||||
zoom: InteractiveCanvasAppState["zoom"],
|
||||
) => {
|
||||
const [element, startOrEnd, bindableElement] = suggestedBinding;
|
||||
const [element, startOrEnd] = suggestedBinding;
|
||||
|
||||
const threshold = maxBindingDistanceFromOutline(
|
||||
bindableElement,
|
||||
bindableElement.width,
|
||||
bindableElement.height,
|
||||
zoom,
|
||||
);
|
||||
const threshold = 0;
|
||||
|
||||
context.strokeStyle = "rgba(0,0,0,0)";
|
||||
context.fillStyle = "rgba(0,0,0,.05)";
|
||||
|
@@ -143,7 +143,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 5,
|
||||
"version": 13,
|
||||
"width": 100,
|
||||
"x": -100,
|
||||
"y": -50,
|
||||
@@ -173,7 +173,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"version": 9,
|
||||
"width": 100,
|
||||
"x": 100,
|
||||
"y": -50,
|
||||
@@ -188,11 +188,18 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
"endBinding": null,
|
||||
"endBinding": {
|
||||
"elementId": "id15",
|
||||
"fixedPoint": [
|
||||
"0.50000",
|
||||
1,
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": "0.03787",
|
||||
"height": "113.98784",
|
||||
"id": "id4",
|
||||
"index": "a2",
|
||||
"isDeleted": false,
|
||||
@@ -208,7 +215,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
],
|
||||
[
|
||||
95,
|
||||
"0.03787",
|
||||
"113.98784",
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
@@ -222,23 +229,58 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "orbit",
|
||||
"mode": "inside",
|
||||
},
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 16,
|
||||
"version": 37,
|
||||
"width": 95,
|
||||
"x": 5,
|
||||
"y": "0.01199",
|
||||
"x": 0,
|
||||
"y": "0.01000",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] number of elements 1`] = `3`;
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] element 3 1`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id4",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 50,
|
||||
"id": "id15",
|
||||
"index": "a3",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"width": 50,
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] number of renders 1`] = `12`;
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] number of elements 1`] = `4`;
|
||||
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] number of renders 1`] = `21`;
|
||||
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and the arrow got bound to a different element in the meantime > [end of test] redo stack 1`] = `
|
||||
[
|
||||
@@ -256,7 +298,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"id1": {
|
||||
"deleted": {
|
||||
"boundElements": [],
|
||||
"version": 4,
|
||||
"version": 9,
|
||||
},
|
||||
"inserted": {
|
||||
"boundElements": [
|
||||
@@ -265,13 +307,35 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"version": 3,
|
||||
"version": 8,
|
||||
},
|
||||
},
|
||||
"id15": {
|
||||
"deleted": {
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "id4",
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"version": 9,
|
||||
},
|
||||
"inserted": {
|
||||
"boundElements": [],
|
||||
"version": 8,
|
||||
},
|
||||
},
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"endBinding": null,
|
||||
"height": "0.88851",
|
||||
"endBinding": {
|
||||
"elementId": "id15",
|
||||
"fixedPoint": [
|
||||
"0.50000",
|
||||
1,
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"height": "100.79596",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
@@ -279,29 +343,30 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
],
|
||||
[
|
||||
90,
|
||||
"0.88851",
|
||||
"100.79596",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
"0.60000",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 14,
|
||||
"version": 36,
|
||||
"width": 90,
|
||||
},
|
||||
"inserted": {
|
||||
"endBinding": {
|
||||
"elementId": "id1",
|
||||
"fixedPoint": [
|
||||
0,
|
||||
"0.50010",
|
||||
"0.60000",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"height": "0.00047",
|
||||
"height": "0.00000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
@@ -309,23 +374,24 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
],
|
||||
[
|
||||
90,
|
||||
"0.00047",
|
||||
"0.00000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
"0.60000",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 12,
|
||||
"version": 33,
|
||||
"width": 90,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"id": "id17",
|
||||
"id": "id22",
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -340,7 +406,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"updated": {
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"height": "0.03787",
|
||||
"height": "113.98784",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
@@ -348,7 +414,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
],
|
||||
[
|
||||
95,
|
||||
"0.03787",
|
||||
"113.98784",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
@@ -357,13 +423,15 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "orbit",
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 16,
|
||||
"version": 37,
|
||||
"width": 95,
|
||||
"x": 0,
|
||||
"y": "0.01000",
|
||||
},
|
||||
"inserted": {
|
||||
"height": "0.88851",
|
||||
"height": "100.79596",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
@@ -371,24 +439,26 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
],
|
||||
[
|
||||
90,
|
||||
"0.88851",
|
||||
"100.79596",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
"0.60000",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 14,
|
||||
"version": 36,
|
||||
"width": 90,
|
||||
"x": 5,
|
||||
"y": "15.52629",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"id": "id18",
|
||||
"id": "id23",
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -570,136 +640,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
},
|
||||
"id": "id6",
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
"delta": Delta {
|
||||
"deleted": {},
|
||||
"inserted": {},
|
||||
},
|
||||
},
|
||||
"elements": {
|
||||
"added": {},
|
||||
"removed": {},
|
||||
"updated": {
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"height": "0.95000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
95,
|
||||
"-0.95000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 7,
|
||||
"width": 95,
|
||||
"x": 5,
|
||||
"y": "0.95000",
|
||||
},
|
||||
"inserted": {
|
||||
"height": 0,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
0,
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 6,
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"id": "id9",
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
"delta": Delta {
|
||||
"deleted": {},
|
||||
"inserted": {},
|
||||
},
|
||||
},
|
||||
"elements": {
|
||||
"added": {},
|
||||
"removed": {},
|
||||
"updated": {
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"height": "0.00950",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
95,
|
||||
"-0.00950",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 9,
|
||||
"y": "0.00950",
|
||||
},
|
||||
"inserted": {
|
||||
"height": "0.95000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
95,
|
||||
"-0.95000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 7,
|
||||
"y": "0.95000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"id": "id11",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -846,9 +786,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 5,
|
||||
"version": 14,
|
||||
"width": 100,
|
||||
"x": -100,
|
||||
"x": 150,
|
||||
"y": -50,
|
||||
}
|
||||
`;
|
||||
@@ -876,9 +816,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"strokeWidth": 2,
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"version": 9,
|
||||
"width": 100,
|
||||
"x": 100,
|
||||
"x": 150,
|
||||
"y": -50,
|
||||
}
|
||||
`;
|
||||
@@ -895,7 +835,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": "0.88851",
|
||||
"height": "0.01000",
|
||||
"id": "id4",
|
||||
"index": "a2",
|
||||
"isDeleted": false,
|
||||
@@ -910,8 +850,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
0,
|
||||
],
|
||||
[
|
||||
90,
|
||||
"0.88851",
|
||||
0,
|
||||
"-0.01000",
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
@@ -925,23 +865,23 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "orbit",
|
||||
"mode": "inside",
|
||||
},
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 18,
|
||||
"width": 90,
|
||||
"x": 5,
|
||||
"y": "0.05936",
|
||||
"version": 30,
|
||||
"width": 0,
|
||||
"x": 250,
|
||||
"y": "0.01000",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] number of elements 1`] = `3`;
|
||||
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] number of renders 1`] = `14`;
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] number of renders 1`] = `23`;
|
||||
|
||||
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind bindings when both are updated through the history and there are no conflicting updates in the meantime > [end of test] redo stack 1`] = `
|
||||
[
|
||||
@@ -959,7 +899,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"id1": {
|
||||
"deleted": {
|
||||
"boundElements": [],
|
||||
"version": 4,
|
||||
"version": 9,
|
||||
},
|
||||
"inserted": {
|
||||
"boundElements": [
|
||||
@@ -968,62 +908,66 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"version": 3,
|
||||
"version": 8,
|
||||
},
|
||||
},
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"endBinding": null,
|
||||
"height": "0.00900",
|
||||
"height": "3.00000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
90,
|
||||
"-0.00900",
|
||||
-45,
|
||||
"-3.00000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
"0.60000",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 16,
|
||||
"version": 29,
|
||||
"width": 45,
|
||||
"y": "3.00000",
|
||||
},
|
||||
"inserted": {
|
||||
"endBinding": {
|
||||
"elementId": "id1",
|
||||
"fixedPoint": [
|
||||
0,
|
||||
"0.50010",
|
||||
"0.60000",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"height": "0.04676",
|
||||
"height": 0,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
90,
|
||||
"-0.04676",
|
||||
0,
|
||||
0,
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
"0.60000",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 14,
|
||||
"version": 28,
|
||||
"width": 0,
|
||||
"y": "9.99861",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1043,15 +987,15 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"updated": {
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"height": "0.88851",
|
||||
"height": "0.01000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
90,
|
||||
"0.88851",
|
||||
0,
|
||||
"-0.01000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
@@ -1060,33 +1004,37 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "orbit",
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 18,
|
||||
"y": "0.05936",
|
||||
"version": 30,
|
||||
"width": 0,
|
||||
"x": 250,
|
||||
"y": "0.01000",
|
||||
},
|
||||
"inserted": {
|
||||
"height": "0.00900",
|
||||
"height": "3.00000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
90,
|
||||
"-0.00900",
|
||||
-45,
|
||||
"-3.00000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
"0.60000",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 16,
|
||||
"y": "0.00950",
|
||||
"version": 29,
|
||||
"width": 45,
|
||||
"x": 145,
|
||||
"y": "3.00000",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1273,262 +1221,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
},
|
||||
"id": "id6",
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
"delta": Delta {
|
||||
"deleted": {},
|
||||
"inserted": {},
|
||||
},
|
||||
},
|
||||
"elements": {
|
||||
"added": {},
|
||||
"removed": {},
|
||||
"updated": {
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"height": "0.95000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
95,
|
||||
"-0.95000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 7,
|
||||
"width": 95,
|
||||
"x": 5,
|
||||
"y": "0.95000",
|
||||
},
|
||||
"inserted": {
|
||||
"height": 0,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
0,
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 6,
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"id": "id9",
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
"delta": Delta {
|
||||
"deleted": {},
|
||||
"inserted": {},
|
||||
},
|
||||
},
|
||||
"elements": {
|
||||
"added": {},
|
||||
"removed": {},
|
||||
"updated": {
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"height": 0,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
95,
|
||||
0,
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 8,
|
||||
"y": 0,
|
||||
},
|
||||
"inserted": {
|
||||
"height": "0.95000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
95,
|
||||
"-0.95000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 7,
|
||||
"y": "0.95000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"id": "id11",
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
"delta": Delta {
|
||||
"deleted": {},
|
||||
"inserted": {},
|
||||
},
|
||||
},
|
||||
"elements": {
|
||||
"added": {},
|
||||
"removed": {},
|
||||
"updated": {
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"height": "0.00950",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
95,
|
||||
"-0.00950",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 10,
|
||||
"y": "0.00950",
|
||||
},
|
||||
"inserted": {
|
||||
"height": 0,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
95,
|
||||
0,
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "inside",
|
||||
},
|
||||
"version": 8,
|
||||
"y": 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"id": "id13",
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
"delta": Delta {
|
||||
"deleted": {},
|
||||
"inserted": {},
|
||||
},
|
||||
},
|
||||
"elements": {
|
||||
"added": {},
|
||||
"removed": {},
|
||||
"updated": {
|
||||
"id4": {
|
||||
"deleted": {
|
||||
"height": "0.93837",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
90,
|
||||
"0.93837",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 11,
|
||||
"width": 90,
|
||||
},
|
||||
"inserted": {
|
||||
"height": "0.00950",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
95,
|
||||
"-0.00950",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id0",
|
||||
"fixedPoint": [
|
||||
1,
|
||||
"0.50010",
|
||||
],
|
||||
"mode": "orbit",
|
||||
},
|
||||
"version": 10,
|
||||
"width": 95,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"id": "id16",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
|
@@ -4557,16 +4557,30 @@ describe("history", () => {
|
||||
|
||||
// create start binding
|
||||
mouse.downAt(0, 0);
|
||||
mouse.moveTo(0, 1);
|
||||
mouse.moveTo(0, 0);
|
||||
mouse.moveTo(0, 10);
|
||||
mouse.moveTo(0, 10);
|
||||
mouse.up();
|
||||
|
||||
// create end binding
|
||||
mouse.downAt(100, 0);
|
||||
mouse.moveTo(100, 1);
|
||||
mouse.moveTo(100, 0);
|
||||
mouse.moveTo(100, 10);
|
||||
mouse.moveTo(100, 10);
|
||||
mouse.up();
|
||||
|
||||
expect(
|
||||
(h.elements[2] as ExcalidrawElbowArrowElement).startBinding
|
||||
?.fixedPoint,
|
||||
).not.toEqual([1, 0.5001]);
|
||||
expect(
|
||||
(h.elements[2] as ExcalidrawElbowArrowElement).startBinding?.mode,
|
||||
).toBe("orbit");
|
||||
expect(
|
||||
(h.elements[2] as ExcalidrawElbowArrowElement).endBinding,
|
||||
).not.toEqual([1, 0.5001]);
|
||||
expect(
|
||||
(h.elements[2] as ExcalidrawElbowArrowElement).endBinding?.mode,
|
||||
).toBe("orbit");
|
||||
|
||||
expect(h.elements).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
@@ -4606,12 +4620,16 @@ describe("history", () => {
|
||||
expect(h.elements).toEqual([
|
||||
expect.objectContaining({
|
||||
id: rect1.id,
|
||||
boundElements: [],
|
||||
boundElements: [{ id: arrowId, type: "arrow" }],
|
||||
}),
|
||||
expect.objectContaining({ id: rect2.id, boundElements: [] }),
|
||||
expect.objectContaining({
|
||||
id: arrowId,
|
||||
startBinding: null,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: [1, 0.5001],
|
||||
mode: "inside",
|
||||
}),
|
||||
endBinding: null,
|
||||
}),
|
||||
]);
|
||||
@@ -4656,13 +4674,13 @@ describe("history", () => {
|
||||
id: arrowId,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
fixedPoint: [1, 0.6],
|
||||
mode: "orbit",
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
fixedPoint: [0, 0.6],
|
||||
mode: "orbit",
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
@@ -4675,12 +4693,21 @@ describe("history", () => {
|
||||
expect(h.elements).toEqual([
|
||||
expect.objectContaining({
|
||||
id: rect1.id,
|
||||
boundElements: [],
|
||||
boundElements: [
|
||||
expect.objectContaining({
|
||||
id: arrowId,
|
||||
type: "arrow",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ id: rect2.id, boundElements: [] }),
|
||||
expect.objectContaining({
|
||||
id: arrowId,
|
||||
startBinding: null,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: [1, 0.5001],
|
||||
mode: "inside",
|
||||
}),
|
||||
endBinding: null,
|
||||
}),
|
||||
]);
|
||||
@@ -4699,13 +4726,13 @@ describe("history", () => {
|
||||
|
||||
// create start binding
|
||||
mouse.downAt(0, 0);
|
||||
mouse.moveTo(0, 1);
|
||||
mouse.upAt(0, 0);
|
||||
mouse.moveTo(0, 10);
|
||||
mouse.upAt(0, 10);
|
||||
|
||||
// create end binding
|
||||
mouse.downAt(100, 0);
|
||||
mouse.moveTo(100, 1);
|
||||
mouse.upAt(100, 0);
|
||||
mouse.moveTo(100, 10);
|
||||
mouse.upAt(100, 10);
|
||||
|
||||
expect(h.elements).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -4746,12 +4773,21 @@ describe("history", () => {
|
||||
expect(h.elements).toEqual([
|
||||
expect.objectContaining({
|
||||
id: rect1.id,
|
||||
boundElements: [],
|
||||
boundElements: [
|
||||
expect.objectContaining({
|
||||
id: arrowId,
|
||||
type: "arrow",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({ id: rect2.id, boundElements: [] }),
|
||||
expect.objectContaining({
|
||||
id: arrowId,
|
||||
startBinding: null,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: [1, 0.5001],
|
||||
mode: "inside",
|
||||
}),
|
||||
endBinding: null,
|
||||
}),
|
||||
]);
|
||||
@@ -4799,14 +4835,14 @@ describe("history", () => {
|
||||
id: arrowId,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
fixedPoint: [1, 0.6],
|
||||
mode: "orbit",
|
||||
}),
|
||||
// rebound with previous rectangle
|
||||
endBinding: expect.objectContaining({
|
||||
elementId: rect2.id,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
fixedPoint: [0, 0.6],
|
||||
mode: "orbit",
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
@@ -4824,7 +4860,12 @@ describe("history", () => {
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: rect1.id,
|
||||
boundElements: [],
|
||||
boundElements: [
|
||||
expect.objectContaining({
|
||||
id: arrowId,
|
||||
type: "arrow",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: rect2.id,
|
||||
@@ -4832,16 +4873,16 @@ describe("history", () => {
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: arrowId,
|
||||
startBinding: null,
|
||||
startBinding: expect.objectContaining({
|
||||
elementId: rect1.id,
|
||||
fixedPoint: [1, 0.5001],
|
||||
mode: "inside",
|
||||
}),
|
||||
endBinding: expect.objectContaining({
|
||||
// now we are back in the previous state!
|
||||
elementId: remoteContainer.id,
|
||||
fixedPoint: [
|
||||
expect.toBeNonNaNNumber(),
|
||||
expect.toBeNonNaNNumber(),
|
||||
],
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
fixedPoint: [0.5, 1],
|
||||
mode: "orbit",
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
|
Reference in New Issue
Block a user