2 var current_model
= {};
3 var current_language
= config
.language
;
6 return document
.getElementById(id
);
10 $(id
).style
.display
= 'block';
14 $(id
).style
.display
= 'none';
17 function build_asa_request() {
18 if (!current_model
|| !current_model
.id
) {
24 return str
.match(/[^\s,]+/g) || [];
27 function get_model_titles(titles
) {
28 return titles
.map(e
=> {
32 return ((e
.vendor
|| '') + (e
.model
|| '') + (e
.variant
|| '')).trim();
43 'profile': current_model
.id
,
44 'packages': split($('packages').value
),
45 'version': $('releases').value
48 console
.log('disable request button / show loading spinner')
50 fetch(config
.asu_url
, {
52 headers
: { 'Content-Type': 'application/json' },
53 body
: JSON
.stringify(request_data
)
56 switch (response
.status
) {
60 console
.log('image found');
65 mobj
.version_number
, mobj
.version_commit
,
66 get_model_titles(mobj
.titles
),
72 // show some spinning animation
73 console
.log('check again in 5 seconds');
74 setTimeout(_
=> { build_asa_request() }, 5000);
76 case 400: // bad request
77 case 422: // bad package
78 case 500: // build failed
80 console
.log('bad request (' + response
.status
+ ')'); // see message
90 function loadFile(url
, callback
) {
91 var xmlhttp
= new XMLHttpRequest();
92 xmlhttp
.onreadystatechange = function() {
93 if (xmlhttp
.readyState
== 4 && xmlhttp
.status
== 200) {
94 callback(JSON
.parse(xmlhttp
.responseText
), url
);
97 xmlhttp
.open('GET', url
, true);
101 function setupSelectList(select
, items
, onselection
) {
102 for (var i
= 0; i
< items
.length
; i
+= 1) {
103 var option
= document
.createElement('OPTION');
104 option
.innerHTML
= items
[i
];
105 select
.appendChild(option
);
108 select
.addEventListener('change', e
=> {
109 onselection(items
[select
.selectedIndex
]);
112 if (select
.selectedIndex
>= 0) {
113 onselection(items
[select
.selectedIndex
]);
117 // Change the translation of the entire document
118 function applyLanguage(language
) {
120 current_language
= language
;
123 var mapping
= translations
[current_language
];
125 for (var tr
in mapping
) {
126 Array
.from(document
.getElementsByClassName(tr
))
127 .forEach(e
=> { e
.innerText
= mapping
[tr
]; })
132 function setupAutocompleteList(input
, items
, onselection
) {
133 // the setupAutocompleteList function takes two arguments,
134 // the text field element and an array of possible autocompleted values:
135 var currentFocus
= -1;
137 // sort numbers and other characters separately
138 var collator
= new Intl
.Collator(undefined, {numeric
: true, sensitivity
: 'base'});
140 items
.sort(collator
.compare
);
142 // execute a function when someone writes in the text field:
143 input
.oninput = function(e
) {
147 var value
= this.value
;
148 // close any already open lists of autocompleted values
155 // create a DIV element that will contain the items (values):
156 var list
= document
.createElement('DIV');
157 list
.setAttribute('id', this.id
+ '-autocomplete-list');
158 list
.setAttribute('class', 'autocomplete-items');
159 // append the DIV element as a child of the autocomplete container:
160 this.parentNode
.appendChild(list
);
162 // for each item in the array...
164 for (var i
= 0; i
< items
.length
; i
+= 1) {
168 var j
= item
.toUpperCase().indexOf(value
.toUpperCase());
175 var div
= document
.createElement('DIV');
176 div
.innerHTML
= '...';
177 list
.appendChild(div
);
180 var div
= document
.createElement('DIV');
181 // make the matching letters bold:
182 div
.innerHTML
= item
.substr(0, j
)
183 + '<strong>' + item
.substr(j
, value
.length
) + '</strong>'
184 + item
.substr(j
+ value
.length
)
185 + '<input type="hidden" value="' + item
+ '">';
187 div
.addEventListener('click', function(e
) {
188 // set text field to selected value
189 input
.value
= this.getElementsByTagName('input')[0].value
;
190 // close the list of autocompleted values,
191 // (or any other open lists of autocompleted values:
194 onselection(input
.value
);
197 list
.appendChild(div
);
202 input
.onkeydown = function(e
) {
203 var x
= document
.getElementById(this.id
+ '-autocomplete-list');
204 if (x
) x
= x
.getElementsByTagName('div');
205 if (e
.keyCode
== 40) {
208 // and and make the current item more visible:
210 } else if (e
.keyCode
== 38) {
213 // and and make the current item more visible:
215 } else if (e
.keyCode
== 13) {
216 // If the ENTER key is pressed, prevent the form from being submitted,
218 if (currentFocus
> -1) {
219 // and simulate a click on the 'active' item:
220 if (x
) x
[currentFocus
].click();
225 input
.onfocus = function() {
226 onselection(input
.value
);
229 function setActive(x
) {
230 // a function to classify an item as 'active':
231 if (!x
) return false;
232 // start by removing the 'active' class on all items:
233 for (var i
= 0; i
< x
.length
; i
++) {
234 x
[i
].classList
.remove('autocomplete-active');
236 if (currentFocus
>= x
.length
) currentFocus
= 0;
237 if (currentFocus
< 0) currentFocus
= (x
.length
- 1);
238 // add class 'autocomplete-active':
239 x
[currentFocus
].classList
.add('autocomplete-active');
242 function closeAllLists(elmnt
) {
243 // close all autocomplete lists in the document,
244 // except the one passed as an argument:
245 var x
= document
.getElementsByClassName('autocomplete-items');
246 for (var i
= 0; i
< x
.length
; i
++) {
247 if (elmnt
!= x
[i
] && elmnt
!= input
) {
248 x
[i
].parentNode
.removeChild(x
[i
]);
253 // execute a function when someone clicks in the document:
254 document
.addEventListener('click', e
=> {
255 closeAllLists(e
.target
);
259 function findCommonPrefix(images
) {
260 var files
= images
.map(image
=> image
.name
)
261 var A
= files
.concat().sort();
263 var last
= A
[A
.length
- 1];
264 var L
= first
.length
;
266 while (i
< L
&& first
.charAt(i
) === last
.charAt(i
)) {
269 return first
.substring(0, i
);
272 function updateImages(version
, commit
, model
, url
, mobj
, is_custom
) {
273 // add download button for image
274 function addLink(label
, tags
, file
, help_id
) {
275 var a
= document
.createElement('A');
276 a
.classList
.add('download-link');
278 .replace('{target}', mobj
.target
)
279 .replace('{release}', version
)
281 var span
= document
.createElement('SPAN');
282 span
.appendChild(document
.createTextNode(''));
286 if (tags
.length
> 0) {
287 a
.appendChild(document
.createTextNode(label
+ ' (' + tags
.join(', ') + ')'));
289 a
.appendChild(document
.createTextNode(label
));
292 if (config
.showHelp
) {
293 a
.onmouseover = function() {
294 // hide all help texts
295 Array
.from(document
.getElementsByClassName('download-help'))
296 .forEach(e
=> e
.style
.display
= 'none');
301 $('download-links').appendChild(a
);
304 function switchClass(id
, from_class
, to_class
) {
305 $(id
).classList
.remove(from_class
);
306 $(id
).classList
.add(to_class
);
309 // remove all download links
310 Array
.from(document
.getElementsByClassName('download-link'))
311 .forEach(e
=> e
.remove());
313 // hide all help texts
314 Array
.from(document
.getElementsByClassName('download-help'))
315 .forEach(e
=> e
.style
.display
= 'none');
317 if (version
&& commit
&& model
&& url
&& mobj
) {
318 var target
= mobj
.target
;
319 var images
= mobj
.images
;
321 // change between "release" and "custom" title
323 switchClass('images-title', 'tr-release-build', 'tr-custom-build');
324 switchClass('downloads-title', 'tr-release-downloads', 'tr-custom-downloads');
326 switchClass('images-title', 'tr-custom-build', 'tr-release-build');
327 switchClass('downloads-title', 'tr-custom-downloads', 'tr-release-downloads');
329 // update title translation
332 // fill out build info
333 $('image-model').innerText
= model
;
334 $('image-target').innerText
= target
;
335 $('image-release').innerText
= version
;
336 $('image-commit').innerText
= commit
;
338 var prefix
= findCommonPrefix(images
);
351 for (var i
in images
) {
352 var image
= images
[i
].name
;
353 var lc
= image
.toLowerCase()
354 if (lc
.includes('factory')) {
355 entries
['FACTORY'].push(image
);
356 } else if (lc
.includes('sysupgrade')) {
357 entries
['SYSUPGRADE'].push(image
);
358 } else if (lc
.includes('kernel') || lc
.includes('zimage') || lc
.includes('uimage')) {
359 entries
['KERNEL'].push(image
);
360 } else if (lc
.includes('rootfs')) {
361 entries
['ROOTFS'].push(image
);
362 } else if (lc
.includes('sdcard')) {
363 entries
['SDCARD'].push(image
);
364 } else if (lc
.includes('tftp')) {
365 entries
['TFTP'].push(image
);
367 entries
['OTHER'].push(image
);
371 function extractTags(prefix
, image
) {
372 var all
= image
.substring(prefix
.length
).split('.')[0].split('-');
373 var ignore
= ['', 'kernel', 'zimage', 'uimage', 'factory', 'sysupgrade', 'rootfs', 'sdcard'];
374 return all
.filter(el
=> !ignore
.includes(el
.toLowerCase()));
377 for (var category
in entries
) {
378 var images
= entries
[category
];
379 for (var i
in images
) {
380 var image
= images
[i
];
381 var tags
= (images
.length
> 1) ? extractTags(prefix
, image
) : [];
382 var label
= category
;
383 addLink(label
, tags
, image
, category
.toLowerCase() + '-help');
393 setupSelectList($('releases'), Object
.keys(config
.versions
), version
=> {
394 loadFile(config
.versions
[version
], obj
=> {
395 setupAutocompleteList($('models'), Object
.keys(obj
['models']), model
=> {
396 if (model
in obj
['models']) {
398 var commit
= obj
.version_commit
;
399 var mobj
= obj
['models'][model
];
400 updateImages(version
, commit
, model
, url
, mobj
, false);
401 current_model
= mobj
;
408 // trigger model update when selected version changes
409 $('models').onfocus();
413 if (config
.asu_url
) {
419 applyLanguage(config
.language
);