Compare commits

..
Author SHA1 Message Date
dependabot[bot]andGitHub 745cedf9a3 build(deps): bump ejs from 3.1.9 to 3.1.10
Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-01 18:48:28 +00:00
8 changed files with 345 additions and 498 deletions
+1 -1
View File
@@ -54,7 +54,7 @@
"vitest-canvas-mock": "0.3.2" "vitest-canvas-mock": "0.3.2"
}, },
"engines": { "engines": {
"node": "18.0.0 - 22.x.x" "node": "18.0.0 - 20.x.x"
}, },
"homepage": ".", "homepage": ".",
"prettier": "@excalidraw/prettier-config", "prettier": "@excalidraw/prettier-config",
+7 -8
View File
@@ -12,13 +12,13 @@ import { arrayToMap } from "../utils";
import { CODES, KEYS } from "../keys"; import { CODES, KEYS } from "../keys";
import { getCommonBoundingBox } from "../element/bounds"; import { getCommonBoundingBox } from "../element/bounds";
import { import {
bindOrUnbindLinearElements, bindOrUnbindSelectedElements,
isBindingEnabled, isBindingEnabled,
unbindLinearElements,
} from "../element/binding"; } from "../element/binding";
import { updateFrameMembershipOfSelectedElements } from "../frame"; import { updateFrameMembershipOfSelectedElements } from "../frame";
import { flipHorizontal, flipVertical } from "../components/icons"; import { flipHorizontal, flipVertical } from "../components/icons";
import { StoreAction } from "../store"; import { StoreAction } from "../store";
import { isLinearElement } from "../element/typeChecks";
export const actionFlipHorizontal = register({ export const actionFlipHorizontal = register({
name: "flipHorizontal", name: "flipHorizontal",
@@ -89,6 +89,7 @@ const flipSelectedElements = (
const updatedElements = flipElements( const updatedElements = flipElements(
selectedElements, selectedElements,
elements,
elementsMap, elementsMap,
appState, appState,
flipDirection, flipDirection,
@@ -104,6 +105,7 @@ const flipSelectedElements = (
const flipElements = ( const flipElements = (
selectedElements: NonDeleted<ExcalidrawElement>[], selectedElements: NonDeleted<ExcalidrawElement>[],
elements: readonly ExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap, elementsMap: NonDeletedSceneElementsMap,
appState: AppState, appState: AppState,
flipDirection: "horizontal" | "vertical", flipDirection: "horizontal" | "vertical",
@@ -122,12 +124,9 @@ const flipElements = (
flipDirection === "horizontal" ? minY : maxY, flipDirection === "horizontal" ? minY : maxY,
); );
bindOrUnbindLinearElements( isBindingEnabled(appState)
selectedElements.filter(isLinearElement), ? bindOrUnbindSelectedElements(selectedElements, app)
app, : unbindLinearElements(selectedElements, elementsMap);
isBindingEnabled(appState),
[],
);
return selectedElements; return selectedElements;
}; };
+35 -37
View File
@@ -122,16 +122,17 @@ import {
} from "../element"; } from "../element";
import { import {
bindOrUnbindLinearElement, bindOrUnbindLinearElement,
bindOrUnbindLinearElements, bindOrUnbindSelectedElements,
fixBindingsAfterDeletion, fixBindingsAfterDeletion,
fixBindingsAfterDuplication, fixBindingsAfterDuplication,
getEligibleElementsForBinding,
getHoveredElementForBinding, getHoveredElementForBinding,
isBindingEnabled, isBindingEnabled,
isLinearElementSimpleAndAlreadyBound, isLinearElementSimpleAndAlreadyBound,
maybeBindLinearElement, maybeBindLinearElement,
shouldEnableBindingForPointerEvent, shouldEnableBindingForPointerEvent,
unbindLinearElements,
updateBoundElements, updateBoundElements,
getSuggestedBindingsForArrows,
} from "../element/binding"; } from "../element/binding";
import { LinearElementEditor } from "../element/linearElementEditor"; import { LinearElementEditor } from "../element/linearElementEditor";
import { mutateElement, newElementWith } from "../element/mutateElement"; import { mutateElement, newElementWith } from "../element/mutateElement";
@@ -3937,12 +3938,7 @@ class App extends React.Component<AppProps, AppState> {
}); });
}); });
this.setState({ this.maybeSuggestBindingForAll(selectedElements);
suggestedBindings: getSuggestedBindingsForArrows(
selectedElements,
this,
),
});
event.preventDefault(); event.preventDefault();
} else if (event.key === KEYS.ENTER) { } else if (event.key === KEYS.ENTER) {
@@ -4109,12 +4105,11 @@ class App extends React.Component<AppProps, AppState> {
this.setState({ isBindingEnabled: true }); this.setState({ isBindingEnabled: true });
} }
if (isArrowKey(event.key)) { if (isArrowKey(event.key)) {
bindOrUnbindLinearElements( const selectedElements = this.scene.getSelectedElements(this.state);
this.scene.getSelectedElements(this.state).filter(isLinearElement), const elementsMap = this.scene.getNonDeletedElementsMap();
this, isBindingEnabled(this.state)
isBindingEnabled(this.state), ? bindOrUnbindSelectedElements(selectedElements, this)
this.state.selectedLinearElement?.selectedPointsIndices ?? [], : unbindLinearElements(selectedElements, elementsMap);
);
this.setState({ suggestedBindings: [] }); this.setState({ suggestedBindings: [] });
} }
}); });
@@ -7458,12 +7453,7 @@ class App extends React.Component<AppProps, AppState> {
event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize, event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize,
); );
this.setState({ this.maybeSuggestBindingForAll(selectedElements);
suggestedBindings: getSuggestedBindingsForArrows(
selectedElements,
this,
),
});
// We duplicate the selected element if alt is pressed on pointer move // We duplicate the selected element if alt is pressed on pointer move
if (event.altKey && !pointerDownState.hit.hasBeenDuplicated) { if (event.altKey && !pointerDownState.hit.hasBeenDuplicated) {
@@ -8510,18 +8500,15 @@ class App extends React.Component<AppProps, AppState> {
} }
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) { if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
// We only allow binding via linear elements, specifically via dragging isBindingEnabled(this.state)
// the endpoints ("start" or "end"). ? bindOrUnbindSelectedElements(
const linearElements = this.scene this.scene.getSelectedElements(this.state),
.getSelectedElements(this.state) this,
.filter(isLinearElement); )
: unbindLinearElements(
bindOrUnbindLinearElements( this.scene.getSelectedElements(this.state),
linearElements, elementsMap,
this, );
isBindingEnabled(this.state),
this.state.selectedLinearElement?.selectedPointsIndices ?? [],
);
} }
if (activeTool.type === "laser") { if (activeTool.type === "laser") {
@@ -9053,6 +9040,19 @@ class App extends React.Component<AppProps, AppState> {
this.setState({ suggestedBindings }); this.setState({ suggestedBindings });
}; };
private maybeSuggestBindingForAll(
selectedElements: NonDeleted<ExcalidrawElement>[],
): void {
if (selectedElements.length > 50) {
return;
}
const suggestedBindings = getEligibleElementsForBinding(
selectedElements,
this,
);
this.setState({ suggestedBindings });
}
private clearSelection(hitElement: ExcalidrawElement | null): void { private clearSelection(hitElement: ExcalidrawElement | null): void {
this.setState((prevState) => ({ this.setState((prevState) => ({
selectedElementIds: makeNextSelectedElementIds({}, prevState), selectedElementIds: makeNextSelectedElementIds({}, prevState),
@@ -9439,6 +9439,8 @@ class App extends React.Component<AppProps, AppState> {
this.state.originSnapOffset, this.state.originSnapOffset,
); );
this.maybeSuggestBindingForAll([draggingElement]);
// highlight elements that are to be added to frames on frames creation // highlight elements that are to be added to frames on frames creation
if ( if (
this.state.activeTool.type === TOOL_TYPE.frame || this.state.activeTool.type === TOOL_TYPE.frame ||
@@ -9561,10 +9563,7 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState.resize.center.y, pointerDownState.resize.center.y,
) )
) { ) {
const suggestedBindings = getSuggestedBindingsForArrows( this.maybeSuggestBindingForAll(selectedElements);
selectedElements,
this,
);
const elementsToHighlight = new Set<ExcalidrawElement>(); const elementsToHighlight = new Set<ExcalidrawElement>();
selectedFrames.forEach((frame) => { selectedFrames.forEach((frame) => {
@@ -9578,7 +9577,6 @@ class App extends React.Component<AppProps, AppState> {
this.setState({ this.setState({
elementsToHighlight: [...elementsToHighlight], elementsToHighlight: [...elementsToHighlight],
suggestedBindings,
}); });
return true; return true;
+182 -190
View File
@@ -131,193 +131,73 @@ const bindOrUnbindLinearElementEdge = (
unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>, unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
elementsMap: NonDeletedSceneElementsMap, elementsMap: NonDeletedSceneElementsMap,
): void => { ): void => {
// "keep" is for method chaining convenience, a "no-op", so just bail out if (bindableElement !== "keep") {
if (bindableElement === "keep") { if (bindableElement != null) {
return; // Don't bind if we're trying to bind or are already bound to the same
} // element on the other edge already ("start" edge takes precedence).
if (
// null means break the bind, so nothing to consider here otherEdgeBindableElement == null ||
if (bindableElement === null) { (otherEdgeBindableElement === "keep"
const unbound = unbindLinearElement(linearElement, startOrEnd); ? !isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
if (unbound != null) { linearElement,
unboundFromElementIds.add(unbound); bindableElement,
} startOrEnd,
return; )
} : startOrEnd === "start" ||
otherEdgeBindableElement.id !== bindableElement.id)
// While complext arrows can do anything, simple arrow with both ends trying ) {
// to bind to the same bindable should not be allowed, start binding takes bindLinearElement(
// precedence linearElement,
if (isLinearElementSimple(linearElement)) { bindableElement,
if ( startOrEnd,
otherEdgeBindableElement == null || elementsMap,
(otherEdgeBindableElement === "keep" );
? // TODO: Refactor - Needlessly complex boundToElementIds.add(bindableElement.id);
!isLinearElementSimpleAndAlreadyBoundOnOppositeEdge( }
linearElement, } else {
bindableElement, const unbound = unbindLinearElement(linearElement, startOrEnd);
startOrEnd, if (unbound != null) {
) unboundFromElementIds.add(unbound);
: startOrEnd === "start" || }
otherEdgeBindableElement.id !== bindableElement.id)
) {
bindLinearElement(
linearElement,
bindableElement,
startOrEnd,
elementsMap,
);
boundToElementIds.add(bindableElement.id);
}
} else {
bindLinearElement(linearElement, bindableElement, startOrEnd, elementsMap);
boundToElementIds.add(bindableElement.id);
}
};
const getOriginalBindingIfStillCloseOfLinearElementEdge = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
edge: "start" | "end",
app: AppClassProperties,
): NonDeleted<ExcalidrawElement> | null => {
const elementsMap = app.scene.getNonDeletedElementsMap();
const coors = getLinearElementEdgeCoors(linearElement, edge, elementsMap);
const elementId =
edge === "start"
? linearElement.startBinding?.elementId
: linearElement.endBinding?.elementId;
if (elementId) {
const element = elementsMap.get(
elementId,
) as NonDeleted<ExcalidrawBindableElement>;
if (bindingBorderTest(element, coors, app)) {
return element;
} }
} }
return null;
}; };
const getOriginalBindingsIfStillCloseToArrowEnds = ( export const bindOrUnbindSelectedElements = (
linearElement: NonDeleted<ExcalidrawLinearElement>, selectedElements: NonDeleted<ExcalidrawElement>[],
app: AppClassProperties, app: AppClassProperties,
): (NonDeleted<ExcalidrawElement> | null)[] =>
["start", "end"].map((edge) =>
getOriginalBindingIfStillCloseOfLinearElementEdge(
linearElement,
edge as "start" | "end",
app,
),
);
const getBindingStrategyForDraggingArrowEndpoints = (
selectedElement: NonDeleted<ExcalidrawLinearElement>,
isBindingEnabled: boolean,
draggingPoints: readonly number[],
app: AppClassProperties,
): (NonDeleted<ExcalidrawBindableElement> | null | "keep")[] => {
const startIdx = 0;
const endIdx = selectedElement.points.length - 1;
const startDragged = draggingPoints.findIndex((i) => i === startIdx) > -1;
const endDragged = draggingPoints.findIndex((i) => i === endIdx) > -1;
const start = startDragged
? isBindingEnabled
? getElligibleElementForBindingElement(selectedElement, "start", app)
: null // If binding is disabled and start is dragged, break all binds
: // We have to update the focus and gap of the binding, so let's rebind
getElligibleElementForBindingElement(selectedElement, "start", app);
const end = endDragged
? isBindingEnabled
? getElligibleElementForBindingElement(selectedElement, "end", app)
: null // If binding is disabled and end is dragged, break all binds
: // We have to update the focus and gap of the binding, so let's rebind
getElligibleElementForBindingElement(selectedElement, "end", app);
return [start, end];
};
const getBindingStrategyForDraggingArrowOrJoints = (
selectedElement: NonDeleted<ExcalidrawLinearElement>,
app: AppClassProperties,
isBindingEnabled: boolean,
): (NonDeleted<ExcalidrawBindableElement> | null | "keep")[] => {
const [startIsClose, endIsClose] = getOriginalBindingsIfStillCloseToArrowEnds(
selectedElement,
app,
);
const start = startIsClose
? isBindingEnabled
? getElligibleElementForBindingElement(selectedElement, "start", app)
: null
: null;
const end = endIsClose
? isBindingEnabled
? getElligibleElementForBindingElement(selectedElement, "end", app)
: null
: null;
return [start, end];
};
export const bindOrUnbindLinearElements = (
selectedElements: NonDeleted<ExcalidrawLinearElement>[],
app: AppClassProperties,
isBindingEnabled: boolean,
draggingPoints: readonly number[] | null,
): void => { ): void => {
selectedElements.forEach((selectedElement) => { selectedElements.forEach((selectedElement) => {
const [start, end] = draggingPoints?.length if (isBindingElement(selectedElement)) {
? // The arrow edge points are dragged (i.e. start, end) bindOrUnbindLinearElement(
getBindingStrategyForDraggingArrowEndpoints( selectedElement,
selectedElement, getElligibleElementForBindingElement(selectedElement, "start", app),
isBindingEnabled, getElligibleElementForBindingElement(selectedElement, "end", app),
draggingPoints ?? [], app.scene.getNonDeletedElementsMap(),
app, );
) } else if (isBindableElement(selectedElement)) {
: // The arrow itself (the shaft) or the inner joins are dragged maybeBindBindableElement(
getBindingStrategyForDraggingArrowOrJoints( selectedElement,
selectedElement, app.scene.getNonDeletedElementsMap(),
app, app,
isBindingEnabled, );
); }
bindOrUnbindLinearElement(
selectedElement,
start,
end,
app.scene.getNonDeletedElementsMap(),
);
}); });
}; };
export const getSuggestedBindingsForArrows = ( const maybeBindBindableElement = (
selectedElements: NonDeleted<ExcalidrawElement>[], bindableElement: NonDeleted<ExcalidrawBindableElement>,
elementsMap: NonDeletedSceneElementsMap,
app: AppClassProperties, app: AppClassProperties,
): SuggestedBinding[] => { ): void => {
// HOT PATH: Bail out if selected elements list is too large getElligibleElementsForBindableElementAndWhere(bindableElement, app).forEach(
if (selectedElements.length > 50) { ([linearElement, where]) =>
return []; bindOrUnbindLinearElement(
} linearElement,
where === "end" ? "keep" : bindableElement,
return ( where === "start" ? "keep" : bindableElement,
selectedElements elementsMap,
.filter(isLinearElement) ),
.flatMap((element) =>
getOriginalBindingsIfStillCloseToArrowEnds(element, app),
)
.filter(
(element): element is NonDeleted<ExcalidrawBindableElement> =>
element !== null,
)
// Filter out bind candidates which are in the
// same selection / group with the arrow
//
// TODO: Is it worth turning the list into a set to avoid dupes?
.filter(
(element) =>
selectedElements.filter((selected) => selected.id === element?.id)
.length === 0,
)
); );
}; };
@@ -403,14 +283,29 @@ export const isLinearElementSimpleAndAlreadyBound = (
bindableElement: ExcalidrawBindableElement, bindableElement: ExcalidrawBindableElement,
): boolean => { ): boolean => {
return ( return (
alreadyBoundToId === bindableElement.id && alreadyBoundToId === bindableElement.id && linearElement.points.length < 3
isLinearElementSimple(linearElement)
); );
}; };
const isLinearElementSimple = ( export const unbindLinearElements = (
linearElement: NonDeleted<ExcalidrawLinearElement>, elements: readonly NonDeleted<ExcalidrawElement>[],
): boolean => linearElement.points.length < 3; elementsMap: NonDeletedSceneElementsMap,
): void => {
elements.forEach((element) => {
if (isBindingElement(element)) {
if (element.startBinding !== null && element.endBinding !== null) {
bindOrUnbindLinearElement(element, null, null, elementsMap);
} else {
bindOrUnbindLinearElement(
element,
element.startBinding ? "keep" : null,
element.endBinding ? "keep" : null,
elementsMap,
);
}
}
});
};
const unbindLinearElement = ( const unbindLinearElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>, linearElement: NonDeleted<ExcalidrawLinearElement>,
@@ -657,6 +552,42 @@ const maybeCalculateNewGapWhenScaling = (
return { elementId, gap: newGap, focus }; return { elementId, gap: newGap, focus };
}; };
// TODO: this is a bottleneck, optimise
export const getEligibleElementsForBinding = (
selectedElements: NonDeleted<ExcalidrawElement>[],
app: AppClassProperties,
): SuggestedBinding[] => {
const includedElementIds = new Set(selectedElements.map(({ id }) => id));
return selectedElements.flatMap((selectedElement) =>
isBindingElement(selectedElement, false)
? (getElligibleElementsForBindingElement(
selectedElement as NonDeleted<ExcalidrawLinearElement>,
app,
).filter(
(element) => !includedElementIds.has(element.id),
) as SuggestedBinding[])
: isBindableElement(selectedElement, false)
? getElligibleElementsForBindableElementAndWhere(
selectedElement,
app,
).filter((binding) => !includedElementIds.has(binding[0].id))
: [],
);
};
const getElligibleElementsForBindingElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
app: AppClassProperties,
): NonDeleted<ExcalidrawBindableElement>[] => {
return [
getElligibleElementForBindingElement(linearElement, "start", app),
getElligibleElementForBindingElement(linearElement, "end", app),
].filter(
(element): element is NonDeleted<ExcalidrawBindableElement> =>
element != null,
);
};
const getElligibleElementForBindingElement = ( const getElligibleElementForBindingElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>, linearElement: NonDeleted<ExcalidrawLinearElement>,
startOrEnd: "start" | "end", startOrEnd: "start" | "end",
@@ -687,6 +618,67 @@ const getLinearElementEdgeCoors = (
); );
}; };
const getElligibleElementsForBindableElementAndWhere = (
bindableElement: NonDeleted<ExcalidrawBindableElement>,
app: AppClassProperties,
): SuggestedPointBinding[] => {
const scene = Scene.getScene(bindableElement)!;
return scene
.getNonDeletedElements()
.map((element) => {
if (!isBindingElement(element, false)) {
return null;
}
const canBindStart = isLinearElementEligibleForNewBindingByBindable(
element,
"start",
bindableElement,
scene.getNonDeletedElementsMap(),
app,
);
const canBindEnd = isLinearElementEligibleForNewBindingByBindable(
element,
"end",
bindableElement,
scene.getNonDeletedElementsMap(),
app,
);
if (!canBindStart && !canBindEnd) {
return null;
}
return [
element,
canBindStart && canBindEnd ? "both" : canBindStart ? "start" : "end",
bindableElement,
];
})
.filter((maybeElement) => maybeElement != null) as SuggestedPointBinding[];
};
const isLinearElementEligibleForNewBindingByBindable = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
startOrEnd: "start" | "end",
bindableElement: NonDeleted<ExcalidrawBindableElement>,
elementsMap: NonDeletedSceneElementsMap,
app: AppClassProperties,
): boolean => {
const existingBinding =
linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"];
return (
existingBinding == null &&
!isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
linearElement,
bindableElement,
startOrEnd,
) &&
bindingBorderTest(
bindableElement,
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
app,
)
);
};
// We need to: // We need to:
// 1: Update elements not selected to point to duplicated elements // 1: Update elements not selected to point to duplicated elements
// 2: Update duplicated elements to point to other duplicated elements // 2: Update duplicated elements to point to other duplicated elements
@@ -822,7 +814,7 @@ const newBoundElements = (
return nextBoundElements; return nextBoundElements;
}; };
const bindingBorderTest = ( export const bindingBorderTest = (
element: NonDeleted<ExcalidrawBindableElement>, element: NonDeleted<ExcalidrawBindableElement>,
{ x, y }: { x: number; y: number }, { x, y }: { x: number; y: number },
app: AppClassProperties, app: AppClassProperties,
@@ -844,7 +836,7 @@ export const maxBindingGap = (
return Math.max(16, Math.min(0.25 * smallerDimension, 32)); return Math.max(16, Math.min(0.25 * smallerDimension, 32));
}; };
const distanceToBindableElement = ( export const distanceToBindableElement = (
element: ExcalidrawBindableElement, element: ExcalidrawBindableElement,
point: Point, point: Point,
elementsMap: ElementsMap, elementsMap: ElementsMap,
@@ -901,7 +893,7 @@ const distanceToDiamond = (
return GAPoint.distanceToLine(pointRel, side); return GAPoint.distanceToLine(pointRel, side);
}; };
const distanceToEllipse = ( export const distanceToEllipse = (
element: ExcalidrawEllipseElement, element: ExcalidrawEllipseElement,
point: Point, point: Point,
elementsMap: ElementsMap, elementsMap: ElementsMap,
@@ -1020,7 +1012,7 @@ const coordsCenter = (
// all focus points lie, so it's a number between -1 and 1. // all focus points lie, so it's a number between -1 and 1.
// The line going through `a` and `b` is a tangent to the "focus image" // The line going through `a` and `b` is a tangent to the "focus image"
// of the element. // of the element.
const determineFocusDistance = ( export const determineFocusDistance = (
element: ExcalidrawBindableElement, element: ExcalidrawBindableElement,
// Point on the line, in absolute coordinates // Point on the line, in absolute coordinates
a: Point, a: Point,
@@ -1061,7 +1053,7 @@ const determineFocusDistance = (
return ret || 0; return ret || 0;
}; };
const determineFocusPoint = ( export const determineFocusPoint = (
element: ExcalidrawBindableElement, element: ExcalidrawBindableElement,
// The oriented, relative distance from the center of `element` of the // The oriented, relative distance from the center of `element` of the
// returned focusPoint // returned focusPoint
@@ -1101,7 +1093,7 @@ const determineFocusPoint = (
// Returns 2 or 0 intersection points between line going through `a` and `b` // Returns 2 or 0 intersection points between line going through `a` and `b`
// and the `element`, in ascending order of distance from `a`. // and the `element`, in ascending order of distance from `a`.
const intersectElementWithLine = ( export const intersectElementWithLine = (
element: ExcalidrawBindableElement, element: ExcalidrawBindableElement,
// Point on the line, in absolute coordinates // Point on the line, in absolute coordinates
a: Point, a: Point,
@@ -1268,7 +1260,7 @@ const getEllipseIntersections = (
]; ];
}; };
const getCircleIntersections = ( export const getCircleIntersections = (
center: GA.Point, center: GA.Point,
radius: number, radius: number,
line: GA.Line, line: GA.Line,
@@ -1298,7 +1290,7 @@ const getCircleIntersections = (
// The focus point is the tangent point of the "focus image" of the // The focus point is the tangent point of the "focus image" of the
// `element`, where the tangent goes through `point`. // `element`, where the tangent goes through `point`.
const findFocusPointForEllipse = ( export const findFocusPointForEllipse = (
ellipse: ExcalidrawEllipseElement, ellipse: ExcalidrawEllipseElement,
// Between -1 and 1 (not 0) the relative size of the "focus image" of // Between -1 and 1 (not 0) the relative size of the "focus image" of
// the element on which the focus point lies // the element on which the focus point lies
@@ -1335,7 +1327,7 @@ const findFocusPointForEllipse = (
return GA.point(x, (-m * x - 1) / n); return GA.point(x, (-m * x - 1) / n);
}; };
const findFocusPointForRectangulars = ( export const findFocusPointForRectangulars = (
element: element:
| ExcalidrawRectangleElement | ExcalidrawRectangleElement
| ExcalidrawImageElement | ExcalidrawImageElement
@@ -71,13 +71,13 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"penMode": false, "penMode": false,
"pendingImageElementId": null, "pendingImageElementId": null,
"previousSelectedElementIds": { "previousSelectedElementIds": {
"id159": true, "id158": true,
}, },
"resizingElement": null, "resizingElement": null,
"scrollX": 0, "scrollX": 0,
"scrollY": 0, "scrollY": 0,
"selectedElementIds": { "selectedElementIds": {
"id159": true, "id158": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": {}, "selectedGroupIds": {},
@@ -112,7 +112,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id157", "id": "id156",
"index": "a0", "index": "a0",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -127,7 +127,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"strokeWidth": 2, "strokeWidth": 2,
"type": "rectangle", "type": "rectangle",
"updated": 1, "updated": 1,
"version": 18, "version": 8,
"width": 100, "width": 100,
"x": -100, "x": -100,
"y": -50, "y": -50,
@@ -144,7 +144,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id158", "id": "id157",
"index": "a1", "index": "a1",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -159,7 +159,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"strokeWidth": 2, "strokeWidth": 2,
"type": "rectangle", "type": "rectangle",
"updated": 1, "updated": 1,
"version": 19, "version": 9,
"width": 100, "width": 100,
"x": 100, "x": 100,
"y": -50, "y": -50,
@@ -174,7 +174,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"customData": undefined, "customData": undefined,
"endArrowhead": "arrow", "endArrowhead": "arrow",
"endBinding": { "endBinding": {
"elementId": "id162", "elementId": "id160",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -182,7 +182,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 99.19725525211979, "height": 99.19725525211979,
"id": "id159", "id": "id158",
"index": "a2", "index": "a2",
"isDeleted": false, "isDeleted": false,
"lastCommittedPoint": null, "lastCommittedPoint": null,
@@ -210,9 +210,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"strokeWidth": 2, "strokeWidth": 2,
"type": "arrow", "type": "arrow",
"updated": 1, "updated": 1,
"version": 54, "version": 24,
"width": 98.40367721010284, "width": 98.40367721010284,
"x": 1, "x": 1.000000000000007,
"y": 0, "y": 0,
} }
`; `;
@@ -223,7 +223,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"backgroundColor": "transparent", "backgroundColor": "transparent",
"boundElements": [ "boundElements": [
{ {
"id": "id159", "id": "id158",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -232,7 +232,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 50, "height": 50,
"id": "id162", "id": "id160",
"index": "a3", "index": "a3",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -274,68 +274,19 @@ History {
"added": Map {}, "added": Map {},
"removed": Map {}, "removed": Map {},
"updated": Map { "updated": Map {
"id159" => Delta { "id156" => Delta {
"deleted": { "deleted": {
"endBinding": { "boundElements": [],
"elementId": "id158",
"focus": 0.009900990099009901,
"gap": 1,
},
"height": 0.9800031696987099,
"points": [
[
0,
0,
],
[
98,
-0.9800031696987099,
],
],
"startBinding": {
"elementId": "id157",
"focus": 0.0297029702970297,
"gap": 1,
},
}, },
"inserted": { "inserted": {
"endBinding": { "boundElements": [
"elementId": "id158", {
"focus": -0.02, "id": "id158",
"gap": 1, "type": "arrow",
}, },
"height": 0.0002487679019458344,
"points": [
[
0,
0,
],
[
98,
0.0002487679019458344,
],
], ],
"startBinding": {
"elementId": "id157",
"focus": 0.02,
"gap": 1,
},
}, },
}, },
},
},
},
HistoryEntry {
"appStateChange": AppStateChange {
"delta": Delta {
"deleted": {},
"inserted": {},
},
},
"elementsChange": ElementsChange {
"added": Map {},
"removed": Map {},
"updated": Map {
"id157" => Delta { "id157" => Delta {
"deleted": { "deleted": {
"boundElements": [], "boundElements": [],
@@ -343,29 +294,16 @@ History {
"inserted": { "inserted": {
"boundElements": [ "boundElements": [
{ {
"id": "id159", "id": "id158",
"type": "arrow", "type": "arrow",
}, },
], ],
}, },
}, },
"id158" => Delta { "id158" => Delta {
"deleted": {
"boundElements": [],
},
"inserted": {
"boundElements": [
{
"id": "id159",
"type": "arrow",
},
],
},
},
"id159" => Delta {
"deleted": { "deleted": {
"endBinding": { "endBinding": {
"elementId": "id162", "elementId": "id160",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -385,11 +323,11 @@ History {
}, },
"inserted": { "inserted": {
"endBinding": { "endBinding": {
"elementId": "id158", "elementId": "id157",
"focus": 0.009900990099009901, "focus": 0.009900990099009901,
"gap": 1, "gap": 1,
}, },
"height": 0.9802432787444684, "height": 0.9800000000000002,
"points": [ "points": [
[ [
0, 0,
@@ -397,22 +335,22 @@ History {
], ],
[ [
98, 98,
-0.9802432787444684, -0.9800000000000002,
], ],
], ],
"startBinding": { "startBinding": {
"elementId": "id157", "elementId": "id156",
"focus": 0.0297029702970297, "focus": 0.0297029702970297,
"gap": 1, "gap": 1,
}, },
"y": 0.9903686540602428, "y": 0.9900000000000004,
}, },
}, },
"id162" => Delta { "id160" => Delta {
"deleted": { "deleted": {
"boundElements": [ "boundElements": [
{ {
"id": "id159", "id": "id158",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -436,7 +374,7 @@ History {
"elementsChange": ElementsChange { "elementsChange": ElementsChange {
"added": Map {}, "added": Map {},
"removed": Map { "removed": Map {
"id157" => Delta { "id156" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -467,7 +405,7 @@ History {
"isDeleted": true, "isDeleted": true,
}, },
}, },
"id158" => Delta { "id157" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -507,9 +445,9 @@ History {
"delta": Delta { "delta": Delta {
"deleted": { "deleted": {
"selectedElementIds": { "selectedElementIds": {
"id159": true, "id158": true,
}, },
"selectedLinearElementId": "id159", "selectedLinearElementId": "id158",
}, },
"inserted": { "inserted": {
"selectedElementIds": {}, "selectedElementIds": {},
@@ -520,7 +458,7 @@ History {
"elementsChange": ElementsChange { "elementsChange": ElementsChange {
"added": Map {}, "added": Map {},
"removed": Map { "removed": Map {
"id159" => Delta { "id158" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -576,7 +514,7 @@ History {
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 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`] = `22`; 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`] = `15`;
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] appState 1`] = ` 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] appState 1`] = `
{ {
@@ -784,7 +722,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"strokeWidth": 2, "strokeWidth": 2,
"type": "arrow", "type": "arrow",
"updated": 1, "updated": 1,
"version": 34, "version": 23,
"width": 0, "width": 0,
"x": 251, "x": 251,
"y": 0, "y": 0,
@@ -800,46 +738,6 @@ History {
], ],
}, },
"redoStack": [ "redoStack": [
HistoryEntry {
"appStateChange": AppStateChange {
"delta": Delta {
"deleted": {},
"inserted": {},
},
},
"elementsChange": ElementsChange {
"added": Map {},
"removed": Map {},
"updated": Map {
"id154" => Delta {
"deleted": {
"points": [
[
0,
0,
],
[
0,
0,
],
],
},
"inserted": {
"points": [
[
0,
0,
],
[
100,
0,
],
],
},
},
},
},
},
HistoryEntry { HistoryEntry {
"appStateChange": AppStateChange { "appStateChange": AppStateChange {
"delta": Delta { "delta": Delta {
@@ -1070,7 +968,7 @@ History {
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 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`] = `24`; 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`] = `16`;
exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] appState 1`] = ` exports[`history > multiplayer undo/redo > conflicts in arrows and their bindable elements > should rebind remotely added arrow when it's bindable elements are added through the history > [end of test] appState 1`] = `
{ {
@@ -1181,7 +1079,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"customData": undefined, "customData": undefined,
"endArrowhead": null, "endArrowhead": null,
"endBinding": { "endBinding": {
"elementId": "id164", "elementId": "id162",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -1189,7 +1087,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 0.03596020595764898, "height": 0.03596020595764898,
"id": "id165", "id": "id163",
"index": "Zz", "index": "Zz",
"isDeleted": false, "isDeleted": false,
"lastCommittedPoint": null, "lastCommittedPoint": null,
@@ -1212,7 +1110,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
}, },
"startArrowhead": null, "startArrowhead": null,
"startBinding": { "startBinding": {
"elementId": "id163", "elementId": "id161",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -1234,7 +1132,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"backgroundColor": "transparent", "backgroundColor": "transparent",
"boundElements": [ "boundElements": [
{ {
"id": "id165", "id": "id163",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -1243,7 +1141,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id163", "id": "id161",
"index": "a0", "index": "a0",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -1271,7 +1169,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"backgroundColor": "transparent", "backgroundColor": "transparent",
"boundElements": [ "boundElements": [
{ {
"id": "id165", "id": "id163",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -1280,7 +1178,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id164", "id": "id162",
"index": "a1", "index": "a1",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -1322,7 +1220,7 @@ History {
"elementsChange": ElementsChange { "elementsChange": ElementsChange {
"added": Map {}, "added": Map {},
"removed": Map { "removed": Map {
"id163" => Delta { "id161" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -1353,7 +1251,7 @@ History {
"isDeleted": true, "isDeleted": true,
}, },
}, },
"id164" => Delta { "id162" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -1386,15 +1284,15 @@ History {
}, },
}, },
"updated": Map { "updated": Map {
"id165" => Delta { "id163" => Delta {
"deleted": { "deleted": {
"endBinding": { "endBinding": {
"elementId": "id164", "elementId": "id162",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
"startBinding": { "startBinding": {
"elementId": "id163", "elementId": "id161",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -1524,7 +1422,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"customData": undefined, "customData": undefined,
"endArrowhead": null, "endArrowhead": null,
"endBinding": { "endBinding": {
"elementId": "id167", "elementId": "id165",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -1532,7 +1430,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 0.03596020595764898, "height": 0.03596020595764898,
"id": "id168", "id": "id166",
"index": "a0", "index": "a0",
"isDeleted": false, "isDeleted": false,
"lastCommittedPoint": null, "lastCommittedPoint": null,
@@ -1555,7 +1453,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
}, },
"startArrowhead": null, "startArrowhead": null,
"startBinding": { "startBinding": {
"elementId": "id166", "elementId": "id164",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -1577,7 +1475,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"backgroundColor": "transparent", "backgroundColor": "transparent",
"boundElements": [ "boundElements": [
{ {
"id": "id168", "id": "id166",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -1586,7 +1484,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id166", "id": "id164",
"index": "a0V", "index": "a0V",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -1614,7 +1512,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"backgroundColor": "transparent", "backgroundColor": "transparent",
"boundElements": [ "boundElements": [
{ {
"id": "id168", "id": "id166",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -1623,7 +1521,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id167", "id": "id165",
"index": "a1", "index": "a1",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -1665,7 +1563,7 @@ History {
"elementsChange": ElementsChange { "elementsChange": ElementsChange {
"added": Map {}, "added": Map {},
"removed": Map { "removed": Map {
"id168" => Delta { "id166" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -1673,7 +1571,7 @@ History {
"customData": undefined, "customData": undefined,
"endArrowhead": null, "endArrowhead": null,
"endBinding": { "endBinding": {
"elementId": "id167", "elementId": "id165",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -1703,7 +1601,7 @@ History {
}, },
"startArrowhead": null, "startArrowhead": null,
"startBinding": { "startBinding": {
"elementId": "id166", "elementId": "id164",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -1721,11 +1619,11 @@ History {
}, },
}, },
"updated": Map { "updated": Map {
"id166" => Delta { "id164" => Delta {
"deleted": { "deleted": {
"boundElements": [ "boundElements": [
{ {
"id": "id168", "id": "id166",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -1734,11 +1632,11 @@ History {
"boundElements": [], "boundElements": [],
}, },
}, },
"id167" => Delta { "id165" => Delta {
"deleted": { "deleted": {
"boundElements": [ "boundElements": [
{ {
"id": "id168", "id": "id166",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -1869,7 +1767,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id169", "id": "id167",
"index": "a0", "index": "a0",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -1901,7 +1799,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id170", "id": "id168",
"index": "a1", "index": "a1",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -1943,7 +1841,7 @@ History {
"elementsChange": ElementsChange { "elementsChange": ElementsChange {
"added": Map {}, "added": Map {},
"removed": Map { "removed": Map {
"id169" => Delta { "id167" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -1974,7 +1872,7 @@ History {
"isDeleted": true, "isDeleted": true,
}, },
}, },
"id170" => Delta { "id168" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -2092,7 +1990,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"scrollX": 0, "scrollX": 0,
"scrollY": 0, "scrollY": 0,
"selectedElementIds": { "selectedElementIds": {
"id173": true, "id171": true,
}, },
"selectedElementsAreBeingDragged": false, "selectedElementsAreBeingDragged": false,
"selectedGroupIds": {}, "selectedGroupIds": {},
@@ -2123,7 +2021,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"backgroundColor": "transparent", "backgroundColor": "transparent",
"boundElements": [ "boundElements": [
{ {
"id": "id173", "id": "id171",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -2132,7 +2030,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id171", "id": "id169",
"index": "a0", "index": "a0",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -2160,7 +2058,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"backgroundColor": "transparent", "backgroundColor": "transparent",
"boundElements": [ "boundElements": [
{ {
"id": "id173", "id": "id171",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -2169,7 +2067,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id172", "id": "id170",
"index": "a1", "index": "a1",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -2199,7 +2097,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"customData": undefined, "customData": undefined,
"endArrowhead": "arrow", "endArrowhead": "arrow",
"endBinding": { "endBinding": {
"elementId": "id172", "elementId": "id170",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -2207,7 +2105,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 373.7994222717614, "height": 373.7994222717614,
"id": "id173", "id": "id171",
"index": "a2", "index": "a2",
"isDeleted": false, "isDeleted": false,
"lastCommittedPoint": null, "lastCommittedPoint": null,
@@ -2230,7 +2128,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
}, },
"startArrowhead": null, "startArrowhead": null,
"startBinding": { "startBinding": {
"elementId": "id171", "elementId": "id169",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -2266,7 +2164,7 @@ History {
"elementsChange": ElementsChange { "elementsChange": ElementsChange {
"added": Map {}, "added": Map {},
"removed": Map { "removed": Map {
"id171" => Delta { "id169" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -2297,7 +2195,7 @@ History {
"isDeleted": true, "isDeleted": true,
}, },
}, },
"id172" => Delta { "id170" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -2337,9 +2235,9 @@ History {
"delta": Delta { "delta": Delta {
"deleted": { "deleted": {
"selectedElementIds": { "selectedElementIds": {
"id173": true, "id171": true,
}, },
"selectedLinearElementId": "id173", "selectedLinearElementId": "id171",
}, },
"inserted": { "inserted": {
"selectedElementIds": {}, "selectedElementIds": {},
@@ -2350,7 +2248,7 @@ History {
"elementsChange": ElementsChange { "elementsChange": ElementsChange {
"added": Map {}, "added": Map {},
"removed": Map { "removed": Map {
"id173" => Delta { "id171" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -2358,7 +2256,7 @@ History {
"customData": undefined, "customData": undefined,
"endArrowhead": "arrow", "endArrowhead": "arrow",
"endBinding": { "endBinding": {
"elementId": "id172", "elementId": "id170",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -2388,7 +2286,7 @@ History {
}, },
"startArrowhead": null, "startArrowhead": null,
"startBinding": { "startBinding": {
"elementId": "id171", "elementId": "id169",
"focus": 0, "focus": 0,
"gap": 1, "gap": 1,
}, },
@@ -2406,11 +2304,11 @@ History {
}, },
}, },
"updated": Map { "updated": Map {
"id171" => Delta { "id169" => Delta {
"deleted": { "deleted": {
"boundElements": [ "boundElements": [
{ {
"id": "id173", "id": "id171",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -2419,11 +2317,11 @@ History {
"boundElements": [], "boundElements": [],
}, },
}, },
"id172" => Delta { "id170" => Delta {
"deleted": { "deleted": {
"boundElements": [ "boundElements": [
{ {
"id": "id173", "id": "id171",
"type": "arrow", "type": "arrow",
}, },
], ],
@@ -5301,7 +5199,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 100, "height": 100,
"id": "id175", "id": "id173",
"index": "Zz", "index": "Zz",
"isDeleted": false, "isDeleted": false,
"link": null, "link": null,
@@ -5333,7 +5231,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
"frameId": null, "frameId": null,
"groupIds": [], "groupIds": [],
"height": 500, "height": 500,
"id": "id174", "id": "id172",
"index": "a0", "index": "a0",
"isDeleted": true, "isDeleted": true,
"link": null, "link": null,
@@ -5376,7 +5274,7 @@ History {
"elementsChange": ElementsChange { "elementsChange": ElementsChange {
"added": Map {}, "added": Map {},
"removed": Map { "removed": Map {
"id175" => Delta { "id173" => Delta {
"deleted": { "deleted": {
"angle": 0, "angle": 0,
"backgroundColor": "transparent", "backgroundColor": "transparent",
@@ -5422,9 +5320,9 @@ History {
"added": Map {}, "added": Map {},
"removed": Map {}, "removed": Map {},
"updated": Map { "updated": Map {
"id175" => Delta { "id173" => Delta {
"deleted": { "deleted": {
"frameId": "id174", "frameId": "id172",
}, },
"inserted": { "inserted": {
"frameId": null, "frameId": null,
+18 -41
View File
@@ -20,7 +20,6 @@ describe("element binding", () => {
const rect = API.createElement({ const rect = API.createElement({
type: "rectangle", type: "rectangle",
x: 0, x: 0,
y: 0,
width: 50, width: 50,
height: 50, height: 50,
}); });
@@ -40,43 +39,32 @@ describe("element binding", () => {
h.elements = [rect, arrow]; h.elements = [rect, arrow];
expect(arrow.startBinding).toBe(null); expect(arrow.startBinding).toBe(null);
// select arrow API.setSelectedElements([arrow]);
mouse.clickAt(150, 0);
// move arrow start to potential binding position
mouse.downAt(100, 0);
mouse.moveTo(55, 0);
mouse.up(0, 0);
// Point selection is evaluated like the points are rendered,
// from right to left. So clicking on the first point should move the joint,
// not the start point.
expect(arrow.startBinding).toBe(null);
// Now that the start point is free, move it into overlapping position
mouse.downAt(100, 0);
mouse.moveTo(55, 0);
mouse.up(0, 0);
expect(API.getSelectedElements()).toEqual([arrow]); expect(API.getSelectedElements()).toEqual([arrow]);
mouse.downAt(100, 0);
expect(arrow.startBinding).toEqual({
elementId: rect.id,
focus: expect.toBeNonNaNNumber(),
gap: expect.toBeNonNaNNumber(),
});
// Move the end point to the overlapping binding position
mouse.downAt(200, 0);
mouse.moveTo(55, 0); mouse.moveTo(55, 0);
mouse.up(0, 0); mouse.up(0, 0);
expect(API.getSelectedElements()).toEqual([arrow]);
// Both the start and the end points should be bound
expect(arrow.startBinding).toEqual({ expect(arrow.startBinding).toEqual({
elementId: rect.id, elementId: rect.id,
focus: expect.toBeNonNaNNumber(), focus: expect.toBeNonNaNNumber(),
gap: expect.toBeNonNaNNumber(), gap: expect.toBeNonNaNNumber(),
}); });
mouse.downAt(100, 0);
mouse.move(-45, 0);
mouse.up();
expect(arrow.startBinding).toEqual({
elementId: rect.id,
focus: expect.toBeNonNaNNumber(),
gap: expect.toBeNonNaNNumber(),
});
mouse.down();
mouse.move(-50, 0);
mouse.up();
expect(arrow.startBinding).toBe(null);
expect(arrow.endBinding).toEqual({ expect(arrow.endBinding).toEqual({
elementId: rect.id, elementId: rect.id,
focus: expect.toBeNonNaNNumber(), focus: expect.toBeNonNaNNumber(),
@@ -156,7 +144,7 @@ describe("element binding", () => {
}, },
); );
it("should unbind arrow when moving it with keyboard", () => { it("should bind/unbind arrow when moving it with keyboard", () => {
const rectangle = UI.createElement("rectangle", { const rectangle = UI.createElement("rectangle", {
x: 75, x: 75,
y: 0, y: 0,
@@ -172,23 +160,12 @@ describe("element binding", () => {
expect(arrow.endBinding).toBe(null); expect(arrow.endBinding).toBe(null);
mouse.downAt(50, 50);
mouse.moveTo(51, 0);
mouse.up(0, 0);
// Test sticky connection
expect(API.getSelectedElement().type).toBe("arrow"); expect(API.getSelectedElement().type).toBe("arrow");
Keyboard.keyPress(KEYS.ARROW_RIGHT); Keyboard.keyPress(KEYS.ARROW_RIGHT);
expect(arrow.endBinding?.elementId).toBe(rectangle.id); expect(arrow.endBinding?.elementId).toBe(rectangle.id);
Keyboard.keyPress(KEYS.ARROW_LEFT);
expect(arrow.endBinding?.elementId).toBe(rectangle.id);
// Sever connection
expect(API.getSelectedElement().type).toBe("arrow");
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);
expect(arrow.endBinding).toBe(null);
}); });
it("should unbind on bound element deletion", () => { it("should unbind on bound element deletion", () => {
+9 -26
View File
@@ -4014,18 +4014,12 @@ describe("history", () => {
const arrowId = h.elements[2].id; const arrowId = h.elements[2].id;
// create start binding // create binding
mouse.downAt(0, 0); mouse.downAt(0, 0);
mouse.moveTo(0, 1); mouse.moveTo(0, 1);
mouse.moveTo(0, 0); mouse.moveTo(0, 0);
mouse.up(); mouse.up();
// create end binding
mouse.downAt(100, 0);
mouse.moveTo(100, 1);
mouse.moveTo(100, 0);
mouse.up();
expect(h.elements).toEqual([ expect(h.elements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: rect1.id, id: rect1.id,
@@ -4050,10 +4044,9 @@ describe("history", () => {
}), }),
]); ]);
Keyboard.undo(); // undo start binding Keyboard.undo();
Keyboard.undo(); // undo end binding
expect(API.getUndoStack().length).toBe(2); expect(API.getUndoStack().length).toBe(2);
expect(API.getRedoStack().length).toBe(2); expect(API.getRedoStack().length).toBe(1);
expect(h.elements).toEqual([ expect(h.elements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: rect1.id, id: rect1.id,
@@ -4088,8 +4081,7 @@ describe("history", () => {
runTwice(() => { runTwice(() => {
Keyboard.redo(); Keyboard.redo();
Keyboard.redo(); expect(API.getUndoStack().length).toBe(3);
expect(API.getUndoStack().length).toBe(4);
expect(API.getRedoStack().length).toBe(0); expect(API.getRedoStack().length).toBe(0);
expect(h.elements).toEqual([ expect(h.elements).toEqual([
expect.objectContaining({ expect.objectContaining({
@@ -4115,10 +4107,9 @@ describe("history", () => {
}), }),
]); ]);
Keyboard.undo();
Keyboard.undo(); Keyboard.undo();
expect(API.getUndoStack().length).toBe(2); expect(API.getUndoStack().length).toBe(2);
expect(API.getRedoStack().length).toBe(2); expect(API.getRedoStack().length).toBe(1);
expect(h.elements).toEqual([ expect(h.elements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: rect1.id, id: rect1.id,
@@ -4144,16 +4135,11 @@ describe("history", () => {
const arrowId = h.elements[2].id; const arrowId = h.elements[2].id;
// create start binding // create binding
mouse.downAt(0, 0); mouse.downAt(0, 0);
mouse.moveTo(0, 1); mouse.moveTo(0, 1);
mouse.upAt(0, 0); mouse.upAt(0, 0);
// create end binding
mouse.downAt(100, 0);
mouse.moveTo(100, 1);
mouse.upAt(100, 0);
expect(h.elements).toEqual([ expect(h.elements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: rect1.id, id: rect1.id,
@@ -4178,10 +4164,9 @@ describe("history", () => {
}), }),
]); ]);
Keyboard.undo();
Keyboard.undo(); Keyboard.undo();
expect(API.getUndoStack().length).toBe(2); expect(API.getUndoStack().length).toBe(2);
expect(API.getRedoStack().length).toBe(2); expect(API.getRedoStack().length).toBe(1);
expect(h.elements).toEqual([ expect(h.elements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: rect1.id, id: rect1.id,
@@ -4217,8 +4202,7 @@ describe("history", () => {
runTwice(() => { runTwice(() => {
Keyboard.redo(); Keyboard.redo();
Keyboard.redo(); expect(API.getUndoStack().length).toBe(3);
expect(API.getUndoStack().length).toBe(4);
expect(API.getRedoStack().length).toBe(0); expect(API.getRedoStack().length).toBe(0);
expect(h.elements).toEqual([ expect(h.elements).toEqual([
expect.objectContaining({ expect.objectContaining({
@@ -4249,10 +4233,9 @@ describe("history", () => {
}), }),
]); ]);
Keyboard.undo();
Keyboard.undo(); Keyboard.undo();
expect(API.getUndoStack().length).toBe(2); expect(API.getUndoStack().length).toBe(2);
expect(API.getRedoStack().length).toBe(2); expect(API.getRedoStack().length).toBe(1);
expect(h.elements).toEqual([ expect(h.elements).toEqual([
expect.objectContaining({ expect.objectContaining({
id: rect1.id, id: rect1.id,
+3 -3
View File
@@ -5525,9 +5525,9 @@ eastasianwidth@^0.2.0:
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
ejs@^3.1.6, ejs@^3.1.9: ejs@^3.1.6, ejs@^3.1.9:
version "3.1.9" version "3.1.10"
resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==
dependencies: dependencies:
jake "^10.8.5" jake "^10.8.5"