3 function get_model_titles(titles
) {
5 for (var i
in titles
) {
7 title
.push(titles
[i
].title
)
9 var title_fragments
= []
10 if (titles
[i
].vendor
) { title_fragments
.push(titles
[i
].vendor
) }
11 title_fragments
.push(titles
[i
].model
)
12 if (titles
[i
].variant
) { title_fragments
.push(titles
[i
].variant
) }
13 title
.push(title_fragments
.join(" "))
16 return title
.join("/")
19 function build_request() {
20 var profile
= data
["models"][$("models").value
]
22 if (profile
=== undefined) {
28 $("loading").style
.display
= 'block';
29 $("custom").style
.display
= 'none';
33 "profile": profile
.id
,
34 "packages": $("packages").value
.trim().split(" "),
35 "version": $("releases").value
38 console
.log("disable request button / show loading spinner")
40 fetch(config
.asu_url
, {
42 headers
: { 'Content-Type': 'application/json' },
43 body
: JSON
.stringify(request_data
)
45 .then(function(response
) {
46 switch (response
.status
) {
48 $("loading").style
.display
= 'none';
49 $("custom").style
.display
= 'block';
50 console
.log("image found");
52 .then(function(mobj
) {
57 get_model_titles(mobj
.titles
),
63 // show some spinning animation
64 console
.log("check again in 5 seconds");
65 setTimeout(function() { build_request(request_data
) }, 5000);
68 $("loading").style
.display
= 'none';
69 $("custom").style
.display
= 'block';
70 console
.log("bad request"); // see message
72 .then(function(mobj
) {
77 $("loading").style
.display
= 'none';
78 $("custom").style
.display
= 'block';
79 console
.log("bad package"); // see message
81 .then(function(mobj
) {
86 $("loading").style
.display
= 'none';
87 $("custom").style
.display
= 'block';
88 console
.log("build failed");
90 .then(function(mobj
) {
98 function loadFile(url
, callback
) {
99 var xmlhttp
= new XMLHttpRequest();
100 xmlhttp
.onreadystatechange = function() {
101 if (xmlhttp
.readyState
== 4 && xmlhttp
.status
== 200) {
102 callback(JSON
.parse(xmlhttp
.responseText
), url
);
105 xmlhttp
.open('GET', url
, true);
109 function setupSelectList(select
, items
, onselection
) {
110 for (var i
= 0; i
< items
.length
; i
+= 1) {
111 var option
= document
.createElement("OPTION");
112 option
.innerHTML
= items
[i
];
113 select
.appendChild(option
);
116 select
.addEventListener("change", function(e
) {
117 onselection(items
[select
.selectedIndex
]);
120 if (select
.selectedIndex
>= 0) {
121 onselection(items
[select
.selectedIndex
]);
125 // Change the translation of the entire document
126 function changeLanguage(language
) {
127 var mapping
= translations
[language
];
129 for (var tr
in mapping
) {
130 Array
.from(document
.getElementsByClassName(tr
))
131 .forEach(function(e
) { e
.innerText
= mapping
[tr
]; })
136 function setupAutocompleteList(input
, items
, onselection
) {
137 // the setupAutocompleteList function takes two arguments,
138 // the text field element and an array of possible autocompleted values:
139 var currentFocus
= -1;
141 // sort numbers and other characters separately
142 var collator
= new Intl
.Collator(undefined, {numeric
: true, sensitivity
: 'base'});
144 items
.sort(collator
.compare
);
146 // execute a function when someone writes in the text field:
147 input
.oninput = function(e
) {
151 var value
= this.value
;
152 // close any already open lists of autocompleted values
159 // create a DIV element that will contain the items (values):
160 var list
= document
.createElement("DIV");
161 list
.setAttribute("id", this.id
+ "-autocomplete-list");
162 list
.setAttribute("class", "autocomplete-items");
163 // append the DIV element as a child of the autocomplete container:
164 this.parentNode
.appendChild(list
);
166 // for each item in the array...
168 for (var i
= 0; i
< items
.length
; i
+= 1) {
172 var j
= item
.toUpperCase().indexOf(value
.toUpperCase());
179 var div
= document
.createElement("DIV");
180 div
.innerHTML
= "...";
181 list
.appendChild(div
);
184 var div
= document
.createElement("DIV");
185 // make the matching letters bold:
186 div
.innerHTML
= item
.substr(0, j
)
187 + "<strong>" + item
.substr(j
, value
.length
) + "</strong>"
188 + item
.substr(j
+ value
.length
)
189 + "<input type='hidden' value='" + item
+ "'>";
191 div
.addEventListener("click", function(e
) {
192 // set text field to selected value
193 input
.value
= this.getElementsByTagName("input")[0].value
;
194 // close the list of autocompleted values,
195 // (or any other open lists of autocompleted values:
198 onselection(input
.value
);
201 list
.appendChild(div
);
206 input
.onkeydown = function(e
) {
207 var x
= document
.getElementById(this.id
+ "-autocomplete-list");
208 if (x
) x
= x
.getElementsByTagName("div");
209 if (e
.keyCode
== 40) {
212 // and and make the current item more visible:
214 } else if (e
.keyCode
== 38) {
217 // and and make the current item more visible:
219 } else if (e
.keyCode
== 13) {
220 // If the ENTER key is pressed, prevent the form from being submitted,
222 if (currentFocus
> -1) {
223 // and simulate a click on the "active" item:
224 if (x
) x
[currentFocus
].click();
229 input
.onfocus = function() {
230 onselection(input
.value
);
233 function setActive(x
) {
234 // a function to classify an item as "active":
235 if (!x
) return false;
236 // start by removing the "active" class on all items:
237 for (var i
= 0; i
< x
.length
; i
++) {
238 x
[i
].classList
.remove("autocomplete-active");
240 if (currentFocus
>= x
.length
) currentFocus
= 0;
241 if (currentFocus
< 0) currentFocus
= (x
.length
- 1);
242 // add class "autocomplete-active":
243 x
[currentFocus
].classList
.add("autocomplete-active");
246 function closeAllLists(elmnt
) {
247 // close all autocomplete lists in the document,
248 // except the one passed as an argument:
249 var x
= document
.getElementsByClassName("autocomplete-items");
250 for (var i
= 0; i
< x
.length
; i
++) {
251 if (elmnt
!= x
[i
] && elmnt
!= input
) {
252 x
[i
].parentNode
.removeChild(x
[i
]);
257 // execute a function when someone clicks in the document:
258 document
.addEventListener("click", function (e
) {
259 closeAllLists(e
.target
);
264 return document
.getElementById(id
);
267 function findCommonPrefix(images
) {
268 var files
= images
.map(image
=> image
.name
)
269 var A
= files
.concat().sort();
271 var last
= A
[A
.length
- 1];
272 var L
= first
.length
;
274 while (i
< L
&& first
.charAt(i
) === last
.charAt(i
)) {
277 return first
.substring(0, i
);
280 function updateImages(version
, commit
, model
, url
, mobj
) {
281 // add download button for image
282 function addLink(label
, tags
, file
, help_id
) {
283 var a
= document
.createElement('A');
284 a
.classList
.add('download-link');
286 .replace('{target}', mobj
.target
)
287 .replace('{release}', version
)
289 var span
= document
.createElement('SPAN');
290 span
.appendChild(document
.createTextNode(''));
294 if (tags
.length
> 0) {
295 a
.appendChild(document
.createTextNode(label
+ ' (' + tags
.join(', ') + ')'));
297 a
.appendChild(document
.createTextNode(label
));
300 if (config
.showHelp
) {
301 a
.onmouseover = function() {
302 // hide all help texts
303 Array
.from(document
.getElementsByClassName('download-help'))
304 .forEach(function(e
) { e
.style
.display
= 'none'; });
305 $(help_id
).style
.display
= 'block';
309 $('download-links').appendChild(a
);
312 // remove all download links
313 Array
.from(document
.getElementsByClassName('download-link'))
314 .forEach(function(e
) { e
.remove(); });
316 // hide all help texts
317 Array
.from(document
.getElementsByClassName('download-help'))
318 .forEach(function(e
) { e
.style
.display
= 'none'; });
320 if (version
&& commit
&& model
&& url
&& mobj
) {
321 var target
= mobj
.target
;
322 var images
= mobj
.images
;
324 // fill out build info
325 $('image-model').innerText
= model
;
326 $('image-target').innerText
= target
;
327 $('image-release').innerText
= version
;
328 $('image-commit').innerText
= commit
;
330 var prefix
= findCommonPrefix(images
);
343 for (var i
in images
) {
344 var image
= images
[i
].name
;
345 var lc
= image
.toLowerCase()
346 if (lc
.includes('factory')) {
347 entries
['FACTORY'].push(image
);
348 } else if (lc
.includes('sysupgrade')) {
349 entries
['SYSUPGRADE'].push(image
);
350 } else if (lc
.includes('kernel') || lc
.includes('zimage') || lc
.includes('uimage')) {
351 entries
['KERNEL'].push(image
);
352 } else if (lc
.includes('rootfs')) {
353 entries
['ROOTFS'].push(image
);
354 } else if (lc
.includes('sdcard')) {
355 entries
['SDCARD'].push(image
);
356 } else if (lc
.includes('tftp')) {
357 entries
['TFTP'].push(image
);
359 entries
['OTHER'].push(image
);
363 function extractTags(prefix
, image
) {
364 var all
= image
.substring(prefix
.length
).split('.')[0].split('-');
365 var ignore
= ['', 'kernel', 'zImage', 'uImage', 'factory', 'sysupgrade', 'rootfs', 'sdcard'];
366 return all
.filter(function (el
) { return !ignore
.includes(el
); });
369 for (var category
in entries
) {
370 var images
= entries
[category
];
371 for (var i
in images
) {
372 var image
= images
[i
];
373 var tags
= (images
.length
> 1) ? extractTags(prefix
, image
) : [];
374 addLink(category
, tags
, image
, category
.toLowerCase() + '-help');
378 $('images').style
.display
= 'block';
380 $('images').style
.display
= 'none';
386 changeLanguage(config
.language
);
388 if (config
.asu_url
) {
389 $('custom').style
.display
= 'block';
392 setupSelectList($("releases"), Object
.keys(config
.versions
), function(version
) {
393 loadFile(config
.versions
[version
], function(obj
) {
395 setupAutocompleteList($("models"), Object
.keys(obj
['models']), function(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
);
406 // trigger model update when selected version changes
407 $("models").onfocus();