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:
Aakansha Doshi 2024-02-19 11:49:01 +05:30 committed by GitHub
parent 47f87f4ecb
commit 9013c84524
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 172 additions and 123 deletions

View File

@ -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,

View File

@ -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;
}

View File

@ -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;

View File

@ -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,
},

View File

@ -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;
};

View File

@ -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,

View File

@ -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,

View File

@ -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,12 +8229,16 @@ class App extends React.Component<AppProps, AppState> {
}
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
(isBindingEnabled(this.state)
? bindOrUnbindSelectedElements
: unbindLinearElements)(
this.scene.getSelectedElements(this.state),
elementsMap,
);
isBindingEnabled(this.state)
? bindOrUnbindSelectedElements(
this.scene.getSelectedElements(this.state),
this.scene.getNonDeletedElements(),
elementsMap,
)
: unbindLinearElements(
this.scene.getSelectedElements(this.state),
elementsMap,
);
}
if (activeTool.type === "laser") {
@ -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),

View File

@ -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)!;

View File

@ -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,16 +181,18 @@ export const bindOrUnbindSelectedElements = (
const maybeBindBindableElement = (
bindableElement: NonDeleted<ExcalidrawBindableElement>,
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): void => {
getElligibleElementsForBindableElementAndWhere(bindableElement).forEach(
([linearElement, where]) =>
bindOrUnbindLinearElement(
linearElement,
where === "end" ? "keep" : bindableElement,
where === "start" ? "keep" : bindableElement,
elementsMap,
),
getElligibleElementsForBindableElementAndWhere(
bindableElement,
elementsMap,
).forEach(([linearElement, where]) =>
bindOrUnbindLinearElement(
linearElement,
where === "end" ? "keep" : bindableElement,
where === "start" ? "keep" : bindableElement,
elementsMap,
),
);
};
@ -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"];

View File

@ -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;

View File

@ -354,7 +354,8 @@ const renderLinearElementPointHighlight = (
) {
return;
}
const element = LinearElementEditor.getElement(elementId);
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element) {
return;
}

View File

@ -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>,