aboutsummaryrefslogtreecommitdiff
path: root/src_js/hatter/renderer.js
diff options
context:
space:
mode:
Diffstat (limited to 'src_js/hatter/renderer.js')
-rw-r--r--src_js/hatter/renderer.js279
1 files changed, 153 insertions, 126 deletions
diff --git a/src_js/hatter/renderer.js b/src_js/hatter/renderer.js
index f09d40f..aed7c48 100644
--- a/src_js/hatter/renderer.js
+++ b/src_js/hatter/renderer.js
@@ -1,126 +1,153 @@
-import Delegator from 'dom-delegator';
-import R from 'ramda';
-import bean from 'bean';
-import vh from 'virtual-dom/h';
-import diff from 'virtual-dom/diff';
-import patch from 'virtual-dom/patch';
-import createElement from 'virtual-dom/create-element';
-
-import * as l from 'hatter/lenses';
-
-
-const delegator = Delegator();
-const vhTypes = ['VirtualNode', 'Widget'];
-
-
-function vhFromArray(node) {
- if (!node)
- return [];
- if (typeof node == 'string' || vhTypes.includes(node.type))
- return node;
- if (!Array.isArray(node))
- throw 'Invalid node structure';
- if (node.length < 1)
- return [];
- if (typeof node[0] != 'string')
- return node.map(vhFromArray);
- let hasProps = (node.length > 1 &&
- typeof node[1] == 'object' &&
- !Array.isArray(node[1]) &&
- !vhTypes.includes(node[1].type));
- let children = R.flatten(node.slice(hasProps ? 2 : 1).map(vhFromArray));
- let result = hasProps ? vh(node[0], node[1], children) :
- vh(node[0], children);
-
- // disable SoftSetHook for input
- if (result.tagName == 'INPUT' &&
- result.properties &&
- result.properties.value &&
- typeof(result.properties.value) === 'object') {
- result.properties.value = result.properties.value.value;
- }
-
- return result;
-}
-
-
-class VTreeRenderer {
-
- constructor(el) {
- this._el = el;
- this._vtree = null;
- }
-
- render(vtree) {
- let vt = vhFromArray(vtree);
- if (vt.type == 'VirtualNode') {
- if (this._vtree) {
- let d = diff(this._vtree, vt);
- patch(this._el.firstChild, d);
- } else {
- while (this._el.firstChild)
- this._el.removeChild(this._el.firstChild);
- this._el.appendChild(createElement(vt));
- }
- this._vtree = vt;
- } else {
- this._vtree = null;
- while (this._el.firstChild)
- this._el.removeChild(this._el.firstChild);
- }
- }
-
-}
-
-
-export class Renderer {
-
- constructor(el, initState, vtCb) {
- this.init(el, initState, vtCb);
- }
-
- init(el, initState, vtCb) {
- this._state = null;
- this._changeCbs = [];
- this._vtCb = vtCb;
- this._r = new VTreeRenderer(el || document.querySelector('body'));
- if (initState)
- this.change(R.identity, _ => initState);
- }
-
- view(...lenses) {
- return R.view(R.apply(l.path, lenses), this._state);
- }
-
- set(lens, value) {
- if (arguments.length < 2) {
- value = lens;
- lens = R.identity;
- }
- this.change(lens, _ => value);
- }
-
- change(lens, cb) {
- if (arguments.length < 2) {
- cb = lens;
- lens = R.identity;
- }
- if (this._changeCbs.push(cb) > 1)
- return;
- let startingSubState = this.view(lens);
- while (this._changeCbs.length > 0) {
- this._state = R.over(l.path(lens), this._changeCbs[0], this._state);
- this._changeCbs.shift();
- }
- if (!this._vtCb ||
- (this._state && R.equals(startingSubState, this.view(lens))))
- return;
- this._r.render(this._vtCb(this._state));
- bean.fire(this, 'render', this._state);
- }
-
-}
-
-
-const defaultRenderer = new Renderer();
-export default defaultRenderer;
+import Delegator from 'dom-delegator';
+import bean from 'bean';
+import vh from 'virtual-dom/h';
+import diff from 'virtual-dom/diff';
+import patch from 'virtual-dom/patch';
+import createElement from 'virtual-dom/create-element';
+
+import * as u from 'hatter/util';
+
+
+const delegator = Delegator();
+const vhTypes = ['VirtualNode', 'Widget'];
+
+
+function vhFromArray(node) {
+ if (!node)
+ return [];
+ if (u.isString(node) || vhTypes.includes(node.type))
+ return node;
+ if (!u.isArray(node))
+ throw 'Invalid node structure';
+ if (node.length < 1)
+ return [];
+ if (typeof node[0] != 'string')
+ return node.map(vhFromArray);
+ let hasProps = (node.length > 1 &&
+ u.isObject(node[1]) &&
+ !vhTypes.includes(node[1].type));
+ let children = Array.from(
+ u.flatten(node.slice(hasProps ? 2 : 1).map(vhFromArray)));
+ let result = hasProps ? vh(node[0], node[1], children) :
+ vh(node[0], children);
+ return result;
+}
+
+
+class VTreeRenderer {
+
+ constructor(el) {
+ this._el = el;
+ this._vtree = null;
+ }
+
+ render(vtree) {
+ let vt = vhFromArray(vtree);
+ if (vt.type == 'VirtualNode') {
+ if (this._vtree) {
+ let d = diff(this._vtree, vt);
+ patch(this._el.firstChild, d);
+ } else {
+ while (this._el.firstChild)
+ this._el.removeChild(this._el.firstChild);
+ this._el.appendChild(createElement(vt));
+ }
+ this._vtree = vt;
+ } else {
+ this._vtree = null;
+ while (this._el.firstChild)
+ this._el.removeChild(this._el.firstChild);
+ }
+ }
+
+}
+
+
+export class Renderer {
+
+ constructor(el, initState, vtCb, maxFps) {
+ this.init(el, initState, vtCb, maxFps);
+ }
+
+ init(el, initState, vtCb, maxFps) {
+ this._state = null;
+ this._changes = [];
+ this._promise = null;
+ this._timeout = null;
+ this._lastRender = null;
+ this._vtCb = vtCb;
+ this._maxFps = maxFps;
+ this._r = new VTreeRenderer(el || document.querySelector('body'));
+ if (initState)
+ this.change(_ => initState);
+ }
+
+ get(...paths) {
+ return u.get(paths, this._state);
+ }
+
+ set(path, value) {
+ if (arguments.length < 2) {
+ value = path;
+ path = [];
+ }
+ return this.change(path, _ => value);
+ }
+
+ change(path, cb) {
+ if (arguments.length < 2) {
+ cb = path;
+ path = [];
+ }
+ this._changes.push([path, cb]);
+ if (this._promise)
+ return this._promise;
+ this._promise = new Promise((resolve, reject) => {
+ setTimeout(() => {
+ try {
+ this._change();
+ } catch(e) {
+ this._promise = null;
+ reject(e);
+ throw e;
+ }
+ this._promise = null;
+ resolve();
+ }, 0);
+ });
+ return this._promise;
+ }
+
+ _change() {
+ let change = false;
+ while (this._changes.length > 0) {
+ let [path, cb] = this._changes.shift();
+ let view = u.get(path);
+ let oldState = this._state;
+ this._state = u.change(path, cb, this._state);
+ if (this._state && u.equals(view(oldState),
+ view(this._state)))
+ continue;
+ change = true;
+ if (!this._vtCb || this._timeout)
+ continue;
+ let delay = (!this._lastRender || !this._maxFps ?
+ 0 :
+ (1000 / self._maxFps) -
+ (performance.now() - this._lastRender));
+ this._timeout = setTimeout(() => {
+ this._timeout = null;
+ this._lastRender = performance.now();
+ this._r.render(this._vtCb(this._state));
+ bean.fire(this, 'render', this._state);
+ }, (delay > 0 ? delay : 0));
+ }
+ if (change)
+ bean.fire(this, 'change', this._state);
+ }
+
+}
+
+
+const defaultRenderer = new Renderer();
+export default defaultRenderer;