fix: make LinearElementEditor independent of scene (#7670)

* fix: make LinearElementEditor independent of scene

* more fixes

* pass elements and elementsMap to maybeBindBindableElement,getHoveredElementForBinding,bindingBorderTest,getElligibleElementsForBindableElementAndWhere,isLinearElementEligibleForNewBindingByBindable

* replace `ElementsMap` with `NonDeletedSceneElementsMap` & remove unused params

* fix lint

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Aakansha Doshi 2024-02-19 11:49:01 +05:30 committed by GitHub
parent 47f87f4ecb
commit 9013c84524
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 172 additions and 123 deletions

View file

@ -6,6 +6,7 @@ import {
PointBinding,
ExcalidrawElement,
ElementsMap,
NonDeletedSceneElementsMap,
} from "./types";
import { getElementAtPosition } from "../scene";
import { AppState } from "../types";
@ -67,7 +68,7 @@ export const bindOrUnbindLinearElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
startBindingElement: ExcalidrawBindableElement | null | "keep",
endBindingElement: ExcalidrawBindableElement | null | "keep",
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): void => {
const boundToElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
const unboundFromElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
@ -115,7 +116,7 @@ const bindOrUnbindLinearElementEdge = (
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
// Is mutated
unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): void => {
if (bindableElement !== "keep") {
if (bindableElement != null) {
@ -151,7 +152,8 @@ const bindOrUnbindLinearElementEdge = (
export const bindOrUnbindSelectedElements = (
selectedElements: NonDeleted<ExcalidrawElement>[],
elementsMap: ElementsMap,
elements: readonly ExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap,
): void => {
selectedElements.forEach((selectedElement) => {
if (isBindingElement(selectedElement)) {
@ -160,11 +162,13 @@ export const bindOrUnbindSelectedElements = (
getElligibleElementForBindingElement(
selectedElement,
"start",
elements,
elementsMap,
),
getElligibleElementForBindingElement(
selectedElement,
"end",
elements,
elementsMap,
),
elementsMap,
@ -177,16 +181,18 @@ export const bindOrUnbindSelectedElements = (
const maybeBindBindableElement = (
bindableElement: NonDeleted<ExcalidrawBindableElement>,
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): void => {
getElligibleElementsForBindableElementAndWhere(bindableElement).forEach(
([linearElement, where]) =>
bindOrUnbindLinearElement(
linearElement,
where === "end" ? "keep" : bindableElement,
where === "start" ? "keep" : bindableElement,
elementsMap,
),
getElligibleElementsForBindableElementAndWhere(
bindableElement,
elementsMap,
).forEach(([linearElement, where]) =>
bindOrUnbindLinearElement(
linearElement,
where === "end" ? "keep" : bindableElement,
where === "start" ? "keep" : bindableElement,
elementsMap,
),
);
};
@ -195,7 +201,7 @@ export const maybeBindLinearElement = (
appState: AppState,
scene: Scene,
pointerCoords: { x: number; y: number },
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): void => {
if (appState.startBoundElement != null) {
bindLinearElement(
@ -205,7 +211,11 @@ export const maybeBindLinearElement = (
elementsMap,
);
}
const hoveredElement = getHoveredElementForBinding(pointerCoords, scene);
const hoveredElement = getHoveredElementForBinding(
pointerCoords,
scene.getNonDeletedElements(),
elementsMap,
);
if (
hoveredElement != null &&
!isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
@ -222,7 +232,7 @@ export const bindLinearElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
hoveredElement: ExcalidrawBindableElement,
startOrEnd: "start" | "end",
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): void => {
mutateElement(linearElement, {
[startOrEnd === "start" ? "startBinding" : "endBinding"]: {
@ -274,7 +284,7 @@ export const isLinearElementSimpleAndAlreadyBound = (
export const unbindLinearElements = (
elements: NonDeleted<ExcalidrawElement>[],
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): void => {
elements.forEach((element) => {
if (isBindingElement(element)) {
@ -301,17 +311,14 @@ export const getHoveredElementForBinding = (
x: number;
y: number;
},
scene: Scene,
elements: readonly NonDeletedExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap,
): NonDeleted<ExcalidrawBindableElement> | null => {
const hoveredElement = getElementAtPosition(
scene.getNonDeletedElements(),
elements,
(element) =>
isBindableElement(element, false) &&
bindingBorderTest(
element,
pointerCoords,
scene.getNonDeletedElementsMap(),
),
bindingBorderTest(element, pointerCoords, elementsMap),
);
return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
};
@ -320,7 +327,7 @@ const calculateFocusAndGap = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
hoveredElement: ExcalidrawBindableElement,
startOrEnd: "start" | "end",
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): { focus: number; gap: number } => {
const direction = startOrEnd === "start" ? -1 : 1;
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
@ -539,33 +546,47 @@ const maybeCalculateNewGapWhenScaling = (
// TODO: this is a bottleneck, optimise
export const getEligibleElementsForBinding = (
elements: NonDeleted<ExcalidrawElement>[],
elementsMap: ElementsMap,
selectedElements: NonDeleted<ExcalidrawElement>[],
elements: readonly ExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap,
): SuggestedBinding[] => {
const includedElementIds = new Set(elements.map(({ id }) => id));
return elements.flatMap((element) =>
isBindingElement(element, false)
const includedElementIds = new Set(selectedElements.map(({ id }) => id));
return selectedElements.flatMap((selectedElement) =>
isBindingElement(selectedElement, false)
? (getElligibleElementsForBindingElement(
element as NonDeleted<ExcalidrawLinearElement>,
selectedElement as NonDeleted<ExcalidrawLinearElement>,
elements,
elementsMap,
).filter(
(element) => !includedElementIds.has(element.id),
) as SuggestedBinding[])
: isBindableElement(element, false)
? getElligibleElementsForBindableElementAndWhere(element).filter(
(binding) => !includedElementIds.has(binding[0].id),
)
: isBindableElement(selectedElement, false)
? getElligibleElementsForBindableElementAndWhere(
selectedElement,
elementsMap,
).filter((binding) => !includedElementIds.has(binding[0].id))
: [],
);
};
const getElligibleElementsForBindingElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
elementsMap: ElementsMap,
elements: readonly ExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap,
): NonDeleted<ExcalidrawBindableElement>[] => {
return [
getElligibleElementForBindingElement(linearElement, "start", elementsMap),
getElligibleElementForBindingElement(linearElement, "end", elementsMap),
getElligibleElementForBindingElement(
linearElement,
"start",
elements,
elementsMap,
),
getElligibleElementForBindingElement(
linearElement,
"end",
elements,
elementsMap,
),
].filter(
(element): element is NonDeleted<ExcalidrawBindableElement> =>
element != null,
@ -575,18 +596,20 @@ const getElligibleElementsForBindingElement = (
const getElligibleElementForBindingElement = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
startOrEnd: "start" | "end",
elementsMap: ElementsMap,
elements: readonly ExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap,
): NonDeleted<ExcalidrawBindableElement> | null => {
return getHoveredElementForBinding(
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
Scene.getScene(linearElement)!,
elements,
elementsMap,
);
};
const getLinearElementEdgeCoors = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
startOrEnd: "start" | "end",
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): { x: number; y: number } => {
const index = startOrEnd === "start" ? 0 : -1;
return tupleToCoors(
@ -600,6 +623,7 @@ const getLinearElementEdgeCoors = (
const getElligibleElementsForBindableElementAndWhere = (
bindableElement: NonDeleted<ExcalidrawBindableElement>,
elementsMap: NonDeletedSceneElementsMap,
): SuggestedPointBinding[] => {
const scene = Scene.getScene(bindableElement)!;
return scene
@ -612,13 +636,13 @@ const getElligibleElementsForBindableElementAndWhere = (
element,
"start",
bindableElement,
scene.getNonDeletedElementsMap(),
elementsMap,
);
const canBindEnd = isLinearElementEligibleForNewBindingByBindable(
element,
"end",
bindableElement,
scene.getNonDeletedElementsMap(),
elementsMap,
);
if (!canBindStart && !canBindEnd) {
return null;
@ -636,7 +660,7 @@ const isLinearElementEligibleForNewBindingByBindable = (
linearElement: NonDeleted<ExcalidrawLinearElement>,
startOrEnd: "start" | "end",
bindableElement: NonDeleted<ExcalidrawBindableElement>,
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): boolean => {
const existingBinding =
linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"];

View file

@ -6,6 +6,8 @@ import {
ExcalidrawBindableElement,
ExcalidrawTextElementWithContainer,
ElementsMap,
NonDeletedExcalidrawElement,
NonDeletedSceneElementsMap,
} from "./types";
import {
distance2d,
@ -36,7 +38,6 @@ import {
import { mutateElement } from "./mutateElement";
import History from "../history";
import Scene from "../scene/Scene";
import {
bindOrUnbindLinearElement,
getHoveredElementForBinding,
@ -86,11 +87,10 @@ export class LinearElementEditor {
public readonly hoverPointIndex: number;
public readonly segmentMidPointHoveredCoords: Point | null;
constructor(element: NonDeleted<ExcalidrawLinearElement>, scene: Scene) {
constructor(element: NonDeleted<ExcalidrawLinearElement>) {
this.elementId = element.id as string & {
_brand: "excalidrawLinearElementId";
};
Scene.mapElementToScene(this.elementId, scene);
LinearElementEditor.normalizePoints(element);
this.selectedPointsIndices = null;
@ -123,8 +123,11 @@ export class LinearElementEditor {
* @param id the `elementId` from the instance of this class (so that we can
* statically guarantee this method returns an ExcalidrawLinearElement)
*/
static getElement(id: InstanceType<typeof LinearElementEditor>["elementId"]) {
const element = Scene.getScene(id)?.getNonDeletedElement(id);
static getElement(
id: InstanceType<typeof LinearElementEditor>["elementId"],
elementsMap: ElementsMap,
) {
const element = elementsMap.get(id);
if (element) {
return element as NonDeleted<ExcalidrawLinearElement>;
}
@ -135,7 +138,7 @@ export class LinearElementEditor {
event: PointerEvent,
appState: AppState,
setState: React.Component<any, AppState>["setState"],
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
) {
if (
!appState.editingLinearElement ||
@ -146,7 +149,7 @@ export class LinearElementEditor {
const { editingLinearElement } = appState;
const { selectedPointsIndices, elementId } = editingLinearElement;
const element = LinearElementEditor.getElement(elementId);
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element) {
return false;
}
@ -197,13 +200,13 @@ export class LinearElementEditor {
pointSceneCoords: { x: number; y: number }[],
) => void,
linearElementEditor: LinearElementEditor,
elementsMap: ElementsMap,
elementsMap: NonDeletedSceneElementsMap,
): boolean {
if (!linearElementEditor) {
return false;
}
const { selectedPointsIndices, elementId } = linearElementEditor;
const element = LinearElementEditor.getElement(elementId);
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element) {
return false;
}
@ -331,11 +334,12 @@ export class LinearElementEditor {
event: PointerEvent,
editingLinearElement: LinearElementEditor,
appState: AppState,
elementsMap: ElementsMap,
elements: readonly NonDeletedExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap,
): LinearElementEditor {
const { elementId, selectedPointsIndices, isDragging, pointerDownState } =
editingLinearElement;
const element = LinearElementEditor.getElement(elementId);
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element) {
return editingLinearElement;
}
@ -376,7 +380,8 @@ export class LinearElementEditor {
elementsMap,
),
),
Scene.getScene(element)!,
elements,
elementsMap,
)
: null;
@ -490,7 +495,7 @@ export class LinearElementEditor {
elementsMap: ElementsMap,
) => {
const { elementId } = linearElementEditor;
const element = LinearElementEditor.getElement(elementId);
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element) {
return null;
}
@ -614,6 +619,7 @@ export class LinearElementEditor {
) {
const element = LinearElementEditor.getElement(
linearElementEditor.elementId,
elementsMap,
);
if (!element) {
return -1;
@ -639,7 +645,8 @@ export class LinearElementEditor {
history: History,
scenePointer: { x: number; y: number },
linearElementEditor: LinearElementEditor,
elementsMap: ElementsMap,
elements: readonly NonDeletedExcalidrawElement[],
elementsMap: NonDeletedSceneElementsMap,
): {
didAddPoint: boolean;
hitElement: NonDeleted<ExcalidrawElement> | null;
@ -656,7 +663,7 @@ export class LinearElementEditor {
}
const { elementId } = linearElementEditor;
const element = LinearElementEditor.getElement(elementId);
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element) {
return ret;
@ -709,7 +716,8 @@ export class LinearElementEditor {
lastUncommittedPoint: null,
endBindingElement: getHoveredElementForBinding(
scenePointer,
Scene.getScene(element)!,
elements,
elementsMap,
),
};
@ -813,7 +821,7 @@ export class LinearElementEditor {
return null;
}
const { elementId, lastUncommittedPoint } = appState.editingLinearElement;
const element = LinearElementEditor.getElement(elementId);
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element) {
return appState.editingLinearElement;
}
@ -1020,14 +1028,14 @@ export class LinearElementEditor {
mutateElement(element, LinearElementEditor.getNormalizedPoints(element));
}
static duplicateSelectedPoints(appState: AppState) {
static duplicateSelectedPoints(appState: AppState, elementsMap: ElementsMap) {
if (!appState.editingLinearElement) {
return false;
}
const { selectedPointsIndices, elementId } = appState.editingLinearElement;
const element = LinearElementEditor.getElement(elementId);
const element = LinearElementEditor.getElement(elementId, elementsMap);
if (!element || selectedPointsIndices === null) {
return false;
@ -1189,9 +1197,11 @@ export class LinearElementEditor {
linearElementEditor: LinearElementEditor,
pointerCoords: PointerCoords,
appState: AppState,
elementsMap: ElementsMap,
) {
const element = LinearElementEditor.getElement(
linearElementEditor.elementId,
elementsMap,
);
if (!element) {
@ -1234,6 +1244,7 @@ export class LinearElementEditor {
) {
const element = LinearElementEditor.getElement(
linearElementEditor.elementId,
elementsMap,
);
if (!element) {
return;