Merge pull request #18 from mwarning/rename
[web/firmware-selector-openwrt-org.git] / index.js
1
2 var current_model = {};
3 var current_language = config.language;
4
5 function $(id) {
6 return document.getElementById(id);
7 }
8
9 function show(id) {
10 $(id).style.display = 'block';
11 }
12
13 function hide(id) {
14 $(id).style.display = 'none';
15 }
16
17 function build_asa_request() {
18 if (!current_model || !current_model.id) {
19 alert('bad profile');
20 return;
21 }
22
23 function split(str) {
24 return str.match(/[^\s,]+/g) || [];
25 }
26
27 function get_model_titles(titles) {
28 return titles.map(e => {
29 if (e.title) {
30 return e.title;
31 } else {
32 return ((e.vendor || '') + (e.model || '') + (e.variant || '')).trim();
33 }
34 }).join('/');
35 }
36
37 // hide image view
38 updateImages();
39
40 show('loading');
41
42 var request_data = {
43 'profile': current_model.id,
44 'packages': split($('packages').value),
45 'version': $('versions').value
46 }
47
48 console.log('disable request button / show loading spinner')
49
50 fetch(config.asu_url, {
51 method: 'POST',
52 headers: { 'Content-Type': 'application/json' },
53 body: JSON.stringify(request_data)
54 })
55 .then(response => {
56 switch (response.status) {
57 case 200:
58 hide('loading');
59
60 console.log('image found');
61 response.json()
62 .then(mobj => {
63 console.log(mobj)
64 updateImages(
65 mobj.version_number,
66 mobj.version_code,
67 get_model_titles(mobj.titles),
68 mobj.url, mobj, true
69 );
70 });
71 break;
72 case 202:
73 // show some spinning animation
74 console.log('check again in 5 seconds');
75 setTimeout(_ => { build_asa_request() }, 5000);
76 break;
77 case 400: // bad request
78 case 422: // bad package
79 case 500: // build failed
80 hide('loading');
81 console.log('error (' + response.status + ')');
82 response.json()
83 .then(mobj => {
84 alert(mobj.message)
85 });
86 break;
87 }
88 })
89 }
90
91 function loadFile(url, callback) {
92 var xmlhttp = new XMLHttpRequest();
93 xmlhttp.onreadystatechange = function() {
94 if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
95 callback(JSON.parse(xmlhttp.responseText), url);
96 }
97 };
98 xmlhttp.open('GET', url, true);
99 xmlhttp.send();
100 }
101
102 function setupSelectList(select, items, onselection) {
103 for (var i = 0; i < items.length; i += 1) {
104 var option = document.createElement('OPTION');
105 option.innerHTML = items[i];
106 select.appendChild(option);
107 }
108
109 select.addEventListener('change', e => {
110 onselection(items[select.selectedIndex]);
111 });
112
113 if (select.selectedIndex >= 0) {
114 onselection(items[select.selectedIndex]);
115 }
116 }
117
118 // Change the translation of the entire document
119 function applyLanguage(language) {
120 if (language) {
121 current_language = language;
122 }
123
124 var mapping = translations[current_language];
125 if (mapping) {
126 for (var tr in mapping) {
127 Array.from(document.getElementsByClassName(tr))
128 .forEach(e => { e.innerText = mapping[tr]; })
129 }
130 }
131 }
132
133 function setupAutocompleteList(input, items, onselection) {
134 // the setupAutocompleteList function takes two arguments,
135 // the text field element and an array of possible autocompleted values:
136 var currentFocus = -1;
137
138 // sort numbers and other characters separately
139 var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
140
141 items.sort(collator.compare);
142
143 // execute a function when someone writes in the text field:
144 input.oninput = function(e) {
145 // clear images
146 updateImages();
147
148 var value = this.value;
149 // close any already open lists of autocompleted values
150 closeAllLists();
151
152 if (!value) {
153 return false;
154 }
155
156 // create a DIV element that will contain the items (values):
157 var list = document.createElement('DIV');
158 list.setAttribute('id', this.id + '-autocomplete-list');
159 list.setAttribute('class', 'autocomplete-items');
160 // append the DIV element as a child of the autocomplete container:
161 this.parentNode.appendChild(list);
162
163 // for each item in the array...
164 var c = 0;
165 for (var i = 0; i < items.length; i += 1) {
166 var item = items[i];
167
168 // match
169 var j = item.toUpperCase().indexOf(value.toUpperCase());
170 if (j < 0) {
171 continue;
172 }
173
174 c += 1;
175 if (c >= 15) {
176 var div = document.createElement('DIV');
177 div.innerHTML = '...';
178 list.appendChild(div);
179 break;
180 } else {
181 var div = document.createElement('DIV');
182 // make the matching letters bold:
183 div.innerHTML = item.substr(0, j)
184 + '<strong>' + item.substr(j, value.length) + '</strong>'
185 + item.substr(j + value.length)
186 + '<input type="hidden" value="' + item + '">';
187
188 div.addEventListener('click', function(e) {
189 // set text field to selected value
190 input.value = this.getElementsByTagName('input')[0].value;
191 // close the list of autocompleted values,
192 // (or any other open lists of autocompleted values:
193 closeAllLists();
194 // callback
195 onselection(input.value);
196 });
197
198 list.appendChild(div);
199 }
200 }
201 };
202
203 input.onkeydown = function(e) {
204 var x = document.getElementById(this.id + '-autocomplete-list');
205 if (x) x = x.getElementsByTagName('div');
206 if (e.keyCode == 40) {
207 // key down
208 currentFocus += 1;
209 // and and make the current item more visible:
210 setActive(x);
211 } else if (e.keyCode == 38) {
212 // key up
213 currentFocus -= 1;
214 // and and make the current item more visible:
215 setActive(x);
216 } else if (e.keyCode == 13) {
217 // If the ENTER key is pressed, prevent the form from being submitted,
218 e.preventDefault();
219 if (currentFocus > -1) {
220 // and simulate a click on the 'active' item:
221 if (x) x[currentFocus].click();
222 }
223 }
224 };
225
226 input.onfocus = function() {
227 onselection(input.value);
228 }
229
230 function setActive(x) {
231 // a function to classify an item as 'active':
232 if (!x) return false;
233 // start by removing the 'active' class on all items:
234 for (var i = 0; i < x.length; i++) {
235 x[i].classList.remove('autocomplete-active');
236 }
237 if (currentFocus >= x.length) currentFocus = 0;
238 if (currentFocus < 0) currentFocus = (x.length - 1);
239 // add class 'autocomplete-active':
240 x[currentFocus].classList.add('autocomplete-active');
241 }
242
243 function closeAllLists(elmnt) {
244 // close all autocomplete lists in the document,
245 // except the one passed as an argument:
246 var x = document.getElementsByClassName('autocomplete-items');
247 for (var i = 0; i < x.length; i++) {
248 if (elmnt != x[i] && elmnt != input) {
249 x[i].parentNode.removeChild(x[i]);
250 }
251 }
252 }
253
254 // execute a function when someone clicks in the document:
255 document.addEventListener('click', e => {
256 closeAllLists(e.target);
257 });
258 }
259
260 function updateImages(version, code, model, url, mobj, is_custom) {
261 // add download button for image
262 function addLink(type, file) {
263 var a = document.createElement('A');
264 a.classList.add('download-link');
265 a.href = url
266 .replace('{target}', mobj.target)
267 .replace('{version}', version)
268 + '/' + file;
269 var span = document.createElement('SPAN');
270 span.appendChild(document.createTextNode(''));
271 a.appendChild(span);
272 a.appendChild(document.createTextNode(type.toUpperCase()));
273
274 if (config.showHelp) {
275 a.onmouseover = function() {
276 // hide all help texts
277 Array.from(document.getElementsByClassName('download-help'))
278 .forEach(e => e.style.display = 'none');
279 var lc = type.toLowerCase();
280 if (lc.includes('sysupgrade')) {
281 show('sysupgrade-help');
282 } else if (lc.includes('factory') || lc == 'trx' || lc == 'chk') {
283 show('factory-help');
284 } else if (lc.includes('kernel') || lc.includes('zimage') || lc.includes('uimage')) {
285 show('kernel-help');
286 } else if (lc.includes('root')) {
287 show('rootfs-help');
288 } else if (lc.includes('sdcard')) {
289 show('sdcard-help');
290 } else if (lc.includes('tftp')) {
291 show('tftp-help');
292 } else {
293 show('other-help');
294 }
295 };
296 }
297
298 $('download-links').appendChild(a);
299 }
300
301 function switchClass(id, from_class, to_class) {
302 $(id).classList.remove(from_class);
303 $(id).classList.add(to_class);
304 }
305
306 // remove all download links
307 Array.from(document.getElementsByClassName('download-link'))
308 .forEach(e => e.remove());
309
310 // hide all help texts
311 Array.from(document.getElementsByClassName('download-help'))
312 .forEach(e => e.style.display = 'none');
313
314 if (version && code && model && url && mobj) {
315 var target = mobj.target;
316 var images = mobj.images;
317
318 // change between "version" and "custom" title
319 if (is_custom) {
320 switchClass('images-title', 'tr-version-build', 'tr-custom-build');
321 switchClass('downloads-title', 'tr-version-downloads', 'tr-custom-downloads');
322 } else {
323 switchClass('images-title', 'tr-custom-build', 'tr-version-build');
324 switchClass('downloads-title', 'tr-custom-downloads', 'tr-version-downloads');
325 }
326 // update title translation
327 applyLanguage();
328
329 // fill out build info
330 $('image-model').innerText = model;
331 $('image-target').innerText = target;
332 $('image-version').innerText = version;
333 $('image-code').innerText = code;
334
335 images.sort((a, b) => a.name.localeCompare(b.name));
336
337 for (var i in images) {
338 addLink(images[i].type, images[i].name);
339 }
340
341 show('images');
342 } else {
343 hide('images');
344 }
345 }
346
347 setupSelectList($('versions'), Object.keys(config.versions), version => {
348 loadFile(config.versions[version], obj => {
349 setupAutocompleteList($('models'), Object.keys(obj['models']), model => {
350 if (model in obj['models']) {
351 var url = obj.url;
352 var code = obj.version_code;
353 var mobj = obj['models'][model];
354 updateImages(version, code, model, url, mobj, false);
355 current_model = mobj;
356 } else {
357 updateImages();
358 current_model = {};
359 }
360 });
361
362 // trigger model update when selected version changes
363 $('models').onfocus();
364 });
365 });
366
367 if (config.asu_url) {
368 show('custom');
369 }
370
371 // hide fields
372 updateImages();
373 applyLanguage(config.language);