UNPKG

reactronic

Version:

Reactronic - Transactional Reactive State Management

147 lines (146 loc) 5.93 kB
import { misuse } from "../util/Dbg.js"; import { Isolation } from "../Enums.js"; import { SxObject } from "./Mvcc.js"; import { Meta, ContentFootprint } from "./Data.js"; import { Changeset, EMPTY_OBJECT_VERSION } from "./Changeset.js"; import { Transaction } from "./Transaction.js"; import { Sealant } from "../util/Sealant.js"; export class Journal extends SxObject { static create() { return new JournalImpl(); } } export class JournalImpl extends Journal { constructor() { super(...arguments); this._capacity = 5; this._edits = []; this._unsaved = new Map(); this._position = 0; } get capacity() { return this._capacity; } set capacity(value) { this._capacity = value; if (value < this._edits.length) this._edits.splice(0, this._edits.length - value); } get edits() { return this._edits; } get unsaved() { return this._unsaved; } get canUndo() { return this._edits.length > 0 && this._position > 0; } get canRedo() { return this._position < this._edits.length; } edited(p) { Transaction.run({ hint: "EditJournal.edited", isolation: Isolation.disjoinFromOuterAndInnerTransactions }, () => { const items = this._edits = this._edits.toMutable(); if (items.length >= this._capacity) items.shift(); else items.splice(this._position); this.mergePatchToUnsaved(p, false); items.push(p); this._position = items.length; }); } saved(patch) { if (this._unsaved === patch) this._unsaved = new Map(); else throw misuse("not implemented"); } undo(count = 1) { Transaction.run({ hint: "Journal.undo", isolation: Isolation.disjoinFromOuterAndInnerTransactions }, () => { let i = this._position - 1; while (i >= 0 && count > 0) { const patch = this._edits[i]; JournalImpl.applyPatch(patch, true); this.mergePatchToUnsaved(patch, true); i--, count--; } this._position = i + 1; }); } redo(count = 1) { Transaction.run({ hint: "Journal.redo", isolation: Isolation.disjoinFromOuterAndInnerTransactions }, () => { let i = this._position; while (i < this._edits.length && count > 0) { const patch = this._edits[i]; JournalImpl.applyPatch(patch, false); this.mergePatchToUnsaved(patch, false); i++, count--; } this._position = i; }); } static buildPatch(hint, items) { const patch = new Map(); items.forEach((ov, h) => { const op = new Map(); const former = ov.former.objectVersion !== EMPTY_OBJECT_VERSION ? ov.former.objectVersion.data : undefined; ov.changes.forEach(fk => { const vp = { fieldKey: fk, patchKind: "update", freshContent: unseal(ov.data[fk]), formerContent: undefined, }; if (former) vp.formerContent = unseal(former[fk]); op.set(fk, vp); }); if (!former) { const vp = { fieldKey: Meta.Revision, patchKind: "remove", freshContent: Meta.Undefined, formerContent: undefined, }; op.set(Meta.Revision, vp); } patch.set(h.proxy, op); }); return patch; } static applyPatch(patch, undoing) { const ctx = Changeset.edit(); patch.forEach((op, obj) => { const h = Meta.get(obj, Meta.Handle); const rev = op.get(Meta.Revision); const disposed = rev && (undoing ? rev.formerContent : rev.freshContent) === Meta.Undefined; if (!disposed) { op.forEach((vp, fk) => { const content = undoing ? vp.formerContent : vp.freshContent; const ov = ctx.getEditableObjectVersion(h, fk, content); if (ov.changeset === ctx) { ov.data[fk] = new ContentFootprint(content, ctx.id); const existing = ov.former.objectVersion.data[fk]; Changeset.markEdited(existing, content, existing !== content, ov, fk, h); } }); } else Changeset.doDispose(ctx, h); }); } mergePatchToUnsaved(patch, undoing) { const unsaved = this._unsaved = this._unsaved.toMutable(); patch.forEach((op, obj) => { let result = unsaved.get(obj); if (!result) unsaved.set(obj, result = new Map()); op.forEach((vp, fk) => { let merged = result.get(fk); if (!merged) result.set(fk, merged = { fieldKey: fk, patchKind: "update", freshContent: undefined, formerContent: undefined, }); const value = undoing ? vp.formerContent : vp.freshContent; const former = undoing ? vp.freshContent : vp.formerContent; if (value !== merged.formerContent) { merged.freshContent = value; merged.formerContent = former; } else { result.delete(fk); if (result.size === 0) unsaved.delete(obj); } }); }); } } function unseal(cf) { const result = cf.content; const createCopy = result === null || result === void 0 ? void 0 : result[Sealant.CreateCopy]; return createCopy !== undefined ? createCopy.call(result) : result; }