reactronic-dom
Version:
Reactronic DOM - Transactional Reactive Front-End Development Framework
401 lines (400 loc) • 14.5 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { reaction, nonreactive, Transaction, options, Reentrance, Rx, Collection } from 'reactronic';
export class RxNode {
render() {
return this.renderer(this.element, this);
}
get isInitialRendering() {
return this.stamp === 2;
}
static launch(render) {
gSysRoot.self.renderer = render;
prepareThenRunRender(gSysRoot, false, false);
}
static get current() {
return gContext.self;
}
static shuffleChildrenRendering(shuffle) {
gContext.self.shuffle = shuffle;
}
static renderChildrenThenDo(action) {
runRenderChildrenThenDo(undefined, action);
}
static forAllNodesDo(action) {
forEachChildRecursively(gSysRoot, action);
}
static emit(name, triggers, inline, renderer, priority, monitor, throttling, logging, factory) {
const parent = gContext.self;
const children = parent.children;
const item = children.claim(name);
let node;
if (item) {
node = item.self;
if (node.factory !== factory && factory !== undefined)
throw new Error(`changing node type is not yet supported: "${node.factory.name}" -> "${factory === null || factory === void 0 ? void 0 : factory.name}"`);
if (node.inline || !triggersAreEqual(node.triggers, triggers))
node.triggers = triggers;
node.renderer = renderer;
node.priority = priority !== null && priority !== void 0 ? priority : 0;
}
else {
node = new RxNodeImpl(name, factory !== null && factory !== void 0 ? factory : NodeFactory.default, inline !== null && inline !== void 0 ? inline : false, parent, triggers, renderer, undefined, priority, monitor, throttling, logging);
node.item = children.add(node);
RxNodeImpl.grandCount++;
if (!node.inline)
RxNodeImpl.disposableCount++;
}
return node;
}
static getDefaultLoggingOptions() {
return RxNodeImpl.logging;
}
static setDefaultLoggingOptions(logging) {
RxNodeImpl.logging = logging;
}
}
RxNode.frameDuration = 10;
const NOP = () => { };
export class NodeFactory {
constructor(name, strict) {
this.name = name;
this.strict = strict;
}
initialize(node, element) {
const impl = node;
impl.element = element;
}
finalize(node, isLeader) {
const impl = node;
impl.element = undefined;
return isLeader;
}
arrange(node, strict) {
}
render(node) {
let result;
if (node.wrapper)
result = node.wrapper(node.element, node);
else
result = node.render();
return result;
}
}
NodeFactory.default = new NodeFactory('default', false);
export class StaticNodeFactory extends NodeFactory {
constructor(name, sequential, element) {
super(name, sequential);
this.element = element;
}
initialize(node, element) {
super.initialize(node, this.element);
}
}
function getNodeName(node) {
return node.stamp >= 0 ? node.name : undefined;
}
class RxNodeImpl extends RxNode {
constructor(name, factory, inline, parent, triggers, renderer, wrapper, priority, monitor, throttling, logging) {
super();
this.name = name;
this.factory = factory;
this.inline = inline;
this.triggers = triggers;
this.renderer = renderer;
this.wrapper = wrapper;
this.monitor = monitor;
this.throttling = throttling !== null && throttling !== void 0 ? throttling : -1;
this.logging = logging !== null && logging !== void 0 ? logging : RxNodeImpl.logging;
this.priority = priority !== null && priority !== void 0 ? priority : 0;
this.shuffle = false;
this.model = undefined;
this.level = parent.level + 1;
this.parent = parent;
this.children = new Collection(factory.strict, getNodeName);
this.item = undefined;
this.stamp = 0;
this.element = undefined;
}
autorender(_triggers) {
runRender(this.item);
}
wrapBy(renderer) {
this.wrapper = renderer;
return this;
}
}
RxNodeImpl.grandCount = 0;
RxNodeImpl.disposableCount = 0;
RxNodeImpl.logging = undefined;
__decorate([
reaction,
options({
reentrance: Reentrance.CancelAndWaitPrevious,
triggeringArgs: true,
noSideEffects: false,
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], RxNodeImpl.prototype, "autorender", null);
function runRenderChildrenThenDo(error, action) {
const context = gContext;
const node = context.self;
const children = node.children;
if (children.isMergeInProgress) {
let promised = undefined;
try {
children.endMerge(error);
for (const child of children.removedItems(true))
doFinalize(child, true);
const strict = children.strict;
let p1 = undefined;
let p2 = undefined;
let isMoved = false;
for (const child of children.items()) {
if (Transaction.isCanceled)
break;
const x = child.self;
if (x.element) {
if (isMoved) {
children.markAsMoved(child);
isMoved = false;
}
}
else if (strict && children.isMoved(child))
isMoved = true;
if (x.priority === 0)
prepareThenRunRender(child, children.isMoved(child), strict);
else if (x.priority === 1)
p1 = push(p1, child);
else
p2 = push(p2, child);
}
if (!Transaction.isCanceled && (p1 !== undefined || p2 !== undefined))
promised = startIncrementalRendering(children, context, p1, p2).then(() => action(error), e => action(e));
}
finally {
if (!promised)
action(error);
}
}
}
function startIncrementalRendering(allChildren, parent, priority1, priority2) {
return __awaiter(this, void 0, void 0, function* () {
if (priority1)
yield renderIncrementally(allChildren, parent, priority1);
if (priority2)
yield renderIncrementally(allChildren, parent, priority2);
});
}
function renderIncrementally(allChildren, parent, items) {
return __awaiter(this, void 0, void 0, function* () {
const checkEveryN = 30;
yield Transaction.requestNextFrame();
if (!Transaction.isCanceled) {
const node = parent.self;
const strict = node.children.strict;
if (node.shuffle)
shuffle(items);
for (const child of items) {
prepareThenRunRender(child, allChildren.isMoved(child), strict);
if (Transaction.isFrameOver(checkEveryN, RxNode.frameDuration))
yield Transaction.requestNextFrame(5);
if (Transaction.isCanceled)
break;
}
}
});
}
function prepareThenRunRender(item, moved, strict) {
const node = item.self;
if (node.stamp >= 0) {
prepareRender(item, moved, strict);
if (node.inline)
runRender(item);
else
nonreactive(node.autorender, node.triggers);
}
}
function prepareRender(item, moved, strict) {
var _a, _b, _c;
const node = item.self;
const factory = node.factory;
if (node.stamp === 0) {
node.stamp = 1;
if (!node.inline) {
Transaction.off(() => {
if (Rx.isLogging)
Rx.setLoggingHint(node, node.name);
Rx.getController(node.autorender).configure({
order: node.level,
monitor: node.monitor,
throttling: node.throttling,
logging: node.logging,
});
});
}
(_a = factory.initialize) === null || _a === void 0 ? void 0 : _a.call(factory, node, undefined);
(_b = factory.arrange) === null || _b === void 0 ? void 0 : _b.call(factory, node, strict);
}
else if (moved)
(_c = factory.arrange) === null || _c === void 0 ? void 0 : _c.call(factory, node, strict);
}
function runRender(item) {
const node = item.self;
if (node.stamp >= 0) {
runUnder(item, () => {
let result = undefined;
try {
node.stamp++;
node.children.beginMerge();
result = node.factory.render(node);
if (result instanceof Promise)
result.then(v => { runRenderChildrenThenDo(undefined, NOP); return v; }, e => { console.log(e); runRenderChildrenThenDo(e !== null && e !== void 0 ? e : new Error('unknown error'), NOP); });
else
runRenderChildrenThenDo(undefined, NOP);
}
catch (e) {
runRenderChildrenThenDo(e, NOP);
console.log(`Rendering failed: ${node.name}`);
console.log(`${e}`);
}
});
}
}
function doFinalize(item, isLeader) {
const node = item.self;
if (node.stamp >= 0) {
node.stamp = ~node.stamp;
const childrenAreLeaders = node.factory.finalize(node, isLeader);
if (!node.inline) {
item.aux = undefined;
const last = gLastToDispose;
if (last)
gLastToDispose = last.aux = item;
else
gFirstToDispose = gLastToDispose = item;
if (gFirstToDispose === item)
Transaction.run({ standalone: 'disposal', hint: `runDisposalLoop(initiator=${item.self.name})` }, () => {
void runDisposalLoop().then(NOP, error => console.log(error));
});
}
for (const item of node.children.items())
doFinalize(item, childrenAreLeaders);
RxNodeImpl.grandCount--;
}
}
function runDisposalLoop() {
return __awaiter(this, void 0, void 0, function* () {
yield Transaction.requestNextFrame();
let item = gFirstToDispose;
while (item !== undefined) {
if (Transaction.isFrameOver(500, 5))
yield Transaction.requestNextFrame();
Rx.dispose(item.self);
item = item.aux;
RxNodeImpl.disposableCount--;
}
gFirstToDispose = gLastToDispose = undefined;
});
}
function forEachChildRecursively(item, action) {
const node = item.self;
const e = node.element;
e && action(e);
for (const item of node.children.items())
forEachChildRecursively(item, action);
}
function wrap(func) {
const parent = gContext;
const wrappedRunUnder = (...args) => {
return runUnder(parent, func, ...args);
};
return wrappedRunUnder;
}
function runUnder(item, func, ...args) {
const outer = gContext;
try {
gContext = item;
return func(...args);
}
finally {
gContext = outer;
}
}
function triggersAreEqual(a1, a2) {
let result = a1 === a2;
if (!result) {
if (Array.isArray(a1)) {
result = Array.isArray(a2) &&
a1.length === a2.length &&
a1.every((t, i) => t === a2[i]);
}
else if (a1 === Object(a1) && a2 === Object(a2)) {
for (const p in a1) {
result = a1[p] === a2[p];
if (!result)
break;
}
}
}
return result;
}
function push(array, item) {
if (array == undefined)
array = new Array();
array.push(item);
return array;
}
function shuffle(array) {
let i = array.length - 1;
while (i >= 0) {
const j = Math.floor(Math.random() * i);
const t = array[i];
array[i] = array[j];
array[j] = t;
i--;
}
return array;
}
const ORIGINAL_PROMISE_THEN = Promise.prototype.then;
function reactronicDomHookedThen(resolve, reject) {
resolve = resolve ? wrap(resolve) : defaultResolve;
reject = reject ? wrap(reject) : defaultReject;
return ORIGINAL_PROMISE_THEN.call(this, resolve, reject);
}
function defaultResolve(value) {
return value;
}
function defaultReject(error) {
throw error;
}
Promise.prototype.then = reactronicDomHookedThen;
const gSysRoot = Collection.createItem(new RxNodeImpl('SYSTEM', new StaticNodeFactory('SYSTEM', false, null), false, { level: 0 }, undefined, NOP));
gSysRoot.self.item = gSysRoot;
Object.defineProperty(gSysRoot, 'parent', {
value: gSysRoot,
writable: false,
configurable: false,
enumerable: true,
});
let gContext = gSysRoot;
let gFirstToDispose = undefined;
let gLastToDispose = undefined;