UNPKG

evectr.api

Version:

The official eVectr� API.

1,501 lines (1,447 loc) 53.1 kB
/// <reference path="./stock.d.tsx" /> 'use strict'; /** * @typedef {{[props: string]: any}} ReactProps */ /** * Defines "_stock_" `Handlers`, `Components`, and `Mixins`. * @param {FluxComponents} COMPS The `global` `Components` bucket. * @void */ module.exports = function Comps(COMPS) { //////////////////////////////////////////////////////////////////////// // CONSTANTS ----------------------------------------------------------- const Actions = COMPS.Actions; const Reflux = COMPS.Reflux; const React = COMPS.React; const Frag = React.Fragment; const RDOM = COMPS.RDOM; const STOCK = {}; const RNotify = '__notify__'; //////////////////////////////////////////////////////////////////////// // FIXINS -------------------------------------------------------------- let RFLXC = Reflux.Component.prototype; if (!RFLXC.hasOwnProperty('componentDidMount')) { let _reflux = RFLXC.componentWillMount; delete RFLXC.componentWillMount; Object.defineProperties(RFLXC, { componentDidMount: { writable: true, enumerable: true, value: function componentDidMount() { _reflux.bind(this)(); } }, mapState: { writable: true, enumerable: true, value: function mapState(mappers = {}) { let THS = this, chkStamp = (stmp)=>(!!stmp&&stmp>THS.state.stamp), maps = Imm.Map(mappers).map(m=>( m.isAlert=(!!m.isAlert), m.state=(m.state||(items=>items)).bind(THS), m.default=(m.default||{}), m )); THS.mapStoreToState(COMPS.Stores.Data, store => { let which = { true:store[RNotify]||{}, false:store }; return maps.reduce((prev, map, id) => { try { let { stamp, items = map.default } = ( which[map.isAlert][id]||{} ); return (chkStamp(stamp) ? Assign({ loaded: true, status: 'done', stamp }, map.state(items)) : prev); } catch (e) { console.error(e) } }, null) }); } }, }); }; //////////////////////////////////////////////////////////////////////// // UTILITIES ----------------------------------------------------------- /** * Run actions that should only be executed in a Browser. * @param {()=>void} callback A function that will execute Browser-specific actions. * @void */ function onBrowser(callback) { !!MODE.Browser && callback(); }; /** * A general, in-house `Logger` for `Components`. * @param {string} task An identifier for the log-message. * @void */ function onLog (task) { var LG = function () { console.log("%s%s %s", this.lvl, task, this.name); } // setTimeout(LG.bind({ lvl: this.lvl, name: this.name }), 0); } /** * Carries out some default-initializations before hydrating a `Component` with new `Props`. * @returns {ReactProps} */ function onInitial () { var nme = this.name, cnt = nme.appears('\\.'); this.lvl = '\t'.dup(cnt); return this.props; } /** * Carries out some default-actions when a `Component` is mounted. * @void */ function onMounted () { let THS = this, { props, state } = THS, pmeta = props._meta||{}, nme, smeta = state._meta||{}, cnt; // -------------------------------------------------------- // nme = THS.name; cnt = nme.appears('\\.'); THS.lvl = '\t'.dup(cnt); !!THS.addReceiver && THS.addReceiver(); THS.componentLog("{} Mounted"); // -------------------------------------------------------- // onBrowser(() => { try { if (pmeta.defer!==false && smeta.load===false) { THS.setState({ _meta: Assign( {}, smeta||{}, { load: true } ) }); } } catch(e) {}; }); } /** * Checks if `props` or `state` has been changed. * @param {ReactProps} prev The old `props` or `state`. * @param {ReactProps} next The new `props` or `state`. * @returns {boolean} */ function onShouldChk(prev, next) { return FromJS(prev).equals(FromJS(next))===false; } /** * Determines whether or not to re-render a `Component`. * @param {ReactProps} nextProps The new props. * @param {ReactProps} nextState The new state. * @returns {boolean} `true`, if `nextProps` is different from the `Component's` current `props`. */ function onShouldB (nextProps, nextState) { let { props, state } = this, result = { props: onShouldChk(props,nextProps), state: onShouldChk(state,nextState), }; /* if (['PORTAL'].has(this.name)) { console.log(this.constructor.name, { result, props: FromJS(props).toJS(), nextProps: FromJS(nextProps).toJS(), state: FromJS(state).toJS(), nextState: FromJS(nextState).toJS(), }) } */ return (result.props || result.state) } /** * Determines whether or not to re-render a Static `Component`. * @param {ReactProps} nextProps The new props. * @param {ReactProps} _nextState The new state. * @returns {boolean} `true`, if `nextProps` is different from the `Component's` current `props`. */ function onShouldP (nextProps, _nextState) { return onShouldChk(this.props,nextProps) === false; } /** * Determines whether or not to re-render a Dynamic `Component`. * @param {ReactProps} _nextProps The new props. * @param {ReactProps} nextState The new state. * @returns {boolean} `true`, if `nextState` is different from the `Component's` current `props`. */ function onShouldS (_nextProps, nextState) { return onShouldChk(this.state,nextState) === false; } /** * An internal `logger` used for debugging renders. * @void */ function onUpdate () { this.componentLog("[] Updated"); } const onShould = { B: onShouldB, P: onShouldP, S: onShouldS }; //////////////////////////////////////////////////////////////////////// // MIXINS -------------------------------------------------------------- const Elements = COMPS.Elements; const MixBase = { React: React.Component, Pure: React.PureComponent, Reflux: Reflux.Component }; COMPS.RNotify = RNotify; COMPS.ExLinks = {}; COMPS.qryOmit = Imm.List(['id','as','at','to','path','offset','kind']); COMPS.Tag = (tag) => { if (IS(tag)==='string') return tag; let From = [tag.from].concat(tag.name); return eval(`COMPS.Elements.${From.join('.')}`); }; COMPS.Agnostic = (config, key) => { if (!!!config) return config; let tag = config.tag, xerox = !!config.xerox, props = Assign({},config.props||{},{key:key}), childA = (v,i)=>(IS(v)==='string'?v:Agnostic(v,i)), xeroxA = (v,i) => Agnostic(Assign( {}, config, {items:[v],xerox:null} ), i); if (IS(config)==='array') { return (<React.Fragment key={key}>{config.map(childA)}</React.Fragment>); } else if (!!tag) { if (IS(tag)==='string') { let items = (config.items||[]), chldn = items.map(childA); if (xerox) { return Agnostic(chldn.map(xeroxA),key); } else { return COMPS.React.createElement( tag, props, chldn.length?chldn:null ); } } else { let TagName = COMPS.Tag(tag), items = (config.items||[]), chldn = items.map(childA); if (!!chldn.length) { return (<TagName {...props}>{chldn}</TagName>); } else { return (<TagName {...props} />); } } } else { return config; } }; COMPS.Agnolist = (list = []) => { let items = list.filter(i=>!!i), agnos = COMPS.Agnostic; return items.map((v,i)=>agnos(v, i)); }, COMPS.FA = (icon, theme = 'fas') => { if (!!icon && !!theme) { let fa = ' fa-', sfx = ['','fw']; return [theme].concat(( sfx.splice(1,0,icon), sfx.join(fa) )).join(''); } else return null; }; COMPS.iURL = (url) => { return !!url ? `url('${url||''}')` : null; }; COMPS.joinV = (obj, delim = ' ') => { try { return Object.values(obj).join(delim); } catch(e) { return ''; } }; COMPS.stopEvent = (e) => { try { e.stopPropagation(); e.preventDefault(); } catch(e) {}; } COMPS.Uploader = (uri, method = "POST", id, headers = {}) => { // Assertions -------------------------------------------------------------- // if (!!!uri) throw new Error('UPLOAD: Must specify a valid URI.'); if (!!!id ) console.warn('UPLOAD: No ID was specified.'); // Return ------------------------------------------------------------------ // return function UploadHandler(e) { // Variables ------------------------------------------------------- // let TRG = e.currentTarget, isF = TRG.tagName=='FORM', FRM = isF ? TRG : TRG.form, XHR = new XMLHttpRequest(), DTA = new FormData(FRM), JSN = {}, PTH = uri; // Event Handlers -------------------------------------------------- // function handleEvent(e) { let Type = e.type.toUpperCase(); console.info(`${PTH}: ${Type} | ${e.loaded}B Transferred\n`); } // Add Event Handlers ---------------------------------------------- // XHR.addEventListener('error', handleEvent); XHR.addEventListener('abort', handleEvent); XHR.addEventListener('loadstart', handleEvent); XHR.addEventListener('load', handleEvent); XHR.addEventListener('progress', handleEvent); XHR.onreadystatechange = function () { let { readyState, status } = this; if (readyState==4 && status==200) { /** * @type {ROUT.JSN.Payload} */ JSN = JSON.parse(this.responseText); }; }; XHR.addEventListener('loadend', (e) => { console.info(`${PTH}: UPLOAD |`, JSN); Actions.Data.place(id, { items: JSN.result }); }); // Setup the Request ----------------------------------------------- // XHR.open(method, PTH, true); Imm.Map(headers).map((V,K)=>( XHR.setRequestHeader(K, V) )); XHR.send(DTA); }; }; COMPS.onSocket = (props) => { var links, socks; try { links = props.value.replace('SocketLink','') .replace(/\\"/g,'"'); socks = JSON.parse(links); } catch (e) { console.log(e, links, socks) } return { sid: props.master, key: props.id, path: props.path, display: props.display, link: COMPS.onRequest(socks), point: ('/'+socks.point.join('/')), params: socks.params, query: socks.query } }; COMPS.onRequest = (obj) => { var req = obj.point.join('/'), omt = COMPS.qryOmit, map = function (v,k) { return (k+'='+v); }, flt = function (v,k) { return !omt.contains(k); }, prm = Imm.Map(obj.params).toList().toArray().join('/'), qry = Imm.Map(obj.query).filter(flt).map(map).toArray().join('&'); return ('/')+(req+(!!prm?'/'+prm:''))+(!!qry?'?'+qry:''); }; COMPS.onBrowser = onBrowser; COMPS.Mixins = { Defer: { componentDidMount() { let THS = this; // ---------------------------------------------- // // THS.callSuperMethod('componentDidMount'); // ---------------------------------------------- // if (MODE.Browser) { let state = THS.state; // console.log(`DEFERRING ${THS.name}...`) THS.setState({ _meta: Assign( {}, state._meta||{}, { load: true } ) }); } }, }, General: { getInitialState: onInitial, shouldComponentUpdate: onShould.B, componentDidMount: onMounted, componentDidUpdate: onUpdate, componentLog: onLog, }, Static: { getInitialState: onInitial, shouldComponentUpdate: onShould.B, componentDidMount: onMounted, componentDidUpdate: onUpdate, componentLog: onLog, }, Dynamic: { getInitialState: onInitial, shouldComponentUpdate: onShould.B, componentDidMount: onMounted, componentDidUpdate: onUpdate, componentLog: onLog, }, MonoSpacer: { toMono(text) { let regex = /(?:[{]{2}.*?[}]{2}|[(]{2}.*?[)]{2}|'[^']*?')(?=\s|)|(?:[^{(')}]|([{()}])(?!\1))+/g, items = (text.match(regex)||[]); return (<span>{items.map((m,i) => { let mno = m.match(/^[{]{2}(.+)[}]{2}$/), brk = m.match(/^[(]{2}(.+)[)]{2}$/), quo = m.match(/^'([^']+)'$/), cnd = !!(mno||brk||quo), prp = (cnd ? {className:'mono'+(!!brk?' bracket':(!!quo?' quote':''))} : {}), val = (cnd ? (mno||brk||quo)[1] : m); return (<span key={i} {...prp}>{val}</span>); })}</span>); }, }, Requests: { Required: [], Latest: {}, isUpload: false, getInitialState: onInitial, componentDidMount: onMounted, shouldComponentUpdate: onShould.P, componentLog: onLog, typeIsUpload(method) { this.isUpload = (method == 'PUT'); }, setValue(obj, key, val, def) { var mch = new RegExp(key,'i'), res = (val || (!!def.match(mch) ? '' : def)); if (!!res) obj[key] = res; }, setRequest(params) { var THS = this, prms = Imm.Map(params); // -------------------------------------------- Imm.Map(this.refs).map(function (v,k) { let sfx = { nrm: '', tag: ';' }, typ = (!!v.refs.field?'nrm':'tag'), fld = v.refs.field||v.refs.taggr.refs.input, val = (prms.has(k)?unescape(prms.get(k)):""); fld.value = val+(!!val?sfx[typ]:''); // if (!!val && typ=='tag') { if (typ=='tag') { v.refs.taggr.refs.value.value = val; v.refs.taggr.tagAdd(true); } }); THS.hndRequest(); }, lenRequest(typ, req) { return req.filter(v=>(v.type==typ)).length||null; }, impRequest(typ) { return ((p, c)=>(p+Number(!c.valid && c.type==typ))); }, chkRequest(req) { var req = this.Required, ast = req.reduce(this.impRequest('*'), 0), tld = req.reduce(this.impRequest('~'), 0), tln = this.lenRequest('~',req); if (ast > 0 || tld == tln) { window.alert([ '<',this.props.paths.join('/'),'>', 'Must fill in Required Field', [ this.Required .filter(function(v){return v.type=='*';}) .map(function(v){return '[ '+v.name+' ]';}) .join(' and '), this.Required .filter(function(v){return v.type=='~';}) .map(function(v){return '[ '+v.name+' ]';}) .join(' or ') ].filter(function(v){return !!v;}) .join(' or ')+'.' ].join(' ')); return false; }; this.Latest = req; return true; }, getRequest(point, fields) { let THS = this, prm = {}, qry = {}, att = ['placeholder','data-to'], pth = ['payload'], res = {}, qnm = this.isUpload?'body':'query'; THS.Required = []; Imm.Map(fields).map((v,k) => { // let ref = v.refs, fld; // if (!!ref.field) { // fld = ref.field; // } else { // ref = ref.taggr.refs; // fld = ref.value; // ref.input.value = fld.value; // } let fld = v.refs.field||v.refs.taggr.refs.value; let key = k.toLowerCase(), atr = INPUT.Attrs(fld, att), obj = {}, plc = atr.placeholder, def = (plc==k?'':plc), val = fld.value; INPUT.InValid(fld, k, THS.Required); switch (atr.dataTo) { case 'param': obj = prm; break;; case 'query': obj = qry; break;; }; THS.setValue(obj, k, val, def); }); res.headers = { token: COMPS.Token, /* authorization: COMPS.Basic */ }; res.params = prm; res[qnm] = Assign(qry, { id: point, at: pth, to: pth }); return res; }, hndRequest(e) { COMPS.Rejecters = 0; if (!!e) { e.preventDefault(); e.stopPropagation(); } if (!!COMPS.IsAuthd) { // ----------------------------------------- var fields = this.refs, req = {}, point = this.state.act, upload = this.isUpload; // ----------------------------------------- req = this.getRequest(point, fields); // ----------------------------------------- if (!this.chkRequest(req)) return false; // ----------------------------------------- if (upload) { var field = fields.file.refs.field; req.files = field.files; req.files = []; for (var x=0; x<field.files.length; x++) { req.files[x] = { fieldname: field.name, originalname: field.files[x].name, mimetype: field.files[x].type, stream: field.files[x] }; }; Actions.Data.send(point, req); } else { console.log('POINT:', point) Actions.Data.send(point, req); } // ----------------------------------------- } return false; }, uplRequest(field, point, req) { var files = field.files; console.log(point, files); // iterate over the files dragged on to the browser for (var x=0; x<files.length; x++) { // instantiate a new FileReader object var FR = new FileReader(), file = files[x]; // // loading files from the file system is an asynchronous // // operation, run this function when the loading process // // is complete // FR.addEventListener('loadend', function () { // // send the file over web sockets // console.log('FILE:', FR.result); // IOs.Socket.emit('upload', { file: FR.result }); // }); // // load the file into an array buffer // FR.readAsArrayBuffer(file); IOs.Socket.emit('upload', { file: file }); } } }, Items: { extClass: { array: 'iterator', object: 'iterator' }, getInitialState: onInitial, shouldComponentUpdate: onShould.S, componentDidMount: onMounted, componentDidUpdate() { console.log("[] Updated"); }, componentLog: onLog, types: {}, hndAccords(e) { e.stopPropagation(); var chk = this.refs.accord; chk.checked = !chk.checked; return false; }, isIP(key, val) { // -------------------------------------- return (key == 'ip' ? Lng2IP(val) : val); }, toSocket: COMPS.onSocket, toImage(val) { var dims = '70px'; return { backgroundImage: 'url("'+val+'")' } }, toValue(props) { var value = props.value, name = props.name, type = props.type, tag = {_:'div'}, elems = []; switch (type) { case 'date': elems = [dateFrm(value)]; break;; case 'image': elems = [<Bubble key="img" {...{img:value,opts:['lite','large']}} />]; break;; case 'email': elems = [<NormalLink key="a" kind="email" value={value} safe={true} />]; break;; case 'link': elems = [<NormalLink key="a" kind="link" value={value} safe={true} />]; break;; case 'socket': return (<SocketLink {...this.toSocket(props)} />); case 'array': return (<JSONP.Iter {...value} obj={props.obj} />); case 'object': return (<JSONP.Iter {...value} obj={props.obj} />); default: elems = [<span key="span" className={type}>{this.isIP(name,value)}</span>]; } return (<tag._ key={'val-'+props.id} className="value">{elems||'null'}</tag._>); }, }, Receivers: { name: 'SOCKET', logReceiver() { this.componentLog('Receivers: '+COMPS.Receivers); }, addReceiver() { COMPS.Receivers++; this.logReceiver(); }, remReceiver() { COMPS.Receivers--; this.logReceiver(); }, logRejecter() { this.componentLog('Rejecters: '+COMPS.Rejecters); }, setRejecter() { COMPS.Rejecters=0; this.logRejecter(); }, addRejecter() { COMPS.Rejecters++; this.logRejecter(); }, remRejecter() { COMPS.Rejecters--; this.logRejecter(); }, componentWillUnmount() { this.remReceiver(); super.componentWillUnmount(); }, }, Sockets: { name: 'SOCKET', getInitialState: onInitial, request: {}, kind: { int: { classes: 'value socketLink', tip: '[Double-Click] to Load the Full Result...', handles: { setRequest: function (e) { var props = this.state, qry = props.query, at = ['payload','result']; qry.id = props.sid; qry.to = (qry.to||props.path); //qry.at = at; if (qry.as=='item') qry.at = qry.to; this.request = { headers: { token: COMPS.Token }, params: props.params, query: qry }; }, hndRequest: function (e) { COMPS.Rejecters = 0; e.stopPropagation(); e.preventDefault(); if (!!COMPS.IsAuthd) { var pnt = this.state.point, req = this.request; Actions.Data.send(pnt, req); } return false; } } }, ext: { classes: 'value socketLink exlink', tip: '[Double-Click] to Load this Request...', handles: { setRequest(e) { var props = this.state, prm = props.params, qry = props.query; this.request = Imm.Map(prm).mergeDeep(qry).toObject(); }, hndRequest(e) { COMPS.Rejecters = 0; e.stopPropagation(); e.preventDefault(); COMPS.ExLinks[this.state.sid](this.request); return false; } } } }, getRID() { // -------------------------------------------- return this.state.link.split('/').slice(-1)[0]; }, cncRequest(e) { e.stopPropagation(); e.preventDefault(); return false; }, }, Phone: { getLink(num) { let mp = c=>((v,i)=>({ 'className':c.toLowerCase(), 'key':`${c}${i}`,'data-v':v })), fl = v=>!!v, rg = /\d+/g, pr = (num['#']||'').match(rg)||[], er = (num.Ext ||'').match(rg)||[], ph = [pr.join('') ].filter(fl), ex = [er.join(';')].filter(fl); return { parts: pr.map(mp('NUM')).concat(er.map(mp('EXT'))), link: `tel:${ph.concat(ex).join(';')}` }; }, }, Taggr: { ///////////////////////////////////////////////////////////////////////////////////////////////// // REACTORS getInitialState() { let nme = this.name, cnt = nme.appears('\\.'); this.lvl = '\t'.dup(cnt); return FromJS({ fcsed: false, tags: [], value: {}, input: {}, range: [0], text: '', }) .mergeDeep(FromJS(this.props)) .toJS(); }, componentDidMount() { // ------------------------------------------------------- this.onRefresh(); // this.componentLog("[] Mounted"); }, componentDidUpdate() { // ------------------------------------------------------- this.onRefresh(); this.componentLog("[] Updated"); }, componentMerge(props) { // ------------------------------------------------------- return Assign({}, this.state, props); }, shouldComponentUpdate: onShould.S, componentLog: onLog, // componentLog(task) { // var LG = function () { console.log("%s%s %s", this.lvl, task, this.name); } // setTimeout(LG.bind({ lvl: this.lvl, name: this.name }), 0); // }, ///////////////////////////////////////////////////////////////////////////////////////////////// // CACHE Mods: { Shf: false, Ctl: false, Alt: false, Met: false, }, tagOpt: [ 'none','must','and','not' ], tagMch: [ 'none','brand','ctid','code','navision','name' ], tagOptWhich: { true:'Mch', false:'Opt' }, tagDir: { 37:-1, 38:-1, 39:1, 40:1 }, tagAnchor: -1, tagSubmit: false, ///////////////////////////////////////////////////////////////////////////////////////////////// // EVENTS onModifiers(e, log=true) { let { Key: Key, Shf: Shf, Ctl: Ctl, Alt: Alt, Met: Met } = this.Mods = { Key: e.keyCode, Shf: e.shiftKey, Ctl: e.ctrlKey, Alt: e.altKey, Met: e.metaKey, }, Pos = this.tagAnchor; if (Shf) { Pos = this.tagAnchor = ((Pos==-1) ? this.tagLast() : Pos); } else { Pos = this.tagAnchor = (-1); } if (log) { //console.log(`Tag [ ANC:${Pos} | KEY:${Key} | SHF:${Shf} | CTL:${Ctl} | ALT:${Alt} | MET:${Met} ]`); } }, onKeyUp(e) { this.onModifiers(e, 0); }, onKeyDown(e) { e.stopPropagation(); if (!!!this.refs.input.value) { this.onModifiers(e, 1); switch (this.Mods.Key) { case 35: case 36: this.tagGoto(e); break;; case 37: case 39: this.tagMove(e); break;; case 38: case 40: this.tagOpts(e); break;; case 46: case 8: this.tagBack(e); break;; } } else if (e.keyCode==13) { this.tagAdd(); } }, onInput(e) { let text = this.refs.input.value; console.log('INPUTTED!!!!!!'); if (!!text.match(/^([^;]+;)+.*$/)) this.tagAdd(); }, onEnter(e) { e.stopPropagation(); if (e.keyCode == 13) this.onSave(e); return false; }, onEdit(e) { let elm = e.target, idx = this.tagIndex(elm), tag = this.state.tags[idx]; elm .parentElement.lastChild.firstChild.focus(); if (!tag.edt) return false; }, onSave(e) { e.preventDefault(); let elm = e.target, val = elm.value, idx = this.tagIndex(elm), nws; nws = this.tagSetIn([idx,'txt'], val); nws = this.tagSetIn([idx,'edt'], false); this.tagUpdate(nws, [idx], 'onEnter'); return false; }, onWrite(e) { e.stopPropagation(); let elm = e.target, pos = this.tagIndex(elm), val = elm.value; this.tagUpdate(this.tagSetIn([pos,'txt'], val), null, 'onWrite'); return false; }, onFocus(e) { var anch = this.state.tags.length, next = FromJS(this.state.tags) .map((v,i) => v.set('edt',false)) .toJS(); this.tagUpdate(next, [anch], 'onFocus', true); }, onBlur(e) { // ---------------------------------------- var trg = e.nativeEvent.relatedTarget; if (!!!trg || !!!trg.name.match(/^[A-Z]\w+Tag\d+$/)) { this.tagUpdate(null, null, 'onBlur', false); } }, onClick(e) { let evt = e.nativeEvent, elm = evt.path[0], idx = this.tagIndex(elm), ted = this.tagGetIn(idx,'edt'), tin = this.tagIn(idx); // console.log(`\tTag IN ? ${tin} | Tag EDITS ? ${ted}`) if (!ted) { if (tin) { e.stopPropagation(); e.preventDefault(); let nxt = this.tagSetIn([idx,'edt'], true); // console.log('\t\tGoing into Edit Mode.') this.tagUpdate(nxt, [idx], 'onClick'); } else { // console.log('\t\tGoing to Selection.') this.tagGoto(idx, e); } }; return false; }, onRefresh() { let refcs = this.refs, items = Map(refcs) .filter((v,k) => !isNaN(k)).toArray() .filter((v,i) => v.checked).reverse(); if (!!this.state.fcsed) { if (!!!items.length) { refcs.input.focus(); } else { let last = items.last, nput = this.tagInput(last); if (nput.disabled) last.focus(); else nput.focus(); } }; // console.log('REFRESH:', this.state) }, ///////////////////////////////////////////////////////////////////////////////////////////////// // UPDATERS tagUpdate(next, range, action, focus) { let prop = this.state, rnge = (range||prop.range), tags = (next||prop.tags), fcus = (!!focus), text = this.tagJoin(this.tagFormat(tags)); // ----------------------------------------------------- // console.log(`${action} NEXT (${focus}):`, this.componentMerge({ // text: text, range: rnge, tags: tags, fcsed: fcus // }), next); // ----------------------------------------------------- this.setState(this.componentMerge({ text: text, range: rnge, tags: tags, fcsed: fcus })); }, tagAdd(reset) { let nput = this.refs.input, vals = this.refs.value, text = nput.value, opts = {'':0,';':0,'!':1,'+':2,'-':3}, prev = !!!reset?this.state.tags||[]:[], next = prev.concat((text.match(/(?:[^;+]|\\[;+])*[^\\](?:[;+]|$)/g)||[]) .map((v,i) => { var o = 0, m = [ v.match(/^((?:[^;]|\\;)+)(;|)$/), v.match(/^"((?:\\"|[^"])+)";?$/), v.match(/^((?:[^+]|\\\+)+)\+;?$/), v.match(/^\[((?:\\[\[\]]|[^\[\]])+)\];?$/), ]; switch (true) { case !!m[1]: o = 1; break;; case !!m[2]: o = 2; break;; case !!m[3]: o = 3; break;; } return { txt:m[o][1], opt:o, mch:0, quo:false, edt:false }; })), anch = next.length; nput.blur(); this.tagUpdate(next, [anch], 'tagAdd', true); nput.value = ''; }, tagBack(e) { e.preventDefault(); let prop = this.state, rnge = prop.range, lnth = rnge.length, tags = FromJS(prop.tags), anch = (rnge.last||1)-1, next = [], amnt = ''; // --- if (lnth == 1 && rnge[0] == tags.count()) { amnt = 'One'; next = tags.filter((v,i) => !(i==anch)) .toJS(); } else { amnt = 'Many'; next = tags.filter((v,i) => !this.tagIn(i)) .toJS(); } // --- // console.log(`Deleting ${amnt} | Remaining`, next, rnge); anch = next.length-1; this.tagUpdate(next, [anch], 'tagBack', true); }, tagGoto(idx) { let next = this.state.range.concat(), mod = this.Mods; idx = idx||0; switch (this.Mods.Key) { case 35: idx = this.state.tags.length; break;; case 36: idx = 0; break;; } if (this.tagLimit(idx)) { let lmt = ((mod.Ctl||mod.Met)&&!mod.Shf); if (this.tagIn(idx)) { next = List(next).filter((v,i) => v!=idx); } else { next = lmt ? next.concat(idx) : [idx]; } this.tagUpdate(null, next, 'tagGoto', true); } }, tagMove(e) { let key = this.Mods.Key, move = { 37: -1, 39: 1 }, prop = this.state, l = prop.tags.length, last = prop.range.last, next = last + move[key], rnge = prop.range.concat(); // console.log(`\tPREV: ${prev} | (${rnge}>=0 && ${rnge}<=${l}) == ${(rnge>=0 && rnge<=l)}`); if (this.tagLimit(next)) { this.setState(this.componentMerge({ range: this.tagShift(key, last, next, rnge), fcsed: true })); } else if (key==39) { this.tagBlur(); } e.preventDefault(); }, tagShift(key, last, next, range) { let res = [next], mod = this.Mods; if (mod.Shf&&!mod.Ctl) { let rng = List(range), fst = range[0], lst = last, nxt = next, pop = { L: { '-1': 'push', '1': 'pop' }, C: { '-1': 'pop', '1': 'push' }, R: { '-1': 'pop', '1': 'push' }, }, anc = this.tagAnchor, dir = (fst<next?'R':(fst>next?'L':'C')), inc = (nxt-lst).toString(), // dir = ((nxt-lst)>), wch = pop[dir][inc]; res = rng[wch](next).toArray(); }; return res; }, tagBlur() { let anch = this.state.tags.length; this.setState(this.componentMerge({ range: [anch] })); }, tagOpts() { let { Key: key, Ctl: ctl } = this.Mods; if ([38,40].has(key)) { let wch = this.tagOptWhich[ctl], ops = this[`tag${wch}`], pos = this.state.range.last, dir = this.tagDir[key], nws = this.tagIncIn(wch, pos, ops, dir); // console.log(`\tPOS: ${pos} | OPT: ${opt} | NXT: ${res} |`, this.tagOpt); this.tagUpdate(nws, null, 'tagOpts', true); } }, ///////////////////////////////////////////////////////////////////////////////////////////////// // HELPERS tagNew() { // ----------------------------------------------------- return FromJS(this.state.tags); }, tagGetIn(...path) { // ----------------------------------------------------- return FromJS(this.state.tags).getIn(path); }, tagSetIn(path, val) { // ----------------------------------------------------- return FromJS(this.state.tags).setIn(path, val).toJS(); }, tagIncIn(which, tag, ops, dir) { which = which.toLowerCase(); let opt = this.tagGetIn(tag, which), nxt = ops.safeIndex(opt, dir); return this.tagSetIn([tag, which], nxt); }, tagInput(elm) { // ----------------------------------------------------- return elm.parentElement.lastChild.children[1]; }, tagLast() { // ----------------------------------------------------- return this.state.range.last; }, tagFormat(next) { var opts = (val) => { let txt = `${val.txt .replace(/"(\w+)"/g, '$1') .replace(/(^\[+|\]+$|[+])/g, '')}`; switch (val.opt) { case 3: txt = `${txt.replace(/\\(?=[\[\]])/g,'') .replace(/([\[\]])/g,"\\$1") .replace(/^(.+)$/, '[$1]')}`; break;; case 2: txt = `${txt.replace(/\\(?=\+)/g,'') .replace(/(\+)/g,"\\$1") .replace(/^(.+[^+])$/, '$1+')}`; break;; case 1: txt = `${txt.replace(/\\(?=")/g,'') .replace(/(")/g,"\\$1") .replace(/^(.*)$/,'"$1"')}`; break;; } // console.log(`\tOPT: ${val.opt} [${this.tagOpt[val.opt]}] | ${txt}`); return { txt: txt }; }; return next.map((v,i) => Assign({}, v,opts(v))); }, tagJoin(next) { // ---------------------------------------------- return next.map((v,i) => v.txt).join(';').replace(/([+];)/g, '+'); }, tagIn(idx) { let range = this.state.range, reslt = List(range).includes(idx); return reslt; }, tagLimit(idx) { let b = 0, e = this.state.tags.length; return idx.amid(b,e,1); }, tagIndex(elm) { // -------------------------------- let idx = INPUT.Attr(elm, 'data-ref'); return Number(idx || 0); }, } }; /** * A React Mixin Factory. * @kind function * @param {('React'|'Pure'|'Reflux')} kind ... * @param {...string} mixes ... * @returns {(React.Component<{},{},any>|React.PureComponent<{},{},any>|Reflux.Component<{},{},any>)} */ COMPS.Mix = (kind, ...mixes) => { let All = {}, Mixed, Defaults = {}; // Declare Base Class -------------------------------------------- // All[kind] = class extends MixBase[kind] { constructor(props) { super(props); this.state = props; Object .keys(Mixed) .filter((F)=>(Mixed[F]!=this[F])) .map((F) => { let over = this[F].bind(this), mixs = Mixed[F].bind(this); this[F] = function(...args) { return (mixs(...args),over(...args)); }; }); } callSuperMethod(name, ...args) { // let method = super[name]; let method = this.__proto__.__proto__.__proto__[name]; // console.log(`${this.name}.${name}:`, method); if (!!method) return method(...args); } static get defaultProps( ) { return FromJS(Defaults).toJS(); } static set defaultProps(def) { Defaults = FromJS(Defaults).mergeDeep(def).toJS(); } }; // Establish Name & Default meta-prop All[kind].defaultProps = { _meta: {} }; DEFINE(All[kind].constructor, { name: { enumerable:0, configurable:0, writable:1, value: kind } }); // Setup Reflux Mixin; if needed --------------------------------- // if (kind == "Reflux") { let RFLX = All[kind].prototype; Defaults = Assign(Defaults, { loaded: false, stamp: null, status: null, }); // RFLX.getDerivedStateFromProps = function(props, state) { // console.log({ props, state }) // if (!!!state.stamp||props.stamp!==state.stamp) { // return Assign({}, props.stamp>state.stamp?props:state); // }; return null; // }; }; // Inherit Mixin Classes ----------------------------------------- // Mixed = {}; mixes.map((M) => { try { let PRTO = All[kind].prototype; Imm.Map(MX[M]).map((F,N) => { if (!!!Mixed[N]) Mixed[N] = [PRTO[N]].filter(P=>!!P); Mixed[N].push(F); }); } catch(err) { console.log(C); throw err; } }); Mixed = Imm.Map(Mixed).map((FNS,N) => { return All[kind].prototype[N] = function(...args) { let prev = args.last; args = args.slice(0,-1); return FNS.reduce((P,C)=>( C.bind(this)(...args, P) ), prev); }; }).toObject(); // Return Extended Class ----------------------------------------- // return All[kind]; }; //////////////////////////////////////////////////////////////////////// // COMPONENTS ---------------------------------------------------------- const MX = COMPS.Mixins; const Mix = COMPS.Mix; const Agnostic = COMPS.Agnostic; const FA = COMPS.FA; const iURL = COMPS.iURL; const joinV = COMPS.joinV; // DEFER /////////////////////////////////////////////////////////// STOCK.Defer = function Defer({ load = false, what }) { // console.log('DEFERRING:', load, what) return (!!!load ? null : what()); } // JSONP /////////////////////////////////////////////////////////// STOCK.JSONP = class extends Mix('Reflux','Dynamic','Receivers') { constructor(props) { super(props); this.name = 'JSONP'; let THS = this, store = COMPS.Stores.Data; THS.mapStoreToState(store, data => { let path = THS.state.paths.join('/'), epnt = `/${path.toLowerCase()}`, cont = data[epnt]; return { content: cont }; }); } componentDidUpdate() { Actions.App.progress(100); } render() { var props = this.state, content = props.content; return ( <div data-page={props.page} className='jsonp' ref="jsonp" > <div className="object" ref="objct"> <span key="obrk" className="obrckt" /> <JSONP.Iter {...content} id={props.id} key={props.id} /> <span key="cbrk" className="cbrckt" /> </div> </div> ); } }; STOCK.JSONP.Iter = class extends Mix('React', 'Dynamic') { constructor(props) { super(props) this.name = 'JSONP.ITER'; } render() { var th = this, props = th.state, size = (props.size || 0), sbsz = 0, items; try { // console.log("PROPS:", props.map((k,v) => k)) items = props/*.slice(0,20)*/.map( function (v,k) { // console.log("k,v:", k, v) return (<JSONP.Item {...v} key={v.id} />); }); // if ( items.length>0 ) { let prp = props[props.keys[0]]; sbsz = ( ['array','object'].has(prp.type) ? prp.size : 1 ); } } catch (e) { items = []; } // console.log("ITEMS:", items) return ( <ul id={props.id} data-cnt={`${size}.${sbsz}`} className="value gpu"> {items} </ul> ); } }; STOCK.JSONP.Item = class extends Mix('Reflux','Items','Receivers') { constructor(props) { super(props); this.name = 'JSONP.ITEM'; let THS = this, store = COMPS.Stores.Data; console.log('JSONi+', this,state) THS.mapStoreToState(store, data => { console.log('JSONi:', data) return {}; // var ste = THS.state, // mst = ste.master, // pto, cur, pth, // rgx, dth, sth, // gti, rej, mch, // res; // if (mst == data.master) { // pto = path.slice(); // pth = mst+'/'+ste.path.join('/'); // cur = data.path; // dth = pto.join("/"); // sth = ste.path; // if (pth == dth) { // res = data; // } else if (rgx = new RegExp('^'+pto.join("\\/")+'$'), mch = pth.match(rgx), !!mch) { // var nth = mch[0].split('/').slice(2), nme = ste.name, // dta = data.value.getIn(nth, true).Child.toObject(); // // console.log('\tRECD:', dta) // dta.name = nme; res = dta; // } else if (gti = data.value.getIn(sth, true), !!gti.Child && gti.Child.name == ste.name) { console.log('\tRECD:', sth, gti.Child.toObject()) // res = gti.Child.toObject(); // } else { THS.addRejecter(); (COMPS.Rejecters == COMPS.Receivers) && // Actions.App.progress(100); // } // return res; // } else return {} }); } componentDidUpdate() { Actions.App.progress(100); } render() { var props = this.state, name = props.name, type = props.type, value = this.isIP(props.name, props.value), alone = props.alone, iter = (this.extClass[type]||''), checked = ['result','user'].indexOf(name) > -1 ? '' : 'checked', classLI = CSS.Flex({Align:'L'/*,Wrap:!!iter*/},type,'gpu',iter), classOB = classN("obrckt", { alone: alone }), classCB = classN("cbrckt", { alone: alone }), collps = ('chbx-'+props.id), accord = ('acrd-'+props.id); // ------------------------------------------------- return ( <li key={props.id} className={classLI} id={props.id}> <input type='checkbox' ref="collps" id={collps} name={collps} key={collps} disabled={alone} className="collps" defaultChecked={checked} /> <label key={collps+'-L1'} htmlFor={collps} /> { (props.inside == 'object') ? <div className="key" key='KEY' onDoubleClick={this.hndAccords}> {name} <span className="colon" key="CLN" dangerouslySetInnerHTML={{ __html: [props.pad.join('&nbsp;')] }} /> </div> : null } <span key="obrk" className={classOB} /> <input type='checkbox' id={accord} name={accord} key={accord} ref="accord" className="accord" /> <span key="elps" className="ellipse"> {(() => { try { return value.size } catch (e) { return 0; }})()} </span> {this.toValue(props)} <span key="cbrk" className={classCB} /> <span key="cma" className="comma" /> <label key={collps+'-L2'}></label> <div className="highlight" /> </li> ); } }; // TAGS //////////////////////////////////////////////////////////// STOCK.Tags = class extends Mix('React', 'Taggr') { constructor(props) { super(props) this.name = 'TAGS'; } render() { let props = (FromJS(this.state||{}).toJS()), focused = (!!props.fcsed?' focused':''), tags = (props.tags ||[]), text = (props.text ||''), value = (props.value||{}), input = (props.input||{}), sClick = (this.onClick), dClick = (this.onDblClick); if (!!tags.length) input.placeholder = ""; // console.log('TAGS:', tags); return ( <div className={'taggr paramInput'+focused} ref='tags' onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp}>{[(!!tags ? ( <div key='tags' className='tags'>{ tags.map((v,i) => { let id = `${input.name}Tag${i}`, md = `${input.name}Mch${i}`, od = `${input.name}Opt${i}`, mp = this.tagMch[v.mch], op = this.tagOpt[v.opt], ch = this.tagIn(i), ed = !!v.edt, tx = v.txt, ln = tx.length, sz = ln||1; // (ln>1?ln-1:ln); return ( <div key={i} className={'tag'+(op=='and'?' plus':'')} data-ref={i}> <input key='iL' name={id} className='focal' ref={i} type='checkbox' checked={ch} data-ref={i} data-mch={mp} data-opt={op} onFocus={this.onEdit} /> {this.tagMch.map((o,n) => ( <input key={o} name={md} checked={o==mp} type='radio' className={'matcher '+o} value={o} readOnly={true} data-ref={i} /> ))} {this.tagOpt.map((o,n) => ( <input key={o} name={od} checked={o==op} type='radio' className={'modifier '+o} value={o} readOnly={true} data-ref={i} /> ))} <label key='tL' htmlFor={id} data-ref={i} onClick={sClick} data-ref={i}> <span className="field" /> <input data-ref={i} name='tag' type='text' disabled={!ed} value={tx} onBlur={this.onSave} onKeyDown={this.onEnter} onChange={this.onWrite} size={sz} /> <span className="option" /> </label> </div> ); }) }</div> ) : null), <input key='vals' ref='value' {...value} value={text} onChange={(e) => { console.log("I INPUTTED!!!!!!"); }} onInput={(e) => { console.log("I INPUTTED!!!!!!"); }} />, <input key='flds' ref='input' {...input} className='taggee' onFocus={this.onFocus} onBlur={this.onBlur} onInput={this.onInput} data-value={text} /> ]}</div> ); } }; // BUBBLE ////////////////////////////////////////////////////////// STOCK.Bubble = class Bubble extends Mix('React', 'Static') { /** * @param {BubbleProps} props */ constructor(props) { super(props); this.name = 'BUBBLE'; /** * @type {BubbleProps} */ this.props; this.icons = {user:'user',add:'plus',more:'ellipsis-h','':''} } /** * Converts a `NameObj` into a full-string. * @param {NameObj} name The user's name-object */ getName(name) { return joinV(name||{First:'',Last:''}); } /** * Grabs the initials of a user's name. * @param {string} name The name of the user. */ getInitials(name) { return name.replace(/[^A-Z]|\B[A-Z]/g,'')||null } /** * Parses the `multi` prop into a digestible object. * @param {boolean|number} multi Either a `boolean` denoting it's multi-state, or a * `Number`, with `0` being `false`. When a `Number` is used, the amount of other * users this bubble represents will be shown to the right of the primary user's * name. * @returns {{cName:string,count:number}} */ getMulti(multi) { let res = { cName: { multiple: !!multi }, count: null }, typ = (multi||false).constructor.name; (typ=='Number' && !!multi) && (res.count = (multi-1)); return res; } /** * Creates an Alert-Count Badge * @param {Object} props * @param {number} props.count The current Alert-Count. */ Badge({ count } = props) { return (!!count ? ( <div className="counter" data-cnt={count<100?count:"!"} /> ) : null); } render() { var { Badge, props, icons, getName, getMulti, getInitials } = this, { id, name, multi, alerts, img:Photo, kind:Kind, style, opts, onClick, htmlFor, } = props, Tag = !!htmlFor?'label':'div', Name = getName(name), Multi = getMulti(multi), Click = !!onClick?onClick.bind(this):null, Opts = [Kind].concat(opts), Icons = icons, classes = classN('bubble', Multi.cName, ...Opts), style = Assign({}, style, { backgroundImage: iURL(Photo), }); return ( <Tag key="bub" id={id} style={style} htmlFor={htmlFor} className={classes} onClick={Click} data-name={Name} data-cnt={Multi.count} role="img"> <Badge count={alerts} /> {!!!Photo ? (<i {...{ 'className': FA(Icons[Kind]), 'data-initial': getInitials(Name), 'aria-hidden': true, }}></i>) : null} </Tag> ); } }; STOCK.Bubble.defaultProps = { id: null, kind: '', name: {First:'',Last:''}, img: null, opts: [], style: {}, }; // LINK //////////////////////////////////////////////////////////// STOCK.NormalLink = class extends Mix('React', 'Static') { constructor(props) { super(props) this.name = 'NORMALLINK'; this.href = "#"; } onSafeClick(e) { e.preventDefault(); window.open(this.href, "_self"); return false; } toMail(val, safe) { var prop = { className:'email', href:'#' }, prts = val.match(/([^@.]+|@|\.)/g), link = INPUT.Email(val); if (!!safe) { prop.onClick = this.onSafeClick; this.href = link; delete prop.href; } else { prop.href = link; } return (<a {...prop}>{prts.map((v,i)=>{ let C = (['@','.'].has(v)?'special':null); return <span key={`EML${i}`} className={C}>{v}</span> })}</a>) } toLink(val, safe) { var prop = { className:'link', href:'#' }, disp = val, mtch = val.match(/^<T:(.+?),U:(.+)>$/); if (!!mtch) { disp = mtch[1]; if (!!safe) { prop.onClick = this.onSafeClick; this.href = mtch[2]; delete prop.href; } else { prop.href = mtch[2]; } } return (<a {...prop}>{disp}</a>); } render() { var prop = this.props, kind = prop.kind, hndl = (kind=='email'?'toMail':'toLink'); return this[hndl](prop.value, prop.safe); } }; STOCK.SocketLink = class extends Mix('React', 'Sockets') { constructor(props) { super(props) } toText(value) { return (<span className="sck">{value}</span>); } toLink(kind, props) { var point = props.point.split('/').slice(1), param = Imm.Map(props.params), query = Imm.Map(props.query).filter( function (v,k) { return !COMPS.qryOmit.contains(k); }), cnt = 0; return [ (point.map(function (v,i) { return (<span key={v+i} className='sck pnt'>{v}</span>); })), ((!!param.count()) ? param.map(function (v,k) { return (<span key={k} className='sck prm'>{ v.split(';').map(function (p,i) { return (<span key={i}>{p}</span>); }) }</span>); }).toArray() : nu