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
+159 -106
View File
@@ -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,
@@ -303,6 +300,10 @@ import {
import { isNonDeletedElement } from "@excalidraw/element";
import Scene from "@excalidraw/element/Scene";
import type { ElementUpdate } from "@excalidraw/element/mutateElement";
import type { LocalPoint, Radians } from "@excalidraw/math";
import type {
@@ -328,9 +329,10 @@ import type {
MagicGenerationData,
ExcalidrawNonSelectionElement,
ExcalidrawArrowElement,
ExcalidrawElbowArrowElement,
} from "@excalidraw/element/types";
import type { ValueOf } from "@excalidraw/common/utility-types";
import type { Mutable, ValueOf } from "@excalidraw/common/utility-types";
import {
actionAddToLibrary,
@@ -403,7 +405,6 @@ import {
hasBackground,
isSomeElementSelected,
} from "../scene";
import Scene from "../scene/Scene";
import { getStateForZoom } from "../scene/zoom";
import {
dataURLToFile,
@@ -760,6 +761,7 @@ class App extends React.Component<AppProps, AppState> {
if (excalidrawAPI) {
const api: ExcalidrawImperativeAPI = {
updateScene: this.updateScene,
mutateElement: this.mutateElement,
updateLibrary: this.library.updateLibrary,
addFiles: this.addFiles,
resetScene: this.resetScene,
@@ -1387,7 +1389,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 });
};
@@ -1444,7 +1446,7 @@ class App extends React.Component<AppProps, AppState> {
autoFocus
value={frameNameInEdit}
onChange={(e) => {
mutateElement(f, {
this.scene.mutateElement(f, {
name: e.target.value,
});
}}
@@ -1670,7 +1672,7 @@ class App extends React.Component<AppProps, AppState> {
<Hyperlink
key={firstSelectedElement.id}
element={firstSelectedElement}
elementsMap={allElementsMap}
scene={this.scene}
setAppState={this.setAppState}
onLinkOpen={this.props.onLinkOpen}
setToast={this.setToast}
@@ -1938,16 +1940,20 @@ 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(
this.scene.mutateElement(
frameElement,
{ customData: { generationData: undefined } },
false,
{
customData: { generationData: undefined },
},
{ informMutation: false, isDragging: false },
);
} else {
mutateElement(
this.scene.mutateElement(
frameElement,
{ customData: { generationData: data } },
false,
{
customData: { generationData: data },
},
{ informMutation: false, isDragging: false },
);
}
this.magicGenerations.set(frameElement.id, data);
@@ -2119,7 +2125,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({
@@ -2918,8 +2924,7 @@ class App extends React.Component<AppProps, AppState> {
nonDeletedElementsMap,
),
),
this.scene.getNonDeletedElementsMap(),
this.scene.getNonDeletedElements(),
this.scene,
);
}
@@ -3317,11 +3322,7 @@ class App extends React.Component<AppProps, AppState> {
newElement,
this.scene.getElementsMapIncludingDeleted(),
);
redrawTextBoundingBox(
newElement,
container,
this.scene.getElementsMapIncludingDeleted(),
);
redrawTextBoundingBox(newElement, container, this.scene);
}
});
@@ -3444,7 +3445,11 @@ 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, isDragging: false },
);
y = imageElement.y + imageElement.height + 25;
@@ -3998,6 +4003,17 @@ class App extends React.Component<AppProps, AppState> {
},
);
public mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
element: TElement,
updates: ElementUpdate<TElement>,
informMutation = true,
) => {
return this.scene.mutateElement(element, updates, {
informMutation,
isDragging: false,
});
};
private triggerRender = (
/** force always re-renders canvas even if no change */
force?: boolean,
@@ -4166,9 +4182,9 @@ class App extends React.Component<AppProps, AppState> {
) {
this.flowChartCreator.createNodes(
selectedElements[0],
this.scene.getNonDeletedElementsMap(),
this.state,
getLinkDirectionFromKey(event.key),
this.scene,
);
}
@@ -4410,16 +4426,16 @@ class App extends React.Component<AppProps, AppState> {
}
selectedElements.forEach((element) => {
mutateElement(
this.scene.mutateElement(
element,
{
x: element.x + offsetX,
y: element.y + offsetY,
},
false,
{ informMutation: false, isDragging: false },
);
updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
updateBoundElements(element, this.scene, {
simultaneouslyUpdated: selectedElements,
});
});
@@ -4453,6 +4469,7 @@ class App extends React.Component<AppProps, AppState> {
this.setState({
editingLinearElement: new LinearElementEditor(
selectedElement,
this.scene.getNonDeletedElementsMap(),
),
});
}
@@ -4646,11 +4663,9 @@ class App extends React.Component<AppProps, AppState> {
if (isArrowKey(event.key)) {
bindOrUnbindLinearElements(
this.scene.getSelectedElements(this.state).filter(isLinearElement),
this.scene.getNonDeletedElementsMap(),
this.scene.getNonDeletedElements(),
this.scene,
isBindingEnabled(this.state),
this.state.selectedLinearElement?.selectedPointsIndices ?? [],
this.scene,
this.state.zoom,
);
this.setState({ suggestedBindings: [] });
@@ -4957,7 +4972,7 @@ class App extends React.Component<AppProps, AppState> {
onChange: withBatchedUpdates((nextOriginalText) => {
updateElement(nextOriginalText, false);
if (isNonDeletedElement(element)) {
updateBoundElements(element, this.scene.getNonDeletedElementsMap());
updateBoundElements(element, this.scene);
}
}),
onSubmit: withBatchedUpdates(({ viaKeyboard, nextOriginalText }) => {
@@ -5320,7 +5335,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) {
@@ -5371,7 +5389,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,
@@ -5447,7 +5465,10 @@ class App extends React.Component<AppProps, AppState> {
) {
this.store.shouldCaptureIncrement();
this.setState({
editingLinearElement: new LinearElementEditor(selectedElements[0]),
editingLinearElement: new LinearElementEditor(
selectedElements[0],
this.scene.getNonDeletedElementsMap(),
),
});
return;
} else if (
@@ -5471,7 +5492,11 @@ class App extends React.Component<AppProps, AppState> {
if (midPoint && midPoint > -1) {
this.store.shouldCaptureIncrement();
LinearElementEditor.deleteFixedSegment(selectedElements[0], midPoint);
LinearElementEditor.deleteFixedSegment(
selectedElements[0],
this.scene,
midPoint,
);
const nextCoords = LinearElementEditor.getSegmentMidpointHitCoords(
{
@@ -5854,7 +5879,6 @@ class App extends React.Component<AppProps, AppState> {
scenePointerX,
scenePointerY,
this,
this.scene.getNonDeletedElementsMap(),
);
if (
@@ -5916,7 +5940,7 @@ class App extends React.Component<AppProps, AppState> {
lastPoint,
) >= LINE_CONFIRM_THRESHOLD
) {
mutateElement(
this.scene.mutateElement(
multiElement,
{
points: [
@@ -5924,7 +5948,7 @@ class App extends React.Component<AppProps, AppState> {
pointFrom<LocalPoint>(scenePointerX - rx, scenePointerY - ry),
],
},
false,
{ informMutation: false, isDragging: false },
);
} else {
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
@@ -5940,12 +5964,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, isDragging: false },
);
} else {
const [gridX, gridY] = getGridPoint(
@@ -5977,8 +6001,9 @@ class App extends React.Component<AppProps, AppState> {
if (isPathALoop(points, this.state.zoom.value)) {
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
}
// update last uncommitted point
mutateElement(
this.scene.mutateElement(
multiElement,
{
points: [
@@ -5989,9 +6014,9 @@ class App extends React.Component<AppProps, AppState> {
),
],
},
false,
{
isDragging: true,
informMutation: false,
},
);
@@ -6578,7 +6603,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,
@@ -7633,7 +7658,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],
});
@@ -7644,7 +7669,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],
});
@@ -7681,7 +7706,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);
@@ -7767,7 +7792,7 @@ class App extends React.Component<AppProps, AppState> {
),
};
});
mutateElement(element, {
this.scene.mutateElement(element, {
points: [...element.points, pointFrom<LocalPoint>(0, 0)],
});
const boundElement = getHoveredElementForBinding(
@@ -8017,7 +8042,7 @@ class App extends React.Component<AppProps, AppState> {
index,
gridX,
gridY,
this.scene.getNonDeletedElementsMap(),
this.scene,
);
flushSync(() => {
@@ -8122,7 +8147,7 @@ class App extends React.Component<AppProps, AppState> {
pointerCoords,
this,
!event[KEYS.CTRL_OR_CMD],
elementsMap,
this.scene,
);
if (!ret) {
return;
@@ -8349,7 +8374,7 @@ class App extends React.Component<AppProps, AppState> {
),
};
mutateElement(croppingElement, {
this.scene.mutateElement(croppingElement, {
crop: nextCrop,
});
@@ -8606,13 +8631,16 @@ 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,
isDragging: false,
},
);
this.setState({
@@ -8635,24 +8663,23 @@ class App extends React.Component<AppProps, AppState> {
}
if (points.length === 1) {
mutateElement(
this.scene.mutateElement(
newElement,
{
points: [...points, pointFrom<LocalPoint>(dx, dy)],
},
false,
{ informMutation: false, isDragging: false },
);
} else if (
points.length === 2 ||
(points.length > 1 && isElbowArrow(newElement))
) {
mutateElement(
this.scene.mutateElement(
newElement,
{
points: [...points.slice(0, -1), pointFrom<LocalPoint>(dx, dy)],
},
false,
{ isDragging: true },
{ isDragging: true, informMutation: false },
);
}
@@ -8763,7 +8790,10 @@ class App extends React.Component<AppProps, AppState> {
selectedLinearElement:
elementsWithinSelection.length === 1 &&
isLinearElement(elementsWithinSelection[0])
? new LinearElementEditor(elementsWithinSelection[0])
? new LinearElementEditor(
elementsWithinSelection[0],
this.scene.getNonDeletedElementsMap(),
)
: null,
showHyperlinkPopup:
elementsWithinSelection.length === 1 &&
@@ -8869,7 +8899,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, {});
});
}
}
@@ -8905,7 +8935,10 @@ class App extends React.Component<AppProps, AppState> {
this.scene.getNonDeletedElementsMap(),
);
if (element) {
mutateElement(element, {}, true);
this.scene.mutateElement(
element as ExcalidrawElbowArrowElement,
{},
);
}
}
@@ -8934,7 +8967,6 @@ class App extends React.Component<AppProps, AppState> {
element,
startBindingElement,
endBindingElement,
elementsMap,
this.scene,
);
}
@@ -9001,7 +9033,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),
@@ -9048,15 +9080,20 @@ class App extends React.Component<AppProps, AppState> {
);
if (!pointerDownState.drag.hasOccurred && newElement && !multiElement) {
mutateElement(newElement, {
points: [
...newElement.points,
pointFrom<LocalPoint>(
pointerCoords.x - newElement.x,
pointerCoords.y - newElement.y,
),
],
});
this.scene.mutateElement(
newElement,
{
points: [
...newElement.points,
pointFrom<LocalPoint>(
pointerCoords.x - newElement.x,
pointerCoords.y - newElement.y,
),
],
},
{ informMutation: false, isDragging: false },
);
this.setState({
multiElement: newElement,
newElement,
@@ -9070,8 +9107,7 @@ class App extends React.Component<AppProps, AppState> {
newElement,
this.state,
pointerCoords,
this.scene.getNonDeletedElementsMap(),
this.scene.getNonDeletedElements(),
this.scene,
);
}
this.setState({ suggestedBindings: [], startBoundElement: null });
@@ -9089,7 +9125,10 @@ class App extends React.Component<AppProps, AppState> {
},
prevState,
),
selectedLinearElement: new LinearElementEditor(newElement),
selectedLinearElement: new LinearElementEditor(
newElement,
this.scene.getNonDeletedElementsMap(),
),
}));
} else {
this.setState((prevState) => ({
@@ -9112,7 +9151,7 @@ class App extends React.Component<AppProps, AppState> {
);
if (newElement.width < minWidth) {
mutateElement(newElement, {
this.scene.mutateElement(newElement, {
autoResize: true,
});
}
@@ -9162,7 +9201,14 @@ class App extends React.Component<AppProps, AppState> {
}
if (newElement) {
mutateElement(newElement, getNormalizedDimensions(newElement));
this.scene.mutateElement(
newElement,
getNormalizedDimensions(newElement),
{
informMutation: false,
isDragging: false,
},
);
// the above does not guarantee the scene to be rendered again, hence the trigger below
this.scene.triggerUpdate();
}
@@ -9194,7 +9240,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: [],
});
@@ -9223,12 +9269,12 @@ class App extends React.Component<AppProps, AppState> {
this.state.editingGroupId!,
);
mutateElement(
this.scene.mutateElement(
element,
{
groupIds: element.groupIds.slice(0, index),
},
false,
{ informMutation: false, isDragging: false },
);
}
@@ -9240,12 +9286,12 @@ class App extends React.Component<AppProps, AppState> {
element.groupIds[element.groupIds.length - 1],
).length < 2
) {
mutateElement(
this.scene.mutateElement(
element,
{
groupIds: [],
},
false,
{ informMutation: false, isDragging: false },
);
}
});
@@ -9355,7 +9401,10 @@ class App extends React.Component<AppProps, AppState> {
// the one we've hit
if (selectedElements.length === 1) {
this.setState({
selectedLinearElement: new LinearElementEditor(hitElement),
selectedLinearElement: new LinearElementEditor(
hitElement,
this.scene.getNonDeletedElementsMap(),
),
});
}
}
@@ -9480,7 +9529,10 @@ class App extends React.Component<AppProps, AppState> {
selectedLinearElement:
newSelectedElements.length === 1 &&
isLinearElement(newSelectedElements[0])
? new LinearElementEditor(newSelectedElements[0])
? new LinearElementEditor(
newSelectedElements[0],
this.scene.getNonDeletedElementsMap(),
)
: prevState.selectedLinearElement,
};
});
@@ -9554,7 +9606,10 @@ class App extends React.Component<AppProps, AppState> {
// Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1.
// Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized
prevState.selectedLinearElement?.elementId !== hitElement.id
? new LinearElementEditor(hitElement)
? new LinearElementEditor(
hitElement,
this.scene.getNonDeletedElementsMap(),
)
: prevState.selectedLinearElement,
}));
}
@@ -9647,11 +9702,9 @@ class App extends React.Component<AppProps, AppState> {
bindOrUnbindLinearElements(
linearElements,
this.scene.getNonDeletedElementsMap(),
this.scene.getNonDeletedElements(),
this.scene,
isBindingEnabled(this.state),
this.state.selectedLinearElement?.selectedPointsIndices ?? [],
this.scene,
this.state.zoom,
);
}
@@ -9808,12 +9861,12 @@ class App extends React.Component<AppProps, AppState> {
const dataURL =
this.files[fileId]?.dataURL || (await getDataURL(imageFile));
const imageElement = mutateElement(
const imageElement = this.scene.mutateElement(
_imageElement,
{
fileId,
},
false,
{ informMutation: false, isDragging: false },
) as NonDeleted<InitializedExcalidrawImageElement>;
return new Promise<NonDeleted<InitializedExcalidrawImageElement>>(
@@ -9879,7 +9932,7 @@ class App extends React.Component<AppProps, AppState> {
showCursorImagePreview,
});
} catch (error: any) {
mutateElement(imageElement, {
this.scene.mutateElement(imageElement, {
isDeleted: true,
});
this.actionManager.executeAction(actionFinalize);
@@ -10025,7 +10078,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,
@@ -10059,7 +10112,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,
@@ -10490,7 +10543,10 @@ class App extends React.Component<AppProps, AppState> {
this,
),
selectedLinearElement: isLinearElement(element)
? new LinearElementEditor(element)
? new LinearElementEditor(
element,
this.scene.getNonDeletedElementsMap(),
)
: null,
}
: this.state),
@@ -10523,8 +10579,9 @@ class App extends React.Component<AppProps, AppState> {
height: distance(pointerDownState.origin.y, pointerCoords.y),
shouldMaintainAspectRatio: shouldMaintainAspectRatio(event),
shouldResizeFromCenter: false,
scene: this.scene,
zoom: this.state.zoom.value,
informMutation,
informMutation: false,
});
return;
}
@@ -10588,6 +10645,7 @@ class App extends React.Component<AppProps, AppState> {
: shouldMaintainAspectRatio(event),
shouldResizeFromCenter: shouldResizeFromCenter(event),
zoom: this.state.zoom.value,
scene: this.scene,
widthAspectRatio: aspectRatio,
originOffset: this.state.originSnapOffset,
informMutation,
@@ -10675,7 +10733,7 @@ class App extends React.Component<AppProps, AppState> {
transformHandleType,
);
mutateElement(
this.scene.mutateElement(
croppingElement,
cropElement(
croppingElement,
@@ -10690,16 +10748,12 @@ class App extends React.Component<AppProps, AppState> {
),
);
updateBoundElements(
croppingElement,
this.scene.getNonDeletedElementsMap(),
{
newSize: {
width: croppingElement.width,
height: croppingElement.height,
},
updateBoundElements(croppingElement, this.scene, {
newSize: {
width: croppingElement.width,
height: croppingElement.height,
},
);
});
this.setState({
isCropping: transformHandleType && transformHandleType !== "rotation",
@@ -10813,7 +10867,6 @@ class App extends React.Component<AppProps, AppState> {
pointerDownState.originalElements,
transformHandleType,
selectedElements,
this.scene.getElementsMapIncludingDeleted(),
this.scene,
shouldRotateWithDiscreteAngle(event),
shouldResizeFromCenter(event),
@@ -6,9 +6,10 @@ import {
defaultGetElementLinkFromSelection,
getLinkIdAndTypeFromSelection,
} from "@excalidraw/element/elementLink";
import { mutateElement } from "@excalidraw/element/mutateElement";
import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
import type { ExcalidrawElement } from "@excalidraw/element/types";
import type Scene from "@excalidraw/element/Scene";
import { t } from "../i18n";
import { getSelectedElements } from "../scene";
@@ -21,20 +22,20 @@ import { TrashIcon } from "./icons";
import "./ElementLinkDialog.scss";
import type { AppProps, AppState, UIAppState } from "../types";
const ElementLinkDialog = ({
sourceElementId,
onClose,
elementsMap,
appState,
scene,
generateLinkForSelection = defaultGetElementLinkFromSelection,
}: {
sourceElementId: ExcalidrawElement["id"];
elementsMap: ElementsMap;
appState: UIAppState;
scene: Scene;
onClose?: () => void;
generateLinkForSelection: AppProps["generateLinkForSelection"];
}) => {
const elementsMap = scene.getNonDeletedElementsMap();
const originalLink = elementsMap.get(sourceElementId)?.link ?? null;
const [nextLink, setNextLink] = useState<string | null>(originalLink);
@@ -70,7 +71,7 @@ const ElementLinkDialog = ({
if (nextLink && nextLink !== elementsMap.get(sourceElementId)?.link) {
const elementToLink = elementsMap.get(sourceElementId);
elementToLink &&
mutateElement(elementToLink, {
scene.mutateElement(elementToLink, {
link: nextLink,
});
}
@@ -78,13 +79,13 @@ const ElementLinkDialog = ({
if (!nextLink && linkEdited && sourceElementId) {
const elementToLink = elementsMap.get(sourceElementId);
elementToLink &&
mutateElement(elementToLink, {
scene.mutateElement(elementToLink, {
link: null,
});
}
onClose?.();
}, [sourceElementId, nextLink, elementsMap, linkEdited, onClose]);
}, [sourceElementId, nextLink, elementsMap, linkEdited, scene, onClose]);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
+12 -16
View File
@@ -5,6 +5,7 @@ import {
CLASSES,
DEFAULT_SIDEBAR,
TOOL_TYPE,
arrayToMap,
capitalizeString,
isShallowEqual,
} from "@excalidraw/common";
@@ -17,7 +18,6 @@ import { ShapeCache } from "@excalidraw/element/ShapeCache";
import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
import Scene from "../scene/Scene";
import { actionToggleStats } from "../actions";
import { trackEvent } from "../analytics";
import { isHandToolActive } from "../appState";
@@ -446,22 +446,18 @@ const LayerUI = ({
if (selectedElements.length) {
for (const element of selectedElements) {
mutateElement(
element,
{
[altKey && eyeDropperState.swapPreviewOnAlt
? colorPickerType === "elementBackground"
? "strokeColor"
: "backgroundColor"
: colorPickerType === "elementBackground"
? "backgroundColor"
: "strokeColor"]: color,
},
false,
);
mutateElement(element, arrayToMap(elements), {
[altKey && eyeDropperState.swapPreviewOnAlt
? colorPickerType === "elementBackground"
? "strokeColor"
: "backgroundColor"
: colorPickerType === "elementBackground"
? "backgroundColor"
: "strokeColor"]: color,
});
ShapeCache.delete(element);
}
Scene.getScene(selectedElements[0])?.triggerUpdate();
app.scene.triggerUpdate();
} else if (colorPickerType === "elementBackground") {
setAppState({
currentItemBackgroundColor: color,
@@ -494,7 +490,7 @@ const LayerUI = ({
openDialog: null,
});
}}
elementsMap={app.scene.getNonDeletedElementsMap()}
scene={app.scene}
appState={appState}
generateLinkForSelection={generateLinkForSelection}
/>
+8 -10
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 });
}
}
};
@@ -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 {
@@ -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,
@@ -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,
@@ -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,
);
}
}
@@ -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();
@@ -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,
@@ -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,
);
}
@@ -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,
);
@@ -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,
);
@@ -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]);
+10 -22
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);
}
};
@@ -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<any, AppState>["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.mutateElement(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.mutateElement(element, {
link,
});
updateEmbedValidationStatus(element, false);
@@ -144,7 +145,7 @@ export const Hyperlink = ({
: 1;
const hasLinkChanged =
embeddableLinkCache.get(element.id) !== element.link;
mutateElement(element, {
scene.mutateElement(element, {
...(hasLinkChanged
? {
width:
@@ -169,10 +170,11 @@ export const Hyperlink = ({
}
}
} else {
mutateElement(element, { link });
scene.mutateElement(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.mutateElement(element, { link: null });
setAppState({ showHyperlinkPopup: false });
}, [setAppState, element]);
}, [setAppState, element, scene]);
const onEdit = () => {
trackEvent("hyperlink", "edit", "popup-ui");