fix help text display
[web/firmware-selector-openwrt-org.git] / www / 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 if (!value) { return false; }
59
60 // create a DIV element that will contain the items (values):
61 var list = document.createElement("DIV");
62 list.setAttribute("id", this.id + "-autocomplete-list");
63 list.setAttribute("class", "autocomplete-items");
64 // append the DIV element as a child of the autocomplete container:
65 this.parentNode.appendChild(list);
66
67 // for each item in the array...
68 var c = 0;
69 for (var i = 0; i < items.length; i += 1) {
70 var item = items[i];
71
72 // match
73 var j = item.toUpperCase().indexOf(value.toUpperCase());
74 if (j < 0) {
75 continue;
76 }
77
78 c += 1;
79 if (c >= 15) {
80 var div = document.createElement("DIV");
81 div.innerHTML = "...";
82 list.appendChild(div);
83 break;
84 } else {
85 var div = document.createElement("DIV");
86 // make the matching letters bold:
87 div.innerHTML = item.substr(0, j)
88 + "<strong>" + item.substr(j, value.length) + "</strong>"
89 + item.substr(j + value.length)
90 + "<input type='hidden' value='" + item + "'>";
91
92 div.addEventListener("click", function(e) {
93 // set text field to selected value
94 input.value = this.getElementsByTagName("input")[0].value;
95 // close the list of autocompleted values,
96 // (or any other open lists of autocompleted values:
97 closeAllLists();
98 // callback
99 onselection(input.value);
100 });
101
102 list.appendChild(div);
103 }
104 }
105 };
106
107 input.onkeydown = function(e) {
108 var x = document.getElementById(this.id + "-autocomplete-list");
109 if (x) x = x.getElementsByTagName("div");
110 if (e.keyCode == 40) {
111 // key down
112 currentFocus += 1;
113 // and and make the current item more visible:
114 setActive(x);
115 } else if (e.keyCode == 38) {
116 // key up
117 currentFocus -= 1;
118 // and and make the current item more visible:
119 setActive(x);
120 } else if (e.keyCode == 13) {
121 // If the ENTER key is pressed, prevent the form from being submitted,
122 e.preventDefault();
123 if (currentFocus > -1) {
124 // and simulate a click on the "active" item:
125 if (x) x[currentFocus].click();
126 }
127 }
128 };
129
130 input.onfocus = function() {
131 onselection(input.value);
132 }
133
134 function setActive(x) {
135 // a function to classify an item as "active":
136 if (!x) return false;
137 // start by removing the "active" class on all items:
138 for (var i = 0; i < x.length; i++) {
139 x[i].classList.remove("autocomplete-active");
140 }
141 if (currentFocus >= x.length) currentFocus = 0;
142 if (currentFocus < 0) currentFocus = (x.length - 1);
143 // add class "autocomplete-active":
144 x[currentFocus].classList.add("autocomplete-active");
145 }
146
147 function closeAllLists(elmnt) {
148 // close all autocomplete lists in the document,
149 // except the one passed as an argument:
150 var x = document.getElementsByClassName("autocomplete-items");
151 for (var i = 0; i < x.length; i++) {
152 if (elmnt != x[i] && elmnt != input) {
153 x[i].parentNode.removeChild(x[i]);
154 }
155 }
156 }
157
158 // execute a function when someone clicks in the document:
159 document.addEventListener("click", function (e) {
160 closeAllLists(e.target);
161 });
162 }
163
164 function $(id) {
165 return document.getElementById(id);
166 }
167
168 function updateImages(dllink, model, target, release, commit, prefix, images) {
169 // add download button for image
170 function add_link(label, tags, image, help_id) {
171 var a = document.createElement('A');
172
173 a.classList.add('download-link');
174 a.href = (dllink ? dllink : config.downloadLink)
175 .replace('%target', target)
176 .replace('%release', release)
177 .replace('%file', prefix + image)
178 .replace('%commit', commit);
179 var span = document.createElement('SPAN');
180 span.appendChild(document.createTextNode(''));
181 a.appendChild(span);
182
183 // add sub label
184 if (tags.length > 0) {
185 a.appendChild(document.createTextNode(label + ' (' + tags.join(', ') + ')'));
186 } else {
187 a.appendChild(document.createTextNode(label));
188 }
189
190 if (config.showHelp) {
191 a.onmouseover = function() {
192 // hide all help texts
193 Array.from(document.getElementsByClassName('download-help'))
194 .forEach(function(e) { e.style.display = 'none'; });
195 $(help_id).style.display = 'block';
196 };
197 }
198
199 $('download-links').appendChild(a);
200 }
201
202 // remove all download links
203 Array.from(document.getElementsByClassName('download-link'))
204 .forEach(function(e) { e.remove(); });
205
206 // hide all help texts
207 Array.from(document.getElementsByClassName('download-help'))
208 .forEach(function(e) { e.style.display = 'none'; });
209
210 if (model && target && release && commit && prefix && images) {
211 // fill out build info
212 $('image-model').innerText = model;
213 $('image-target').innerText = target;
214 $('image-release').innerText = release;
215 $('image-commit').innerText = commit;
216
217 images.sort();
218
219 var entries = {'FACTORY': [], 'SYSUPGRADE': [], 'KERNEL': [], 'ROOTFS': [], 'SDCARD': [], 'OTHER': [] };
220
221 for (var i in images) {
222 var image = images[i];
223 var file = prefix + image;
224 if (file.includes('-factory')) {
225 entries['FACTORY'].push(image);
226 } else if (file.includes('-sysupgrade')) {
227 entries['SYSUPGRADE'].push(image);
228 } else if (file.includes('-kernel') || file.includes('-zImage') || file.includes('-uImage')) {
229 entries['KERNEL'].push(image);
230 } else if (file.includes('-rootfs')) {
231 entries['ROOTFS'].push(image);
232 } else if (file.includes('-sdcard')) {
233 entries['SDCARD'].push(image);
234 } else {
235 entries['OTHER'].push(image);
236 }
237 }
238
239 function extractTags(image) {
240 var all = image.split('.')[0].split('-');
241 var ignore = ['', 'kernel', 'zImage', 'uImage', 'factory', 'sysupgrade', 'rootfs', 'sdcard'];
242 return all.filter(function (el) { return !ignore.includes(el); });
243 }
244
245 for (var category in entries) {
246 var images = entries[category];
247 for (var i in images) {
248 var image = images[i];
249 var tags = (images.length > 1) ? extractTags(image) : [];
250 add_link(category, tags, image, category.toLowerCase() + '-help');
251 }
252 }
253
254 /*
255 for (var i in images) {
256 var image = images[i];
257 var label_suffix = "";
258
259 if (images.length >= 2) {
260 var extra = extractImageExtra(image);
261 if (extra.length > 0) {
262 label_suffix = " (" + extra.toUpperCase() + ")";
263 }
264 }
265
266 if (image.includes('-factory')) {
267 add_link("FACTORY" + label_suffix, image, 'factory-help');
268 } else if (image.includes('-sysupgrade')) {
269 add_link("SYSUPGRADE" + label_suffix, image, 'sysupgrade-help');
270 } else if (image.includes('-kernel') || image.includes('-zImage') || image.includes('-uImage')) {
271 add_link("KERNEL" + label_suffix, image, 'kernel-help');
272 } else if (image.includes('-rootfs')) {
273 add_link("ROOTFS" + label_suffix, image, 'rootfs-help');
274 } else if (image.includes('sdcard')) {
275 add_link("SDCARD" + label_suffix, image, 'sdcard-help');
276 } else {
277 add_link("OTHER" + label_suffix, image, 'other-help');
278 }
279 }
280 */
281
282 $('images').style.display = 'block';
283 } else {
284 $('images').style.display = 'none';
285 }
286 }
287
288 // hide fields
289 updateImages();
290 changeLanguage(config.language);
291
292 // parse data for internal use
293 function parseData(data) {
294 var obj = JSON.parse(data);
295 var out = {};
296 for (var release in obj) {
297 var link = obj[release]['link'];
298 var commit = obj[release]['commit']
299 var entries = obj[release]['models'];
300 var models = {};
301 for (var i = 0; i < entries.length; i += 1) {
302 var entry = entries[i];
303 var name = (entry[0] + " " + entry[1] + " " + entry[2]).trim();
304 var target = entry[3];
305 var prefix = entry[4];
306 var images = entry[5];
307 models[name] = {'link': link, 'name': name, 'target': target, 'commit': commit, 'prefix': prefix, 'images': images};
308 }
309 out[release] = models;
310 }
311 return out;
312 }
313
314 loadFile(config.data, function(data) {
315 var obj = parseData(data);
316 setupSelectList($("releases"), Object.keys(obj), function(release) {
317 setupAutocompleteList($("models"), Object.keys(obj[release]), function(model) {
318 if (model in obj[release]) {
319 // e.g. 'https://openwrt.org/%release/%file'
320 var dllink = obj[release][model].link;
321 // e.g. 'ath79/generic'
322 var target = obj[release][model].target;
323 // e.g. 'r12345-abcfefg321'
324 var commit = obj[release][model].commit;
325 // e.g. 'openwrt-ath79-generic-tp-link-841-v10-'
326 var prefix = obj[release][model].prefix;
327 // e.g. ['sysupgrade.bin', 'factory.bin']
328 var images = obj[release][model].images;
329 updateImages(dllink, model, target, release, commit, prefix, images);
330 } else {
331 updateImages();
332 }
333 });
334
335 // trigger model update when selected release changes
336 $("models").onfocus();
337 });
338 })