UNPKG

phlox

Version:

A frontend architecture that's easy to visualize

410 lines (377 loc) 12.9 kB
// Generated by CoffeeScript 2.4.1 (function() { var $, DEV, RE, _, equals, has, hasChanged, isEmpty, isNil, join, map, mapI, match, merge, path, popsiql, prop, props, qq, qqq, sf2, split, subSelectIfDEV, tail, test, type; equals = require('ramda/src/equals'); has = require('ramda/src/has'); isEmpty = require('ramda/src/isEmpty'); isNil = require('ramda/src/isNil'); join = require('ramda/src/join'); map = require('ramda/src/map'); match = require('ramda/src/match'); merge = require('ramda/src/merge'); path = require('ramda/src/path'); prop = require('ramda/src/prop'); props = require('ramda/src/props'); split = require('ramda/src/split'); tail = require('ramda/src/tail'); test = require('ramda/src/test'); type = require('ramda/src/type'); ({mapI, $, sf2} = RE = require('ramda-extras')); //auto_require: ramda-extras //auto_sugar []; qq = function(f) { return console.log(match(/return (.*);/, f.toString())[1], f()); }; qqq = function(f) { return console.log(match(/return (.*);/, f.toString())[1], JSON.stringify(f(), null, 2)); }; _ = function(...xs) { return xs; }; popsiql = require('popsiql'); DEV = process.env.NODE_ENV === 'development'; // DEV = false hasChanged = function(lastState, currentState) { return !equals(lastState, currentState); }; subSelectIfDEV = function(dataQuery, data) { var missing, subData; if (DEV) { [missing, subData] = popsiql.utils.subSelect(dataQuery, data); return subData; } else { return data; } }; module.exports = function(React, app, ops, report) { var _renderPre, _renderPreMissing, _renderPreMissingOps, _renderPreVMAccess, comp, missings, pushToReportStack, reportStack, useData, withData; // Save info to print on error (eg. error during render), that will otherwise not be printed because of error reportStack = []; missings = {}; pushToReportStack = function(o) { reportStack.unshift(o); return reportStack.length = Math.min(100, reportStack.length); // don't let array too big }; window.addEventListener('error', function() { var i, len, r, results; results = []; for (i = 0, len = reportStack.length; i < len; i++) { r = reportStack[i]; results.push(report({ ...r, ERROR: true })); } return results; }); // React hook with state and effect keeping you subscribed to the dataQuery useData = function(dataQuery, name = void 0) { var cacheCounter, initialData, setState, state; if (!dataQuery.UI || !dataQuery.Data || !dataQuery.State) { throw new Error(`your dataQuery is missing UI, Data or State:\n${sf2(dataQuery)}`); } // [uds, commitId] = app.getUDS() initialData = subSelectIfDEV(dataQuery, app.getUDS()); [state, setState] = React.useState(initialData); // hasInitialChange = false // initialUnsub = app.sub dataQuery, (() -> hasInitialChange = true), name + '_INITIAL' cacheCounter = 0; React.useEffect(function() { var data, handleChange, unsub; // initialUnsub() handleChange = function(data) { cacheCounter++; return setState(merge({cacheCounter}, subSelectIfDEV(dataQuery, data))); }; // if hasInitialChange // qq -> 'hasInitialChange' // currentData = app.getUDS() // setState subSelectIfDEV dataQuery, currentData // Do shallow compare to see if data has changed since since initialData and trigger setState if it has data = app.getUDS(); if (initialData.UI !== data.UI || initialData.Data !== data.Data || initialData.State !== data.State) { cacheCounter++; setState(merge({cacheCounter}, subSelectIfDEV(dataQuery, data))); } unsub = app.sub(dataQuery, handleChange, name); return function() { return unsub(); }; // https://github.com/facebook/react/issues/14476#issuecomment-471199055 // Note: we know dataQueries are small shallow objects anyway so JSON.stringify // shouldn't put any significant burden. But test it some time to be sure :) }, [JSON.stringify(dataQuery)]); return state; }; // HOC that wraps a component and subscribes it with useData withData = function(name, f) { var dataQuery; if (type(name) !== 'String') { throw new Error('withData requires name as first argument'); } [dataQuery] = popsiql.utils.parseArguments(f.toString()); if (dataQuery.UI == null) { dataQuery.UI = {}; } if (dataQuery.Data == null) { dataQuery.Data = {}; } if (dataQuery.State == null) { dataQuery.State = {}; } // fMemoed = React.memo f, equals return function() { var dataToRender, renderRes, t0, time; dataToRender = useData(dataQuery, name); // dataToRender = subRes // dataToRender = if isNil res then [uiQuery, dataQuery, stateQuery] // else [res.UI, res.Data, res.State] t0 = performance.now(); renderRes = f(dataToRender); time = { tot: performance.now() - t0 }; report({ name: name, kind: 'withData', dataToRender, time, ts: t0 }); return renderRes; }; }; _renderPre = function(data) { return React.createElement('pre', { style: { width: '100%' } }, sf2(data)); }; _renderPreMissing = function(name, missing, data) { // Not removing this so developer needs to refresh when this happens = good engough // if !missings[name] // div = document.createElement 'div' // missings[name] = div // document.body.appendChild div // missDiv = missings[name] // missDiv.style.position = 'absolute' // missDiv.style.top = '10px' // missDiv.style.left = '10px' // missDiv.style.color = 'blue' // missDiv.style.zIndex = 9999999999999 // missDiv.style.border = '2px solid red' // missDiv.style.padding = '20px' // missDiv.style.background = 'white' // missDiv.style.display = 'flex' // missDiv.style.flexDirection = 'column' // d1 = document.createElement 'div' // d1.textContent = "'#{name}' missing data!" // missDiv.appendChild d1 // fmap missing, (ar) -> // path = join '.', ar // dp = document.createElement 'div' // dp.textContent = path // missDiv.appendChild dp // pre = document.createElement 'pre' // pre.style.width = '100%' // pre.textContent = sf2 data // missDiv.appendChild pre return React.createElement('div', { style: { color: 'blue', position: 'absolute', top: 10, left: 10, zIndex: 999999999, border: '2px solid red', padding: 20, background: 'white' } }, React.createElement('div', { style: { color: 'red', fontSize: 20 } }, `'${name}' missing data!`), $(missing, mapI(function(ar, idx) { path = join('.', ar); return React.createElement('div', { key: idx, style: { color: 'red', fontSize: 12 } }, path); })), React.createElement('div', {}, 'Result from VM:'), _renderPre(data)); }; _renderPreMissingOps = function(name, missing) { return React.createElement('div', { style: { color: 'blue', position: 'absolute', top: 10, left: 10, zIndex: 999999999, border: '2px solid red', padding: 20, background: 'white' } }, React.createElement('div', { style: { color: 'red', fontSize: 20 } }, `'${name}' missing operations!`), $(missing, map(function(ar) { path = join('.', ar); return React.createElement('div', { key: path, style: { color: 'red', fontSize: 12 } }, path); }))); }; _renderPreVMAccess = function(path) { return React.createElement('div', { style: { color: 'blue', position: 'absolute', top: 10, left: 10, zIndex: 999999999, border: '2px solid red', padding: 20, background: 'white' } }, React.createElement('div', { style: { color: 'red', fontSize: 20 } }, "Missing VM-dependency"), React.createElement('div', { style: { color: 'red', fontSize: 12 } }, `vm.${path}`)); }; comp = function(name, vm, deps, renderF) { var Data, Ops, State, UI, VM, dataQuery, lastCacheCounter, missingOps, prev, vmOps; if (!deps.VM) { throw new Error(`Missing VM deps in comp ${name}`); } ({UI, Data, State, VM, Ops} = popsiql.utils.toDataQuery(deps)); dataQuery = {UI, Data, State}; if (DEV && Ops) { [missingOps, vmOps] = popsiql.utils.subSelect(Ops, ops); } lastCacheCounter = -1; prev = null; return function(props) { var VMandOps, dataForVM, dataForVMarr, dataToRender, dataToRenderOriginal, err, errorCount, getHandler, isMatch, missing, pathInVM, renderRes, res, rf0, t0, tempPrev, time, vm0, vmRes; res = useData(dataQuery, name); // TODO: GÖR PROXY FÖR VM SÅ MAN SER OM MAN ACCESSAR NÅGON MAN INTE FRÅGADE EFTER time = {}; t0 = performance.now(); dataForVMarr = _(res.UI, res.Data, res.State); dataForVM = { UI: res.UI, Data: res.Data, State: res.State }; vm0 = performance.now(); vmRes = vm(...dataForVMarr, prev); time.vm = performance.now() - vm0; tempPrev = { UI: res.UI, Data: res.Data, State: res.State, VM: vmRes }; React.useEffect(function() { report({ name, dataForVM, dataToRender, time: { ...time, tot: performance.now() - t0 }, ts: t0, noCacheChange: isNil(res.cacheCounter) || lastCacheCounter === res.cacheCounter }); lastCacheCounter = res.cacheCounter; prev = tempPrev; return function() {}; }); if (missingOps && !isEmpty(missingOps)) { return _renderPreMissingOps(name, missingOps); } // TODO: turn this on dataToRender = vmRes; reportStack.push({name, dataForVM, dataToRender}); if (DEV) { [missing, dataToRender] = popsiql.utils.subSelect(VM, vmRes); if (!isEmpty(missing) && vmRes.loading !== true) { return _renderPreMissing(name, missing, vmRes); } } // else if missings[name] // document.body.removeChild missings[name] // delete missings[name] if (Ops) { if (DEV) { dataToRender.Ops = vmOps; } else { dataToRender.Ops = ops; } } errorCount = 0; if (DEV) { VMandOps = merge(VM, { Ops: vmOps }); pathInVM = function(path, o) { if (isEmpty(path)) { return true; } else if (test(/@@/, path[0])) { return true; // workaround for: vm.records.@@functional/placeholder } else if (has(path[0], o)) { return pathInVM(tail(path), o[path[0]]); } else if (has(path[0] + '〳', o)) { return pathInVM(tail(path), o[path[0] + '〳']); } else { return false; } }; dataToRenderOriginal = dataToRender; getHandler = { get: function(o, prop, path) { if (!pathInVM(split('.', path), VMandOps)) { throw new Error(`vm.${path} VM-dep`); } return o[prop]; } }; dataToRender = RE.recursiveProxy(dataToRender, getHandler); } rf0 = performance.now(); try { renderRes = renderF(dataToRender); } catch (error) { err = error; [isMatch, path] = match(/vm\.(.*?) VM-dep/, err.message); if (isMatch) { return _renderPreVMAccess(path); } else { throw err; } } time.rf = performance.now() - rf0; return renderRes; }; }; return {useData, withData, comp}; }; }).call(this);