From acfa33650edcca2f57cea0fd2a49ba3f1c3fcc8a Mon Sep 17 00:00:00 2001 From: Marcel Mraz Date: Tue, 15 Apr 2025 23:24:59 +0200 Subject: [PATCH] Remove mutators, pass scene everywhere, make temp scenes for special cases --- .../scene => element/src}/Scene.ts | 10 +++- packages/element/src/align.ts | 6 +-- packages/element/src/binding.ts | 51 +++++------------- packages/element/src/dragElements.ts | 6 +-- packages/element/src/flowchart.ts | 22 ++------ packages/element/src/frame.ts | 4 +- packages/element/src/linearElementEditor.ts | 42 +++++++-------- packages/element/src/resizeElements.ts | 14 ++--- packages/element/src/textElement.ts | 20 +++---- packages/element/src/zindex.ts | 4 +- packages/element/tests/elbowArrow.test.tsx | 13 ++--- .../excalidraw/actions/actionBoundText.tsx | 15 ++---- .../actions/actionDeleteSelected.tsx | 2 +- .../excalidraw/actions/actionProperties.tsx | 52 +++++-------------- packages/excalidraw/actions/actionStyles.ts | 8 +-- packages/excalidraw/change.ts | 26 +++++----- packages/excalidraw/components/App.tsx | 29 ++++------- .../excalidraw/components/Stats/Angle.tsx | 3 +- .../components/Stats/CanvasGrid.tsx | 3 +- .../excalidraw/components/Stats/Dimension.tsx | 3 +- .../excalidraw/components/Stats/DragInput.tsx | 3 +- .../excalidraw/components/Stats/FontSize.tsx | 6 +-- .../components/Stats/MultiAngle.tsx | 3 +- .../components/Stats/MultiDimension.tsx | 25 ++++----- .../components/Stats/MultiFontSize.tsx | 9 ++-- .../components/Stats/MultiPosition.tsx | 3 +- .../excalidraw/components/Stats/Position.tsx | 3 +- packages/excalidraw/components/Stats/utils.ts | 9 ++-- packages/excalidraw/data/transform.ts | 32 +++++------- packages/excalidraw/fonts/Fonts.ts | 4 +- packages/excalidraw/scene/Renderer.ts | 3 +- .../tests/linearElementEditor.test.tsx | 2 +- packages/excalidraw/wysiwyg/textWysiwyg.tsx | 8 +-- 33 files changed, 177 insertions(+), 266 deletions(-) rename packages/{excalidraw/scene => element/src}/Scene.ts (98%) diff --git a/packages/excalidraw/scene/Scene.ts b/packages/element/src/Scene.ts similarity index 98% rename from packages/excalidraw/scene/Scene.ts rename to packages/element/src/Scene.ts index e3b84b6b9..51a86ded9 100644 --- a/packages/excalidraw/scene/Scene.ts +++ b/packages/element/src/Scene.ts @@ -34,6 +34,7 @@ import type { NonDeletedSceneElementsMap, OrderedExcalidrawElement, Ordered, + ElementsMap, } from "@excalidraw/element/types"; import type { @@ -42,7 +43,7 @@ import type { SameType, } from "@excalidraw/common/utility-types"; -import type { AppState } from "../types"; +import type { AppState } from "../../excalidraw/types"; type SceneStateCallback = () => void; type SceneStateCallbackRemover = () => void; @@ -166,6 +167,12 @@ class Scene { return this.frames; } + constructor(elementsMap: ElementsMap | null = null) { + if (elementsMap) { + this.replaceAllElements(elementsMap); + } + } + getSelectedElements(opts: { // NOTE can be ommitted by making Scene constructor require App instance selectedElementIds: AppState["selectedElementIds"]; @@ -419,7 +426,6 @@ class Scene { // TODO_SCENE: should be accessed as app.scene through the API // TODO_SCENE: inform mutation false is the new default, meaning all mutateElement with nothing should likely use scene instead - // TODO_SCENE: think one more time about moving the scene inside element (probably we will end up with it either way) // Mutate an element with passed updates and trigger the component to update. Make sure you // are calling it either from a React event handler or within unstable_batchedUpdates(). mutate>( diff --git a/packages/element/src/align.ts b/packages/element/src/align.ts index eeb3b48f2..9d7b254fc 100644 --- a/packages/element/src/align.ts +++ b/packages/element/src/align.ts @@ -1,9 +1,9 @@ -import type Scene from "@excalidraw/excalidraw/scene/Scene"; - import { updateBoundElements } from "./binding"; import { getCommonBoundingBox } from "./bounds"; import { getMaximumGroups } from "./groups"; +import type Scene from "./Scene"; + import type { BoundingBox } from "./bounds"; import type { ExcalidrawElement } from "./types"; @@ -38,7 +38,7 @@ export const alignElements = ( }); // update bound elements - updateBoundElements(element, elementsMap, { + updateBoundElements(element, scene, { simultaneouslyUpdated: group, }); return updatedEle; diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 1982a449b..e1b5be6cc 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -30,8 +30,6 @@ import { isPointOnShape } from "@excalidraw/utils/collision"; import type { LocalPoint, Radians } from "@excalidraw/math"; -import type Scene from "@excalidraw/excalidraw/scene/Scene"; - import type { AppState } from "@excalidraw/excalidraw/types"; import type { Mutable } from "@excalidraw/common/utility-types"; @@ -68,6 +66,8 @@ import { import { aabbForElement, getElementShape, pointInsideBounds } from "./shapes"; import { updateElbowArrowPoints } from "./elbowArrow"; +import type Scene from "./Scene"; + import type { Bounds } from "./bounds"; import type { ElementUpdate } from "./mutateElement"; import type { @@ -177,8 +177,6 @@ const bindOrUnbindLinearElementEdge = ( unboundFromElementIds: Set, scene: Scene, ): void => { - const elementsMap = scene.getNonDeletedElementsMap(); - // "keep" is for method chaining convenience, a "no-op", so just bail out if (bindableElement === "keep") { return; @@ -209,23 +207,11 @@ const bindOrUnbindLinearElementEdge = ( : startOrEnd === "start" || otherEdgeBindableElement.id !== bindableElement.id) ) { - bindLinearElement( - linearElement, - bindableElement, - startOrEnd, - elementsMap, - (...args) => scene.mutate(...args), - ); + bindLinearElement(linearElement, bindableElement, startOrEnd, scene); boundToElementIds.add(bindableElement.id); } } else { - bindLinearElement( - linearElement, - bindableElement, - startOrEnd, - elementsMap, - (...args) => scene.mutate(...args), - ); + bindLinearElement(linearElement, bindableElement, startOrEnd, scene); boundToElementIds.add(bindableElement.id); } }; @@ -443,8 +429,7 @@ export const maybeBindLinearElement = ( linearElement, appState.startBoundElement, "start", - elementsMap, - (...args) => scene.mutate(...args), + scene, ); } @@ -465,13 +450,7 @@ export const maybeBindLinearElement = ( "end", ) ) { - bindLinearElement( - linearElement, - hoveredElement, - "end", - elementsMap, - (...args) => scene.mutate(...args), - ); + bindLinearElement(linearElement, hoveredElement, "end", scene); } } }; @@ -500,11 +479,7 @@ export const bindLinearElement = ( linearElement: NonDeleted, hoveredElement: ExcalidrawBindableElement, startOrEnd: "start" | "end", - elementsMap: ElementsMap, - mutator: ( - element: ExcalidrawElement, - updates: ElementUpdate, - ) => ExcalidrawElement, + scene: Scene, ): void => { if (!isArrowElement(linearElement)) { return; @@ -517,7 +492,7 @@ export const bindLinearElement = ( linearElement, hoveredElement, startOrEnd, - elementsMap as NonDeletedSceneElementsMap, + scene.getNonDeletedElementsMap(), ), hoveredElement, ), @@ -534,13 +509,13 @@ export const bindLinearElement = ( }; } - mutator(linearElement, { + scene.mutate(linearElement, { [startOrEnd === "start" ? "startBinding" : "endBinding"]: binding, }); const boundElementsMap = arrayToMap(hoveredElement.boundElements || []); if (!boundElementsMap.has(linearElement.id)) { - mutator(hoveredElement, { + scene.mutate(hoveredElement, { boundElements: (hoveredElement.boundElements || []).concat({ id: linearElement.id, type: "arrow", @@ -757,7 +732,7 @@ const calculateFocusAndGap = ( // in explicitly. export const updateBoundElements = ( changedElement: NonDeletedExcalidrawElement, - elementsMap: ElementsMap, + scene: Scene, options?: { simultaneouslyUpdated?: readonly ExcalidrawElement[]; newSize?: { width: number; height: number }; @@ -773,6 +748,8 @@ export const updateBoundElements = ( return; } + const elementsMap = scene.getNonDeletedElementsMap(); + boundElementsVisitor(elementsMap, changedElement, (element) => { if (!isLinearElement(element) || element.isDeleted) { return; @@ -860,7 +837,7 @@ export const updateBoundElements = ( }> => update !== null, ); - LinearElementEditor.movePoints(element, elementsMap, updates, { + LinearElementEditor.movePoints(element, scene, updates, { ...(changedElement.id === element.startBinding?.elementId ? { startBinding: bindings.startBinding } : {}), diff --git a/packages/element/src/dragElements.ts b/packages/element/src/dragElements.ts index e200b375d..4bbb8dcd2 100644 --- a/packages/element/src/dragElements.ts +++ b/packages/element/src/dragElements.ts @@ -11,8 +11,6 @@ import type { PointerDownState, } from "@excalidraw/excalidraw/types"; -import type Scene from "@excalidraw/excalidraw/scene/Scene"; - import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types"; import { updateBoundElements } from "./binding"; @@ -28,6 +26,8 @@ import { isTextElement, } from "./typeChecks"; +import type Scene from "./Scene"; + import type { Bounds } from "./bounds"; import type { ExcalidrawElement } from "./types"; @@ -118,7 +118,7 @@ export const dragSelectedElements = ( adjustedOffset, ); } - updateBoundElements(element, scene.getNonDeletedElementsMap(), { + updateBoundElements(element, scene, { simultaneouslyUpdated: Array.from(elementsToUpdate), }); } diff --git a/packages/element/src/flowchart.ts b/packages/element/src/flowchart.ts index dca8fb77e..ada1b8e1b 100644 --- a/packages/element/src/flowchart.ts +++ b/packages/element/src/flowchart.ts @@ -7,8 +7,6 @@ import type { PendingExcalidrawElements, } from "@excalidraw/excalidraw/types"; -import type Scene from "@excalidraw/excalidraw/scene/Scene"; - import { bindLinearElement } from "./binding"; import { updateElbowArrowPoints } from "./elbowArrow"; import { @@ -41,6 +39,8 @@ import { type OrderedExcalidrawElement, } from "./types"; +import type Scene from "./Scene"; + type LinkDirection = "up" | "right" | "down" | "left"; const VERTICAL_OFFSET = 100; @@ -445,20 +445,8 @@ const createBindingArrow = ( const elementsMap = scene.getNonDeletedElementsMap(); - bindLinearElement( - bindingArrow, - startBindingElement, - "start", - elementsMap, - (...args) => scene.mutate(...args), - ); - bindLinearElement( - bindingArrow, - endBindingElement, - "end", - elementsMap, - (...args) => scene.mutate(...args), - ); + bindLinearElement(bindingArrow, startBindingElement, "start", scene); + bindLinearElement(bindingArrow, endBindingElement, "end", scene); const changedElements = new Map(); changedElements.set( @@ -474,7 +462,7 @@ const createBindingArrow = ( bindingArrow as OrderedExcalidrawElement, ); - LinearElementEditor.movePoints(bindingArrow, elementsMap, [ + LinearElementEditor.movePoints(bindingArrow, scene, [ { index: 1, point: bindingArrow.points[1], diff --git a/packages/element/src/frame.ts b/packages/element/src/frame.ts index 645681ac3..7431cbff8 100644 --- a/packages/element/src/frame.ts +++ b/packages/element/src/frame.ts @@ -3,8 +3,6 @@ import { isPointWithinBounds, pointFrom } from "@excalidraw/math"; import { doLineSegmentsIntersect } from "@excalidraw/utils/bbox"; import { elementsOverlappingBBox } from "@excalidraw/utils/withinBounds"; -import type { ExcalidrawElementsIncludingDeleted } from "@excalidraw/excalidraw/scene/Scene"; - import type { AppClassProperties, AppState, @@ -29,6 +27,8 @@ import { isTextElement, } from "./typeChecks"; +import type { ExcalidrawElementsIncludingDeleted } from "./Scene"; + import type { ElementsMap, ElementsMapOrArray, diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index ac4424c3c..d5ea102c1 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -20,8 +20,6 @@ import { tupleToCoors, } from "@excalidraw/common"; -import type Scene from "@excalidraw/excalidraw/scene/Scene"; - import type { Store } from "@excalidraw/excalidraw/store"; import type { Radians } from "@excalidraw/math"; @@ -69,6 +67,8 @@ import { import { getLockedLinearCursorAlignSize } from "./sizeHelpers"; +import type Scene from "./Scene"; + import type { Bounds } from "./bounds"; import type { NonDeleted, @@ -80,7 +80,6 @@ import type { ElementsMap, NonDeletedSceneElementsMap, FixedPointBinding, - SceneElementsMap, FixedSegment, ExcalidrawElbowArrowElement, } from "./types"; @@ -307,7 +306,7 @@ export class LinearElementEditor { event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(), ); - LinearElementEditor.movePoints(element, elementsMap, [ + LinearElementEditor.movePoints(element, scene, [ { index: selectedIndex, point: pointFrom( @@ -331,7 +330,7 @@ export class LinearElementEditor { LinearElementEditor.movePoints( element, - elementsMap, + scene, selectedPointsIndices.map((pointIndex) => { const newPointPosition: LocalPoint = pointIndex === lastClickedPoint @@ -452,7 +451,7 @@ export class LinearElementEditor { selectedPoint === element.points.length - 1 ) { if (isPathALoop(element.points, appState.zoom.value)) { - LinearElementEditor.movePoints(element, elementsMap, [ + LinearElementEditor.movePoints(element, scene, [ { index: selectedPoint, point: @@ -932,13 +931,13 @@ export class LinearElementEditor { scenePointerX: number, scenePointerY: number, app: AppClassProperties, - elementsMap: NonDeletedSceneElementsMap | SceneElementsMap, ): LinearElementEditor | null { const appState = app.state; if (!appState.editingLinearElement) { return null; } const { elementId, lastUncommittedPoint } = appState.editingLinearElement; + const elementsMap = app.scene.getNonDeletedElementsMap(); const element = LinearElementEditor.getElement(elementId, elementsMap); if (!element) { return appState.editingLinearElement; @@ -949,7 +948,7 @@ export class LinearElementEditor { if (!event.altKey) { if (lastPoint === lastUncommittedPoint) { - LinearElementEditor.deletePoints(element, elementsMap, [ + LinearElementEditor.deletePoints(element, app.scene, [ points.length - 1, ]); } @@ -989,16 +988,14 @@ export class LinearElementEditor { } if (lastPoint === lastUncommittedPoint) { - LinearElementEditor.movePoints(element, elementsMap, [ + LinearElementEditor.movePoints(element, app.scene, [ { index: element.points.length - 1, point: newPoint, }, ]); } else { - LinearElementEditor.addPoints(element, elementsMap, [ - { point: newPoint }, - ]); + LinearElementEditor.addPoints(element, app.scene, [{ point: newPoint }]); } return { ...appState.editingLinearElement, @@ -1168,7 +1165,6 @@ export class LinearElementEditor { element: NonDeleted, elementsMap: ElementsMap, ) { - // TODO_SCENE: we don't need to inform mutation here? mutateElementWith( element, elementsMap, @@ -1231,7 +1227,7 @@ export class LinearElementEditor { // potentially expanding the bounding box if (pointAddedToEnd) { const lastPoint = element.points[element.points.length - 1]; - LinearElementEditor.movePoints(element, elementsMap, [ + LinearElementEditor.movePoints(element, scene, [ { index: element.points.length - 1, point: pointFrom(lastPoint[0] + 30, lastPoint[1] + 30), @@ -1250,7 +1246,7 @@ export class LinearElementEditor { static deletePoints( element: NonDeleted, - elementsMap: ElementsMap, + scene: Scene, pointIndices: readonly number[], ) { let offsetX = 0; @@ -1283,7 +1279,7 @@ export class LinearElementEditor { LinearElementEditor._updatePoints( element, - elementsMap, + scene, nextPoints, offsetX, offsetY, @@ -1292,7 +1288,7 @@ export class LinearElementEditor { static addPoints( element: NonDeleted, - elementsMap: ElementsMap, + scene: Scene, targetPoints: { point: LocalPoint }[], ) { const offsetX = 0; @@ -1301,7 +1297,7 @@ export class LinearElementEditor { const nextPoints = [...element.points, ...targetPoints.map((x) => x.point)]; LinearElementEditor._updatePoints( element, - elementsMap, + scene, nextPoints, offsetX, offsetY, @@ -1310,7 +1306,7 @@ export class LinearElementEditor { static movePoints( element: NonDeleted, - elementsMap: ElementsMap, + scene: Scene, targetPoints: { index: number; point: LocalPoint; isDragging?: boolean }[], otherUpdates?: { startBinding?: PointBinding | null; @@ -1349,7 +1345,7 @@ export class LinearElementEditor { LinearElementEditor._updatePoints( element, - elementsMap, + scene, nextPoints, offsetX, offsetY, @@ -1462,7 +1458,7 @@ export class LinearElementEditor { private static _updatePoints( element: NonDeleted, - elementsMap: ElementsMap, + scene: Scene, nextPoints: readonly LocalPoint[], offsetX: number, offsetY: number, @@ -1499,7 +1495,7 @@ export class LinearElementEditor { updates.points = Array.from(nextPoints); - mutateElementWith(element, elementsMap, updates, { + scene.mutate(element, updates, { isDragging: options?.isDragging, }); } else { @@ -1516,7 +1512,7 @@ export class LinearElementEditor { pointFrom(dX, dY), element.angle, ); - mutateElement(element, { + scene.mutate(element, { ...otherUpdates, points: nextPoints, x: element.x + rotated[0], diff --git a/packages/element/src/resizeElements.ts b/packages/element/src/resizeElements.ts index 7e457fbe2..13cc0731f 100644 --- a/packages/element/src/resizeElements.ts +++ b/packages/element/src/resizeElements.ts @@ -17,8 +17,6 @@ import { import type { GlobalPoint } from "@excalidraw/math"; -import type Scene from "@excalidraw/excalidraw/scene/Scene"; - import type { PointerDownState } from "@excalidraw/excalidraw/types"; import type { Mutable } from "@excalidraw/common/utility-types"; @@ -60,6 +58,8 @@ import { import { isInGroup } from "./groups"; +import type Scene from "./Scene"; + import type { BoundingBox } from "./bounds"; import type { MaybeTransformHandleType, @@ -103,7 +103,7 @@ export const transformElements = ( pointerY, shouldRotateWithDiscreteAngle, ); - updateBoundElements(element, elementsMap); + updateBoundElements(element, scene); } } else if (isTextElement(element) && transformHandleType) { resizeSingleTextElement( @@ -115,7 +115,7 @@ export const transformElements = ( pointerX, pointerY, ); - updateBoundElements(element, elementsMap); + updateBoundElements(element, scene); return true; } else if (transformHandleType) { const elementId = selectedElements[0].id; @@ -554,7 +554,7 @@ const rotateMultipleElements = ( scene.mutate(element, updates); - updateBoundElements(element, elementsMap, { + updateBoundElements(element, scene, { simultaneouslyUpdated: elements, }); @@ -964,7 +964,7 @@ export const resizeSingleElement = ( const elementsMap = scene.getNonDeletedElementsMap(); - updateBoundElements(latestElement, elementsMap, { + updateBoundElements(latestElement, scene, { // TODO: confirm with MARK if this actually makes sense newSize: { width: nextWidth, height: nextHeight }, }); @@ -1525,7 +1525,7 @@ export const resizeMultipleElements = ( isDragging: true, }); - updateBoundElements(element, scene.getNonDeletedElementsMap(), { + updateBoundElements(element, scene, { simultaneouslyUpdated: elementsToUpdate, newSize: { width, height }, }); diff --git a/packages/element/src/textElement.ts b/packages/element/src/textElement.ts index 0514cb2c6..5cd914c96 100644 --- a/packages/element/src/textElement.ts +++ b/packages/element/src/textElement.ts @@ -27,7 +27,7 @@ import { isTextElement, } from "./typeChecks"; -import type { ElementUpdate } from "./mutateElement"; +import type Scene from "./Scene"; import type { MaybeTransformHandleType } from "./transformHandles"; import type { @@ -43,12 +43,10 @@ import type { export const redrawTextBoundingBox = ( textElement: ExcalidrawTextElement, container: ExcalidrawElement | null, - elementsMap: ElementsMap, - mutator: ( - element: ExcalidrawElement, - updates: ElementUpdate, - ) => ExcalidrawElement, + scene: Scene, ) => { + const elementsMap = scene.getNonDeletedElementsMap(); + let maxWidth = undefined; const boundTextUpdates = { x: textElement.x, @@ -96,30 +94,34 @@ export const redrawTextBoundingBox = ( metrics.height, container.type, ); - mutator(container, { height: nextHeight }); + scene.mutate(container, { height: nextHeight }); updateOriginalContainerCache(container.id, nextHeight); } + if (metrics.width > maxContainerWidth) { const nextWidth = computeContainerDimensionForBoundText( metrics.width, container.type, ); - mutator(container, { width: nextWidth }); + scene.mutate(container, { width: nextWidth }); } + const updatedTextElement = { ...textElement, ...boundTextUpdates, } as ExcalidrawTextElementWithContainer; + const { x, y } = computeBoundTextPosition( container, updatedTextElement, elementsMap, ); + boundTextUpdates.x = x; boundTextUpdates.y = y; } - mutator(textElement, boundTextUpdates); + scene.mutate(textElement, boundTextUpdates); }; export const handleBindTextResize = ( diff --git a/packages/element/src/zindex.ts b/packages/element/src/zindex.ts index e09142e4a..b99cf833c 100644 --- a/packages/element/src/zindex.ts +++ b/packages/element/src/zindex.ts @@ -2,8 +2,6 @@ import { arrayToMap, findIndex, findLastIndex } from "@excalidraw/common"; import type { AppState } from "@excalidraw/excalidraw/types"; -import type Scene from "@excalidraw/excalidraw/scene/Scene"; - import { isFrameLikeElement } from "./typeChecks"; import { getElementsInGroup } from "./groups"; @@ -12,6 +10,8 @@ import { syncMovedIndices } from "./fractionalIndex"; import { getSelectedElements } from "./selection"; +import type Scene from "./Scene"; + import type { ExcalidrawElement, ExcalidrawFrameLikeElement } from "./types"; const isOfTargetFrame = (element: ExcalidrawElement, frameId: string) => { diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index 824e3e666..2eea65ad5 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -2,7 +2,6 @@ import { ARROW_TYPE } from "@excalidraw/common"; import { pointFrom } from "@excalidraw/math"; import { Excalidraw, mutateElement } from "@excalidraw/excalidraw"; -import Scene from "@excalidraw/excalidraw/scene/Scene"; import { actionSelectAll } from "@excalidraw/excalidraw/actions"; import { actionDuplicateSelection } from "@excalidraw/excalidraw/actions/actionDuplicateSelection"; @@ -23,6 +22,8 @@ import type { LocalPoint } from "@excalidraw/math"; import { bindLinearElement } from "../src/binding"; +import Scene from "../src/Scene"; + import type { ExcalidrawArrowElement, ExcalidrawBindableElement, @@ -187,13 +188,9 @@ describe("elbow arrow routing", () => { scene.insertElement(rectangle1); scene.insertElement(rectangle2); scene.insertElement(arrow); - const elementsMap = scene.getNonDeletedElementsMap(); - bindLinearElement(arrow, rectangle1, "start", elementsMap, (...args) => - scene.mutate(...args), - ); - bindLinearElement(arrow, rectangle2, "end", elementsMap, (...args) => - scene.mutate(...args), - ); + + bindLinearElement(arrow, rectangle1, "start", scene); + bindLinearElement(arrow, rectangle2, "end", scene); expect(arrow.startBinding).not.toBe(null); expect(arrow.endBinding).not.toBe(null); diff --git a/packages/excalidraw/actions/actionBoundText.tsx b/packages/excalidraw/actions/actionBoundText.tsx index 90680545f..facd7354a 100644 --- a/packages/excalidraw/actions/actionBoundText.tsx +++ b/packages/excalidraw/actions/actionBoundText.tsx @@ -162,12 +162,7 @@ export const actionBindText = register({ }), }); const originalContainerHeight = container.height; - redrawTextBoundingBox( - textElement, - container, - app.scene.getNonDeletedElementsMap(), - (...args) => app.scene.mutate(...args), - ); + redrawTextBoundingBox(textElement, container, app.scene); // overwritting the cache with original container height so // it can be restored when unbind updateOriginalContainerCache(container.id, originalContainerHeight); @@ -312,12 +307,8 @@ export const actionWrapTextInContainer = register({ textAlign: TEXT_ALIGN.CENTER, autoResize: true, }); - redrawTextBoundingBox( - textElement, - container, - app.scene.getNonDeletedElementsMap(), - (...args) => app.scene.mutate(...args), - ); + + redrawTextBoundingBox(textElement, container, app.scene); updatedElements = pushContainerBelowText( [...updatedElements, container], diff --git a/packages/excalidraw/actions/actionDeleteSelected.tsx b/packages/excalidraw/actions/actionDeleteSelected.tsx index 2daf154ce..444b95125 100644 --- a/packages/excalidraw/actions/actionDeleteSelected.tsx +++ b/packages/excalidraw/actions/actionDeleteSelected.tsx @@ -259,7 +259,7 @@ export const actionDeleteSelected = register({ LinearElementEditor.deletePoints( element, - elementsMap, + app.scene, selectedPointsIndices, ); diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index 337d7cffe..02bf9f721 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -68,6 +68,8 @@ import type { VerticalAlign, } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import { trackEvent } from "../analytics"; import { ButtonIconSelect } from "../components/ButtonIconSelect"; import { ColorPicker } from "../components/ColorPicker/ColorPicker"; @@ -135,7 +137,6 @@ import { register } from "./register"; import type { CaptureUpdateActionType } from "../store"; import type { AppClassProperties, AppState, Primitive } from "../types"; -import type Scene from "../scene/Scene"; const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1; @@ -246,8 +247,7 @@ const changeFontSize = ( redrawTextBoundingBox( newElement, app.scene.getContainerElement(oldElement), - app.scene.getNonDeletedElementsMap(), - (...args) => app.scene.mutate(...args), + app.scene, ); newElement = offsetElementAfterFontResize( @@ -264,12 +264,11 @@ const changeFontSize = ( ); // Update arrow elements after text elements have been updated - const updatedElementsMap = arrayToMap(updatedElements); getSelectedElements(elements, appState, { includeBoundTextElement: true, }).forEach((element) => { if (isTextElement(element)) { - updateBoundElements(element, updatedElementsMap); + updateBoundElements(element, app.scene); } }); @@ -947,12 +946,7 @@ export const actionChangeFontFamily = register({ // we either skip the check (have at least one font face loaded) or do the check and find out all the font faces have loaded for (const [element, container] of elementContainerMapping) { // trigger synchronous redraw - redrawTextBoundingBox( - element, - container, - app.scene.getNonDeletedElementsMap(), - (...args) => app.scene.mutate(...args), - ); + redrawTextBoundingBox(element, container, app.scene); } } else { // otherwise try to load all font faces for the given chars and redraw elements once our font faces loaded @@ -969,8 +963,7 @@ export const actionChangeFontFamily = register({ redrawTextBoundingBox( latestElement as ExcalidrawTextElement, latestContainer, - app.scene.getNonDeletedElementsMap(), - (...args) => app.scene.mutate(...args), + app.scene, ); } } @@ -1176,8 +1169,7 @@ export const actionChangeTextAlign = register({ redrawTextBoundingBox( newElement, app.scene.getContainerElement(oldElement), - app.scene.getNonDeletedElementsMap(), - (...args) => app.scene.mutate(...args), + app.scene, ); return newElement; } @@ -1268,8 +1260,7 @@ export const actionChangeVerticalAlign = register({ redrawTextBoundingBox( newElement, app.scene.getContainerElement(oldElement), - app.scene.getNonDeletedElementsMap(), - (...args) => app.scene.mutate(...args), + app.scene, ); return newElement; } @@ -1669,17 +1660,10 @@ export const actionChangeArrowType = register({ newElement, startHoveredElement, "start", - elementsMap, - (...args) => app.scene.mutate(...args), + app.scene, ); endHoveredElement && - bindLinearElement( - newElement, - endHoveredElement, - "end", - elementsMap, - (...args) => app.scene.mutate(...args), - ); + bindLinearElement(newElement, endHoveredElement, "end", app.scene); const startBinding = startElement && newElement.startBinding @@ -1733,13 +1717,7 @@ export const actionChangeArrowType = register({ newElement.startBinding.elementId, ) as ExcalidrawBindableElement; if (startElement) { - bindLinearElement( - newElement, - startElement, - "start", - elementsMap, - (...args) => app.scene.mutate(...args), - ); + bindLinearElement(newElement, startElement, "start", app.scene); } } if (newElement.endBinding) { @@ -1747,13 +1725,7 @@ export const actionChangeArrowType = register({ newElement.endBinding.elementId, ) as ExcalidrawBindableElement; if (endElement) { - bindLinearElement( - newElement, - endElement, - "end", - elementsMap, - (...args) => app.scene.mutate(...args), - ); + bindLinearElement(newElement, endElement, "end", app.scene); } } } diff --git a/packages/excalidraw/actions/actionStyles.ts b/packages/excalidraw/actions/actionStyles.ts index b2a248a6c..08b32e227 100644 --- a/packages/excalidraw/actions/actionStyles.ts +++ b/packages/excalidraw/actions/actionStyles.ts @@ -139,12 +139,8 @@ export const actionPasteStyles = register({ element.id === newElement.containerId, ) || null; } - redrawTextBoundingBox( - newElement, - container, - app.scene.getNonDeletedElementsMap(), - (...args) => app.scene.mutate(...args), - ); + + redrawTextBoundingBox(newElement, container, app.scene); } if ( diff --git a/packages/excalidraw/change.ts b/packages/excalidraw/change.ts index 9b83cfc49..e27bf5d4c 100644 --- a/packages/excalidraw/change.ts +++ b/packages/excalidraw/change.ts @@ -16,7 +16,6 @@ import { import { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; import { mutateElement, - mutateElementWith, newElementWith, } from "@excalidraw/element/mutateElement"; import { @@ -38,6 +37,8 @@ import { syncMovedIndices, } from "@excalidraw/element/fractionalIndex"; +import Scene from "@excalidraw/element/Scene"; + import type { BindableProp, BindingProp } from "@excalidraw/element/binding"; import type { ElementUpdate } from "@excalidraw/element/mutateElement"; @@ -1135,8 +1136,13 @@ export class ElementsChange implements Change { } try { + // we don't have an up-to-date scene, as we can be just in the middle of applying history entry + // we also don't have a scene on the server + // so we are creating a temp scene just to query and mutate elements + const tempScene = new Scene(nextElements); + // TODO: #7348 refactor away mutations below, so that we couldn't end up in an incosistent state - ElementsChange.redrawTextBoundingBoxes(nextElements, changedElements); + ElementsChange.redrawTextBoundingBoxes(tempScene, changedElements); // the following reorder performs also mutations, but only on new instances of changed elements // (unless something goes really bad and it fallbacks to fixing all invalid indices) @@ -1147,7 +1153,7 @@ export class ElementsChange implements Change { ); // Need ordered nextElements to avoid z-index binding issues - ElementsChange.redrawBoundArrows(nextElements, changedElements); + ElementsChange.redrawBoundArrows(tempScene, changedElements); } catch (e) { console.error( `Couldn't mutate elements after applying elements change`, @@ -1459,9 +1465,10 @@ export class ElementsChange implements Change { } private static redrawTextBoundingBoxes( - elements: SceneElementsMap, + scene: Scene, changed: Map, ) { + const elements = scene.getNonDeletedElementsMap(); const boxesToRedraw = new Map< string, { container: OrderedExcalidrawElement; boundText: ExcalidrawTextElement } @@ -1501,22 +1508,17 @@ export class ElementsChange implements Change { continue; } - redrawTextBoundingBox( - boundText, - container, - elements, - (element, updates) => mutateElementWith(element, elements, updates), - ); + redrawTextBoundingBox(boundText, container, scene); } } private static redrawBoundArrows( - elements: SceneElementsMap, + scene: Scene, changed: Map, ) { for (const element of changed.values()) { if (!element.isDeleted && isBindableElement(element)) { - updateBoundElements(element, elements, { + updateBoundElements(element, scene, { changedElements: changed, }); } diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index c27395b8b..e92b19ebe 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -299,6 +299,8 @@ import { import { isNonDeletedElement } from "@excalidraw/element"; +import Scene from "@excalidraw/element/Scene"; + import type { LocalPoint, Radians } from "@excalidraw/math"; import type { @@ -400,7 +402,6 @@ import { hasBackground, isSomeElementSelected, } from "../scene"; -import Scene from "../scene/Scene"; import { getStateForZoom } from "../scene/zoom"; import { dataURLToFile, @@ -3332,12 +3333,7 @@ class App extends React.Component { newElement, this.scene.getElementsMapIncludingDeleted(), ); - redrawTextBoundingBox( - newElement, - container, - this.scene.getElementsMapIncludingDeleted(), - (...args) => this.scene.mutate(...args), - ); + redrawTextBoundingBox(newElement, container, this.scene); } }); @@ -4439,7 +4435,7 @@ class App extends React.Component { { informMutation: false }, ); - updateBoundElements(element, this.scene.getNonDeletedElementsMap(), { + updateBoundElements(element, this.scene, { simultaneouslyUpdated: selectedElements, }); }); @@ -4976,7 +4972,7 @@ class App extends React.Component { onChange: withBatchedUpdates((nextOriginalText) => { updateElement(nextOriginalText, false); if (isNonDeletedElement(element)) { - updateBoundElements(element, this.scene.getNonDeletedElementsMap()); + updateBoundElements(element, this.scene); } }), onSubmit: withBatchedUpdates(({ viaKeyboard, nextOriginalText }) => { @@ -5883,7 +5879,6 @@ class App extends React.Component { scenePointerX, scenePointerY, this, - this.scene.getNonDeletedElementsMap(), ); if ( @@ -10762,16 +10757,12 @@ class App extends React.Component { ), ); - updateBoundElements( - croppingElement, - this.scene.getNonDeletedElementsMap(), - { - newSize: { - width: croppingElement.width, - height: croppingElement.height, - }, + updateBoundElements(croppingElement, this.scene, { + newSize: { + width: croppingElement.width, + height: croppingElement.height, }, - ); + }); this.setState({ isCropping: transformHandleType && transformHandleType !== "rotation", diff --git a/packages/excalidraw/components/Stats/Angle.tsx b/packages/excalidraw/components/Stats/Angle.tsx index f3f50ef3a..3873516e0 100644 --- a/packages/excalidraw/components/Stats/Angle.tsx +++ b/packages/excalidraw/components/Stats/Angle.tsx @@ -7,13 +7,14 @@ import type { Degrees } from "@excalidraw/math"; import type { ExcalidrawElement } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import { angleIcon } from "../icons"; import DragInput from "./DragInput"; import { getStepSizedValue, isPropertyEditable, updateBindings } from "./utils"; import type { DragInputCallbackType } from "./DragInput"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; interface AngleProps { diff --git a/packages/excalidraw/components/Stats/CanvasGrid.tsx b/packages/excalidraw/components/Stats/CanvasGrid.tsx index 4611365f4..4766f8204 100644 --- a/packages/excalidraw/components/Stats/CanvasGrid.tsx +++ b/packages/excalidraw/components/Stats/CanvasGrid.tsx @@ -1,9 +1,10 @@ +import type Scene from "@excalidraw/element/Scene"; + import { getNormalizedGridStep } from "../../scene"; import StatsDragInput from "./DragInput"; import { getStepSizedValue } from "./utils"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; interface PositionProps { diff --git a/packages/excalidraw/components/Stats/Dimension.tsx b/packages/excalidraw/components/Stats/Dimension.tsx index fb93d3ec0..d974bd8de 100644 --- a/packages/excalidraw/components/Stats/Dimension.tsx +++ b/packages/excalidraw/components/Stats/Dimension.tsx @@ -10,11 +10,12 @@ import { isImageElement } from "@excalidraw/element/typeChecks"; import type { ExcalidrawElement } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import DragInput from "./DragInput"; import { getStepSizedValue, isPropertyEditable } from "./utils"; import type { DragInputCallbackType } from "./DragInput"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; interface DimensionDragInputProps { diff --git a/packages/excalidraw/components/Stats/DragInput.tsx b/packages/excalidraw/components/Stats/DragInput.tsx index 1b97afff9..6fdf909b2 100644 --- a/packages/excalidraw/components/Stats/DragInput.tsx +++ b/packages/excalidraw/components/Stats/DragInput.tsx @@ -7,6 +7,8 @@ import { deepCopyElement } from "@excalidraw/element/duplicate"; import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import { CaptureUpdateAction } from "../../store"; import { useApp } from "../App"; import { InlineIcon } from "../InlineIcon"; @@ -16,7 +18,6 @@ import { SMALLEST_DELTA } from "./utils"; import "./DragInput.scss"; import type { StatsInputProperty } from "./utils"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; export type DragInputCallbackType< diff --git a/packages/excalidraw/components/Stats/FontSize.tsx b/packages/excalidraw/components/Stats/FontSize.tsx index bdbbebd89..8939914e8 100644 --- a/packages/excalidraw/components/Stats/FontSize.tsx +++ b/packages/excalidraw/components/Stats/FontSize.tsx @@ -13,13 +13,14 @@ import type { ExcalidrawTextElement, } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import { fontSizeIcon } from "../icons"; import StatsDragInput from "./DragInput"; import { getStepSizedValue } from "./utils"; import type { DragInputCallbackType } from "./DragInput"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; interface FontSizeProps { @@ -74,8 +75,7 @@ const handleFontSizeChange: DragInputCallbackType< redrawTextBoundingBox( latestElement, scene.getContainerElement(latestElement), - scene.getNonDeletedElementsMap(), - (...args) => scene.mutate(...args), + scene, ); } } diff --git a/packages/excalidraw/components/Stats/MultiAngle.tsx b/packages/excalidraw/components/Stats/MultiAngle.tsx index 895fe5cff..f5c3178ed 100644 --- a/packages/excalidraw/components/Stats/MultiAngle.tsx +++ b/packages/excalidraw/components/Stats/MultiAngle.tsx @@ -11,13 +11,14 @@ import type { Degrees } from "@excalidraw/math"; import type { ExcalidrawElement } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import { angleIcon } from "../icons"; import DragInput from "./DragInput"; import { getStepSizedValue, isPropertyEditable } from "./utils"; import type { DragInputCallbackType } from "./DragInput"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; interface MultiAngleProps { diff --git a/packages/excalidraw/components/Stats/MultiDimension.tsx b/packages/excalidraw/components/Stats/MultiDimension.tsx index cc771b56f..0bd1b55d9 100644 --- a/packages/excalidraw/components/Stats/MultiDimension.tsx +++ b/packages/excalidraw/components/Stats/MultiDimension.tsx @@ -16,24 +16,20 @@ import { isTextElement } from "@excalidraw/element/typeChecks"; import { getCommonBounds } from "@excalidraw/utils"; -import { - mutateElement, - mutateElementWith, -} from "@excalidraw/element/mutateElement"; - import type { ElementsMap, ExcalidrawElement, NonDeletedSceneElementsMap, } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import DragInput from "./DragInput"; import { getAtomicUnits, getStepSizedValue, isPropertyEditable } from "./utils"; import { getElementsInAtomicUnit } from "./utils"; import type { DragInputCallbackType } from "./DragInput"; import type { AtomicUnit } from "./utils"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; interface MultiDimensionProps { @@ -79,12 +75,13 @@ const resizeElementInGroup = ( scale: number, latestElement: ExcalidrawElement, origElement: ExcalidrawElement, - elementsMap: NonDeletedSceneElementsMap, originalElementsMap: ElementsMap, + scene: Scene, ) => { + const elementsMap = scene.getNonDeletedElementsMap(); const updates = getResizedUpdates(anchorX, anchorY, scale, origElement); - mutateElementWith(latestElement, elementsMap, updates); + scene.mutate(latestElement, updates); const boundTextElement = getBoundTextElement( origElement, @@ -92,12 +89,12 @@ const resizeElementInGroup = ( ); if (boundTextElement) { const newFontSize = boundTextElement.fontSize * scale; - updateBoundElements(latestElement, elementsMap, { + updateBoundElements(latestElement, scene, { newSize: { width: updates.width, height: updates.height }, }); const latestBoundTextElement = elementsMap.get(boundTextElement.id); if (latestBoundTextElement && isTextElement(latestBoundTextElement)) { - mutateElement(latestBoundTextElement, { + scene.mutate(latestBoundTextElement, { fontSize: newFontSize, }); handleBindTextResize( @@ -119,8 +116,8 @@ const resizeGroup = ( property: MultiDimensionProps["property"], latestElements: ExcalidrawElement[], originalElements: ExcalidrawElement[], - elementsMap: NonDeletedSceneElementsMap, originalElementsMap: ElementsMap, + scene: Scene, ) => { // keep aspect ratio for groups if (property === "width") { @@ -142,8 +139,8 @@ const resizeGroup = ( scale, latestElement, origElement, - elementsMap, originalElementsMap, + scene, ); } }; @@ -195,8 +192,8 @@ const handleDimensionChange: DragInputCallbackType< property, latestElements, originalElements, - elementsMap, originalElementsMap, + scene, ); } else { const [el] = elementsInUnit; @@ -302,8 +299,8 @@ const handleDimensionChange: DragInputCallbackType< property, latestElements, originalElements, - elementsMap, originalElementsMap, + scene, ); } else { const [el] = elementsInUnit; diff --git a/packages/excalidraw/components/Stats/MultiFontSize.tsx b/packages/excalidraw/components/Stats/MultiFontSize.tsx index 1060f2529..973e5d665 100644 --- a/packages/excalidraw/components/Stats/MultiFontSize.tsx +++ b/packages/excalidraw/components/Stats/MultiFontSize.tsx @@ -16,13 +16,14 @@ import type { NonDeletedSceneElementsMap, } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import { fontSizeIcon } from "../icons"; import StatsDragInput from "./DragInput"; import { getStepSizedValue } from "./utils"; import type { DragInputCallbackType } from "./DragInput"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; interface MultiFontSizeProps { @@ -91,8 +92,7 @@ const handleFontSizeChange: DragInputCallbackType< redrawTextBoundingBox( textElement, scene.getContainerElement(textElement), - elementsMap, - (...args) => scene.mutate(...args), + scene, ); } @@ -120,8 +120,7 @@ const handleFontSizeChange: DragInputCallbackType< redrawTextBoundingBox( latestElement, scene.getContainerElement(latestElement), - elementsMap, - (...args) => scene.mutate(...args), + scene, ); } diff --git a/packages/excalidraw/components/Stats/MultiPosition.tsx b/packages/excalidraw/components/Stats/MultiPosition.tsx index a6931d6dc..ae6b52296 100644 --- a/packages/excalidraw/components/Stats/MultiPosition.tsx +++ b/packages/excalidraw/components/Stats/MultiPosition.tsx @@ -7,13 +7,14 @@ import { getCommonBounds } from "@excalidraw/element/bounds"; import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import StatsDragInput from "./DragInput"; import { getAtomicUnits, getStepSizedValue, isPropertyEditable } from "./utils"; import { getElementsInAtomicUnit, moveElement } from "./utils"; import type { DragInputCallbackType } from "./DragInput"; import type { AtomicUnit } from "./utils"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; interface MultiPositionProps { diff --git a/packages/excalidraw/components/Stats/Position.tsx b/packages/excalidraw/components/Stats/Position.tsx index 7e1bc6529..eb985184f 100644 --- a/packages/excalidraw/components/Stats/Position.tsx +++ b/packages/excalidraw/components/Stats/Position.tsx @@ -9,11 +9,12 @@ import { isImageElement } from "@excalidraw/element/typeChecks"; import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import StatsDragInput from "./DragInput"; import { getStepSizedValue, moveElement } from "./utils"; import type { DragInputCallbackType } from "./DragInput"; -import type Scene from "../../scene/Scene"; import type { AppState } from "../../types"; interface PositionProps { diff --git a/packages/excalidraw/components/Stats/utils.ts b/packages/excalidraw/components/Stats/utils.ts index 374b03d41..3905a012e 100644 --- a/packages/excalidraw/components/Stats/utils.ts +++ b/packages/excalidraw/components/Stats/utils.ts @@ -25,7 +25,8 @@ import type { NonDeletedExcalidrawElement, } from "@excalidraw/element/types"; -import type Scene from "../../scene/Scene"; +import type Scene from "@excalidraw/element/Scene"; + import type { AppState } from "../../types"; export type StatsInputProperty = @@ -206,10 +207,6 @@ export const updateBindings = ( if (isLinearElement(latestElement)) { bindOrUnbindLinearElements([latestElement], true, [], scene, options?.zoom); } else { - updateBoundElements( - latestElement, - scene.getNonDeletedElementsMap(), - options, - ); + updateBoundElements(latestElement, scene, options); } }; diff --git a/packages/excalidraw/data/transform.ts b/packages/excalidraw/data/transform.ts index f9beaf226..787f2489d 100644 --- a/packages/excalidraw/data/transform.ts +++ b/packages/excalidraw/data/transform.ts @@ -38,14 +38,13 @@ import { redrawTextBoundingBox } from "@excalidraw/element/textElement"; import { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; -import { mutateElementWith } from "@excalidraw/element/mutateElement"; - import { getCommonBounds } from "@excalidraw/element/bounds"; +import Scene from "@excalidraw/element/Scene"; + import type { ElementConstructorOpts } from "@excalidraw/element/newElement"; import type { - ElementsMap, ExcalidrawArrowElement, ExcalidrawBindableElement, ExcalidrawElement, @@ -223,7 +222,7 @@ const DEFAULT_DIMENSION = 100; const bindTextToContainer = ( container: ExcalidrawElement, textProps: { text: string } & MarkOptional, - elementsMap: ElementsMap, + scene: Scene, ) => { const textElement: ExcalidrawTextElement = newTextElement({ x: 0, @@ -242,12 +241,8 @@ const bindTextToContainer = ( }), }); - redrawTextBoundingBox( - textElement, - container, - elementsMap, - (element, updates) => mutateElementWith(element, elementsMap, updates), - ); + redrawTextBoundingBox(textElement, container, scene); + return [container, textElement] as const; }; @@ -256,7 +251,7 @@ const bindLinearElementToElement = ( start: ValidLinearElement["start"], end: ValidLinearElement["end"], elementStore: ElementStore, - elementsMap: NonDeletedSceneElementsMap, + scene: Scene, ): { linearElement: ExcalidrawLinearElement; startBoundElement?: ExcalidrawElement; @@ -342,8 +337,7 @@ const bindLinearElementToElement = ( linearElement, startBoundElement as ExcalidrawBindableElement, "start", - elementsMap, - (element, updates) => mutateElementWith(element, elementsMap, updates), + scene, ); } } @@ -418,8 +412,7 @@ const bindLinearElementToElement = ( linearElement, endBoundElement as ExcalidrawBindableElement, "end", - elementsMap, - (element, updates) => mutateElementWith(element, elementsMap, updates), + scene, ); } } @@ -660,6 +653,9 @@ export const convertToExcalidrawElements = ( } const elementsMap = elementStore.getElementsMap(); + // we don't have a real scene, so we just use a temp scene to query and mutate elements + const scene = new Scene(elementsMap); + // Add labels and arrow bindings for (const [id, element] of elementsWithIds) { const excalidrawElement = elementStore.getElement(id)!; @@ -673,7 +669,7 @@ export const convertToExcalidrawElements = ( let [container, text] = bindTextToContainer( excalidrawElement, element?.label, - elementsMap, + scene, ); elementStore.add(container); elementStore.add(text); @@ -701,7 +697,7 @@ export const convertToExcalidrawElements = ( originalStart, originalEnd, elementStore, - elementsMap, + scene, ); container = linearElement; elementStore.add(linearElement); @@ -726,7 +722,7 @@ export const convertToExcalidrawElements = ( start, end, elementStore, - elementsMap, + scene, ); elementStore.add(linearElement); diff --git a/packages/excalidraw/fonts/Fonts.ts b/packages/excalidraw/fonts/Fonts.ts index 79b5ea1af..a9419892b 100644 --- a/packages/excalidraw/fonts/Fonts.ts +++ b/packages/excalidraw/fonts/Fonts.ts @@ -28,6 +28,8 @@ import type { import type { ValueOf } from "@excalidraw/common/utility-types"; +import type Scene from "@excalidraw/element/Scene"; + import { CascadiaFontFaces } from "./Cascadia"; import { ComicShannsFontFaces } from "./ComicShanns"; import { EmojiFontFaces } from "./Emoji"; @@ -40,8 +42,6 @@ import { NunitoFontFaces } from "./Nunito"; import { VirgilFontFaces } from "./Virgil"; import { XiaolaiFontFaces } from "./Xiaolai"; -import type Scene from "../scene/Scene"; - export class Fonts { // it's ok to track fonts across multiple instances only once, so let's use // a static member to reduce memory footprint diff --git a/packages/excalidraw/scene/Renderer.ts b/packages/excalidraw/scene/Renderer.ts index e22c997ed..47d859cbf 100644 --- a/packages/excalidraw/scene/Renderer.ts +++ b/packages/excalidraw/scene/Renderer.ts @@ -9,10 +9,11 @@ import type { NonDeletedExcalidrawElement, } from "@excalidraw/element/types"; +import type Scene from "@excalidraw/element/Scene"; + import { renderInteractiveSceneThrottled } from "../renderer/interactiveScene"; import { renderStaticSceneThrottled } from "../renderer/staticScene"; -import type Scene from "./Scene"; import type { RenderableElementsMap } from "./types"; import type { AppState } from "../types"; diff --git a/packages/excalidraw/tests/linearElementEditor.test.tsx b/packages/excalidraw/tests/linearElementEditor.test.tsx index e2922edd7..555c1384d 100644 --- a/packages/excalidraw/tests/linearElementEditor.test.tsx +++ b/packages/excalidraw/tests/linearElementEditor.test.tsx @@ -1384,7 +1384,7 @@ describe("Test Linear Elements", () => { const [origStartX, origStartY] = [line.x, line.y]; act(() => { - LinearElementEditor.movePoints(line, arrayToMap(h.elements), [ + LinearElementEditor.movePoints(line, h.app.scene, [ { index: 0, point: pointFrom(line.points[0][0] + 10, line.points[0][1] + 10), diff --git a/packages/excalidraw/wysiwyg/textWysiwyg.tsx b/packages/excalidraw/wysiwyg/textWysiwyg.tsx index 5f0e3134d..7fe1f9457 100644 --- a/packages/excalidraw/wysiwyg/textWysiwyg.tsx +++ b/packages/excalidraw/wysiwyg/textWysiwyg.tsx @@ -577,12 +577,8 @@ export const textWysiwyg = ({ ), }); } - redrawTextBoundingBox( - updateElement, - container, - app.scene.getNonDeletedElementsMap(), - (...args) => app.scene.mutate(...args), - ); + + redrawTextBoundingBox(updateElement, container, app.scene); } onSubmit({