reactronic
Version:
Reactronic - Transactional Reactive State Management
147 lines (146 loc) • 5.93 kB
JavaScript
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;
}