Refactor binding suggestions

This commit is contained in:
Mark Tolmacs 2025-05-16 16:46:56 +02:00
parent 95d89a751a
commit 9af3177dbd
No known key found for this signature in database
3 changed files with 94 additions and 83 deletions

View File

@ -398,6 +398,51 @@ export const getSuggestedBindingsForArrows = (
);
};
export const maybeSuggestBindingsForLinearElementAtCoords = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
/** scene coords */
pointerCoords: {
x: number;
y: number;
}[],
scene: Scene,
zoom: AppState["zoom"],
// During line creation the start binding hasn't been written yet
// into `linearElement`
oppositeBindingBoundElement?: ExcalidrawBindableElement | null,
): ExcalidrawBindableElement[] => {
if (!pointerCoords.length) {
return [];
}
const suggestedBindings = pointerCoords.reduce(
(acc: NonDeleted<ExcalidrawBindableElement>[], coords) => {
const hoveredBindableElement = getHoveredElementForBinding(
coords,
scene.getNonDeletedElements(),
scene.getNonDeletedElementsMap(),
zoom,
isElbowArrow(linearElement),
isElbowArrow(linearElement),
);
if (
hoveredBindableElement != null &&
!isLinearElementSimpleAndAlreadyBound(
linearElement,
oppositeBindingBoundElement?.id,
hoveredBindableElement,
)
) {
acc.push(hoveredBindableElement);
}
return acc;
},
[],
);
return suggestedBindings;
};
export const maybeBindLinearElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
appState: AppState,

View File

