forgot image link
[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(JSON.parse(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(version, 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.target)
190 .replace('%release', version)
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 (version && commit && model && image_link && mobj) {
224 var target = mobj.target;
225 var images = mobj.images;
226
227 // fill out build info
228 $('image-model').innerText = model;
229 $('image-target').innerText = target;
230 $('image-release').innerText = version;
231 $('image-commit').innerText = commit;
232
233 var prefix = findCommonPrefix(images);
234 var entries = {
235 'FACTORY': [],
236 'SYSUPGRADE': [],
237 'KERNEL': [],
238 'ROOTFS': [],
239 'SDCARD': [],
240 'TFTP': [],
241 'OTHER': []
242 };
243
244 images.sort();
245
246 for (var i in images) {
247 var image = images[i];
248 var lc = image.toLowerCase()
249 if (lc.includes('factory')) {
250 entries['FACTORY'].push(image);
251 } else if (lc.includes('sysupgrade')) {
252 entries['SYSUPGRADE'].push(image);
253 } else if (lc.includes('kernel') || lc.includes('zimage') || lc.includes('uimage')) {
254 entries['KERNEL'].push(image);
255 } else if (lc.includes('rootfs')) {
256 entries['ROOTFS'].push(image);
257 } else if (lc.includes('sdcard')) {
258 entries['SDCARD'].push(image);
259 } else if (lc.includes('tftp')) {
260 entries['TFTP'].push(image);
261 } else {
262 entries['OTHER'].push(image);
263 }
264 }
265
266 function extractTags(prefix, image) {
267 var all = image.substring(prefix.length).split('.')[0].split('-');
268 var ignore = ['', 'kernel', 'zImage', 'uImage', 'factory', 'sysupgrade', 'rootfs', 'sdcard'];
269 return all.filter(function (el) { return !ignore.includes(el); });
270 }
271
272 for (var category in entries) {
273 var images = entries[category];
274 for (var i in images) {
275 var image = images[i];
276 var tags = (images.length > 1) ? extractTags(prefix, image) : [];
277 addLink(category, tags, image, category.toLowerCase() + '-help');
278 }
279 }
280
281 $('images').style.display = 'block';
282 } else {
283 $('images').style.display = 'none';
284 }
285 }
286
287 // hide fields
288 updateImages();
289 changeLanguage(config.language);
290
291 setupSelectList($("releases"), Object.keys(config.versions), function(version) {
292 loadFile(config.versions[version], function(obj) {
293 setupAutocompleteList($("models"), Object.keys(obj['models']), function(model) {
294 if (model in obj['models']) {
295 var link = obj.link;
296 var commit = obj.commit;
297 var mobj = obj['models'][model];
298 updateImages(version, commit, model, link, mobj);
299 } else {
300 updateImages();
301 }
302 });
303
304 // trigger model update when selected version changes
305 $("models").onfocus();
306 });
307 });