From 28446d95471e74de364b53c3f24d6182fddac0e0 Mon Sep 17 00:00:00 2001 From: "bozo.kopic" Date: Wed, 16 Aug 2017 15:11:21 +0200 Subject: backend --- src_js/hatter/lenses.js | 21 ---- src_js/hatter/main.js | 1 - src_js/hatter/renderer.js | 279 +++++++++++++++++++++++++--------------------- src_js/hatter/util.js | 172 ++++++++++++++++++++++++++++ 4 files changed, 325 insertions(+), 148 deletions(-) delete mode 100644 src_js/hatter/lenses.js create mode 100644 src_js/hatter/util.js (limited to 'src_js/hatter') 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])); -- cgit v1.2.3-70-g09d2