mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
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:
parent
47f87f4ecb
commit
9013c84524
13 changed files with 172 additions and 123 deletions
|
@ -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"];
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue