diff options
| author | bozo.kopic <bozo.kopic@gmail.com> | 2017-08-16 15:11:21 +0200 |
|---|---|---|
| committer | bozo.kopic <bozo.kopic@gmail.com> | 2017-08-16 15:11:21 +0200 |
| commit | 28446d95471e74de364b53c3f24d6182fddac0e0 (patch) | |
| tree | 5cf96e6d26a8e27aef74464c6a0a3b0ab191ffff /src_js/hatter | |
| parent | 1b6052d7e74d90806650ff09153c581e7ae851eb (diff) | |
backend
Diffstat (limited to 'src_js/hatter')
| -rw-r--r-- | src_js/hatter/lenses.js | 21 | ||||
| -rw-r--r-- | src_js/hatter/main.js | 1 | ||||
| -rw-r--r-- | src_js/hatter/renderer.js | 279 | ||||
| -rw-r--r-- | src_js/hatter/util.js | 172 |
4 files changed, 325 insertions, 148 deletions
diff --git a/src_js/hatter/lenses.js b/src_js/hatter/lenses.js deleted file mode 100644 index 39da314..0000000 --- a/src_js/hatter/lenses.js +++ /dev/null @@ -1,21 +0,0 @@ -import R from 'ramda'; - - -export const index = R.lensIndex; - -export const prop = R.lensProp; - -export function path(...xs) { - return R.reduce((acc, i) => R.compose(acc, pathParamToLens(i)), - R.identity, xs); -} - -function pathParamToLens(x) { - switch (typeof(x)) { - case 'function': return x; - case 'number': return index(x); - case 'string': return prop(x); - case 'object': if (Array.isArray(x)) return R.apply(path, x); - } - throw 'Invalid path parameter'; -} diff --git a/src_js/hatter/main.js b/src_js/hatter/main.js index f5bee07..892b6af 100644 --- a/src_js/hatter/main.js +++ b/src_js/hatter/main.js @@ -2,7 +2,6 @@ import bean from 'bean'; import R from 'ramda'; import r from 'hatter/renderer'; -import * as l from 'hatter/lenses'; import * as common from 'hatter/common'; import * as vt from 'hatter/vt'; 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;
diff --git a/src_js/hatter/util.js b/src_js/hatter/util.js new file mode 100644 index 0000000..e3dbfeb --- /dev/null +++ b/src_js/hatter/util.js @@ -0,0 +1,172 @@ +
+
+export const isArray = Array.isArray;
+export const isObject = obj => obj !== null &&
+ typeof(obj) == 'object' &&
+ !isArray(obj);
+export const isNumber = n => typeof(n) == 'number';
+export const isInteger = Number.isInteger;
+export const isString = str => typeof(str) == 'string';
+
+
+export function clone(obj) {
+ if (isArray(obj))
+ return Array.from(obj, clone);
+ if (isObject(obj)) {
+ let ret = {};
+ for (let i in obj)
+ ret[i] = clone(obj[i]);
+ return ret;
+ }
+ return obj;
+}
+
+
+export function equals(x, y) {
+ if (x === y)
+ return true;
+ if (typeof(x) != 'object' || typeof(y) != 'object' || x === null || y === null)
+ return false;
+ if (Array.isArray(x) || Array.isArray(y)) {
+ if (!Array.isArray(x) || !Array.isArray(y) || x.length != y.length)
+ return false;
+ }
+ for (let i in x)
+ if (!equals(x[i], y[i]))
+ return false;
+ for (let i in y)
+ if (!equals(x[i], y[i]))
+ return false;
+ return true;
+}
+
+
+export function toPairs(obj) {
+ return Object.entries(obj);
+}
+
+
+export function fromPairs(arr) {
+ let ret = {};
+ for (let [k, v] of arr)
+ ret[k] = v;
+ return ret;
+}
+
+
+export function* flatten(arr) {
+ if (isArray(arr)) {
+ for (let i of arr)
+ if (isArray(i))
+ yield* flatten(i);
+ else
+ yield i;
+ } else {
+ yield arr;
+ }
+}
+
+
+export function pipe(...fns) {
+ if (fns.length < 1)
+ throw 'no functions';
+ return function (...args) {
+ let ret = fns[0].apply(this, args);
+ for (let fn of fns.slice(1))
+ ret = fn(ret);
+ return ret;
+ };
+}
+
+
+export function curry(fn) {
+ let wrapper = function(oldArgs) {
+ return function(...args) {
+ args = oldArgs.concat(args);
+ if (args.length >= fn.length)
+ return fn.apply(this, args);
+ return wrapper(args);
+ };
+ };
+ return wrapper([]);
+}
+
+
+export const get = curry((path, obj) => {
+ let ret = obj;
+ for (let i of flatten(path)) {
+ if (ret === null || typeof(ret) != 'object')
+ return undefined;
+ ret = ret[i];
+ }
+ return ret;
+});
+
+
+export const change = curry((path, fn, obj) => {
+ function _change(path, obj) {
+ if (isInteger(path[0])) {
+ obj = (isArray(obj) ? Array.from(obj) : []);
+ } else if (isString(path[0])) {
+ obj = (isObject(obj) ? Object.assign({}, obj) : {});
+ } else {
+ throw 'invalid path';
+ }
+ if (path.length > 1) {
+ obj[path[0]] = _change(path.slice(1), obj[path[0]]);
+ } else {
+ obj[path[0]] = fn(obj[path[0]]);
+ }
+ return obj;
+ }
+ path = Array.from(flatten(path));
+ if (path.length < 1)
+ return fn(obj);
+ return _change(path, obj);
+});
+
+
+export const set = curry((path, val, obj) => change(path, _ => val, obj));
+
+
+export const omit = curry((path, obj) => {
+ function _omit(path, obj) {
+ if (isInteger(path[0])) {
+ obj = (isArray(obj) ? Array.from(obj) : []);
+ } else if (isString(path[0])) {
+ obj = (isObject(obj) ? Object.assign({}, obj) : {});
+ } else {
+ throw 'invalid path';
+ }
+ if (path.length > 1) {
+ obj[path[0]] = _omit(path.slice(1), obj[path[0]]);
+ } else {
+ delete obj[path[0]];
+ }
+ return obj;
+ }
+ path = Array.from(flatten(path));
+ if (path.length < 1)
+ return undefined;
+ return _omit(path, obj);
+});
+
+
+export const sortBy = curry((fn, arr) => Array.from(arr).sort((x, y) => {
+ let xVal = fn(x);
+ let yVal = fn(y);
+ if (xVal < yVal)
+ return -1;
+ if (xVal > yVal)
+ return 1;
+ return 0;
+}));
+
+
+export const map = curry((fn, arr) => arr.map(fn));
+
+
+export const filter = curry((fn, arr) => arr.filter(fn));
+
+
+export const append = curry((val, arr) => arr.concat([val]));
|
