Корректная работа на мобильных устройствах
[esp-clock.git] / data / web / script.js
1 var pages
2 var parameters = {}
3 var daynames = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] 
4
5 function toggleMenu(e) {
6   active = (document.getElementById('menuLink').className.indexOf('active') !== -1)
7   if (active || e.target.id == 'menuLink' || e.target.id == 'menuBtn') {
8     elements = [ document.getElementById('layout'), document.getElementById('menu'), document.getElementById('menuLink') ]
9     for (const element of elements) {
10       if (!active) {
11         element.classList.add('active')
12       } else {
13         element.classList.remove('active')
14       }
15     }
16     if (e.target.id == 'menuLink' || e.target.id == 'menuBtn') {
17       e.preventDefault()
18     }
19     e.stopPropagation()
20   }
21 }
22
23 document.getElementById('layout').addEventListener('click', toggleMenu)
24
25 function encode(r){
26   r = String(r)
27   return r.replace(/[\x26\x0A\<>'"]/g,function(r){return"&#"+r.charCodeAt(0)+";"})
28 }
29
30 function getAnchor() {
31     return window.location.hash;
32 }
33
34 function DrawHeader(project) {
35   var menu_header = document.getElementById('_ui_menu_header')
36   menu_header.innerHTML = project.name
37   document.title = project.name + '/' + project.version
38 }
39
40 function ParseJsonQ(url, callback) {
41   var req = new XMLHttpRequest();
42
43   req.onreadystatechange = function () {
44     if (this.readyState != 4) return; 
45     if (this.status != 200 && this.status != 500 && this.status != 404) {
46       setTimeout(ParseJsonQ(url, callback),30000);
47       return;
48     }
49     var json = JSON.parse(this.responseText)
50     callback(json);
51   };
52
53   req.open("GET", url, true);
54   req.send()
55
56 }
57
58 function UpdateElement(id, value) {
59   var element = document.getElementById("_ui_element_"+id)
60   if (!element) return;
61   var ui_class = element.dataset.ui_class;
62   switch (ui_class) {
63     case "table":
64       element.innerText = value
65       break
66     case "input":
67     case "password":
68     case "select":
69     case "number":
70     case "range":
71       element.value = value  
72       break
73     case "checkbox":
74       element.checked = value
75       break
76     case "week":
77       for (i=0; i<7; i++) {
78         var subname = '_ui_elpart_'+i+'_'+id
79         var subelement = document.getElementById(subname) 
80         if (value[i]==1) { 
81           subelement.className = "weekday-selected"
82         } else {
83           subelement.className = "weekday"
84         }
85       }
86       break
87   }  
88 }
89
90 function UpdateValues(json) {
91   for (var key in json) {
92     var obj = document.getElementById("_ui_element_"+key)
93     if (obj) {
94       UpdateElement(key, json[key])
95     }
96     parameters[key] = json[key]
97   }
98   var notification = document.getElementById('_ui_notification');
99   if (parameters['_changed']) {
100     notification.innerHTML = '<input style="margin:1em .5em 0;" type="button" id="save" value="Сохранить внесенные изменения" class="pure-button pure-button-primary" onclick="sendAction(\'save\')">'
101     notification.removeAttribute('hidden')
102   } else {
103     notification.innerHTML = ''
104     notification.hidden = true
105   }
106 }
107
108 function sendUpdate(id) {
109   var input = document.getElementById('_ui_element_'+id)
110   var ui_class = input.dataset.ui_class;
111   switch (ui_class) {
112     case 'input':
113     case 'password':
114     case 'number':
115     case 'range':
116       if (input.checkValidity() && input.value != parameters[id]) {
117         ParseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.value), function(json) {
118           UpdateValues(json)
119         })
120       }
121       break
122     case 'select':
123       ParseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.selectedOptions[0].value), function(json) {
124         UpdateValues(json)
125       })
126       break;
127     case 'checkbox':
128       ParseJsonQ('/config/set?name=' + id + '&value=' + (input.checked?'true':'false'), function(json) {
129         UpdateValues(json)
130       })      
131       break;
132     case 'week':        
133       ParseJsonQ('/config/set?name=' + id + '&value=' + input.dataset.value, function(json) {
134         UpdateValues(json)
135       })      
136       break;
137   }
138 }
139
140 function sendAction(name) {
141   ParseJsonQ('/action?name=' + name, function(json) {
142     if (json.result == 'FAILED') {
143       alert(json.message)
144       if (json.page) {
145         DrawPage(json.page)
146       }
147     } else {
148       location.reload()
149     }
150   })
151 }
152
153 function ShowPwd(id) {
154   var x = document.getElementById('_ui_element_' + id)
155   if (x.type === "password") {
156     x.type = "text";
157   } else {
158     x.type = "password";
159   }
160 }
161
162 function OpenSelect(id) {
163   var selector = document.getElementById('_ui_elemmodal_'+id);
164   selector.removeAttribute("hidden")
165 }
166
167 function CloseSelect(id) {
168   var selector = document.getElementById('_ui_elemmodal_'+id);
169   selector.hidden = true
170 }
171
172 function SelectWiFi(id, ssid) {
173   CloseSelect(id);
174   var x = document.getElementById('_ui_element_' + id)
175   UpdateElement(x,ssid);
176   sendUpdate(id)
177 }
178
179 function getWiFi(id) {
180   var list = document.getElementById('_ui_elemselect_'+id)
181
182   var req = new XMLHttpRequest();
183
184   req.onreadystatechange = function () {
185     if (this.readyState != 4) return; 
186     if (this.status != 200 && this.status != 500 && this.status != 404) {
187       setTimeout(getWiFi(id),30000);
188       return;
189     }
190     var json = JSON.parse(this.responseText)
191     var table = '<table cellpadding="5" border="0" align="center"><thead class="table-header"><tr><td style="padding: 1rem">SSID</td><td style="padding: 1rem">BSSID</td><td style="padding: 1rem">RSSI</td><td style="padding: 1rem">Канал</td><td style="padding: 1rem">Защита</td></tr></thead></tbody style="border-bottom: lightgrey 1px solid">'
192     if (!json.length) {
193       setTimeout(getWiFi(id),5000);
194     }
195     for (idx in json) {
196       var encryption = json[idx].secure == 2? "TKIP" : json[idx].secure == 5? "WEP" : json[idx].secure == 4? "CCMP" : json[idx].secure == 7? "нет" : json[idx].secure == 8? "Автоматически" : "Не определено";
197       table += '<tr onclick="SelectWiFi(\''+id+'\',\''+json[idx].ssid+'\')"><td style="padding: 1rem">'+json[idx].ssid+'</td><td style="padding: 1rem">'+json[idx].bssid+'</td><td style="padding: 1rem">'+json[idx].rssi+'</td><td style="padding: 1rem">'+json[idx].channel+'</td><td style="padding: 1rem">'+encryption+'</td></tr>'
198     }
199     
200     table += '</tbody></table>'
201     list.innerHTML = table;
202   };
203
204   req.open("GET", "/wifi/scan", true);
205   req.send()
206
207 }
208
209 function ClickDay(id, day) {
210   value = parameters[id].split('')
211   day_value = value[day]
212   day_value = (day_value=='0')?'1':'0'
213   value[day] = day_value
214   element = document.getElementById('_ui_element_' + id)
215   value = value.join('')
216   element.dataset.value = value;
217   sendUpdate(id);
218 }
219
220 function ElementHTML(element) {
221   var value
222   if (parameters[element.id] || !isNaN(parameters[element.id])) {
223     value = parameters[element.id]
224   } else if (element.value) {
225     value = element.value
226   } else {
227     value = ""
228   }
229   switch (element.type) {
230     case 'hr':
231       return '<div class="pure-u-1 pure-u-md-1-3"><hr></div>'
232     case 'button':
233       return '<div class="pure-u-1 pure-u-md-1-3"><div align="center"><input style="margin:1em .5em 0;" type="button" id="' 
234         + element.id + '" value="' + encode(element.label) + '" class="pure-button pure-button-primary" onclick="sendAction(\'' + element.id + '\')" /></div></div>'
235     case 'password':
236       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
237         + '<input data-ui_class="password" type="password" id="_ui_element_' + element.id + '" value="' + encode(value) 
238         + '" class="pure-u-1" style="display:inline-block" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" />'
239         + '<div class="hint" onclick="ShowPwd(\'' + element.id + '\')">&#x1F441;</div></div>'
240     case 'input':
241       var pattern = ""
242       if (element.pattern) {
243         pattern = ' pattern="' + encode(element.pattern) + '"'
244       }
245       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
246         + '<input data-ui_class="input" type="text" id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
247         + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" /></div>'
248     case 'input-wifi':
249       var pattern = ""
250       if (element.pattern) {
251         pattern = ' pattern="' + encode(element.pattern) + '"'
252       }
253       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
254         + '<input data-ui_class="input" type="text" id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
255         + ' class="pure-u-1" style="display:inline-block" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" />' 
256         + '<div class="hint" onclick="OpenSelect(\'' + element.id+ '\'); getWiFi(\'' + element.id + '\')">&#128246;</div>'
257         + '<div class="modal" id="_ui_elemmodal_' + element.id + '" hidden>' 
258         + '<div class="modal-content">'
259         + '<div id="_ui_elemselect_' + element.id + '"></div>'
260         + '<div class="pure-u-1 pure-u-md-1-3"><div align="center"><input style="margin:1em .5em 0;" type="button" id="_ui_button_' 
261         + element.id + '" value="Закрыть" class="pure-button pure-button-primary" onclick="CloseSelect(\'' + element.id + '\')"></div>'
262         + '</div></div></div>'
263     case 'checkbox':
264       return '<div class="pure-u-1 pure-u-md-1-3"><label class="switch socket" for="_ui_element_' + element.id + '">'
265         + '<input class="switch" data-ui_class="checkbox" type="checkbox" id="_ui_element_' + element.id + '"' + (parameters[element.id]?' checked':'') + ' onchange="sendUpdate(\'' + element.id + '\')" />'
266         + '<span class="switch slider">'+ encode(element.label) + '</span>'
267         + '</label></div>'
268     case 'select':
269       var options = '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
270         + '<select class="pure-u-24-24" data-ui_class="select" id="_ui_element_' + element.id + '" onchange="sendUpdate(\'' + element.id + '\')">'
271       for (const option of element.options) {
272         var list_option = '<option value="' + encode(option.value) + '" ';
273         if (option.value == parameters[element.id]) {
274           list_option += 'selected '
275         }
276         list_option += '>' + encode(option.text) + '</option>'
277         options += list_option
278       }
279       options += '</select></div>'
280       return options
281     case 'week':
282       days = '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>' 
283         + '<table data-ui_class="week" data-value="' + value + '" id="_ui_element_' + element.id + '" cellpadding="5" border="0" class="week"><tbody><tr>'
284       for (i=0; i<7; i++) {
285         a_enabled = (value[i] == "1")
286         days = days + '<td><div class="weekday' + (a_enabled?"-selected":"") + '" id="_ui_elpart_'+i+'_'+element.id+'" onclick="ClickDay(\'' + element.id + '\', ' + i + ')">'+ daynames[i] + '</div></td>'
287       }
288       days += '</tr></tbody></table></div>'
289       return days
290     case 'text':
291       return '<div class="pure-u-1 pure-u-md-1-3"><h2 id="_ui_element_'+ element.id +'" ' + (element.color?'style="color:'+ element.color+'" ':'')+ '>' + encode(value) + '</h2></div>'
292     case 'number':
293       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
294         + '<input data-ui_class="number" type="number" '+ (!isNaN(element.min)?'min="'+element.min+'" ':'') + (!isNaN(element.max)?'max="'+element.max+'" ':'') + (!isNaN(element.step)?'step="'+element.step+'" ':'')
295         + 'id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
296         + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" /></div>'
297     case 'range':
298       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
299         + '<input data-ui_class="range" type="range" '+ (!isNaN(element.min)?'min="'+element.min+'" ':'') + (!isNaN(element.max)?'max="'+element.max+'" ':'') + (!isNaN(element.step)?'step="'+element.step+'" ':'')
300         + 'id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
301         + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" /></div>'
302     case 'table':
303     default:
304       return '<div class="pure-u-1 pure-u-md-1-3"><table cellpadding="5" border="0" align="center"><tbody><tr><td style="padding-right: 10px; width: 50%;" align="right"><pre>' 
305         + encode(element.label)+ '</pre></td><td style="padding-left: 10px;" id="cT"><pre id="_ui_element_' 
306         + element.id + '" data-ui_class="table" ' + (element.color?'style="color:'+ element.color+'" ':'')+ '>' + encode(value) + '</pre></td></tr></tbody></table></div>'
307   }
308 }
309
310 function DrawPage(id) {
311   var idx =0, i=0
312   for (const page of pages) {
313     var menu_link = document.getElementById('_ui_pglink_' + page.id)  
314     if (page.id != id) {
315       menu_link.classList.remove("pure-menu-selected")
316     } else {
317       menu_link.classList.add("pure-menu-selected")
318       idx = i
319     }
320     i++
321   }
322   var page_header = document.getElementById("_ui_page_header")
323   page_header.innerHTML = pages[idx].title
324   var page_content = document.getElementById("_ui_page_content");
325   var content = ''
326   for (const element of pages[idx].elements) {
327     content = content + ElementHTML(element) + '\n'
328   }
329   page_content.innerHTML = content
330   window.location.hash = id
331 }
332
333 function DrawNavigator(project, pages) {
334   var menu = document.getElementById('_ui_menu_list');
335   var list = ''
336   for (const page of pages) {
337     list = list + '<li id="_ui_pglink_' + page.id 
338       + '" class="pure-menu-item"><a class="pure-menu-link" onclick="DrawPage(\''+ page.id +'\')" href="#' + page.id + '">'
339       + page.title+'</a></li>'
340   }
341   menu.innerHTML = list
342 }
343
344 function DrawContacts(contacts) {
345   if (!contacts) return;
346   var contact_list = '<hr><h4 class="pure-u1">Контакты</h4>'
347   for (const contact of contacts) {
348     const url = new URL(contact)
349     var ref
350     console.log(url)
351     switch (url.protocol) {
352       case 'http':
353       case 'https:':
354         ref = '&#8962; '+url.hostname
355         break
356       case 'mailto:':
357         ref = '&#9993; '+url.pathname
358         break
359       default:
360         ref = '&#128389; '+url.pathname
361     }
362     contact_list += '<a href="'+contact+'">'+ref+'</a>'
363   }
364   var footer = document.getElementById('_ui_contacts');
365   footer.innerHTML = contact_list
366 }
367
368 function DrawUI(ui) {
369   DrawHeader(ui.project)
370   pages = ui.pages
371   DrawNavigator(ui.project, pages)
372   DrawContacts(ui.project.contacts)
373   var anchor = getAnchor()
374   if (anchor) {
375     DrawPage(anchor)
376   } else {
377     DrawPage(pages[0].id)
378   }
379 }
380
381 function GetUI() {
382   ParseJsonQ("/ui", DrawUI);
383 }
384
385 GetUI()
386
387 function initES() {
388   if (!!window.EventSource) {
389     var source = new EventSource('/events');
390
391     source.onerror = function(e) {
392       if (source.readyState == 2) {
393         setTimeout(initES, 5000);
394       }
395     };
396         
397     source.addEventListener('keepalive', function(e) {
398       UpdateValues(JSON.parse(e.data));
399     }, false);
400
401     source.addEventListener('update', function(e) {
402       UpdateValues(JSON.parse(e.data));
403     }, false);
404   }
405 }
406
407 initES();
408
409 function DrawConfig(cfg) {
410   UpdateValues(cfg)
411 }
412
413 function GetCfg() {
414   ParseJsonQ("/config/get", DrawConfig);
415 }
416
417 GetCfg()