stable #1

Merged
KaKi87 merged 14 commits from 1.0-rc1 into master 2018-10-23 04:57:05 +02:00
5 changed files with 344 additions and 0 deletions
Showing only changes of commit 3ce82e66a2 - Show all commits

298
public/assets/js/index.js Normal file
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

3
public/assets/js/uikit.min.js vendored Normal file

File diff suppressed because one or more lines are too long