refactor: remove dependency on the (static) Scene (#9389)

This commit is contained in:
Marcel Mraz
2025-04-23 13:45:08 +02:00
committed by GitHub
parent debf2ad608
commit 1913599594
67 changed files with 812 additions and 925 deletions

View File

@@ -1,7 +1,5 @@
import { degreesToRadians, radiansToDegrees } from "@excalidraw/math";
import { mutateElement } from "@excalidraw/element/mutateElement";
import { getBoundTextElement } from "@excalidraw/element/textElement";
import { isArrowElement, isElbowArrow } from "@excalidraw/element/typeChecks";
@@ -9,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 {
@@ -35,7 +34,6 @@ const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
scene,
}) => {
const elementsMap = scene.getNonDeletedElementsMap();
const elements = scene.getNonDeletedElements();
const origElement = originalElements[0];
if (origElement && !isElbowArrow(origElement)) {
const latestElement = elementsMap.get(origElement.id);
@@ -45,14 +43,14 @@ const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
if (nextValue !== undefined) {
const nextAngle = degreesToRadians(nextValue as Degrees);
mutateElement(latestElement, {
scene.mutateElement(latestElement, {
angle: nextAngle,
});
updateBindings(latestElement, elementsMap, elements, scene);
updateBindings(latestElement, scene);
const boundTextElement = getBoundTextElement(latestElement, elementsMap);
if (boundTextElement && !isArrowElement(latestElement)) {
mutateElement(boundTextElement, { angle: nextAngle });
scene.mutateElement(boundTextElement, { angle: nextAngle });
}
return;
@@ -71,14 +69,14 @@ const handleDegreeChange: DragInputCallbackType<AngleProps["property"]> = ({
const nextAngle = degreesToRadians(nextAngleInDegrees as Degrees);
mutateElement(latestElement, {
scene.mutateElement(latestElement, {
angle: nextAngle,
});
updateBindings(latestElement, elementsMap, elements, scene);
updateBindings(latestElement, scene);
const boundTextElement = getBoundTextElement(latestElement, elementsMap);
if (boundTextElement && !isArrowElement(latestElement)) {
mutateElement(boundTextElement, { angle: nextAngle });
scene.mutateElement(boundTextElement, { angle: nextAngle });
}
}
};

View File

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

View File

@@ -5,17 +5,17 @@ import {
MINIMAL_CROP_SIZE,
getUncroppedWidthAndHeight,
} from "@excalidraw/element/cropElement";
import { mutateElement } from "@excalidraw/element/mutateElement";
import { resizeSingleElement } from "@excalidraw/element/resizeElements";
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 {
@@ -113,7 +113,7 @@ const handleDimensionChange: DragInputCallbackType<
};
}
mutateElement(element, {
scene.mutateElement(element, {
crop: nextCrop,
width: nextCrop.width / (crop.naturalWidth / uncroppedWidth),
height: nextCrop.height / (crop.naturalHeight / uncroppedHeight),
@@ -144,7 +144,7 @@ const handleDimensionChange: DragInputCallbackType<
height: nextCropHeight,
};
mutateElement(element, {
scene.mutateElement(element, {
crop: nextCrop,
width: nextCrop.width / (crop.naturalWidth / uncroppedWidth),
height: nextCrop.height / (crop.naturalHeight / uncroppedHeight),
@@ -176,8 +176,8 @@ const handleDimensionChange: DragInputCallbackType<
nextHeight,
latestElement,
origElement,
elementsMap,
originalElementsMap,
scene,
property === "width" ? "e" : "s",
{
shouldMaintainAspectRatio: keepAspectRatio,
@@ -223,8 +223,8 @@ const handleDimensionChange: DragInputCallbackType<
nextHeight,
latestElement,
origElement,
elementsMap,
originalElementsMap,
scene,
property === "width" ? "e" : "s",
{
shouldMaintainAspectRatio: keepAspectRatio,

View File

@@ -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<
@@ -216,13 +217,12 @@ const StatsDragInput = <
y: number;
} | null = null;
let originalElementsMap: Map<string, ExcalidrawElement> | null =
app.scene
.getNonDeletedElements()
.reduce((acc: ElementsMap, element) => {
acc.set(element.id, deepCopyElement(element));
return acc;
}, new Map());
let originalElementsMap: ElementsMap | null = app.scene
.getNonDeletedElements()
.reduce((acc: ElementsMap, element) => {
acc.set(element.id, deepCopyElement(element));
return acc;
}, new Map());
let originalElements: readonly E[] | null = elements.map(
(element) => originalElementsMap!.get(element.id) as E,

View File

@@ -1,4 +1,3 @@
import { mutateElement } from "@excalidraw/element/mutateElement";
import {
getBoundTextElement,
redrawTextBoundingBox,
@@ -13,13 +12,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 {
@@ -68,13 +68,13 @@ const handleFontSizeChange: DragInputCallbackType<
}
if (nextFontSize) {
mutateElement(latestElement, {
scene.mutateElement(latestElement, {
fontSize: nextFontSize,
});
redrawTextBoundingBox(
latestElement,
scene.getContainerElement(latestElement),
scene.getNonDeletedElementsMap(),
scene,
);
}
}

View File

@@ -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";
@@ -11,13 +9,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 {
@@ -54,17 +53,13 @@ const handleDegreeChange: DragInputCallbackType<
if (!element) {
continue;
}
mutateElement(
element,
{
angle: nextAngle,
},
false,
);
scene.mutateElement(element, {
angle: nextAngle,
});
const boundTextElement = getBoundTextElement(element, elementsMap);
if (boundTextElement && !isArrowElement(element)) {
mutateElement(boundTextElement, { angle: nextAngle }, false);
scene.mutateElement(boundTextElement, { angle: nextAngle });
}
}
@@ -92,17 +87,13 @@ const handleDegreeChange: DragInputCallbackType<
const nextAngle = degreesToRadians(nextAngleInDegrees as Degrees);
mutateElement(
latestElement,
{
angle: nextAngle,
},
false,
);
scene.mutateElement(latestElement, {
angle: nextAngle,
});
const boundTextElement = getBoundTextElement(latestElement, elementsMap);
if (boundTextElement && !isArrowElement(latestElement)) {
mutateElement(boundTextElement, { angle: nextAngle }, false);
scene.mutateElement(boundTextElement, { angle: nextAngle });
}
}
scene.triggerUpdate();

View File

@@ -3,7 +3,6 @@ import { useMemo } from "react";
import { MIN_WIDTH_OR_HEIGHT } from "@excalidraw/common";
import { updateBoundElements } from "@excalidraw/element/binding";
import { mutateElement } from "@excalidraw/element/mutateElement";
import {
rescalePointsInElement,
resizeSingleElement,
@@ -23,13 +22,14 @@ import type {
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 {
@@ -75,33 +75,31 @@ 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);
mutateElement(latestElement, updates, false);
scene.mutateElement(latestElement, updates);
const boundTextElement = getBoundTextElement(
origElement,
originalElementsMap,
);
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,
{
fontSize: newFontSize,
},
false,
);
scene.mutateElement(latestBoundTextElement, {
fontSize: newFontSize,
});
handleBindTextResize(
latestElement,
elementsMap,
scene,
property === "width" ? "e" : "s",
true,
);
@@ -118,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") {
@@ -141,8 +139,8 @@ const resizeGroup = (
scale,
latestElement,
origElement,
elementsMap,
originalElementsMap,
scene,
);
}
};
@@ -194,8 +192,8 @@ const handleDimensionChange: DragInputCallbackType<
property,
latestElements,
originalElements,
elementsMap,
originalElementsMap,
scene,
);
} else {
const [el] = elementsInUnit;
@@ -237,8 +235,8 @@ const handleDimensionChange: DragInputCallbackType<
nextHeight,
latestElement,
origElement,
elementsMap,
originalElementsMap,
scene,
property === "width" ? "e" : "s",
{
shouldInformMutation: false,
@@ -301,8 +299,8 @@ const handleDimensionChange: DragInputCallbackType<
property,
latestElements,
originalElements,
elementsMap,
originalElementsMap,
scene,
);
} else {
const [el] = elementsInUnit;
@@ -340,8 +338,8 @@ const handleDimensionChange: DragInputCallbackType<
nextHeight,
latestElement,
origElement,
elementsMap,
originalElementsMap,
scene,
property === "width" ? "e" : "s",
{
shouldInformMutation: false,

View File

@@ -1,4 +1,3 @@
import { mutateElement } from "@excalidraw/element/mutateElement";
import {
getBoundTextElement,
redrawTextBoundingBox,
@@ -16,13 +15,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 {
@@ -84,19 +84,14 @@ const handleFontSizeChange: DragInputCallbackType<
nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE);
for (const textElement of latestTextElements) {
mutateElement(
textElement,
{
fontSize: nextFontSize,
},
false,
);
scene.mutateElement(textElement, {
fontSize: nextFontSize,
});
redrawTextBoundingBox(
textElement,
scene.getContainerElement(textElement),
elementsMap,
false,
scene,
);
}
@@ -117,19 +112,14 @@ const handleFontSizeChange: DragInputCallbackType<
if (shouldChangeByStepSize) {
nextFontSize = getStepSizedValue(nextFontSize, STEP_SIZE);
}
mutateElement(
latestElement,
{
fontSize: nextFontSize,
},
false,
);
scene.mutateElement(latestElement, {
fontSize: nextFontSize,
});
redrawTextBoundingBox(
latestElement,
scene.getContainerElement(latestElement),
elementsMap,
false,
scene,
);
}

View File

@@ -5,12 +5,9 @@ import { isTextElement } from "@excalidraw/element/typeChecks";
import { getCommonBounds } from "@excalidraw/element/bounds";
import type {
ElementsMap,
ExcalidrawElement,
NonDeletedExcalidrawElement,
NonDeletedSceneElementsMap,
} from "@excalidraw/element/types";
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";
@@ -18,7 +15,6 @@ 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 {
@@ -36,13 +32,11 @@ const moveElements = (
property: MultiPositionProps["property"],
changeInTopX: number,
changeInTopY: number,
elements: readonly ExcalidrawElement[],
originalElements: readonly ExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap,
originalElementsMap: ElementsMap,
scene: Scene,
) => {
for (let i = 0; i < elements.length; i++) {
for (let i = 0; i < originalElements.length; i++) {
const origElement = originalElements[i];
const [cx, cy] = [
@@ -65,8 +59,6 @@ const moveElements = (
newTopLeftX,
newTopLeftY,
origElement,
elementsMap,
elements,
scene,
originalElementsMap,
false,
@@ -78,11 +70,10 @@ const moveGroupTo = (
nextX: number,
nextY: number,
originalElements: ExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap,
elements: readonly NonDeletedExcalidrawElement[],
originalElementsMap: ElementsMap,
scene: Scene,
) => {
const elementsMap = scene.getNonDeletedElementsMap();
const [x1, y1, ,] = getCommonBounds(originalElements);
const offsetX = nextX - x1;
const offsetY = nextY - y1;
@@ -112,8 +103,6 @@ const moveGroupTo = (
topLeftX + offsetX,
topLeftY + offsetY,
origElement,
elementsMap,
elements,
scene,
originalElementsMap,
false,
@@ -135,7 +124,6 @@ const handlePositionChange: DragInputCallbackType<
originalAppState,
}) => {
const elementsMap = scene.getNonDeletedElementsMap();
const elements = scene.getNonDeletedElements();
if (nextValue !== undefined) {
for (const atomicUnit of getAtomicUnits(
@@ -159,8 +147,6 @@ const handlePositionChange: DragInputCallbackType<
newTopLeftX,
newTopLeftY,
elementsInUnit.map((el) => el.original),
elementsMap,
elements,
originalElementsMap,
scene,
);
@@ -188,8 +174,6 @@ const handlePositionChange: DragInputCallbackType<
newTopLeftX,
newTopLeftY,
origElement,
elementsMap,
elements,
scene,
originalElementsMap,
false,
@@ -214,8 +198,6 @@ const handlePositionChange: DragInputCallbackType<
changeInTopX,
changeInTopY,
originalElements,
originalElements,
elementsMap,
originalElementsMap,
scene,
);

View File

@@ -4,16 +4,16 @@ 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";
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 {
@@ -38,7 +38,6 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
originalAppState,
}) => {
const elementsMap = scene.getNonDeletedElementsMap();
const elements = scene.getNonDeletedElements();
const origElement = originalElements[0];
const [cx, cy] = [
origElement.x + origElement.width / 2,
@@ -101,7 +100,7 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
};
}
mutateElement(element, {
scene.mutateElement(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.mutateElement(element, {
crop: nextCrop,
});
@@ -133,8 +132,6 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
newTopLeftX,
newTopLeftY,
origElement,
elementsMap,
elements,
scene,
originalElementsMap,
);
@@ -166,8 +163,6 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
newTopLeftX,
newTopLeftY,
origElement,
elementsMap,
elements,
scene,
originalElementsMap,
);

View File

@@ -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.mutateElement(container, {
boundElements: [{ type: "text", id: text.id }],
});
API.setElements([container, text]);

View File

@@ -4,7 +4,6 @@ import {
bindOrUnbindLinearElements,
updateBoundElements,
} from "@excalidraw/element/binding";
import { mutateElement } from "@excalidraw/element/mutateElement";
import { getBoundTextElement } from "@excalidraw/element/textElement";
import {
isFrameLikeElement,
@@ -24,10 +23,10 @@ import type {
ElementsMap,
ExcalidrawElement,
NonDeletedExcalidrawElement,
NonDeletedSceneElementsMap,
} 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 =
@@ -119,12 +118,11 @@ export const moveElement = (
newTopLeftX: number,
newTopLeftY: number,
originalElement: ExcalidrawElement,
elementsMap: NonDeletedSceneElementsMap,
elements: readonly NonDeletedExcalidrawElement[],
scene: Scene,
originalElementsMap: ElementsMap,
shouldInformMutation = true,
) => {
const elementsMap = scene.getNonDeletedElementsMap();
const latestElement = elementsMap.get(originalElement.id);
if (!latestElement) {
return;
@@ -148,15 +146,15 @@ export const moveElement = (
-originalElement.angle as Radians,
);
mutateElement(
scene.mutateElement(
latestElement,
{
x,
y,
},
shouldInformMutation,
{ informMutation: shouldInformMutation, isDragging: false },
);
updateBindings(latestElement, elementsMap, elements, scene);
updateBindings(latestElement, scene);
const boundTextElement = getBoundTextElement(
originalElement,
@@ -165,13 +163,13 @@ export const moveElement = (
if (boundTextElement) {
const latestBoundTextElement = elementsMap.get(boundTextElement.id);
latestBoundTextElement &&
mutateElement(
scene.mutateElement(
latestBoundTextElement,
{
x: boundTextElement.x + changeInX,
y: boundTextElement.y + changeInY,
},
shouldInformMutation,
{ informMutation: shouldInformMutation, isDragging: false },
);
}
};
@@ -199,8 +197,6 @@ export const getAtomicUnits = (
export const updateBindings = (
latestElement: ExcalidrawElement,
elementsMap: NonDeletedSceneElementsMap,
elements: readonly NonDeletedExcalidrawElement[],
scene: Scene,
options?: {
simultaneouslyUpdated?: readonly ExcalidrawElement[];
@@ -209,16 +205,8 @@ export const updateBindings = (
},
) => {
if (isLinearElement(latestElement)) {
bindOrUnbindLinearElements(
[latestElement],
elementsMap,
elements,
scene,
true,
[],
options?.zoom,
);
bindOrUnbindLinearElements([latestElement], true, [], scene, options?.zoom);
} else {
updateBoundElements(latestElement, elementsMap, options);
updateBoundElements(latestElement, scene, options);
}
};