chore: Refactor and remove scene from elbow arrow generation (#8342)

* Refactor and remove scene from elbow arrow generation
This commit is contained in:
Márk Tolmács 2024-08-08 14:06:26 +02:00 committed by GitHub
parent 72d6ee48fc
commit dd1370381d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 115 additions and 156 deletions

View file

@ -25,6 +25,7 @@ import type {
OrderedExcalidrawElement,
ExcalidrawElbowArrowElement,
FixedPoint,
SceneElementsMap,
} from "./types";
import type { Bounds } from "./bounds";
@ -124,7 +125,6 @@ export const bindOrUnbindLinearElement = (
boundToElementIds,
unboundFromElementIds,
elementsMap,
scene,
);
bindOrUnbindLinearElementEdge(
linearElement,
@ -134,7 +134,6 @@ export const bindOrUnbindLinearElement = (
boundToElementIds,
unboundFromElementIds,
elementsMap,
scene,
);
const onlyUnbound = Array.from(unboundFromElementIds).filter(
@ -161,7 +160,6 @@ const bindOrUnbindLinearElementEdge = (
// 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") {
@ -571,8 +569,7 @@ const calculateFocusAndGap = (
// in explicitly.
export const updateBoundElements = (
changedElement: NonDeletedExcalidrawElement,
elementsMap: ElementsMap,
scene: Scene,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
options?: {
simultaneouslyUpdated?: readonly ExcalidrawElement[];
oldSize?: { width: number; height: number };
@ -658,7 +655,7 @@ export const updateBoundElements = (
LinearElementEditor.movePoints(
element,
updates,
scene,
elementsMap,
{
...(changedElement.id === element.startBinding?.elementId
? { startBinding: bindings.startBinding }

View file

@ -91,14 +91,9 @@ export const dragSelectedElements = (
updateElementCoords(pointerDownState, textElement, adjustedOffset);
}
}
updateBoundElements(
element,
scene.getElementsMapIncludingDeleted(),
scene,
{
simultaneouslyUpdated: Array.from(elementsToUpdate),
},
);
updateBoundElements(element, scene.getElementsMapIncludingDeleted(), {
simultaneouslyUpdated: Array.from(elementsToUpdate),
});
});
};

View file

@ -9,6 +9,7 @@ import type {
NonDeletedSceneElementsMap,
OrderedExcalidrawElement,
FixedPointBinding,
SceneElementsMap,
} from "./types";
import {
distance2d,
@ -290,7 +291,7 @@ export class LinearElementEditor {
isDragging: selectedIndex === lastClickedPoint,
},
],
scene,
elementsMap,
);
} else {
const newDraggingPointPosition = LinearElementEditor.createPointAt(
@ -326,7 +327,7 @@ export class LinearElementEditor {
isDragging: pointIndex === lastClickedPoint,
};
}),
scene,
elementsMap,
);
}
@ -420,7 +421,7 @@ export class LinearElementEditor {
: element.points[0],
},
],
scene,
elementsMap,
);
}
@ -876,13 +877,12 @@ export class LinearElementEditor {
scenePointerX: number,
scenePointerY: number,
appState: AppState,
scene: Scene,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
): LinearElementEditor | null {
if (!appState.editingLinearElement) {
return null;
}
const { elementId, lastUncommittedPoint } = appState.editingLinearElement;
const elementsMap = scene.getNonDeletedElementsMap();
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element) {
return appState.editingLinearElement;
@ -893,7 +893,11 @@ export class LinearElementEditor {
if (!event.altKey) {
if (lastPoint === lastUncommittedPoint) {
LinearElementEditor.deletePoints(element, [points.length - 1], scene);
LinearElementEditor.deletePoints(
element,
[points.length - 1],
elementsMap,
);
}
return {
...appState.editingLinearElement,
@ -939,14 +943,13 @@ export class LinearElementEditor {
point: newPoint,
},
],
scene,
elementsMap,
);
} else {
LinearElementEditor.addPoints(
element,
appState,
[{ point: newPoint }],
scene,
elementsMap,
);
}
return {
@ -1091,7 +1094,7 @@ export class LinearElementEditor {
const offsetY = points[0][1];
return {
points: points.map((point, _idx) => {
points: points.map((point) => {
return [point[0] - offsetX, point[1] - offsetY] as const;
}),
x: element.x + offsetX,
@ -1106,13 +1109,15 @@ export class LinearElementEditor {
mutateElement(element, LinearElementEditor.getNormalizedPoints(element));
}
static duplicateSelectedPoints(appState: AppState, scene: Scene) {
static duplicateSelectedPoints(
appState: AppState,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
) {
if (!appState.editingLinearElement) {
return false;
}
const { selectedPointsIndices, elementId } = appState.editingLinearElement;
const elementsMap = scene.getNonDeletedElementsMap();
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element || selectedPointsIndices === null) {
@ -1163,7 +1168,7 @@ export class LinearElementEditor {
point: [lastPoint[0] + 30, lastPoint[1] + 30],
},
],
scene,
elementsMap,
);
}
@ -1181,7 +1186,7 @@ export class LinearElementEditor {
static deletePoints(
element: NonDeleted<ExcalidrawLinearElement>,
pointIndices: readonly number[],
scene: Scene,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
) {
let offsetX = 0;
let offsetY = 0;
@ -1214,15 +1219,14 @@ export class LinearElementEditor {
nextPoints,
offsetX,
offsetY,
scene,
elementsMap,
);
}
static addPoints(
element: NonDeleted<ExcalidrawLinearElement>,
appState: AppState,
targetPoints: { point: Point }[],
scene: Scene,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
) {
const offsetX = 0;
const offsetY = 0;
@ -1233,14 +1237,14 @@ export class LinearElementEditor {
nextPoints,
offsetX,
offsetY,
scene,
elementsMap,
);
}
static movePoints(
element: NonDeleted<ExcalidrawLinearElement>,
targetPoints: { index: number; point: Point; isDragging?: boolean }[],
scene: Scene,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
otherUpdates?: {
startBinding?: PointBinding | null;
endBinding?: PointBinding | null;
@ -1296,7 +1300,7 @@ export class LinearElementEditor {
nextPoints,
offsetX,
offsetY,
scene,
elementsMap,
otherUpdates,
{
isDragging: targetPoints.reduce(
@ -1413,7 +1417,7 @@ export class LinearElementEditor {
nextPoints: readonly Point[],
offsetX: number,
offsetY: number,
scene: Scene,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
otherUpdates?: {
startBinding?: PointBinding | null;
endBinding?: PointBinding | null;
@ -1445,7 +1449,7 @@ export class LinearElementEditor {
mutateElbowArrow(
element,
scene,
elementsMap,
nextPoints,
[offsetX, offsetY],
bindings,

View file

@ -11,6 +11,8 @@ import type {
ExcalidrawTextElementWithContainer,
ExcalidrawImageElement,
ElementsMap,
NonDeletedSceneElementsMap,
SceneElementsMap,
} from "./types";
import type { Mutable } from "../utility-types";
import {
@ -69,7 +71,7 @@ export const transformElements = (
originalElements: PointerDownState["originalElements"],
transformHandleType: MaybeTransformHandleType,
selectedElements: readonly NonDeletedExcalidrawElement[],
elementsMap: ElementsMap,
elementsMap: SceneElementsMap,
shouldRotateWithDiscreteAngle: boolean,
shouldResizeFromCenter: boolean,
shouldMaintainAspectRatio: boolean,
@ -77,7 +79,6 @@ export const transformElements = (
pointerY: number,
centerX: number,
centerY: number,
scene: Scene,
) => {
if (selectedElements.length === 1) {
const [element] = selectedElements;
@ -90,7 +91,7 @@ export const transformElements = (
pointerY,
shouldRotateWithDiscreteAngle,
);
updateBoundElements(element, elementsMap, scene);
updateBoundElements(element, elementsMap);
}
} else if (isTextElement(element) && transformHandleType) {
resizeSingleTextElement(
@ -102,7 +103,7 @@ export const transformElements = (
pointerX,
pointerY,
);
updateBoundElements(element, elementsMap, scene);
updateBoundElements(element, elementsMap);
} else if (transformHandleType) {
resizeSingleElement(
originalElements,
@ -113,7 +114,6 @@ export const transformElements = (
shouldResizeFromCenter,
pointerX,
pointerY,
scene,
);
}
@ -129,7 +129,6 @@ export const transformElements = (
shouldRotateWithDiscreteAngle,
centerX,
centerY,
scene,
);
return true;
} else if (transformHandleType) {
@ -142,7 +141,6 @@ export const transformElements = (
shouldMaintainAspectRatio,
pointerX,
pointerY,
scene,
);
return true;
}
@ -434,12 +432,11 @@ export const resizeSingleElement = (
originalElements: PointerDownState["originalElements"],
shouldMaintainAspectRatio: boolean,
element: NonDeletedExcalidrawElement,
elementsMap: ElementsMap,
elementsMap: SceneElementsMap,
transformHandleDirection: TransformHandleDirection,
shouldResizeFromCenter: boolean,
pointerX: number,
pointerY: number,
scene: Scene,
) => {
const stateAtResizeStart = originalElements.get(element.id)!;
// Gets bounds corners
@ -710,7 +707,7 @@ export const resizeSingleElement = (
) {
mutateElement(element, resizedElement);
updateBoundElements(element, elementsMap, scene, {
updateBoundElements(element, elementsMap, {
oldSize: {
width: stateAtResizeStart.width,
height: stateAtResizeStart.height,
@ -734,13 +731,12 @@ export const resizeSingleElement = (
export const resizeMultipleElements = (
originalElements: PointerDownState["originalElements"],
selectedElements: readonly NonDeletedExcalidrawElement[],
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
transformHandleType: TransformHandleDirection,
shouldResizeFromCenter: boolean,
shouldMaintainAspectRatio: boolean,
pointerX: number,
pointerY: number,
scene: Scene,
) => {
// map selected elements to the original elements. While it never should
// happen that pointerDownState.originalElements won't contain the selected
@ -974,12 +970,19 @@ export const resizeMultipleElements = (
mutateElement(element, update, false);
if (isArrowElement(element) && isElbowArrow(element)) {
mutateElbowArrow(element, scene, element.points, undefined, undefined, {
informMutation: false,
});
mutateElbowArrow(
element,
elementsMap,
element.points,
undefined,
undefined,
{
informMutation: false,
},
);
}
updateBoundElements(element, elementsMap, scene, {
updateBoundElements(element, elementsMap, {
simultaneouslyUpdated: elementsToUpdate,
oldSize: { width: oldWidth, height: oldHeight },
});
@ -1004,13 +1007,12 @@ export const resizeMultipleElements = (
const rotateMultipleElements = (
originalElements: PointerDownState["originalElements"],
elements: readonly NonDeletedExcalidrawElement[],
elementsMap: ElementsMap,
elementsMap: SceneElementsMap,
pointerX: number,
pointerY: number,
shouldRotateWithDiscreteAngle: boolean,
centerX: number,
centerY: number,
scene: Scene,
) => {
let centerAngle =
(5 * Math.PI) / 2 + Math.atan2(pointerY - centerY, pointerX - centerX);
@ -1037,7 +1039,7 @@ const rotateMultipleElements = (
if (isArrowElement(element) && isElbowArrow(element)) {
const points = getArrowLocalFixedPoints(element, elementsMap);
mutateElbowArrow(element, scene, points);
mutateElbowArrow(element, elementsMap, points);
} else {
mutateElement(
element,
@ -1050,7 +1052,7 @@ const rotateMultipleElements = (
);
}
updateBoundElements(element, elementsMap, scene, {
updateBoundElements(element, elementsMap, {
simultaneouslyUpdated: elements,
});

View file

@ -45,7 +45,7 @@ describe("elbow arrow routing", () => {
elbowed: true,
}) as ExcalidrawElbowArrowElement;
scene.insertElement(arrow);
mutateElbowArrow(arrow, scene, [
mutateElbowArrow(arrow, scene.getNonDeletedElementsMap(), [
[-45 - arrow.x, -100.1 - arrow.y],
[45 - arrow.x, 99.9 - arrow.y],
]);
@ -98,7 +98,7 @@ describe("elbow arrow routing", () => {
expect(arrow.startBinding).not.toBe(null);
expect(arrow.endBinding).not.toBe(null);
mutateElbowArrow(arrow, scene, [
mutateElbowArrow(arrow, elementsMap, [
[0, 0],
[90, 200],
]);

View file

@ -10,7 +10,6 @@ import {
translatePoint,
} from "../math";
import { getSizeFromPoints } from "../points";
import type Scene from "../scene/Scene";
import type { Point } from "../types";
import { isAnyTrue, toBrandedType, tupleToCoors } from "../utils";
import {
@ -37,14 +36,10 @@ import { isBindableElement, isRectanguloidElement } from "./typeChecks";
import type {
ExcalidrawElbowArrowElement,
FixedPointBinding,
NonDeletedExcalidrawElement,
NonDeletedSceneElementsMap,
SceneElementsMap,
} from "./types";
import type {
ElementsMap,
ExcalidrawBindableElement,
OrderedExcalidrawElement,
} from "./types";
import type { ElementsMap, ExcalidrawBindableElement } from "./types";
type Node = {
f: number;
@ -67,7 +62,7 @@ const BASE_PADDING = 40;
export const mutateElbowArrow = (
arrow: ExcalidrawElbowArrowElement,
scene: Scene,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
nextPoints: readonly Point[],
offset?: Point,
otherUpdates?: {
@ -75,15 +70,11 @@ export const mutateElbowArrow = (
endBinding?: FixedPointBinding | null;
},
options?: {
changedElements?: Map<string, OrderedExcalidrawElement>;
isDragging?: boolean;
disableBinding?: boolean;
informMutation?: boolean;
},
) => {
const elements = getAllElements(scene, options?.changedElements);
const elementsMap = getAllElementsMap(scene, options?.changedElements);
const origStartGlobalPoint = translatePoint(nextPoints[0], [
arrow.x + (offset ? offset[0] : 0),
arrow.y + (offset ? offset[1] : 0),
@ -99,22 +90,9 @@ export const mutateElbowArrow = (
const endElement =
arrow.endBinding &&
getBindableElementForId(arrow.endBinding.elementId, elementsMap);
const hoveredStartElement = options?.isDragging
? getHoveredElementForBinding(
tupleToCoors(origStartGlobalPoint),
elements,
elementsMap,
true,
)
: startElement;
const hoveredEndElement = options?.isDragging
? getHoveredElementForBinding(
tupleToCoors(origEndGlobalPoint),
elements,
elementsMap,
true,
)
: endElement;
const [hoveredStartElement, hoveredEndElement] = options?.isDragging
? getHoveredElements(origStartGlobalPoint, origEndGlobalPoint, elementsMap)
: [startElement, endElement];
const startGlobalPoint = getGlobalPoint(
arrow.startBinding?.fixedPoint,
origStartGlobalPoint,
@ -895,7 +873,7 @@ const normalizedArrowElementUpdate = (
const offsetY = global[0][1];
const points = global.map(
(point, _idx) => [point[0] - offsetX, point[1] - offsetY] as const,
(point) => [point[0] - offsetX, point[1] - offsetY] as const,
);
return {
@ -935,32 +913,11 @@ const neighborIndexToHeading = (idx: number): Heading => {
return HEADING_LEFT;
};
const getAllElementsMap = (
scene: Scene,
changedElements?: Map<string, OrderedExcalidrawElement>,
): NonDeletedSceneElementsMap =>
changedElements
? toBrandedType<NonDeletedSceneElementsMap>(
new Map([...scene.getNonDeletedElementsMap(), ...changedElements]),
)
: scene.getNonDeletedElementsMap();
const getAllElements = (
scene: Scene,
changedElements?: Map<string, OrderedExcalidrawElement>,
): readonly NonDeletedExcalidrawElement[] =>
changedElements
? ([
...scene.getNonDeletedElements(),
...[...changedElements].map(([_, value]) => value),
] as NonDeletedExcalidrawElement[])
: scene.getNonDeletedElements();
const getGlobalPoint = (
fixedPointRatio: [number, number] | undefined | null,
initialPoint: Point,
otherPoint: Point,
elementsMap: NonDeletedSceneElementsMap,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
boundElement?: ExcalidrawBindableElement | null,
hoveredElement?: ExcalidrawBindableElement | null,
isDragging?: boolean,
@ -1016,7 +973,7 @@ const getSnapPoint = (
const getBindPointHeading = (
point: Point,
otherPoint: Point,
elementsMap: NonDeletedSceneElementsMap,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
hoveredElement: ExcalidrawBindableElement | null | undefined,
origPoint: Point,
) =>
@ -1034,3 +991,30 @@ const getBindPointHeading = (
elementsMap,
origPoint,
);
const getHoveredElements = (
origStartGlobalPoint: Point,
origEndGlobalPoint: Point,
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
) => {
// TODO: Might be a performance bottleneck and the Map type
// remembers the insertion order anyway...
const nonDeletedSceneElementsMap = toBrandedType<NonDeletedSceneElementsMap>(
new Map([...elementsMap].filter((el) => !el[1].isDeleted)),
);
const elements = Array.from(elementsMap.values());
return [
getHoveredElementForBinding(
tupleToCoors(origStartGlobalPoint),
elements,
nonDeletedSceneElementsMap,
true,
),
getHoveredElementForBinding(
tupleToCoors(origEndGlobalPoint),
elements,
nonDeletedSceneElementsMap,
true,
),
];
};