fix js syntax
[web/firmware-selector-openwrt-org.git] / index.js
1
2 function loadFile(url, callback) {
3 var xmlhttp = new XMLHttpRequest();
4 xmlhttp.onreadystatechange = function() {
5 if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
6 callback(xmlhttp.responseText, url);
7 }
8 };
9 xmlhttp.open('GET', url, true);
10 xmlhttp.send();
11 }
12
13 function setupSelectList(select, items, onselection) {
14 for (var i = 0; i < items.length; i += 1) {
15 var option = document.createElement("OPTION");
16 option.innerHTML = items[i];
17 select.appendChild(option);
18 }
19
20 select.addEventListener("change", function(e) {
21 onselection(items[select.selectedIndex]);
22 });
23
24 if (select.selectedIndex >= 0) {
25 onselection(items[select.selectedIndex]);
26 }
27 }
28
29 // Change the translation of the entire document
30 function changeLanguage(language) {
31 var mapping = translations[language];
32 if (mapping) {
33 for (var tr in mapping) {
34 Array.from(document.getElementsByClassName(tr))
35 .forEach(function(e) { e.innerText = mapping[tr]; })
36 }
37 }
38 }
39
40 function setupAutocompleteList(input, items, onselection) {
41 // the setupAutocompleteList function takes two arguments,
42 // the text field element and an array of possible autocompleted values:
43 var currentFocus = -1;
44
45 // sort numbers and other characters separately
46 var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
47
48 items.sort(collator.compare);
49
50 // execute a function when someone writes in the text field:
51 input.oninput = function(e) {
52 // clear images
53 updateImages();
54
55 var value = this.value;
56 // close any already open lists of autocompleted values
57 closeAllLists();
58
59 if (!value) {
60 return false;
61 }
62
63 // create a DIV element that will contain the items (values):
64 var list = document.createElement("DIV");
65 list.setAttribute("id", this.id + "-autocomplete-list");
66 list.setAttribute("class", "autocomplete-items");
67 // append the DIV element as a child of the autocomplete container:
68 this.parentNode.appendChild(list);
69
70 // for each item in the array...
71 var c = 0;
72 for (var i = 0; i < items.length; i += 1) {
73 var item = items[i];
74
75 // match
76 var j = item.toUpperCase().indexOf(value.toUpperCase());
77 if (j < 0) {
78 continue;
79 }
80
81 c += 1;
82 if (c >= 15) {
83 var div = document.createElement("DIV");
84 div.innerHTML = "...";
85 list.appendChild(div);
86 break;
87 } else {
88 var div = document.createElement("DIV");
89 // make the matching letters bold:
90 div.innerHTML = item.substr(0, j)
91 + "<strong>" + item.substr(j, value.length) + "</strong>"
92 + item.substr(j + value.length)
93 + "<input type='hidden' value='" + item + "'>";
94
95 div.addEventListener("click", function(e) {
96 // set text field to selected value
97 input.value = this.getElementsByTagName("input")[0].value;
98 // close the list of autocompleted values,
99 // (or any other open lists of autocompleted values:
100 closeAllLists();
101 // callback
102 onselection(input.value);
103 });
104
105 list.appendChild(div);
106 }
107 }
108 };
109
110 input.onkeydown = function(e) {
111 var x = document.getElementById(this.id + "-autocomplete-list");
112 if (x) x = x.getElementsByTagName("div");
113 if (e.keyCode == 40) {
114 // key down
115 currentFocus += 1;
116 // and and make the current item more visible:
117 setActive(x);
118 } else if (e.keyCode == 38) {
119 // key up
120 currentFocus -= 1;
121 // and and make the current item more visible:
122 setActive(x);
123 } else if (e.keyCode == 13) {
124 // If the ENTER key is pressed, prevent the form from being submitted,
125 e.preventDefault();
126 if (currentFocus > -1) {
127 // and simulate a click on the "active" item:
128 if (x) x[currentFocus].click();
129 }
130 }
131 };
132
133 input.onfocus = function() {
134 onselection(input.value);
135 }
136
137 function setActive(x) {
138 // a function to classify an item as "active":
139 if (!x) return false;
140 // start by removing the "active" class on all items:
141 for (var i = 0; i < x.length; i++) {
142 x[i].classList.remove("autocomplete-active");
143 }
144 if (currentFocus >= x.length) currentFocus = 0;
145 if (currentFocus < 0) currentFocus = (x.length - 1);
146 // add class "autocomplete-active":
147 x[currentFocus].classList.add("autocomplete-active");
148 }
149
150 function closeAllLists(elmnt) {
151 // close all autocomplete lists in the document,
152 // except the one passed as an argument:
153 var x = document.getElementsByClassName("autocomplete-items");
154 for (var i = 0; i < x.length; i++) {
155 if (elmnt != x[i] && elmnt != input) {
156 x[i].parentNode.removeChild(x[i]);
157 }
158 }
159 }
160
161 // execute a function when someone clicks in the document:
162 document.addEventListener("click", function (e) {
163 closeAllLists(e.target);
164 });
165 }
166
167 function $(id) {
168 return document.getElementById(id);
169 }
170
171 function findCommonPrefix(files){
172 var A = files.concat().sort();
173 var first = A[0];
174 var last = A[A.length - 1];
175 var L = first.length;
176 var i = 0;
177 while (i < L && first.charAt(i) === last.charAt(i)) {
178 i += 1;
179 }
180 return first.substring(0, i);
181 }
182
183 function updateImages(release, commit, model, image_link, mobj) {
184 // add download button for image
185 function addLink(label, tags, file, help_id) {
186 var a = document.createElement('A');
187 a.classList.add('download-link');
188 a.href = image_link
189 .replace('%target', mobj[0])
190 .replace('%release', release)
191 .replace('%file', file);
192 var span = document.createElement('SPAN');
193 span.appendChild(document.createTextNode(''));
194 a.appendChild(span);
195
196 // add sub label
197 if (tags.length > 0) {
198 a.appendChild(document.createTextNode(label + ' (' + tags.join(', ') + ')'));
199 } else {
200 a.appendChild(document.createTextNode(label));
201 }
202
203 if (config.showHelp) {
204 a.onmouseover = function() {
205 // hide all help texts
206 Array.from(document.getElementsByClassName('download-help'))
207 .forEach(function(e) { e.style.display = 'none'; });
208 $(help_id).style.display = 'block';
209 };
210 }
211
212 $('download-links').appendChild(a);
213 }
214
215 // remove all download links
216 Array.from(document.getElementsByClassName('download-link'))
217 .forEach(function(e) { e.remove(); });
218
219 // hide all help texts
220 Array.from(document.getElementsByClassName('download-help'))
221 .forEach(function(e) { e.style.display = 'none'; });
222
223 if (release && commit && model && image_link && mobj) {
224 var target = mobj[0];
225 var files = mobj[1];
226
227 // fill out build info
228 $('image-model').innerText = model;
229 $('image-target').innerText = target;
230 $('image-release').innerText = release;
231 $('image-commit').innerText = commit;
232
233 var prefix = findCommonPrefix(files);
234 var entries = {
235 'FACTORY': [],
236 'SYSUPGRADE': [],
237 'KERNEL': [],
238 'ROOTFS': [],
239 'SDCARD': [],
240 'TFTP': [],
241 'OTHER': []
242 };
243
244 files.sort();
245
246 for (var i in files) {
247 var file = files[i];
248 if (file.includes('-factory')) {
249 entries['FACTORY'].push(file);
250 } else if (file.includes('-sysupgrade')) {
251 entries['SYSUPGRADE'].push(file);
252 } else if (file.includes('-kernel') || file.includes('-zImage') || file.includes('-uImage')) {
253 entries['KERNEL'].push(file);
254 } else if (file.includes('-rootfs')) {
255 entries['ROOTFS'].push(file);
256 } else if (file.includes('-sdcard')) {
257 entries['SDCARD'].push(file);
258 } else if (file.includes('-tftp')) {
259 entries['TFTP'].push(file);
260 } else {
261 entries['OTHER'].push(file);
262 }
263 }
264
265 function extractTags(prefix, file) {
266 var all = file.substring(prefix.length).split('.')[0].split('-');
267 var ignore = ['', 'kernel', 'zImage', 'uImage', 'factory', 'sysupgrade', 'rootfs', 'sdcard'];
268 return all.filter(function (el) { return !ignore.includes(el); });
269 }
270
271 for (var category in entries) {
272 var files = entries[category];
273 for (var i in files) {
274 var file = files[i];
275 var tags = (files.length > 1) ? extractTags(prefix, file) : [];
276 addLink(category, tags, file, category.toLowerCase() + '-help');
277 }
278 }
279
280 $('images').style.display = 'block';
281 } else {
282 $('images').style.display = 'none';
283 }
284 }
285
286 // hide fields
287 updateImages();
288 changeLanguage(config.language);
289
290 loadFile(config.data, function(data) {
291 var obj = JSON.parse(data);
292 setupSelectList($("releases"), Object.keys(obj), function(release) {
293 setupAutocompleteList($("models"), Object.keys(obj[release]['models']), function(model) {
294 if (model in obj[release]['models']) {
295 var link = obj[release].link;
296 var commit = obj[release].commit;
297 var mobj = obj[release]['models'][model];
298 updateImages(release, commit, model, link, mobj);
299 } else {
300 updateImages();
301 }
302 });
303
304 // trigger model update when selected release changes
305 $("models").onfocus();
306 });
307 })