@infinite-canvas-tutorial/webcomponents
Version:
WebComponents UI implementation
166 lines • 6.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.newElementWith = exports.Snapshot = void 0;
const ecs_1 = require("@infinite-canvas-tutorial/ecs");
const context_1 = require("../context");
const Delta_1 = require("./Delta");
class Snapshot {
constructor(elements, appState, meta = {
didElementsChange: false,
didAppStateChange: false,
isEmpty: false,
}) {
this.elements = elements;
this.appState = appState;
this.meta = meta;
}
static empty() {
return new Snapshot(new Map(), (0, context_1.getDefaultAppState)(), {
didElementsChange: false,
didAppStateChange: false,
isEmpty: true,
});
}
// public isEmpty() {
// return this.meta.isEmpty;
// }
/**
* Efficiently clone the existing snapshot, only if we detected changes.
*
* @returns same instance if there are no changes detected, new instance otherwise.
*/
maybeClone(elements, appState) {
const nextElementsSnapshot = this.maybeCreateElementsSnapshot(elements);
const nextAppStateSnapshot = this.maybeCreateAppStateSnapshot(appState);
let didElementsChange = false;
let didAppStateChange = false;
if (this.elements !== nextElementsSnapshot) {
didElementsChange = true;
}
if (this.appState !== nextAppStateSnapshot) {
didAppStateChange = true;
}
if (!didElementsChange && !didAppStateChange) {
return this;
}
const snapshot = new Snapshot(nextElementsSnapshot, nextAppStateSnapshot, {
didElementsChange,
didAppStateChange,
});
return snapshot;
}
maybeCreateAppStateSnapshot(appState) {
if (!appState) {
return this.appState;
}
// Not watching over everything from the app state, just the relevant props
// const nextAppStateSnapshot = !isObservedAppState(appState)
// ? getObservedAppState(appState)
// : appState;
const nextAppStateSnapshot = appState;
const didAppStateChange = this.detectChangedAppState(nextAppStateSnapshot);
if (!didAppStateChange) {
return this.appState;
}
return nextAppStateSnapshot;
}
detectChangedAppState(nextObservedAppState) {
return !(0, Delta_1.isShallowEqual)(this.appState, nextObservedAppState, {
// selectedElementIds: isShallowEqual,
// selectedGroupIds: isShallowEqual,
});
}
maybeCreateElementsSnapshot(elements) {
if (!elements) {
return this.elements;
}
const didElementsChange = this.detectChangedElements(elements);
if (!didElementsChange) {
return this.elements;
}
const elementsSnapshot = this.createElementsSnapshot(elements);
return elementsSnapshot;
}
/**
* Detect if there any changed elements.
*
* NOTE: we shouldn't just use `sceneVersionNonce` instead, as we need to call this before the scene updates.
*/
detectChangedElements(nextElements) {
if (this.elements === nextElements) {
return false;
}
if (this.elements.size !== nextElements.size) {
return true;
}
// loop from right to left as changes are likelier to happen on new elements
const keys = Array.from(nextElements.keys());
for (let i = keys.length - 1; i >= 0; i--) {
const prev = this.elements.get(keys[i]);
const next = nextElements.get(keys[i]);
if (!prev ||
!next ||
prev.id !== next.id ||
prev.versionNonce !== next.versionNonce) {
return true;
}
}
return false;
}
/**
* Perform structural clone, cloning only elements that changed.
*/
createElementsSnapshot(nextElements) {
const clonedElements = new Map();
for (const [id, prevElement] of this.elements.entries()) {
// Clone previous elements, never delete, in case nextElements would be just a subset of previous elements
// i.e. during collab, persist or whenenever isDeleted elements get cleared
if (!nextElements.get(id)) {
// When we cannot find the prev element in the next elements, we mark it as deleted
clonedElements.set(id, (0, exports.newElementWith)(prevElement, { isDeleted: true }));
}
else {
clonedElements.set(id, prevElement);
}
}
for (const [id, nextElement] of nextElements.entries()) {
const prevElement = clonedElements.get(id);
// At this point our elements are reconcilled already, meaning the next element is always newer
if (!prevElement || // element was added
(prevElement && prevElement.versionNonce !== nextElement.versionNonce) // element was updated
) {
// @see https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore?tab=readme-ov-file#_clonedeep
clonedElements.set(id, structuredClone(nextElement));
}
}
return clonedElements;
}
}
exports.Snapshot = Snapshot;
/**
* @see https://github.com/excalidraw/excalidraw/blob/ab89d4c16f53bd1e06cb980c600f0952b7a3d7d3/packages/excalidraw/element/mutateElement.ts#L152
*/
const newElementWith = (element, updates,
/** pass `true` to always regenerate */
force = false) => {
let didChange = false;
for (const key in updates) {
const value = updates[key];
if (typeof value !== 'undefined') {
if (element[key] === value &&
// if object, always update because its attrs could have changed
(typeof value !== 'object' || value === null)) {
continue;
}
didChange = true;
}
}
if (!didChange && !force) {
return element;
}
return Object.assign(Object.assign(Object.assign({}, element), updates), {
// updated: getUpdatedTimestamp(),
version: element.version + 1, versionNonce: (0, ecs_1.randomInteger)() });
};
exports.newElementWith = newElementWith;
//# sourceMappingURL=Snapshot.js.map