@ -39,6 +39,7 @@ import {
bindOrUnbindLinearElement,
getHoveredElementForBinding,
isBindingEnabled,
maybeSuggestBindingsForLinearElementAtCoords,
} from "./binding";
import {
getElementAbsoluteCoords,
@ -245,18 +246,13 @@ export class LinearElementEditor {
app: AppClassProperties,
scenePointerX: number,
scenePointerY: number,
maybeSuggestBinding: (
element: NonDeleted<ExcalidrawLinearElement>,
pointSceneCoords: { x: number; y: number }[],
) => void,
linearElementEditor: LinearElementEditor,
scene: Scene,
): LinearElementEditor | null {
): Pick<AppState, keyof AppState> | null {
if (!linearElementEditor) {
return null;
}
const { elementId } = linearElementEditor;
const elementsMap = scene.getNonDeletedElementsMap();
const elementsMap = app.scene.getNonDeletedElementsMap();
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element) {
return null;
@ -309,7 +305,7 @@ export class LinearElementEditor {
LinearElementEditor.movePoints(
element,
scene,
app.scene,
new Map([
[
selectedIndex,
@ -337,7 +333,7 @@ export class LinearElementEditor {
LinearElementEditor.movePoints(
element,
scene,
app.scene,
new Map(
selectedPointsIndices.map((pointIndex) => {
const newPointPosition: LocalPoint =
@ -369,10 +365,11 @@ export class LinearElementEditor {
const boundTextElement = getBoundTextElement(element, elementsMap);
if (boundTextElement) {
handleBindTextResize(element, scene, false);
handleBindTextResize(element, app.scene, false);
}
// suggest bindings for first and last point if selected
let suggestedBindings: ExcalidrawBindableElement[] = [];
if (isBindingElement(element, false)) {
const coords: { x: number; y: number }[] = [];
@ -404,11 +401,16 @@ export class LinearElementEditor {
}
if (coords.length) {
maybeSuggestBinding(element, coords);
suggestedBindings = maybeSuggestBindingsForLinearElementAtCoords(
element,
coords,
app.scene,
app.state.zoom,
);
}
}
return {
const newLinearElementEditor = {
...linearElementEditor,
selectedPointsIndices,
segmentMidPointHoveredCoords:
@ -427,6 +429,15 @@ export class LinearElementEditor {
: -1,
isDragging: true,
};
return {
...app.state,
editingLinearElement: app.state.editingLinearElement
? newLinearElementEditor
: null,
selectedLinearElement: newLinearElementEditor,
suggestedBindings,
};
}
return null;

View File

@ -104,7 +104,11 @@ import {
Emitter,
} from "@excalidraw/common";
import { getCommonBounds, getElementAbsoluteCoords } from "@excalidraw/element";
import {
getCommonBounds,
getElementAbsoluteCoords,
maybeSuggestBindingsForLinearElementAtCoords,
} from "@excalidraw/element";
import {
bindOrUnbindLinearElement,
@ -112,7 +116,6 @@ import {
fixBindingsAfterDeletion,
getHoveredElementForBinding,
isBindingEnabled,
isLinearElementSimpleAndAlreadyBound,
maybeBindLinearElement,
shouldEnableBindingForPointerEvent,
updateBoundElements,
@ -299,7 +302,6 @@ import type { ElementUpdate } from "@excalidraw/element";
import type { LocalPoint, Radians } from "@excalidraw/math";
import type {
ExcalidrawBindableElement,
ExcalidrawElement,
ExcalidrawFreeDrawElement,
ExcalidrawGenericElement,
@ -5961,11 +5963,15 @@ class App extends React.Component<AppProps, AppState> {
// and point
const { newElement } = this.state;
if (isBindingElement(newElement, false)) {
this.maybeSuggestBindingsForLinearElementAtCoords(
this.setState({
suggestedBindings: maybeSuggestBindingsForLinearElementAtCoords(
newElement,
[scenePointer],
this.scene,
this.state.zoom,
this.state.startBoundElement,
);
),
});
} else {
this.maybeSuggestBindingAtCursor(scenePointer, false);
}
@ -8251,31 +8257,19 @@ class App extends React.Component<AppProps, AppState> {
return;
}
const newLinearElementEditor = LinearElementEditor.handlePointDragging(
const newState = LinearElementEditor.handlePointDragging(
event,
this,
pointerCoords.x,
pointerCoords.y,
(element, pointsSceneCoords) => {
this.maybeSuggestBindingsForLinearElementAtCoords(
element,
pointsSceneCoords,
);
},
linearElementEditor,
this.scene,
);
if (newLinearElementEditor) {
if (newState) {
pointerDownState.lastCoords.x = pointerCoords.x;
pointerDownState.lastCoords.y = pointerCoords.y;
pointerDownState.drag.hasOccurred = true;
this.setState({
editingLinearElement: this.state.editingLinearElement
? newLinearElementEditor
: null,
selectedLinearElement: newLinearElementEditor,
});
this.setState(newState);
return;
}
@ -8754,11 +8748,15 @@ class App extends React.Component<AppProps, AppState> {
if (isBindingElement(newElement, false)) {
// When creating a linear element by dragging
this.maybeSuggestBindingsForLinearElementAtCoords(
this.setState({
suggestedBindings: maybeSuggestBindingsForLinearElementAtCoords(
newElement,
[pointerCoords],
this.scene,
this.state.zoom,
this.state.startBoundElement,
);
),
});
}
} else {
pointerDownState.lastCoords.x = pointerCoords.x;
@ -10285,49 +10283,6 @@ class App extends React.Component<AppProps, AppState> {
});
};
private maybeSuggestBindingsForLinearElementAtCoords = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
/** scene coords */
pointerCoords: {
x: number;
y: number;
}[],
// During line creation the start binding hasn't been written yet
// into `linearElement`
oppositeBindingBoundElement?: ExcalidrawBindableElement | null,
): void => {
if (!pointerCoords.length) {
return;
}
const suggestedBindings = pointerCoords.reduce(
(acc: NonDeleted<ExcalidrawBindableElement>[], coords) => {
const hoveredBindableElement = getHoveredElementForBinding(
coords,
this.scene.getNonDeletedElements(),
this.scene.getNonDeletedElementsMap(),
this.state.zoom,
isElbowArrow(linearElement),
isElbowArrow(linearElement),
);
if (
hoveredBindableElement != null &&
!isLinearElementSimpleAndAlreadyBound(
linearElement,
oppositeBindingBoundElement?.id,
hoveredBindableElement,
)
) {
acc.push(hoveredBindableElement);
}
return acc;
},
[],
);
this.setState({ suggestedBindings });
};
private clearSelection(hitElement: ExcalidrawElement | null): void {
this.setState((prevState) => ({
selectedElementIds: makeNextSelectedElementIds({}, prevState),