mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
Deprecate mutateElement, use scene.mutateElement or mutateElementWIth instead
This commit is contained in:
parent
a9c3b2a4d4
commit
8b2df92012
42 changed files with 560 additions and 652 deletions
|
@ -679,7 +679,7 @@ export const arrayToMap = <T extends { id: string } | string>(
|
|||
return items.reduce((acc: Map<string, T>, element) => {
|
||||
acc.set(typeof element === "string" ? element : element.id, element);
|
||||
return acc;
|
||||
}, new Map());
|
||||
}, new Map() as Map<string, T>);
|
||||
};
|
||||
|
||||
export const arrayToMapWithIndex = <T extends { id: string }>(
|
||||
|
|
|
@ -2,11 +2,10 @@ import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
|||
|
||||
import { updateBoundElements } from "./binding";
|
||||
import { getCommonBoundingBox } from "./bounds";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { getMaximumGroups } from "./groups";
|
||||
|
||||
import type { BoundingBox } from "./bounds";
|
||||
import type { ElementsMap, ExcalidrawElement } from "./types";
|
||||
import type { ExcalidrawElement } from "./types";
|
||||
|
||||
export interface Alignment {
|
||||
position: "start" | "center" | "end";
|
||||
|
@ -15,10 +14,10 @@ export interface Alignment {
|
|||
|
||||
export const alignElements = (
|
||||
selectedElements: ExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
alignment: Alignment,
|
||||
scene: Scene,
|
||||
): ExcalidrawElement[] => {
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
const groups: ExcalidrawElement[][] = getMaximumGroups(
|
||||
selectedElements,
|
||||
elementsMap,
|
||||
|
@ -33,12 +32,13 @@ export const alignElements = (
|
|||
);
|
||||
return group.map((element) => {
|
||||
// update element
|
||||
const updatedEle = mutateElement(element, {
|
||||
const updatedEle = scene.mutateElement(element, {
|
||||
x: element.x + translation.x,
|
||||
y: element.y + translation.y,
|
||||
});
|
||||
|
||||
// update bound elements
|
||||
updateBoundElements(element, scene.getNonDeletedElementsMap(), {
|
||||
updateBoundElements(element, elementsMap, {
|
||||
simultaneouslyUpdated: group,
|
||||
});
|
||||
return updatedEle;
|
||||
|
|
|
@ -50,7 +50,7 @@ import {
|
|||
type Heading,
|
||||
} from "./heading";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { mutateElementWith, mutateElement } from "./mutateElement";
|
||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||
import {
|
||||
isArrowElement,
|
||||
|
@ -66,7 +66,7 @@ import {
|
|||
} from "./typeChecks";
|
||||
|
||||
import { aabbForElement, getElementShape, pointInsideBounds } from "./shapes";
|
||||
import { mutateElbowArrow, updateElbowArrowPoints } from "./elbowArrow";
|
||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
||||
|
||||
import type { Bounds } from "./bounds";
|
||||
import type { ElementUpdate } from "./mutateElement";
|
||||
|
@ -84,7 +84,6 @@ import type {
|
|||
OrderedExcalidrawElement,
|
||||
ExcalidrawElbowArrowElement,
|
||||
FixedPoint,
|
||||
SceneElementsMap,
|
||||
FixedPointBinding,
|
||||
} from "./types";
|
||||
|
||||
|
@ -130,7 +129,6 @@ export const bindOrUnbindLinearElement = (
|
|||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||
endBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
scene: Scene,
|
||||
): void => {
|
||||
const boundToElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
||||
|
@ -142,7 +140,7 @@ export const bindOrUnbindLinearElement = (
|
|||
"start",
|
||||
boundToElementIds,
|
||||
unboundFromElementIds,
|
||||
elementsMap,
|
||||
scene,
|
||||
);
|
||||
bindOrUnbindLinearElementEdge(
|
||||
linearElement,
|
||||
|
@ -151,7 +149,7 @@ export const bindOrUnbindLinearElement = (
|
|||
"end",
|
||||
boundToElementIds,
|
||||
unboundFromElementIds,
|
||||
elementsMap,
|
||||
scene,
|
||||
);
|
||||
|
||||
const onlyUnbound = Array.from(unboundFromElementIds).filter(
|
||||
|
@ -159,7 +157,7 @@ export const bindOrUnbindLinearElement = (
|
|||
);
|
||||
|
||||
getNonDeletedElements(scene, onlyUnbound).forEach((element) => {
|
||||
mutateElement(element, {
|
||||
scene.mutateElement(element, {
|
||||
boundElements: element.boundElements?.filter(
|
||||
(element) =>
|
||||
element.type !== "arrow" || element.id !== linearElement.id,
|
||||
|
@ -177,7 +175,7 @@ const bindOrUnbindLinearElementEdge = (
|
|||
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||
// Is mutated
|
||||
unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
scene: Scene,
|
||||
): void => {
|
||||
// "keep" is for method chaining convenience, a "no-op", so just bail out
|
||||
if (bindableElement === "keep") {
|
||||
|
@ -186,7 +184,7 @@ const bindOrUnbindLinearElementEdge = (
|
|||
|
||||
// null means break the bind, so nothing to consider here
|
||||
if (bindableElement === null) {
|
||||
const unbound = unbindLinearElement(linearElement, startOrEnd);
|
||||
const unbound = unbindLinearElement(linearElement, startOrEnd, scene);
|
||||
if (unbound != null) {
|
||||
unboundFromElementIds.add(unbound);
|
||||
}
|
||||
|
@ -213,12 +211,19 @@ const bindOrUnbindLinearElementEdge = (
|
|||
linearElement,
|
||||
bindableElement,
|
||||
startOrEnd,
|
||||
elementsMap,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
(...args) => scene.mutateElement(...args),
|
||||
);
|
||||
boundToElementIds.add(bindableElement.id);
|
||||
}
|
||||
} else {
|
||||
bindLinearElement(linearElement, bindableElement, startOrEnd, elementsMap);
|
||||
bindLinearElement(
|
||||
linearElement,
|
||||
bindableElement,
|
||||
startOrEnd,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
(...args) => scene.mutateElement(...args),
|
||||
);
|
||||
boundToElementIds.add(bindableElement.id);
|
||||
}
|
||||
};
|
||||
|
@ -362,11 +367,9 @@ const getBindingStrategyForDraggingArrowOrJoints = (
|
|||
|
||||
export const bindOrUnbindLinearElements = (
|
||||
selectedElements: NonDeleted<ExcalidrawLinearElement>[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
scene: Scene,
|
||||
isBindingEnabled: boolean,
|
||||
draggingPoints: readonly number[] | null,
|
||||
scene: Scene,
|
||||
zoom?: AppState["zoom"],
|
||||
): void => {
|
||||
selectedElements.forEach((selectedElement) => {
|
||||
|
@ -376,20 +379,20 @@ export const bindOrUnbindLinearElements = (
|
|||
selectedElement,
|
||||
isBindingEnabled,
|
||||
draggingPoints ?? [],
|
||||
elementsMap,
|
||||
elements,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
scene.getNonDeletedElements(),
|
||||
zoom,
|
||||
)
|
||||
: // The arrow itself (the shaft) or the inner joins are dragged
|
||||
getBindingStrategyForDraggingArrowOrJoints(
|
||||
selectedElement,
|
||||
elementsMap,
|
||||
elements,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
scene.getNonDeletedElements(),
|
||||
isBindingEnabled,
|
||||
zoom,
|
||||
);
|
||||
|
||||
bindOrUnbindLinearElement(selectedElement, start, end, elementsMap, scene);
|
||||
bindOrUnbindLinearElement(selectedElement, start, end, scene);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -429,22 +432,22 @@ export const maybeBindLinearElement = (
|
|||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
appState: AppState,
|
||||
pointerCoords: { x: number; y: number },
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
scene: Scene,
|
||||
): void => {
|
||||
if (appState.startBoundElement != null) {
|
||||
bindLinearElement(
|
||||
linearElement,
|
||||
appState.startBoundElement,
|
||||
"start",
|
||||
elementsMap,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
(...args) => scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
|
||||
const hoveredElement = getHoveredElementForBinding(
|
||||
pointerCoords,
|
||||
elements,
|
||||
elementsMap,
|
||||
scene.getNonDeletedElements(),
|
||||
scene.getNonDeletedElementsMap(),
|
||||
appState.zoom,
|
||||
isElbowArrow(linearElement),
|
||||
isElbowArrow(linearElement),
|
||||
|
@ -458,7 +461,13 @@ export const maybeBindLinearElement = (
|
|||
"end",
|
||||
)
|
||||
) {
|
||||
bindLinearElement(linearElement, hoveredElement, "end", elementsMap);
|
||||
bindLinearElement(
|
||||
linearElement,
|
||||
hoveredElement,
|
||||
"end",
|
||||
scene.getNonDeletedElementsMap(),
|
||||
(...args) => scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -487,7 +496,11 @@ export const bindLinearElement = (
|
|||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
hoveredElement: ExcalidrawBindableElement,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
elementsMap: Map<string, ExcalidrawElement>,
|
||||
mutator: (
|
||||
element: ExcalidrawElement,
|
||||
updates: ElementUpdate<ExcalidrawElement>,
|
||||
) => ExcalidrawElement,
|
||||
): void => {
|
||||
if (!isArrowElement(linearElement)) {
|
||||
return;
|
||||
|
@ -500,7 +513,7 @@ export const bindLinearElement = (
|
|||
linearElement,
|
||||
hoveredElement,
|
||||
startOrEnd,
|
||||
elementsMap,
|
||||
elementsMap as NonDeletedSceneElementsMap,
|
||||
),
|
||||
hoveredElement,
|
||||
),
|
||||
|
@ -513,18 +526,17 @@ export const bindLinearElement = (
|
|||
linearElement,
|
||||
hoveredElement,
|
||||
startOrEnd,
|
||||
elementsMap,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
mutateElement(linearElement, {
|
||||
mutator(linearElement, {
|
||||
[startOrEnd === "start" ? "startBinding" : "endBinding"]: binding,
|
||||
});
|
||||
|
||||
const boundElementsMap = arrayToMap(hoveredElement.boundElements || []);
|
||||
if (!boundElementsMap.has(linearElement.id)) {
|
||||
mutateElement(hoveredElement, {
|
||||
mutator(hoveredElement, {
|
||||
boundElements: (hoveredElement.boundElements || []).concat({
|
||||
id: linearElement.id,
|
||||
type: "arrow",
|
||||
|
@ -566,13 +578,14 @@ const isLinearElementSimple = (
|
|||
const unbindLinearElement = (
|
||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||
startOrEnd: "start" | "end",
|
||||
scene: Scene,
|
||||
): ExcalidrawBindableElement["id"] | null => {
|
||||
const field = startOrEnd === "start" ? "startBinding" : "endBinding";
|
||||
const binding = linearElement[field];
|
||||
if (binding == null) {
|
||||
return null;
|
||||
}
|
||||
mutateElement(linearElement, { [field]: null });
|
||||
scene.mutateElement(linearElement, { [field]: null });
|
||||
return binding.elementId;
|
||||
};
|
||||
|
||||
|
@ -740,7 +753,7 @@ const calculateFocusAndGap = (
|
|||
// in explicitly.
|
||||
export const updateBoundElements = (
|
||||
changedElement: NonDeletedExcalidrawElement,
|
||||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
|
||||
elementsMap: Map<string, ExcalidrawElement>,
|
||||
options?: {
|
||||
simultaneouslyUpdated?: readonly ExcalidrawElement[];
|
||||
newSize?: { width: number; height: number };
|
||||
|
@ -796,20 +809,7 @@ export const updateBoundElements = (
|
|||
|
||||
// `linearElement` is being moved/scaled already, just update the binding
|
||||
if (simultaneouslyUpdatedElementIds.has(element.id)) {
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
bindings as {
|
||||
startBinding: FixedPointBinding;
|
||||
endBinding: FixedPointBinding;
|
||||
},
|
||||
true,
|
||||
elementsMap,
|
||||
);
|
||||
} else {
|
||||
mutateElement(element, bindings, true);
|
||||
}
|
||||
|
||||
mutateElementWith(element, elementsMap, bindings);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -898,7 +898,6 @@ export const getHeadingForElbowArrowSnap = (
|
|||
otherPoint: Readonly<GlobalPoint>,
|
||||
bindableElement: ExcalidrawBindableElement | undefined | null,
|
||||
aabb: Bounds | undefined | null,
|
||||
elementsMap: ElementsMap,
|
||||
origPoint: GlobalPoint,
|
||||
zoom?: AppState["zoom"],
|
||||
): Heading => {
|
||||
|
@ -908,12 +907,7 @@ export const getHeadingForElbowArrowSnap = (
|
|||
return otherPointHeading;
|
||||
}
|
||||
|
||||
const distance = getDistanceForBinding(
|
||||
origPoint,
|
||||
bindableElement,
|
||||
elementsMap,
|
||||
zoom,
|
||||
);
|
||||
const distance = getDistanceForBinding(origPoint, bindableElement, zoom);
|
||||
|
||||
if (!distance) {
|
||||
return vectorToHeading(
|
||||
|
@ -933,7 +927,6 @@ export const getHeadingForElbowArrowSnap = (
|
|||
const getDistanceForBinding = (
|
||||
point: Readonly<GlobalPoint>,
|
||||
bindableElement: ExcalidrawBindableElement,
|
||||
elementsMap: ElementsMap,
|
||||
zoom?: AppState["zoom"],
|
||||
) => {
|
||||
const distance = distanceToBindableElement(bindableElement, point);
|
||||
|
@ -1239,7 +1232,6 @@ const updateBoundPoint = (
|
|||
linearElement,
|
||||
bindableElement,
|
||||
startOrEnd === "startBinding" ? "start" : "end",
|
||||
elementsMap,
|
||||
).fixedPoint;
|
||||
const globalMidPoint = pointFrom<GlobalPoint>(
|
||||
bindableElement.x + bindableElement.width / 2,
|
||||
|
@ -1349,7 +1341,6 @@ export const calculateFixedPointForElbowArrowBinding = (
|
|||
linearElement: NonDeleted<ExcalidrawElbowArrowElement>,
|
||||
hoveredElement: ExcalidrawBindableElement,
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
): { fixedPoint: FixedPoint } => {
|
||||
const bounds = [
|
||||
hoveredElement.x,
|
||||
|
|
|
@ -17,7 +17,6 @@ import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
|
|||
|
||||
import { updateBoundElements } from "./binding";
|
||||
import { getCommonBounds } from "./bounds";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { getPerfectElementSize } from "./sizeHelpers";
|
||||
import { getBoundTextElement } from "./textElement";
|
||||
import { getMinTextElementWidth } from "./textMeasurements";
|
||||
|
@ -104,7 +103,7 @@ export const dragSelectedElements = (
|
|||
);
|
||||
|
||||
elementsToUpdate.forEach((element) => {
|
||||
updateElementCoords(pointerDownState, element, adjustedOffset);
|
||||
updateElementCoords(pointerDownState, element, scene, adjustedOffset);
|
||||
if (!isArrowElement(element)) {
|
||||
// skip arrow labels since we calculate its position during render
|
||||
const textElement = getBoundTextElement(
|
||||
|
@ -112,9 +111,14 @@ export const dragSelectedElements = (
|
|||
scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
if (textElement) {
|
||||
updateElementCoords(pointerDownState, textElement, adjustedOffset);
|
||||
updateElementCoords(
|
||||
pointerDownState,
|
||||
textElement,
|
||||
scene,
|
||||
adjustedOffset,
|
||||
);
|
||||
}
|
||||
updateBoundElements(element, scene.getElementsMapIncludingDeleted(), {
|
||||
updateBoundElements(element, scene.getNonDeletedElementsMap(), {
|
||||
simultaneouslyUpdated: Array.from(elementsToUpdate),
|
||||
});
|
||||
}
|
||||
|
@ -155,6 +159,7 @@ const calculateOffset = (
|
|||
const updateElementCoords = (
|
||||
pointerDownState: PointerDownState,
|
||||
element: NonDeletedExcalidrawElement,
|
||||
scene: Scene,
|
||||
dragOffset: { x: number; y: number },
|
||||
) => {
|
||||
const originalElement =
|
||||
|
@ -163,7 +168,7 @@ const updateElementCoords = (
|
|||
const nextX = originalElement.x + dragOffset.x;
|
||||
const nextY = originalElement.y + dragOffset.y;
|
||||
|
||||
mutateElement(element, {
|
||||
scene.mutateElement(element, {
|
||||
x: nextX,
|
||||
y: nextY,
|
||||
});
|
||||
|
@ -190,6 +195,7 @@ export const dragNewElement = ({
|
|||
shouldMaintainAspectRatio,
|
||||
shouldResizeFromCenter,
|
||||
zoom,
|
||||
scene,
|
||||
widthAspectRatio = null,
|
||||
originOffset = null,
|
||||
informMutation = true,
|
||||
|
@ -205,6 +211,7 @@ export const dragNewElement = ({
|
|||
shouldMaintainAspectRatio: boolean;
|
||||
shouldResizeFromCenter: boolean;
|
||||
zoom: NormalizedZoomValue;
|
||||
scene: Scene;
|
||||
/** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is
|
||||
true */
|
||||
widthAspectRatio?: number | null;
|
||||
|
@ -285,7 +292,7 @@ export const dragNewElement = ({
|
|||
};
|
||||
}
|
||||
|
||||
mutateElement(
|
||||
scene.mutateElement(
|
||||
newElement,
|
||||
{
|
||||
x: newX + (originOffset?.x ?? 0),
|
||||
|
@ -295,7 +302,7 @@ export const dragNewElement = ({
|
|||
...textAutoResize,
|
||||
...imageInitialDimension,
|
||||
},
|
||||
informMutation,
|
||||
{ informMutation },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -22,8 +22,6 @@ import {
|
|||
isDevEnv,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import type { Radians } from "@excalidraw/math";
|
||||
|
||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import {
|
||||
|
@ -47,12 +45,11 @@ import {
|
|||
vectorToHeading,
|
||||
headingForPoint,
|
||||
} from "./heading";
|
||||
import { mutateElement, type ElementUpdate } from "./mutateElement";
|
||||
import { type ElementUpdate } from "./mutateElement";
|
||||
import { isBindableElement, isElbowArrow } from "./typeChecks";
|
||||
import {
|
||||
type ExcalidrawElbowArrowElement,
|
||||
type NonDeletedSceneElementsMap,
|
||||
type SceneElementsMap,
|
||||
} from "./types";
|
||||
|
||||
import { aabbForElement, pointInsideBounds } from "./shapes";
|
||||
|
@ -903,38 +900,6 @@ export const elbowArrowNeedsToGetNormalized = (
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutates an elbow arrow element and renormalizes it's properties if necessary.
|
||||
*/
|
||||
export const mutateElbowArrow = (
|
||||
element: Readonly<ExcalidrawElbowArrowElement>,
|
||||
updates: ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap | ElementsMap,
|
||||
options?: {
|
||||
isDragging?: boolean;
|
||||
},
|
||||
): ElementUpdate<ExcalidrawElbowArrowElement> => {
|
||||
invariant(
|
||||
!isElbowArrow(element),
|
||||
`Element "${element.type}" is not an elbow arrow! Use \`mutateElement\` instead`,
|
||||
);
|
||||
|
||||
if (!elbowArrowNeedsToGetNormalized(element, updates)) {
|
||||
return mutateElement(element, updates);
|
||||
}
|
||||
|
||||
return mutateElement(element, {
|
||||
...updates,
|
||||
angle: 0 as Radians,
|
||||
...updateElbowArrowPoints(
|
||||
element,
|
||||
elementsMap as NonDeletedSceneElementsMap,
|
||||
updates,
|
||||
options,
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -1329,14 +1294,12 @@ const getElbowArrowData = (
|
|||
const startHeading = getBindPointHeading(
|
||||
startGlobalPoint,
|
||||
endGlobalPoint,
|
||||
elementsMap,
|
||||
hoveredStartElement,
|
||||
origStartGlobalPoint,
|
||||
);
|
||||
const endHeading = getBindPointHeading(
|
||||
endGlobalPoint,
|
||||
startGlobalPoint,
|
||||
elementsMap,
|
||||
hoveredEndElement,
|
||||
origEndGlobalPoint,
|
||||
);
|
||||
|
@ -2306,7 +2269,6 @@ const getGlobalPoint = (
|
|||
const getBindPointHeading = (
|
||||
p: GlobalPoint,
|
||||
otherPoint: GlobalPoint,
|
||||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
|
||||
hoveredElement: ExcalidrawBindableElement | null | undefined,
|
||||
origPoint: GlobalPoint,
|
||||
): Heading =>
|
||||
|
@ -2324,7 +2286,6 @@ const getBindPointHeading = (
|
|||
number,
|
||||
],
|
||||
),
|
||||
elementsMap,
|
||||
origPoint,
|
||||
);
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import type {
|
|||
PendingExcalidrawElements,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
|
||||
import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||
|
||||
import { bindLinearElement } from "./binding";
|
||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
||||
import {
|
||||
|
@ -239,6 +241,7 @@ const addNewNode = (
|
|||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
direction: LinkDirection,
|
||||
scene: Scene,
|
||||
) => {
|
||||
const successors = getSuccessors(element, elementsMap, direction);
|
||||
const predeccessors = getPredecessors(element, elementsMap, direction);
|
||||
|
@ -277,6 +280,7 @@ const addNewNode = (
|
|||
elementsMap,
|
||||
direction,
|
||||
appState,
|
||||
scene,
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -290,6 +294,7 @@ export const addNewNodes = (
|
|||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
direction: LinkDirection,
|
||||
scene: Scene,
|
||||
numberOfNodes: number,
|
||||
) => {
|
||||
// always start from 0 and distribute evenly
|
||||
|
@ -355,6 +360,7 @@ export const addNewNodes = (
|
|||
elementsMap,
|
||||
direction,
|
||||
appState,
|
||||
scene,
|
||||
);
|
||||
|
||||
newNodes.push(nextNode);
|
||||
|
@ -370,6 +376,7 @@ const createBindingArrow = (
|
|||
elementsMap: ElementsMap,
|
||||
direction: LinkDirection,
|
||||
appState: AppState,
|
||||
scene: Scene,
|
||||
) => {
|
||||
let startX: number;
|
||||
let startY: number;
|
||||
|
@ -444,13 +451,15 @@ const createBindingArrow = (
|
|||
bindingArrow,
|
||||
startBindingElement,
|
||||
"start",
|
||||
elementsMap as NonDeletedSceneElementsMap,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
(...args) => scene.mutateElement(...args),
|
||||
);
|
||||
bindLinearElement(
|
||||
bindingArrow,
|
||||
endBindingElement,
|
||||
"end",
|
||||
elementsMap as NonDeletedSceneElementsMap,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
(...args) => scene.mutateElement(...args),
|
||||
);
|
||||
|
||||
const changedElements = new Map<string, OrderedExcalidrawElement>();
|
||||
|
@ -635,6 +644,7 @@ export class FlowChartCreator {
|
|||
elementsMap: ElementsMap,
|
||||
appState: AppState,
|
||||
direction: LinkDirection,
|
||||
scene: Scene,
|
||||
) {
|
||||
if (direction !== this.direction) {
|
||||
const { nextNode, bindingArrow } = addNewNode(
|
||||
|
@ -642,6 +652,7 @@ export class FlowChartCreator {
|
|||
elementsMap,
|
||||
appState,
|
||||
direction,
|
||||
scene,
|
||||
);
|
||||
|
||||
this.numberOfNodes = 1;
|
||||
|
@ -655,6 +666,7 @@ export class FlowChartCreator {
|
|||
elementsMap,
|
||||
appState,
|
||||
direction,
|
||||
scene,
|
||||
this.numberOfNodes,
|
||||
);
|
||||
|
||||
|
@ -682,13 +694,9 @@ export class FlowChartCreator {
|
|||
)
|
||||
) {
|
||||
this.pendingNodes = this.pendingNodes.map((node) =>
|
||||
mutateElement(
|
||||
node,
|
||||
{
|
||||
frameId: startNode.frameId,
|
||||
},
|
||||
false,
|
||||
),
|
||||
mutateElement(node, {
|
||||
frameId: startNode.frameId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { generateNKeysBetween } from "fractional-indexing";
|
|||
|
||||
import { arrayToMap } from "@excalidraw/common";
|
||||
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { mutateElementWith } from "./mutateElement";
|
||||
import { getBoundTextElement } from "./textElement";
|
||||
import { hasBoundTextElement } from "./typeChecks";
|
||||
|
||||
|
@ -176,7 +176,7 @@ export const syncMovedIndices = (
|
|||
|
||||
// split mutation so we don't end up in an incosistent state
|
||||
for (const [element, update] of elementsUpdates) {
|
||||
mutateElement(element, update, false);
|
||||
mutateElementWith(element, arrayToMap(elements), update);
|
||||
}
|
||||
} catch (e) {
|
||||
// fallback to default sync
|
||||
|
@ -197,7 +197,7 @@ export const syncInvalidIndices = (
|
|||
const indicesGroups = getInvalidIndicesGroups(elements);
|
||||
const elementsUpdates = generateIndices(elements, indicesGroups);
|
||||
for (const [element, update] of elementsUpdates) {
|
||||
mutateElement(element, update, false);
|
||||
mutateElementWith(element, arrayToMap(elements), update);
|
||||
}
|
||||
|
||||
return elements as OrderedExcalidrawElement[];
|
||||
|
|
|
@ -57,13 +57,9 @@ export const bindElementsToFramesAfterDuplication = (
|
|||
if (nextElementId) {
|
||||
const nextElement = nextElementMap.get(nextElementId);
|
||||
if (nextElement) {
|
||||
mutateElement(
|
||||
nextElement,
|
||||
{
|
||||
frameId: nextFrameId ?? element.frameId,
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(nextElement, {
|
||||
frameId: nextFrameId ?? element.frameId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -567,13 +563,9 @@ export const addElementsToFrame = <T extends ElementsMapOrArray>(
|
|||
}
|
||||
|
||||
for (const element of finalElementsToAdd) {
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
frameId: frame.id,
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(element, {
|
||||
frameId: frame.id,
|
||||
});
|
||||
}
|
||||
|
||||
return allElements;
|
||||
|
@ -611,13 +603,9 @@ export const removeElementsFromFrame = (
|
|||
}
|
||||
|
||||
for (const [, element] of _elementsToRemove) {
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
frameId: null,
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(element, {
|
||||
frameId: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -48,10 +48,8 @@ import {
|
|||
getMinMaxXYFromCurvePathOps,
|
||||
} from "./bounds";
|
||||
|
||||
import { mutateElbowArrow, updateElbowArrowPoints } from "./elbowArrow";
|
||||
|
||||
import { headingIsHorizontal, vectorToHeading } from "./heading";
|
||||
import { bumpVersion, mutateElement } from "./mutateElement";
|
||||
import { mutateElementWith, mutateElement } from "./mutateElement";
|
||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||
import {
|
||||
isBindingElement,
|
||||
|
@ -125,15 +123,17 @@ export class LinearElementEditor {
|
|||
public readonly segmentMidPointHoveredCoords: GlobalPoint | null;
|
||||
public readonly elbowed: boolean;
|
||||
|
||||
constructor(element: NonDeleted<ExcalidrawLinearElement>) {
|
||||
constructor(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
) {
|
||||
this.elementId = element.id as string & {
|
||||
_brand: "excalidrawLinearElementId";
|
||||
};
|
||||
if (!pointsEqual(element.points[0], pointFrom(0, 0))) {
|
||||
console.error("Linear element is not normalized", Error().stack);
|
||||
LinearElementEditor.normalizePoints(element);
|
||||
LinearElementEditor.normalizePoints(element, elementsMap);
|
||||
}
|
||||
|
||||
this.selectedPointsIndices = null;
|
||||
this.lastUncommittedPoint = null;
|
||||
this.isDragging = false;
|
||||
|
@ -796,7 +796,7 @@ export class LinearElementEditor {
|
|||
linearElementEditor.lastUncommittedPoint == null &&
|
||||
!isElbowArrow(element)
|
||||
) {
|
||||
mutateElement(element, {
|
||||
scene.mutateElement(element, {
|
||||
points: [
|
||||
...element.points,
|
||||
LinearElementEditor.createPointAt(
|
||||
|
@ -862,7 +862,6 @@ export class LinearElementEditor {
|
|||
element,
|
||||
startBindingElement,
|
||||
endBindingElement,
|
||||
elementsMap,
|
||||
scene,
|
||||
);
|
||||
}
|
||||
|
@ -1161,23 +1160,26 @@ export class LinearElementEditor {
|
|||
y: element.y + offsetY,
|
||||
};
|
||||
}
|
||||
|
||||
// element-mutating methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static normalizePoints(element: NonDeleted<ExcalidrawLinearElement>) {
|
||||
mutateElement(element, LinearElementEditor.getNormalizedPoints(element));
|
||||
static normalizePoints(
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elementsMap: ElementsMap,
|
||||
) {
|
||||
mutateElementWith(
|
||||
element,
|
||||
elementsMap,
|
||||
LinearElementEditor.getNormalizedPoints(element),
|
||||
);
|
||||
}
|
||||
|
||||
static duplicateSelectedPoints(
|
||||
appState: AppState,
|
||||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
|
||||
): AppState {
|
||||
static duplicateSelectedPoints(appState: AppState, scene: Scene): AppState {
|
||||
invariant(
|
||||
appState.editingLinearElement,
|
||||
"Not currently editing a linear element",
|
||||
);
|
||||
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
const { selectedPointsIndices, elementId } = appState.editingLinearElement;
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
|
||||
|
@ -1220,12 +1222,7 @@ export class LinearElementEditor {
|
|||
return acc;
|
||||
}, []);
|
||||
|
||||
const updates = { points: nextPoints };
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(element, updates, true, elementsMap);
|
||||
} else {
|
||||
mutateElement(element, updates);
|
||||
}
|
||||
scene.mutateElement(element, { points: nextPoints });
|
||||
|
||||
// temp hack to ensure the line doesn't move when adding point to the end,
|
||||
// potentially expanding the bounding box
|
||||
|
@ -1400,8 +1397,9 @@ export class LinearElementEditor {
|
|||
pointerCoords: PointerCoords,
|
||||
app: AppClassProperties,
|
||||
snapToGrid: boolean,
|
||||
elementsMap: ElementsMap,
|
||||
scene: Scene,
|
||||
) {
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElementEditor.elementId,
|
||||
elementsMap,
|
||||
|
@ -1431,12 +1429,7 @@ export class LinearElementEditor {
|
|||
...element.points.slice(segmentMidpoint.index!),
|
||||
];
|
||||
|
||||
const updates = { points };
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(element, updates, true, elementsMap);
|
||||
} else {
|
||||
mutateElement(element, updates);
|
||||
}
|
||||
scene.mutateElement(element, { points });
|
||||
|
||||
ret.pointerDownState = {
|
||||
...linearElementEditor.pointerDownState,
|
||||
|
@ -1488,28 +1481,10 @@ export class LinearElementEditor {
|
|||
|
||||
updates.points = Array.from(nextPoints);
|
||||
|
||||
if (!options?.sceneElementsMap) {
|
||||
mutateElbowArrow(element, updates, true, options?.sceneElementsMap!, {
|
||||
isDragging: options?.isDragging,
|
||||
});
|
||||
} else {
|
||||
// The element is not in the scene, so we need to use the provided
|
||||
// scene map.
|
||||
Object.assign(element, {
|
||||
...updates,
|
||||
angle: 0 as Radians,
|
||||
|
||||
...updateElbowArrowPoints(
|
||||
element,
|
||||
options.sceneElementsMap,
|
||||
updates,
|
||||
{
|
||||
isDragging: options?.isDragging,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
bumpVersion(element);
|
||||
// TODO_SCENE: fix
|
||||
mutateElementWith(element, options?.sceneElementsMap!, updates, {
|
||||
isDragging: options?.isDragging,
|
||||
});
|
||||
} else {
|
||||
const nextCoords = getElementPointsCoords(element, nextPoints);
|
||||
const prevCoords = getElementPointsCoords(element, element.points);
|
||||
|
@ -1790,8 +1765,9 @@ export class LinearElementEditor {
|
|||
index: number,
|
||||
x: number,
|
||||
y: number,
|
||||
elementsMap: ElementsMap,
|
||||
scene: Scene,
|
||||
): LinearElementEditor {
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
const element = LinearElementEditor.getElement(
|
||||
linearElement.elementId,
|
||||
elementsMap,
|
||||
|
@ -1834,14 +1810,9 @@ export class LinearElementEditor {
|
|||
.map((segment) => segment.index)
|
||||
.reduce((count, idx) => (idx < index ? count + 1 : count), 0);
|
||||
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
{
|
||||
fixedSegments: nextFixedSegments,
|
||||
},
|
||||
true,
|
||||
elementsMap,
|
||||
);
|
||||
scene.mutateElement(element, {
|
||||
fixedSegments: nextFixedSegments,
|
||||
});
|
||||
|
||||
const point = pointFrom<GlobalPoint>(
|
||||
element.x +
|
||||
|
@ -1873,19 +1844,14 @@ export class LinearElementEditor {
|
|||
|
||||
static deleteFixedSegment(
|
||||
element: ExcalidrawElbowArrowElement,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
scene: Scene,
|
||||
index: number,
|
||||
): void {
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
{
|
||||
fixedSegments: element.fixedSegments?.filter(
|
||||
(segment) => segment.index !== index,
|
||||
),
|
||||
},
|
||||
true,
|
||||
elementsMap,
|
||||
);
|
||||
scene.mutateElement(element, {
|
||||
fixedSegments: element.fixedSegments?.filter(
|
||||
(segment) => segment.index !== index,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,16 +2,24 @@ import {
|
|||
getSizeFromPoints,
|
||||
randomInteger,
|
||||
getUpdatedTimestamp,
|
||||
invariant,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import type { Radians } from "@excalidraw/math";
|
||||
|
||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { ShapeCache } from "./ShapeCache";
|
||||
|
||||
import { elbowArrowNeedsToGetNormalized } from "./elbowArrow";
|
||||
import {
|
||||
elbowArrowNeedsToGetNormalized,
|
||||
updateElbowArrowPoints,
|
||||
} from "./elbowArrow";
|
||||
|
||||
import type { ExcalidrawElement } from "./types";
|
||||
import type {
|
||||
ExcalidrawElbowArrowElement,
|
||||
ExcalidrawElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "./types";
|
||||
|
||||
export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
|
||||
Partial<TElement>,
|
||||
|
@ -20,8 +28,48 @@ export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
|
|||
|
||||
// This function tracks updates of text elements for the purposes for collaboration.
|
||||
// The version is used to compare updates when more than one user is working in
|
||||
// the same drawing. Note: this will trigger the component to update. Make sure you
|
||||
// are calling it either from a React event handler or within unstable_batchedUpdates().
|
||||
// the same drawing. Note: this won't trigger the component to update, unlike `scene.mutateElement`
|
||||
export const mutateElementWith = <TElement extends Mutable<ExcalidrawElement>>(
|
||||
element: TElement,
|
||||
elementsMap: Map<string, ExcalidrawElement>,
|
||||
updates: ElementUpdate<TElement>,
|
||||
options?: {
|
||||
isDragging?: boolean;
|
||||
},
|
||||
) => {
|
||||
if (
|
||||
elbowArrowNeedsToGetNormalized(
|
||||
element,
|
||||
updates as ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||
)
|
||||
) {
|
||||
const normalizedUpdates = {
|
||||
...updates,
|
||||
angle: 0 as Radians,
|
||||
...updateElbowArrowPoints(
|
||||
element as ExcalidrawElbowArrowElement,
|
||||
elementsMap as NonDeletedSceneElementsMap,
|
||||
updates as ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||
options,
|
||||
),
|
||||
} as ElementUpdate<ExcalidrawElbowArrowElement>;
|
||||
|
||||
return mutateElement(
|
||||
element as ExcalidrawElbowArrowElement,
|
||||
normalizedUpdates,
|
||||
);
|
||||
}
|
||||
|
||||
return mutateElement(element, updates);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function tracks updates of text elements for the purposes for collaboration.
|
||||
* The version is used to compare updates when more than one user is working in
|
||||
* the same drawing.
|
||||
*
|
||||
* @deprecated Use `scene.mutateElement` as direct equivalent, or `mutateElementWith` in case you don't need to trigger component update.
|
||||
*/
|
||||
export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
||||
element: TElement,
|
||||
updates: ElementUpdate<TElement>,
|
||||
|
@ -30,18 +78,7 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||
|
||||
// casting to any because can't use `in` operator
|
||||
// (see https://github.com/microsoft/TypeScript/issues/21732)
|
||||
const { points, fileId, fixedSegments, startBinding, endBinding } =
|
||||
updates as any;
|
||||
|
||||
invariant(
|
||||
elbowArrowNeedsToGetNormalized(element, {
|
||||
points,
|
||||
fixedSegments,
|
||||
startBinding,
|
||||
endBinding,
|
||||
}),
|
||||
"Elbow arrow should get normalized! Use `mutateElbowArrow` instead.",
|
||||
);
|
||||
const { points, fileId } = updates as any;
|
||||
|
||||
if (typeof points !== "undefined") {
|
||||
updates = { ...getSizeFromPoints(points), ...updates };
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
getElementBounds,
|
||||
} from "./bounds";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { mutateElementWith, mutateElement } from "./mutateElement";
|
||||
import {
|
||||
getBoundTextElement,
|
||||
getBoundTextElementId,
|
||||
|
@ -60,8 +60,6 @@ import {
|
|||
|
||||
import { isInGroup } from "./groups";
|
||||
|
||||
import { mutateElbowArrow } from "./elbowArrow";
|
||||
|
||||
import type { BoundingBox } from "./bounds";
|
||||
import type {
|
||||
MaybeTransformHandleType,
|
||||
|
@ -76,7 +74,6 @@ import type {
|
|||
ExcalidrawTextElementWithContainer,
|
||||
ExcalidrawImageElement,
|
||||
ElementsMap,
|
||||
SceneElementsMap,
|
||||
ExcalidrawElbowArrowElement,
|
||||
} from "./types";
|
||||
|
||||
|
@ -85,7 +82,6 @@ export const transformElements = (
|
|||
originalElements: PointerDownState["originalElements"],
|
||||
transformHandleType: MaybeTransformHandleType,
|
||||
selectedElements: readonly NonDeletedExcalidrawElement[],
|
||||
elementsMap: SceneElementsMap,
|
||||
scene: Scene,
|
||||
shouldRotateWithDiscreteAngle: boolean,
|
||||
shouldResizeFromCenter: boolean,
|
||||
|
@ -95,13 +91,13 @@ export const transformElements = (
|
|||
centerX: number,
|
||||
centerY: number,
|
||||
): boolean => {
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
if (selectedElements.length === 1) {
|
||||
const [element] = selectedElements;
|
||||
if (transformHandleType === "rotation") {
|
||||
if (!isElbowArrow(element)) {
|
||||
rotateSingleElement(
|
||||
element,
|
||||
elementsMap,
|
||||
scene,
|
||||
pointerX,
|
||||
pointerY,
|
||||
|
@ -113,7 +109,7 @@ export const transformElements = (
|
|||
resizeSingleTextElement(
|
||||
originalElements,
|
||||
element,
|
||||
elementsMap,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
transformHandleType,
|
||||
shouldResizeFromCenter,
|
||||
pointerX,
|
||||
|
@ -123,7 +119,7 @@ export const transformElements = (
|
|||
return true;
|
||||
} else if (transformHandleType) {
|
||||
const elementId = selectedElements[0].id;
|
||||
const latestElement = elementsMap.get(elementId);
|
||||
const latestElement = scene.getNonDeletedElementsMap().get(elementId);
|
||||
const origElement = originalElements.get(elementId);
|
||||
|
||||
if (latestElement && origElement) {
|
||||
|
@ -147,8 +143,8 @@ export const transformElements = (
|
|||
nextHeight,
|
||||
latestElement,
|
||||
origElement,
|
||||
elementsMap,
|
||||
originalElements,
|
||||
scene,
|
||||
transformHandleType,
|
||||
{
|
||||
shouldMaintainAspectRatio,
|
||||
|
@ -163,7 +159,6 @@ export const transformElements = (
|
|||
rotateMultipleElements(
|
||||
originalElements,
|
||||
selectedElements,
|
||||
elementsMap,
|
||||
scene,
|
||||
pointerX,
|
||||
pointerY,
|
||||
|
@ -212,13 +207,15 @@ export const transformElements = (
|
|||
|
||||
const rotateSingleElement = (
|
||||
element: NonDeletedExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
scene: Scene,
|
||||
pointerX: number,
|
||||
pointerY: number,
|
||||
shouldRotateWithDiscreteAngle: boolean,
|
||||
) => {
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(
|
||||
element,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
const cx = (x1 + x2) / 2;
|
||||
const cy = (y1 + y2) / 2;
|
||||
let angle: Radians;
|
||||
|
@ -235,13 +232,13 @@ const rotateSingleElement = (
|
|||
}
|
||||
const boundTextElementId = getBoundTextElementId(element);
|
||||
|
||||
mutateElement(element, { angle });
|
||||
scene.mutateElement(element, { angle });
|
||||
if (boundTextElementId) {
|
||||
const textElement =
|
||||
scene.getElement<ExcalidrawTextElementWithContainer>(boundTextElementId);
|
||||
|
||||
if (textElement && !isArrowElement(element)) {
|
||||
mutateElement(textElement, { angle });
|
||||
scene.mutateElement(textElement, { angle });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -517,7 +514,6 @@ const resizeSingleTextElement = (
|
|||
const rotateMultipleElements = (
|
||||
originalElements: PointerDownState["originalElements"],
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
elementsMap: SceneElementsMap,
|
||||
scene: Scene,
|
||||
pointerX: number,
|
||||
pointerY: number,
|
||||
|
@ -525,6 +521,7 @@ const rotateMultipleElements = (
|
|||
centerX: number,
|
||||
centerY: number,
|
||||
) => {
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
let centerAngle =
|
||||
(5 * Math.PI) / 2 + Math.atan2(pointerY - centerY, pointerX - centerX);
|
||||
if (shouldRotateWithDiscreteAngle) {
|
||||
|
@ -547,24 +544,15 @@ const rotateMultipleElements = (
|
|||
|
||||
if (isElbowArrow(element)) {
|
||||
// Needed to re-route the arrow
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
{
|
||||
points: getArrowLocalFixedPoints(element, elementsMap),
|
||||
},
|
||||
false,
|
||||
elementsMap,
|
||||
);
|
||||
mutateElementWith(element, elementsMap, {
|
||||
points: getArrowLocalFixedPoints(element, elementsMap),
|
||||
});
|
||||
} else {
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
x: element.x + (rotatedCX - cx),
|
||||
y: element.y + (rotatedCY - cy),
|
||||
angle: normalizeRadians((centerAngle + origAngle) as Radians),
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(element, {
|
||||
x: element.x + (rotatedCX - cx),
|
||||
y: element.y + (rotatedCY - cy),
|
||||
angle: normalizeRadians((centerAngle + origAngle) as Radians),
|
||||
});
|
||||
}
|
||||
|
||||
updateBoundElements(element, elementsMap, {
|
||||
|
@ -573,15 +561,11 @@ const rotateMultipleElements = (
|
|||
|
||||
const boundText = getBoundTextElement(element, elementsMap);
|
||||
if (boundText && !isArrowElement(element)) {
|
||||
mutateElement(
|
||||
boundText,
|
||||
{
|
||||
x: boundText.x + (rotatedCX - cx),
|
||||
y: boundText.y + (rotatedCY - cy),
|
||||
angle: normalizeRadians((centerAngle + origAngle) as Radians),
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(boundText, {
|
||||
x: boundText.x + (rotatedCX - cx),
|
||||
y: boundText.y + (rotatedCY - cy),
|
||||
angle: normalizeRadians((centerAngle + origAngle) as Radians),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -826,8 +810,8 @@ export const resizeSingleElement = (
|
|||
nextHeight: number,
|
||||
latestElement: ExcalidrawElement,
|
||||
origElement: ExcalidrawElement,
|
||||
elementsMap: ElementsMap,
|
||||
originalElementsMap: ElementsMap,
|
||||
scene: Scene,
|
||||
handleDirection: TransformHandleDirection,
|
||||
{
|
||||
shouldInformMutation = true,
|
||||
|
@ -840,7 +824,10 @@ export const resizeSingleElement = (
|
|||
} = {},
|
||||
) => {
|
||||
let boundTextFont: { fontSize?: number } = {};
|
||||
const boundTextElement = getBoundTextElement(latestElement, elementsMap);
|
||||
const boundTextElement = getBoundTextElement(
|
||||
latestElement,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
|
||||
if (boundTextElement) {
|
||||
const stateOfBoundTextElementAtResize = originalElementsMap.get(
|
||||
|
@ -860,7 +847,7 @@ export const resizeSingleElement = (
|
|||
|
||||
const nextFont = measureFontSizeFromWidth(
|
||||
boundTextElement,
|
||||
elementsMap,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
getBoundTextMaxWidth(updatedElement, boundTextElement),
|
||||
);
|
||||
if (nextFont === null) {
|
||||
|
@ -939,7 +926,7 @@ export const resizeSingleElement = (
|
|||
}
|
||||
|
||||
if ("scale" in latestElement && "scale" in origElement) {
|
||||
mutateElement(latestElement, {
|
||||
scene.mutateElement(latestElement, {
|
||||
scale: [
|
||||
// defaulting because scaleX/Y can be 0/-0
|
||||
(Math.sign(nextWidth) || origElement.scale[0]) * origElement.scale[0],
|
||||
|
@ -974,21 +961,23 @@ export const resizeSingleElement = (
|
|||
...rescaledPoints,
|
||||
};
|
||||
|
||||
mutateElement(latestElement, updates, shouldInformMutation);
|
||||
scene.mutateElement(latestElement, updates, {
|
||||
informMutation: shouldInformMutation,
|
||||
});
|
||||
|
||||
updateBoundElements(latestElement, elementsMap as SceneElementsMap, {
|
||||
updateBoundElements(latestElement, scene.getNonDeletedElementsMap(), {
|
||||
// TODO: confirm with MARK if this actually makes sense
|
||||
newSize: { width: nextWidth, height: nextHeight },
|
||||
});
|
||||
|
||||
if (boundTextElement && boundTextFont != null) {
|
||||
mutateElement(boundTextElement, {
|
||||
scene.mutateElement(boundTextElement, {
|
||||
fontSize: boundTextFont.fontSize,
|
||||
});
|
||||
}
|
||||
handleBindTextResize(
|
||||
latestElement,
|
||||
elementsMap,
|
||||
scene.getNonDeletedElementsMap(),
|
||||
handleDirection,
|
||||
shouldMaintainAspectRatio,
|
||||
);
|
||||
|
@ -1534,26 +1523,22 @@ export const resizeMultipleElements = (
|
|||
} of elementsAndUpdates) {
|
||||
const { width, height, angle } = update;
|
||||
|
||||
scene.mutateElement(element, update, false, {
|
||||
scene.mutateElement(element, update, {
|
||||
// needed for the fixed binding point udpate to take effect
|
||||
isDragging: true,
|
||||
});
|
||||
|
||||
updateBoundElements(element, elementsMap as SceneElementsMap, {
|
||||
updateBoundElements(element, scene.getNonDeletedElementsMap(), {
|
||||
simultaneouslyUpdated: elementsToUpdate,
|
||||
newSize: { width, height },
|
||||
});
|
||||
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement && boundTextFontSize) {
|
||||
scene.mutateElement(
|
||||
boundTextElement,
|
||||
{
|
||||
fontSize: boundTextFontSize,
|
||||
angle: isLinearElement(element) ? undefined : angle,
|
||||
},
|
||||
false,
|
||||
);
|
||||
scene.mutateElement(boundTextElement, {
|
||||
fontSize: boundTextFontSize,
|
||||
angle: isLinearElement(element) ? undefined : angle,
|
||||
});
|
||||
handleBindTextResize(element, elementsMap, handleDirection, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
updateOriginalContainerCache,
|
||||
} from "./containerCache";
|
||||
import { LinearElementEditor } from "./linearElementEditor";
|
||||
|
||||
import { mutateElement } from "./mutateElement";
|
||||
import { measureText } from "./textMeasurements";
|
||||
import { wrapText } from "./textWrapping";
|
||||
|
@ -26,6 +27,8 @@ import {
|
|||
isTextElement,
|
||||
} from "./typeChecks";
|
||||
|
||||
import type { ElementUpdate } from "./mutateElement";
|
||||
|
||||
import type { MaybeTransformHandleType } from "./transformHandles";
|
||||
import type {
|
||||
ElementsMap,
|
||||
|
@ -41,7 +44,10 @@ export const redrawTextBoundingBox = (
|
|||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawElement | null,
|
||||
elementsMap: ElementsMap,
|
||||
informMutation = true,
|
||||
mutator: (
|
||||
element: ExcalidrawElement,
|
||||
updates: ElementUpdate<ExcalidrawElement>,
|
||||
) => ExcalidrawElement,
|
||||
) => {
|
||||
let maxWidth = undefined;
|
||||
const boundTextUpdates = {
|
||||
|
@ -90,7 +96,7 @@ export const redrawTextBoundingBox = (
|
|||
metrics.height,
|
||||
container.type,
|
||||
);
|
||||
mutateElement(container, { height: nextHeight }, informMutation);
|
||||
mutator(container, { height: nextHeight });
|
||||
updateOriginalContainerCache(container.id, nextHeight);
|
||||
}
|
||||
if (metrics.width > maxContainerWidth) {
|
||||
|
@ -98,7 +104,7 @@ export const redrawTextBoundingBox = (
|
|||
metrics.width,
|
||||
container.type,
|
||||
);
|
||||
mutateElement(container, { width: nextWidth }, informMutation);
|
||||
mutator(container, { width: nextWidth });
|
||||
}
|
||||
const updatedTextElement = {
|
||||
...textElement,
|
||||
|
@ -113,7 +119,7 @@ export const redrawTextBoundingBox = (
|
|||
boundTextUpdates.y = y;
|
||||
}
|
||||
|
||||
mutateElement(textElement, boundTextUpdates, informMutation);
|
||||
mutator(textElement, boundTextUpdates);
|
||||
};
|
||||
|
||||
export const handleBindTextResize = (
|
||||
|
|
|
@ -188,8 +188,12 @@ describe("elbow arrow routing", () => {
|
|||
scene.insertElement(rectangle2);
|
||||
scene.insertElement(arrow);
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
bindLinearElement(arrow, rectangle1, "start", elementsMap);
|
||||
bindLinearElement(arrow, rectangle2, "end", elementsMap);
|
||||
bindLinearElement(arrow, rectangle1, "start", elementsMap, (...args) =>
|
||||
scene.mutateElement(...args),
|
||||
);
|
||||
bindLinearElement(arrow, rectangle2, "end", elementsMap, (...args) =>
|
||||
scene.mutateElement(...args),
|
||||
);
|
||||
|
||||
expect(arrow.startBinding).not.toBe(null);
|
||||
expect(arrow.endBinding).not.toBe(null);
|
||||
|
|
|
@ -333,7 +333,7 @@ describe("line element", () => {
|
|||
element,
|
||||
element,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.app.scene,
|
||||
"ne",
|
||||
);
|
||||
|
||||
|
@ -369,7 +369,7 @@ describe("line element", () => {
|
|||
element,
|
||||
element,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.app.scene,
|
||||
"se",
|
||||
);
|
||||
|
||||
|
@ -424,7 +424,7 @@ describe("line element", () => {
|
|||
element,
|
||||
element,
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.app.scene.getNonDeletedElementsMap(),
|
||||
h.app.scene,
|
||||
"e",
|
||||
{
|
||||
shouldResizeFromCenter: true,
|
||||
|
|
|
@ -50,14 +50,8 @@ const alignSelectedElements = (
|
|||
alignment: Alignment,
|
||||
) => {
|
||||
const selectedElements = app.scene.getSelectedElements(appState);
|
||||
const elementsMap = arrayToMap(elements);
|
||||
|
||||
const updatedElements = alignElements(
|
||||
selectedElements,
|
||||
elementsMap,
|
||||
alignment,
|
||||
app.scene,
|
||||
);
|
||||
const updatedElements = alignElements(selectedElements, alignment, app.scene);
|
||||
|
||||
const updatedElementsMap = arrayToMap(updatedElements);
|
||||
|
||||
|
|
|
@ -21,27 +21,22 @@ import {
|
|||
|
||||
import {
|
||||
hasBoundTextElement,
|
||||
isElbowArrow,
|
||||
isTextBindableContainer,
|
||||
isTextElement,
|
||||
isUsingAdaptiveRadius,
|
||||
} from "@excalidraw/element/typeChecks";
|
||||
|
||||
import { mutateElement } from "@excalidraw/element/mutateElement";
|
||||
import { measureText } from "@excalidraw/element/textMeasurements";
|
||||
|
||||
import { syncMovedIndices } from "@excalidraw/element/fractionalIndex";
|
||||
|
||||
import { newElement } from "@excalidraw/element/newElement";
|
||||
|
||||
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextContainer,
|
||||
ExcalidrawTextElement,
|
||||
FixedPointBinding,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||
|
@ -81,7 +76,7 @@ export const actionUnbindText = register({
|
|||
boundTextElement,
|
||||
elementsMap,
|
||||
);
|
||||
mutateElement(boundTextElement as ExcalidrawTextElement, {
|
||||
app.scene.mutateElement(boundTextElement as ExcalidrawTextElement, {
|
||||
containerId: null,
|
||||
width,
|
||||
height,
|
||||
|
@ -89,7 +84,7 @@ export const actionUnbindText = register({
|
|||
x,
|
||||
y,
|
||||
});
|
||||
mutateElement(element, {
|
||||
app.scene.mutateElement(element, {
|
||||
boundElements: element.boundElements?.filter(
|
||||
(ele) => ele.id !== boundTextElement.id,
|
||||
),
|
||||
|
@ -154,13 +149,13 @@ export const actionBindText = register({
|
|||
textElement = selectedElements[1] as ExcalidrawTextElement;
|
||||
container = selectedElements[0] as ExcalidrawTextContainer;
|
||||
}
|
||||
mutateElement(textElement, {
|
||||
app.scene.mutateElement(textElement, {
|
||||
containerId: container.id,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
autoResize: true,
|
||||
});
|
||||
mutateElement(container, {
|
||||
app.scene.mutateElement(container, {
|
||||
boundElements: (container.boundElements || []).concat({
|
||||
type: "text",
|
||||
id: textElement.id,
|
||||
|
@ -171,6 +166,7 @@ export const actionBindText = register({
|
|||
textElement,
|
||||
container,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
// overwritting the cache with original container height so
|
||||
// it can be restored when unbind
|
||||
|
@ -301,40 +297,26 @@ export const actionWrapTextInContainer = register({
|
|||
}
|
||||
|
||||
if (startBinding || endBinding) {
|
||||
const updates = { startBinding, endBinding };
|
||||
|
||||
if (isElbowArrow(ele)) {
|
||||
mutateElbowArrow(
|
||||
ele,
|
||||
updates as {
|
||||
startBinding: FixedPointBinding;
|
||||
endBinding: FixedPointBinding;
|
||||
},
|
||||
false,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
} else {
|
||||
mutateElement(ele, updates, false);
|
||||
}
|
||||
app.scene.mutateElement(ele, {
|
||||
startBinding,
|
||||
endBinding,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mutateElement(
|
||||
textElement,
|
||||
{
|
||||
containerId: container.id,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
boundElements: null,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
autoResize: true,
|
||||
},
|
||||
false,
|
||||
);
|
||||
app.scene.mutateElement(textElement, {
|
||||
containerId: container.id,
|
||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||
boundElements: null,
|
||||
textAlign: TEXT_ALIGN.CENTER,
|
||||
autoResize: true,
|
||||
});
|
||||
redrawTextBoundingBox(
|
||||
textElement,
|
||||
container,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
|
||||
updatedElements = pushContainerBelowText(
|
||||
|
|
|
@ -17,8 +17,6 @@ import {
|
|||
selectGroupsForSelectedElements,
|
||||
} from "@excalidraw/element/groups";
|
||||
|
||||
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import { t } from "../i18n";
|
||||
|
@ -93,21 +91,14 @@ const deleteSelectedElements = (
|
|||
el.boundElements.forEach((candidate) => {
|
||||
const bound = app.scene.getNonDeletedElementsMap().get(candidate.id);
|
||||
if (bound && isElbowArrow(bound)) {
|
||||
mutateElbowArrow(
|
||||
bound,
|
||||
{
|
||||
startBinding:
|
||||
el.id === bound.startBinding?.elementId
|
||||
? null
|
||||
: bound.startBinding,
|
||||
endBinding:
|
||||
el.id === bound.endBinding?.elementId
|
||||
? null
|
||||
: bound.endBinding,
|
||||
},
|
||||
true,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
app.scene.mutateElement(bound, {
|
||||
startBinding:
|
||||
el.id === bound.startBinding?.elementId
|
||||
? null
|
||||
: bound.startBinding,
|
||||
endBinding:
|
||||
el.id === bound.endBinding?.elementId ? null : bound.endBinding,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import { syncMovedIndices } from "@excalidraw/element/fractionalIndex";
|
|||
|
||||
import { duplicateElements } from "@excalidraw/element/duplicate";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import { ToolButton } from "../components/ToolButton";
|
||||
import { DuplicateIcon } from "../components/icons";
|
||||
|
@ -52,7 +52,7 @@ export const actionDuplicateSelection = register({
|
|||
try {
|
||||
const newAppState = LinearElementEditor.duplicateSelectedPoints(
|
||||
appState,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
app.scene,
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -91,11 +91,16 @@ export const actionDuplicateSelection = register({
|
|||
}
|
||||
}
|
||||
|
||||
syncMovedIndices(nextElements, arrayToMap(duplicatedElements));
|
||||
|
||||
return {
|
||||
elements: syncMovedIndices(nextElements, arrayToMap(duplicatedElements)),
|
||||
elements: nextElements,
|
||||
appState: {
|
||||
...appState,
|
||||
...updateLinearElementEditors(duplicatedElements),
|
||||
...updateLinearElementEditors(
|
||||
duplicatedElements,
|
||||
arrayToMap(nextElements),
|
||||
),
|
||||
...selectGroupsForSelectedElements(
|
||||
{
|
||||
editingGroupId: appState.editingGroupId,
|
||||
|
@ -131,7 +136,10 @@ export const actionDuplicateSelection = register({
|
|||
),
|
||||
});
|
||||
|
||||
const updateLinearElementEditors = (clonedElements: ExcalidrawElement[]) => {
|
||||
const updateLinearElementEditors = (
|
||||
clonedElements: ExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
) => {
|
||||
const linears = clonedElements.filter(isLinearElement);
|
||||
if (linears.length === 1) {
|
||||
const linear = linears[0];
|
||||
|
@ -142,7 +150,7 @@ const updateLinearElementEditors = (clonedElements: ExcalidrawElement[]) => {
|
|||
|
||||
if (onlySingleLinearSelected) {
|
||||
return {
|
||||
selectedLinearElement: new LinearElementEditor(linear),
|
||||
selectedLinearElement: new LinearElementEditor(linear, elementsMap),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,8 @@ import {
|
|||
} from "@excalidraw/element/binding";
|
||||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||
|
||||
import { mutateElement } from "@excalidraw/element/mutateElement";
|
||||
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||
import {
|
||||
isBindingElement,
|
||||
isElbowArrow,
|
||||
isLinearElement,
|
||||
} from "@excalidraw/element/typeChecks";
|
||||
|
||||
|
@ -19,13 +16,6 @@ import { isPathALoop } from "@excalidraw/element/shapes";
|
|||
|
||||
import { isInvisiblySmallElement } from "@excalidraw/element/sizeHelpers";
|
||||
|
||||
import type {
|
||||
ExcalidrawElbowArrowElement,
|
||||
ExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type { ElementUpdate } from "@excalidraw/element/mutateElement";
|
||||
|
||||
import { t } from "../i18n";
|
||||
import { resetCursor } from "../cursor";
|
||||
import { done } from "../components/icons";
|
||||
|
@ -56,7 +46,6 @@ export const actionFinalize = register({
|
|||
element,
|
||||
startBindingElement,
|
||||
endBindingElement,
|
||||
elementsMap,
|
||||
scene,
|
||||
);
|
||||
}
|
||||
|
@ -82,7 +71,11 @@ export const actionFinalize = register({
|
|||
scene.getElement(appState.pendingImageElementId);
|
||||
|
||||
if (pendingImageElement) {
|
||||
mutateElement(pendingImageElement, { isDeleted: true }, false);
|
||||
scene.mutateElement(
|
||||
pendingImageElement,
|
||||
{ isDeleted: true },
|
||||
{ informMutation: false },
|
||||
);
|
||||
}
|
||||
|
||||
if (window.document.activeElement instanceof HTMLElement) {
|
||||
|
@ -95,16 +88,6 @@ export const actionFinalize = register({
|
|||
? appState.newElement
|
||||
: null;
|
||||
|
||||
const mutate = (updates: ElementUpdate<ExcalidrawElbowArrowElement>) =>
|
||||
isElbowArrow(multiPointElement as ExcalidrawElbowArrowElement)
|
||||
? mutateElbowArrow(
|
||||
multiPointElement as ExcalidrawElbowArrowElement,
|
||||
updates,
|
||||
true,
|
||||
elementsMap,
|
||||
)
|
||||
: mutateElement(multiPointElement as ExcalidrawElement, updates);
|
||||
|
||||
if (multiPointElement) {
|
||||
// pen and mouse have hover
|
||||
if (
|
||||
|
@ -116,7 +99,7 @@ export const actionFinalize = register({
|
|||
!lastCommittedPoint ||
|
||||
points[points.length - 1] !== lastCommittedPoint
|
||||
) {
|
||||
mutate({
|
||||
scene.mutateElement(multiPointElement, {
|
||||
points: multiPointElement.points.slice(0, -1),
|
||||
});
|
||||
}
|
||||
|
@ -140,7 +123,7 @@ export const actionFinalize = register({
|
|||
if (isLoop) {
|
||||
const linePoints = multiPointElement.points;
|
||||
const firstPoint = linePoints[0];
|
||||
mutate({
|
||||
scene.mutateElement(multiPointElement, {
|
||||
points: linePoints.map((p, index) =>
|
||||
index === linePoints.length - 1
|
||||
? pointFrom(firstPoint[0], firstPoint[1])
|
||||
|
@ -160,13 +143,7 @@ export const actionFinalize = register({
|
|||
-1,
|
||||
arrayToMap(elements),
|
||||
);
|
||||
maybeBindLinearElement(
|
||||
multiPointElement,
|
||||
appState,
|
||||
{ x, y },
|
||||
elementsMap,
|
||||
elements,
|
||||
);
|
||||
maybeBindLinearElement(multiPointElement, appState, { x, y }, scene);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,7 +199,10 @@ export const actionFinalize = register({
|
|||
// To select the linear element when user has finished mutipoint editing
|
||||
selectedLinearElement:
|
||||
multiPointElement && isLinearElement(multiPointElement)
|
||||
? new LinearElementEditor(multiPointElement)
|
||||
? new LinearElementEditor(
|
||||
multiPointElement,
|
||||
arrayToMap(newElements),
|
||||
)
|
||||
: appState.selectedLinearElement,
|
||||
pendingImageElementId: null,
|
||||
},
|
||||
|
|
|
@ -4,10 +4,7 @@ import {
|
|||
isBindingEnabled,
|
||||
} from "@excalidraw/element/binding";
|
||||
import { getCommonBoundingBox } from "@excalidraw/element/bounds";
|
||||
import {
|
||||
mutateElement,
|
||||
newElementWith,
|
||||
} from "@excalidraw/element/mutateElement";
|
||||
import { newElementWith } from "@excalidraw/element/mutateElement";
|
||||
import { deepCopyElement } from "@excalidraw/element/duplicate";
|
||||
import { resizeMultipleElements } from "@excalidraw/element/resizeElements";
|
||||
import {
|
||||
|
@ -162,11 +159,9 @@ const flipElements = (
|
|||
|
||||
bindOrUnbindLinearElements(
|
||||
selectedElements.filter(isLinearElement),
|
||||
elementsMap,
|
||||
app.scene.getNonDeletedElements(),
|
||||
app.scene,
|
||||
isBindingEnabled(appState),
|
||||
[],
|
||||
app.scene,
|
||||
appState.zoom,
|
||||
);
|
||||
|
||||
|
@ -194,13 +189,13 @@ const flipElements = (
|
|||
getCommonBoundingBox(selectedElements);
|
||||
const [diffX, diffY] = [midX - newMidX, midY - newMidY];
|
||||
otherElements.forEach((element) =>
|
||||
mutateElement(element, {
|
||||
app.scene.mutateElement(element, {
|
||||
x: element.x + diffX,
|
||||
y: element.y + diffY,
|
||||
}),
|
||||
);
|
||||
elbowArrows.forEach((element) =>
|
||||
mutateElement(element, {
|
||||
app.scene.mutateElement(element, {
|
||||
x: element.x + diffX,
|
||||
y: element.y + diffY,
|
||||
}),
|
||||
|
|
|
@ -2,6 +2,8 @@ import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
|||
|
||||
import { isElbowArrow, isLinearElement } from "@excalidraw/element/typeChecks";
|
||||
|
||||
import { arrayToMap } from "@excalidraw/common";
|
||||
|
||||
import type { ExcalidrawLinearElement } from "@excalidraw/element/types";
|
||||
|
||||
import { DEFAULT_CATEGORIES } from "../components/CommandPalette/CommandPalette";
|
||||
|
@ -50,7 +52,7 @@ export const actionToggleLinearEditor = register({
|
|||
const editingLinearElement =
|
||||
appState.editingLinearElement?.elementId === selectedElement.id
|
||||
? null
|
||||
: new LinearElementEditor(selectedElement);
|
||||
: new LinearElementEditor(selectedElement, arrayToMap(elements));
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
|
|
|
@ -34,10 +34,7 @@ import {
|
|||
|
||||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||
|
||||
import {
|
||||
mutateElement,
|
||||
newElementWith,
|
||||
} from "@excalidraw/element/mutateElement";
|
||||
import { newElementWith } from "@excalidraw/element/mutateElement";
|
||||
|
||||
import {
|
||||
getBoundTextElement,
|
||||
|
@ -68,7 +65,6 @@ import type {
|
|||
FontFamilyValues,
|
||||
TextAlign,
|
||||
VerticalAlign,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import { trackEvent } from "../analytics";
|
||||
|
@ -138,6 +134,7 @@ import { register } from "./register";
|
|||
|
||||
import type { CaptureUpdateActionType } from "../store";
|
||||
import type { AppClassProperties, AppState, Primitive } from "../types";
|
||||
import type Scene from "../scene/Scene";
|
||||
|
||||
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
||||
|
||||
|
@ -207,25 +204,22 @@ export const getFormValue = function <T extends Primitive>(
|
|||
const offsetElementAfterFontResize = (
|
||||
prevElement: ExcalidrawTextElement,
|
||||
nextElement: ExcalidrawTextElement,
|
||||
scene: Scene,
|
||||
) => {
|
||||
if (isBoundToContainer(nextElement) || !nextElement.autoResize) {
|
||||
return nextElement;
|
||||
}
|
||||
return mutateElement(
|
||||
nextElement,
|
||||
{
|
||||
x:
|
||||
prevElement.textAlign === "left"
|
||||
? prevElement.x
|
||||
: prevElement.x +
|
||||
(prevElement.width - nextElement.width) /
|
||||
(prevElement.textAlign === "center" ? 2 : 1),
|
||||
// centering vertically is non-standard, but for Excalidraw I think
|
||||
// it makes sense
|
||||
y: prevElement.y + (prevElement.height - nextElement.height) / 2,
|
||||
},
|
||||
false,
|
||||
);
|
||||
return scene.mutateElement(nextElement, {
|
||||
x:
|
||||
prevElement.textAlign === "left"
|
||||
? prevElement.x
|
||||
: prevElement.x +
|
||||
(prevElement.width - nextElement.width) /
|
||||
(prevElement.textAlign === "center" ? 2 : 1),
|
||||
// centering vertically is non-standard, but for Excalidraw I think
|
||||
// it makes sense
|
||||
y: prevElement.y + (prevElement.height - nextElement.height) / 2,
|
||||
});
|
||||
};
|
||||
|
||||
const changeFontSize = (
|
||||
|
@ -252,9 +246,14 @@ const changeFontSize = (
|
|||
newElement,
|
||||
app.scene.getContainerElement(oldElement),
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
|
||||
newElement = offsetElementAfterFontResize(oldElement, newElement);
|
||||
newElement = offsetElementAfterFontResize(
|
||||
oldElement,
|
||||
newElement,
|
||||
app.scene,
|
||||
);
|
||||
|
||||
return newElement;
|
||||
}
|
||||
|
@ -269,10 +268,7 @@ const changeFontSize = (
|
|||
includeBoundTextElement: true,
|
||||
}).forEach((element) => {
|
||||
if (isTextElement(element)) {
|
||||
updateBoundElements(
|
||||
element,
|
||||
updatedElementsMap as NonDeletedSceneElementsMap,
|
||||
);
|
||||
updateBoundElements(element, updatedElementsMap);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -919,7 +915,7 @@ export const actionChangeFontFamily = register({
|
|||
|
||||
if (resetContainers && container && cachedContainer) {
|
||||
// reset the container back to it's cached version
|
||||
mutateElement(container, { ...cachedContainer }, false);
|
||||
app.scene.mutateElement(container, { ...cachedContainer });
|
||||
}
|
||||
|
||||
if (!skipFontFaceCheck) {
|
||||
|
@ -954,7 +950,7 @@ export const actionChangeFontFamily = register({
|
|||
element,
|
||||
container,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
false,
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -973,7 +969,7 @@ export const actionChangeFontFamily = register({
|
|||
latestElement as ExcalidrawTextElement,
|
||||
latestContainer,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
false,
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1180,6 +1176,7 @@ export const actionChangeTextAlign = register({
|
|||
newElement,
|
||||
app.scene.getContainerElement(oldElement),
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
return newElement;
|
||||
}
|
||||
|
@ -1271,6 +1268,7 @@ export const actionChangeVerticalAlign = register({
|
|||
newElement,
|
||||
app.scene.getContainerElement(oldElement),
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
return newElement;
|
||||
}
|
||||
|
@ -1670,10 +1668,17 @@ export const actionChangeArrowType = register({
|
|||
newElement,
|
||||
startHoveredElement,
|
||||
"start",
|
||||
elementsMap,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
endHoveredElement &&
|
||||
bindLinearElement(newElement, endHoveredElement, "end", elementsMap);
|
||||
bindLinearElement(
|
||||
newElement,
|
||||
endHoveredElement,
|
||||
"end",
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
|
||||
const startBinding =
|
||||
startElement && newElement.startBinding
|
||||
|
@ -1684,7 +1689,6 @@ export const actionChangeArrowType = register({
|
|||
newElement,
|
||||
startElement,
|
||||
"start",
|
||||
elementsMap,
|
||||
),
|
||||
}
|
||||
: null;
|
||||
|
@ -1697,7 +1701,6 @@ export const actionChangeArrowType = register({
|
|||
newElement,
|
||||
endElement,
|
||||
"end",
|
||||
elementsMap,
|
||||
),
|
||||
}
|
||||
: null;
|
||||
|
@ -1729,7 +1732,13 @@ export const actionChangeArrowType = register({
|
|||
newElement.startBinding.elementId,
|
||||
) as ExcalidrawBindableElement;
|
||||
if (startElement) {
|
||||
bindLinearElement(newElement, startElement, "start", elementsMap);
|
||||
bindLinearElement(
|
||||
newElement,
|
||||
startElement,
|
||||
"start",
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (newElement.endBinding) {
|
||||
|
@ -1737,7 +1746,13 @@ export const actionChangeArrowType = register({
|
|||
newElement.endBinding.elementId,
|
||||
) as ExcalidrawBindableElement;
|
||||
if (endElement) {
|
||||
bindLinearElement(newElement, endElement, "end", elementsMap);
|
||||
bindLinearElement(
|
||||
newElement,
|
||||
endElement,
|
||||
"end",
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1758,6 +1773,7 @@ export const actionChangeArrowType = register({
|
|||
if (selected) {
|
||||
newState.selectedLinearElement = new LinearElementEditor(
|
||||
selected as ExcalidrawLinearElement,
|
||||
arrayToMap(elements),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { getNonDeletedElements } from "@excalidraw/element";
|
|||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||
import { isLinearElement, isTextElement } from "@excalidraw/element/typeChecks";
|
||||
|
||||
import { KEYS } from "@excalidraw/common";
|
||||
import { arrayToMap, KEYS } from "@excalidraw/common";
|
||||
|
||||
import { selectGroupsForSelectedElements } from "@excalidraw/element/groups";
|
||||
|
||||
|
@ -53,7 +53,7 @@ export const actionSelectAll = register({
|
|||
// single linear element selected
|
||||
Object.keys(selectedElementIds).length === 1 &&
|
||||
isLinearElement(elements[0])
|
||||
? new LinearElementEditor(elements[0])
|
||||
? new LinearElementEditor(elements[0], arrayToMap(elements))
|
||||
: null,
|
||||
},
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
|
|
|
@ -143,6 +143,7 @@ export const actionPasteStyles = register({
|
|||
newElement,
|
||||
container,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||
import {
|
||||
mutateElement,
|
||||
mutateElementWith,
|
||||
newElementWith,
|
||||
} from "@excalidraw/element/mutateElement";
|
||||
import {
|
||||
|
@ -490,6 +491,7 @@ export class AppStateChange implements Change<AppState> {
|
|||
nextElements.get(
|
||||
selectedLinearElementId,
|
||||
) as NonDeleted<ExcalidrawLinearElement>,
|
||||
nextElements,
|
||||
)
|
||||
: null;
|
||||
|
||||
|
@ -499,6 +501,7 @@ export class AppStateChange implements Change<AppState> {
|
|||
nextElements.get(
|
||||
editingLinearElementId,
|
||||
) as NonDeleted<ExcalidrawLinearElement>,
|
||||
nextElements,
|
||||
)
|
||||
: null;
|
||||
|
||||
|
@ -1316,7 +1319,7 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
nextElements: SceneElementsMap,
|
||||
) {
|
||||
const nextAffectedElements = new Map<string, OrderedExcalidrawElement>();
|
||||
const updater = (
|
||||
const updator = (
|
||||
element: ExcalidrawElement,
|
||||
updates: ElementUpdate<ExcalidrawElement>,
|
||||
) => {
|
||||
|
@ -1347,12 +1350,12 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
|
||||
// removed delta is affecting the bindings always, as all the affected elements of the removed elements need to be unbound
|
||||
for (const [id] of this.removed) {
|
||||
ElementsChange.unbindAffected(prevElements, nextElements, id, updater);
|
||||
ElementsChange.unbindAffected(prevElements, nextElements, id, updator);
|
||||
}
|
||||
|
||||
// added delta is affecting the bindings always, all the affected elements of the added elements need to be rebound
|
||||
for (const [id] of this.added) {
|
||||
ElementsChange.rebindAffected(prevElements, nextElements, id, updater);
|
||||
ElementsChange.rebindAffected(prevElements, nextElements, id, updator);
|
||||
}
|
||||
|
||||
// updated delta is affecting the binding only in case it contains changed binding or bindable property
|
||||
|
@ -1367,7 +1370,7 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
continue;
|
||||
}
|
||||
|
||||
ElementsChange.rebindAffected(prevElements, nextElements, id, updater);
|
||||
ElementsChange.rebindAffected(prevElements, nextElements, id, updator);
|
||||
}
|
||||
|
||||
// filter only previous elements, which were now affected
|
||||
|
@ -1429,7 +1432,7 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
prevElements: SceneElementsMap,
|
||||
nextElements: SceneElementsMap,
|
||||
id: string,
|
||||
updater: (
|
||||
updator: (
|
||||
element: ExcalidrawElement,
|
||||
updates: ElementUpdate<ExcalidrawElement>,
|
||||
) => void,
|
||||
|
@ -1438,8 +1441,8 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
const prevElement = () => prevElements.get(id); // element before addition / update
|
||||
const nextElement = () => nextElements.get(id); // element after addition / update
|
||||
|
||||
BoundElement.unbindAffected(nextElements, prevElement(), updater);
|
||||
BoundElement.rebindAffected(nextElements, nextElement(), updater);
|
||||
BoundElement.unbindAffected(nextElements, prevElement(), updator);
|
||||
BoundElement.rebindAffected(nextElements, nextElement(), updator);
|
||||
|
||||
BindableElement.unbindAffected(
|
||||
nextElements,
|
||||
|
@ -1448,11 +1451,11 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
// we cannot rebind arrows with bindable element so we don't unbind them at all during rebind (we still need to unbind them on removal)
|
||||
// TODO: #7348 add startBinding / endBinding to the `BoundElement` context so that we could rebind arrows and remove this condition
|
||||
if (isTextElement(element)) {
|
||||
updater(element, updates);
|
||||
updator(element, updates);
|
||||
}
|
||||
},
|
||||
);
|
||||
BindableElement.rebindAffected(nextElements, nextElement(), updater);
|
||||
BindableElement.rebindAffected(nextElements, nextElement(), updator);
|
||||
}
|
||||
|
||||
private static redrawTextBoundingBoxes(
|
||||
|
@ -1498,7 +1501,12 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
continue;
|
||||
}
|
||||
|
||||
redrawTextBoundingBox(boundText, container, elements, false);
|
||||
redrawTextBoundingBox(
|
||||
boundText,
|
||||
container,
|
||||
elements,
|
||||
(element, updates) => mutateElementWith(element, elements, updates),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1950,13 +1950,21 @@ class App extends React.Component<AppProps, AppState> {
|
|||
// state only.
|
||||
// Thus reset so that we prefer local cache (if there was some
|
||||
// generationData set previously)
|
||||
this.scene.mutateElement(frameElement, {
|
||||
customData: { generationData: undefined },
|
||||
}, { informMutation: false });
|
||||
this.scene.mutateElement(
|
||||
frameElement,
|
||||
{
|
||||
customData: { generationData: undefined },
|
||||
},
|
||||
{ informMutation: false },
|
||||
);
|
||||
} else {
|
||||
this.scene.mutateElement(frameElement, {
|
||||
customData: { generationData: data },
|
||||
}, { informMutation: false });
|
||||
this.scene.mutateElement(
|
||||
frameElement,
|
||||
{
|
||||
customData: { generationData: data },
|
||||
},
|
||||
{ informMutation: false },
|
||||
);
|
||||
}
|
||||
this.magicGenerations.set(frameElement.id, data);
|
||||
this.triggerRender();
|
||||
|
@ -2926,8 +2934,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
nonDeletedElementsMap,
|
||||
),
|
||||
),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.scene,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3329,6 +3336,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
newElement,
|
||||
container,
|
||||
this.scene.getElementsMapIncludingDeleted(),
|
||||
(...args) => this.scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -3452,7 +3460,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||
}
|
||||
// hack to reset the `y` coord because we vertically center during
|
||||
// insertImageElement
|
||||
this.scene.mutateElement(initializedImageElement, { y }, { informMutation: false });
|
||||
this.scene.mutateElement(
|
||||
initializedImageElement,
|
||||
{ y },
|
||||
{ informMutation: false },
|
||||
);
|
||||
|
||||
y = imageElement.y + imageElement.height + 25;
|
||||
|
||||
|
@ -4177,6 +4189,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.scene.getNonDeletedElementsMap(),
|
||||
this.state,
|
||||
getLinkDirectionFromKey(event.key),
|
||||
this.scene,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4418,10 +4431,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||
}
|
||||
|
||||
selectedElements.forEach((element) => {
|
||||
this.scene.mutateElement(element, {
|
||||
x: element.x + offsetX,
|
||||
y: element.y + offsetY,
|
||||
}, { informMutation: false });
|
||||
this.scene.mutateElement(
|
||||
element,
|
||||
{
|
||||
x: element.x + offsetX,
|
||||
y: element.y + offsetY,
|
||||
},
|
||||
{ informMutation: false },
|
||||
);
|
||||
|
||||
updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
|
||||
simultaneouslyUpdated: selectedElements,
|
||||
|
@ -4457,6 +4474,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.setState({
|
||||
editingLinearElement: new LinearElementEditor(
|
||||
selectedElement,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@ -4650,11 +4668,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: [] });
|
||||
|
@ -5454,7 +5470,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 (
|
||||
|
@ -5480,7 +5499,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.store.shouldCaptureIncrement();
|
||||
LinearElementEditor.deleteFixedSegment(
|
||||
selectedElements[0],
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.scene,
|
||||
midPoint,
|
||||
);
|
||||
|
||||
|
@ -8113,7 +8132,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
index,
|
||||
gridX,
|
||||
gridY,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
this.scene,
|
||||
);
|
||||
|
||||
flushSync(() => {
|
||||
|
@ -8218,7 +8237,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
pointerCoords,
|
||||
this,
|
||||
!event[KEYS.CTRL_OR_CMD],
|
||||
elementsMap,
|
||||
this.scene,
|
||||
);
|
||||
if (!ret) {
|
||||
return;
|
||||
|
@ -8800,7 +8819,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 &&
|
||||
|
@ -8968,7 +8990,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||
element,
|
||||
startBindingElement,
|
||||
endBindingElement,
|
||||
elementsMap,
|
||||
this.scene,
|
||||
);
|
||||
}
|
||||
|
@ -9109,8 +9130,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 });
|
||||
|
@ -9128,7 +9148,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
},
|
||||
prevState,
|
||||
),
|
||||
selectedLinearElement: new LinearElementEditor(newElement),
|
||||
selectedLinearElement: new LinearElementEditor(
|
||||
newElement,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
}));
|
||||
} else {
|
||||
this.setState((prevState) => ({
|
||||
|
@ -9268,9 +9291,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.state.editingGroupId!,
|
||||
);
|
||||
|
||||
this.scene.mutateElement(element, {
|
||||
groupIds: element.groupIds.slice(0, index),
|
||||
}, { informMutation: false });
|
||||
this.scene.mutateElement(
|
||||
element,
|
||||
{
|
||||
groupIds: element.groupIds.slice(0, index),
|
||||
},
|
||||
{ informMutation: false },
|
||||
);
|
||||
}
|
||||
|
||||
nextElements.forEach((element) => {
|
||||
|
@ -9281,9 +9308,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||
element.groupIds[element.groupIds.length - 1],
|
||||
).length < 2
|
||||
) {
|
||||
this.scene.mutateElement(element, {
|
||||
groupIds: [],
|
||||
}, { informMutation: false });
|
||||
this.scene.mutateElement(
|
||||
element,
|
||||
{
|
||||
groupIds: [],
|
||||
},
|
||||
{ informMutation: false },
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -9392,7 +9423,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(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9517,7 +9551,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,
|
||||
};
|
||||
});
|
||||
|
@ -9591,7 +9628,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,
|
||||
}));
|
||||
}
|
||||
|
@ -9684,11 +9724,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,
|
||||
);
|
||||
}
|
||||
|
@ -9845,9 +9883,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||
const dataURL =
|
||||
this.files[fileId]?.dataURL || (await getDataURL(imageFile));
|
||||
|
||||
const imageElement = this.scene.mutateElement(_imageElement, {
|
||||
fileId,
|
||||
}, { informMutation: false }) as NonDeleted<InitializedExcalidrawImageElement>;
|
||||
const imageElement = this.scene.mutateElement(
|
||||
_imageElement,
|
||||
{
|
||||
fileId,
|
||||
},
|
||||
{ informMutation: false },
|
||||
) as NonDeleted<InitializedExcalidrawImageElement>;
|
||||
|
||||
return new Promise<NonDeleted<InitializedExcalidrawImageElement>>(
|
||||
async (resolve, reject) => {
|
||||
|
@ -10523,7 +10565,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this,
|
||||
),
|
||||
selectedLinearElement: isLinearElement(element)
|
||||
? new LinearElementEditor(element)
|
||||
? new LinearElementEditor(
|
||||
element,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
)
|
||||
: null,
|
||||
}
|
||||
: this.state),
|
||||
|
@ -10556,6 +10601,7 @@ 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,
|
||||
});
|
||||
|
@ -10621,6 +10667,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,
|
||||
|
@ -10846,7 +10893,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||
pointerDownState.originalElements,
|
||||
transformHandleType,
|
||||
selectedElements,
|
||||
this.scene.getElementsMapIncludingDeleted(),
|
||||
this.scene,
|
||||
shouldRotateWithDiscreteAngle(event),
|
||||
shouldResizeFromCenter(event),
|
||||
|
|
|
@ -5,11 +5,12 @@ import {
|
|||
CLASSES,
|
||||
DEFAULT_SIDEBAR,
|
||||
TOOL_TYPE,
|
||||
arrayToMap,
|
||||
capitalizeString,
|
||||
isShallowEqual,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { mutateElement } from "@excalidraw/element/mutateElement";
|
||||
import { mutateElementWith } from "@excalidraw/element/mutateElement";
|
||||
|
||||
import { showSelectedShapeActions } from "@excalidraw/element/showSelectedShapeActions";
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
mutateElementWith(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,
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
@ -35,7 +33,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 +42,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 +68,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 });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@ 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";
|
||||
|
||||
|
@ -113,7 +112,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 +143,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 +175,8 @@ const handleDimensionChange: DragInputCallbackType<
|
|||
nextHeight,
|
||||
latestElement,
|
||||
origElement,
|
||||
elementsMap,
|
||||
originalElementsMap,
|
||||
scene,
|
||||
property === "width" ? "e" : "s",
|
||||
{
|
||||
shouldMaintainAspectRatio: keepAspectRatio,
|
||||
|
@ -223,8 +222,8 @@ const handleDimensionChange: DragInputCallbackType<
|
|||
nextHeight,
|
||||
latestElement,
|
||||
origElement,
|
||||
elementsMap,
|
||||
originalElementsMap,
|
||||
scene,
|
||||
property === "width" ? "e" : "s",
|
||||
{
|
||||
shouldMaintainAspectRatio: keepAspectRatio,
|
||||
|
|
|
@ -75,6 +75,7 @@ const handleFontSizeChange: DragInputCallbackType<
|
|||
latestElement,
|
||||
scene.getContainerElement(latestElement),
|
||||
scene.getNonDeletedElementsMap(),
|
||||
(...args) => scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,17 +54,13 @@ const handleDegreeChange: DragInputCallbackType<
|
|||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
mutateElement(
|
||||
element,
|
||||
{
|
||||
angle: nextAngle,
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(element, {
|
||||
angle: nextAngle,
|
||||
});
|
||||
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
if (boundTextElement && !isArrowElement(element)) {
|
||||
mutateElement(boundTextElement, { angle: nextAngle }, false);
|
||||
mutateElement(boundTextElement, { angle: nextAngle });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,17 +88,13 @@ const handleDegreeChange: DragInputCallbackType<
|
|||
|
||||
const nextAngle = degreesToRadians(nextAngleInDegrees as Degrees);
|
||||
|
||||
mutateElement(
|
||||
latestElement,
|
||||
{
|
||||
angle: nextAngle,
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(latestElement, {
|
||||
angle: nextAngle,
|
||||
});
|
||||
|
||||
const boundTextElement = getBoundTextElement(latestElement, elementsMap);
|
||||
if (boundTextElement && !isArrowElement(latestElement)) {
|
||||
mutateElement(boundTextElement, { angle: nextAngle }, false);
|
||||
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,
|
||||
|
@ -13,11 +12,14 @@ import {
|
|||
handleBindTextResize,
|
||||
} from "@excalidraw/element/textElement";
|
||||
|
||||
import { isElbowArrow, isTextElement } from "@excalidraw/element/typeChecks";
|
||||
import { isTextElement } from "@excalidraw/element/typeChecks";
|
||||
|
||||
import { getCommonBounds } from "@excalidraw/utils";
|
||||
|
||||
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||
import {
|
||||
mutateElement,
|
||||
mutateElementWith,
|
||||
} from "@excalidraw/element/mutateElement";
|
||||
|
||||
import type {
|
||||
ElementsMap,
|
||||
|
@ -82,11 +84,7 @@ const resizeElementInGroup = (
|
|||
) => {
|
||||
const updates = getResizedUpdates(anchorX, anchorY, scale, origElement);
|
||||
|
||||
if (isElbowArrow(latestElement)) {
|
||||
mutateElbowArrow(latestElement, updates, false, elementsMap);
|
||||
} else {
|
||||
mutateElement(latestElement, updates, false);
|
||||
}
|
||||
mutateElementWith(latestElement, elementsMap, updates);
|
||||
|
||||
const boundTextElement = getBoundTextElement(
|
||||
origElement,
|
||||
|
@ -99,13 +97,9 @@ const resizeElementInGroup = (
|
|||
});
|
||||
const latestBoundTextElement = elementsMap.get(boundTextElement.id);
|
||||
if (latestBoundTextElement && isTextElement(latestBoundTextElement)) {
|
||||
mutateElement(
|
||||
latestBoundTextElement,
|
||||
{
|
||||
fontSize: newFontSize,
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(latestBoundTextElement, {
|
||||
fontSize: newFontSize,
|
||||
});
|
||||
handleBindTextResize(
|
||||
latestElement,
|
||||
elementsMap,
|
||||
|
@ -244,8 +238,8 @@ const handleDimensionChange: DragInputCallbackType<
|
|||
nextHeight,
|
||||
latestElement,
|
||||
origElement,
|
||||
elementsMap,
|
||||
originalElementsMap,
|
||||
scene,
|
||||
property === "width" ? "e" : "s",
|
||||
{
|
||||
shouldInformMutation: false,
|
||||
|
@ -347,8 +341,8 @@ const handleDimensionChange: DragInputCallbackType<
|
|||
nextHeight,
|
||||
latestElement,
|
||||
origElement,
|
||||
elementsMap,
|
||||
originalElementsMap,
|
||||
scene,
|
||||
property === "width" ? "e" : "s",
|
||||
{
|
||||
shouldInformMutation: false,
|
||||
|
|
|
@ -84,19 +84,15 @@ const handleFontSizeChange: DragInputCallbackType<
|
|||
nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE);
|
||||
|
||||
for (const textElement of latestTextElements) {
|
||||
mutateElement(
|
||||
textElement,
|
||||
{
|
||||
fontSize: nextFontSize,
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(textElement, {
|
||||
fontSize: nextFontSize,
|
||||
});
|
||||
|
||||
redrawTextBoundingBox(
|
||||
textElement,
|
||||
scene.getContainerElement(textElement),
|
||||
elementsMap,
|
||||
false,
|
||||
(...args) => scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -117,19 +113,15 @@ const handleFontSizeChange: DragInputCallbackType<
|
|||
if (shouldChangeByStepSize) {
|
||||
nextFontSize = getStepSizedValue(nextFontSize, STEP_SIZE);
|
||||
}
|
||||
mutateElement(
|
||||
latestElement,
|
||||
{
|
||||
fontSize: nextFontSize,
|
||||
},
|
||||
false,
|
||||
);
|
||||
mutateElement(latestElement, {
|
||||
fontSize: nextFontSize,
|
||||
});
|
||||
|
||||
redrawTextBoundingBox(
|
||||
latestElement,
|
||||
scene.getContainerElement(latestElement),
|
||||
elementsMap,
|
||||
false,
|
||||
(...args) => scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,7 @@ 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 StatsDragInput from "./DragInput";
|
||||
import { getAtomicUnits, getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
|
@ -36,13 +31,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 +58,6 @@ const moveElements = (
|
|||
newTopLeftX,
|
||||
newTopLeftY,
|
||||
origElement,
|
||||
elementsMap,
|
||||
elements,
|
||||
scene,
|
||||
originalElementsMap,
|
||||
false,
|
||||
|
@ -78,11 +69,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 +102,6 @@ const moveGroupTo = (
|
|||
topLeftX + offsetX,
|
||||
topLeftY + offsetY,
|
||||
origElement,
|
||||
elementsMap,
|
||||
elements,
|
||||
scene,
|
||||
originalElementsMap,
|
||||
false,
|
||||
|
@ -135,7 +123,6 @@ const handlePositionChange: DragInputCallbackType<
|
|||
originalAppState,
|
||||
}) => {
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
const elements = scene.getNonDeletedElements();
|
||||
|
||||
if (nextValue !== undefined) {
|
||||
for (const atomicUnit of getAtomicUnits(
|
||||
|
@ -159,8 +146,6 @@ const handlePositionChange: DragInputCallbackType<
|
|||
newTopLeftX,
|
||||
newTopLeftY,
|
||||
elementsInUnit.map((el) => el.original),
|
||||
elementsMap,
|
||||
elements,
|
||||
originalElementsMap,
|
||||
scene,
|
||||
);
|
||||
|
@ -188,8 +173,6 @@ const handlePositionChange: DragInputCallbackType<
|
|||
newTopLeftX,
|
||||
newTopLeftY,
|
||||
origElement,
|
||||
elementsMap,
|
||||
elements,
|
||||
scene,
|
||||
originalElementsMap,
|
||||
false,
|
||||
|
@ -214,8 +197,6 @@ const handlePositionChange: DragInputCallbackType<
|
|||
changeInTopX,
|
||||
changeInTopY,
|
||||
originalElements,
|
||||
originalElements,
|
||||
elementsMap,
|
||||
originalElementsMap,
|
||||
scene,
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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,7 +23,6 @@ import type {
|
|||
ElementsMap,
|
||||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type Scene from "../../scene/Scene";
|
||||
|
@ -119,12 +117,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 +145,15 @@ export const moveElement = (
|
|||
-originalElement.angle as Radians,
|
||||
);
|
||||
|
||||
mutateElement(
|
||||
scene.mutateElement(
|
||||
latestElement,
|
||||
{
|
||||
x,
|
||||
y,
|
||||
},
|
||||
shouldInformMutation,
|
||||
{ informMutation: shouldInformMutation },
|
||||
);
|
||||
updateBindings(latestElement, elementsMap, elements, scene);
|
||||
updateBindings(latestElement, scene);
|
||||
|
||||
const boundTextElement = getBoundTextElement(
|
||||
originalElement,
|
||||
|
@ -165,13 +162,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 },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -199,8 +196,6 @@ export const getAtomicUnits = (
|
|||
|
||||
export const updateBindings = (
|
||||
latestElement: ExcalidrawElement,
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
elements: readonly NonDeletedExcalidrawElement[],
|
||||
scene: Scene,
|
||||
options?: {
|
||||
simultaneouslyUpdated?: readonly ExcalidrawElement[];
|
||||
|
@ -209,16 +204,12 @@ 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.getNonDeletedElementsMap(),
|
||||
options,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -38,6 +38,10 @@ import { redrawTextBoundingBox } from "@excalidraw/element/textElement";
|
|||
|
||||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||
|
||||
import { mutateElementWith } from "@excalidraw/element/mutateElement";
|
||||
|
||||
import { getCommonBounds } from "@excalidraw/element/bounds";
|
||||
|
||||
import type { ElementConstructorOpts } from "@excalidraw/element/newElement";
|
||||
|
||||
import type {
|
||||
|
@ -63,8 +67,6 @@ import type {
|
|||
|
||||
import type { MarkOptional } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { getCommonBounds } from "..";
|
||||
|
||||
export type ValidLinearElement = {
|
||||
type: "arrow" | "line";
|
||||
x: number;
|
||||
|
@ -240,7 +242,12 @@ const bindTextToContainer = (
|
|||
}),
|
||||
});
|
||||
|
||||
redrawTextBoundingBox(textElement, container, elementsMap);
|
||||
redrawTextBoundingBox(
|
||||
textElement,
|
||||
container,
|
||||
elementsMap,
|
||||
(element, updates) => mutateElementWith(element, elementsMap, updates),
|
||||
);
|
||||
return [container, textElement] as const;
|
||||
};
|
||||
|
||||
|
@ -336,6 +343,7 @@ const bindLinearElementToElement = (
|
|||
startBoundElement as ExcalidrawBindableElement,
|
||||
"start",
|
||||
elementsMap,
|
||||
(element, updates) => mutateElementWith(element, elementsMap, updates),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -411,6 +419,7 @@ const bindLinearElementToElement = (
|
|||
endBoundElement as ExcalidrawBindableElement,
|
||||
"end",
|
||||
elementsMap,
|
||||
(element, updates) => mutateElementWith(element, elementsMap, updates),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -259,13 +259,12 @@ export {
|
|||
} from "@excalidraw/common";
|
||||
|
||||
export {
|
||||
mutateElementWith,
|
||||
mutateElement,
|
||||
newElementWith,
|
||||
bumpVersion,
|
||||
} from "@excalidraw/element/mutateElement";
|
||||
|
||||
export { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||
|
||||
export { CaptureUpdateAction } from "./store";
|
||||
|
||||
export { parseLibraryTokensFromUrl, useHandleLibrary } from "./data/library";
|
||||
|
|
|
@ -149,6 +149,7 @@ export class LassoTrail extends AnimatedTrail {
|
|||
this.app.scene.getNonDeletedElement(
|
||||
selectedIds[0],
|
||||
) as NonDeleted<ExcalidrawLinearElement>,
|
||||
this.app.scene.getNonDeletedElementsMap(),
|
||||
)
|
||||
: null,
|
||||
};
|
||||
|
|
|
@ -8,10 +8,7 @@ import {
|
|||
isTestEnv,
|
||||
} from "@excalidraw/common";
|
||||
import { isNonDeletedElement } from "@excalidraw/element";
|
||||
import {
|
||||
isElbowArrow,
|
||||
isFrameLikeElement,
|
||||
} from "@excalidraw/element/typeChecks";
|
||||
import { isFrameLikeElement } from "@excalidraw/element/typeChecks";
|
||||
import { getElementsInGroup } from "@excalidraw/element/groups";
|
||||
|
||||
import {
|
||||
|
@ -23,12 +20,10 @@ import {
|
|||
import { getSelectedElements } from "@excalidraw/element/selection";
|
||||
|
||||
import {
|
||||
mutateElement,
|
||||
mutateElementWith,
|
||||
type ElementUpdate,
|
||||
} from "@excalidraw/element/mutateElement";
|
||||
|
||||
import { mutateElbowArrow } from "@excalidraw/element/elbowArrow";
|
||||
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
|
@ -39,7 +34,6 @@ import type {
|
|||
NonDeletedSceneElementsMap,
|
||||
OrderedExcalidrawElement,
|
||||
Ordered,
|
||||
ExcalidrawElbowArrowElement,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type {
|
||||
|
@ -425,6 +419,9 @@ class Scene {
|
|||
|
||||
// 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
|
||||
// TODO_SCENE: think one more time about moving the scene inside element (probably we will end up with ti either way)
|
||||
// Mutate an element with passed updates and trigger the component to update. Make sure you
|
||||
// are calling it either from a React event handler or within unstable_batchedUpdates().
|
||||
mutateElement<TElement extends Mutable<ExcalidrawElement>>(
|
||||
element: TElement,
|
||||
updates: ElementUpdate<TElement>,
|
||||
|
@ -435,16 +432,9 @@ class Scene {
|
|||
informMutation: true,
|
||||
},
|
||||
) {
|
||||
if (isElbowArrow(element)) {
|
||||
mutateElbowArrow(
|
||||
element,
|
||||
updates as ElementUpdate<ExcalidrawElbowArrowElement>,
|
||||
this.getNonDeletedElementsMap(),
|
||||
options,
|
||||
);
|
||||
} else {
|
||||
mutateElement(element, updates);
|
||||
}
|
||||
const elementsMap = this.getNonDeletedElementsMap();
|
||||
|
||||
mutateElementWith(element, elementsMap, updates, options);
|
||||
|
||||
if (options.informMutation) {
|
||||
this.triggerUpdate();
|
||||
|
|
|
@ -13,8 +13,6 @@ import type {
|
|||
ExcalidrawRectangleElement,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import * as InteractiveCanvas from "../renderer/interactiveScene";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
|
@ -85,15 +83,13 @@ describe("move element", () => {
|
|||
const rectA = UI.createElement("rectangle", { size: 100 });
|
||||
const rectB = UI.createElement("rectangle", { x: 200, y: 0, size: 300 });
|
||||
const arrow = UI.createElement("arrow", { x: 110, y: 50, size: 80 });
|
||||
const elementsMap = h.app.scene.getNonDeletedElementsMap();
|
||||
act(() => {
|
||||
// bind line to two rectangles
|
||||
bindOrUnbindLinearElement(
|
||||
arrow.get() as NonDeleted<ExcalidrawLinearElement>,
|
||||
rectA.get() as ExcalidrawRectangleElement,
|
||||
rectB.get() as ExcalidrawRectangleElement,
|
||||
elementsMap,
|
||||
{} as Scene,
|
||||
h.app.scene,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ import type {
|
|||
|
||||
import { actionSaveToActiveFile } from "../actions";
|
||||
|
||||
import Scene from "../scene/Scene";
|
||||
import { parseClipboard } from "../clipboard";
|
||||
import {
|
||||
actionDecreaseFontSize,
|
||||
|
@ -130,8 +129,7 @@ export const textWysiwyg = ({
|
|||
|
||||
const updateWysiwygStyle = () => {
|
||||
const appState = app.state;
|
||||
const updatedTextElement =
|
||||
Scene.getScene(element)?.getElement<ExcalidrawTextElement>(id);
|
||||
const updatedTextElement = app.scene.getElement<ExcalidrawTextElement>(id);
|
||||
|
||||
if (!updatedTextElement) {
|
||||
return;
|
||||
|
@ -544,7 +542,7 @@ export const textWysiwyg = ({
|
|||
// it'd get stuck in an infinite loop of blur→onSubmit after we re-focus the
|
||||
// wysiwyg on update
|
||||
cleanup();
|
||||
const updateElement = Scene.getScene(element)?.getElement(
|
||||
const updateElement = app.scene.getElement(
|
||||
element.id,
|
||||
) as ExcalidrawTextElement;
|
||||
if (!updateElement) {
|
||||
|
@ -583,6 +581,7 @@ export const textWysiwyg = ({
|
|||
updateElement,
|
||||
container,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
(...args) => app.scene.mutateElement(...args),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue