MIDAS
Loading...
Searching...
No Matches
eqtable.js
Go to the documentation of this file.
1let eqtable_style = `
2.mtable {
3 border-spacing: 0;
4}
5.mvarheader {
6 border-bottom-left-radius: 0;
7 border-bottom-right-radius: 0;
8 border-right: 1px solid white;
9}
10.mtable td {
11 padding: 5px;
12 border-right: 1px solid white;
13}
14.mtable th {
15 padding: 5px;
16 text-align: left;
17}
18.mthselect {
19 color:white;
20 font-weight:bold;
21 text-align-last:center;
22 border: none;
23 outline: none;
24 background-color: transparent;
25 width:100%;
26}
27td.pageSelector {
28 padding: 5px;
29 border-left: 2px solid #808080;
30 border-right: 2px solid #808080;
31 border-top: 2px solid #808080;
32 border-radius: 5px 5px 0 0;
33}
34`;
35
36let estyle = document.createElement('style');
37estyle.textContent = eqtable_style;
38document.head.appendChild(estyle);
39
40<!-- Select file dialog -->
41let dlgFileSelect = `
42<div class="dlgTitlebar">Select file</div>
43<div class="dlgPanel">
44 <br />
45 <div style="margin: auto;width: 70%">
46 <input type="file" id="fileSelector" accept=".json">
47 </div>
48 <br /><br />
49 <button class="dlgButton" onclick="importFileFromSelector(this.parentNode.parentNode);dlgHide('dlgFileSelect');">Load</button>
50 <button class="dlgButton" onclick="dlgHide('dlgFileSelect')">Cancel</button>
51</div>
52`;
53
54let equipment = {
55 "name" : "",
56 "enabled": [],
57 "tid": "",
58 "table" : [],
59 "vars": [],
60 "group": [],
61 "frontend_name": ""
62};
63
64function check_frontend() {
65
66 let fename = equipment.frontend_name;
67 mjsonrpc_call("cm_exist", {"name": fename}).then( function(rpc) {
68 if (rpc.result.status === 1)
69 document.getElementById("fewarning").style.display = "none";
70 else {
71 document.getElementById("fewarning").style.display = "";
72 document.getElementById("fewarning").firstChild.innerHTML =
73 "<a href='?cmd=Programs'>" + fename + "</a> not running";
74 }
75 window.setTimeout(check_frontend, 5000);
76 }).catch(function(error){mjsonrpc_error_alert(error);});
77
78}
79
80function valueChanged(t) {
81
82 let td = t.parentElement;
83
84 // don't make extra display yellow
85 if (td.parentElement &&
86 td.parentElement.parentElement &&
87 td.parentElement.parentElement.parentElement &&
88 td.parentElement.parentElement.parentElement.parentElement &&
89 td.parentElement.parentElement.parentElement.parentElement.id === "displayExtra")
90 return;
91
92 // check if value really changed
93 if (t.oldValue === undefined) {
94 t.oldValue = t.innerText;
95 return;
96 }
97
98 if (t.innerText === t.oldValue)
99 return;
100 t.oldValue = t.innerText;
101
102 td.style.backgroundColor = 'var(--myellow)';
103 td.style.setProperty("-webkit-transition", "", "");
104 td.style.setProperty("transition", "", "");
105
106 window.setTimeout(() => {
107 td.style.setProperty("-webkit-transition", "background-color 1s", "");
108 td.style.setProperty("transition", "background-color 1s", "");
109 td.style.backgroundColor = "";
110 }, 1000);
111}
112
113function redirectEq(div, eqName) {
114 // update URL manually
115 let url = window.location.href;
116 if (url.search("&eq=") !== -1) {
117 url = url.slice(0, url.search("&eq="));
118 url += "&eq=" + eqName;
119 if (url !== window.location.href)
120 window.history.replaceState({}, "Equipment", url);
121 }
122
123 // delete lower part of old table
124 let table = document.getElementById(equipment.tid);
125 table.remove();
126
127 equipment = {
128 "name" : "",
129 "table" : [],
130 "vars": [],
131 "group": []
132 };
133
134 // add new equipment
135 eqtable_init(div, eqName, true, true);
136}
137
138let confirmFlag = 0;
139
140function validateData(value, elem) {
141 // avoid double call of validateData
142 // - once by direct call
143 // - once by ODBFinishInlineEdit if focus is lost
144 if (confirmFlag === 1)
145 return false;
146
147 // get selected rows
148 let nSel = 0;
149 equipment.table.forEach( e => {
150 if (e.selected)
151 nSel++;
152 });
153
154
155 if (nSel > 1) {
156 confirmFlag++;
157 dlgConfirm("You have selected " + nSel + " values. Do you want to change all of them to " + value + " ?",
158 setAll, { "value" : value, "elem": elem });
159 return false;
160 }
161 return true;
162}
163
164function setAll(flag, param) {
165 confirmFlag--;
166 if (flag === true) {
167 let tr = param.elem.parentNode.parentNode;
168 let index;
169 for (let i=0 ; i<tr.childNodes.length ; i++)
170 if (tr.childNodes[i] === param.elem.parentNode) {
171 index = i;
172 break;
173 }
174 equipment.table.forEach(e => {
175 if (index && e.selected) {
176 let m = e.tr.childNodes[index].firstChild;
177 m.setValue(param.value);
178 m.sendValueToOdb();
179 }
180 });
181 }
182}
183function goToOdb() {
184 window.location.href = "?cmd=odb&odb_path=/Equipment/" + equipment.name;
185}
186
187function selectGroup(t) {
188 for (let g of equipment.group) {
189 let trGroup = document.getElementsByName(g);
190
191 for (let tr of trGroup)
192 tr.style.display = (g === t.value || t.value === "All") ? "table-row" : "none";
193 }
194}
195
196function enable_row(row, enabled, index) {
197 let disabled = false;
198 if (enabled !== undefined) {
199 if (Array.isArray(enabled)) {
200 if (!enabled[index])
201 disabled = true;
202 } else if (enabled === false)
203 disabled = true;
204 }
205
206 row.disabled = disabled;
207
208 if (disabled)
209 row.style.backgroundColor = "#B0B0B0";
210 else
211 row.style.backgroundColor = "";
212
213 // go through row elements and disable input and select elements
214 for (let child of row.children) {
215 if (child.childNodes[0] && child.childNodes[0].dataset && child.childNodes[0].dataset.odbEditable !== undefined)
216 child.childNodes[0].dataset.odbEditable = disabled ? "0" : "1";
217 if (child.childNodes[0] && child.childNodes[0].tagName === "SELECT")
218 child.childNodes[0].disabled = disabled;
219 }
220}
221
222function pageSel(page) {
223
224 let ps = document.getElementsByClassName('pageSelector');
225 if (ps.length === 0)
226 return;
227
228 // change color of tabs
229 let pageIndex = 0;
230 for (let i= 0 ; i<ps.length ; i++) {
231 if (ps[i].children[0].innerText === page) {
232 pageIndex = i-1;
233 ps[i].style.backgroundColor = "#8cbdff";
234 ps[i].children[0].style.color = "#f8f8f8";
235 } else {
236 ps[i].style.backgroundColor = "#dddddd";
237 ps[i].children[0].style.color = "#0000ff";
238 }
239 }
240
241 // hide/unhide table rows
242 if (page === "All") {
243 for (let i=0 ; i<equipment.table.length ; i++)
244 equipment.table[i].tr.style.display = "";
245 } else {
246 for (let i=0 ; i<equipment.table.length ; i++) {
247 if (Math.floor(i / equipment.channelsPerDevice) === pageIndex)
248 equipment.table[i].tr.style.display = "";
249 else
250 equipment.table[i].tr.style.display = "none";
251 }
252 }
253
254 // modify extra values
255 let extra = document.getElementById('displayExtra');
256 if (extra) {
257 extra = extra.children[0].children[0].children[0];
258 for (let i=0 ; i<extra.children.length ; i++) {
259 let d = extra.children[i].children[0];
260 if (d.className === "modbselect" || d.className ==="modbvalue") {
261
262 let p = d.dataset.odbPath;
263
264 if (page === "All") {
265 if (p.includes('['))
266 p = p.replace(/\[.*?\]/g, "[*]");
267 else
268 p = p + "[*]";
269 } else {
270 if (p.includes('['))
271 p = p.replace(/\[.*?\]/g, "[" + pageIndex + "]");
272 else
273 p = p + "[" + pageIndex + "]";
274 }
275
276 d.dataset.odbPath = p;
277 }
278 }
279 }
280
281 equipment.selectedPage = pageIndex;
282
283 mhttpd_refresh();
284}
285
286function eqtable_init(div, eqName, bButtons, bEqSelect) {
287
288 if (eqName === undefined)
289 equipment.name = new URLSearchParams(window.location.search).get('eq');
290 else
291 equipment.name = eqName;
292
293 if (equipment.name === undefined || equipment.name === null) {
294 dlgAlert("Please specify equipment name in URL with \"eq=<name>\"");
295 return;
296 }
297
298 // add equipment table to <div>
299 if (div === undefined) {
300 dlgAlert("Please specify <div> element when calling eqtable_init()");
301 return;
302 }
303
304 equipment.tid = 'eqTable' + equipment.name.replace(/\s+/g, ''); // remove all spaces from eq
305
306 let html = '<table id="' + equipment.tid + '" class="mtable" style="visibility: hidden">';
307
308 if (bEqSelect)
309 html +=
310 '<tr>\n' +
311 ' <td id="tableHeader" class="mtableheader">\n' +
312 ' <select class="mthselect" id="eqSelect" onchange="redirectEq(document.getElementById(\'' + div.id + '\'), this.value)">\n' +
313 ' </select>' +
314 ' </td>' +
315 '</tr>';
316
317 html += '<tr><td id="fewarning" width="100%" style="text-align: center; display: none">' +
318 '<span style="font-size: 2em; padding: 5px; display: block; background-color: var(--mred);">' +
319 '<a href="?cmd=Programs">Frontend</a> not running</span>' +
320 '</td></tr>';
321
322 if (bButtons)
323 html += `
324 <tr>
325 <td id="menuButtons" style="border-radius: 12px;">
326 <button class="dlgButton" id="btnSave" title="Save current setting" onclick="saveFilePicker();">Save</button>
327 <button class="dlgButton" id="btnLoad" title="Load setting from file" style="display: none" onclick="loadFilePicker();">Load</button>
328 <button class="dlgButton" id="btnExport" title="Export current setting" onclick="exportFileQuery();">Export</button>
329 <button class="dlgButton" id="btnImport" title="Import current setting" style="display: none" onclick="importFile();">Import</button>
330 <button class="dlgButton" id="btnODB" title="Go to ODB" style="float: right" onclick="goToOdb();">ODB</button>
331 </td>
332 </tr>`;
333
334 html += '</table>';
335
336 div.innerHTML = html;
337
338 mjsonrpc_db_get_value('/Equipment/' + equipment.name).then(
339 rpc => {
340 let eq = rpc.result.data[0];
341 if (eq === null) {
342 dlgAlert('Equipment \"' + equipment.name + '\" does not exist in ODB',
343 () => window.location.href = ".");
344 return;
345 }
346
347 if (eq.variables === null) {
348 dlgAlert('No valid \"/Equipment/' + equipment.name + '/Variables\" in ODB',
349 () => window.location.href = ".");
350 return;
351 }
352
353 let table = document.getElementById(equipment.tid);
354 let columns = 0;
355 let nRows = 0;
356 let gridDisplay = true;
357
358 equipment.frontend_name = eq.common["frontend name"];
359
360 if (document.getElementById('eqSelect'))
361 document.getElementById('eqSelect').innerHTML = equipment.name;
362 if (document.getElementById('tableHeader'))
363 document.getElementById('tableHeader').style.width = "300px";
364
365 // obtain lengths of elements under /Variables
366 let nVar = 0;
367 for (const e in eq.variables) {
368 if (e.includes('/'))
369 continue;
370
371 nVar++;
372 let v = eq.variables[e];
373 let len = v.length ? v.length : 1;
374 if (nRows === 0)
375 nRows = len;
376 if (nRows !== len) {
377 gridDisplay = false;
378 break;
379 }
380 }
381
382 if (nVar < 2)
383 gridDisplay = false;
384
385 // overwrite gridDisplay flag from ODB if present
386 if (eq.settings && eq.settings["grid display"] === undefined) {
387 eq.settings["grid display"] = gridDisplay;
388
389 let p = "/Equipment/" + equipment.name + "/Settings/Grid display";
390 mjsonrpc_db_create([{"path": p, "type": TID_BOOL}]).then(function (rpc) {
391 mjsonrpc_db_paste([p], [gridDisplay]).then(function (rpc) {
392 }).catch(function (error) {
393 mjsonrpc_error_alert(error);
394 });
395 }).catch(function (error) {
396 mjsonrpc_error_alert(error);
397 });
398
399 } else {
400 if (eq.settings)
401 gridDisplay = eq.settings["grid display"];
402 else
403 gridDisplay = false;
404 }
405
406 let cpd;
407 if (eq.settings)
408 cpd = eq.settings["channels per device"];
409 if (cpd && cpd > 0) {
410 // page selector
411 let tr = table.insertRow();
412 let td = tr.insertCell();
413 td.id = "pageSelector";
414 td.style.padding = "0";
415 td.style.borderBottom = "2px solid #808080";
416
417 let pageSel = "<table><tr>";
418 pageSel += "<td class=\"pageSelector\"><a href=\"#\" onclick=\"pageSel('All')\">All</a></td>";
419 eq.devNames = eq.settings["device names"];
420 if (eq.devNames !== undefined && !Array.isArray(eq.devNames))
421 eq.devNames = new Array(eq.devNames);
422
423 if (eq.devNames === undefined) {
424 eq.devNames = [];
425 for (let i = 0; i < nRows / cpd; i++)
426 eq.devNames[i] = String(i);
427 }
428
429 for (let i = 0; i < nRows / cpd; i++) {
430 pageSel += "<td class=\"pageSelector\"><a href=\"#\" onclick=\"pageSel('" + eq.devNames[i] +
431 "')\">" + eq.devNames[i] + "</a></td>";
432 }
433 pageSel += "</tr></table>";
434 td.innerHTML = pageSel;
435 equipment.channelsPerDevice = cpd;
436 }
437
438 // extra display at the top
439 if (eq.settings && eq.settings["display extra"]) {
440 let tr = table.insertRow();
441 let td = tr.insertCell();
442 td.id = "displayExtra";
443 td.style.padding = "0";
444
445 let extraTable = "<table><tr>";
446 let displayExtra = eq.settings["display extra"].split(',').map(item => item.trim());
447 for (const vName of displayExtra) {
448 td.style.height = "22px";
449
450 let subdir = "Variables";
451 if (eq.variables[vName.toLowerCase()] === undefined)
452 subdir = "Settings";
453 let editable = (eq.settings &&
454 eq.settings.editable && eq.settings.editable.includes(vName));
455
456 let bool = false;
457 if (eq.variables[vName.toLowerCase()] === undefined) {
458 if (Array.isArray(eq.settings[vName.toLowerCase()]))
459 bool = (typeof eq.settings[vName.toLowerCase()][0] === 'boolean');
460 else
461 bool = (typeof eq.settings[vName.toLowerCase()] === 'boolean');
462 } else {
463 if (Array.isArray(eq.variables[vName.toLowerCase()]))
464 bool = (typeof eq.variables[vName.toLowerCase()][0] === 'boolean');
465 else
466 bool = (typeof eq.variables[vName.toLowerCase()] === 'boolean');
467 }
468
469 let format;
470 let vDiv;
471 let unit;
472 if (eq.settings)
473 format = eq.settings['format ' + vName.toLowerCase()];
474 if (format === null)
475 format = undefined;
476 if (eq.settings)
477 unit = eq.settings['unit ' + vName.toLowerCase()];
478 if (unit === null)
479 unit = undefined;
480
481 if (editable) {
482 if (bool)
483 vDiv = "<select class=\"modbselect\" " +
484 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
485 "><option value='false'>No</option>" +
486 "<option value='true'>Yes</option>" +
487 "<option value='...'>...</option>" +
488 "onchange='valueChanged(this);' " +
489 "</select>";
490 else if (format)
491 vDiv = "<span class=\"modbvalue\" style=\"border-right: 1px black;\" " +
492 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
493 "data-odb-editable='1' " +
494 "data-size='10' " +
495 "data-format=\"" + format + "\" " +
496 "onchange='valueChanged(this);' " +
497 "></span>";
498 else
499 vDiv = "<span class=\"modbvalue\" " +
500 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
501 "data-odb-editable='1' " +
502 "data-size='10' " +
503 "onchange='valueChanged(this);' " +
504 "></span>";
505
506 } else {
507 if (format)
508 vDiv = "<span class=\"modbvalue\" " +
509 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
510 "data-format=\"" + format + "\" " +
511 "onchange='valueChanged(this);' " +
512 "></span>";
513 else
514 vDiv = "<span class=\"modbvalue\" " +
515 "data-odb-path=\"/Equipment/" + equipment.name + "/" + subdir + "/" + vName + "\" " +
516 "onchange='valueChanged(this);' " +
517 "></span>";
518 }
519
520 if (unit)
521 vDiv += unit;
522
523 extraTable += "<td>";
524 extraTable += vName + ":&nbsp;" + vDiv + "&nbsp;";
525 extraTable += "</td>";
526 }
527
528 extraTable += "</tr></table>";
529 td.innerHTML = extraTable;
530 }
531
532 if (eq.settings && eq.settings.names !== undefined && !Array.isArray(eq.settings.names))
533 eq.settings.names = [eq.settings.names];
534
535 if (eq.settings)
536 equipment.enabled = eq.settings.enabled;
537 else
538 equipment.enabled = true;
539
540 let groupDisplay = false;
541 let groupList = [];
542 if (eq.settings && eq.settings.names !== undefined)
543 groupDisplay = eq.settings.names.every(e => e.includes('%'));
544
545 if (groupDisplay) {
546 let tr = table.insertRow();
547 let td = tr.insertCell();
548 td.id = "groupSelector";
549 let h = "Group:&nbsp;&nbsp;";
550
551 eq.settings.names.forEach(n => {
552 let g = n.substring(0, n.indexOf('%'));
553 if (!groupList.includes(g))
554 groupList.push(g);
555 });
556
557 h += "<select onchange='selectGroup(this)'>";
558 groupList.forEach(g => {
559 h += "<option value=\"" + g + "\">" + g + "</option>";
560 });
561 h += "<option value=\"All\">- All -</option>";
562 h += "</select>";
563 td.innerHTML = h;
564
565 equipment.group = groupList;
566 }
567
568 if (gridDisplay) {
569
570 // assemble display list
571 let display = [];
572
573 if (eq.settings && eq.settings.display) {
574 display = eq.settings.display.split(',').map(item => item.trim());
575 } else {
576 display.push("#");
577 display.push("Names");
578 let v = Object.entries(eq.variables)
579 .filter(([key, value]) => key.includes('/name'))
580 .map(([key, value]) => value);
581 display = display.concat(v);
582 }
583
584 let tr = table.insertRow();
585
586 for (const title of display) {
587 let th = document.createElement('th');
588 th.innerHTML = (title === "Names" ? "Name" : title);
589 th.className = "mtableheader mvarheader";
590
591 let subdir;
592 if (eq.variables[title.toLowerCase()] !== undefined)
593 subdir = "/Variables/";
594 else if (eq.settings && eq.settings[title.toLowerCase()] !== undefined)
595 subdir = "/Settings/";
596
597 if (eq.settings && eq.settings['unit ' + title.toLowerCase()]) {
598 th.colSpan = 2;
599 columns++;
600 }
601
602 tr.appendChild(th);
603 columns++;
604
605 let editable = (eq.settings &&
606 eq.settings.editable &&
607 eq.settings.editable.toLowerCase().includes(title.toLowerCase()));
608
609 // enable load/import if we have editable variables
610 if (editable && bButtons) {
611 document.getElementById('btnLoad').style.display = "inline";
612 document.getElementById('btnImport').style.display = "inline";
613 }
614
615 equipment.vars.push({
616 'name': title,
617 'editable': editable,
618 'subdir': subdir
619 });
620 }
621
622 for (let i = 0; i < nRows; i++) {
623 if (equipment.table.length <= i)
624 equipment.table.push({});
625
626 equipment.table[i].selected = false;
627 equipment.table[i].lastSelected = false;
628
629 // create new row, install listener and set style
630 let tr = table.insertRow();
631
632 tr.id = "spTr";
633 if (cpd && cpd > 0)
634 tr.classList.add("p" + Math.floor(i / cpd));
635
636 tr.addEventListener('mousedown', mouseEvent);
637 tr.addEventListener('mousemove', mouseEvent);
638 tr.addEventListener('mouseout', mouseEvent);
639
640 // catch all mouseup events to stop dragging
641 document.addEventListener('mouseup', mouseEvent);
642
643 tr.style.userSelect = 'none';
644
645 if (groupDisplay) {
646 let group = eq.settings.names[i];
647 group = group.substring(0, group.indexOf('%'));
648 tr.setAttribute('name', group);
649 if (groupList.length > 0 && group !== groupList[0])
650 tr.style.display = "none";
651 }
652
653 equipment.table[i].tr = tr;
654
655 // iterate over all variables in equipment
656 for (let v of equipment.vars) {
657
658 let td = tr.insertCell();
659 td.style.textAlign = "right";
660
661 if (eq.settings && eq.settings.group)
662 if (eq.settings.group[i])
663 td.style.borderTop = "2px solid #808080";
664
665 if (v.name === "#") {
666 td.innerHTML = i.toString();
667 td.style.width = "10px";
668 } else if (v.name === "Names") {
669 td.style.textAlign = "center";
670 if (eq.settings && eq.settings.names) {
671 if (groupDisplay)
672 td.innerHTML = eq.settings.names[i].substring(eq.settings.names[i].indexOf('%') + 1);
673 else
674 td.innerHTML = eq.settings.names[i];
675 }
676 } else {
677 let format = eq.settings && eq.settings['format ' + v.name.toLowerCase()];
678 if (format === undefined || format === null)
679 format = "%f2"; // default format
680 else if (Array.isArray(format))
681 format = format[i];
682
683 if (v.editable) {
684
685 let b = false;
686 if (v.subdir.includes("Variables")) {
687 b = typeof eq.variables[v.name.toLowerCase()] === 'boolean' ||
688 (typeof eq.variables[v.name.toLowerCase()] === 'object' &&
689 typeof eq.variables[v.name.toLowerCase()][0] === 'boolean');
690 } else if (v.subdir.includes("Settings")) {
691 b = typeof eq.settings[v.name.toLowerCase()] === 'boolean' ||
692 (typeof eq.settings[v.name.toLowerCase()] === 'object' &&
693 typeof eq.settings[v.name.toLowerCase()][0] === 'boolean');
694 }
695
696 if (b) {
697 td.innerHTML = "<select class=\"modbselect\" " +
698 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
699 "data-validate='validateData' " +
700 "><option value='false'>No</option>" +
701 "<option value='true'>Yes</option>" +
702 "</select>";
703 } else {
704 td.style.width = "90px";
705 td.style.height = "22px";
706 td.innerHTML = "<div class=\"modbvalue\" " +
707 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
708 "data-odb-editable='1' " +
709 "data-size='10' " +
710 "data-format=\"" + format + "\" " +
711 "data-validate='validateData' " +
712 "onchange='valueChanged(this);' " +
713 "></div>";
714 }
715 } else
716 td.innerHTML = "<div class=\"modbvalue\" " +
717 "data-odb-path=\"/Equipment/" + equipment.name + v.subdir + v.name + "[" + i + "]\" " +
718 "data-format=\"" + format + "\" " +
719 "onchange='valueChanged(this);' " +
720 "></div>";
721
722 // Unit
723 if (eq.settings && eq.settings['unit ' + v.name.toLowerCase()]) {
724
725 let unit = eq.settings['unit ' + v.name.toLowerCase()];
726 if (Array.isArray(unit))
727 unit = unit[i];
728
729 td = tr.insertCell();
730 td.style.width = "10px";
731
732 if (eq.settings && eq.settings.group)
733 if (eq.settings.group[i])
734 td.style.borderTop = "2px solid #808080";
735
736 td.innerHTML = unit;
737 } else
738 td.style.textAlign = 'center';
739
740 }
741 }
742
743 document.getElementById(equipment.tid).style.visibility = '';
744
745 if (cpd && cpd > 0) {
746 enable_row(tr, eq.settings.enabled, Math.floor(i / cpd));
747 } else {
748 if (eq.settings === undefined)
749 enable_row(tr, true, i);
750 else {
751 if (Array.isArray(eq.settings.enabled))
752 enable_row(tr, eq.settings.enabled[i], i);
753 else
754 enable_row(tr, eq.settings.enabled, i);
755 }
756 }
757 }
758
759 if (cpd && cpd > 0)
760 pageSel(eq.devNames[0]);
761
762 } else { // ------------ non-grid display
763
764 let n = 0;
765
766 for (const e in eq.variables) {
767 if (!e.includes('/name'))
768 continue;
769
770 let vName = eq.variables[e];
771 let vArr = eq.variables[vName.toLowerCase()];
772 let editable;
773 if (eq.settings && eq.settings.editable) {
774 if (Array.isArray(eq.settings.editable))
775 editable = eq.settings.editable;
776 else
777 editable = eq.settings.editable.toLowerCase().includes(vName.toLowerCase());
778 }
779
780 // skip subdirectories
781 if (typeof vArr === 'object' && !Array.isArray(vArr))
782 continue;
783
784 let flag = editable;
785 if (Array.isArray(editable))
786 flag = editable.includes(true);
787
788 equipment.vars.push({
789 'name': vName,
790 'editable': flag
791 });
792
793 // enable load/import if we have editable variables
794 if (flag && bButtons) {
795 document.getElementById('btnLoad').style.display = "inline";
796 document.getElementById('btnImport').style.display = "inline";
797 }
798
799 let tr = table.insertRow();
800
801 // Index
802 let th = document.createElement('th');
803 th.className = "mtableheader mvarheader";
804 th.innerHTML = "#";
805 tr.appendChild(th);
806 columns = 1;
807
808 // Name
809 if (eq.settings && eq.settings['names ' + vName.toLowerCase()] !== undefined) {
810 let th = document.createElement('th');
811 th.className = "mtableheader mvarheader";
812 th.innerHTML = "Name";
813 tr.appendChild(th);
814 columns = 2;
815 }
816
817 th = document.createElement('th');
818 th.innerHTML = vName;
819 th.className = "mtableheader mvarheader";
820 th.colSpan = 2;
821 tr.appendChild(th);
822 columns += 2;
823
824 // variables
825 if (!Array.isArray(vArr))
826 vArr = [vArr];
827 for (let i = 0; i < vArr.length; i++) {
828
829 equipment.table.push({});
830
831 equipment.table[n].selected = false;
832 equipment.table[n].lastSelected = false;
833
834 // create new row, install listener and set style
835 let tr = table.insertRow();
836
837 tr.id = "spTr";
838 tr.addEventListener('mousedown', mouseEvent);
839 tr.addEventListener('mousemove', mouseEvent);
840 tr.addEventListener('mouseout', mouseEvent);
841
842 // catch all mouseup events to stop dragging
843 document.addEventListener('mouseup', mouseEvent);
844
845 tr.style.userSelect = 'none';
846
847 if (groupDisplay) {
848 let group = eq.settings.names[i];
849 group = group.substring(0, group.indexOf('%'));
850 tr.setAttribute('name', group);
851 if (groupList.length > 0 && group !== groupList[0])
852 tr.style.display = "none";
853 }
854
855 equipment.table[n].tr = tr;
856
857 // index and name
858 if (eq.settings && eq.settings['names ' + vName.toLowerCase()] !== undefined) {
859 td = tr.insertCell();
860 if (eq.settings && eq.settings.group)
861 if (eq.settings.group[i])
862 td.style.borderTop = "2px solid #808080";
863
864 td.innerHTML = i;
865
866 td = tr.insertCell();
867 if (eq.settings && eq.settings.group)
868 if (eq.settings.group[i])
869 td.style.borderTop = "2px solid #808080";
870
871 if (Array.isArray(eq.settings['names ' + vName.toLowerCase()])) {
872 td.innerHTML = eq.settings['names ' + vName.toLowerCase()][i]
873 } else {
874 td.innerHTML = eq.settings['names ' + vName.toLowerCase()];
875 }
876 } else {
877 td = tr.insertCell();
878
879 if (eq.settings && eq.settings.group)
880 if (eq.settings.group[i])
881 td.style.borderTop = "2px solid #808080";
882
883 td.innerHTML = vName + '[' + i + ']';
884 }
885
886 // units
887 let unit;
888 if (eq.settings && Array.isArray(eq.settings['unit ' + vName.toLowerCase()]))
889 unit = eq.settings['unit ' + vName.toLowerCase()];
890 else if (eq.settings && eq.settings['unit ' + vName.toLowerCase()])
891 unit = new Array(vArr.length).fill(eq.settings['unit ' + vName.toLowerCase()]);
892
893 // value
894 td = tr.insertCell();
895
896 if (eq.settings && eq.settings.group)
897 if (eq.settings.group[i])
898 td.style.borderTop = "2px solid #808080";
899
900 let format;
901 if (eq.settings)
902 format = eq.settings['format ' + vName.toLowerCase()];
903 if (format === undefined || format === null)
904 format = new Array(vArr.length).fill("%f2"); // default format
905
906 let flag = editable;
907 if (Array.isArray(flag))
908 flag = flag[n];
909
910 if (flag) {
911 td.style.width = "90px";
912 td.style.height = "22px";
913 td.style.textAlign = "right";
914 td.innerHTML = "<div class=\"modbvalue\" " +
915 "data-odb-path=\"/Equipment/" + equipment.name + "/Variables/" + vName + "[" + i + "]\" " +
916 "data-odb-editable='1' " +
917 "data-size='10' " +
918 "data-format=\"" + format[i] + "\" " +
919 "onchange='valueChanged(this);' " +
920 "></div>";
921 } else {
922 td.style.textAlign = "right";
923 td.innerHTML = "<div class=\"modbvalue\" " +
924 "data-odb-path=\"/Equipment/" + equipment.name + "/Variables/" + vName + "[" + i + "]\" " +
925 "data-format=\"" + format[i] + "\" " +
926 "onchange='valueChanged(this);' " +
927 "></div>";
928 }
929
930 td = tr.insertCell();
931 td.style.width = "10px";
932
933 if (eq.settings && eq.settings.group)
934 if (eq.settings.group[i])
935 td.style.borderTop = "2px solid #808080";
936
937 if (unit)
938 td.innerHTML = unit[i];
939 else
940 td.innerHTML = " ";
941
942 n++;
943 }
944 }
945
946 document.getElementById(equipment.tid).style.visibility = '';
947 }
948
949 if (document.getElementById('fewarning'))
950 document.getElementById('fewarning').colSpan = columns;
951 if (document.getElementById('tableHeader'))
952 document.getElementById('tableHeader').colSpan = columns;
953 if (document.getElementById('menuButtons'))
954 document.getElementById('menuButtons').colSpan = columns;
955 if (groupDisplay)
956 document.getElementById('groupSelector').colSpan = columns;
957 if (document.getElementById('displayExtra'))
958 document.getElementById('displayExtra').colSpan = columns;
959 if (document.getElementById('pageSelector'))
960 document.getElementById('pageSelector').colSpan = columns;
961
962 // populate equipment table
963 if (bEqSelect) {
964 mjsonrpc_db_ls(["/Equipment"]).then(
965 rpc => {
966 let eqList = rpc.result.data[0];
967 let sel = document.getElementById('eqSelect');
968 for (eq in eqList) {
969 let o = document.createElement('option');
970 o.text = eq;
971 sel.add(o);
972 }
973 sel.value = equipment.name;
974 });
975 }
976
977 let d = document.getElementById(equipment.tid);
978 if (d) {
979 d = d.parentNode.parentNode.parentNode;
980 if (d.tagName === 'DIV')
981 dlgCenter(d);
982 }
983
984 check_frontend();
985
986 document.addEventListener('mousedown', removeSelection);
987 mhttpd_refresh();
988 });
989}
990
991let allowIncDec = false;
992
993function saveFilePicker() {
994 file_picker("equipment", "*.json", saveFile, true, equipment.name, true);
995}
996
997function saveFile(filename) {
998 mjsonrpc_db_copy(['/Equipment/' + equipment.name + '/Variables']).then(rpc => {
999
1000 if (filename.indexOf('.json') === -1)
1001 filename += '.json';
1002
1003 let header = {
1004 "/MIDAS version": "2.1",
1005 "/filename": filename,
1006 "/ODB path": "/Equipment/" + equipment.name + "/Variables"
1007 }
1008 header = JSON.stringify(header, null, ' ');
1009 header = header.substring(0, header.length-2) + ','; // strip trailing '}'
1010
1011 let odbJson = JSON.stringify(rpc.result.data[0], null, ' ');
1012 if (odbJson.indexOf('{') === 0)
1013 odbJson = odbJson.substring(1); // strip leading '{'
1014
1015 odbJson = header + odbJson;
1016
1017 file_save_ascii(filename, odbJson, "Equipment \"" + equipment.name +
1018 "\" saved to file \"" + filename + "\"");
1019
1020 }).catch(error => mjsonrpc_error_alert(error));
1021}
1022
1023function exportFileQuery() {
1024 dlgQuery("Enter filename: ", "default.json", exportFile);
1025}
1026
1027function exportFile(filename) {
1028 if (filename === false)
1029 return;
1030 mjsonrpc_db_copy(['/Equipment/' + equipment.name + '/Variables']).then(rpc => {
1031
1032 if (filename.indexOf('.json') === -1)
1033 filename += '.json';
1034
1035 let header = {
1036 "/MIDAS version": "2.1",
1037 "/filename": filename,
1038 "/ODB path": "/Equipment/" + equipment.name + "/Variables"
1039 }
1040 header = JSON.stringify(header, null, ' ');
1041 header = header.substring(0, header.length-2) + ','; // strip trailing '}'
1042
1043 let odbJson = JSON.stringify(rpc.result.data[0], null, ' ');
1044 if (odbJson.indexOf('{') === 0)
1045 odbJson = odbJson.substring(1); // strip leading '{'
1046
1047 odbJson = header + odbJson;
1048
1049 // use trick from FileSaver.js
1050 let a = document.getElementById('downloadHook');
1051 if (a === null) {
1052 a = document.createElement("a");
1053 a.style.display = "none";
1054 a.id = "downloadHook";
1055 document.body.appendChild(a);
1056 }
1057
1058 let blob = new Blob([odbJson], {type: "text/json"});
1059 let url = window.URL.createObjectURL(blob);
1060
1061 a.href = url;
1062 a.download = filename;
1063 a.click();
1064 window.URL.revokeObjectURL(url);
1065 dlgAlert("Equipment \"" + equipment.name +
1066 "\" downloaded to file \"" + filename + "\"");
1067
1068 }).catch(error => mjsonrpc_error_alert(error));
1069}
1070
1071function loadFilePicker() {
1072 file_picker("equipment", "*.json", loadFile);
1073}
1074
1075function loadFile(filename) {
1076 file_load_ascii(filename, (text) => fileInterprete(filename, text) );
1077}
1078
1079async function importFile() {
1080 // Chrome has file picker, others might have not
1081 try {
1082 let fileHandle;
1083 [fileHandle] = await window.showOpenFilePicker();
1084 const file = await fileHandle.getFile();
1085 const text = await file.text();
1086 fileInterprete(file.name, text);
1087 } catch (error) {
1088 if (error.name !== 'AbortError') {
1089 // fall-back to old method
1090 if (document.getElementById('dlgFileSelect') === null) {
1091 let fsDiv = document.createElement('div');
1092 fsDiv.id = "dlgFileSelect";
1093 fsDiv.className = "dlgFrame";
1094 fsDiv.innerHTML = dlgFileSelect;
1095 document.body.appendChild(fsDiv);
1096 }
1097 dlgShow('dlgFileSelect', true);
1098 }
1099 }
1100}
1101
1102function importFileFromSelector() {
1103
1104 let input = document.getElementById('fileSelector');
1105 let file = input.files[0];
1106 if (file !== undefined) {
1107 let reader = new FileReader();
1108 reader.readAsText(file);
1109
1110 reader.onerror = function () {
1111 dlgAlert('File read error: ' + reader.error);
1112 }
1113
1114 reader.onload = function () {
1115 fileInterprete(file.name, reader.result);
1116 }
1117 }
1118}
1119
1120function fileInterprete(filename, text) {
1121 try {
1122 let j = JSON.parse(text);
1123
1124 // check for correct equipment
1125 let path = j["/ODB path"].split('/');
1126 if (path[2] !== equipment.name) {
1127 dlgAlert("File contains settings for equipment \"" + path[2] + "\"<br />"+
1128 "and therefore cannot be loaded into equipment \"" + equipment.name +"\"");
1129 return;
1130 }
1131
1132 mhttpd_refresh_pause(true);
1133
1134 for (let v of equipment.vars)
1135 if (v.editable)
1136 modbset("/Equipment/" + equipment.name + "/Variables/" + v.name, j[v.name]);
1137
1138 mhttpd_refresh_pause(false);
1139
1140 window.setTimeout(mhttpd_refresh, 10);
1141
1142 mjsonrpc_cm_msg("Values loaded from file \"" + filename + "\"");
1143 } catch (error) {
1144 dlgAlert("File \"" + filename + "\" is not a valid JSON file:<br /><br />" + error);
1145 }
1146}
1147
1148function renderSelection() {
1149 // change color according to selection
1150 for (let i=0 ; i<equipment.table.length ; i++) {
1151 if (equipment.table[i].selected) {
1152 equipment.table[i].tr.style.backgroundColor = '#004CBD';
1153 equipment.table[i].tr.style.color = '#FFFFFF';
1154
1155 // change <a> color
1156 if (equipment.table[i].tr.getElementsByTagName('a').length > 0)
1157 equipment.table[i].tr.getElementsByTagName('a')[0].style.color = '#FFFFFF';
1158 } else {
1159
1160 let index = i;
1161
1162 if (equipment.channelsPerDevice)
1163 index = Math.floor(i / equipment.channelsPerDevice);
1164 enable_row(equipment.table[i].tr, equipment.enabled, index);
1165
1166 equipment.table[i].tr.style.color = ''; // text is black again
1167
1168 // change <a> color
1169 let a = equipment.table[i].tr.getElementsByTagName('a');
1170 if (a.length > 0) {
1171 a = a[0];
1172 a.style.color = '';
1173 }
1174 }
1175 }
1176}
1177
1178let mouseDragged = false;
1179
1180// function gets called from document
1181function removeSelection(e) {
1182 // don't de-select if we click on "->" or on drop-down box
1183 if (e.target.tagName === 'BUTTON' || e.target.tagName === 'SELECT')
1184 return;
1185
1186 for (let i = 0; i < equipment.table.length; i++) {
1187 equipment.table[i].selected = false;
1188 equipment.table[i].lastSelected = false;
1189 }
1190 renderSelection();
1191}
1192
1193function getSelectedElements() {
1194 let sel = [];
1195 for (let i = 0; i < equipment.table.length; i++)
1196 if (equipment.table[i].selected)
1197 sel.push(i);
1198
1199 return sel;
1200}
1201
1202function mouseEvent(e) {
1203
1204 if (e.type === 'mouseup')
1205 mouseDragged = false;
1206
1207 // keep off secondary mouse buttons
1208 if (e.button !== 0)
1209 return;
1210
1211 // keep off drop down boxes
1212 if (e.target.tagName === 'SELECT')
1213 return;
1214
1215 e.preventDefault();
1216 e.stopPropagation();
1217
1218 // find current row
1219 let index;
1220 for (index = 0; index < equipment.table.length; index++)
1221 if (equipment.table[index].tr === e.target ||
1222 equipment.table[index].tr.contains(e.target))
1223 break;
1224 if (index === equipment.table.length)
1225 index = undefined;
1226
1227 // ignore clicks to <button>'a and <a>'s
1228 if (e.target.tagName === 'A' || e.target.tagName === 'BUTTON' || e.target.tagName === 'IMG')
1229 return;
1230
1231 if (e.type === 'mousedown') {
1232 mouseDragged = true;
1233
1234 if (e.shiftKey) {
1235
1236 // search last selected element
1237 let i1;
1238 for (i1 = 0 ; i1 < equipment.table.length ; i1++)
1239 if (equipment.table[i1].lastSelected)
1240 break;
1241 if (i1 === equipment.table.length)
1242 i1 = 0; // none selected, so use first one
1243
1244 if (i1 > index)
1245 [i1, index] = [index, i1];
1246
1247 for (let i=i1 ; i<= index ; i++)
1248 if (!equipment.table[i].tr.disabled)
1249 equipment.table[i].selected = true;
1250
1251 } else if (e.metaKey || e.ctrlKey) {
1252
1253 console.log("index = " + index);
1254
1255 // just toggle current selection
1256 if (!equipment.table[index].tr.disabled)
1257 equipment.table[index].selected = !equipment.table[index].selected;
1258
1259 // remember which row was last selected
1260 for (let i=0 ; i<equipment.table.length ; i++)
1261 equipment.table[i].lastSelected = false;
1262 if (equipment.table[index].selected)
1263 equipment.table[index].lastSelected = true;
1264
1265 } else {
1266 // no key pressed -> un-select all but current
1267 for (let i = 0; i < equipment.table.length; i++) {
1268 equipment.table[i].selected = false;
1269 equipment.table[i].lastSelected = false;
1270 }
1271
1272 if (!equipment.table[index].tr.disabled) {
1273 equipment.table[index].selected = true;
1274 equipment.table[index].lastSelected = true;
1275 }
1276 }
1277
1278 renderSelection();
1279 }
1280
1281 if (e.type === 'mousemove' && mouseDragged) {
1282
1283 // don't do dragging if shift or ctrl key pressed
1284 if (!e.shiftKey && !e.metaKey && !e.ctrlKey) {
1285 // unselect all
1286 for (let i = 0; i < equipment.table.length; i++)
1287 equipment.table[i].selected = false;
1288
1289 // current row
1290 let i2;
1291 for (i2 = 0; i2 < equipment.table.length; i2++)
1292 if (equipment.table[i2].tr === e.target ||
1293 equipment.table[i2].tr.contains(e.target))
1294 break;
1295
1296 if (i2 < equipment.table.length) {
1297 // search last selected element
1298 let i1;
1299 for (i1 = 0; i1 < equipment.table.length; i1++)
1300 if (equipment.table[i1].lastSelected)
1301 break;
1302 if (i1 === equipment.table.length)
1303 i1 = 0; // none selected, so use first one
1304
1305 if (i1 > i2)
1306 [i1, i2] = [i2, i1];
1307
1308 for (let i = i1; i <= i2; i++)
1309 if (!equipment.table[i].tr.disabled)
1310 equipment.table[i].selected = true;
1311
1312 renderSelection();
1313 }
1314 }
1315 }
1316}
1317
1318function dlgEquipment(eqName, bButtons = false, bEqSelect = false) {
1319
1320 // check if equipment exists in ODB
1321 mjsonrpc_db_ls(['/Equipment/' + eqName]).then(
1322 rpc => {
1323 let eq = rpc.result.data[0];
1324 if (eq === null) {
1325 dlgAlert('dlgEquipment: Equipment \"' + eqName + '\" does not exist in ODB');
1326 return;
1327 }
1328
1329 if (eq.variables === null) {
1330 dlgAlert('dlgEquipment: No valid \"/Equipment/' + eqName + '/Variables\" in ODB');
1331 return;
1332 }
1333
1334 let d = document.getElementById('dlgEqTitle');
1335
1336 // If dialog is already there, reuse it.
1337 if (!d) {
1338 d = document.createElement("div");
1339 d.className = "dlgFrame";
1340 d.style.zIndex = "30";
1341 d.shouldDestroy = true;
1342
1343 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgEqTitle\">" + eqName + "</div>" +
1344 "<div class=\"dlgPanel\" style=\"padding: 4px;\">" + "<div id=\"dlgEquipment\">" + "</div></div>";
1345 document.body.appendChild(d);
1346 dlgShow(d);
1347 }
1348
1349 eqtable_init(document.getElementById('dlgEquipment'), eqName, bButtons, bEqSelect);
1350
1351 if (bEqSelect) {
1352 let eqSelect = document.getElementById('eqSelect');
1353 eqSelect.onchange = (event) => {
1354 redirectEq(document.getElementById('dlgEquipment'), event.target.value);
1355 };
1356 }
1357 });
1358
1359}
1360
1361