diff --git a/packages/excalidraw/actions/actionClipboard.tsx b/packages/excalidraw/actions/actionClipboard.tsx index b2457341d..b9634886b 100644 --- a/packages/excalidraw/actions/actionClipboard.tsx +++ b/packages/excalidraw/actions/actionClipboard.tsx @@ -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, diff --git a/packages/excalidraw/actions/actionDeleteSelected.tsx b/packages/excalidraw/actions/actionDeleteSelected.tsx index de25ed898..65f751d93 100644 --- a/packages/excalidraw/actions/actionDeleteSelected.tsx +++ b/packages/excalidraw/actions/actionDeleteSelected.tsx @@ -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; } diff --git a/packages/excalidraw/actions/actionDuplicateSelection.tsx b/packages/excalidraw/actions/actionDuplicateSelection.tsx index 7126f549e..86391f9e3 100644 --- a/packages/excalidraw/actions/actionDuplicateSelection.tsx +++ b/packages/excalidraw/actions/actionDuplicateSelection.tsx @@ -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; diff --git a/packages/excalidraw/actions/actionFinalize.tsx b/packages/excalidraw/actions/actionFinalize.tsx index 623876d58..9dad4ef91 100644 --- a/packages/excalidraw/actions/actionFinalize.tsx +++ b/packages/excalidraw/actions/actionFinalize.tsx @@ -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, }, diff --git a/packages/excalidraw/actions/actionFlip.ts b/packages/excalidraw/actions/actionFlip.ts index 70fbe026d..ee4a6f0f5 100644 --- a/packages/excalidraw/actions/actionFlip.ts +++ b/packages/excalidraw/actions/actionFlip.ts @@ -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, 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[], - 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; }; diff --git a/packages/excalidraw/actions/actionLinearEditor.ts b/packages/excalidraw/actions/actionLinearEditor.ts index 83611b027..5f1e672cb 100644 --- a/packages/excalidraw/actions/actionLinearEditor.ts +++ b/packages/excalidraw/actions/actionLinearEditor.ts @@ -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, diff --git a/packages/excalidraw/actions/actionSelectAll.ts b/packages/excalidraw/actions/actionSelectAll.ts index 49f5072ce..398416f0c 100644 --- a/packages/excalidraw/actions/actionSelectAll.ts +++ b/packages/excalidraw/actions/actionSelectAll.ts @@ -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, diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index b4410ab2b..1f21de9cf 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -2603,7 +2603,7 @@ class App extends React.Component { 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 { this.setState({ editingLinearElement: new LinearElementEditor( selectedElement, - this.scene, ), }); } @@ -4013,7 +4012,11 @@ class App extends React.Component { 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 { ) { 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 { 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 { 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 { 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 { }); 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 { return true; } } + const elementsMap = this.scene.getNonDeletedElementsMap(); if (this.state.selectedLinearElement) { const linearElementEditor = @@ -7007,6 +7013,7 @@ class App extends React.Component { this.state.selectedLinearElement, pointerCoords, this.state, + elementsMap, ) ) { const ret = LinearElementEditor.addMidpoint( @@ -7014,7 +7021,7 @@ class App extends React.Component { pointerCoords, this.state, !event[KEYS.CTRL_OR_CMD], - this.scene.getNonDeletedElementsMap(), + elementsMap, ); if (!ret) { return; @@ -7435,10 +7442,7 @@ class App extends React.Component { 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 { childEvent, this.state.editingLinearElement, this.state, + this.scene.getNonDeletedElements(), elementsMap, ); if (editingLinearElement !== this.state.editingLinearElement) { @@ -7563,6 +7568,7 @@ class App extends React.Component { childEvent, this.state.selectedLinearElement, this.state, + this.scene.getNonDeletedElements(), elementsMap, ); @@ -7732,10 +7738,7 @@ class App extends React.Component { }, prevState, ), - selectedLinearElement: new LinearElementEditor( - draggingElement, - this.scene, - ), + selectedLinearElement: new LinearElementEditor(draggingElement), })); } else { this.setState((prevState) => ({ @@ -7975,10 +7978,7 @@ class App extends React.Component { // 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 { 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 { // 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 { } 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 { }): 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 { (acc: NonDeleted[], 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 { } const suggestedBindings = getEligibleElementsForBinding( selectedElements, + this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap(), ); this.setState({ suggestedBindings }); @@ -9037,7 +9041,7 @@ class App extends React.Component { this, ), selectedLinearElement: isLinearElement(element) - ? new LinearElementEditor(element, this.scene) + ? new LinearElementEditor(element) : null, } : this.state), diff --git a/packages/excalidraw/data/transform.ts b/packages/excalidraw/data/transform.ts index 8d5b63a19..936272f07 100644 --- a/packages/excalidraw/data/transform.ts +++ b/packages/excalidraw/data/transform.ts @@ -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(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)!; diff --git a/packages/excalidraw/element/binding.ts b/packages/excalidraw/element/binding.ts index be766e33f..8d3959bc7 100644 --- a/packages/excalidraw/element/binding.ts +++ b/packages/excalidraw/element/binding.ts @@ -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, startBindingElement: ExcalidrawBindableElement | null | "keep", endBindingElement: ExcalidrawBindableElement | null | "keep", - elementsMap: ElementsMap, + elementsMap: NonDeletedSceneElementsMap, ): void => { const boundToElementIds: Set = new Set(); const unboundFromElementIds: Set = new Set(); @@ -115,7 +116,7 @@ const bindOrUnbindLinearElementEdge = ( boundToElementIds: Set, // Is mutated unboundFromElementIds: Set, - elementsMap: ElementsMap, + elementsMap: NonDeletedSceneElementsMap, ): void => { if (bindableElement !== "keep") { if (bindableElement != null) { @@ -151,7 +152,8 @@ const bindOrUnbindLinearElementEdge = ( export const bindOrUnbindSelectedElements = ( selectedElements: NonDeleted[], - 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, - 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, 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[], - 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 | null => { const hoveredElement = getElementAtPosition( - scene.getNonDeletedElements(), + elements, (element) => isBindableElement(element, false) && - bindingBorderTest( - element, - pointerCoords, - scene.getNonDeletedElementsMap(), - ), + bindingBorderTest(element, pointerCoords, elementsMap), ); return hoveredElement as NonDeleted | null; }; @@ -320,7 +327,7 @@ const calculateFocusAndGap = ( linearElement: NonDeleted, 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[], - elementsMap: ElementsMap, + selectedElements: NonDeleted[], + 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, + selectedElement as NonDeleted, + 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, - elementsMap: ElementsMap, + elements: readonly ExcalidrawElement[], + elementsMap: NonDeletedSceneElementsMap, ): NonDeleted[] => { return [ - getElligibleElementForBindingElement(linearElement, "start", elementsMap), - getElligibleElementForBindingElement(linearElement, "end", elementsMap), + getElligibleElementForBindingElement( + linearElement, + "start", + elements, + elementsMap, + ), + getElligibleElementForBindingElement( + linearElement, + "end", + elements, + elementsMap, + ), ].filter( (element): element is NonDeleted => element != null, @@ -575,18 +596,20 @@ const getElligibleElementsForBindingElement = ( const getElligibleElementForBindingElement = ( linearElement: NonDeleted, startOrEnd: "start" | "end", - elementsMap: ElementsMap, + elements: readonly ExcalidrawElement[], + elementsMap: NonDeletedSceneElementsMap, ): NonDeleted | null => { return getHoveredElementForBinding( getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap), - Scene.getScene(linearElement)!, + elements, + elementsMap, ); }; const getLinearElementEdgeCoors = ( linearElement: NonDeleted, 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, + 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, startOrEnd: "start" | "end", bindableElement: NonDeleted, - elementsMap: ElementsMap, + elementsMap: NonDeletedSceneElementsMap, ): boolean => { const existingBinding = linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"]; diff --git a/packages/excalidraw/element/linearElementEditor.ts b/packages/excalidraw/element/linearElementEditor.ts index 85483b3d7..d493f1fbd 100644 --- a/packages/excalidraw/element/linearElementEditor.ts +++ b/packages/excalidraw/element/linearElementEditor.ts @@ -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, scene: Scene) { + constructor(element: NonDeleted) { 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["elementId"]) { - const element = Scene.getScene(id)?.getNonDeletedElement(id); + static getElement( + id: InstanceType["elementId"], + elementsMap: ElementsMap, + ) { + const element = elementsMap.get(id); if (element) { return element as NonDeleted; } @@ -135,7 +138,7 @@ export class LinearElementEditor { event: PointerEvent, appState: AppState, setState: React.Component["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 | 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; diff --git a/packages/excalidraw/renderer/renderScene.ts b/packages/excalidraw/renderer/renderScene.ts index d80540fd0..0e9fce164 100644 --- a/packages/excalidraw/renderer/renderScene.ts +++ b/packages/excalidraw/renderer/renderScene.ts @@ -354,7 +354,8 @@ const renderLinearElementPointHighlight = ( ) { return; } - const element = LinearElementEditor.getElement(elementId); + const element = LinearElementEditor.getElement(elementId, elementsMap); + if (!element) { return; } diff --git a/packages/excalidraw/tests/move.test.tsx b/packages/excalidraw/tests/move.test.tsx index 625175700..06086f119 100644 --- a/packages/excalidraw/tests/move.test.tsx +++ b/packages/excalidraw/tests/move.test.tsx @@ -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,