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
);
9 xmlhttp
.open('GET', url
, true);
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
);
20 select
.addEventListener("change", function(e
) {
21 onselection(items
[select
.selectedIndex
]);
24 if (select
.selectedIndex
>= 0) {
25 onselection(items
[select
.selectedIndex
]);
29 // Change the translation of the entire document
30 function changeLanguage(language
) {
31 var mapping
= translations
[language
];
33 for (var tr
in mapping
) {
34 Array
.from(document
.getElementsByClassName(tr
))
35 .forEach(function(e
) { e
.innerText
= mapping
[tr
]; })
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;
45 // sort numbers and other characters separately
46 var collator
= new Intl
.Collator(undefined, {numeric
: true, sensitivity
: 'base'});
48 items
.sort(collator
.compare
);
50 // execute a function when someone writes in the text field:
51 input
.oninput = function(e
) {
55 var value
= this.value
;
56 // close any already open lists of autocompleted values
58 if (!value
) { return false; }
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
);
67 // for each item in the array...
69 for (var i
= 0; i
< items
.length
; i
+= 1) {
73 var j
= item
.toUpperCase().indexOf(value
.toUpperCase());
80 var div
= document
.createElement("DIV");
81 div
.innerHTML
= "...";
82 list
.appendChild(div
);
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
+ "'>";
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:
99 onselection(input
.value
);
102 list
.appendChild(div
);
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) {
113 // and and make the current item more visible:
115 } else if (e
.keyCode
== 38) {
118 // and and make the current item more visible:
120 } else if (e
.keyCode
== 13) {
121 // If the ENTER key is pressed, prevent the form from being submitted,
123 if (currentFocus
> -1) {
124 // and simulate a click on the "active" item:
125 if (x
) x
[currentFocus
].click();
130 input
.onfocus = function() {
131 onselection(input
.value
);
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");
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");
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
]);
158 // execute a function when someone clicks in the document:
159 document
.addEventListener("click", function (e
) {
160 closeAllLists(e
.target
);
165 return document
.getElementById(id
);
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');
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(''));
184 if (tags
.length
> 0) {
185 a
.appendChild(document
.createTextNode(label
+ ' (' + tags
.join(', ') + ')'));
187 a
.appendChild(document
.createTextNode(label
));
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';
199 $('download-links').appendChild(a
);
202 // remove all download links
203 Array
.from(document
.getElementsByClassName('download-link'))
204 .forEach(function(e
) { e
.remove(); });
206 // hide all help texts
207 Array
.from(document
.getElementsByClassName('download-help'))
208 .forEach(function(e
) { e
.style
.display
= 'none'; });
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
;
219 var entries
= {'FACTORY': [], 'SYSUPGRADE': [], 'KERNEL': [], 'ROOTFS': [], 'SDCARD': [], 'OTHER': [] };
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
);
235 entries
['OTHER'].push(image
);
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
); });
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');
255 for (var i in images) {
256 var image = images[i];
257 var label_suffix = "";
259 if (images.length >= 2) {
260 var extra = extractImageExtra(image);
261 if (extra.length > 0) {
262 label_suffix = " (" + extra.toUpperCase() + ")";
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');
277 add_link("OTHER" + label_suffix, image, 'other-help');
282 $('images').style
.display
= 'block';
284 $('images').style
.display
= 'none';
290 changeLanguage(config
.language
);
292 // parse data for internal use
293 function parseData(data
) {
294 var obj
= JSON
.parse(data
);
296 for (var release
in obj
) {
297 var link
= obj
[release
]['link'];
298 var commit
= obj
[release
]['commit']
299 var entries
= obj
[release
]['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
};
309 out
[release
] = models
;
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
);
335 // trigger model update when selected release changes
336 $("models").onfocus();