stable #1
5 changed files with 344 additions and 0 deletions
298
public/assets/js/index.js
Normal file
298
public/assets/js/index.js
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
Search
|
||||
*/
|
||||
|
||||
// Android versions
|
||||
[
|
||||
'1.0', '1.1', '1.5', '1.6',
|
||||
'2.0', '2.0.1', '2.1', '2.2', '2.2.1', '2.2.2', '2.2.3', '2.3', '2.3.1', '2.3.2', '2.3.3', '2.3.4', '2.3.5', '2.3.6', '2.3.7',
|
||||
'3.0', '3.1', '3.2', '3.2.1', '3.2.2', '3.2.3', '3.2.4', '3.2.5', '3.2.6',
|
||||
'4.0', '4.0.1', '4.0.2', '4.0.3', '4.0.4', '4.1', '4.1.1', '4.1.2', '4.2', '4.2.1', '4.2.2', '4.3', '4.3.1', '4.4', '4.4.1', '4.4.2', '4.4.3', '4.4.4',
|
||||
'5.0', '5.0.1', '5.0.2', '5.1', '5.1.1',
|
||||
'6.0', '6.0.1',
|
||||
'7.0', '7.1', '7.1.1', '7.1.2',
|
||||
'8.0', '8.1',
|
||||
'9.0'
|
||||
].forEach(v => document.querySelector('#minAndroidVersion').innerHTML += `<option value="${v}">${v}</option>`);
|
||||
|
||||
/**
|
||||
* Search request
|
||||
* @param query
|
||||
* @param callback
|
||||
*/
|
||||
let search = (query, callback) => {
|
||||
socket.emit('search', query);
|
||||
socket.on('res', res => {
|
||||
let r = [];
|
||||
for(let i = 0; i < res.length; i++){
|
||||
let _c = [];
|
||||
for(let j = 0; j < query.conditions.length; j++){
|
||||
_c.push(query.conditions[j](res[i]));
|
||||
}
|
||||
if(_c.every(c => c === true) || _c.length === 0){
|
||||
r.push(res[i]);
|
||||
}
|
||||
for(let key in res[i]){
|
||||
if(res[i].hasOwnProperty(key)){
|
||||
if(['size', 'version', 'androidVersion'].indexOf(key) !== -1 && res[i][key].search(/var[iy]/i) !== -1){
|
||||
res[i][key] = '-';
|
||||
}
|
||||
if(key === 'offersIAP'){
|
||||
res[i][key] = res[i][key].toString().replace('true', 'yes').replace('false', 'no');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
callback(r);
|
||||
socket.removeListener('res');
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Conditions checking functions
|
||||
*/
|
||||
let _c = {
|
||||
maxPrice: (app, n) => app.price <= n,
|
||||
adFree: app => !app.adSupported,
|
||||
inAppFree: app => !app.offersIAP,
|
||||
minInstalls: (app, n) => app.minInstalls >= n,
|
||||
minScore: (app, n) => app.score.toFixed(1) >= n,
|
||||
maxSize: (app, n) => parseFloat(app.size) <= n,
|
||||
maxUpdatedDays: (app, n) => Math.floor((Date.now() - new Date(app.updated)) / 86400000) <= n,
|
||||
minAndroidVersion: (app, n) => [app.androidVersion, n].sort()[0] === app.androidVersion
|
||||
};
|
||||
|
||||
// Global search settings
|
||||
let Query_Conditions, Query_Price, Query_Number;
|
||||
// Default search text display (for reset)
|
||||
let Query_Text = [...document.querySelectorAll('.search__settings span')].map(el => el.textContent);
|
||||
let Query_Set = [...document.querySelectorAll('.search__setting')].map(el => el.classList.contains('search__setting--set'));
|
||||
|
||||
let resetSettings = () => {
|
||||
// Reset search settings
|
||||
Query_Conditions = [_c.adFree, _c.inAppFree];
|
||||
Query_Price = 'free';
|
||||
Query_Number = 25;
|
||||
document.querySelector('.search__settings').reset();
|
||||
// Reset default text display
|
||||
document.querySelectorAll('.search__settings span').forEach((el, index) => el.textContent = Query_Text[index]);
|
||||
document.querySelectorAll('.search__setting').forEach((el, index) => {
|
||||
if(Query_Set[index]){
|
||||
el.classList.add('search__setting--set');
|
||||
}
|
||||
else {
|
||||
el.classList.remove('search__setting--set');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Init default query settings
|
||||
resetSettings();
|
||||
|
||||
document.querySelector('.resetSettings').onclick = resetSettings;
|
||||
|
||||
// Reset arrow sort symbols in results table
|
||||
let resetSorting = () => document.querySelectorAll('.output__header th').forEach(el => el.classList.remove('output__header--sortUp', 'output__header--sortDown'));
|
||||
|
||||
let searchInput = document.querySelector('.search__input');
|
||||
let searchButton = document.querySelector('.search__submit');
|
||||
|
||||
searchButton.onclick = () => {
|
||||
// Empty query won't work
|
||||
if(!searchInput.value) return;
|
||||
// Time search
|
||||
let startTime = Date.now();
|
||||
let outputMessage = document.querySelector('.output__message');
|
||||
// Query settings
|
||||
let query = { name: searchInput.value, number: Query_Number, price: Query_Price, conditions: Query_Conditions };
|
||||
// Let user know that I'm searching
|
||||
outputMessage.innerHTML = '<div class="spinner" uk-spinner="ratio: 0.75"></div></div>Searching<span class="loading-dots"></span>';
|
||||
// Reset and hide previous search elements
|
||||
resetSorting();
|
||||
document.querySelectorAll('.output__tabs, .output__tables').forEach(el => el.style.display = 'none');
|
||||
document.querySelector('.output__data').innerHTML = '';
|
||||
document.querySelector('.output2__header').innerHTML = '<tr><th>Permissions \\ Apps</th></tr>';
|
||||
document.querySelector('.output2__data').innerHTML = '';
|
||||
// Send query
|
||||
search(query, res => {
|
||||
if(res.length === 0){
|
||||
return outputMessage.textContent = 'No result.';
|
||||
}
|
||||
// Calculate elapsed time
|
||||
let elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||
/*
|
||||
Show results
|
||||
*/
|
||||
// Message
|
||||
outputMessage.textContent = `Results : ${res.length} items (${(res.length / query.number * 100).toFixed(2)}% of ${query.number} apps), elapsed time : ${elapsedTime}s`;
|
||||
let displayedPermissions = [];
|
||||
res.forEach(app => {
|
||||
// Show all permissions
|
||||
app.permissions.forEach(permission => {
|
||||
let p = permission.permission;
|
||||
let d = permission.description;
|
||||
if(displayedPermissions.indexOf(p) === -1){
|
||||
displayedPermissions.push(p);
|
||||
document.querySelector('.output2__data').innerHTML += `<tr class="output2__permission"><th class="output2__permission__name" uk-tooltip="title: ${d}; pos: right">${p}</th></tr>`;
|
||||
}
|
||||
});
|
||||
document.querySelector('.output2__header tr').innerHTML += `<th class="output2__app">${app.title}</th>`;
|
||||
// Show apps details
|
||||
document.querySelector('.output__data').innerHTML += `<tr class="output__item" data-link="${app.url}"><td class="output__data__name"><img class="output__icon" src="${app.icon}" alt="${app.title}">${app.title}</td><td class="output__data__description">${app.summary}</td><td class="output__data__installs">${app.minInstalls.toLocaleString()}</td><td class="output__data__score">${app.score.toFixed(1)}</td><td class="output__data__size">${app.size.endsWith('k') ? `0.${app.size.slice(0, -1)}M` : app.size}</td><td class="output__data__version">${app.version}</td><td class="output__data__last-update" data-date=${app.updated}">${new Date(app.updated).toDateString()}</td><td class="output__data__price">${app.priceText}</td><td class="output__data__ads">${app.adSupported}</td><td class="output__data__IAP">${app.offersIAP}</td><td class="output__data__android-version">${app.androidVersion}</td><td class="output__data__permissions">${app.permissions.length}</td></tr>`;
|
||||
});
|
||||
res.forEach(app => {
|
||||
// Show permissions per app
|
||||
for(let i = 0; i < displayedPermissions.length; i++){
|
||||
document.querySelectorAll('.output2__permission')[i].innerHTML += `<td>${app.permissions.map(p => p.permission).indexOf(displayedPermissions[i]) !== -1 ? '•' : ''}</td>`;
|
||||
}
|
||||
});
|
||||
// Display tables
|
||||
document.querySelectorAll('.output__tabs, .output__tables').forEach(el => el.style.display = '');
|
||||
// Open link on click
|
||||
document.querySelectorAll('.output__item').forEach(el => {
|
||||
let d = false;
|
||||
el.onclick = () => {
|
||||
if(window.getSelection().toString().length > 0) return;
|
||||
setTimeout(() => {
|
||||
if(!d) window.open(el.getAttribute('data-link'));
|
||||
}, 200);
|
||||
};
|
||||
el.ondblclick = () => d = true;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
searchInput.onkeydown = e => {
|
||||
if(e.key === 'Enter') searchButton.click();
|
||||
};
|
||||
|
||||
searchInput.focus();
|
||||
|
||||
let inputs = document.querySelectorAll('.search__settings input, .search__settings select');
|
||||
inputs.forEach(el => el.oninput = () => {
|
||||
/*
|
||||
Set query conditions from user input
|
||||
*/
|
||||
let filter = el.id;
|
||||
Query_Conditions = [];
|
||||
inputs.forEach(el => {
|
||||
if([el.min, 'on', ''].indexOf(el.value) === -1 || el.checked){
|
||||
let filter = el.id;
|
||||
switch(filter){
|
||||
case 'maxPrice':
|
||||
case 'minScore':
|
||||
case 'maxSize':
|
||||
case 'maxUpdatedDays':
|
||||
case 'minAndroidVersion':
|
||||
Query_Conditions.push(app => _c[filter](app, parseFloat(el.value)));
|
||||
break;
|
||||
case 'adFree':
|
||||
case 'inAppFree':
|
||||
Query_Conditions.push(_c[filter]);
|
||||
break;
|
||||
case 'minInstalls':
|
||||
let range = [0, 1, 1000, 10000, 100000, 1000000, 10000000];
|
||||
Query_Conditions.push(app => _c[filter](app, range[parseInt(el.value)]));
|
||||
break;
|
||||
}
|
||||
el.parentElement.classList.add('search__setting--set');
|
||||
}
|
||||
else {
|
||||
el.parentElement.classList.remove('search__setting--set')
|
||||
}
|
||||
});
|
||||
/*
|
||||
Display proper conditions text from user input
|
||||
*/
|
||||
Query_Number = Math.floor(Query_Conditions.length * 7.5);
|
||||
if(el.type === 'range'){
|
||||
let text = document.querySelector(`.${filter}-text`);
|
||||
let suffix = '';
|
||||
let minText = 'min';
|
||||
let maxText = 'max';
|
||||
if(filter === 'maxSize') suffix = 'M';
|
||||
if(filter === 'maxPrice'){
|
||||
minText = 'free';
|
||||
suffix = '$';
|
||||
}
|
||||
if(filter === 'maxPrice'){
|
||||
if(el.value === '0')
|
||||
Query_Price = 'free';
|
||||
else
|
||||
Query_Price = 'all';
|
||||
}
|
||||
if(filter === 'minInstalls')
|
||||
maxText = '10M+';
|
||||
if(el.value === el.min)
|
||||
text.textContent = minText;
|
||||
else if(el.value === el.max)
|
||||
text.textContent = maxText;
|
||||
else {
|
||||
if(filter === 'minInstalls'){
|
||||
let range = ['0', '1', '1k', '10k', '100k', '1M', '10M'];
|
||||
return text.textContent = range[parseInt(el.value)];
|
||||
}
|
||||
text.textContent = `${el.value}${suffix}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
Sort
|
||||
*/
|
||||
|
||||
document.querySelectorAll('.output__header th').forEach(el => el.onclick = () => {
|
||||
// App description and version cannot be sorted
|
||||
if(['output__header__description', 'output__header__version'].indexOf(el.className) !== -1) return;
|
||||
// Reset and re-sort
|
||||
resetSorting();
|
||||
let items = [...document.querySelectorAll('.output__item')];
|
||||
let sortedItems = items.slice(0);
|
||||
let getItem = item => item.querySelector(`.output__data__${el.className.split('__').slice(-1)[0]}`).textContent;
|
||||
if(el.className === 'output__header__last-update'){
|
||||
// Use UNIX timestamp instead of interpreted date string
|
||||
getItem = item => item.querySelector(`.output__data__${el.className.split('__').slice(-1)[0]}`).getAttribute('data-date');
|
||||
}
|
||||
switch(el.className){
|
||||
// Different comparison function for each data type
|
||||
case 'output__header__name':
|
||||
sortedItems.sort((a, b) => getItem(a) < getItem(b) ? -1 : 1);
|
||||
break;
|
||||
case 'output__header__installs':
|
||||
sortedItems.sort((a, b) => parseInt(getItem(a).replace(/\s/g, '')) - parseInt(getItem(b).replace(/\s/g, '')));
|
||||
break;
|
||||
case 'output__header__score':
|
||||
case 'output__header__permissions':
|
||||
sortedItems.sort((a, b) => getItem(a) - getItem(b));
|
||||
break;
|
||||
case 'output__header__size':
|
||||
case 'output__header__android-version':
|
||||
sortedItems.sort((a, b) => getItem(a).slice(0, -1) - getItem(b).slice(0, -1));
|
||||
break;
|
||||
case 'output__header__last-update':
|
||||
sortedItems.sort((a, b) => parseInt(getItem(a)) - parseInt(getItem(b)));
|
||||
break;
|
||||
case 'output__header__price':
|
||||
sortedItems = [].concat(items.filter(el => getItem(el) === 'Free'),
|
||||
items.filter(el => getItem(el) !== 'Free').sort((a, b) => [getItem(a), getItem(b)].sort()[0] === getItem(a) ? -1 : 1));
|
||||
break;
|
||||
case 'output__header__ads':
|
||||
case 'output__header__IAP':
|
||||
sortedItems.sort(a => getItem(a) === 'yes' ? 1 : -1);
|
||||
break;
|
||||
}
|
||||
// Display proper sort symbol
|
||||
if(JSON.stringify(items.map(item => getItem(item))) === JSON.stringify(sortedItems.map(item => getItem(item)))){
|
||||
sortedItems.reverse();
|
||||
el.classList.add('output__header--sortUp');
|
||||
}
|
||||
else {
|
||||
el.classList.add('output__header--sortDown');
|
||||
}
|
||||
// Hide all items
|
||||
items.forEach(item => item.parentNode.removeChild(item));
|
||||
// Unhide all items in proper sort order
|
||||
for(let item of sortedItems){
|
||||
document.querySelector('.output__data').appendChild(item);
|
||||
}
|
||||
});
|
||||
8
public/assets/js/js.cookie.min.js
vendored
Normal file
8
public/assets/js/js.cookie.min.js
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Minified by jsDelivr using UglifyJS v3.4.4.
|
||||
* Original file: /npm/js-cookie@2.2.0/src/js.cookie.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
!function(e){var n=!1;if("function"==typeof define&&define.amd&&(define(e),n=!0),"object"==typeof exports&&(module.exports=e(),n=!0),!n){var o=window.Cookies,t=window.Cookies=e();t.noConflict=function(){return window.Cookies=o,t}}}(function(){function g(){for(var e=0,n={};e<arguments.length;e++){var o=arguments[e];for(var t in o)n[t]=o[t]}return n}return function e(l){function C(e,n,o){var t;if("undefined"!=typeof document){if(1<arguments.length){if("number"==typeof(o=g({path:"/"},C.defaults,o)).expires){var r=new Date;r.setMilliseconds(r.getMilliseconds()+864e5*o.expires),o.expires=r}o.expires=o.expires?o.expires.toUTCString():"";try{t=JSON.stringify(n),/^[\{\[]/.test(t)&&(n=t)}catch(e){}n=l.write?l.write(n,e):encodeURIComponent(String(n)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),e=(e=(e=encodeURIComponent(String(e))).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent)).replace(/[\(\)]/g,escape);var i="";for(var c in o)o[c]&&(i+="; "+c,!0!==o[c]&&(i+="="+o[c]));return document.cookie=e+"="+n+i}e||(t={});for(var a=document.cookie?document.cookie.split("; "):[],s=/(%[0-9A-Z]{2})+/g,f=0;f<a.length;f++){var p=a[f].split("="),d=p.slice(1).join("=");this.json||'"'!==d.charAt(0)||(d=d.slice(1,-1));try{var u=p[0].replace(s,decodeURIComponent);if(d=l.read?l.read(d,u):l(d,u)||d.replace(s,decodeURIComponent),this.json)try{d=JSON.parse(d)}catch(e){}if(e===u){t=d;break}e||(t[u]=d)}catch(e){}}return t}}return(C.set=C).get=function(e){return C.call(C,e)},C.getJSON=function(){return C.apply({json:!0},[].slice.call(arguments))},C.defaults={},C.remove=function(e,n){C(e,"",g(n,{expires:-1}))},C.withConverter=e,C}(function(){})});
|
||||
//# sourceMappingURL=/sm/31d5cd1b58ce5e6231e4ea03a69b2801a53e76e98152bc29dc82a494ed0a1ee6.map
|
||||
32
public/assets/js/main.js
Normal file
32
public/assets/js/main.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Socket.IO connection
|
||||
const socket = io();
|
||||
|
||||
// Animated loading dots
|
||||
setInterval(() => document.querySelectorAll('.loading-dots').forEach(el => el.textContent.length < 3 ? el.textContent += '.' : el.textContent = ''), 500);
|
||||
|
||||
|
||||
/*
|
||||
Dark mode
|
||||
*/
|
||||
let toggleDark = () => {
|
||||
// Toggle UIkit properties
|
||||
['uk-light', 'uk-background-secondary'].forEach(Class => [document.body, document.querySelector('.uk-modal-body')].forEach(el => el.classList.toggle(Class)));
|
||||
// Toggle dark button text
|
||||
let darkButton = document.querySelector('.dark');
|
||||
darkButton.textContent = darkButton.textContent === 'Dark' ? 'White' : 'Dark';
|
||||
};
|
||||
|
||||
document.querySelector('.dark').onclick = () => {
|
||||
// Save white/dark state
|
||||
if(Cookies.get('dark') === 'true'){
|
||||
Cookies.set('dark', 'false');
|
||||
}
|
||||
else {
|
||||
Cookies.set('dark', 'true');
|
||||
}
|
||||
// Toggle
|
||||
toggleDark();
|
||||
};
|
||||
|
||||
// Restore saved dark style
|
||||
if(Cookies.get('dark') === 'true') toggleDark();
|
||||
3
public/assets/js/uikit-icons.min.js
vendored
Normal file
3
public/assets/js/uikit-icons.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3
public/assets/js/uikit.min.js
vendored
Normal file
3
public/assets/js/uikit.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in a new issue