PageSourceSearch

https://emit-beta-v2.replit.app/script.js

js emit-beta-v2.replit.app collected 2026-10-03 03:50:00 UTC 33,185 bytes, 816 lines download raw bytes

1'use strict';
2/** EMIT - Emissions Modeling Inventory Tool
3 * 
4 *  Chad Meister, BLM CO 2019
5 *  BETA (0.2.0) Pub Date: 9/20/2020
6 *  JS Entry File: script.js
7 * 
8 */
9
10/**  SET ENVIRONMENT VARIABLES */
11
12var emit = (function() {
13    let _db = new loki('emit'), _fs, _version = '0.2.0', _doc,
14        _user = { logged: false, profile: undefined }, // profile needs to contain sub collections for preferences, theme!!
15        _proj = { projs: [], active: undefined, forms: [], data: undefined, saved: true },
16        _computed = {},
17        _map = { admin: undefined, geo: [] };
18    //_nav;
19    // session log
20
21    let url_1 = 'https://script.google.com/macros/s/AKfycbyAHDySU_Flh8--9PH_MNKiYMn2iZPl88dfEuSqIsg3Y0TZzPw/exec',
22        url_2 = 'https://us-central1-arctic-defender-258123.cloudfunctions.net/';
23
24
25    /**  INITIALIZE FIREBASE & EMIT */
26    
27    // axios({ method: 'get', params: { call: 'config' }, url: url_1 }).then((res) => {
28    //     firebase.initializeApp(res.data.config);
29    //     _fs = firebase.firestore();
30    //     _db.addCollection('modules').insert(res.data.modules);
31    //     _db.addCollection('forms').insert(res.data.forms);
32    //     _db.addCollection('lookups').insert(res.data.lookups);
33    //     app.setLoading({ val: false });
34
35    //     document.getElementById('title').style.display = 'block';
36
37    //     firebase.auth().onAuthStateChanged((user) => {
38    //         console.log('auth state change', user);
39    //         if (user) {
40    //             axios({ method: 'get', params: { call: 'init' }, url: url_1 }).then((res) => {
41    //                 for (let n in res.data) { _db.addCollection(n).insert(res.data[n]) }
42    //             }).catch((error) => {
43    //                 alert('Warning: Application initialization failed, please reload the app from the entry link or press F5');
44    //             }).finally(() => { app.setLoading({ val: false }) });
45
46    //             if (user.emailVerified || user.email == '[email protected]') {
47    //                 _fs.collection('user_data').doc(user.uid).get().then((doc) => {
48    //                     if (doc.exists) {
49    //                         _user.profile = { ...doc.data(), _id: user.uid };
50    //                         _user.profile['created'] = user.metadata.creationTime;
51    //                         _user.profile['last_log'] = user.metadata.lastSignInTime;
52    //                         _user.logged = true;
53
54    //                         app.$q.notify({ color: 'grey-8', position: 'top', message: 'Please wait for EMIT to finish initialization' });
55    //                         router.push('main');
56    //                         _fs.collection('user_data').doc(user.uid).update({ log_cnt: ++_user.profile.log_cnt });
57
58    //                         if (_user.profile.acct_type == 'external' && _user.profile.govt == true) {
59    //                             _fs.collection('projects').where('agency_review', '==', true).get().then((docs) => {
60    //                                 docs.forEach(doc => {
61    //                                     _proj.projs.push({ ...doc.data(), _id: doc.id });
62    //                                 });
63    //                             });
64    //                         }
65
66    //                         if (_user.profile.acct_type == 'external') {
67    //                             let owned = _fs.collection('projects').where('owner', 'in', [firebase.auth().currentUser.uid, 'default']).get().then((docs) => {
68    //                                 return docs;
69    //                             });
70
71    //                             let shared = _fs.collection('projects').where(`shared.${firebase.auth().currentUser.uid}.access_level`, 'in', ['Read-Only', 'Write-Limited', 'Full-Write']).get().then((docs) => {
72    //                                 return docs;
73    //                             });
74
75    //                             Promise.all([owned, shared]).then(results => {
76    //                                 results.forEach(res => {
77    //                                     res.docs.forEach(doc => {
78    //                                         _proj.projs.push({ ...doc.data(), _id: doc.id });
79    //                                     });
80    //                                 });
81    //                                 router.push('modules');
82    //                             }).catch(e => { console.log("@promise error", e) });
83    //                         }
84
85    //                         if (_user.profile.acct_type == 'internal' && _user.profile.title != 'Air Resource Specialist') {
86    //                             _fs.collection('projects').where('field_office', 'in', _user.profile.flds).get().then((docs) => {
87    //                                 docs.forEach(doc => {
88    //                                     _proj.projs.push({ ...doc.data(), _id: doc.id });
89    //                                 });
90    //                                 router.push('modules');
91    //                             });
92
93    //                         } else if (_user.profile.acct_type == 'internal') { setTimeout(() => { router.push('modules') }, 500) }
94
95    //                     } else { app.$q.notify({ message: 'User data NOT found, contact adminstrator.' }) }
96
97    //                 }).catch(function(error) { console.log("Error getting user documents: ", error) });
98
99    //             } else { app.$q.notify({ message: 'Please finish the registration process to login' }) }
100
101    //         } else { _user.logged = false }
102    //     });
103
104    //     // firebase.analytics(); HERE
105    //     console.log('fb initialization complete');
106
107    // }).catch((error) => { alert('Warning: Application initialization failed, please reload the page (press F5) before continuing') });
108
109
110
111    /**  FIREBASE FUNCTIONS */
112
113    function fire(req) {
114        switch (req.op) {
115
116            case 'register': {
117                app.setLoading({ val: true });
118                firebase.auth().createUserWithEmailAndPassword(req.user.email, req.user.passcode).then((data) => {
119
120                    let { user } = data;
121                    if (user) {
122                        user.updateProfile({ displayName: req.user.fName }).then(() => {
123                            user.sendEmailVerification().catch((error) => { console.error('Email Verification Error', error) });
124                            delete req.user.passcode;
125
126                            _fs.collection('user_data').doc(user.uid).set(req.user).then((doc) => {
127                                firebase.auth().signOut().then(() => {
128                                    router.push('/');
129                                    app.notify({ title: 'Success', message: 'Verify your email address to finish the registration process!' });
130                                });
131                            }).catch((error) => { console.error("Error Adding Extended Profile Doc: ", error) });
132
133                        });
134                    }
135                }).catch((error) => { console.error("Registration Error: ", error) });
136            } break;
137
138            case 'sign_in': {
139                console.log('@ sign in');
140                app.setLoading({ val: true });
141                firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION).then(() => {
142
143                    firebase.auth().signInWithEmailAndPassword(req.username, req.passcode).catch((error) => {
144                        // console.log(error)
145                        app.notify({ message: "Please verify your credentials" });
146                        app.setLoading({ val: false });
147                    });
148
149                }).catch((error) => { console.error("Sign in Error: ", error) });
150            } break;
151
152            case 'reset_passcode': {
153                firebase.auth().sendPasswordResetEmail(req.user).then(() => {
154                    app.notify({ message: "Email reset link sent." });
155                }).catch((error) => { alert("Auth Error:\n" + error); });
156                app.setLoading({ val: false });
157            } break;
158
159            case 'create_proj': {
160                _fs.collection('projects').add(req.proj).then((ref) => {
161
162                    _fs.collection('projects').doc(ref.id).get().then((doc) => {
163                        _proj.projs.push({ ...doc.data(), _id: doc.id });
164                        app.notify({ color: 'positive', textColor: 'grey-2', icon: 'check_circle', position: 'top', message: 'SUCCESS: Project created!' });
165                        req.callback('create', doc.id);
166                    });
167
168                }).catch((error) => {
169                    console.error("Error creating project: ", error);
170                    app.setLoading({ val: false });
171                });
172            } break;
173
174            case 'get_proj': {
175                _proj.active = _proj.projs.filter(proj => proj._id == req.proj_id)[0];
176
177                _doc = _fs.collection('proj_data').doc(req.proj_id).onSnapshot(doc => {
178                    let loaded = (_proj.data) ? true : false;
179                    if (doc.exists) {
180                        _proj.data = doc.data().vm;
181                        _proj.forms = doc.data().forms;
182
183                        if (loaded && req.update) {
184                            _fs.collection('projects').doc(req.proj_id).update({
185                                access_cnt: ++_proj.active.access_cnt,
186                                last_user: firebase.auth().currentUser.email
187                                // last access date => NOT functioning server side due to Unix Code translation issues
188                            });
189                        }
190                    }
191
192                    if (!loaded) { loadModule() }
193                    app.$eventHub.$emit('update');
194                });
195
196
197            } break;
198
199            case 'copy_proj': {
200                _fs.collection('proj_data').doc(req.proj_id).get().then((data) => {
201                    if (data.exists) {
202
203                        _fs.collection('projects').add(req.proj).then((ref) => {
204                            _fs.collection('projects').doc(ref.id).get().then((doc) => {
205                                _proj.projs.push({ ...doc.data(), _id: doc.id });
206
207                                _fs.collection('proj_data').doc(ref.id).set({ vm: data.data().vm, forms: data.data().forms }).then(() => {
208                                    app.notify({ color: 'positive', textColor: 'grey-2', icon: 'done_outline', position: 'top', message: 'SUCCESS: Project data copied!' });
209                                    req.callback('copy', ref.id);
210                                });
211
212                            });
213                        });
214                    } else {
215                        app.notify({ color: 'negative', textColor: 'grey-2', icon: 'close', position: 'bottom', message: 'Error: Cannot copy an empty project!' });
216                        app.setLoading({ val: false });
217                    }
218                }).catch((error) => {
219                    console.error("Copy Error: ", error);
220                    app.setLoading({ val: false });
221                });
222            } break;
223
224            case 'save_proj': {
225                let proj = JSON.parse(JSON.stringify(_proj.data));
226                let forms = JSON.parse(JSON.stringify(_proj.forms));
227
228                _fs.collection('proj_data').doc(_proj.active._id).set({ vm: proj, forms: forms }, { merge: true }).then(() => {
229                    app.notify({ color: 'positive', textColor: 'grey-2', icon: 'done_outline', position: 'top', message: 'SUCCESS: Project data saved!' });
230                }).catch((error) => { console.error("Error saving project: ", error) });
231            } break;
232
233            case 'delete_proj': {
234                _fs.collection('proj_data').doc(req.proj_id).delete().then(() => {
235
236                    _fs.collection('projects').doc(req.proj_id).delete().then(() => {
237                        for (let n = 0; n < _proj.projs.length; n++) {
238                            if (_proj.projs[n]._id == req.proj_id) { _proj.projs.splice(n, 1); break; }
239                        }
240                        req.callback('delete');
241                    });
242
243                }).catch((error) => { console.error("Delete Error: ", error) });
244            } break;
245
246            case 'share_proj': {
247                if (req.type == 'share') {
248                    _fs.collection('user_data').where('email', '==', req.share_email).get().then((docs) => {
249
250                        docs.forEach((doc) => {
251                            let user = doc.data();
252                            if (user.acct_type != 'internal') {
253                                _fs.collection('projects').doc(req.proj_id).set({ shared: { [doc.id]: { user_email: req.share_email, access_level: req.access_level } } }, { merge: true }).then(() => {
254
255                                    _fs.collection('projects').doc(req.proj_id).get().then((doc) => {
256                                        for (let n = 0; n < _proj.projs.length; n++) {
257                                            if (_proj.projs[n]._id == doc.id) { _proj.projs.splice(n, 1, { ...doc.data(), _id: doc.id }); break; }
258                                        }
259                                        req.callback('admin');
260                                    });
261
262                                });
263                            } else { app.notify({ message: 'Internal user sharing is prohibited!' }) }
264                        });
265
266                    }).catch((error) => { console.error("Share Error: " + error) });
267
268                } else {
269                    _fs.collection('projects').doc(req.proj_id).update({ [`shared.${req.user}`]: firebase.firestore.FieldValue.delete() }).then(() => {
270
271                        _fs.collection('projects').doc(req.proj_id).get().then((doc) => {
272                            for (let n = 0; n < _proj.projs.length; n++) {
273                                if (_proj.projs[n]._id == doc.id) { _proj.projs.splice(n, 1, { ...doc.data(), _id: doc.id }); break; }
274                            }
275                            req.callback('admin');
276                        });
277
278                    });
279                }
280            } break;
281
282            case 'update_proj': {
283                _fs.collection('projects').doc(req.proj_id).update({ [req.fld]: req.val }).then(() => {
284                    req.callback('admin');
285                }).catch((error) => { console.error("Error updating project: ", error) });
286            } break;
287
288            case 'ars_query': {
289                _fs.collection('projects').where('field_office', '==', req.fld).get().then((docs) => {
290                    //console.log('NEW Docs ', docs);
291                    docs.forEach(doc => {
292                        _proj.projs.push({ ...doc.data(), _id: doc.id });
293                    });
294                    req.callback('hello!');
295                });
296            } break;
297        }
298    }
299
300
301
302    /**  INITIALIZE VUE.js VARIABLES (APP, EVENTBUS, ROUTER, ETC... ) */
303
304    const routes = [
305        {
306            path: '/', component: () => import('./routes/landing.js'),
307            beforeEnter: (to, from, next) => {
308                if (!_user.logged) { next() }
309            }
310        },
311        {
312            path: '/register', props: { url: url_1 }, component: () => import('./routes/register.js'),
313            beforeEnter: (to, from, next) => {
314                if (!_user.logged) { next() }
315            }
316        },
317        { path: '/dev', props: { url: url_1 }, component: () => import('./routes/devnotes.js') },
318        { path: '/faq', props: { url: url_1 }, component: () => import('./routes/faq.js') },
319        {
320            path: '/main', props: { version: _version }, component: () => import('./routes/main.js'),
321            beforeEnter: (to, from, next) => {
322                if (_user.logged) { next() }
323            },
324            children: [
325                //{ path: '/settings', name: 'settings', component: () => import('./routes/settings.js') },
326                { path: '/modules', name: 'modules', props: { version: _version }, component: () => import('./routes/modules.js') }, // consider killing version here... removing v integrity check!!!         
327                {
328                    path: '/module', name: 'module', component: () => import(`./modules/${_proj.active.module}.js`),
329                    beforeEnter: (to, from, next) => {
330                        (_proj.active) ? next() : app.notify({ message: 'Project is NOT loaded, navigation cancelled.' });
331                    },
332                },
333                { path: '/emissions', name: 'emissions', component: () => import('./routes/emissions.js') },
334                { path: '/reports', name: 'reports', props: { url: url_1 }, component: () => import('./routes/reports.js') },
335            ]
336        },
337    ];
338
339    const router = new VueRouter({ routes });
340
341    router.afterEach((to, from) => { // move to before leave in route?
342        if (from.name == 'module' && _proj.data) {
343            console.log('Updating calculations...');
344            let calcs = _db.getCollection('fields').find({
345                '$and': [
346                    { module: _proj.active.module },
347                    { element: 'Calculation' },
348                    { type: { '$ne': 'Emissions' } }
349                ]
350            });
351
352            calcs.forEach((calc) => {
353                console.log(calc.name);
354                for (let j = 0; j < _proj.data[calc.component].length; j++) {
355                    _proj.data[calc.component][j][calc.name] = _computed[c
355alc.name](_proj.data[calc.component][j]);
356                }
357            });
358        }
359    });
360
361
362
363    Vue.prototype.$eventHub = new Vue();
364    const app = new Vue({
365        el: '#app',
366        router,
367        data() {
368            return {}
369        },
370        created() {
371            this.$eventHub.$on('loading', this.setLoading);
372            // this.$eventHub.$on('saved', this.setSaved);
373            //console.log(this.$q.dark.isActive) // true, false
374
375
376        },
377        methods: {
378            setLoading(o) {
379                if (o.val == true) {
380                    this.$q.loading.show({ message: o.msg });
381                } else {
382                    this.$q.loading.hide();
383                    this.$q.loadingBar.increment(100);
384                }
385            },
386            notify(o) { this.$q.notify(o) },
387            setSaved(val) { _proj.saved = val }
388        },
389        mounted() {
390            //console.log(Quasar.components);
391            //console.log(Quasar.components.QBtn);
392            //this.$q.dark.set(true)
393        }
394    });
395
396
397
398    /**  STATE & PROPERTY FUNCTIONS */
399
400    function property(prop, id) {
401        switch (prop) {
402            case 'start-up': return (!_db.getCollection('lookups')) ? true : false; break; // MOVE to bd check!!!
403            case 'acct': return { type: _user.profile.acct_type, govt: _user.profile.govt }; break;
404            case 'profile': return _user.profile; break;
405            case 'init': return (_db.getCollection('modules')) ? true : false; break; // MOVE to bd check!!!
406            case 'projects': return _proj.projs; break;
407            case 'loaded': return (_proj.active) ? true : false; break;
408            case 'proj': return _proj.active; break;
409            case 'module': return _proj.active.module; break;
410            case 'model': return _proj.data; break;
411            case 'forms': return _proj.forms; break;
412            case 'vm': return _proj.data[id]; break;
413            case 'pctFed': return (_proj.data) ? _proj.data['development'][0].pctFed : 0; break; // THIS WILL FAIL IN OTHER MODULES, habndle it?!?!?!
414            case 'map': return _map[id]; break;
415
416        }
417    }
418
419
420
421    /**  MODULE FUNCTIONS */
422
423    function loadModule() {
424        if (_proj.data == undefined) { // NEW Project
425            let model = {};
426            let forms = query({ typ: 'find', coll: 'forms', q: { module: _proj.active.module } });
427
428            forms.forEach(function(form) {
429                _proj.forms.push(true);
430                model[form.name] = [];
431
432                let fields = emit.qry({
433                    typ: 'find', coll: 'fields', q: {
434                        '$and': [
435                            { module: _proj.active.module },
436                            { component: form.name },
437                            { element: { '$and': [{ '$ne': 'Header' }, { '$ne': 'Button' }] } }
438                        ]
439                    }
440                });
441
442                let m = {};
443                fields.forEach(function(fld) {
444                    if (fld.type != 'Emissions') { m[fld.name] = '' }
445                    if (fld.element == 'Calculation') { _computed[fld.name] = new Function('o', fld.equation) }
446                });
447
448                model[form.name].push(m);
449            });
450
451            _proj.data = model;
452
453        } else { // Re-load of Existing Model Data
454
455            let calcs = emit.qry({
456                typ: 'find', coll: 'fields', q: {
457                    '$and': [
458                        { module: _proj.active.module },
459                        { element: 'Calculation' }
460                    ]
461                }
462            });
463
464            calcs.forEach(calc => { _computed[calc.name] = new Function('o', calc.equation) });
465
466
467            // This NEEDS to be managed by a Cloud Function!!
468            if (_proj.active.model_version != _version) { // Check Version (two way data integrity control)
469
470                let forms = query({ typ: 'find', coll: 'forms', q: { module: _proj.active.module } });
471                forms.forEach(function(form) {
472                    let fields = emit.qry({
473                        typ: 'find', coll: 'fields', q: {
474                            '$and': [
475                                { module: _proj.active.module },
476                                { component: form.name },
477                                { element: { '$and': [{ '$ne': 'Header' }, { '$ne': 'Button' }, { '$ne': 'Query' }] } }
478                            ]
479                        }
480                    });
481
482                    let mFlds = [];
483                    fields.forEach(function(fld) {
484                        if (fld.type != 'Emissions') {
485                            if (_proj.data[form.name][0].hasOwnProperty(fld.name) == false) {
486                                if (fld.alias != '') { mFlds.push(fld.alias) }
487                                for (let n = 0; n < _proj.data[form.name].length; n++) {
488                                    _proj.data[form.name][n][fld.name] = (fld.alias) ? _proj.data[form.name][n][fld.alias] : '';
489                                }
490                            }
491                        }
492                    });
493                });
494            }
495        }
496        router.push('module');
497    }
498
499
500    function unloadModule() {
501        router.push('modules');
502        _doc();
503        _proj.active = undefined;
504        _proj.forms = [];
505        _proj.data = undefined;
506        _computed = {};
507        _map.admin = undefined;
508        _map.geo = [];
509    }
510
511
512    function emissions() {
513        if (!_proj.data) { return [] }
514
515        let flds = query({ typ: 'find', coll: 'fields', q: { type: 'Emissions' } }), key = 1,  // add module to query { $and : [{ range: 'Emissions' },{ $or : [{ module: this.module},{module: 'all' }] } ]}
516            pols = ['pm10', 'pm25', 'voc', 'nox', 'co', 'so2', 'haps', 'co2', 'ch4', 'n2o', 'co2e'], oEFs, emis = [];
517
518        console.log('Starting emissions compilation');
519        let calc_start = window.performance.now();
520        for (let j = 0; j < flds.length; j++) {
521            let model = _proj.data[flds[j].component];
522
523            for (let k = 0; k < model.length; k++) {
524                if (/^\d+$/.test(flds[j].ref)) {
525                    if (Array.isArray(model[k].efs) && model[k].efs.length > 0) {
526                        oEFs = model[k].efs[flds[j].ref];
527                        console.log(flds[j].name, oEFs)
528                    } else { continue }
529                } else {
530                    if (typeof model[k][flds[j].ref] === 'object') {
531                        oEFs = model[k][flds[j].ref];
532                    } else { continue }
533                }
534
535                if (model[k][flds[j].def_val] && model[k][flds[j].def_val] != '') {
536                    let eName = flds[j].label.split('_')
537                    let e = { id: key, name: '', units: flds[j].range, ref: flds[j].desc.split(','), permit: model[k].permit, deps: flds[j].dep };
538
539                    if (eName.length > 1) {
540                        if (typeof model[k][eName[1]] === 'object') {
541                            e.name = eName[0] + ' - ' + model[k][eName[1]].label;
542                        } else { e.name = eName[0] + ' - ' + model[k][eName[1]] }
543                    } else { e.name = eName[0] }
544
545                    for (let n = 0; n < pols.length; n++) {
546                        e[pols[n]] = (oEFs[pols[n]] * emit.calc(flds[j].name, model[k])).toFixed(4);
547                    }
548                    emis.push(e);
549                    key++;
550                }
551            }
552        }
553
554        let calc_end = window.performance.now();
555        console.log('Calculation runtime: ' + (calc_end - calc_start).toFixed(4) + ' ms');
556        // if settings showCalcTimes => app.$message.info('Calculation runtime: ' + (calc_end - calc_start).toFixed(4) + ' ms');   
557        return emis;
558    }
559
560
561
562
563    /** DB Functions */
564
565    function query(qry) {
566        let res, coll;
567
568        switch (qry.typ) {
569            case 'check': return _db.getCollection(qry.coll); break;
570
571            case 'find':
572                res = JSON.stringify(_db.getCollection(qry.coll).find(qry.q));
573                return JSON.parse(res);
574                break;
575
576            case 'lookup':
577                let lu = _db.getCollection('lookups').findOne(qry.q);
578                if (qry.map) {
579                    let ary = lu.data.split(',');
580                    return ary.map(item => { return { label: item, value: item } })
581                } else {
582                    return lu.data.split(',');
583                }
584                break;
585
586            case 'replace':
587                coll = _db.getCollection(qry.coll);
588                res = coll.findOne(qry.q);
589                if (res != null) { coll.remove(res.$loki) }
590                coll.insert(qry.doc);
591                break;
592
593            case 'load': // Any subactivity that creates a cusutom loki entry needs to be loaded on the module create hook!
594                coll = _db.getCollection(qry.coll);
595                coll.insert(qry.doc);
596                break;
597
598            case 'add':
599                for (let n in qry.data) { _db.addCollection(n).insert(qry.data[n]) } break;
600
601        }
602    }
603
604
605
606    /**  CLOSURE ACCESS */
607
608    return {
609        recover: function() { resetPass() },
610        nav: function(route) { (route != -1) ? router.push(route).catch(err => { }) : router.go(-1) },
611        ajax: function(req) { return request(req) },
612        getProp: function(prop, id) { return property(prop, id) },
613        qry: function(qry) { return query(qry) },
614        fb: function(req) { fire(req) },
615        fs: function(req) { return fire(req) },
616        calc: function(prop, vm) { return _computed[prop](vm) },
617        emis: function() { return emissions() },
618
619        ifLoaded: function(frame) { templatesLoaded(frame) }, // kill for prod => render fucntion eliminate the need for this
620        eject: function() { unloadModule() },
621        setMap: function(marks) { _map.geo = marks; console.log('geo =', _map.geo) },
622        datSize: function() { return _proj.data },
623
624        log: function(prop) { return _proj.data }, // kill for prod
625    }
626})();
627
628
629
630
631
632
633/** HELPER FUNCTIONS
634 * 
635 * 
636 * 
637 */
638
639function getVM(sub, ret_prop, cond_prop, val) { // cond must always match "==" val, should always return a number, returns first match only! 
640    let data = emit.getProp('vm', sub);
641    if (cond_prop) {
642        for (let n = 0; n < data.length; n++) {
643            if (data[n][cond_prop] == val) { return Number(data[n][ret_prop]); }
644        }
645        return 0;
646    } else {
647        return data[0][ret_prop];
648    }
649}
650
651function getVMAry(sub, prop) {
652    let data = emit.getProp('vm', sub), list = [];
653    for (let n = 0; n < data.length; n++) {
654        if (data[n][prop] != '' && data[n][prop] != null) {
655            if (typeof data[n][prop] != 'object') {
656                list.push({ label: data[n][prop], value: data[n][prop] });
657            } else {
658                list.push(data[n][prop]);
659            }
660        }
661    }
662    return list;
663}
664
665function totalProd(o, target) {
666    let flow, dev = emit.getProp('vm', 'development')[0];
667    //console.log('@ eur target =', target);
668    //console.log('model =', o);
669    //console.log('dev = ', dev);
670
671    switch (dev.decCurv.value) {
672        case 'Hyperbolic':
673            flow = Number(dev[target]) / Math.pow(1 + 0.51 * dev.nomDec * dev.lop, 1.96078);
674            console.log('flow =', flow);
675            return ((Math.pow(dev[target], 0.51) / (-0.49 * dev.nomDec)) * (Math.pow(flow, 0.49) - Math.pow(dev[target], 0.49)) * o.compWells / 1000);
676            break;
677        case 'Exponential':
678            flow = dev[target] * Math.pow(2.718281828459, -dev.effDec / 100 * dev.lop);
679            console.log('flow =', flow);
680            return (((dev[target] - flow) / (dev.effDec / 100)) * o.compWells / 1000);
681            break;
682        case 'Harmonic':
683            flow = dev[target] / (1 + dev.nomDec * dev.lop);
684            console.log('flow =', flow);
685            return ((dev[target] / dev.nomDec * Math.log(dev[target] / flow)) * o.compWells / 1000);
686            break;
687        default: return NaN;
688    }
689}
690
691function calcGOR(o) {
692    if (o.api && o.dump) {
693        if (o.api < 30) {
694            return (0.459524 * o.dump + 8.071429);
695        } else if (o.api >= 30 && o.api < 40) {
696            return (0.801190 * o.dump + 11.821429);
697        } else { // api is > 40
698            return (1.119048 * o.dump + 14.642857);
699        }
700    } else { return 0 }
701}
702
703
704function sumAry(ary) { // could combine above with VM
705    let val = 0;
706    for (let x = 0; x < ary.length; x++) {
707        if (ary[x] != '') { val += Number(ary[x]) }
708    }
709    return val;
710}
711
712
713function mapVMAry(model, fn) {
714    let m = emit.getProp('vm', model);
715    return m.map(fn);
716}
717
718function sumVMAry(model, prop, cntNull) {
719    let m = emit.getProp('vm', model);
720    if (prop) {
721        let val = 0;
722        for (let i = 0; i < m.length; i++) {
723            if (cntNull) {
724                val += (m[i][prop] != '') ? Number(m[i][prop]) : 1;
725            } else {
726                if (m[i][prop] != '') { val += Number(m[i][prop]) }
727            }
728        }
729        return val;
730    } else { return m.length }
731}
732
733
734function exportTable(csv, filename) {
735    let csvFile = new Blob([csv], { type: "text/csv" });
736    let dlLink = document.createElement("a");
737    dlLink.download = filename;
738    dlLink.href = window.URL.createObjectURL(csvFile);
739    dlLink.click();
740    window.URL.revokeObjectURL(dlLink.href);
741}
742
743function switchAry(val, op, ary) {
744    let aLen = ary.length / 2;
745    for (let pos = 0; pos < ary.length; pos++) {
746        switch (op) {
747            case '=': if (ary[pos] == val) { return ary[Number(pos + aLen)] } break;
748            case '>': if (Number(val) > Number(ary[pos])) { return ary[Number(pos + aLen)] } break;
749            case '<': if (Number(val) < Number(ary[pos])) { return ary[Number(pos + aLen)] }
750        }
751    }
752}
753
754function aryMax(arr) {  //maxAry
755    let max = arr.reduce(function(a, b) {
756        return Math.max(a, b);
757    });
758    return max;
759}
760
761/*
762function decline(typ, yr) {
763    if (typ == 'Exponential') {
764        return 114 * Math.pow(Math.E, -0.117 * yr);
765    } else { // hyperbolic
766        return 56.652 * Math.pow(yr, -0.963);
767    }
768}
769*/
770
771
772
773
774
775
776
777
778
779function memorySizeOf(obj) {
780    var bytes = 0;
781
782    function sizeOf(obj) {
783        if (obj !== null && obj !== undefined) {
784            switch (typeof obj) {
785                case 'number':
786                    bytes += 8;
787                    break;
788                case 'string':
789                    bytes += obj.length * 2;
790                    break;
791                case 'boolean':
792                    bytes += 4;
793                    break;
794                case 'object':
795                    var objClass = Object.prototype.toString.call(obj).slice(8, -1);
796                    if (objClass === 'Object' || objClass === 'Array') {
797                        for (var key in obj) {
798                            if (!obj.hasOwnProperty(key)) continue;
799                            sizeOf(obj[key]);
800                        }
801                    } else bytes += obj.toString().length * 2;
802                    break;
803            }
804        }
805        return bytes;
806    };
807
808    function formatByteSize(bytes) {
809        if (bytes < 1024) return bytes + " bytes";
810        else if (bytes < 1048576) return (bytes / 1024).toFixed(3) + " KiB";
811        else if (bytes < 1073741824) return (bytes / 1048576).toFixed(3) + " MiB";
812        else return (bytes / 1073741824).toFixed(3) + " GiB";
813    };
814
815    return formatByteSize(sizeOf(obj));
816};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.