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