UNPKG

reactronic-dom

Version:

Reactronic DOM - Transactional Reactive Front-End Development Framework

401 lines (400 loc) 14.5 kB
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;