Fix arrow rebinding on rotation (take 2) (#2104)
* Clear up test, fix simple rotation * Fix eligibility rules
This commit is contained in:
parent
26ef235019
commit
7ebeae2d38
@ -167,7 +167,6 @@ import {
|
|||||||
bindOrUnbindSelectedElements,
|
bindOrUnbindSelectedElements,
|
||||||
unbindLinearElements,
|
unbindLinearElements,
|
||||||
fixBindingsAfterDuplication,
|
fixBindingsAfterDuplication,
|
||||||
getElligibleElementForBindingElementAtCoors,
|
|
||||||
fixBindingsAfterDeletion,
|
fixBindingsAfterDeletion,
|
||||||
isLinearElementSimpleAndAlreadyBound,
|
isLinearElementSimpleAndAlreadyBound,
|
||||||
isBindingEnabled,
|
isBindingEnabled,
|
||||||
@ -2944,7 +2943,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this.maybeSuggestBindingForAll(selectedElements);
|
this.maybeSuggestBindingForAll(selectedElements);
|
||||||
bindOrUnbindSelectedElements(selectedElements);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3201,6 +3199,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
elementType,
|
elementType,
|
||||||
elementLocked,
|
elementLocked,
|
||||||
isResizing,
|
isResizing,
|
||||||
|
isRotating,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -3313,7 +3312,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3337,12 +3335,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
draggingElement,
|
draggingElement,
|
||||||
getNormalizedDimensions(draggingElement),
|
getNormalizedDimensions(draggingElement),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isBindingEnabled(this.state)) {
|
|
||||||
bindOrUnbindSelectedElements(
|
|
||||||
getSelectedElements(this.scene.getElements(), this.state),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resizingElement) {
|
if (resizingElement) {
|
||||||
@ -3470,7 +3462,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
history.resumeRecording();
|
history.resumeRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pointerDownState.drag.hasOccurred || isResizing) {
|
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
||||||
(isBindingEnabled(this.state)
|
(isBindingEnabled(this.state)
|
||||||
? bindOrUnbindSelectedElements
|
? bindOrUnbindSelectedElements
|
||||||
: unbindLinearElements)(
|
: unbindLinearElements)(
|
||||||
@ -3519,10 +3511,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
// into `linearElement`
|
// into `linearElement`
|
||||||
oppositeBindingBoundElement?: ExcalidrawBindableElement | null,
|
oppositeBindingBoundElement?: ExcalidrawBindableElement | null,
|
||||||
): void => {
|
): void => {
|
||||||
const hoveredBindableElement = getElligibleElementForBindingElementAtCoors(
|
const hoveredBindableElement = getHoveredElementForBinding(
|
||||||
linearElement,
|
|
||||||
startOrEnd,
|
|
||||||
pointerCoords,
|
pointerCoords,
|
||||||
|
this.scene,
|
||||||
);
|
);
|
||||||
this.setState({
|
this.setState({
|
||||||
suggestedBindings:
|
suggestedBindings:
|
||||||
|
@ -46,6 +46,7 @@ export const bindOrUnbindLinearElement = (
|
|||||||
bindOrUnbindLinearElementEdge(
|
bindOrUnbindLinearElementEdge(
|
||||||
linearElement,
|
linearElement,
|
||||||
startBindingElement,
|
startBindingElement,
|
||||||
|
endBindingElement,
|
||||||
"start",
|
"start",
|
||||||
boundToElementIds,
|
boundToElementIds,
|
||||||
unboundFromElementIds,
|
unboundFromElementIds,
|
||||||
@ -53,6 +54,7 @@ export const bindOrUnbindLinearElement = (
|
|||||||
bindOrUnbindLinearElementEdge(
|
bindOrUnbindLinearElementEdge(
|
||||||
linearElement,
|
linearElement,
|
||||||
endBindingElement,
|
endBindingElement,
|
||||||
|
startBindingElement,
|
||||||
"end",
|
"end",
|
||||||
boundToElementIds,
|
boundToElementIds,
|
||||||
unboundFromElementIds,
|
unboundFromElementIds,
|
||||||
@ -75,6 +77,7 @@ export const bindOrUnbindLinearElement = (
|
|||||||
const bindOrUnbindLinearElementEdge = (
|
const bindOrUnbindLinearElementEdge = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
bindableElement: ExcalidrawBindableElement | null | "keep",
|
bindableElement: ExcalidrawBindableElement | null | "keep",
|
||||||
|
otherEdgeBindableElement: ExcalidrawBindableElement | null | "keep",
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
// Is mutated
|
// Is mutated
|
||||||
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
|
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||||
@ -83,8 +86,22 @@ const bindOrUnbindLinearElementEdge = (
|
|||||||
): void => {
|
): void => {
|
||||||
if (bindableElement !== "keep") {
|
if (bindableElement !== "keep") {
|
||||||
if (bindableElement != null) {
|
if (bindableElement != null) {
|
||||||
|
// 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 (
|
||||||
|
otherEdgeBindableElement == null ||
|
||||||
|
(otherEdgeBindableElement === "keep"
|
||||||
|
? !isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
|
||||||
|
linearElement,
|
||||||
|
bindableElement,
|
||||||
|
startOrEnd,
|
||||||
|
)
|
||||||
|
: startOrEnd === "start" ||
|
||||||
|
otherEdgeBindableElement.id !== bindableElement.id)
|
||||||
|
) {
|
||||||
bindLinearElement(linearElement, bindableElement, startOrEnd);
|
bindLinearElement(linearElement, bindableElement, startOrEnd);
|
||||||
boundToElementIds.add(bindableElement.id);
|
boundToElementIds.add(bindableElement.id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const unbound = unbindLinearElement(linearElement, startOrEnd);
|
const unbound = unbindLinearElement(linearElement, startOrEnd);
|
||||||
if (unbound != null) {
|
if (unbound != null) {
|
||||||
@ -110,7 +127,7 @@ export const bindOrUnbindSelectedElements = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const maybeBindBindableElement = (
|
const maybeBindBindableElement = (
|
||||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||||
): void => {
|
): void => {
|
||||||
getElligibleElementsForBindableElementAndWhere(
|
getElligibleElementsForBindableElementAndWhere(
|
||||||
@ -134,7 +151,14 @@ export const maybeBindLinearElement = (
|
|||||||
bindLinearElement(linearElement, appState.startBoundElement, "start");
|
bindLinearElement(linearElement, appState.startBoundElement, "start");
|
||||||
}
|
}
|
||||||
const hoveredElement = getHoveredElementForBinding(pointerCoords, scene);
|
const hoveredElement = getHoveredElementForBinding(pointerCoords, scene);
|
||||||
if (hoveredElement != null) {
|
if (
|
||||||
|
hoveredElement != null &&
|
||||||
|
!isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
|
||||||
|
linearElement,
|
||||||
|
hoveredElement,
|
||||||
|
"end",
|
||||||
|
)
|
||||||
|
) {
|
||||||
bindLinearElement(linearElement, hoveredElement, "end");
|
bindLinearElement(linearElement, hoveredElement, "end");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -144,15 +168,6 @@ const bindLinearElement = (
|
|||||||
hoveredElement: ExcalidrawBindableElement,
|
hoveredElement: ExcalidrawBindableElement,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
): void => {
|
): void => {
|
||||||
if (
|
|
||||||
isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
|
|
||||||
linearElement,
|
|
||||||
hoveredElement,
|
|
||||||
startOrEnd,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mutateElement(linearElement, {
|
mutateElement(linearElement, {
|
||||||
[startOrEnd === "start" ? "startBinding" : "endBinding"]: {
|
[startOrEnd === "start" ? "startBinding" : "endBinding"]: {
|
||||||
elementId: hoveredElement.id,
|
elementId: hoveredElement.id,
|
||||||
@ -442,40 +457,10 @@ const getElligibleElementForBindingElement = (
|
|||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
): NonDeleted<ExcalidrawBindableElement> | null => {
|
): NonDeleted<ExcalidrawBindableElement> | null => {
|
||||||
return getElligibleElementForBindingElementAtCoors(
|
return getHoveredElementForBinding(
|
||||||
linearElement,
|
|
||||||
startOrEnd,
|
|
||||||
getLinearElementEdgeCoors(linearElement, startOrEnd),
|
getLinearElementEdgeCoors(linearElement, startOrEnd),
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getElligibleElementForBindingElementAtCoors = (
|
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
|
||||||
startOrEnd: "start" | "end",
|
|
||||||
pointerCoords: {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
},
|
|
||||||
): NonDeleted<ExcalidrawBindableElement> | null => {
|
|
||||||
const bindableElement = getHoveredElementForBinding(
|
|
||||||
pointerCoords,
|
|
||||||
Scene.getScene(linearElement)!,
|
Scene.getScene(linearElement)!,
|
||||||
);
|
);
|
||||||
if (bindableElement == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Note: We could push this check inside a version of
|
|
||||||
// `getHoveredElementForBinding`, but it's unlikely this is needed.
|
|
||||||
if (
|
|
||||||
isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
|
|
||||||
linearElement,
|
|
||||||
bindableElement,
|
|
||||||
startOrEnd,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return bindableElement;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLinearElementEdgeCoors = (
|
const getLinearElementEdgeCoors = (
|
||||||
|
@ -13,37 +13,36 @@ describe("element binding", () => {
|
|||||||
render(<App />);
|
render(<App />);
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE if this tests fails, skip it -- it was really flaky at one point
|
|
||||||
it("rotation of arrow should rebind both ends", () => {
|
it("rotation of arrow should rebind both ends", () => {
|
||||||
const rect1 = UI.createElement("rectangle", {
|
const rectLeft = UI.createElement("rectangle", {
|
||||||
x: 0,
|
x: 0,
|
||||||
width: 100,
|
width: 200,
|
||||||
height: 1000,
|
height: 500,
|
||||||
});
|
});
|
||||||
const rect2 = UI.createElement("rectangle", {
|
const rectRight = UI.createElement("rectangle", {
|
||||||
x: 200,
|
x: 400,
|
||||||
width: 100,
|
width: 200,
|
||||||
height: 1000,
|
height: 500,
|
||||||
});
|
});
|
||||||
const arrow = UI.createElement("arrow", {
|
const arrow = UI.createElement("arrow", {
|
||||||
x: 110,
|
x: 220,
|
||||||
y: 50,
|
y: 250,
|
||||||
width: 80,
|
width: 160,
|
||||||
height: 1,
|
height: 1,
|
||||||
});
|
});
|
||||||
expect(arrow.startBinding?.elementId).toBe(rect1.id);
|
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
||||||
expect(arrow.endBinding?.elementId).toBe(rect2.id);
|
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
|
||||||
|
|
||||||
const { rotation } = getTransformHandles(arrow, h.state.zoom, "mouse");
|
const rotation = getTransformHandles(arrow, h.state.zoom, "mouse")
|
||||||
if (rotation) {
|
.rotation!;
|
||||||
const rotationHandleX = rotation[0] + rotation[2] / 2;
|
const rotationHandleX = rotation[0] + rotation[2] / 2;
|
||||||
const rotationHandleY = rotation[1] + rotation[3] / 2;
|
const rotationHandleY = rotation[1] + rotation[3] / 2;
|
||||||
mouse.down(rotationHandleX, rotationHandleY);
|
mouse.down(rotationHandleX, rotationHandleY);
|
||||||
mouse.move(0, 1000);
|
mouse.move(300, 400);
|
||||||
mouse.up();
|
mouse.up();
|
||||||
}
|
expect(arrow.angle).toBeGreaterThan(0.7 * Math.PI);
|
||||||
expect(arrow.angle).toBeGreaterThan(3);
|
expect(arrow.angle).toBeLessThan(1.3 * Math.PI);
|
||||||
expect(arrow.startBinding?.elementId).toBe(rect2.id);
|
expect(arrow.startBinding?.elementId).toBe(rectRight.id);
|
||||||
expect(arrow.endBinding?.elementId).toBe(rect1.id);
|
expect(arrow.endBinding?.elementId).toBe(rectLeft.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user