From 11600ee6a69090aa0a1f766a3709d4519a18b843 Mon Sep 17 00:00:00 2001 From: Marcel Mraz Date: Tue, 15 Apr 2025 23:53:43 +0200 Subject: [PATCH] Deprecate mutateElement in the codebase --- packages/element/src/binding.ts | 32 +++++++++-------- packages/element/src/flowchart.ts | 4 +-- packages/element/src/linearElementEditor.ts | 6 ++-- packages/element/src/resizeElements.ts | 6 ++-- packages/element/src/sizeHelpers.ts | 36 ------------------- packages/element/src/textElement.ts | 10 +++--- packages/element/tests/duplicate.test.tsx | 3 +- packages/element/tests/elbowArrow.test.tsx | 6 ++-- packages/element/tests/sortElements.test.ts | 8 ++--- .../actions/actionDeleteSelected.test.tsx | 10 +++--- packages/excalidraw/change.ts | 7 ++-- packages/excalidraw/clipboard.ts | 4 +-- packages/excalidraw/components/App.tsx | 2 +- .../components/ElementLinkDialog.tsx | 17 ++++----- packages/excalidraw/components/LayerUI.tsx | 2 +- .../excalidraw/components/Stats/FontSize.tsx | 3 +- .../components/Stats/MultiAngle.tsx | 10 +++--- .../components/Stats/MultiDimension.tsx | 2 +- .../components/Stats/MultiFontSize.tsx | 5 ++- .../excalidraw/components/Stats/Position.tsx | 5 ++- .../components/Stats/stats.test.tsx | 4 +-- .../components/hyperlink/Hyperlink.tsx | 22 ++++++------ .../excalidraw/tests/elementLocking.test.tsx | 8 ++--- packages/excalidraw/tests/helpers/api.ts | 9 +++-- packages/excalidraw/tests/helpers/ui.ts | 3 +- .../tests/linearElementEditor.test.tsx | 4 +-- packages/excalidraw/wysiwyg/textWysiwyg.tsx | 12 +++---- 27 files changed, 100 insertions(+), 140 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index e1b5be6cc..0ab7746ff 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -48,7 +48,7 @@ import { type Heading, } from "./heading"; import { LinearElementEditor } from "./linearElementEditor"; -import { mutateElementWith, mutateElement } from "./mutateElement"; +import { mutateElementWith } from "./mutateElement"; import { getBoundTextElement, handleBindTextResize } from "./textElement"; import { isArrowElement, @@ -848,7 +848,7 @@ export const updateBoundElements = ( const boundText = getBoundTextElement(element, elementsMap); if (boundText && !boundText.isDeleted) { - handleBindTextResize(element, elementsMap, false); + handleBindTextResize(element, scene, false); } }); }; @@ -1499,7 +1499,7 @@ const fixReversedBindingsForBindables = ( (el) => el.id === newArrowId, )! as ExcalidrawArrowElement; - mutateElement(newArrow, { + mutateElementWith(newArrow, originalElements, { startBinding: oldArrow.startBinding?.elementId === binding.id ? { @@ -1515,7 +1515,7 @@ const fixReversedBindingsForBindables = ( } : newArrow.endBinding, }); - mutateElement(duplicate, { + mutateElementWith(duplicate, originalElements, { boundElements: [ ...(duplicate.boundElements ?? []).filter( (el) => el.id !== binding.id && el.id !== newArrowId, @@ -1529,7 +1529,7 @@ const fixReversedBindingsForBindables = ( } else { // Linked arrow is outside the selection, // so we move the binding to the duplicate - mutateElement(oldArrow, { + mutateElementWith(oldArrow, originalElements, { startBinding: oldArrow.startBinding?.elementId === original.id ? { @@ -1545,7 +1545,7 @@ const fixReversedBindingsForBindables = ( } : oldArrow.endBinding, }); - mutateElement(duplicate, { + mutateElementWith(duplicate, originalElements, { boundElements: [ ...(duplicate.boundElements ?? []), { @@ -1554,7 +1554,7 @@ const fixReversedBindingsForBindables = ( }, ], }); - mutateElement(original, { + mutateElementWith(original, originalElements, { boundElements: original.boundElements?.filter((_, i) => i !== idx) ?? null, }); @@ -1580,13 +1580,13 @@ const fixReversedBindingsForArrows = ( const newBindable = elementsWithClones.find( (el) => el.id === newBindableId, ) as ExcalidrawBindableElement; - mutateElement(duplicate, { + mutateElementWith(duplicate, originalElements, { [bindingProp]: { ...original[bindingProp], elementId: newBindableId, }, }); - mutateElement(newBindable, { + mutateElementWith(newBindable, originalElements, { boundElements: [ ...(newBindable.boundElements ?? []).filter( (el) => el.id !== original.id && el.id !== duplicate.id, @@ -1603,13 +1603,13 @@ const fixReversedBindingsForArrows = ( (el) => el.id === oldBindableId, ); if (originalBindable) { - mutateElement(duplicate, { + mutateElementWith(duplicate, originalElements, { [bindingProp]: original[bindingProp], }); - mutateElement(original, { + mutateElementWith(original, originalElements, { [bindingProp]: null, }); - mutateElement(originalBindable, { + mutateElementWith(originalBindable, originalElements, { boundElements: [ ...(originalBindable.boundElements?.filter( (el) => el.id !== original.id, @@ -1671,8 +1671,12 @@ export const fixBindingsAfterDeletion = ( const elements = arrayToMap(sceneElements); for (const element of deletedElements) { - BoundElement.unbindAffected(elements, element, mutateElement); - BindableElement.unbindAffected(elements, element, mutateElement); + BoundElement.unbindAffected(elements, element, (element, updates) => + mutateElementWith(element, elements, updates), + ); + BindableElement.unbindAffected(elements, element, (element, updates) => + mutateElementWith(element, elements, updates), + ); } }; diff --git a/packages/element/src/flowchart.ts b/packages/element/src/flowchart.ts index ada1b8e1b..eb70cb770 100644 --- a/packages/element/src/flowchart.ts +++ b/packages/element/src/flowchart.ts @@ -19,7 +19,7 @@ import { type Heading, } from "./heading"; import { LinearElementEditor } from "./linearElementEditor"; -import { mutateElement } from "./mutateElement"; +import { mutateElementWith } from "./mutateElement"; import { newArrowElement, newElement } from "./newElement"; import { aabbForElement } from "./shapes"; import { elementsAreInFrameBounds, elementOverlapsWithFrame } from "./frame"; @@ -678,7 +678,7 @@ export class FlowChartCreator { ) ) { this.pendingNodes = this.pendingNodes.map((node) => - mutateElement(node, { + mutateElementWith(node, elementsMap, { frameId: startNode.frameId, }), ); diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index d5ea102c1..ed0180b04 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -47,7 +47,7 @@ import { } from "./bounds"; import { headingIsHorizontal, vectorToHeading } from "./heading"; -import { mutateElementWith, mutateElement } from "./mutateElement"; +import { mutateElementWith } from "./mutateElement"; import { getBoundTextElement, handleBindTextResize } from "./textElement"; import { isBindingElement, @@ -356,7 +356,7 @@ export class LinearElementEditor { const boundTextElement = getBoundTextElement(element, elementsMap); if (boundTextElement) { - handleBindTextResize(element, elementsMap, false); + handleBindTextResize(element, scene, false); } // suggest bindings for first and last point if selected @@ -1571,7 +1571,7 @@ export class LinearElementEditor { elementsMap, ); if (points.length < 2) { - mutateElement(boundTextElement, { isDeleted: true }); + mutateElementWith(boundTextElement, elementsMap, { isDeleted: true }); } let x = 0; let y = 0; diff --git a/packages/element/src/resizeElements.ts b/packages/element/src/resizeElements.ts index 13cc0731f..ddd845661 100644 --- a/packages/element/src/resizeElements.ts +++ b/packages/element/src/resizeElements.ts @@ -962,8 +962,6 @@ export const resizeSingleElement = ( informMutation: shouldInformMutation, }); - const elementsMap = scene.getNonDeletedElementsMap(); - updateBoundElements(latestElement, scene, { // TODO: confirm with MARK if this actually makes sense newSize: { width: nextWidth, height: nextHeight }, @@ -976,7 +974,7 @@ export const resizeSingleElement = ( } handleBindTextResize( latestElement, - elementsMap, + scene, handleDirection, shouldMaintainAspectRatio, ); @@ -1536,7 +1534,7 @@ export const resizeMultipleElements = ( fontSize: boundTextFontSize, angle: isLinearElement(element) ? undefined : angle, }); - handleBindTextResize(element, elementsMap, handleDirection, true); + handleBindTextResize(element, scene, handleDirection, true); } } diff --git a/packages/element/src/sizeHelpers.ts b/packages/element/src/sizeHelpers.ts index 7a84dadba..bd3d3fb0c 100644 --- a/packages/element/src/sizeHelpers.ts +++ b/packages/element/src/sizeHelpers.ts @@ -6,7 +6,6 @@ import { import type { AppState, Offsets, Zoom } from "@excalidraw/excalidraw/types"; import { getCommonBounds, getElementBounds } from "./bounds"; -import { mutateElement } from "./mutateElement"; import { isFreeDrawElement, isLinearElement } from "./typeChecks"; import type { ElementsMap, ExcalidrawElement } from "./types"; @@ -170,41 +169,6 @@ export const getLockedLinearCursorAlignSize = ( return { width, height }; }; -export const resizePerfectLineForNWHandler = ( - element: ExcalidrawElement, - x: number, - y: number, -) => { - const anchorX = element.x + element.width; - const anchorY = element.y + element.height; - const distanceToAnchorX = x - anchorX; - const distanceToAnchorY = y - anchorY; - if (Math.abs(distanceToAnchorX) < Math.abs(distanceToAnchorY) / 2) { - mutateElement(element, { - x: anchorX, - width: 0, - y, - height: -distanceToAnchorY, - }); - } else if (Math.abs(distanceToAnchorY) < Math.abs(element.width) / 2) { - mutateElement(element, { - y: anchorY, - height: 0, - }); - } else { - const nextHeight = - Math.sign(distanceToAnchorY) * - Math.sign(distanceToAnchorX) * - element.width; - mutateElement(element, { - x, - y: anchorY - nextHeight, - width: -distanceToAnchorX, - height: nextHeight, - }); - } -}; - export const getNormalizedDimensions = ( element: Pick, ): { diff --git a/packages/element/src/textElement.ts b/packages/element/src/textElement.ts index 5cd914c96..fef3eb825 100644 --- a/packages/element/src/textElement.ts +++ b/packages/element/src/textElement.ts @@ -18,7 +18,6 @@ import { } from "./containerCache"; import { LinearElementEditor } from "./linearElementEditor"; -import { mutateElement } from "./mutateElement"; import { measureText } from "./textMeasurements"; import { wrapText } from "./textWrapping"; import { @@ -126,10 +125,11 @@ export const redrawTextBoundingBox = ( export const handleBindTextResize = ( container: NonDeletedExcalidrawElement, - elementsMap: ElementsMap, + scene: Scene, transformHandleType: MaybeTransformHandleType, shouldMaintainAspectRatio = false, ) => { + const elementsMap = scene.getNonDeletedElementsMap(); const boundTextElementId = getBoundTextElementId(container); if (!boundTextElementId) { return; @@ -182,20 +182,20 @@ export const handleBindTextResize = ( transformHandleType === "n") ? container.y - diff : container.y; - mutateElement(container, { + scene.mutate(container, { height: containerHeight, y: updatedY, }); } - mutateElement(textElement, { + scene.mutate(textElement, { text, width: nextWidth, height: nextHeight, }); if (!isArrowElement(container)) { - mutateElement( + scene.mutate( textElement, computeBoundTextPosition(container, textElement, elementsMap), ); diff --git a/packages/element/tests/duplicate.test.tsx b/packages/element/tests/duplicate.test.tsx index 7492bcc58..577522ba1 100644 --- a/packages/element/tests/duplicate.test.tsx +++ b/packages/element/tests/duplicate.test.tsx @@ -25,7 +25,6 @@ import { import type { LocalPoint } from "@excalidraw/math"; -import { mutateElement } from "../src/mutateElement"; import { duplicateElement, duplicateElements } from "../src/duplicate"; import type { ExcalidrawLinearElement } from "../src/types"; @@ -63,7 +62,7 @@ describe("duplicating single elements", () => { // @ts-ignore element.__proto__ = { hello: "world" }; - mutateElement(element, { + h.app.scene.mutate(element, { points: [pointFrom(1, 2), pointFrom(3, 4)], }); diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index 2eea65ad5..752ff3d64 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -1,6 +1,6 @@ import { ARROW_TYPE } from "@excalidraw/common"; import { pointFrom } from "@excalidraw/math"; -import { Excalidraw, mutateElement } from "@excalidraw/excalidraw"; +import { Excalidraw } from "@excalidraw/excalidraw"; import { actionSelectAll } from "@excalidraw/excalidraw/actions"; import { actionDuplicateSelection } from "@excalidraw/excalidraw/actions/actionDuplicateSelection"; @@ -143,7 +143,7 @@ describe("elbow arrow routing", () => { elbowed: true, }) as ExcalidrawElbowArrowElement; scene.insertElement(arrow); - mutateElement(arrow, { + h.app.scene.mutate(arrow, { points: [ pointFrom(-45 - arrow.x, -100.1 - arrow.y), pointFrom(45 - arrow.x, 99.9 - arrow.y), @@ -195,7 +195,7 @@ describe("elbow arrow routing", () => { expect(arrow.startBinding).not.toBe(null); expect(arrow.endBinding).not.toBe(null); - mutateElement(arrow, { + h.app.scene.mutate(arrow, { points: [pointFrom(0, 0), pointFrom(90, 200)], }); diff --git a/packages/element/tests/sortElements.test.ts b/packages/element/tests/sortElements.test.ts index 509e5e9d0..29ce376aa 100644 --- a/packages/element/tests/sortElements.test.ts +++ b/packages/element/tests/sortElements.test.ts @@ -1,10 +1,10 @@ import { API } from "@excalidraw/excalidraw/tests/helpers/api"; -import { mutateElement } from "../src/mutateElement"; import { normalizeElementOrder } from "../src/sortElements"; import type { ExcalidrawElement } from "../src/types"; +const { h } = window; const assertOrder = ( elements: readonly ExcalidrawElement[], expectedOrder: string[], @@ -35,7 +35,7 @@ describe("normalizeElementsOrder", () => { boundElements: [], }); - mutateElement(container, { + h.app.scene.mutate(container, { boundElements: [{ type: "text", id: boundText.id }], }); @@ -352,7 +352,7 @@ describe("normalizeElementsOrder", () => { containerId: container.id, }); - mutateElement(container, { + h.app.scene.mutate(container, { boundElements: [ { type: "text", id: boundText.id }, { type: "text", id: "xxx" }, @@ -387,7 +387,7 @@ describe("normalizeElementsOrder", () => { boundElements: [], groupIds: ["C", "A"], }); - mutateElement(container, { + h.app.scene.mutate(container, { boundElements: [{ type: "text", id: boundText.id }], }); diff --git a/packages/excalidraw/actions/actionDeleteSelected.test.tsx b/packages/excalidraw/actions/actionDeleteSelected.test.tsx index 090c81941..245df9528 100644 --- a/packages/excalidraw/actions/actionDeleteSelected.test.tsx +++ b/packages/excalidraw/actions/actionDeleteSelected.test.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Excalidraw, mutateElement } from "../index"; +import { Excalidraw } from "../index"; import { API } from "../tests/helpers/api"; import { act, assertElements, render } from "../tests/test-utils"; @@ -56,7 +56,7 @@ describe("deleting selected elements when frame selected should keep children + frameId: f1.id, }); - mutateElement(r1, { + h.app.scene.mutate(r1, { boundElements: [{ type: "text", id: t1.id }], }); @@ -94,7 +94,7 @@ describe("deleting selected elements when frame selected should keep children + frameId: null, }); - mutateElement(r1, { + h.app.scene.mutate(r1, { boundElements: [{ type: "text", id: t1.id }], }); @@ -132,7 +132,7 @@ describe("deleting selected elements when frame selected should keep children + frameId: null, }); - mutateElement(r1, { + h.app.scene.mutate(r1, { boundElements: [{ type: "text", id: t1.id }], }); @@ -170,7 +170,7 @@ describe("deleting selected elements when frame selected should keep children + frameId: null, }); - mutateElement(a1, { + h.app.scene.mutate(a1, { boundElements: [{ type: "text", id: t1.id }], }); diff --git a/packages/excalidraw/change.ts b/packages/excalidraw/change.ts index e27bf5d4c..9b2e0a56d 100644 --- a/packages/excalidraw/change.ts +++ b/packages/excalidraw/change.ts @@ -15,7 +15,7 @@ import { } from "@excalidraw/element/binding"; import { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; import { - mutateElement, + mutateElementWith, newElementWith, } from "@excalidraw/element/mutateElement"; import { @@ -1344,10 +1344,11 @@ export class ElementsChange implements Change { updates as ElementUpdate, ); } else { - affectedElement = mutateElement( + affectedElement = mutateElementWith( nextElement, + nextElements, updates as ElementUpdate, - ); + ) as OrderedExcalidrawElement; } nextAffectedElements.set(affectedElement.id, affectedElement); diff --git a/packages/excalidraw/clipboard.ts b/packages/excalidraw/clipboard.ts index 40e4f8b96..086c407db 100644 --- a/packages/excalidraw/clipboard.ts +++ b/packages/excalidraw/clipboard.ts @@ -7,7 +7,7 @@ import { isPromiseLike, } from "@excalidraw/common"; -import { mutateElement } from "@excalidraw/element/mutateElement"; +import { mutateElementWith } from "@excalidraw/element/mutateElement"; import { deepCopyElement } from "@excalidraw/element/duplicate"; import { isFrameLikeElement, @@ -172,7 +172,7 @@ export const serializeAsClipboardJSON = ({ !framesToCopy.has(getContainingFrame(element, elementsMap)!) ) { const copiedElement = deepCopyElement(element); - mutateElement(copiedElement, { + mutateElementWith(copiedElement, elementsMap, { frameId: null, }); return copiedElement; diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index e92b19ebe..53d81d3ed 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -1686,7 +1686,7 @@ class App extends React.Component { void; generateLinkForSelection: AppProps["generateLinkForSelection"]; }) => { + const elementsMap = scene.getNonDeletedElementsMap(); const originalLink = elementsMap.get(sourceElementId)?.link ?? null; const [nextLink, setNextLink] = useState(originalLink); @@ -70,7 +71,7 @@ const ElementLinkDialog = ({ if (nextLink && nextLink !== elementsMap.get(sourceElementId)?.link) { const elementToLink = elementsMap.get(sourceElementId); elementToLink && - mutateElement(elementToLink, { + scene.mutate(elementToLink, { link: nextLink, }); } @@ -78,13 +79,13 @@ const ElementLinkDialog = ({ if (!nextLink && linkEdited && sourceElementId) { const elementToLink = elementsMap.get(sourceElementId); elementToLink && - mutateElement(elementToLink, { + scene.mutate(elementToLink, { link: null, }); } onClose?.(); - }, [sourceElementId, nextLink, elementsMap, linkEdited, onClose]); + }, [sourceElementId, nextLink, elementsMap, linkEdited, scene, onClose]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { diff --git a/packages/excalidraw/components/LayerUI.tsx b/packages/excalidraw/components/LayerUI.tsx index 2db2578c2..c3720eeb5 100644 --- a/packages/excalidraw/components/LayerUI.tsx +++ b/packages/excalidraw/components/LayerUI.tsx @@ -490,7 +490,7 @@ const LayerUI = ({ openDialog: null, }); }} - elementsMap={app.scene.getNonDeletedElementsMap()} + scene={app.scene} appState={appState} generateLinkForSelection={generateLinkForSelection} /> diff --git a/packages/excalidraw/components/Stats/FontSize.tsx b/packages/excalidraw/components/Stats/FontSize.tsx index 8939914e8..4670fe74f 100644 --- a/packages/excalidraw/components/Stats/FontSize.tsx +++ b/packages/excalidraw/components/Stats/FontSize.tsx @@ -1,4 +1,3 @@ -import { mutateElement } from "@excalidraw/element/mutateElement"; import { getBoundTextElement, redrawTextBoundingBox, @@ -69,7 +68,7 @@ const handleFontSizeChange: DragInputCallbackType< } if (nextFontSize) { - mutateElement(latestElement, { + scene.mutate(latestElement, { fontSize: nextFontSize, }); redrawTextBoundingBox( diff --git a/packages/excalidraw/components/Stats/MultiAngle.tsx b/packages/excalidraw/components/Stats/MultiAngle.tsx index f5c3178ed..52fee522a 100644 --- a/packages/excalidraw/components/Stats/MultiAngle.tsx +++ b/packages/excalidraw/components/Stats/MultiAngle.tsx @@ -1,7 +1,5 @@ import { degreesToRadians, radiansToDegrees } from "@excalidraw/math"; -import { mutateElement } from "@excalidraw/element/mutateElement"; - import { getBoundTextElement } from "@excalidraw/element/textElement"; import { isArrowElement } from "@excalidraw/element/typeChecks"; @@ -55,13 +53,13 @@ const handleDegreeChange: DragInputCallbackType< if (!element) { continue; } - mutateElement(element, { + scene.mutate(element, { angle: nextAngle, }); const boundTextElement = getBoundTextElement(element, elementsMap); if (boundTextElement && !isArrowElement(element)) { - mutateElement(boundTextElement, { angle: nextAngle }); + scene.mutate(boundTextElement, { angle: nextAngle }); } } @@ -89,13 +87,13 @@ const handleDegreeChange: DragInputCallbackType< const nextAngle = degreesToRadians(nextAngleInDegrees as Degrees); - mutateElement(latestElement, { + scene.mutate(latestElement, { angle: nextAngle, }); const boundTextElement = getBoundTextElement(latestElement, elementsMap); if (boundTextElement && !isArrowElement(latestElement)) { - mutateElement(boundTextElement, { angle: nextAngle }); + scene.mutate(boundTextElement, { angle: nextAngle }); } } scene.triggerUpdate(); diff --git a/packages/excalidraw/components/Stats/MultiDimension.tsx b/packages/excalidraw/components/Stats/MultiDimension.tsx index 0bd1b55d9..1b9b5b750 100644 --- a/packages/excalidraw/components/Stats/MultiDimension.tsx +++ b/packages/excalidraw/components/Stats/MultiDimension.tsx @@ -99,7 +99,7 @@ const resizeElementInGroup = ( }); handleBindTextResize( latestElement, - elementsMap, + scene, property === "width" ? "e" : "s", true, ); diff --git a/packages/excalidraw/components/Stats/MultiFontSize.tsx b/packages/excalidraw/components/Stats/MultiFontSize.tsx index 973e5d665..1f3daa3ea 100644 --- a/packages/excalidraw/components/Stats/MultiFontSize.tsx +++ b/packages/excalidraw/components/Stats/MultiFontSize.tsx @@ -1,4 +1,3 @@ -import { mutateElement } from "@excalidraw/element/mutateElement"; import { getBoundTextElement, redrawTextBoundingBox, @@ -85,7 +84,7 @@ const handleFontSizeChange: DragInputCallbackType< nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE); for (const textElement of latestTextElements) { - mutateElement(textElement, { + scene.mutate(textElement, { fontSize: nextFontSize, }); @@ -113,7 +112,7 @@ const handleFontSizeChange: DragInputCallbackType< if (shouldChangeByStepSize) { nextFontSize = getStepSizedValue(nextFontSize, STEP_SIZE); } - mutateElement(latestElement, { + scene.mutate(latestElement, { fontSize: nextFontSize, }); diff --git a/packages/excalidraw/components/Stats/Position.tsx b/packages/excalidraw/components/Stats/Position.tsx index eb985184f..d4e0cb9d0 100644 --- a/packages/excalidraw/components/Stats/Position.tsx +++ b/packages/excalidraw/components/Stats/Position.tsx @@ -4,7 +4,6 @@ import { getFlipAdjustedCropPosition, getUncroppedWidthAndHeight, } from "@excalidraw/element/cropElement"; -import { mutateElement } from "@excalidraw/element/mutateElement"; import { isImageElement } from "@excalidraw/element/typeChecks"; import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; @@ -101,7 +100,7 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({ }; } - mutateElement(element, { + scene.mutate(element, { crop: nextCrop, }); @@ -119,7 +118,7 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({ y: clamp(crop.y + changeInY, 0, crop.naturalHeight - crop.height), }; - mutateElement(element, { + scene.mutate(element, { crop: nextCrop, }); diff --git a/packages/excalidraw/components/Stats/stats.test.tsx b/packages/excalidraw/components/Stats/stats.test.tsx index fc94da056..d842678b0 100644 --- a/packages/excalidraw/components/Stats/stats.test.tsx +++ b/packages/excalidraw/components/Stats/stats.test.tsx @@ -17,7 +17,7 @@ import type { ExcalidrawTextElement, } from "@excalidraw/element/types"; -import { Excalidraw, getCommonBounds, mutateElement } from "../.."; +import { Excalidraw, getCommonBounds } from "../.."; import { actionGroup } from "../../actions"; import { t } from "../../i18n"; import * as StaticScene from "../../renderer/staticScene"; @@ -478,7 +478,7 @@ describe("stats for a non-generic element", () => { containerId: container.id, fontSize: 20, }); - mutateElement(container, { + h.app.scene.mutate(container, { boundElements: [{ type: "text", id: text.id }], }); API.setElements([container, text]); diff --git a/packages/excalidraw/components/hyperlink/Hyperlink.tsx b/packages/excalidraw/components/hyperlink/Hyperlink.tsx index 9a386a163..d07bff005 100644 --- a/packages/excalidraw/components/hyperlink/Hyperlink.tsx +++ b/packages/excalidraw/components/hyperlink/Hyperlink.tsx @@ -21,8 +21,6 @@ import { embeddableURLValidator, } from "@excalidraw/element/embeddable"; -import { mutateElement } from "@excalidraw/element/mutateElement"; - import { sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, @@ -33,6 +31,8 @@ import { import { isEmbeddableElement } from "@excalidraw/element/typeChecks"; +import type Scene from "@excalidraw/element/Scene"; + import type { ElementsMap, ExcalidrawEmbeddableElement, @@ -70,14 +70,14 @@ const embeddableLinkCache = new Map< export const Hyperlink = ({ element, - elementsMap, + scene, setAppState, onLinkOpen, setToast, updateEmbedValidationStatus, }: { element: NonDeletedExcalidrawElement; - elementsMap: ElementsMap; + scene: Scene; setAppState: React.Component["setState"]; onLinkOpen: ExcalidrawProps["onLinkOpen"]; setToast: ( @@ -88,6 +88,7 @@ export const Hyperlink = ({ status: boolean, ) => void; }) => { + const elementsMap = scene.getNonDeletedElementsMap(); const appState = useExcalidrawAppState(); const appProps = useAppProps(); const device = useDevice(); @@ -114,7 +115,7 @@ export const Hyperlink = ({ setAppState({ activeEmbeddable: null }); } if (!link) { - mutateElement(element, { + scene.mutate(element, { link: null, }); updateEmbedValidationStatus(element, false); @@ -126,7 +127,7 @@ export const Hyperlink = ({ setToast({ message: t("toast.unableToEmbed"), closable: true }); } element.link && embeddableLinkCache.set(element.id, element.link); - mutateElement(element, { + scene.mutate(element, { link, }); updateEmbedValidationStatus(element, false); @@ -144,7 +145,7 @@ export const Hyperlink = ({ : 1; const hasLinkChanged = embeddableLinkCache.get(element.id) !== element.link; - mutateElement(element, { + scene.mutate(element, { ...(hasLinkChanged ? { width: @@ -169,10 +170,11 @@ export const Hyperlink = ({ } } } else { - mutateElement(element, { link }); + scene.mutate(element, { link }); } }, [ element, + scene, setToast, appProps.validateEmbeddable, appState.activeEmbeddable, @@ -229,9 +231,9 @@ export const Hyperlink = ({ const handleRemove = useCallback(() => { trackEvent("hyperlink", "delete"); - mutateElement(element, { link: null }); + scene.mutate(element, { link: null }); setAppState({ showHyperlinkPopup: false }); - }, [setAppState, element]); + }, [setAppState, element, scene]); const onEdit = () => { trackEvent("hyperlink", "edit", "popup-ui"); diff --git a/packages/excalidraw/tests/elementLocking.test.tsx b/packages/excalidraw/tests/elementLocking.test.tsx index 45e370ed8..a687816d0 100644 --- a/packages/excalidraw/tests/elementLocking.test.tsx +++ b/packages/excalidraw/tests/elementLocking.test.tsx @@ -1,7 +1,5 @@ import React from "react"; -import { mutateElement } from "@excalidraw/element/mutateElement"; - import { KEYS } from "@excalidraw/common"; import { actionSelectAll } from "../actions"; @@ -298,7 +296,7 @@ describe("element locking", () => { height: textSize, containerId: container.id, }); - mutateElement(container, { + h.app.scene.mutate(container, { boundElements: [{ id: text.id, type: "text" }], }); @@ -339,7 +337,7 @@ describe("element locking", () => { containerId: container.id, locked: true, }); - mutateElement(container, { + h.app.scene.mutate(container, { boundElements: [{ id: text.id, type: "text" }], }); API.setElements([container, text]); @@ -373,7 +371,7 @@ describe("element locking", () => { containerId: container.id, locked: true, }); - mutateElement(container, { + h.app.scene.mutate(container, { boundElements: [{ id: text.id, type: "text" }], }); API.setElements([container, text]); diff --git a/packages/excalidraw/tests/helpers/api.ts b/packages/excalidraw/tests/helpers/api.ts index 6eb76b199..52df18f08 100644 --- a/packages/excalidraw/tests/helpers/api.ts +++ b/packages/excalidraw/tests/helpers/api.ts @@ -6,7 +6,6 @@ import { pointFrom, type LocalPoint, type Radians } from "@excalidraw/math"; import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS, assertNever } from "@excalidraw/common"; -import { mutateElement } from "@excalidraw/element/mutateElement"; import { newArrowElement, newElement, @@ -100,10 +99,10 @@ export class API { // eslint-disable-next-line prettier/prettier static updateElement = ( - ...args: Parameters> + ...args: Parameters> ) => { act(() => { - mutateElement(...args); + h.app.scene.mutate(...args); }); }; @@ -419,7 +418,7 @@ export class API { }); - mutateElement( + h.app.scene.mutate( rectangle, { boundElements: [{ type: "text", id: text.id }], @@ -453,7 +452,7 @@ export class API { : opts?.label?.frameId ?? null, }); - mutateElement( + h.app.scene.mutate( arrow, { boundElements: [{ type: "text", id: text.id }], diff --git a/packages/excalidraw/tests/helpers/ui.ts b/packages/excalidraw/tests/helpers/ui.ts index 0e5e43367..79d343126 100644 --- a/packages/excalidraw/tests/helpers/ui.ts +++ b/packages/excalidraw/tests/helpers/ui.ts @@ -5,7 +5,6 @@ import { getElementPointsCoords, } from "@excalidraw/element/bounds"; import { cropElement } from "@excalidraw/element/cropElement"; -import { mutateElement } from "@excalidraw/element/mutateElement"; import { getTransformHandles, getTransformHandlesFromCoords, @@ -519,7 +518,7 @@ export class UI { if (angle !== 0) { act(() => { - mutateElement(origElement, { angle }); + h.app.scene.mutate(origElement, { angle }); }); } diff --git a/packages/excalidraw/tests/linearElementEditor.test.tsx b/packages/excalidraw/tests/linearElementEditor.test.tsx index 555c1384d..dcd022b89 100644 --- a/packages/excalidraw/tests/linearElementEditor.test.tsx +++ b/packages/excalidraw/tests/linearElementEditor.test.tsx @@ -30,7 +30,7 @@ import type { FontString, } from "@excalidraw/element/types"; -import { Excalidraw, mutateElement } from "../index"; +import { Excalidraw } from "../index"; import * as InteractiveCanvas from "../renderer/interactiveScene"; import * as StaticScene from "../renderer/staticScene"; import { API } from "../tests/helpers/api"; @@ -118,7 +118,7 @@ describe("Test Linear Elements", () => { ], roundness, }); - mutateElement(line, { points: line.points }); + h.app.scene.mutate(line, { points: line.points }); API.setElements([line]); mouse.clickAt(p1[0], p1[1]); return line; diff --git a/packages/excalidraw/wysiwyg/textWysiwyg.tsx b/packages/excalidraw/wysiwyg/textWysiwyg.tsx index 7fe1f9457..a1511a32e 100644 --- a/packages/excalidraw/wysiwyg/textWysiwyg.tsx +++ b/packages/excalidraw/wysiwyg/textWysiwyg.tsx @@ -15,7 +15,7 @@ import { } from "@excalidraw/element/containerCache"; import { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; -import { bumpVersion, mutateElement } from "@excalidraw/element/mutateElement"; +import { bumpVersion } from "@excalidraw/element/mutateElement"; import { getBoundTextElementId, getContainerElement, @@ -199,7 +199,7 @@ export const textWysiwyg = ({ container.type, ); - mutateElement(container, { height: targetContainerHeight }); + app.scene.mutate(container, { height: targetContainerHeight }); return; } else if ( // autoshrink container height until original container height @@ -212,7 +212,7 @@ export const textWysiwyg = ({ height, container.type, ); - mutateElement(container, { height: targetContainerHeight }); + app.scene.mutate(container, { height: targetContainerHeight }); } else { const { y } = computeBoundTextPosition( container, @@ -285,7 +285,7 @@ export const textWysiwyg = ({ editable.style.fontFamily = getFontFamilyString(updatedTextElement); } - mutateElement(updatedTextElement, { x: coordX, y: coordY }); + app.scene.mutate(updatedTextElement, { x: coordX, y: coordY }); } }; @@ -557,7 +557,7 @@ export const textWysiwyg = ({ if (editable.value.trim()) { const boundTextElementId = getBoundTextElementId(container); if (!boundTextElementId || boundTextElementId !== element.id) { - mutateElement(container, { + app.scene.mutate(container, { boundElements: (container.boundElements || []).concat({ type: "text", id: element.id, @@ -568,7 +568,7 @@ export const textWysiwyg = ({ bumpVersion(container); } } else { - mutateElement(container, { + app.scene.mutate(container, { boundElements: container.boundElements?.filter( (ele) => !isTextElement(