PageSourceSearch

https://joymeter.uk/js/app.js

js joymeter.uk collected 2026-10-04 04:30:30 UTC 55,708 bytes, 1,314 lines download raw bytes

1// ActivEnergy App v 1.2.1
2var type;
3
4var app = {
5  loadJSON: async function(version) {
6    localStorage.setItem('version',version);
7    const response = await fetch(`js/getContent.php?v=${version}`)
8    const json     = await response.json();
9    app.screens    = json.screens;
10    app.activities = json.activities;
11    app.tips       = json.tips;
12    document.getElementById('loading').style.display = 'none';
13    downloadCustomActivities();
14    app.history = new Array();
15    app.history.push(screen);        // for 'back'
16    console.log(screen);
17    app.moveTo(screen);
18  },
19
20  changeVersion: async function(version,title) {
21    await app.loadJSON(version);
22    app.moveTo("menu");
23    document.getElementById('title').innerHTML = "Changed to "+title;
24  },
25
26  loadText: async function() {
27    // passed as _GET values to index.php
28    if (!(typeof id === 'undefined' || id === null)) { 
29        if (localStorage.getItem('metaID') != id) { localStorage.clear(); } // change of user
30        localStorage.setItem('metaID', id);
31    }
32    localStorage.setItem('version',version); 
33
34    if (localStorage.getItem('hhPage')  == null) { localStorage.setItem('hhPage',1); }
35    if (!(localStorage.getItem('index')   > 0)) { localStorage.setItem('index',0); }
36    if (localStorage.getItem('lastNotification') == null) { localStorage.setItem('lastNotification',new Date()); }
37    if (localStorage.getItem('scheme')  != null) { document.documentElement.setAttribute('scheme', localStorage.getItem('scheme')) };
38
39    // load JSON
40    await app.loadJSON(localStorage.getItem('version'));
41
42    // does a local record of activities exist?
43    if (localStorage.getItem('acts') == null) {
44        localStorage.setItem('acts', JSON.stringify({})); // JSON to store activities
45        localStorage.setItem('key', 0);                   // activity key currently being modified
46        localStorage.setItem('index', 0);                 // incremental for unique keys
47    } else {
48        if (localStorage.getItem('metaID') != '0') { 
49            localStorage.setItem('acts', JSON.stringify({})); // JSON to store activities
50            downloadActs() 
51        };
52        app.actCount();
53    }
54
55    // CONSENT?
56    if (localStorage.getItem('consent') != null) {
57        document.getElementById('consent').style.display = 'none';
58        if (!(typeof sc === 'undefined' || sc === null)) { localStorage.setItem('sc', sc); }
59        // app.moveTo("menu");
60    } else {
61        document.getElementById('consent').style.display = '';
62        document.getElementById('navbar').style.display  = 'none';
63        document.getElementById('header').style.display  = 'none';
64        document.getElementById('slider').style.display  = 'none';
65        document.getElementById('input').style.display   = 'none';
66        document.getElementById('server').style.display  = 'none';
67        document.getElementById('list').style.display    = 'none';
68        document.getElementById('buttons').style.display = 'none';
69        document.getElementById('footer').style.display  = 'none';
70        window.scrollTo(0, 0);
71    }
72    // external url given with:  https://joymeter.uk?url=XY
73    if (typeof url !== 'undefined') {
74        console.log(url);
75        document.getElementById('server').setAttribute('src',url);
76        app.moveTo("server");
77    }
78    // app.history = new Array();
79  },
80
81  getAct: function() { // returns current activity
82    const key = localStorage.getItem('key');
83    const acts = JSON.parse(localStorage.getItem('acts'));
84    return acts[key];
85  },
86
87  isHome: function() { // true is currently reported activity is at home
88      if (app.getAct() != undefined) {
89         if (app.getAct()['location'] == 1) {
90            return true;
91         } else { 
92            return false; 
93         } 
94      } else {
95        return false; 
96     } 
97  },
98
99  actCount: function() {
100    let acts = JSON.parse(localStorage.getItem('acts'));
101    var weekAgo = new Date();
102        weekAgo.setDate(weekAgo.getDate() - 7);
103    var midnight = new Date().setHours(0,0,0,0);
104    const nonSurvey = Object.keys(acts).filter(x => (acts[x].category != 'survey'));
105    const weekKeys = nonSurvey.filter(x => x.split('_')[0] > weekAgo);
106    const dayKeys = nonSurvey.filter(x => x.split('_')[0] > midnight);
107    const count = dayKeys.length;
108    var score = 0;
109    if (count > 0) { score = 1 }
110    if (count > 4) { score = 2 }
111    if (count > 8) { score = 3 }
112    if (count > 12) { score = 4 }
113    if (count > 19) { score = 5 }
114    app.activities.Home.icon = 'stars_'+score;
115    document.getElementById('nav-home-img').src = `svg/stars_${score}.svg`;
116    return count;
117  },
118
119  formatString: function(str) {
120    if (str.includes('${actCount}')) {
121        str = str.replace('${actCount}', app.actCount());
122    }
123    if (str.includes('${metaID}')) {
124        str = str.replace('${metaID}', localStorage.getItem('metaID'));
125    }
126    return str
127  },
128
129  moveFrom: function(from) {
130    const act = app.activities[from];
131    var   cat = act.category;
132    var   key = localStorage.getItem('key');
133    var  acts = JSON.parse(localStorage.getItem('acts'));
134    if (cat == 'selection') {
135        app.activities[from]['icon'] = "done";
136        app.activities[from]['help'] = "Done. You can update it.";
137        app.activities[from]['activity'] = from;   // reinstated to aid custom act download ... removed - messed up the label with the json 'key' ?!
138        uploadModifiedAct(app.activities[from]);
139        cat = 'survey';
140      }
141
142    switch(cat) {
143        case "menu":
144            console.log("no FROM action (menu)");
145            break;
146        case "skip":
147            console.log("skipping");
148            break;
149        case "abort":
150            delete acts[key]
151            key = '';
152            localStorage.setItem('key',key);
153            break;
154        case "location":
155            acts[key]['location'] = act.value;
156            break;
157        case "people":
158            acts[key]['people'] = act.value;
159            break;
160        case "enjoyment":
161            acts[key]['enjoyment'] = act.value;
162            break;
163        case "comfort":
164            acts[key]['comfort'] = act.value;
165            if (acts[key]['activity'] == null) {
166                acts[key]['activity'] = act.activity;
167                acts[key]['category'] = act.category;
168            }
169            localStorage.setItem('lastNotification',new Date());
170            break;
171        //case "freetext":
172        //    app.setFooter('save');
173        //    break;
174        case "server":
175            var url = act.url;
176            url = url.replace("{hhID}", localStorage.getItem('hhID'));
177            url = url.replace("{hhSC}", localStorage.getItem('hhSC'));
178            url = url.replace("{hhPage}", localStorage.getItem('hhPage'));
179            document.getElementById('server').setAttribute('src',`${url}`);
180            console.log(`server: ${url}`);
181            break;
182        case "survey":
183            // immediate upload as activity
184            // app.newAct('survey');
185            console.log("New Survey Entry")
186            key = `${Number(new Date())}_${localStorage.getItem('index')}_${localStorage.getItem('metaID')}`;
187            acts[key] = {};
188            acts[key]['idActivities'] = key;
189            acts[key]['dt_recorded'] = new Date();
190            acts[key]['dt_activity'] = new Date();
191            acts[key]['Meta_idMeta'] = localStorage.getItem('metaID');
192            acts[key]['path'] = from;
193            acts[key]['key']  = from;
194            // acts[key]['tuc']  = act.tuc;
195            acts[key]['activity']  = act.activity;
196            acts[key]['category'] = cat;
197            uploadActs(acts);
198            // make Menu/Survey button return to last survey screen
199            app.activities.Survey.next = act.next;
200            app.activities.Survey['activity'] = "Survey";
201            app.activities.Survey['icon'] = "survey_progress";
202            app.activities.Survey['help'] = act.help;
203            uploadModifiedAct(app.activities.Survey);
204            break;
205        default:
206            console.log(from);
207            console.log(key);
208            console.log(acts[key]);
209            acts[key]['key']  = from;
210            // acts[key]['tuc']  = act.tuc;
211            acts[key]['activity']  = act.activity;
212            acts[key]['category'] = act.category;
213            document.getElementById('freetext').value = act.activity;
214            app.setFooter('done');
215    }
216
217    if ('belongs' in act) {
218        if (act.belongs != "") {
219            console.log("XXX Uploading a belonger:", act.belongs);
220            acts[key]['belongs'] = act.belongs;
221            // any activity that 'belongs' somewhere is put there and prepared to become a custom activity during upload
222            app.activities[from].icon              = act.icon;
223            app.activities['Change icon new'].icon = act.icon;
224            app.activities['Change icon'].icon     = act.icon;
225            idx = app.screens[act.belongs]['activities'].indexOf(from);
226            // button exists: delete and insert at top
227            if (idx > -1) { delete app.screens[act.belongs]['activities'][idx]; }
228            app.screens[act.belongs]['activities'].unshift(from);
229            uploadModifiedAct(app.activities[from]);
230        }
231    }
232
233    if (key in acts) { acts[key]['path'] += `,${act.key}` }; // 'if' for server use (key could be '')
234    localStorage.setItem('acts',JSON.stringify(acts));
235  },
236
237
238  moveTo: function(to) {
239    if ((to == "home") && app.isHome()) {
240      // time for a timed request?
241      hoursElapsed = (new Date().getTime() - new Date(localStorage.getItem('lastNotification')).getTime())/3600000;
242      if (hoursElapsed > 22) { to = 'comfort'; }
243    }
244    document.getElementById('buttons').style.height = '';
245    app.drawButtons(to);
246    switch(to) {
247        case "home":
248            downloadCustomActivities( uploadActs(app.listActivities()) );
249            app.history = new Array();
250            document.getElementById('list').style.display = '';
251            setTimeout(function() { document.getElementById('buttons').style.display = 'none'; }, 0);
252            document.getElementById('footer').style.display = 'none';
253            localStorage.setItem('eOffset',0);
254            app.setNav('nav-home');
255            break;
256        case "user":
257            app.setNav('nav-user');
258            document.getElementById('footer').style.display = 'none';
259            break;
260        case "menu":
261            downloadCustomActivities();
262            app.history = new Array();
263            app.setNav('nav-menu');
264            document.getElementById('footer').style.display = 'none';
265            document.getElementById('buttons').style.height = '75%';
266            break;
267        case "comfort":
268            sliderInput('comfort');
269            document.getElementById('slider').style.display = '';
270            setTimeout(function() { document.getElementById('buttons').style.display = 'none'; }, 0);
271            app.setFooter('abort');
272            document.getElementById('footer').style.display = '';
273            break;
274        case "freetext":
275            app.setFooter('save');
276            document.getElementById('input').style.display = '';
277            break;
278        case "freetextNew":
279            app.setFooter('save');
280            document.getElementById('input').style.display = '';
281            break;
282    }
283    app.history.push(to);        // for 'back'
284  },
285
286  drawButtons: function(to) {
287    if (typeof app.screens[to] == 'undefined') {
288        to = "menu";
289    }
290    const screen = app.screens[to];
291    app.defaultView();
292    if (to == "server") {
293        if (localStorage.getItem('sc') == null) {
294            document.getElementById('navbar').style.display  = 'none';
295        }
296        document.getElementById('server').style.display = '';
297        document.getElementById('header').style.display = 'none';
298        document.getElementById('footer').style.display = 'none';
299    } else {
300        document.getElementById('buttons').innerHTML = ''; // start afresh
301        document.getElementById('buttons').className = 'buttons';
302        document.getElementById('title').innerHTML = app.formatString(screen.title);
303        for (i in screen.activities) {
304            actKey = screen.activities[i];
305            act    = app.activities[actKey];
306            // Button
307            var btn   = document.createElement('div');
308            btn.className   = `btn ${act.category}`;
309            btn.style.backgroundImage = `url('svg/${act.icon}.svg')`;
310            if (Object.keys(screen.activities).length > 6) {
311                document.getElementById('buttons').className = 'buttons oneCol';
312                btn.style.backgroundPosition = 'left';
313            }
314            if (act.category == 'function') {
315                btn.setAttribute("onclick", act.next);
316            } else {
317                btn.setAttribute("onclick", `app.moveFromTo('${actKey}','${act.next}')`);
318            }
319            // Title
320            var title = document.createElement('div');
321            title.className = 'btn-title';
322            title.innerHTML = app.formatString(act.activity);
323            // Help
324            var help  = document.createElement('div');
325            help.className  = 'help no-help';
326            help.innerHTML  = act.help;
327            btn.appendChild(title);
328            btn.appendChild(help);
329            document.getElementById('buttons').appendChild(btn);
330        }
331        // quirk to make animation work
332        setTimeout(function() {
333            document.getElementById('list').scrollTo(0,0); 
334            document.getElementById('buttons').style.display = ''; }
335            , 0);
336    }
337  },
338
339
340  moveFromTo: function(from, to) {
341    console.log("FROM:",from);
342    console.log("to:",to);
343    document.getElementById('buttons').style.animation = '0.5s ease-out 0s 1 slideIn';
344    app.moveFrom(from);
345    app.moveTo(to);
346  },
347
348  setFooter: function(footRight) {
349    const footer = app.activities[footRight];
350    document.getElementById('foot-right-img').src = `svg/${footer.icon}.svg`;
351    document.getElementById('foot-right-help').innerHTML = footer.help;
352    document.getElementById('foot-right').setAttribute("onclick", footer.next);
353  },
354
355  defaultView: async function() {
356    document.getElementById('navbar').style.display = '';
357    document.getElementById('header').style.display = '';
358    document.getElementById('slider').style.display = 'none';
359    document.getElementById('input').style.display = 'none';
360    document.getElementById('server').style.display = 'none';
361    document.getElementById('list').style.display = 'none';
362    document.getElementById('buttons').style.display = 'none';
363    document.getElementById('footer').style.display = '';
364    [...document.querySelectorAll('.help')].map(e=>{e.classList.add('no-help')});
365    document.getElementById('nav-help').classList.remove('nav-on');
366  },
367
368  setNav: function(on) {
369    document.getElementById('nav-menu').classList = 'btn-nav';
370    document.getElementById('nav-home').classList = 'btn-nav';
371    document.getElementById('nav-user').classList = 'btn-nav';
372    document.getElementById(on).classList = 'nav-on';
373  },
374
375  deleteAct: async function() {
376    const key = localStorage.getItem('key');
377    var acts = JSON.parse(localStorage.getItem('acts'));
378    deleteAct(key); // remove from server
379    await delete acts[key];
380    await localStorage.setItem('acts',JSON.stringify(acts));
381    app.moveTo('home');
382  },
383
384  changeEnj: function(key) {
385    localStorage.setItem('key',key);
386    app.moveTo('enjoyment');
387    app.setFooter('done');
388    app.markForUpload(key);
389  },
390
391  changeTitle: function(key) {
392    localStorage.setItem('key',key);
393      console.log("Changing key:",key);
394    var  acts = JSON.parse(localStorage.getItem('acts'));
395    document.getElementById('freetext').value = acts[key]['activity'];
396    if (acts[key].key in app.activities) {
397        app.activities['Change icon'].icon = app.activities[acts[key].key].icon;
398    }
399    app.setFooter('save');
400    app.moveTo('freetext');
401    app.markForUpload(key);
402  },
403
404  markForUpload: function(key) {
405    var  acts = JSON.parse(localStorage.getItem('acts'));
406    acts[key]['uploaded'] = false;
407    localStorage.setItem('acts',JSON.stringify(acts));
408  },
409
410  listActivities: function() {
411    var list = document.getElementById('list');
412    list.innerHTML = ''; // start afresh
413    list.className = '';
414    const options = { weekday: 'long', day: 'numeric', month: 'short'};
415    var acts = JSON.parse(localStorage.getItem('acts'));
416    var weekday = '';
417    var row = document.createElement('div');
418        row.className = 'activity-row new';
419        var recent = document.createElement('div');
420        recent.setAttribute("onclick","app.newAct('setTime');");
421        recent.className = 'activity-time';
422        recent.innerHTML = 'Set';
423        var actImg = document.createElement('img');
424        actImg.className = 'activity-icon';
425        actImg.setAttribute("onclick","app.newAct('setTime')");
426        actImg.src = 'svg/act_add.svg';
427        var label = document.createElement('div');
428        label.setAttribute("onclick","app.newAct('activity root');");
429        label.className = 'activity-item';
430        label.innerHTML = "New entry";
431        var blank = document.createElement('div');
432        blank.setAttribute("onclick","app.newAct('activity root');");
433        row.appendChild(recent);
434        row.appendChild(actImg);
435        row.appendChild(label);
436        row.appendChild(blank);
437    var recentHelp = document.createElement('div');
438        recentHelp.setAttribute("onclick","app.newAct('setTime');");
439        recentHelp.className = 'help no-help help-relative span-2';
440        recentHelp.innerHTML = 'Set time & duration';
441    var labelHelp = document.createElement('div');
442        labelHelp.setAttribute("onclick","app.newAct('activity root');");
443        labelHelp.className = 'help no-help help-relative';
444        labelHelp.innerHTML = 'Add activity or appliance you use now';
445        row.appendChild(recentHelp);
446        row.appendChild(labelHelp);
447        list.appendChild(row);
448
449
450    for (key in acts) {
451        act = acts[key];
452        if (!('activity' in act) || (act['activity'] == null)) {
453            console.log("deleted:", act.idActivities);
454            delete acts[key];
455        }
456    }
457    var keys = Object.keys(acts).sort().reverse();
458    for (var i = 0; i < keys.length; i++) {
459        // use i to process in date (key) order
460        var key = keys[i];
461        var act = acts[key];
462        if (('activity' in act) & (act['activity'] !== null) & (act['category'] != 'comfort') & (act['category'] != 'survey')) {
463            var dt = new Date(act.dt_activity);
464            thisWeekday = dt.toLocaleString(getLang(), options);
465            if (weekday != thisWeekday) { // add date row
466                var row   = document.createElement('div');
467                    row.className = 'activity-row date';
468                    var label = document.createElement('div');
469                    label.innerHTML = thisWeekday;
470                row.appendChild(document.createElement('div'));
471                row.appendChild(document.createElement('div'));
472                row.appendChild(label);
473                list.appendChild(row);
474                weekday = thisWeekday;
475            }
476            var row = document.createElement('div');
477            row.className = `activity-row ${act.category}`;
478                var time = document.createElement('div');
479                time.className = 'activity-time';
480                time.setAttribute("onclick",`app.timeSliders("${key}")`);
481                time.innerHTML = dt.toLocaleTimeString(getLang(), {hour: 'numeric', minute:'2-digit'});
482                var icon = document.createElement('img');
483                    icon.className = 'activity-icon';
484                    icon.setAttribute("onclick",`app.changeTitle("${key}")`);
485                if (act.key in app.activities) {
486                    icon.src = `svg/${app.activities[act.key].icon}.svg`;
487                }
488                var title = document.createElement('div');
489                title.className = 'activity-item';
490                title.setAttribute("onclick",`app.changeTitle("${key}")`);
491                title.innerHTML = act.activity;
492                var divEnj = document.createElement('div');
493                divEnj.className = 'activity-item';
494                divEnj.setAttribute("onclick",`app.changeEnj("${key}")`);
495                var actIcon = document.createElement('img');
496                actIcon.className = 'activity-enjoy';
497                actIcon.src = `svg/enjoy_${act.enjoyment}.svg`;
498                list.appendChild(row);
499            row.appendChild(time);
500            row.appendChild(icon);
501            row.appendChild(title);
502            row.appendChild(divEnj);
503            if (i == 0) {
504    var recentActHelp = document.createElement('div');
505        recentActHelp.setAttribute("onclick",`app.timeSliders("${key}")`);
506        recentActHelp.className = 'help no-help help-relative span-2';
507        recentActHelp.innerHTML = 'Change time & duration';
508    var labelActHelp = document.createElement('div');
509        labelActHelp.setAttribute("onclick",`app.changeTitle("${key}")`);
510        labelActHelp.className = 'help no-help help-relative';
511        labelActHelp.innerHTML = 'Rename, pick icon, or delete';
512    var enjoyActHelp = document.createElement('div');
513        enjoyActHelp.setAttribute("onclick",`app.changeEnj("${key}")`);
514        enjoyActHelp.className = 'help no-help help-relative';
515        enjoyActHelp.innerHTML = 'Change enjoyment';
516                row.appendChild(recentActHelp);
517                row.appendChild(labelActHelp);
518                row.appendChild(enjoyActHelp);
519            }
520
521            divEnj.appendChild(actIcon);
522        }
523    }
524    localStorage.setItem('acts',JSON.stringify(acts));
525    return acts;
526  },
527
528
529  listIcons: function(next) {
530    SVGs = ['renew', 'help', 'other', 'plus', 'dots', 'act', 'appliance', 'arrange', 'baking', 'bath', 'bathroom', 'battery_mower', 'bbq', 'beauty', 'bed_awake', 'bike', 'book', 'bread', 'car', 'child_bed', 'child_play', 'children', 'clean_floor', 'clean_yard', 'clothes_sort', 'coffeemaker', 'computer', 'cook', 'copy', 'custom', 'customised', 'data_transfer', 'dishwasher', 'dog',  'dress', 'dry_line', 'e_shower', 'eat',  'enjoy_1', 'enjoy_2', 'enjoy_3', 'enjoy_4', 'enjoy_5', 'EV', 'exercise', 'family', 'fan_heater', 'female', 'fish', 'food', 'foodprocessor', 'friends', 'games', 'garden', 'gardening', 'getting_ready', 'hair_curler', 'hair_dryer', 'hair_trimmer', 'AC', 'heater', 'help_physical', 'hob', 'home', 'hot_drink', 'house_garden', 'iron', 'kettle', 'laptop', 'laundry', 'lay_table', 'leisure', 'light', 'machinery', 'male', 'meal_cold', 'meal_hot', 'microwave', 'mixer', 'mobile', 'nature',  'others', 'outside', 'oven', 'patio_heater', 'personal', 'petrol_mower', 'pets',  'pottering', 'power_tool', 'printer', 'public_transport', 'radio', 'rest', 'router', 'run', 'sauna', 'save', 'screen', 'search', 'server', 'sewing', 'shaver', 'shop', 'shopping', 'shower', 'sink', 'sleep', 'slowcooker', 'snack', 'streaming', 'study', 'supervise', 'tablet', 'tidy', 'tip', 'toaster', 'toothbrush', 'toy', 'travel', 'tumble_dryer', 'TV', 'user_star', 'user', 'vacuum_cleaner', 'volunteer', 'walk', 'wash_dress', 'washer_dryer', 'washing_machine', 'with_child', 'with_others', 'work_machine', 'work_manual', 'work', 'workplace', 'english','language', 'www']
531
532      var list = document.getElementById('list');
533      list.innerHTML = ''; // start afresh
534      list.className = '';
535      var div = document.createElement('div');
536          div.className = 'icon-grid';
537      var cell = document.createElement('div');
538          cell.innerHTML = 'Current icon';
539      div.append(cell);
540
541      var key = localStorage.getItem('key');
542      var acts = JSON.parse(localStorage.getItem('acts'));
543      var actKey = acts[key].key;
544      var icon = app.activities[actKey].icon;
545      var img = document.createElement('img');
546          img.className = 'icon-item';
547          img.setAttribute("src",`svg/${icon}.svg`);
548          img.setAttribute("alt",`Current Icon`);
549          img.setAttribute("onclick", `updateIcon('${icon}','${next}')`);
550      div.append(img)
551      var cell2 = document.createElement('div');
552      cell2.innerHTML = '';
553      div.append(cell2);
554
555      for (i in SVGs) {
556        icon = SVGs[i];
557        //svg = icon.replace('icon/','');
558        //svg = svg.replace('.svg','');
559        img = document.createElement('img');
560        img.className = 'icon-item';
561        img.setAttribute("src",`svg/${icon}.svg`);
562        img.setAttribute("alt",`Image ${icon}`);
563        img.setAttribute("onclick", `updateIcon('${icon}','${next}')`);
564        div.append(img)
565      }
566    list.appendChild(div);
567    document.getElementById('input').style.display  = 'none';
568    document.getElementById('footer').style.display  = 'none';
569    document.getElementById('buttons').style.display  = 'none';
570    document.getElementById('title').innerHTML = "Pick an icon";
571    document.getElementById('list').style.display    = '';
572  },
573
574  featureGrid: function(feature,title,help) {
575    var list = document.getElementById('list');
576    list.innerHTML = ''; // start afresh
577    list.className = `feature-grid`;
578    var actFeature = feature;
579    if (feature == 'enjoy') {actFeature = 'enjoyment'} // bit of a hack to avoid renaming all enjoyment to enjoy
580    var recent = "app.newAct('setTime')"
581    var action = "app.newAct('activity root')";
582    if (feature == 'comfort') {
583         recent = "app.newAct('comfort root')";
584         action = "app.newAct('comfort root')";
585    }
586
587    var div = document.createElement('div');
588        div.className = 'feature-item-add';
589    var row = document.createElement('div');
590        row.className = 'activity-row new';
591        var actImg = document.createElement('img');
592        actImg.className = 'activity-icon';
593        actImg.setAttribute("onclick",recent);
594        actImg.src = 'svg/act.svg';
595        var plus = document.createElement('img');
596        plus.setAttribute("onclick",action);
597        plus.className = 'activity-icon';
598        plus.src = 'svg/plus.svg';
599        var label = document.createElement('div');
600        label.setAttribute("onclick",action);
601        label.className = 'activity-item';
602        label.innerHTML = "New entry";
603        var blank = document.createElement('div');
604        blank.setAttribute("onclick",action);
605        row.appendChild(actImg);
606        row.appendChild(plus);
607        row.appendChild(label);
608        row.appendChild(blank);
609    div.appendChild(row);
610    list.appendChild(div);
611
612    var lastMonth = '';
613    var acts = JSON.parse(localStorage.getItem('acts'));
614    var keys = Object.keys(acts).sort().reverse();
615    var count = 0;
616    for (var i = 0; i < keys.length; i++) {
617        var key = keys[i];
618        var act = acts[key];
619        if ((actFeature in act) & (act[actFeature] !== null)) {
620            var div = document.createElement('div');
621                var featureVal = `${feature}_${act[actFeature]}`;
622                    featureVal = featureVal.replace('.','_');
623                div.className = `feature-item ${featureVal}`;
624                div.setAttribute("onclick",`app.toggleId("feature_${key}",".feature-label")`);
625            var label = document.createElement('div');
626                label.className = `feature-label ${featureVal}`;
627                label.id = `feature_${key}`;
628                label.setAttribute("onclick",`app.toggleId("feature_${key}",".feature-label")`);
629                var dt = new Date(act.dt_activity);
630                var day = dt.toLocaleString(getLang(), { weekday: 'short', day: 'numeric', month: 'numeric'});
631                var time = dt.toLocaleTimeString(getLang(), {hour: '2-digit', minute:'2-digit'});
632                if (featureVal in app.activities) { // e.g. 'comfort_1' exists
633                    caption = app.activities[featureVal].activity;
634                    label.innerHTML = `${day}<br>${time}<br><b>${caption}</b>`;
635                } else {
636                    label.innerHTML = `${day}<br>${time}<br><b>${act.activity}</b><span class="zoom-inner">`;
637                }
638                label.style.display = 'none';
639            var month = dt.toLocaleString(getLang(), {month: 'long', year: 'numeric'});
640            if (month != lastMonth) {
641                var monthDiv = document.createElement('div');
642                    monthDiv.innerHTML = month;
643                monthDiv.className = 'feature-date';
644                    list.appendChild(monthDiv);
645            }
646            lastMonth = month;
647            list.appendChild(div);
648            list.appendChild(label);
649            count +=1;
650        }
651    }
652    var tipText = document.createElement('div');
653        tipText.className = "help help-top no-help";
654        tipText.setAttribute("onclick","app.toggleHelp()");
655        tipText.innerHTML = help
656    list.appendChild(tipText);
657    app.defaultView();
658    document.getElementById('footer').style.display  = 'none';
659    document.getElementById('title').innerHTML = title;
660    document.getElementById('list').style.display    = '';
661  },
662
663
664  onboardedScreen: function() {
665    document.getElementById('title').innerHTML = "Registration in progress";
666    var list = document.getElementById('list');
667    list.innerHTML = ''; // start afresh
668    var strapline = document.createElement('p');
669        strapline.innerHTML = "Your smart meter information has been submitted.<br><br>Please check your email (and junk folder) for an email to confirm your consent. At the very bottom of the Terms & Conditions you need to 'sign' with your postcode. Once you have done this, it should not take long for you to be able to access your smart meter data here. <br><br>Any problems? Please email <a href='mailto:[email protected]'>[email protected]</a> and we try to help.";
670        list.append(strapline);
671
672    var button  = document.createElement('button');
673        button.className = 'btn-green';
674        button.setAttribute("onclick",'app.validateEUIscreen()');
675        button.innerHTML = "Enter smart meter details again";
676        list.append(button);
677
678    app.defaultView();
679    document.getElementById('footer').style.display  = 'none';
680    document.getElementById('list').style.display    = '';
681  },
682
683
684  validateEUIscreen: function() {
685    document.getElementById('title').innerHTML = "Register your smart meter";
686    var list = document.getElementById('list');
687    list.className = '';
688    list.innerHTML = ''; // start afresh
689    var display  = document.createElement('img');
690        display.src = 'svg/GUID.svg';
691        display.setAttribute("width","80%");
692        list.append(display);
693    var strapline = document.createElement('p');
694        strapline.id = 'eui-text';
695        strapline.innerHTML = 'Please enter the 16 digit code under your smart meter display and confirm your postcode';
696        list.append(strapline);
697    var eui = document.createElement('input');
698        eui.id = 'eui';
699        eui.type = 'text';
700        eui.className = 'input-eui';
701        eui.placeholder = 'xx xx xx xx xx xx xx xx';
702        list.append(eui);
703    var postcode  = document.createElement('input');
704        postcode.id = 'postcode';
705        postcode.type = 'text';
706        postcode.className = 'input-postcode';
707        postcode.placeholder = 'postcode';
708        list.append(postcode);
709    var button  = document.createElement('button');
710        button.id = 'eui-button';
711        button.className = 'btn-green';
712        button.setAttribute("onclick",'validateEUI()');
713        button.innerHTML = "Submit";
714        list.append(button);
715    app.defaultView();
716    document.getElementById('footer').style.display  = 'none';
717    document.getElementById('list').style.display    = '';
718},
719
720  newAct: function(next) {
721    var  acts = JSON.parse(localStorage.getItem('acts'));
722    localStorage.setItem('index', parseInt(localStorage.getItem('index'))+1);
723    key = `${Number(new Date())}_${localStorage.getItem('index')}_${localStorage.getItem('metaID')}`;
724    localStorage.setItem('key',key);
725    acts[key] = {};
726    acts[key]['idActivities'] = key;
727    acts[key]['dt_recorded'] = new Date();
728    if (localStorage.getItem('dt_activity') != null) {
729        // hack for appliances to store their time from energy graph
730        acts[key]['dt_activity'] = new Date(localStorage.getItem('dt_activity'));
731        localStorage.removeItem('dt_activity');
732    } else {
733        acts[key]['dt_activity'] = new Date();
734    }
735    acts[key]['Meta_idMeta'] = localStorage.getItem('metaID');
736    acts[key]['path'] = ''; //act.tuc;
737    localStorage.setItem('acts',JSON.stringify(acts));
738    if (next == 'setTime') {
739        app.timeSliders(key);
740    } else {
741        app.moveTo(next);
742    }
743  },
744
745  timeSliders: function(key) {
746    localStorage.setItem('key',key);
747    app.markForUpload(key);
748    const acts = JSON.parse(localStorage.getItem('acts'));
749    var act = acts[key];
750    var list = document.getElementById('list');
751    list.innerHTML = ''; // start afresh
752    list.className = '';
753    app.defaultView();
754    document.getElementById('list').style.display    = '';
755    if ('activity' in act) {
756        document.getElementById('title').innerHTML = act.activity;
757        app.setFooter('saveTime');
758    } else {
759        document.getElementById('title').innerHTML = "Set time for new activity";
760        app.setFooter('next');
761    }
762
763    var dt = new Date(act.dt_activity);
764    var time = document.createElement('h1');
765        time.id = 'sliderTime';
766        time.innerHTML = dt.toLocaleTimeString(getLang(), {hour: 'numeric', minute:'2-digit'})+"<br><small>Slide to change time</small>";
767    var timeSlider = document.createElement('input');
768        timeSlider.id = 'timeSlider';
769        timeSlider.className = 'time-slider';
770        timeSlider.type = 'range';
771        timeSlider.setAttribute("min","-60");
772        timeSlider.setAttribute("max","60");
773        timeSlider.setAttribute("step","5");
774        timeSlider.setAttribute("value","0");
775        timeSlider.setAttribute("oninput","app.changeTime()");
776        timeSlider.setAttribute("onmouseup","app.setTime()");
777        timeSlider.setAttribute("ontouchend","app.setTime()");
778    list.append(time)
779    list.append(timeSlider)
780
781    var duration  = 0;
782    if ('duration' in act) { duration = parseInt(act.duration); }
783
784    var durationText = document.createElement('h1');
785        durationText.id = 'sliderDuration';
786    var durationSlider = document.createElement('input');
787        durationSlider.id = 'durationSlider';
788        durationSlider.className = 'time-slider';
789        durationSlider.type = 'range';
790        durationSlider.setAttribute("min","0");
791        durationSlider.setAttribute("max",duration+60);
792        durationSlider.setAttribute("step",5);
793        durationSlider.setAttribute("value",duration);
794        durationSlider.setAttribute("oninput","app.changeDuration()");
795        durationSlider.setAttribute("onmouseup","app.setDuration()");
796        durationSlider.setAttribute("ontouchend","app.setDuration()");
797    list.append(durationText);
798    list.append(durationSlider);
799    app.changeDuration();
800},
801
802  changeDuration: function() {
803    var duration = parseInt(document.getElementById('durationSlider').value);
804    const key = localStorage.getItem('key');
805    var acts = JSON.parse(localStorage.getItem('acts'));
806    var  act = acts[key];
807    var   dt = new Date(act.dt_activity);
808    var dtime = new Date(dt.setTime(dt.getTime() + (60000*duration)));
809    dtime = roundDate(dtime);
810    var dtimeStr = dtime.toLocaleTimeString(getLang(), {hour: 'numeric', minute:'2-digit'});
811    dtimeStr = `<br><small>until ${dtimeStr}</small>`
812    if (duration > 57) {
813        var hours = Math.floor(duration/60);
814        var minutes = duration%60;
815        if (minutes < 10) {minutes = '0'+minutes;}
816        document.getElementById('sliderDuration').innerHTML = `For ${hours}h ${minutes}min ${dtimeStr}`;
817    } else if (duration > 0) {
818        document.getElementById('sliderDuration').innerHTML = `For ${duration} min ${dtimeStr}`;
819    } else {
820        document.getElementById('sliderDuration').innerHTML = `Duration (optional)`;
821    }
822  },
823
824  setDuration: function() {
825    const key = localStorage.getItem('key');
826    var  acts = JSON.parse(localStorage.getItem('acts'));
827    var durationSlider = document.getElementById('durationSlider');
828    const duration = parseInt(durationSlider.value);
829    acts[key]['duration'] = duration;
830    localStorage.setItem('acts',JSON.stringify(acts));
831    durationSlider.setAttribute("max",duration+60);
832    durationSlider.setAttribute("value",duration);
833  },
834
835  changeTime: function() {
836    const key = localStorage.getItem('key');
837    var acts = JSON.parse(localStorage.getItem('acts'));
838    var  act = acts[key];
839    var   dt = new Date(act.dt_activity);
840    var time = document.getElementById('sliderTime');
841    var slider = document.getElementById('timeSlider');
842    const res = 5*60*1000;
843    var  dt = new Date(dt.setTime(dt.getTime() + (60000*slider.value)));
844    dt = roundDate(dt);
845    var diff = parseInt((dt-roundDate(new Date()))/60000);
846    var diffStr = "(now)";
847    if (diff > 0) {
848        diffStr = `(in ${diff} min)`;
849        }
850    if (diff > 59) {
851        var hours = Math.floor(diff/60);
852        var minutes = diff%60;
853        if (minutes < 10) {minutes = '0'+minutes;}
854        diffStr = `(in ${hours}:${minutes})`;
855        }
856    if (diff < 0) {
857        diffStr = `(${-diff} min ago)`;
858        }
859    if (diff < -59) {
860        var hours = Math.floor(-diff/60);
861        var minutes = -diff%60;
862        if (minutes < 10) {minutes = '0'+minutes;}
863        diffStr = `(${hours}h ${minutes}min ago)`;
864        }
865    diffStr = `<br><small> ${diffStr}</small>`
866    time.innerHTML = dt.toLocaleTimeString(getLang(), {hour: 'numeric', minute:'2-digit'}) + diffStr;
867    // update duration end time
868
869    var duration = parseInt(document.getElementById('durationSlider').value);
870    if (duration > 0) {
871        var dtime = new Date(dt.setTime(dt.getTime() + (60000*duration)));
872        var dtimeStr = dtime.toLocaleTimeString(getLang(), {hour: 'numeric', minute:'2-digit'});
873        dtimeStr = `<br><small>until ${dtimeStr}</small>`
874        document.getElementById('sliderDuration').innerHTML = dtimeStr;
875    }
876
877  },
878
879  setTime: function() {
880    const key = localStorage.getItem('key');
881    var  acts = JSON.parse(localStorage.getItem('acts'));
882    var   act = acts[key];
883    var    dt = new Date(act.dt_activity);
884    const slider = document.getElementById('timeSlider');
885    const res = 5*60*1000;
886    dt = new Date(Math.round(dt.setTime(dt.getTime() + (60000*slider.value))/res)*res);
887    const newKey = `${Number(dt)}_${localStorage.getItem('index')}_${localStorage.getItem('metaID')}`;
888    acts[key]['dt_activity'] = dt;
889    if (newKey != key) {
890        localStorage.setItem('key',newKey);
891        acts[key]['idActivities'] = newKey;
892        acts[newKey] = acts[key];                       // create copy
893        delete acts[key];                               // remove local
894        deleteAct(key);                                 // remove server
895    }
896    localStorage.setItem('acts',JSON.stringify(acts));
897    document.getElementById('timeSlider').value = 0;
898  },
899
900  saveTime: function() {
901    // called from footer as belts and braces in case the 'ontouchend' doesn't fire (Safiri, IE, Opera)
902    app.setDuration();
903    app.setTime();
904    app.moveTo('home');
905  },
906
907
908  energyHistory: function(_type) {
909    type = _type;
910    app.defaultView();
911    document.getElementById('footer').style.display  = 'none';
912
913    var list = document.getElementById('list');
914        list.innerHTML = ''; // start afresh
915        list.style.display    = '';
916    var container = document.createElement('div');
917        container.id = 'energy-year-container';
918        container.className = 'energy-year-container';
919    var switchBtn = document.createElement('button');
920        // switchBtn.id = 'energy-switch';
921        switchBtn.className = 'energy-switch';
922    if (type == 'el') {
923        document.getElementById('title').innerHTML = "Annual Electricity";
924        switchBtn.innerHTML = "show Gas";
925        switchBtn.setAttribute("onclick","app.energyHistory('gas')");
926    } else {
927        document.getElementById('title').innerHTML = "Annual Gas use";
928        switchBtn.innerHTML = "show Electricity";
929        switchBtn.setAttribute("onclick","app.energyHistory('el')");
930    }
931    list.append(switchBtn);
932    var monthContainer = document.createElement('div');
933        monthContainer.id = 'energy-month-container';
934        monthContainer.className = 'energy-month-container';
935        monthContainer.width = '100%';
936
937    var loading = document.createElement('img');
938        loading.id = 'loadingYear';
939        loading.setAttribute("src","vid/loading.gif");
940        loading.setAttribute("alt","Loading your data ...");
941        loading.className = "loading";
942    list.append(loading);
943
944    for (let m = 1; m < 13; m++) {
945        var thisBar = document.createElement('div');
946            thisBar.id = `m${m}`;
947            thisBar.className = `energy-day-bar movable`;
948            thisBar.style.height = "1px";
949            thisBar.style.width  = "6%";
950            thisBar.style.left   = `${m*6}%`;
951        container.appendChild(thisBar);
952        var dayLabel = document.createElement('div');
953            dayLabel.id          = `m${m}_lbl`;
954            dayLabel.className   = 'energy-month-label';
955            dayLabel.style.width = "6%";
956            dayLabel.style.left  = `${m*6}%`;
957        container.appendChild(dayLabel);
958    }
959    var thisBar = document.createElement('div');
960        thisBar.id = `year`;
961        thisBar.className = `energy-day-bar movable`;
962        thisBar.style.height = "100px";
963        thisBar.style.width  = "8%";
964        thisBar.style.right  = `5%`;
965    container.appendChild(thisBar);
966    var thisLine = document.createElement('div');
967        thisLine.id = `year_avg`;
968        thisLine.className = `energy-avg-line movable`;
969    container.appendChild(thisLine);
970    var thisLabel = document.createElement('div');
971        thisLabel.id = `year_lbl`;
972        thisLabel.className = `energy-month-label`;
973    container.appendChild(thisLabel);
974    list.appendChild(container);
975
976    for (let d = 1; d < 32; d++) {
977        var thisBar = document.createElement('div');
978            thisBar.id = `d${d}`;
979            thisBar.className = `energy-day-bar movable`;
980            thisBar.style.width  = "2.8%";
981            thisBar.style.left   = `${d*2.7}%`;
982        monthContainer.appendChild(thisBar);
983        var refBar = document.createElement('div');
984            refBar.id = `d${d}_ref`;
985            refBar.className = `energy-day-bar-ref movable`;
986            refBar.style.width  = "2%"
987            refBar.style.left   = `${d*2.7}%`;
988        monthContainer.appendChild(refBar);
989        var dayLabel = document.createElement('div');
990            dayLabel.id          = `d${d}_lbl`;
991            dayLabel.className   = 'energy-day-label';
992            dayLabel.style.left  = `${d*2.7}%`;
993        monthContainer.appendChild(dayLabel);
994    }
995    list.appendChild(monthContainer);
996    var thisBar = document.createElement('div');
997        thisBar.id = `month`;
998        thisBar.className = `energy-day-bar movable`;
999        thisBar.style.height = "100px";
1000        thisBar.style.width  = "8%";
1001        thisBar.style.right  = `5%`;
1002    monthContainer.appendChild(thisBar);
1003    var thisLine = document.createElement('div');
1004        thisLine.id = `month_avg`;
1005        thisLine.className = `energy-avg-line movable`;
1006    monthContainer.appendChild(thisLine);
1007    var thisLabel = document.createElement('div');
1008        thisLabel.id = `month_lbl`;
1009        thisLabel.className = `energy-month-label`;
1010    monthContainer.appendChild(thisLabel);
1011
1012    var graphLabel = document.createElement('div');
1013        graphLabel.id           = 'energy-month-label';
1014        graphLabel.className    = 'energy-graph-label';
1015    monthContainer.append(graphLabel);
1016
1017// POPULATE WITH DATA
1018    var id  = localStorage.getItem('metaID');
1019    var sc  = localStorage.getItem('sc');
1020    var thisYear = `api/getEnergy.php?range=thisYear&id=${id}&sc=${sc}&type=${type}`
1021    var refYear = `api/getEnergy.php?range=refYear&id=${id}&sc=${sc}&type=${type}`
1022      console.log("refYear api: ",refYear);
1023    if (localStorage.getItem(`thisYear_${type}`) !== null) {
1024        plotEnergyThisYear(JSON.parse(localStorage.getItem(`thisYear_${type}`))); 
1025    }
1026    fetch(thisYear)
1027      .then(response => response.json())
1028          .then(d => {
1029            if ('noAccess' in d) {
1030                // sc and id didn't pass security
1031                url = "register/login.php"
1032                document.getElementById('server').setAttribute('src',url);
1033                app.moveTo("server");
1034            } else if ('notOnboarded' in d) {
1035                // not listed in Meta as 'active','fulfilled' or 'connected'
1036                console.log("not onboarded");
1037                if (localStorage.getItem('hasOnboarded')) {
1038                    // user has already submitted data
1039                    app.onboardedScreen();
1040                } else {
1041                    app.validateEUIscreen();
1042                }
1043            } else {
1044                document.getElementById("loadingYear").style.display = 'none';
1045                localStorage.setItem(`thisYear_${type}`,JSON.stringify(d));
1046                plotEnergyThisYear(d);
1047                fetch(refYear)
1048                  .then(response => response.json())
1049                      .then(d => {
1050                          localStorage.setItem(`refYear_${type}`,JSON.stringify(d));
1051                          plotEnergyRefYear(d);
1052                      })
1053            }
1054          });
1055  },
1056
1057  energyMonth: function(month) {
1058    document.getElementById("loadingYear").style.display = '';
1059
1060    // clear up any previously displayed month
1061    for (let d = 1; d < 32; d++) {
1062        document.getElementById(`d${d}`).style.height = "2px";
1063        document.getElementById(`d${d}_ref`).style.bottom = "0%";
1064        document.getElementById(`d${d}_lbl`).innerHTML = "";
1065    }
1066
1067    var mContainer = document.getElementById('month-container');
1068    var id  = localStorage.getItem('metaID');
1069    var sc  = localStorage.getItem('sc');
1070    var thisMonth = `api/getEnergy.php?range=thisMonth&id=${id}&sc=${sc}&month=${month}&type=${type}`
1071    var refMonth = `api/getEnergy.php?range=refMonth&id=${id}&sc=${sc}&month=${month}&type=${type}`
1072    console.log(thisMonth);
1073    fetch(thisMonth)
1074      .then(response => response.json())
1075          .then(d => {
1076            if ('noAccess' in d) {
1077                // sc and id didn't pass security
1078                url = "register/login.php"
1079                document.getElementById('server').setAttribute('src',url);
1080                app.moveTo("server");
1081            } else if ('notOnboarded' in d) {
1082                // not listed in Meta as 'active','fulfilled' or 'connected'
1083                console.log("not onboarded");
1084                if (localStorage.getItem('hasOnboarded')) {
1085                    // user has already submitted data
1086                    app.onboardedScreen();
1087                } else {
1088                    app.validateEUIscreen();
1089                }
1090            } else {
1091                // document.getElementById('loadingMonth').remove();
1092                plotEnergyThisMonth(d);
1093                fetch(refMonth)
1094                  .then(response => response.json())
1095                      .then(d => { plotEnergyRefMonth(d); 
1096                        document.getElementById("loadingYear").style.display = 'none';
1097                      })
1098            }
1099          });
1100  },
1101
1102  energyGrid: function(type) {
1103    document.getElementById('title').innerHTML = "Daily energy use";
1104    list.innerHTML = ''; // start afresh
1105    app.defaultView();
1106    document.getElementById('footer').style.display  = 'none';
1107    document.getElementById('list').style.display    = '';
1108    var loading = document.createElement('img');
1109        loading.setAttribute("src","vid/loading.gif");
1110        loading.setAttribute("alt","Loading your data ...");
1111        loading.setAttribute("width","100%");
1112        list.append(loading);
1113    var id  = localStorage.getItem('metaID');
1114    var sc  = localStorage.getItem('sc');
1115    var api = `api/getEnergy.php?range=days&id=${id}&sc=${sc}`
1116    fetch(api)
1117      .then(response => response.json())
1118          .then(d => {
1119            if ('noAccess' in d) {
1120                // sc and id didn't pass security
1121                url = "register/login.php"
1122                document.getElementById('server').setAttribute('src',url);
1123                app.moveTo("server");
1124            } else if ('notOnboarded' in d) {
1125                // not listed in Meta as 'active','fulfilled' or 'connected'
1126                console.log("not onboarded");
1127                if (localStorage.getItem('hasOnboarded')) {
1128                    // user has already submitted data
1129                    app.onboardedScreen();
1130                } else {
1131                    app.validateEUIscreen();
1132                }
1133            } else {
1134                energyGrid(d,type);
1135            }
1136          });
1137  },
1138
1139  energyActGraph: function() {
1140    var list = document.getElementById('list');
1141    list.innerHTML = ''; // start afresh
1142    list.className = '';
1143    app.defaultView();
1144    document.getElementById('footer').style.display  = 'none';
1145    document.getElementById('list').style.display    = '';
1146    document.getElementById('title').innerHTML = "Recent energy use";
1147    var loading = document.createElement('img');
1148        loading.setAttribute("src","vid/loading.gif");
1149        loading.setAttribute("alt","Loading your data ...");
1150        loading.setAttribute("width","100%");
1151        list.append(loading);
1152
1153    var id    = localStorage.getItem('metaID');
1154    var sc    = localStorage.getItem('sc');
1155    var api = `api/getEnergy.php?id=${id}&sc=${sc}&range=day`;
1156    fetch(api)
1157      .then(response => response.json())
1158          .then(d => {
1159            if ('noAccess' in d) {
1160                // sc and id didn't pass security
1161                url = "register/login.php"
1162                document.getElementById('server').setAttribute('src',url);
1163                app.moveTo("server");
1164            } else if ('notOnboarded' in d) {
1165                // not listed in Meta as 'active','fulfilled' or 'connected'
1166                console.log("not onboarded");
1167                if (localStorage.getItem('hasOnboarded')) {
1168                    // user has already submitted data
1169                    app.onboardedScreen();
1170                } else {
1171                    app.validateEUIscreen();
1172                }
1173            } else {
1174                // in Meta as 'active', fulfilled of connected
1175                // XXX handle 'no data'
1176                localStorage.setItem('postcode', d.postcode);
1177                plotEnergyPerHour(d);
1178                peakE = parseInt(localStorage.getItem('peakE'));
1179                d.el.forEach(el => {
1180                    var watt = parseInt(el.watt);
1181                    bar = document.getElementById(`bar_${el.hour}`);
1182                    bar.style.height = `${watt/peakE*80}%`;
1183                }
1184                )
1185
1186                var refAPI = `api/getRefEnergy.php?id=${id}&sc=${sc}&range=day`;
1187                console.log(refAPI);
1188                fetch(refAPI)
1189                  .then(response => response.json())
1190                      .then(d => {
1191                          peakE = parseInt(localStorage.getItem('peakE'));
1192                          d.el_ref.forEach( e => { peakE = Math.max(e.watt, peakE) })
1193                          d.el_self_ref.forEach( e => { peakE = Math.max(e.watt, peakE) })
1194                          localStorage.setItem('peakE',peakE);
1195
1196                          d.el_self_ref.forEach(
1197                              ref => {
1198                                  watt = parseInt(ref.watt);
1199                                  refBar = document.getElementById(`self_ref_${ref.hour}`);
1200                                  if (refBar) { refBar.style.bottom = `${10+(watt/peakE*80)}%`; }
1201                                  self_refLbl = document.getElementById(`self_refLabel_${ref.hour}`);
1202                                  if (self_refLbl) { self_refLbl.innerHTML = `Typical for you: ${watt} W`;}
1203                              }
1204                          )
1205                          d.el_ref.forEach(
1206                              ref => {
1207                                  watt = parseInt(ref.watt)
1208                                  refBar = document.getElementById(`ref_${ref.hour}`)
1209                                  if (refBar) { refBar.style.bottom = `${10+(watt/peakE*80)}%`; }
1210                                  refLbl = document.getElementById(`refLabel_${ref.hour}`)
1211                                  if (refLbl) { refLbl.innerHTML = `Typical for UK: ${watt} W`;}
1212                              }
1213                          )
1214                      })
1215            }
1216          });
1217
1218  },
1219
1220  addActToHour: function(dt) {
1221      localStorage.setItem('dt_activity', new Date(dt.replace(/-/g, "/")));
1222      app.newAct('appliances');
1223  },
1224
1225  showLabel: function(label) {
1226      var labels = document.getElementsByClassName('hour-label')
1227      for (var i = 0; i < labels.length; i++) {
1228        labels[i].style.display='none';
1229      }
1230      document.getElementById(label).style.display = '';
1231  },
1232  eShift: function(shift) {
1233    var offset = parseInt(localStorage.getItem('eOffset'));
1234    offset += shift;
1235    if (offset < 0)  { offset = 0; }
1236    localStorage.setItem('eOffset',offset);
1237    app.energyActGraph();
1238  },
1239  eZoom: function(zoom) {
1240    // ±8
1241    var hours = parseInt(localStorage.getItem('eHours'));
1242    hours += zoom;
1243    localStorage.setItem('eHours',hours);
1244    app.energyActGraph();
1245  },
1246
1247  toggleZoom: function() {
1248    var list = document.getElementById('list');
1249    var zoom = document.getElementById('zoom');
1250    if (document.documentElement.getAttribute('zoom') == 'in') {
1251      document.documentElement.setAttribute('zoom','out');
1252      zoom.setAttribute("src","svg/zoom_in.svg");
1253    } else {
1254      document.documentElement.setAttribute('zoom','in');
1255      zoom.setAttribute("src","svg/zoom_out.svg");
1256    }
1257  },
1258
1259  toggleId: function(id,divClass) {
1260    var toggle = document.getElementById(id).style.display;
1261    var labels = document.querySelectorAll(divClass);
1262    for (var i = 0; i < labels.length; i++) {
1263      labels[i].style.display = 'none';
1264    }
1265    if (toggle == 'none') {
1266        document.getElementById(id).style.display = '';
1267    }
1268  },
1269
1270  toggleClass: function(divClass) {
1271    div = document.getElementsByClassName(divClass);
1272    for (var i = 0; i < div.length; i++) {
1273      if (div[i].style.display == 'none') {
1274        div[i].style.display = '';
1275      } else {
1276        div[i].style.display = 'none';
1277      }
1278    }
1279  },
1280
1281  goBack: function() {
1282    app.history.pop(); // remove current
1283    const prev = app.history.pop();
1284    document.getElementById('buttons').style.animation = '0.5s ease-out 0s 1 slideBack';
1285    if (prev === undefined) {
1286        app.moveTo("home");
1287    } else {
1288        app.moveTo(prev);
1289    }
1290  },
1291
1292
1293//                        HELP
1294
1295  toggleHelp: function() {
1296    [...document.querySelectorAll('.help')].map(e=>{
1297        e.classList.toggle('no-help')
1298    });
1299    [...document.querySelectorAll('.help-float')].map(e=>{
1300        e.classList.toggle('no-help')
1301    });
1302    document.getElementById('nav-help').classList.toggle('nav-on');
1303  },
1304
1305  colourScheme: function(scheme) {
1306    document.documentElement.setAttribute('scheme',scheme);
1307    localStorage.setItem('scheme',scheme);
1308    app.moveTo('menu');
1309  },
1310
1311};
1312
1313app.loadText();
1314

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.