mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-02-18 13:29:36 +01:00
fix: make LinearElementEditor independent of scene (#7670)
* fix: make LinearElementEditor independent of scene * more fixes * pass elements and elementsMap to maybeBindBindableElement,getHoveredElementForBinding,bindingBorderTest,getElligibleElementsForBindableElementAndWhere,isLinearElementEligibleForNewBindingByBindable * replace `ElementsMap` with `NonDeletedSceneElementsMap` & remove unused params * fix lint --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
47f87f4ecb
commit
9013c84524
@ -107,7 +107,7 @@ export const actionCut = register({
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState, event: ClipboardEvent | null, app) => {
|
||||
actionCopy.perform(elements, appState, event, app);
|
||||
return actionDeleteSelected.perform(elements, appState);
|
||||
return actionDeleteSelected.perform(elements, appState, null, app);
|
||||
},
|
||||
contextItemLabel: "labels.cut",
|
||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
|
||||
|
@ -73,7 +73,7 @@ const handleGroupEditingState = (
|
||||
export const actionDeleteSelected = register({
|
||||
name: "deleteSelectedElements",
|
||||
trackEvent: { category: "element", action: "delete" },
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, formData, app) => {
|
||||
if (appState.editingLinearElement) {
|
||||
const {
|
||||
elementId,
|
||||
@ -81,7 +81,8 @@ export const actionDeleteSelected = register({
|
||||
startBindingElement,
|
||||
endBindingElement,
|
||||
} = appState.editingLinearElement;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
|
@ -35,10 +35,14 @@ import {
|
||||
export const actionDuplicateSelection = register({
|
||||
name: "duplicateSelection",
|
||||
trackEvent: { category: "element" },
|
||||
perform: (elements, appState) => {
|
||||
perform: (elements, appState, formData, app) => {
|
||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||
// duplicate selected point(s) if editing a line
|
||||
if (appState.editingLinearElement) {
|
||||
const ret = LinearElementEditor.duplicateSelectedPoints(appState);
|
||||
const ret = LinearElementEditor.duplicateSelectedPoints(
|
||||
appState,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
if (!ret) {
|
||||
return false;
|
||||
|
@ -26,12 +26,12 @@ export const actionFinalize = register({
|
||||
_,
|
||||
{ interactiveCanvas, focusContainer, scene },
|
||||
) => {
|
||||
const elementsMap = arrayToMap(elements);
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
|
||||
if (appState.editingLinearElement) {
|
||||
const { elementId, startBindingElement, endBindingElement } =
|
||||
appState.editingLinearElement;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
|
||||
if (element) {
|
||||
if (isBindingElement(element)) {
|
||||
@ -191,7 +191,7 @@ export const actionFinalize = register({
|
||||
// To select the linear element when user has finished mutipoint editing
|
||||
selectedLinearElement:
|
||||
multiPointElement && isLinearElement(multiPointElement)
|
||||
? new LinearElementEditor(multiPointElement, scene)
|
||||
? new LinearElementEditor(multiPointElement)
|
||||
: appState.selectedLinearElement,
|
||||
pendingImageElementId: null,
|
||||
},
|
||||
|
@ -4,7 +4,6 @@ import { getNonDeletedElements } from "../element";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
NonDeleted,
|
||||
NonDeletedElementsMap,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../element/types";
|
||||
import { resizeMultipleElements } from "../element/resizeElements";
|
||||
@ -68,7 +67,7 @@ export const actionFlipVertical = register({
|
||||
|
||||
const flipSelectedElements = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
appState: Readonly<AppState>,
|
||||
flipDirection: "horizontal" | "vertical",
|
||||
) => {
|
||||
@ -83,6 +82,7 @@ const flipSelectedElements = (
|
||||
|
||||
const updatedElements = flipElements(
|
||||
selectedElements,
|
||||
elements,
|
||||
elementsMap,
|
||||
appState,
|
||||
flipDirection,
|
||||
@ -97,7 +97,8 @@ const flipSelectedElements = (
|
||||
|
||||
const flipElements = (
|
||||
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
appState: AppState,
|
||||
flipDirection: "horizontal" | "vertical",
|
||||
): ExcalidrawElement[] => {
|
||||
@ -113,9 +114,9 @@ const flipElements = (
|
||||
flipDirection === "horizontal" ? minY : maxY,
|
||||
);
|
||||
|
||||
(isBindingEnabled(appState)
|
||||
? bindOrUnbindSelectedElements
|
||||
: unbindLinearElements)(selectedElements, elementsMap);
|
||||
isBindingEnabled(appState)
|
||||
? bindOrUnbindSelectedElements(selectedElements, elements, elementsMap)
|
||||
: unbindLinearElements(selectedElements, elementsMap);
|
||||
|
||||
return selectedElements;
|
||||
};
|
||||
|
@ -24,7 +24,7 @@ export const actionToggleLinearEditor = register({
|
||||
const editingLinearElement =
|
||||
appState.editingLinearElement?.elementId === selectedElement.id
|
||||
? null
|
||||
: new LinearElementEditor(selectedElement, app.scene);
|
||||
: new LinearElementEditor(selectedElement);
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
|
@ -43,7 +43,7 @@ export const actionSelectAll = register({
|
||||
// single linear element selected
|
||||
Object.keys(selectedElementIds).length === 1 &&
|
||||
isLinearElement(elements[0])
|
||||
? new LinearElementEditor(elements[0], app.scene)
|
||||
? new LinearElementEditor(elements[0])
|
||||
: null,
|
||||
},
|
||||
commitToHistory: true,
|
||||
|
@ -2603,7 +2603,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||
this.updateEmbeddables();
|
||||
const elements = this.scene.getElementsIncludingDeleted();
|
||||
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
|
||||
if (!this.state.showWelcomeScreen && !elements.length) {
|
||||
this.setState({ showWelcomeScreen: true });
|
||||
@ -3860,7 +3860,6 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.setState({
|
||||
editingLinearElement: new LinearElementEditor(
|
||||
selectedElement,
|
||||
this.scene,
|
||||
),
|
||||
});
|
||||
}
|
||||
@ -4013,7 +4012,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
isBindingEnabled(this.state)
|
||||
? bindOrUnbindSelectedElements(selectedElements, elementsMap)
|
||||
? bindOrUnbindSelectedElements(
|
||||
selectedElements,
|
||||
this.scene.getNonDeletedElements(),
|
||||
elementsMap,
|
||||
)
|
||||
: unbindLinearElements(selectedElements, elementsMap);
|
||||
this.setState({ suggestedBindings: [] });
|
||||
}
|
||||
@ -4578,10 +4581,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
) {
|
||||
this.history.resumeRecording();
|
||||
this.setState({
|
||||
editingLinearElement: new LinearElementEditor(
|
||||
selectedElements[0],
|
||||
this.scene,
|
||||
),
|
||||
editingLinearElement: new LinearElementEditor(selectedElements[0]),
|
||||
});
|
||||
return;
|
||||
} else if (
|
||||
@ -5305,10 +5305,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
scenePointerX: number,
|
||||
scenePointerY: number,
|
||||
) {
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
elementsMap,
|
||||
);
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
|
||||
if (!element) {
|
||||
@ -6122,7 +6124,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.history,
|
||||
pointerDownState.origin,
|
||||
linearElementEditor,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.scene.getNonDeletedElements(),
|
||||
elementsMap,
|
||||
);
|
||||
if (ret.hitElement) {
|
||||
pointerDownState.hit.element = ret.hitElement;
|
||||
@ -6459,7 +6462,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
const boundElement = getHoveredElementForBinding(
|
||||
pointerDownState.origin,
|
||||
this.scene,
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
this.scene.addNewElement(element);
|
||||
this.setState({
|
||||
@ -6727,7 +6731,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
const boundElement = getHoveredElementForBinding(
|
||||
pointerDownState.origin,
|
||||
this.scene,
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
|
||||
this.scene.addNewElement(element);
|
||||
@ -6997,6 +7002,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
|
||||
if (this.state.selectedLinearElement) {
|
||||
const linearElementEditor =
|
||||
@ -7007,6 +7013,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.state.selectedLinearElement,
|
||||
pointerCoords,
|
||||
this.state,
|
||||
elementsMap,
|
||||
)
|
||||
) {
|
||||
const ret = LinearElementEditor.addMidpoint(
|
||||
@ -7014,7 +7021,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerCoords,
|
||||
this.state,
|
||||
!event[KEYS.CTRL_OR_CMD],
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
elementsMap,
|
||||
);
|
||||
if (!ret) {
|
||||
return;
|
||||
@ -7435,10 +7442,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
selectedLinearElement:
|
||||
elementsWithinSelection.length === 1 &&
|
||||
isLinearElement(elementsWithinSelection[0])
|
||||
? new LinearElementEditor(
|
||||
elementsWithinSelection[0],
|
||||
this.scene,
|
||||
)
|
||||
? new LinearElementEditor(elementsWithinSelection[0])
|
||||
: null,
|
||||
showHyperlinkPopup:
|
||||
elementsWithinSelection.length === 1 &&
|
||||
@ -7539,6 +7543,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
childEvent,
|
||||
this.state.editingLinearElement,
|
||||
this.state,
|
||||
this.scene.getNonDeletedElements(),
|
||||
elementsMap,
|
||||
);
|
||||
if (editingLinearElement !== this.state.editingLinearElement) {
|
||||
@ -7563,6 +7568,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
childEvent,
|
||||
this.state.selectedLinearElement,
|
||||
this.state,
|
||||
this.scene.getNonDeletedElements(),
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
@ -7732,10 +7738,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
prevState,
|
||||
),
|
||||
selectedLinearElement: new LinearElementEditor(
|
||||
draggingElement,
|
||||
this.scene,
|
||||
),
|
||||
selectedLinearElement: new LinearElementEditor(draggingElement),
|
||||
}));
|
||||
} else {
|
||||
this.setState((prevState) => ({
|
||||
@ -7975,10 +7978,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
// the one we've hit
|
||||
if (selectedELements.length === 1) {
|
||||
this.setState({
|
||||
selectedLinearElement: new LinearElementEditor(
|
||||
hitElement,
|
||||
this.scene,
|
||||
),
|
||||
selectedLinearElement: new LinearElementEditor(hitElement),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -8091,10 +8091,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
selectedLinearElement:
|
||||
newSelectedElements.length === 1 &&
|
||||
isLinearElement(newSelectedElements[0])
|
||||
? new LinearElementEditor(
|
||||
newSelectedElements[0],
|
||||
this.scene,
|
||||
)
|
||||
? new LinearElementEditor(newSelectedElements[0])
|
||||
: prevState.selectedLinearElement,
|
||||
};
|
||||
});
|
||||
@ -8168,7 +8165,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
// Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1.
|
||||
// Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized
|
||||
prevState.selectedLinearElement?.elementId !== hitElement.id
|
||||
? new LinearElementEditor(hitElement, this.scene)
|
||||
? new LinearElementEditor(hitElement)
|
||||
: prevState.selectedLinearElement,
|
||||
}));
|
||||
}
|
||||
@ -8232,9 +8229,13 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
|
||||
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
||||
(isBindingEnabled(this.state)
|
||||
? bindOrUnbindSelectedElements
|
||||
: unbindLinearElements)(
|
||||
isBindingEnabled(this.state)
|
||||
? bindOrUnbindSelectedElements(
|
||||
this.scene.getSelectedElements(this.state),
|
||||
this.scene.getNonDeletedElements(),
|
||||
elementsMap,
|
||||
)
|
||||
: unbindLinearElements(
|
||||
this.scene.getSelectedElements(this.state),
|
||||
elementsMap,
|
||||
);
|
||||
@ -8714,7 +8715,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}): void => {
|
||||
const hoveredBindableElement = getHoveredElementForBinding(
|
||||
pointerCoords,
|
||||
this.scene,
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
this.setState({
|
||||
suggestedBindings:
|
||||
@ -8741,7 +8743,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
(acc: NonDeleted<ExcalidrawBindableElement>[], coords) => {
|
||||
const hoveredBindableElement = getHoveredElementForBinding(
|
||||
coords,
|
||||
this.scene,
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (
|
||||
hoveredBindableElement != null &&
|
||||
@ -8769,6 +8772,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
const suggestedBindings = getEligibleElementsForBinding(
|
||||
selectedElements,
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
this.setState({ suggestedBindings });
|
||||
@ -9037,7 +9041,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this,
|
||||
),
|
||||
selectedLinearElement: isLinearElement(element)
|
||||
? new LinearElementEditor(element, this.scene)
|
||||
? new LinearElementEditor(element)
|
||||
: null,
|
||||
}
|
||||
: this.state),
|
||||
|
@ -39,11 +39,12 @@ import {
|
||||
ExcalidrawTextElement,
|
||||
FileId,
|
||||
FontFamilyValues,
|
||||
NonDeletedSceneElementsMap,
|
||||
TextAlign,
|
||||
VerticalAlign,
|
||||
} from "../element/types";
|
||||
import { MarkOptional } from "../utility-types";
|
||||
import { arrayToMap, assertNever, cloneJSON, getFontString } from "../utils";
|
||||
import { assertNever, cloneJSON, getFontString, toBrandedType } from "../utils";
|
||||
import { getSizeFromPoints } from "../points";
|
||||
import { randomId } from "../random";
|
||||
|
||||
@ -231,7 +232,7 @@ const bindLinearElementToElement = (
|
||||
start: ValidLinearElement["start"],
|
||||
end: ValidLinearElement["end"],
|
||||
elementStore: ElementStore,
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): {
|
||||
linearElement: ExcalidrawLinearElement;
|
||||
startBoundElement?: ExcalidrawElement;
|
||||
@ -460,6 +461,10 @@ class ElementStore {
|
||||
return Array.from(this.excalidrawElements.values());
|
||||
};
|
||||
|
||||
getElementsMap = () => {
|
||||
return toBrandedType<NonDeletedSceneElementsMap>(this.excalidrawElements);
|
||||
};
|
||||
|
||||
getElement = (id: string) => {
|
||||
return this.excalidrawElements.get(id);
|
||||
};
|
||||
@ -615,7 +620,7 @@ export const convertToExcalidrawElements = (
|
||||
}
|
||||
}
|
||||
|
||||
const elementsMap = arrayToMap(elementStore.getElements());
|
||||
const elementsMap = elementStore.getElementsMap();
|
||||
// Add labels and arrow bindings
|
||||
for (const [id, element] of elementsWithIds) {
|
||||
const excalidrawElement = elementStore.getElement(id)!;
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
PointBinding,
|
||||
ExcalidrawElement,
|
||||
ElementsMap,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "./types";
|
||||
import { getElementAtPosition } from "../scene";
|
||||
import { AppState } from "../types";
|
||||
@ -67,7 +68,7 @@ export const bindOrUnbindLinearElement = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||
endBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): void => {
|
||||
const boundToElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
||||
const unboundFromElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
||||
@ -115,7 +116,7 @@ const bindOrUnbindLinearElementEdge = (
|
||||
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||
// Is mutated
|
||||
unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): void => {
|
||||
if (bindableElement !== "keep") {
|
||||
if (bindableElement != null) {
|
||||
@ -151,7 +152,8 @@ const bindOrUnbindLinearElementEdge = (
|
||||
|
||||
export const bindOrUnbindSelectedElements = (
|
||||
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||
elementsMap: ElementsMap,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): void => {
|
||||
selectedElements.forEach((selectedElement) => {
|
||||
if (isBindingElement(selectedElement)) {
|
||||
@ -160,11 +162,13 @@ export const bindOrUnbindSelectedElements = (
|
||||
getElligibleElementForBindingElement(
|
||||
selectedElement,
|
||||
"start",
|
||||
elements,
|
||||
elementsMap,
|
||||
),
|
||||
getElligibleElementForBindingElement(
|
||||
selectedElement,
|
||||
"end",
|
||||
elements,
|
||||
elementsMap,
|
||||
),
|
||||
elementsMap,
|
||||
@ -177,10 +181,12 @@ export const bindOrUnbindSelectedElements = (
|
||||
|
||||
const maybeBindBindableElement = (
|
||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): void => {
|
||||
getElligibleElementsForBindableElementAndWhere(bindableElement).forEach(
|
||||
([linearElement, where]) =>
|
||||
getElligibleElementsForBindableElementAndWhere(
|
||||
bindableElement,
|
||||
elementsMap,
|
||||
).forEach(([linearElement, where]) =>
|
||||
bindOrUnbindLinearElement(
|
||||
linearElement,
|
||||
where === "end" ? "keep" : bindableElement,
|
||||
@ -195,7 +201,7 @@ export const maybeBindLinearElement = (
|
||||
appState: AppState,
|
||||
scene: Scene,
|
||||
pointerCoords: { x: number; y: number },
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): void => {
|
||||
if (appState.startBoundElement != null) {
|
||||
bindLinearElement(
|
||||
@ -205,7 +211,11 @@ export const maybeBindLinearElement = (
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
const hoveredElement = getHoveredElementForBinding(pointerCoords, scene);
|
||||
const hoveredElement = getHoveredElementForBinding(
|
||||
pointerCoords,
|
||||
scene.getNonDeletedElements(),
|
||||
elementsMap,
|
||||
);
|
||||
if (
|
||||
hoveredElement != null &&
|
||||
!isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
|
||||
@ -222,7 +232,7 @@ export const bindLinearElement = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
hoveredElement: ExcalidrawBindableElement,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): void => {
|
||||
mutateElement(linearElement, {
|
||||
[startOrEnd === "start" ? "startBinding" : "endBinding"]: {
|
||||
@ -274,7 +284,7 @@ export const isLinearElementSimpleAndAlreadyBound = (
|
||||
|
||||
export const unbindLinearElements = (
|
||||
elements: NonDeleted<ExcalidrawElement>[],
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): void => {
|
||||
elements.forEach((element) => {
|
||||
if (isBindingElement(element)) {
|
||||
@ -301,17 +311,14 @@ export const getHoveredElementForBinding = (
|
||||
x: number;
|
||||
y: number;
|
||||
},
|
||||
scene: Scene,
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): NonDeleted<ExcalidrawBindableElement> | null => {
|
||||
const hoveredElement = getElementAtPosition(
|
||||
scene.getNonDeletedElements(),
|
||||
elements,
|
||||
(element) =>
|
||||
isBindableElement(element, false) &&
|
||||
bindingBorderTest(
|
||||
element,
|
||||
pointerCoords,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
bindingBorderTest(element, pointerCoords, elementsMap),
|
||||
);
|
||||
return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
|
||||
};
|
||||
@ -320,7 +327,7 @@ const calculateFocusAndGap = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
hoveredElement: ExcalidrawBindableElement,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): { focus: number; gap: number } => {
|
||||
const direction = startOrEnd === "start" ? -1 : 1;
|
||||
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
|
||||
@ -539,33 +546,47 @@ const maybeCalculateNewGapWhenScaling = (
|
||||
|
||||
// TODO: this is a bottleneck, optimise
|
||||
export const getEligibleElementsForBinding = (
|
||||
elements: NonDeleted<ExcalidrawElement>[],
|
||||
elementsMap: ElementsMap,
|
||||
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||
elements: readonly ExcalidrawElement[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): SuggestedBinding[] => {
|
||||
const includedElementIds = new Set(elements.map(({ id }) => id));
|
||||
return elements.flatMap((element) =>
|
||||
isBindingElement(element, false)
|
||||
const includedElementIds = new Set(selectedElements.map(({ id }) => id));
|
||||
return selectedElements.flatMap((selectedElement) =>
|
||||
isBindingElement(selectedElement, false)
|
||||
? (getElligibleElementsForBindingElement(
|
||||
element as NonDeleted<ExcalidrawLinearElement>,
|
||||
selectedElement as NonDeleted<ExcalidrawLinearElement>,
|
||||
elements,
|
||||
elementsMap,
|
||||
).filter(
|
||||
(element) => !includedElementIds.has(element.id),
|
||||
) as SuggestedBinding[])
|
||||
: isBindableElement(element, false)
|
||||
? getElligibleElementsForBindableElementAndWhere(element).filter(
|
||||
(binding) => !includedElementIds.has(binding[0].id),
|
||||
)
|
||||
: isBindableElement(selectedElement, false)
|
||||
? getElligibleElementsForBindableElementAndWhere(
|
||||
selectedElement,
|
||||
elementsMap,
|
||||
).filter((binding) => !includedElementIds.has(binding[0].id))
|
||||
: [],
|
||||
);
|
||||
};
|
||||
|
||||
const getElligibleElementsForBindingElement = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): NonDeleted<ExcalidrawBindableElement>[] => {
|
||||
return [
|
||||
getElligibleElementForBindingElement(linearElement, "start", elementsMap),
|
||||
getElligibleElementForBindingElement(linearElement, "end", elementsMap),
|
||||
getElligibleElementForBindingElement(
|
||||
linearElement,
|
||||
"start",
|
||||
elements,
|
||||
elementsMap,
|
||||
),
|
||||
getElligibleElementForBindingElement(
|
||||
linearElement,
|
||||
"end",
|
||||
elements,
|
||||
elementsMap,
|
||||
),
|
||||
].filter(
|
||||
(element): element is NonDeleted<ExcalidrawBindableElement> =>
|
||||
element != null,
|
||||
@ -575,18 +596,20 @@ const getElligibleElementsForBindingElement = (
|
||||
const getElligibleElementForBindingElement = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): NonDeleted<ExcalidrawBindableElement> | null => {
|
||||
return getHoveredElementForBinding(
|
||||
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
|
||||
Scene.getScene(linearElement)!,
|
||||
elements,
|
||||
elementsMap,
|
||||
);
|
||||
};
|
||||
|
||||
const getLinearElementEdgeCoors = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): { x: number; y: number } => {
|
||||
const index = startOrEnd === "start" ? 0 : -1;
|
||||
return tupleToCoors(
|
||||
@ -600,6 +623,7 @@ const getLinearElementEdgeCoors = (
|
||||
|
||||
const getElligibleElementsForBindableElementAndWhere = (
|
||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): SuggestedPointBinding[] => {
|
||||
const scene = Scene.getScene(bindableElement)!;
|
||||
return scene
|
||||
@ -612,13 +636,13 @@ const getElligibleElementsForBindableElementAndWhere = (
|
||||
element,
|
||||
"start",
|
||||
bindableElement,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
elementsMap,
|
||||
);
|
||||
const canBindEnd = isLinearElementEligibleForNewBindingByBindable(
|
||||
element,
|
||||
"end",
|
||||
bindableElement,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
elementsMap,
|
||||
);
|
||||
if (!canBindStart && !canBindEnd) {
|
||||
return null;
|
||||
@ -636,7 +660,7 @@ const isLinearElementEligibleForNewBindingByBindable = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startOrEnd: "start" | "end",
|
||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): boolean => {
|
||||
const existingBinding =
|
||||
linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"];
|
||||
|
@ -6,6 +6,8 @@ import {
|
||||
ExcalidrawBindableElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
ElementsMap,
|
||||
NonDeletedExcalidrawElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "./types";
|
||||
import {
|
||||
distance2d,
|
||||
@ -36,7 +38,6 @@ import {
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import History from "../history";
|
||||
|
||||
import Scene from "../scene/Scene";
|
||||
import {
|
||||
bindOrUnbindLinearElement,
|
||||
getHoveredElementForBinding,
|
||||
@ -86,11 +87,10 @@ export class LinearElementEditor {
|
||||
public readonly hoverPointIndex: number;
|
||||
public readonly segmentMidPointHoveredCoords: Point | null;
|
||||
|
||||
constructor(element: NonDeleted<ExcalidrawLinearElement>, scene: Scene) {
|
||||
constructor(element: NonDeleted<ExcalidrawLinearElement>) {
|
||||
this.elementId = element.id as string & {
|
||||
_brand: "excalidrawLinearElementId";
|
||||
};
|
||||
Scene.mapElementToScene(this.elementId, scene);
|
||||
LinearElementEditor.normalizePoints(element);
|
||||
|
||||
this.selectedPointsIndices = null;
|
||||
@ -123,8 +123,11 @@ export class LinearElementEditor {
|
||||
* @param id the `elementId` from the instance of this class (so that we can
|
||||
* statically guarantee this method returns an ExcalidrawLinearElement)
|
||||
*/
|
||||
static getElement(id: InstanceType<typeof LinearElementEditor>["elementId"]) {
|
||||
const element = Scene.getScene(id)?.getNonDeletedElement(id);
|
||||
static getElement(
|
||||
id: InstanceType<typeof LinearElementEditor>["elementId"],
|
||||
elementsMap: ElementsMap,
|
||||
) {
|
||||
const element = elementsMap.get(id);
|
||||
if (element) {
|
||||
return element as NonDeleted<ExcalidrawLinearElement>;
|
||||
}
|
||||
@ -135,7 +138,7 @@ export class LinearElementEditor {
|
||||
event: PointerEvent,
|
||||
appState: AppState,
|
||||
setState: React.Component<any, AppState>["setState"],
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
) {
|
||||
if (
|
||||
!appState.editingLinearElement ||
|
||||
@ -146,7 +149,7 @@ export class LinearElementEditor {
|
||||
const { editingLinearElement } = appState;
|
||||
const { selectedPointsIndices, elementId } = editingLinearElement;
|
||||
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
@ -197,13 +200,13 @@ export class LinearElementEditor {
|
||||
pointSceneCoords: { x: number; y: number }[],
|
||||
) => void,
|
||||
linearElementEditor: LinearElementEditor,
|
||||
elementsMap: ElementsMap,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): boolean {
|
||||
if (!linearElementEditor) {
|
||||
return false;
|
||||
}
|
||||
const { selectedPointsIndices, elementId } = linearElementEditor;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
@ -331,11 +334,12 @@ export class LinearElementEditor {
|
||||
event: PointerEvent,
|
||||
editingLinearElement: LinearElementEditor,
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): LinearElementEditor {
|
||||
const { elementId, selectedPointsIndices, isDragging, pointerDownState } =
|
||||
editingLinearElement;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
if (!element) {
|
||||
return editingLinearElement;
|
||||
}
|
||||
@ -376,7 +380,8 @@ export class LinearElementEditor {
|
||||
elementsMap,
|
||||
),
|
||||
),
|
||||
Scene.getScene(element)!,
|
||||
elements,
|
||||
elementsMap,
|
||||
)
|
||||
: null;
|
||||
|
||||
@ -490,7 +495,7 @@ export class LinearElementEditor {
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
const { elementId } = linearElementEditor;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
@ -614,6 +619,7 @@ export class LinearElementEditor {
|
||||
) {
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
elementsMap,
|
||||
);
|
||||
if (!element) {
|
||||
return -1;
|
||||
@ -639,7 +645,8 @@ export class LinearElementEditor {
|
||||
history: History,
|
||||
scenePointer: { x: number; y: number },
|
||||
linearElementEditor: LinearElementEditor,
|
||||
elementsMap: ElementsMap,
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
): {
|
||||
didAddPoint: boolean;
|
||||
hitElement: NonDeleted<ExcalidrawElement> | null;
|
||||
@ -656,7 +663,7 @@ export class LinearElementEditor {
|
||||
}
|
||||
|
||||
const { elementId } = linearElementEditor;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
|
||||
if (!element) {
|
||||
return ret;
|
||||
@ -709,7 +716,8 @@ export class LinearElementEditor {
|
||||
lastUncommittedPoint: null,
|
||||
endBindingElement: getHoveredElementForBinding(
|
||||
scenePointer,
|
||||
Scene.getScene(element)!,
|
||||
elements,
|
||||
elementsMap,
|
||||
),
|
||||
};
|
||||
|
||||
@ -813,7 +821,7 @@ export class LinearElementEditor {
|
||||
return null;
|
||||
}
|
||||
const { elementId, lastUncommittedPoint } = appState.editingLinearElement;
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
if (!element) {
|
||||
return appState.editingLinearElement;
|
||||
}
|
||||
@ -1020,14 +1028,14 @@ export class LinearElementEditor {
|
||||
mutateElement(element, LinearElementEditor.getNormalizedPoints(element));
|
||||
}
|
||||
|
||||
static duplicateSelectedPoints(appState: AppState) {
|
||||
static duplicateSelectedPoints(appState: AppState, elementsMap: ElementsMap) {
|
||||
if (!appState.editingLinearElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { selectedPointsIndices, elementId } = appState.editingLinearElement;
|
||||
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
|
||||
if (!element || selectedPointsIndices === null) {
|
||||
return false;
|
||||
@ -1189,9 +1197,11 @@ export class LinearElementEditor {
|
||||
linearElementEditor: LinearElementEditor,
|
||||
pointerCoords: PointerCoords,
|
||||
appState: AppState,
|
||||
elementsMap: ElementsMap,
|
||||
) {
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
elementsMap,
|
||||
);
|
||||
|
||||
if (!element) {
|
||||
@ -1234,6 +1244,7 @@ export class LinearElementEditor {
|
||||
) {
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
elementsMap,
|
||||
);
|
||||
if (!element) {
|
||||
return;
|
||||
|
@ -354,7 +354,8 @@ const renderLinearElementPointHighlight = (
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const element = LinearElementEditor.getElement(elementId);
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { render, fireEvent } from "./test-utils";
|
||||
import { Excalidraw } from "../index";
|
||||
@ -13,7 +12,6 @@ import {
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { KEYS } from "../keys";
|
||||
import { vi } from "vitest";
|
||||
import { arrayToMap } from "../utils";
|
||||
|
||||
// Unmount ReactDOM from root
|
||||
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||
@ -76,7 +74,7 @@ describe("move element", () => {
|
||||
const rectA = UI.createElement("rectangle", { size: 100 });
|
||||
const rectB = UI.createElement("rectangle", { x: 200, y: 0, size: 300 });
|
||||
const line = UI.createElement("line", { x: 110, y: 50, size: 80 });
|
||||
const elementsMap = arrayToMap(h.elements);
|
||||
const elementsMap = h.app.scene.getNonDeletedElementsMap();
|
||||
// bind line to two rectangles
|
||||
bindOrUnbindLinearElement(
|
||||
line.get() as NonDeleted<ExcalidrawLinearElement>,
|
||||
|
Loading…
Reference in New Issue
Block a user