fix: Add binding update to manual stat changes (#8183)

Manual stats changes now respect previous element bindings.
This commit is contained in:
Márk Tolmács 2024-07-01 09:45:31 +02:00 committed by GitHub
parent 04668d8263
commit 66a2f24296
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 327 additions and 162 deletions

View file

@ -25,7 +25,7 @@ import type {
} from "./types";
import { getElementAbsoluteCoords } from "./bounds";
import type { AppClassProperties, AppState, Point } from "../types";
import type { AppState, Point } from "../types";
import { isPointOnShape } from "../../utils/collision";
import { getElementAtPosition } from "../scene";
import {
@ -43,6 +43,7 @@ import { LinearElementEditor } from "./linearElementEditor";
import { arrayToMap, tupleToCoors } from "../utils";
import { KEYS } from "../keys";
import { getBoundTextElement, handleBindTextResize } from "./textElement";
import { getElementShape } from "../shapes";
export type SuggestedBinding =
| NonDeleted<ExcalidrawBindableElement>
@ -179,9 +180,8 @@ const bindOrUnbindLinearElementEdge = (
const getOriginalBindingIfStillCloseOfLinearElementEdge = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
edge: "start" | "end",
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
): NonDeleted<ExcalidrawElement> | null => {
const elementsMap = app.scene.getNonDeletedElementsMap();
const coors = getLinearElementEdgeCoors(linearElement, edge, elementsMap);
const elementId =
edge === "start"
@ -189,7 +189,10 @@ const getOriginalBindingIfStillCloseOfLinearElementEdge = (
: linearElement.endBinding?.elementId;
if (elementId) {
const element = elementsMap.get(elementId);
if (isBindableElement(element) && bindingBorderTest(element, coors, app)) {
if (
isBindableElement(element) &&
bindingBorderTest(element, coors, elementsMap)
) {
return element;
}
}
@ -199,13 +202,13 @@ const getOriginalBindingIfStillCloseOfLinearElementEdge = (
const getOriginalBindingsIfStillCloseToArrowEnds = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
): (NonDeleted<ExcalidrawElement> | null)[] =>
["start", "end"].map((edge) =>
getOriginalBindingIfStillCloseOfLinearElementEdge(
linearElement,
edge as "start" | "end",
app,
elementsMap,
),
);
@ -213,7 +216,7 @@ const getBindingStrategyForDraggingArrowEndpoints = (
selectedElement: NonDeleted<ExcalidrawLinearElement>,
isBindingEnabled: boolean,
draggingPoints: readonly number[],
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
): (NonDeleted<ExcalidrawBindableElement> | null | "keep")[] => {
const startIdx = 0;
const endIdx = selectedElement.points.length - 1;
@ -221,37 +224,57 @@ const getBindingStrategyForDraggingArrowEndpoints = (
const endDragged = draggingPoints.findIndex((i) => i === endIdx) > -1;
const start = startDragged
? isBindingEnabled
? getElligibleElementForBindingElement(selectedElement, "start", app)
? getElligibleElementForBindingElement(
selectedElement,
"start",
elementsMap,
)
: 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);
getElligibleElementForBindingElement(
selectedElement,
"start",
elementsMap,
);
const end = endDragged
? isBindingEnabled
? getElligibleElementForBindingElement(selectedElement, "end", app)
? getElligibleElementForBindingElement(
selectedElement,
"end",
elementsMap,
)
: 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);
getElligibleElementForBindingElement(selectedElement, "end", elementsMap);
return [start, end];
};
const getBindingStrategyForDraggingArrowOrJoints = (
selectedElement: NonDeleted<ExcalidrawLinearElement>,
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
isBindingEnabled: boolean,
): (NonDeleted<ExcalidrawBindableElement> | null | "keep")[] => {
const [startIsClose, endIsClose] = getOriginalBindingsIfStillCloseToArrowEnds(
selectedElement,
app,
elementsMap,
);
const start = startIsClose
? isBindingEnabled
? getElligibleElementForBindingElement(selectedElement, "start", app)
? getElligibleElementForBindingElement(
selectedElement,
"start",
elementsMap,
)
: null
: null;
const end = endIsClose
? isBindingEnabled
? getElligibleElementForBindingElement(selectedElement, "end", app)
? getElligibleElementForBindingElement(
selectedElement,
"end",
elementsMap,
)
: null
: null;
@ -260,7 +283,7 @@ const getBindingStrategyForDraggingArrowOrJoints = (
export const bindOrUnbindLinearElements = (
selectedElements: NonDeleted<ExcalidrawLinearElement>[],
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
isBindingEnabled: boolean,
draggingPoints: readonly number[] | null,
): void => {
@ -271,27 +294,22 @@ export const bindOrUnbindLinearElements = (
selectedElement,
isBindingEnabled,
draggingPoints ?? [],
app,
elementsMap,
)
: // The arrow itself (the shaft) or the inner joins are dragged
getBindingStrategyForDraggingArrowOrJoints(
selectedElement,
app,
elementsMap,
isBindingEnabled,
);
bindOrUnbindLinearElement(
selectedElement,
start,
end,
app.scene.getNonDeletedElementsMap(),
);
bindOrUnbindLinearElement(selectedElement, start, end, elementsMap);
});
};
export const getSuggestedBindingsForArrows = (
selectedElements: NonDeleted<ExcalidrawElement>[],
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
): SuggestedBinding[] => {
// HOT PATH: Bail out if selected elements list is too large
if (selectedElements.length > 50) {
@ -302,7 +320,7 @@ export const getSuggestedBindingsForArrows = (
selectedElements
.filter(isLinearElement)
.flatMap((element) =>
getOriginalBindingsIfStillCloseToArrowEnds(element, app),
getOriginalBindingsIfStillCloseToArrowEnds(element, elementsMap),
)
.filter(
(element): element is NonDeleted<ExcalidrawBindableElement> =>
@ -324,17 +342,20 @@ export const maybeBindLinearElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
appState: AppState,
pointerCoords: { x: number; y: number },
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
): void => {
if (appState.startBoundElement != null) {
bindLinearElement(
linearElement,
appState.startBoundElement,
"start",
app.scene.getNonDeletedElementsMap(),
elementsMap,
);
}
const hoveredElement = getHoveredElementForBinding(pointerCoords, app);
const hoveredElement = getHoveredElementForBinding(
pointerCoords,
elementsMap,
);
if (
hoveredElement != null &&
!isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
@ -343,12 +364,7 @@ export const maybeBindLinearElement = (
"end",
)
) {
bindLinearElement(
linearElement,
hoveredElement,
"end",
app.scene.getNonDeletedElementsMap(),
);
bindLinearElement(linearElement, hoveredElement, "end", elementsMap);
}
};
@ -432,13 +448,13 @@ export const getHoveredElementForBinding = (
x: number;
y: number;
},
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
): NonDeleted<ExcalidrawBindableElement> | null => {
const hoveredElement = getElementAtPosition(
app.scene.getNonDeletedElements(),
[...elementsMap].map(([_, value]) => value),
(element) =>
isBindableElement(element, false) &&
bindingBorderTest(element, pointerCoords, app),
bindingBorderTest(element, pointerCoords, elementsMap),
);
return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
};
@ -662,15 +678,11 @@ const maybeCalculateNewGapWhenScaling = (
const getElligibleElementForBindingElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
startOrEnd: "start" | "end",
app: AppClassProperties,
elementsMap: NonDeletedSceneElementsMap,
): NonDeleted<ExcalidrawBindableElement> | null => {
return getHoveredElementForBinding(
getLinearElementEdgeCoors(
linearElement,
startOrEnd,
app.scene.getNonDeletedElementsMap(),
),
app,
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
elementsMap,
);
};
@ -834,10 +846,10 @@ const newBoundElements = (
const bindingBorderTest = (
element: NonDeleted<ExcalidrawBindableElement>,
{ x, y }: { x: number; y: number },
app: AppClassProperties,
elementsMap: ElementsMap,
): boolean => {
const threshold = maxBindingGap(element, element.width, element.height);
const shape = app.getElementShape(element);
const shape = getElementShape(element, elementsMap);
return isPointOnShape([x, y], shape, threshold);
};

View file

@ -381,7 +381,7 @@ export class LinearElementEditor {
elementsMap,
),
),
app,
elementsMap,
)
: null;
@ -715,7 +715,10 @@ export class LinearElementEditor {
},
selectedPointsIndices: [element.points.length - 1],
lastUncommittedPoint: null,
endBindingElement: getHoveredElementForBinding(scenePointer, app),
endBindingElement: getHoveredElementForBinding(
scenePointer,
elementsMap,
),
};
ret.didAddPoint = true;