aboutsummaryrefslogtreecommitdiff
path: root/src_js
diff options
context:
space:
mode:
authorbozokopic <bozo.kopic@gmail.com>2017-04-24 23:08:48 +0200
committerbozokopic <bozo.kopic@gmail.com>2017-04-24 23:08:48 +0200
commited9f4f8991f4e8b31270dbbf17215732d73c9e37 (patch)
treeb90b09f4270ee090627e1f24dfa0c50cabdf7320 /src_js
parent8daf24776f21e4cb36c7e545d74438fe6d9f0a15 (diff)
simple web app
Diffstat (limited to 'src_js')
-rw-r--r--src_js/opcut/common.js3
-rw-r--r--src_js/opcut/lenses.js21
-rw-r--r--src_js/opcut/main.js18
-rw-r--r--src_js/opcut/renderer.js126
-rw-r--r--src_js/opcut/vt.js8
5 files changed, 175 insertions, 1 deletions
diff --git a/src_js/opcut/common.js b/src_js/opcut/common.js
new file mode 100644
index 0000000..adc7191
--- /dev/null
+++ b/src_js/opcut/common.js
@@ -0,0 +1,3 @@
+
+
+export const defaultState = {};
diff --git a/src_js/opcut/lenses.js b/src_js/opcut/lenses.js
new file mode 100644
index 0000000..8458dbd
--- /dev/null
+++ b/src_js/opcut/lenses.js
@@ -0,0 +1,21 @@
+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/opcut/main.js b/src_js/opcut/main.js
index b7850ca..b4da90b 100644
--- a/src_js/opcut/main.js
+++ b/src_js/opcut/main.js
@@ -1,3 +1,19 @@
+import bean from 'bean';
+import R from 'ramda';
-import 'static!static/main.html';
+import r from 'opcut/renderer';
+import * as l from 'opcut/lenses';
+import * as common from 'opcut/common';
+import * as vt from 'opcut/vt';
+
+import 'static!static/index.html';
import 'style/main.scss';
+
+
+function main() {
+ let root = document.body.appendChild(document.createElement('div'));
+ r.init(root, common.defaultState, vt.main);
+}
+
+
+bean.on(window, 'load', main);
diff --git a/src_js/opcut/renderer.js b/src_js/opcut/renderer.js
new file mode 100644
index 0000000..22a74c6
--- /dev/null
+++ b/src_js/opcut/renderer.js
@@ -0,0 +1,126 @@
+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 'opcut/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;
diff --git a/src_js/opcut/vt.js b/src_js/opcut/vt.js
new file mode 100644
index 0000000..e199bbe
--- /dev/null
+++ b/src_js/opcut/vt.js
@@ -0,0 +1,8 @@
+
+
+export function main() {
+ return ['div',
+ 'application in development - ',
+ ['a', {href: 'https://github.com/bozokopic/opcut'}, 'Github page']
+ ];
+}