mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
Mutate element in the scene
This commit is contained in:
parent
703a8f0e78
commit
a9c3b2a4d4
6 changed files with 162 additions and 225 deletions
|
@ -909,7 +909,6 @@ export const elbowArrowNeedsToGetNormalized = (
|
|||
export const mutateElbowArrow = (
|
||||
element: Readonly<ExcalidrawElbowArrowElement>,
|
||||
updates: ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||
informMutation: boolean = true,
|
||||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap | ElementsMap,
|
||||
options?: {
|
||||
isDragging?: boolean;
|
||||
|
@ -921,23 +920,19 @@ export const mutateElbowArrow = (
|
|||
);
|
||||
|
||||
if (!elbowArrowNeedsToGetNormalized(element, updates)) {
|
||||
return mutateElement(element, updates, informMutation);
|
||||
return mutateElement(element, updates);
|
||||
}
|
||||
|
||||
return mutateElement(
|
||||
element,
|
||||
{
|
||||
...updates,
|
||||
angle: 0 as Radians,
|
||||
...updateElbowArrowPoints(
|
||||
element,
|
||||
elementsMap as NonDeletedSceneElementsMap,
|
||||
updates,
|
||||
options,
|
||||
),
|
||||
},
|
||||
informMutation,
|
||||
);
|
||||
return mutateElement(element, {
|
||||
...updates,
|
||||
angle: 0 as Radians,
|
||||
...updateElbowArrowPoints(
|
||||
element,
|
||||
elementsMap as NonDeletedSceneElementsMap,
|
||||
updates,
|
||||
options,
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,8 +20,6 @@ import {
|
|||
tupleToCoors,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
// TODO: remove direct dependency on the scene, should be passed in or injected instead
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||
|
||||
import type { Store } from "@excalidraw/excalidraw/store";
|
||||
|
|
|
@ -5,10 +5,6 @@ import {
|
|||
invariant,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
// TODO: remove direct dependency on the scene, should be passed in or injected instead
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||
|
||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { ShapeCache } from "./ShapeCache";
|
||||
|
@ -29,7 +25,6 @@ export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
|
|||
export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
||||
element: TElement,
|
||||
updates: ElementUpdate<TElement>,
|
||||
informMutation = true,
|
||||
): TElement => {
|
||||
let didChange = false;
|
||||
|
||||
|
@ -118,10 +113,6 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||
element.versionNonce = randomInteger();
|
||||
element.updated = getUpdatedTimestamp();
|
||||
|
||||
if (informMutation) {
|
||||
Scene.getScene(element)?.triggerUpdate();
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
|
|
|
@ -1534,14 +1534,10 @@ export const resizeMultipleElements = (
|
|||
} of elementsAndUpdates) {
|
||||
const { width, height, angle } = update;
|
||||
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(element, update, false, elementsMap, {
|
||||
// needed for the fixed binding point udpate to take effect
|
||||
isDragging: true,
|
||||
});
|
||||
} else {
|
||||
mutateElement(element, update, false);
|
||||
}
|
||||
scene.mutateElement(element, update, false, {
|
||||
// needed for the fixed binding point udpate to take effect
|
||||
isDragging: true,
|
||||
});
|
||||
|
||||
updateBoundElements(element, elementsMap as SceneElementsMap, {
|
||||
simultaneouslyUpdated: elementsToUpdate,
|
||||
|
@ -1550,7 +1546,7 @@ export const resizeMultipleElements = (
|
|||
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement && boundTextFontSize) {
|
||||
mutateElement(
|
||||
scene.mutateElement(
|
||||
boundTextElement,
|
||||
{
|
||||
fontSize: boundTextFontSize,
|
||||
|
|
|
@ -122,10 +122,7 @@ import {
|
|||
|
||||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||
|
||||
import {
|
||||
mutateElement,
|
||||
newElementWith,
|
||||
} from "@excalidraw/element/mutateElement";
|
||||
import { newElementWith } from "@excalidraw/element/mutateElement";
|
||||
|
||||
import {
|
||||
newFrameElement,
|
||||
|
@ -302,10 +299,6 @@ import {
|
|||
|
||||
import { isNonDeletedElement } from "@excalidraw/element";
|
||||
|
||||
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||
|
||||
import type { ElementUpdate } from "@excalidraw/element/mutateElement";
|
||||
|
||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
||||
|
||||
import type {
|
||||
|
@ -1409,7 +1402,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
|
||||
private resetEditingFrame = (frame: ExcalidrawFrameLikeElement | null) => {
|
||||
if (frame) {
|
||||
mutateElement(frame, { name: frame.name?.trim() || null });
|
||||
this.scene.mutateElement(frame, { name: frame.name?.trim() || null });
|
||||
}
|
||||
this.setState({ editingFrame: null });
|
||||
};
|
||||
|
@ -1466,7 +1459,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
autoFocus
|
||||
value={frameNameInEdit}
|
||||
onChange={(e) => {
|
||||
mutateElement(f, {
|
||||
this.scene.mutateElement(f, {
|
||||
name: e.target.value,
|
||||
});
|
||||
}}
|
||||
|
@ -1957,17 +1950,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||
// state only.
|
||||
// Thus reset so that we prefer local cache (if there was some
|
||||
// generationData set previously)
|
||||
mutateElement(
|
||||
frameElement,
|
||||
{ customData: { generationData: undefined } },
|
||||
false,
|
||||
);
|
||||
this.scene.mutateElement(frameElement, {
|
||||
customData: { generationData: undefined },
|
||||
}, { informMutation: false });
|
||||
} else {
|
||||
mutateElement(
|
||||
frameElement,
|
||||
{ customData: { generationData: data } },
|
||||
false,
|
||||
);
|
||||
this.scene.mutateElement(frameElement, {
|
||||
customData: { generationData: data },
|
||||
}, { informMutation: false });
|
||||
}
|
||||
this.magicGenerations.set(frameElement.id, data);
|
||||
this.triggerRender();
|
||||
|
@ -2138,7 +2127,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.scene.insertElement(frame);
|
||||
|
||||
for (const child of selectedElements) {
|
||||
mutateElement(child, { frameId: frame.id });
|
||||
this.scene.mutateElement(child, { frameId: frame.id });
|
||||
}
|
||||
|
||||
this.setState({
|
||||
|
@ -3463,7 +3452,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
}
|
||||
// hack to reset the `y` coord because we vertically center during
|
||||
// insertImageElement
|
||||
mutateElement(initializedImageElement, { y }, false);
|
||||
this.scene.mutateElement(initializedImageElement, { y }, { informMutation: false });
|
||||
|
||||
y = imageElement.y + imageElement.height + 25;
|
||||
|
||||
|
@ -4429,14 +4418,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
}
|
||||
|
||||
selectedElements.forEach((element) => {
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
x: element.x + offsetX,
|
||||
y: element.y + offsetY,
|
||||
},
|
||||
false,
|
||||
);
|
||||
this.scene.mutateElement(element, {
|
||||
x: element.x + offsetX,
|
||||
y: element.y + offsetY,
|
||||
}, { informMutation: false });
|
||||
|
||||
updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
|
||||
simultaneouslyUpdated: selectedElements,
|
||||
|
@ -5339,7 +5324,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const minHeight = getApproxMinLineHeight(fontSize, lineHeight);
|
||||
const newHeight = Math.max(container.height, minHeight);
|
||||
const newWidth = Math.max(container.width, minWidth);
|
||||
mutateElement(container, { height: newHeight, width: newWidth });
|
||||
this.scene.mutateElement(container, {
|
||||
height: newHeight,
|
||||
width: newWidth,
|
||||
});
|
||||
sceneX = container.x + newWidth / 2;
|
||||
sceneY = container.y + newHeight / 2;
|
||||
if (parentCenterPosition) {
|
||||
|
@ -5390,7 +5378,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
});
|
||||
|
||||
if (!existingTextElement && shouldBindToContainer && container) {
|
||||
mutateElement(container, {
|
||||
this.scene.mutateElement(container, {
|
||||
boundElements: (container.boundElements || []).concat({
|
||||
type: "text",
|
||||
id: element.id,
|
||||
|
@ -5939,7 +5927,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
lastPoint,
|
||||
) >= LINE_CONFIRM_THRESHOLD
|
||||
) {
|
||||
mutateElement(
|
||||
this.scene.mutateElement(
|
||||
multiElement,
|
||||
{
|
||||
points: [
|
||||
|
@ -5947,7 +5935,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
pointFrom<LocalPoint>(scenePointerX - rx, scenePointerY - ry),
|
||||
],
|
||||
},
|
||||
false,
|
||||
{ informMutation: false },
|
||||
);
|
||||
} else {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||
|
@ -5963,12 +5951,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||
) < LINE_CONFIRM_THRESHOLD
|
||||
) {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||
mutateElement(
|
||||
this.scene.mutateElement(
|
||||
multiElement,
|
||||
{
|
||||
points: points.slice(0, -1),
|
||||
},
|
||||
false,
|
||||
{ informMutation: false },
|
||||
);
|
||||
} else {
|
||||
const [gridX, gridY] = getGridPoint(
|
||||
|
@ -6000,30 +5988,24 @@ class App extends React.Component<AppProps, AppState> {
|
|||
if (isPathALoop(points, this.state.zoom.value)) {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||
}
|
||||
const updates = {
|
||||
points: [
|
||||
...points.slice(0, -1),
|
||||
pointFrom<LocalPoint>(
|
||||
lastCommittedX + dxFromLastCommitted,
|
||||
lastCommittedY + dyFromLastCommitted,
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
if (isElbowArrow(multiElement)) {
|
||||
mutateElbowArrow(
|
||||
multiElement,
|
||||
updates,
|
||||
false,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
{
|
||||
isDragging: true,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// update last uncommitted point
|
||||
mutateElement(multiElement, updates, false);
|
||||
}
|
||||
// update last uncommitted point
|
||||
this.scene.mutateElement(
|
||||
multiElement,
|
||||
{
|
||||
points: [
|
||||
...points.slice(0, -1),
|
||||
pointFrom<LocalPoint>(
|
||||
lastCommittedX + dxFromLastCommitted,
|
||||
lastCommittedY + dyFromLastCommitted,
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
isDragging: true,
|
||||
informMutation: false,
|
||||
},
|
||||
);
|
||||
|
||||
// in this path, we're mutating multiElement to reflect
|
||||
// how it will be after adding pointer position as the next point
|
||||
|
@ -6693,7 +6675,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
|
||||
const frame = this.getTopLayerFrameAtSceneCoords({ x, y });
|
||||
|
||||
mutateElement(pendingImageElement, {
|
||||
this.scene.mutateElement(pendingImageElement, {
|
||||
x,
|
||||
y,
|
||||
frameId: frame ? frame.id : null,
|
||||
|
@ -7747,7 +7729,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
multiElement.type === "line" &&
|
||||
isPathALoop(multiElement.points, this.state.zoom.value)
|
||||
) {
|
||||
mutateElement(multiElement, {
|
||||
this.scene.mutateElement(multiElement, {
|
||||
lastCommittedPoint:
|
||||
multiElement.points[multiElement.points.length - 1],
|
||||
});
|
||||
|
@ -7758,7 +7740,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
// Elbow arrows cannot be created by putting down points
|
||||
// only the start and end points can be defined
|
||||
if (isElbowArrow(multiElement) && multiElement.points.length > 1) {
|
||||
mutateElement(multiElement, {
|
||||
this.scene.mutateElement(multiElement, {
|
||||
lastCommittedPoint:
|
||||
multiElement.points[multiElement.points.length - 1],
|
||||
});
|
||||
|
@ -7795,7 +7777,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
}));
|
||||
// clicking outside commit zone → update reference for last committed
|
||||
// point
|
||||
mutateElement(multiElement, {
|
||||
this.scene.mutateElement(multiElement, {
|
||||
lastCommittedPoint: multiElement.points[multiElement.points.length - 1],
|
||||
});
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||
|
@ -7881,7 +7863,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
),
|
||||
};
|
||||
});
|
||||
mutateElement(element, {
|
||||
this.scene.mutateElement(element, {
|
||||
points: [...element.points, pointFrom<LocalPoint>(0, 0)],
|
||||
});
|
||||
const boundElement = getHoveredElementForBinding(
|
||||
|
@ -8463,7 +8445,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
),
|
||||
};
|
||||
|
||||
mutateElement(croppingElement, {
|
||||
this.scene.mutateElement(croppingElement, {
|
||||
crop: nextCrop,
|
||||
});
|
||||
|
||||
|
@ -8660,13 +8642,15 @@ class App extends React.Component<AppProps, AppState> {
|
|||
? newElement.pressures
|
||||
: [...newElement.pressures, event.pressure];
|
||||
|
||||
mutateElement(
|
||||
this.scene.mutateElement(
|
||||
newElement,
|
||||
{
|
||||
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
||||
pressures,
|
||||
},
|
||||
false,
|
||||
{
|
||||
informMutation: false,
|
||||
},
|
||||
);
|
||||
|
||||
this.setState({
|
||||
|
@ -8688,33 +8672,24 @@ class App extends React.Component<AppProps, AppState> {
|
|||
));
|
||||
}
|
||||
|
||||
const mutate = (
|
||||
updates: ElementUpdate<ExcalidrawLinearElement>,
|
||||
options: { isDragging?: boolean } = {},
|
||||
) =>
|
||||
isElbowArrow(newElement)
|
||||
? mutateElbowArrow(
|
||||
newElement,
|
||||
updates as ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||
false,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
options,
|
||||
)
|
||||
: mutateElement(newElement, updates, false);
|
||||
|
||||
if (points.length === 1) {
|
||||
mutate({
|
||||
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
||||
});
|
||||
this.scene.mutateElement(
|
||||
newElement,
|
||||
{
|
||||
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
||||
},
|
||||
{ informMutation: false },
|
||||
);
|
||||
} else if (
|
||||
points.length === 2 ||
|
||||
(points.length > 1 && isElbowArrow(newElement))
|
||||
) {
|
||||
mutate(
|
||||
this.scene.mutateElement(
|
||||
newElement,
|
||||
{
|
||||
points: [...points.slice(0, -1), pointFrom<LocalPoint>(dx, dy)],
|
||||
},
|
||||
{ isDragging: true }
|
||||
{ isDragging: true },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -8925,7 +8900,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
.map((e) => elementsMap.get(e.id))
|
||||
.filter((e) => isElbowArrow(e))
|
||||
.forEach((e) => {
|
||||
!!e && mutateElement(e, {}, true);
|
||||
!!e && this.scene.mutateElement(e, {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8961,11 +8936,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (element) {
|
||||
mutateElbowArrow(
|
||||
this.scene.mutateElement(
|
||||
element as ExcalidrawElbowArrowElement,
|
||||
{},
|
||||
true,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9062,7 +9035,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
? []
|
||||
: [...newElement.pressures, childEvent.pressure];
|
||||
|
||||
mutateElement(newElement, {
|
||||
this.scene.mutateElement(newElement, {
|
||||
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
||||
pressures,
|
||||
lastCommittedPoint: pointFrom<LocalPoint>(dx, dy),
|
||||
|
@ -9109,26 +9082,19 @@ class App extends React.Component<AppProps, AppState> {
|
|||
);
|
||||
|
||||
if (!pointerDownState.drag.hasOccurred && newElement && !multiElement) {
|
||||
const updates = {
|
||||
points: [
|
||||
...newElement.points,
|
||||
pointFrom<LocalPoint>(
|
||||
pointerCoords.x - newElement.x,
|
||||
pointerCoords.y - newElement.y,
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
if (isElbowArrow(newElement)) {
|
||||
mutateElbowArrow(
|
||||
newElement,
|
||||
updates,
|
||||
false,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
} else {
|
||||
mutateElement(newElement, updates, false);
|
||||
}
|
||||
this.scene.mutateElement(
|
||||
newElement,
|
||||
{
|
||||
points: [
|
||||
...newElement.points,
|
||||
pointFrom<LocalPoint>(
|
||||
pointerCoords.x - newElement.x,
|
||||
pointerCoords.y - newElement.y,
|
||||
),
|
||||
],
|
||||
},
|
||||
{ informMutation: false },
|
||||
);
|
||||
|
||||
this.setState({
|
||||
multiElement: newElement,
|
||||
|
@ -9185,7 +9151,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
);
|
||||
|
||||
if (newElement.width < minWidth) {
|
||||
mutateElement(newElement, {
|
||||
this.scene.mutateElement(newElement, {
|
||||
autoResize: true,
|
||||
});
|
||||
}
|
||||
|
@ -9235,7 +9201,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||
}
|
||||
|
||||
if (newElement) {
|
||||
mutateElement(newElement, getNormalizedDimensions(newElement));
|
||||
this.scene.mutateElement(
|
||||
newElement,
|
||||
getNormalizedDimensions(newElement),
|
||||
{
|
||||
informMutation: false,
|
||||
},
|
||||
);
|
||||
// the above does not guarantee the scene to be rendered again, hence the trigger below
|
||||
this.scene.triggerUpdate();
|
||||
}
|
||||
|
@ -9267,7 +9239,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
) {
|
||||
// remove the linear element from all groups
|
||||
// before removing it from the frame as well
|
||||
mutateElement(linearElement, {
|
||||
this.scene.mutateElement(linearElement, {
|
||||
groupIds: [],
|
||||
});
|
||||
|
||||
|
@ -9296,13 +9268,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.state.editingGroupId!,
|
||||
);
|
||||
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
groupIds: element.groupIds.slice(0, index),
|
||||
},
|
||||
false,
|
||||
);
|
||||
this.scene.mutateElement(element, {
|
||||
groupIds: element.groupIds.slice(0, index),
|
||||
}, { informMutation: false });
|
||||
}
|
||||
|
||||
nextElements.forEach((element) => {
|
||||
|
@ -9313,13 +9281,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
element.groupIds[element.groupIds.length - 1],
|
||||
).length < 2
|
||||
) {
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
groupIds: [],
|
||||
},
|
||||
false,
|
||||
);
|
||||
this.scene.mutateElement(element, {
|
||||
groupIds: [],
|
||||
}, { informMutation: false });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -9881,13 +9845,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const dataURL =
|
||||
this.files[fileId]?.dataURL || (await getDataURL(imageFile));
|
||||
|
||||
const imageElement = mutateElement(
|
||||
_imageElement,
|
||||
{
|
||||
fileId,
|
||||
},
|
||||
false,
|
||||
) as NonDeleted<InitializedExcalidrawImageElement>;
|
||||
const imageElement = this.scene.mutateElement(_imageElement, {
|
||||
fileId,
|
||||
}, { informMutation: false }) as NonDeleted<InitializedExcalidrawImageElement>;
|
||||
|
||||
return new Promise<NonDeleted<InitializedExcalidrawImageElement>>(
|
||||
async (resolve, reject) => {
|
||||
|
@ -9952,7 +9912,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
showCursorImagePreview,
|
||||
});
|
||||
} catch (error: any) {
|
||||
mutateElement(imageElement, {
|
||||
this.scene.mutateElement(imageElement, {
|
||||
isDeleted: true,
|
||||
});
|
||||
this.actionManager.executeAction(actionFinalize);
|
||||
|
@ -10098,7 +10058,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
imageElement.height < DRAGGING_THRESHOLD / this.state.zoom.value
|
||||
) {
|
||||
const placeholderSize = 100 / this.state.zoom.value;
|
||||
mutateElement(imageElement, {
|
||||
this.scene.mutateElement(imageElement, {
|
||||
x: imageElement.x - placeholderSize / 2,
|
||||
y: imageElement.y - placeholderSize / 2,
|
||||
width: placeholderSize,
|
||||
|
@ -10132,7 +10092,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const x = imageElement.x + imageElement.width / 2 - width / 2;
|
||||
const y = imageElement.y + imageElement.height / 2 - height / 2;
|
||||
|
||||
mutateElement(imageElement, {
|
||||
this.scene.mutateElement(imageElement, {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
|
@ -10748,7 +10708,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
transformHandleType,
|
||||
);
|
||||
|
||||
mutateElement(
|
||||
this.scene.mutateElement(
|
||||
croppingElement,
|
||||
cropElement(
|
||||
croppingElement,
|
||||
|
|
|
@ -8,7 +8,10 @@ import {
|
|||
isTestEnv,
|
||||
} from "@excalidraw/common";
|
||||
import { isNonDeletedElement } from "@excalidraw/element";
|
||||
import { isFrameLikeElement } from "@excalidraw/element/typeChecks";
|
||||
import {
|
||||
isElbowArrow,
|
||||
isFrameLikeElement,
|
||||
} from "@excalidraw/element/typeChecks";
|
||||
import { getElementsInGroup } from "@excalidraw/element/groups";
|
||||
|
||||
import {
|
||||
|
@ -19,7 +22,13 @@ import {
|
|||
|
||||
import { getSelectedElements } from "@excalidraw/element/selection";
|
||||
|
||||
import type { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||
import {
|
||||
mutateElement,
|
||||
type ElementUpdate,
|
||||
} from "@excalidraw/element/mutateElement";
|
||||
|
||||
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
|
@ -30,15 +39,17 @@ import type {
|
|||
NonDeletedSceneElementsMap,
|
||||
OrderedExcalidrawElement,
|
||||
Ordered,
|
||||
ExcalidrawElbowArrowElement,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type { Assert, SameType } from "@excalidraw/common/utility-types";
|
||||
import type {
|
||||
Assert,
|
||||
Mutable,
|
||||
SameType,
|
||||
} from "@excalidraw/common/utility-types";
|
||||
|
||||
import type { AppState } from "../types";
|
||||
|
||||
type ElementIdKey = InstanceType<typeof LinearElementEditor>["elementId"];
|
||||
type ElementKey = ExcalidrawElement | ElementIdKey;
|
||||
|
||||
type SceneStateCallback = () => void;
|
||||
type SceneStateCallbackRemover = () => void;
|
||||
|
||||
|
@ -102,44 +113,7 @@ const hashSelectionOpts = (
|
|||
// in our codebase
|
||||
export type ExcalidrawElementsIncludingDeleted = readonly ExcalidrawElement[];
|
||||
|
||||
const isIdKey = (elementKey: ElementKey): elementKey is ElementIdKey => {
|
||||
if (typeof elementKey === "string") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
class Scene {
|
||||
// ---------------------------------------------------------------------------
|
||||
// static methods/props
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private static sceneMapByElement = new WeakMap<ExcalidrawElement, Scene>();
|
||||
private static sceneMapById = new Map<string, Scene>();
|
||||
|
||||
static mapElementToScene(elementKey: ElementKey, scene: Scene) {
|
||||
if (isIdKey(elementKey)) {
|
||||
// for cases where we don't have access to the element object
|
||||
// (e.g. restore serialized appState with id references)
|
||||
this.sceneMapById.set(elementKey, scene);
|
||||
} else {
|
||||
this.sceneMapByElement.set(elementKey, scene);
|
||||
// if mapping element objects, also cache the id string when later
|
||||
// looking up by id alone
|
||||
this.sceneMapById.set(elementKey.id, scene);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated pass down `app.scene` and use it directly
|
||||
*/
|
||||
static getScene(elementKey: ElementKey): Scene | null {
|
||||
if (isIdKey(elementKey)) {
|
||||
return this.sceneMapById.get(elementKey) || null;
|
||||
}
|
||||
return this.sceneMapByElement.get(elementKey) || null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// instance methods/props
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -308,7 +282,6 @@ class Scene {
|
|||
nextFrameLikes.push(element);
|
||||
}
|
||||
this.elementsMap.set(element.id, element);
|
||||
Scene.mapElementToScene(element, this);
|
||||
});
|
||||
const nonDeletedElements = getNonDeletedElements(this.elements);
|
||||
this.nonDeletedElements = nonDeletedElements.elements;
|
||||
|
@ -353,12 +326,6 @@ class Scene {
|
|||
this.selectedElementsCache.elements = null;
|
||||
this.selectedElementsCache.cache.clear();
|
||||
|
||||
Scene.sceneMapById.forEach((scene, elementKey) => {
|
||||
if (scene === this) {
|
||||
Scene.sceneMapById.delete(elementKey);
|
||||
}
|
||||
});
|
||||
|
||||
// done not for memory leaks, but to guard against possible late fires
|
||||
// (I guess?)
|
||||
this.callbacks.clear();
|
||||
|
@ -455,6 +422,36 @@ class Scene {
|
|||
// then, check if the id is a group
|
||||
return getElementsInGroup(elementsMap, id);
|
||||
};
|
||||
|
||||
// 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
|
||||
mutateElement<TElement extends Mutable<ExcalidrawElement>>(
|
||||
element: TElement,
|
||||
updates: ElementUpdate<TElement>,
|
||||
options: {
|
||||
informMutation?: boolean;
|
||||
isDragging?: boolean;
|
||||
} = {
|
||||
informMutation: true,
|
||||
},
|
||||
) {
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
updates as ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||
this.getNonDeletedElementsMap(),
|
||||
options,
|
||||
);
|
||||
} else {
|
||||
mutateElement(element, updates);
|
||||
}
|
||||
|
||||
if (options.informMutation) {
|
||||
this.triggerUpdate();
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
export default Scene;
|
||||
|
|
Loading…
Add table
Reference in a new issue