UNPKG

@infinite-canvas-tutorial/webcomponents

Version:
186 lines 8.38 kB
/** * Borrow from https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/change.ts#L399 */ var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { Delta } from './Delta'; export class ElementsChange { static empty() { return ElementsChange.create(new Map(), new Map(), new Map()); } static stripIrrelevantProps(partial) { const { id, version, versionNonce } = partial, strippedPartial = __rest(partial, ["id", "version", "versionNonce"]); return strippedPartial; } /** * Calculates the `Delta`s between the previous and next set of elements. * * @param prevElements - Map representing the previous state of elements. * @param nextElements - Map representing the next state of elements. * * @returns `ElementsChange` instance representing the `Delta` changes between the two sets of elements. */ static calculate(prevElements, nextElements) { if (prevElements === nextElements) { return ElementsChange.empty(); } const added = new Map(); const removed = new Map(); const updated = new Map(); // this might be needed only in same edge cases, like during collab, when `isDeleted` elements get removed or when we (un)intentionally remove the elements for (const prevElement of prevElements.values()) { const nextElement = nextElements.get(prevElement.id); if (!nextElement) { const deleted = Object.assign(Object.assign({}, prevElement), { isDeleted: false }); const inserted = { isDeleted: true }; const delta = Delta.create(deleted, inserted, ElementsChange.stripIrrelevantProps); removed.set(prevElement.id, delta); } } for (const nextElement of nextElements.values()) { const prevElement = prevElements.get(nextElement.id); if (!prevElement) { const deleted = { isDeleted: true }; const inserted = Object.assign(Object.assign({}, nextElement), { isDeleted: false }); const delta = Delta.create(deleted, inserted, ElementsChange.stripIrrelevantProps); added.set(nextElement.id, delta); continue; } if (prevElement.versionNonce !== nextElement.versionNonce) { const delta = Delta.calculate(prevElement, nextElement, ElementsChange.stripIrrelevantProps); if ( // making sure we don't get here some non-boolean values (i.e. undefined, null, etc.) typeof prevElement.isDeleted === 'boolean' && typeof nextElement.isDeleted === 'boolean' && prevElement.isDeleted !== nextElement.isDeleted) { // notice that other props could have been updated as well if (prevElement.isDeleted && !nextElement.isDeleted) { added.set(nextElement.id, delta); } else { removed.set(nextElement.id, delta); } continue; } // making sure there are at least some changes if (!Delta.isEmpty(delta)) { updated.set(nextElement.id, delta); } } } return ElementsChange.create(added, removed, updated); } static create(added, removed, updated, options = { shouldRedistribute: false }) { let change; if (options.shouldRedistribute) { const nextAdded = new Map(); const nextRemoved = new Map(); const nextUpdated = new Map(); const deltas = [...added, ...removed, ...updated]; for (const [id, delta] of deltas) { if (this.satisfiesAddition(delta)) { nextAdded.set(id, delta); } else if (this.satisfiesRemoval(delta)) { nextRemoved.set(id, delta); } else { nextUpdated.set(id, delta); } } change = new ElementsChange(nextAdded, nextRemoved, nextUpdated); } else { change = new ElementsChange(added, removed, updated); } return change; } constructor(added, removed, updated) { this.added = added; this.removed = removed; this.updated = updated; } inverse() { const inverseInternal = (deltas) => { const inversedDeltas = new Map(); for (const [id, delta] of deltas.entries()) { inversedDeltas.set(id, Delta.create(delta.inserted, delta.deleted)); } return inversedDeltas; }; const added = inverseInternal(this.added); const removed = inverseInternal(this.removed); const updated = inverseInternal(this.updated); // notice we inverse removed with added not to break the invariants return ElementsChange.create(removed, added, updated); } /** * Update delta/s based on the existing elements. * * @param elements current elements * @param modifierOptions defines which of the delta (`deleted` or `inserted`) will be updated * @returns new instance with modified delta/s */ applyLatestChanges(elements) { const modifier = (element) => (partial) => { // (element: OrderedExcalidrawElement) => (partial: ElementPartial) => { const latestPartial = {}; for (const key of Object.keys(partial)) { // do not update following props: // - `boundElements`, as it is a reference value which is postprocessed to contain only deleted/inserted keys switch (key) { // case 'boundElements': // latestPartial[key] = partial[key]; // break; default: latestPartial[key] = element[key]; } } return latestPartial; }; const applyLatestChangesInternal = (deltas) => { const modifiedDeltas = new Map(); for (const [id, delta] of deltas.entries()) { const existingElement = elements.get(id); if (existingElement) { const modifiedDelta = Delta.create(delta.deleted, delta.inserted, modifier(existingElement), 'inserted'); modifiedDeltas.set(id, modifiedDelta); } else { modifiedDeltas.set(id, delta); } } return modifiedDeltas; }; const added = applyLatestChangesInternal(this.added); const removed = applyLatestChangesInternal(this.removed); const updated = applyLatestChangesInternal(this.updated); return ElementsChange.create(added, removed, updated, { shouldRedistribute: true, // redistribute the deltas as `isDeleted` could have been updated }); } applyTo(elements, snapshot) { const nextElements = new Map(elements); return [nextElements, false]; } isEmpty() { return (this.added.size === 0 && this.removed.size === 0 && this.updated.size === 0); } } ElementsChange.satisfiesAddition = ({ deleted, inserted, }) => // dissallowing added as "deleted", which could cause issues when resolving conflicts deleted.isDeleted === true && !inserted.isDeleted; ElementsChange.satisfiesRemoval = ({ deleted, inserted, }) => !deleted.isDeleted && inserted.isDeleted === true; ElementsChange.satisfiesUpdate = ({ deleted, inserted, }) => !!deleted.isDeleted === !!inserted.isDeleted; //# sourceMappingURL=ElementsChange.js.map