refactor: decoupling global Scene state part-1 (#7577)

This commit is contained in:
David Luzar 2024-01-22 00:23:02 +01:00 committed by GitHub
parent 740a165452
commit 0415c616b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 630 additions and 384 deletions

View file

@ -3,14 +3,18 @@ import {
NonDeletedExcalidrawElement,
NonDeleted,
ExcalidrawFrameLikeElement,
ElementsMapOrArray,
NonDeletedElementsMap,
SceneElementsMap,
} from "../element/types";
import { getNonDeletedElements, isNonDeletedElement } from "../element";
import { isNonDeletedElement } from "../element";
import { LinearElementEditor } from "../element/linearElementEditor";
import { isFrameLikeElement } from "../element/typeChecks";
import { getSelectedElements } from "./selection";
import { AppState } from "../types";
import { Assert, SameType } from "../utility-types";
import { randomInteger } from "../random";
import { toBrandedType } from "../utils";
type ElementIdKey = InstanceType<typeof LinearElementEditor>["elementId"];
type ElementKey = ExcalidrawElement | ElementIdKey;
@ -20,6 +24,20 @@ type SceneStateCallbackRemover = () => void;
type SelectionHash = string & { __brand: "selectionHash" };
const getNonDeletedElements = <T extends ExcalidrawElement>(
allElements: readonly T[],
) => {
const elementsMap = new Map() as NonDeletedElementsMap;
const elements: T[] = [];
for (const element of allElements) {
if (!element.isDeleted) {
elements.push(element as NonDeleted<T>);
elementsMap.set(element.id, element as NonDeletedExcalidrawElement);
}
}
return { elementsMap, elements };
};
const hashSelectionOpts = (
opts: Parameters<InstanceType<typeof Scene>["getSelectedElements"]>[0],
) => {
@ -102,11 +120,13 @@ class Scene {
private callbacks: Set<SceneStateCallback> = new Set();
private nonDeletedElements: readonly NonDeletedExcalidrawElement[] = [];
private nonDeletedElementsMap: NonDeletedElementsMap =
new Map() as NonDeletedElementsMap;
private elements: readonly ExcalidrawElement[] = [];
private nonDeletedFramesLikes: readonly NonDeleted<ExcalidrawFrameLikeElement>[] =
[];
private frames: readonly ExcalidrawFrameLikeElement[] = [];
private elementsMap = new Map<ExcalidrawElement["id"], ExcalidrawElement>();
private elementsMap = toBrandedType<SceneElementsMap>(new Map());
private selectedElementsCache: {
selectedElementIds: AppState["selectedElementIds"] | null;
elements: readonly NonDeletedExcalidrawElement[] | null;
@ -118,6 +138,14 @@ class Scene {
};
private versionNonce: number | undefined;
getElementsMapIncludingDeleted() {
return this.elementsMap;
}
getNonDeletedElementsMap() {
return this.nonDeletedElementsMap;
}
getElementsIncludingDeleted() {
return this.elements;
}
@ -138,7 +166,7 @@ class Scene {
* scene state. This in effect will likely result in cache-miss, and
* the cache won't be updated in this case.
*/
elements?: readonly ExcalidrawElement[];
elements?: ElementsMapOrArray;
// selection-related options
includeBoundTextElement?: boolean;
includeElementsInFrames?: boolean;
@ -227,23 +255,27 @@ class Scene {
return didChange;
}
replaceAllElements(
nextElements: readonly ExcalidrawElement[],
mapElementIds = true,
) {
this.elements = nextElements;
replaceAllElements(nextElements: ElementsMapOrArray, mapElementIds = true) {
this.elements =
// ts doesn't like `Array.isArray` of `instanceof Map`
nextElements instanceof Array
? nextElements
: Array.from(nextElements.values());
const nextFrameLikes: ExcalidrawFrameLikeElement[] = [];
this.elementsMap.clear();
nextElements.forEach((element) => {
this.elements.forEach((element) => {
if (isFrameLikeElement(element)) {
nextFrameLikes.push(element);
}
this.elementsMap.set(element.id, element);
Scene.mapElementToScene(element, this);
Scene.mapElementToScene(element, this, mapElementIds);
});
this.nonDeletedElements = getNonDeletedElements(this.elements);
const nonDeletedElements = getNonDeletedElements(this.elements);
this.nonDeletedElements = nonDeletedElements.elements;
this.nonDeletedElementsMap = nonDeletedElements.elementsMap;
this.frames = nextFrameLikes;
this.nonDeletedFramesLikes = getNonDeletedElements(this.frames);
this.nonDeletedFramesLikes = getNonDeletedElements(this.frames).elements;
this.informMutation();
}
@ -332,6 +364,22 @@ class Scene {
getElementIndex(elementId: string) {
return this.elements.findIndex((element) => element.id === elementId);
}
getContainerElement = (
element:
| (ExcalidrawElement & {
containerId: ExcalidrawElement["id"] | null;
})
| null,
) => {
if (!element) {
return null;
}
if (element.containerId) {
return this.getElement(element.containerId) || null;
}
return null;
};
}
export default Scene;