GHP publish

This commit is contained in:
ace
2021-01-09 21:06:20 +03:00
commit 380dbf855f
68 changed files with 10903 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="en" ng-app="playmaker">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Playmaker</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp"
crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css"
integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU"
crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.5/angular.min.js"></script>
<link href="/static/app.css" rel="stylesheet">
</head>
<body>
<div ng-controller="navbar" class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse"
data-target=".navbar-responsive-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button> <a class="navbar-brand">Playmaker</a>
</div>
<div class="navbar-collapse collapse navbar-responsive-collapse">
<ul class="nav navbar-nav">
<li ng-class="{active: path === '/'}">
<a href="/#!/" id="app-page-link">
<i class="fab fa-android" aria-hidden="true"></i> Apps
</a>
</li>
<li ng-class="{active: path === '/search'}">
<a href="/#!/search" id="search-page-link">
<i class="fas fa-search" aria-hidden="true"></i> Search
</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href=
"https://github.com/NoMore201/playmaker/releases/tag/v0.6.4">v0.6.4</a>
</li>
<li>
<a href="https://github.com/NoMore201/playmaker">
<i class="fab fa-github" aria-hidden="true"></i> Source
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="notification-panel" ng-controller="notify">
<script type="text/ng-template" id="alert.html">
<div ng-transclude></div>
</script>
<div uib-alert="" ng-repeat="alert in alerts" ng-class=
"'alert-' + (alert.type || 'warning')" close="closeAlert($index)"
dismiss-on-timeout="3000">
{{alert.msg}}
</div>
</div>
<div ng-view=""></div>
<!-- angular libraries -->
<script src="/static/js/angular-route.min.js"></script>
<script src="/static/js/angular-animate.min.js"></script>
<script src="/static/js/angular-touch.min.js"></script>
<script src="/static/js/ui-bootstrap.min.js"></script>
<script src="/static/js/additional.js"></script>
<!-- crypto library -->
<script src="/static/js/crypto-js.js"></script>
<script src="/static/app.module.js"></script>
<script src="/static/app.service.js"></script>
<script src="/static/app.controller.js"></script>
</body>
</html>

View File

@@ -0,0 +1,151 @@
import os
import tornado
from tornado import web
from tornado.concurrent import run_on_executor
from tornado.web import MissingArgumentError
from concurrent.futures import ThreadPoolExecutor
app_dir = os.path.dirname(os.path.realpath(__file__))
static_dir = os.path.join(app_dir, 'static')
fdroid_instance = {}
def createServer(service):
class HomeHandler(web.RequestHandler):
def get(self):
with open(app_dir + '/index.html', 'r') as f:
self.write(f.read())
class ApiHandler(web.RequestHandler):
executor = ThreadPoolExecutor()
@run_on_executor
def get_apps(self):
return service.get_apps()
@run_on_executor
def get_last_fdroid_update(self):
return service.get_last_fdroid_update()
@run_on_executor
def search(self):
try:
keyword = self.get_argument('search')
except MissingArgumentError:
return None
return service.search(keyword)
@run_on_executor
def login(self):
data = tornado.escape.json_decode(self.request.body)
service.set_encoded_credentials(data.get('email'), data.get('password'))
return service.login()
@run_on_executor
def download(self):
data = tornado.escape.json_decode(self.request.body)
if data.get('download') is None:
return None
return service.download_selection(data['download'])
@run_on_executor
def check(self):
return service.check_local_apks()
@run_on_executor
def update_state(self):
service.update_state()
@run_on_executor
def remove_app(self, app):
return service.remove_local_app(app)
@run_on_executor
def update_fdroid(self):
return service.fdroid_update()
@tornado.gen.coroutine
def get(self, path):
if path == 'apps':
apps = yield self.get_apps()
self.write(apps)
elif path == 'search':
apps = yield self.search()
if apps is not None:
self.write(apps)
else:
self.clear()
self.set_status(400, 'You should supply a valid search query')
elif path == 'fdroid':
result = yield self.get_last_fdroid_update()
self.write(result)
else:
self.set_status(404)
self.finish()
@tornado.gen.coroutine
def post(self, path):
if path == 'download':
result = yield self.download()
if result is None:
self.clear()
self.set_status(400)
else:
self.write(result)
elif path == 'check':
result = yield self.check()
self.write(result)
elif path == 'login':
result = yield self.login()
self.write(result)
if result['status'] == 'SUCCESS' and result['message'] == 'OK':
self.update_state()
elif path == 'fdroid':
global fdroid_instance
if fdroid_instance != {}:
self.write({'status': 'PENDING'})
else:
fdroid_instance = self
result = yield self.update_fdroid()
self.write(result)
fdroid_instance = {}
else:
self.set_status(404)
self.finish()
@tornado.gen.coroutine
def delete(self, path):
if path == 'delete':
data = tornado.escape.json_decode(self.request.body)
if data.get('delete') is None:
self.clear()
self.set_status(400)
else:
result = yield self.remove_app(data['delete'])
self.write(result)
else:
self.set_status(404)
self.finish()
if service.fdroid:
app = web.Application([
(r'/', HomeHandler),
(r'/api/(.*?)/?', ApiHandler),
(r'/fdroid/(.*)', web.StaticFileHandler, {'path': service.download_path}),
(r'/static/(.*)', web.StaticFileHandler, {'path': static_dir}),
(r'/views/(.*)', web.StaticFileHandler, {'path': app_dir + '/views'}),
], debug=False)
else:
app = web.Application([
(r'/', HomeHandler),
(r'/api/(.*?)/?', ApiHandler),
(r'/static/(.*)', web.StaticFileHandler, {'path': static_dir}),
(r'/views/(.*)', web.StaticFileHandler, {'path': app_dir + '/views'}),
], debug=False)
# overwrite settings
app.settings['static_path'] = ''
return app

View File

@@ -0,0 +1,385 @@
from gpapi.googleplay import GooglePlayAPI, LoginError, RequestError, SecurityCheckError
from pyaxmlparser import APK
from subprocess import Popen, PIPE
import base64
import os
import sys
import concurrent.futures
import locale as locale_service
from datetime import datetime as dt
NOT_LOGGED_IN_ERR = 'Not logged in'
WRONG_CREDENTIALS_ERR = 'Wrong credentials'
SESSION_EXPIRED_ERR = 'Session tokens expired, re-login needed'
FDROID_ERR = 'Error while executing fdroidserver tool'
def makeError(message):
return {'status': 'ERROR',
'message': message}
def get_details_from_apk(apk, downloadPath, service):
if apk is not None:
filepath = os.path.join(downloadPath, apk)
try:
a = APK(filepath)
except Exception as e:
print(e)
return None
print('Fetching details for %s' % a.package)
try:
details = service.details(a.package)
details['filename'] = apk
details['versionCode'] = int(a.version_code)
except RequestError as e:
print('Cannot fetch information for %s' % a.package)
print('Extracting basic information from package...')
return {'docid': a.package,
'filename': apk,
'versionCode': int(a.version_code),
'title': a.application}
print('Added %s to cache' % details['docid'])
return details
class Play(object):
def __init__(self, debug=True, fdroid=False):
self.currentSet = []
self.totalNumOfApps = 0
self.debug = debug
self.fdroid = fdroid
self.firstRun = True
self.loggedIn = False
self._email = None
self._passwd = None
self._gsfId = None
self._token = None
self._last_fdroid_update = None
# configuring download folder
if self.fdroid:
self.download_path = os.path.join(os.getcwd(), 'repo')
else:
self.download_path = os.getcwd()
# configuring fdroid data
if self.fdroid:
self.fdroid_exe = 'fdroid'
self.fdroid_path = os.getcwd()
self.fdroid_init()
# language settings
locale = os.environ.get('LANG_LOCALE')
if locale is None:
locale = locale_service.getdefaultlocale()[0]
timezone = os.environ.get('LANG_TIMEZONE')
if timezone is None:
timezone = 'Europe/Berlin'
device = os.environ.get('DEVICE_CODE')
if device is None:
self.service = GooglePlayAPI(locale, timezone)
else:
self.service = GooglePlayAPI(locale, timezone,
device_codename=device)
def fdroid_init(self):
found = False
for path in os.environ['PATH'].split(':'):
exe = os.path.join(path, self.fdroid_exe)
if os.path.isfile(exe):
found = True
break
if not found:
print('Please install fdroid')
sys.exit(1)
elif os.path.isfile('config.py'):
print('Repo already initalized, skipping init')
else:
p = Popen([self.fdroid_exe, 'init', '-v'], stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
sys.stderr.write("error initializing fdroid repository " +
stderr.decode('utf-8'))
sys.exit(1)
# backup config.py
if self.debug:
print('Checking config.py file')
with open('config.py', 'r') as config_file:
content = config_file.readlines()
with open('config.py', 'w') as config_file:
# copy all the original content of config.py
# if the file was not modified with custom values, do it
modified = False
for line in content:
if '# playmaker' in line:
modified = True
config_file.write(line)
if not modified:
if self.debug:
print('Appending playmaker data to config.py')
config_file.write('\n# playmaker\nrepo_name = "playmaker"\n'
'repo_description = "repository managed with '
'playmaker https://github.com/NoMore201/playmaker"\n')
# ensure all folder and files are setup
p = Popen([self.fdroid_exe, 'update', '--create-key', '-v'], stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
print('Skipping fdroid update')
else:
print('Fdroid repo initialized successfully')
def get_last_fdroid_update(self):
if not self.loggedIn:
return {'status': 'UNAUTHORIZED'}
return {'status': 'SUCCESS',
'message': str(self._last_fdroid_update)}
def fdroid_update(self):
if not self.loggedIn:
return {'status': 'UNAUTHORIZED'}
if self.fdroid:
try:
p = Popen([self.fdroid_exe, 'update', '-c', '--clean'],
stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
sys.stderr.write("error updating fdroid repository " +
stderr.decode('utf-8'))
return makeError(FDROID_ERR)
else:
print('Fdroid repo updated successfully')
self._last_fdroid_update = dt.today().replace(microsecond=0)
return {'status': 'SUCCESS'}
except Exception as e:
return makeError(FDROID_ERR)
else:
return {'status': 'SUCCESS'}
def get_apps(self):
if not self.loggedIn:
return {'status': 'UNAUTHORIZED'}
if self.firstRun:
return {'status': 'PENDING',
'total': self.totalNumOfApps,
'current': len(self.currentSet)}
return {'status': 'SUCCESS',
'message': sorted(self.currentSet, key=lambda k: k['title'])}
def set_encoded_credentials(self, email, password):
self._email = base64.b64decode(email).decode('utf-8')
self._passwd = base64.b64decode(password).decode('utf-8')
def set_credentials(self, email, password):
self._email = email
self._passwd = password
def set_token_credentials(self, gsfId, token):
self._gsfId = int(gsfId, 16)
self._token = token
def has_credentials(self):
passwd_credentials = self._email is not None and self._passwd is not None
token_credentials = self._gsfId is not None and self._token is not None
return passwd_credentials or token_credentials
def login(self):
if self.loggedIn:
return {'status': 'SUCCESS', 'securityCheck': False, 'message': 'OK'}
try:
if not self.has_credentials():
raise LoginError("missing credentials")
self.service.login(self._email,
self._passwd,
self._gsfId,
self._token)
self.loggedIn = True
return {'status': 'SUCCESS', 'securityCheck': False, 'message': 'OK'}
except LoginError as e:
print('LoginError: {0}'.format(e))
self.loggedIn = False
return {'status': 'ERROR',
'securityCheck': False,
'message': 'Wrong credentials'}
except SecurityCheckError as e:
print('SecurityCheckError: {0}'.format(e))
self.loggedIn = False
return {'status': 'ERROR',
'securityCheck': True,
'message': 'Need security check'}
except RequestError as e:
# probably tokens are invalid, so it is better to
# invalidate them
print('RequestError: {0}'.format(e))
self.loggedIn = False
return {'status': 'ERROR',
'securityCheck': False,
'message': 'Request error, probably invalid token'}
def update_state(self):
if not self.loggedIn:
return {'status': 'UNAUTHORIZED'}
print('Updating cache')
with concurrent.futures.ProcessPoolExecutor() as executor:
# get application ids from apk files
apkFiles = [apk for apk in os.listdir(self.download_path)
if os.path.splitext(apk)[1] == '.apk']
self.totalNumOfApps = len(apkFiles)
if self.totalNumOfApps != 0:
future_to_app = [executor.submit(get_details_from_apk,
a,
self.download_path,
self.service)
for a in apkFiles]
for future in concurrent.futures.as_completed(future_to_app):
app = future.result()
if app is not None:
self.currentSet.append(app)
print('Cache correctly initialized')
self.firstRun = False
def insert_app_into_state(self, newApp):
found = False
result = list(filter(lambda x: x['docid'] == newApp['docid'],
self.currentSet))
if len(result) > 0:
found = True
if self.debug:
print('%s is already cached, updating..' % newApp['docid'])
i = self.currentSet.index(result[0])
self.currentSet[i] = newApp
if not found:
if self.debug:
print('Adding %s into cache..' % newApp['docid'])
self.currentSet.append(newApp)
def search(self, appName, numItems=15):
if not self.loggedIn:
return {'status': 'UNAUTHORIZED'}
try:
apps = self.service.search(appName)
except RequestError as e:
print(e)
self.loggedIn = False
return {'status': 'ERROR',
'message': SESSION_EXPIRED_ERR}
except LoginError as e:
print(SESSION_EXPIRED_ERR)
self.loggedIn = False
except IndexError as e:
print(SESSION_EXPIRED_ERR)
self.loggedIn = False
return {'status': 'SUCCESS',
'message': apps}
def details(self, app):
try:
details = self.service.details(app)
except RequestError:
details = None
return details
def get_bulk_details(self, apksList):
if not self.loggedIn:
return {'status': 'UNAUTHORIZED'}
try:
apps = [self.details(a) for a in apksList]
except LoginError as e:
print(e)
self.loggedIn = False
return apps
def download_selection(self, apps):
if not self.loggedIn:
return {'status': 'UNAUTHORIZED'}
success = []
failed = []
unavail = []
for app in apps:
docid = app.get('docid')
details = self.details(docid)
filename = app.get('filename')
if filename is None:
filename = details.get('docid') + '.apk'
if details is None:
print('Package %s does not exits' % docid)
unavail.append(docid)
continue
print('Downloading %s' % docid)
try:
if details.get('offer')[0].get('micros') == 0:
data_gen = self.service.download(docid, details.get('details').get('appDetails')['versionCode'])
else:
data_gen = self.service.delivery(docid, details.get('details').get('appDetails')['versionCode'])
data_gen = data_gen.get('file').get('data')
except IndexError as exc:
print(exc)
print('Package %s does not exists' % docid)
unavail.append(docid)
except Exception as exc:
print(exc)
print('Failed to download %s' % docid)
failed.append(docid)
else:
filepath = os.path.join(self.download_path, filename)
try:
with open(filepath, 'wb') as apk_file:
for chunk in data_gen:
apk_file.write(chunk)
except IOError as exc:
print('Error while writing %s: %s' % (filename, exc))
failed.append(docid)
details['filename'] = filename
success.append(details)
for x in success:
self.insert_app_into_state(x)
return {'status': 'SUCCESS',
'message': {'success': success,
'failed': failed,
'unavail': unavail}}
def check_local_apks(self):
if not self.loggedIn:
return {'status': 'UNAUTHORIZED'}
if len(self.currentSet) == 0:
print('There is no package')
return {'status': 'SUCCESS',
'message': []}
else:
toUpdate = []
for app in self.currentSet:
details = self.details(app.get('docid'))
#print(details)
if details is None:
print('%s not available in Play Store' % app['docid'])
continue
details['filename'] = app.get('filename')
if self.debug:
print('Checking %s' % app['docid'])
print('%d == %d ?' % (app.get('details').get('appDetails')['versionCode'], details.get('details').get('appDetails')['versionCode']))
if app.get('details').get('appDetails')['versionCode'] != details.get('details').get('appDetails')['versionCode']:
toUpdate.append(details)
return {'status': 'SUCCESS',
'message': toUpdate}
def remove_local_app(self, docid):
if not self.loggedIn:
return {'status': 'UNAUTHORIZED'}
# get app from cache
app = list(filter(lambda x: x['docid'] == docid, self.currentSet))
if len(app) < 1:
return {'status': 'ERROR'}
apkPath = os.path.join(self.download_path, app[0]['filename'])
if os.path.isfile(apkPath):
os.remove(apkPath)
self.currentSet.remove(app[0])
return {'status': 'SUCCESS'}
return {'status': 'ERROR'}

View File

@@ -0,0 +1,28 @@
angular.module('playmaker').controller('notify', [
'$scope',
'global',
function($scope, global) {
$scope.alerts = [];
$scope.closeAlert = function(index) {
$scope.alerts.splice(index, 1);
};
global.addAlert = function(type, msg) {
newAlert = {
type: type,
msg: msg
};
$scope.alerts.push(newAlert);
};
}]);
angular.module('playmaker').controller('navbar', [
'$location',
'$scope',
'$rootScope',
function($location, $scope, $rootScope) {
$rootScope.$on('$routeChangeSuccess', function() {
$scope.path = $location.path();
});
}]);

View File

@@ -0,0 +1,177 @@
/*
* index.html
*/
.navbar-fixed-top {
top:0;
}
.notification-panel {
position: fixed;
right: 30px;
top: 75px;
width: 300px;
z-index: 10;
}
/*
* Login view
*/
.login-body {
margin:0 auto;
max-width: 500px;
}
.loading-login {
max-width: 500px;
margin: 0 auto;
margin-top: 50px;
}
.loading-login-text {
font-size: 20px;
margin: 10px auto;
}
/*
* Apps view
*/
.sidebar {
margin: 0 auto;
margin-top: 71px;
width: 300px;
}
.sidebar-list {
display: flex;
flex-direction: column;
}
.sidebar-list-el {
align-items: center;
display: flex;
flex-direction: row;
height:50px;
justify-content: space-between;
padding-left:15px;
padding-right:15px;
}
.app-container {
margin-top: 30px;
}
.panel-body {
align-items: center;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
overflow: hidden;
white-space: nowrap;
}
.apk-item-title {
display: block;
font-size: 22px;
margin-bottom: 12px;
margin-top:10px;
white-space: nowrap;
}
.apk-info {
margin-left: 20px;
}
.apk-buttons {
display: block;
padding:15px;
text-align:right;
}
.apk-buttons > a {
margin-left: 30px;
}
.apk-progress {
margin:24px 15px 18px 15px;
}
/*
* Search view
*/
.view-container {
max-width: 1000px;
margin:0 auto;
margin-top: 81px;
padding:0 15px;
}
.dl-button {
cursor: pointer;
color: #333333;
}
.dl-button:hover {
color: #333333;
}
.dl-button-disabled {
cursor: default;
pointer-event: none;
color: #BABABA;
}
.dl-button-disabled:hover {
color: #BABABA;
}
#dl-button-td {
width:80px;
}
#search-progress {
margin-top:40px;
margin-left:30px;
margin-right:30px;
}
#search-area {
padding:0 10px;
}
.table {
margin-top:40px;
}
#table-body > tr {
line-height: 70px;
vertical-align: middle;
}
#table-body > tr > td {
vertical-align: middle;
}
@media (min-width: 768px) {
.sidebar {
background-color:#f7f7f7;
bottom:0;
left:0;
margin:0;
padding-top:30px;
position: fixed;
top: 51px;
width: 300px;
}
.app-container {
margin-left:300px;
margin-top: 81px;
max-width: 1200px;
overflow: auto;
}
}

View File

@@ -0,0 +1,347 @@
var app = angular.module('playmaker', [
'ngRoute',
'ui.bootstrap'
]);
app.config(['$locationProvider', '$routeProvider',
function config($locationProvider, $routeProvider) {
$routeProvider.
when('/', {
template: '<app-list></app-list>'
}).
when('/search', {
template: '<search-view></search-view>'
}).
when('/login', {
template: '<login-view></login-view>'
}).
otherwise('/');
}
]).run(['$rootScope', '$location', 'api', 'global',
function ($rootScope, $location, api, global) {
api.getApps(function(response) {
if (response.status === 'SUCCESS') {
global.auth.login();
}
if (response === 'err' || !global.auth.isLoggedIn()) {
$location.path('/login');
} else {
// redirect home
$location.path('/');
}
$rootScope.$on('$routeChangeStart', function (event, next, current) {
if (!global.auth.isLoggedIn() && $location.path() !== '/login') {
event.preventDefault();
$location.path('/login');
} else if (global.auth.isLoggedIn() && $location.path() === '/login') {
// redirect home
event.preventDefault();
$location.path('/');
}
});
});
}
]);
app.component('appList', {
templateUrl: '/views/app.html',
controller: function AppController(api, global, $location) {
var ctrl = this;
ctrl.apps = [];
ctrl.lastFdroidUpdate = 'None';
ctrl.desktop = global.desktop;
ctrl.mobile = global.mobile;
var port = $location.port();
ctrl.baseUrl = $location.protocol() + '://' + $location.host();
if (port !== 80 && port !== 443) {
ctrl.baseUrl += ":" + port.toString();
}
var updateApp = function(app) {
app.updating = true;
api.download(app, function(data) {
if (data === 'err' || data.status === 'ERROR') {
global.addAlert('danger', 'Unable to update ' + app.docid);
app.updating = false;
return;
}
if (data.message.success.length === 0) {
global.addAlert('danger', 'Unable to update ' + app.docid);
app.updating = false;
return;
}
app.versionCode = data.message.success[0].versionCode;
app.updating = false;
});
};
ctrl.check = function() {
global.addAlert('info', 'Checking for updates');
api.check(function(data) {
if (data === 'err') {
global.addAlert('danger', 'Cannot check for updates');
return;
}
if (data.status === 'SUCCESS' && data.message.length === 0) {
global.addAlert('success', 'All apps are up-to-date!');
}
if (data.status === 'SUCCESS' && data.message.length > 0) {
global.addAlert('success', 'Updating ' + data.message.length.toString() + ' apps');
data.message.forEach(function(newApp) {
var oldAppIndex = ctrl.apps.findIndex(function(elem) {
return elem.docid === newApp.docid
});
if (oldAppIndex === -1) return;
updateApp(ctrl.apps[oldAppIndex]);
});
}
});
};
ctrl.delete = function(app) {
api.remove(app.docid, function(data) {
if (data.status === 'SUCCESS') {
var i = ctrl.apps.findIndex(function(elem) {
return elem.docid === app.docid;
});
ctrl.apps.splice(i, 1);
} else {
global.addAlert('danger', 'Unable to delete ' + app.docid);
}
});
};
ctrl.fdroid = function() {
var oldUpdate = ctrl.lastFdroidUpdate;
ctrl.lastFdroidUpdate = 'Pending';
api.fdroidUpdate(function (data) {
if (data === 'err') {
global.addAlert('danger', 'Error updating repository');
ctrl.lastFdroidUpdate = oldUpdate;
return;
}
if (data.status === 'PENDING') {
return;
}
if (data.status === 'SUCCESS') {
api.fdroid(function(data) {
if (data.status !== 'SUCCESS') {
return;
}
ctrl.lastFdroidUpdate = data.message;
});
}
});
};
api.getApps(function(data) {
if (data.status === 'UNAUTHORIZED') {
return;
}
ctrl.apps = data.message.map(function(a) {
if (a.aggregateRating !== undefined) {
roundedStars = Math.floor(a.aggregateRating.starRating);
a.formattedStars = a.aggregateRating.starRating.toFixed(1);
a.starList = [];
for (i = 0; i < 5; i++) {
if (i+1 <= roundedStars){
a.starList.push({index: i, full: true});
} else {
a.starList.push({index: i, full: false});
}
}
}
if (a.image !== undefined) {
a.previewImage = a.image.filter(function(img) {
return img.imageType === 4;
});
}
if (a.details.appDetails.installationSize !== undefined) {
a.formattedSize = a.details.appDetails.installationSize / (1024*1024);
a.formattedSize = a.formattedSize.toFixed(2);
}
if (a.author === undefined) {
a.author = "unknown";
}
if (a.files === undefined) {
a.files = ["unknown"];
}
a.updating = false;
return a;
});
});
api.fdroid(function(data) {
if (data.status !== 'SUCCESS') {
return;
}
ctrl.lastFdroidUpdate = data.message;
});
}
});
app.directive('onEnter', function() {
return function(scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.onEnter);
});
event.preventDefault();
}
});
};
});
app.component('searchView', {
templateUrl: '/views/search.html',
controller: function SearchController($uibModal, api, global) {
var ctrl = this;
ctrl.desktop = global.desktop;
ctrl.mobile = global.mobile;
ctrl.results = [];
ctrl.searching = false;
ctrl.modalOpen = function (item) {
$uibModal.open({
animation: true,
ariaLabelledBy: 'modal-title',
ariaDescribedBy: 'modal-body',
templateUrl: 'myModalContent.html',
controller: function($scope) {
$scope.app = item;
}
});
};
ctrl.search = function(app) {
// no input by the user
if (app === undefined || app === '') return;
ctrl.results = [];
ctrl.searching = true;
api.search(app, function(data) {
if (data === 'err') {
global.addAlert('danger', 'Error while searching');
ctrl.searching = false;
return;
}
if (data.status === 'SUCCESS' && data.message.length === 0) {
global.addAlert('warning', 'No result for "' + app + '"');
ctrl.searching = false;
return;
}
data.message.forEach(function(d) {
d.downloading = false;
d.disabled = false;
});
ctrl.results = data.message[0].child[0].child;
ctrl.searching = false;
});
};
ctrl.download = function(app) {
if (app.disabled) {
return;
}
app.downloading = true;
api.download(app, function(data) {
if (data === 'err') {
app.downloading = false;
global.addAlert('danger', 'Error downloading app');
return;
}
if (data.status === 'SUCCESS') {
if (data.message.success.length === 0) {
app.downloading = false;
global.addAlert('warning', app.docid + ' can\'t be downloaded');
return;
}
}
app.downloading = false;
app.disabled = true;
});
};
}
});
app.component('loginView', {
templateUrl: '/views/login.html',
controller: function LoginController($location, api, global) {
var ctrl = this;
ctrl.current = 0;
ctrl.max = -1;
ctrl.formattedPercent = 0;
ctrl.securityCheck = false;
var polling = function() {
api.getApps(function(response) {
if (response === 'err') {
ctrl.loggingIn = false;
return;
}
if (response.status === 'UNAUTHORIZED') {
return;
}
if (response.status === 'PENDING') {
ctrl.loggingIn = true;
if (response.total !== 0) {
ctrl.max = response.total;
ctrl.current = response.current;
ctrl.formattedPercent = (ctrl.current / ctrl.max)*100;
ctrl.formattedPercent = ctrl.formattedPercent.toFixed(1);
}
}
if (response.status === 'SUCCESS') {
global.auth.login();
$location.path('/');
ctrl.loggingIn = false;
clearInterval(interval);
}
});
};
ctrl.loggingIn = false;
ctrl.badUsername = false;
ctrl.badPassword = false;
polling();
var interval = setInterval(polling, 3000);
ctrl.login = function(user) {
ctrl.badUsername = false;
ctrl.badPassword = false;
if (user.email === '' || user.email === undefined) {
ctrl.badUsername = true;
return;
}
if (user.password === '' || user.password === undefined) {
ctrl.badPassword = true;
return;
}
ctrl.loggingIn = true;
var email = CryptoJS.enc.Utf8.parse(user.email);
var passwd = CryptoJS.enc.Utf8.parse(user.password);
var emailB64 = CryptoJS.enc.Base64.stringify(email);
var passwdB64 = CryptoJS.enc.Base64.stringify(passwd);
api.login(emailB64, passwdB64, function(data) {
if (data.status === 'ERROR') {
global.addAlert('danger', data.message);
ctrl.loggingIn = false;
ctrl.securityCheck = data.securityCheck;
return;
}
});
};
}
});

View File

@@ -0,0 +1,150 @@
angular.module('playmaker').service('global', ['$http', function($http) {
function AuthManager() {
this.loggedIn = false;
this.isLoggedIn = function () {
return this.loggedIn;
};
this.login = function () {
this.loggedIn = !this.loggedIn;
};
}
this.addAlert = {};
this.desktop = false;
this.mobile = false;
this.auth = new AuthManager();
var screenWidth = window.innerWidth;
if (screenWidth < 700) {
this.mobile = true;
} else {
this.desktop = true;
}
}]);
angular.module('playmaker').service('api', ['$http', '$location', 'global', function($http, $location, global) {
function loginHandler(result) {
if (result.data.status === 'ERROR') {
if (result.data.message !== undefined) {
global.addAlert('danger', result.data.message);
} else {
global.addAlert('danger', 'Application error');
}
global.auth.loggedIn = false;
$location.path('/login');
}
}
this.getApps = function(callback) {
$http({
method: 'GET',
url: '/api/apps'
}).then(function success(response) {
callback(response.data);
}, function error(response) {
callback('err');
});
};
this.search = function(app, callback) {
$http({
method: 'GET',
url: '/api/search?search=' + app
}).then(function success(response) {
loginHandler(response);
callback(response.data);
}, function error(response) {
callback('err');
});
};
this.check = function(callback) {
$http.post('/api/check')
.then(function success(response) {
loginHandler(response);
callback(response.data);
}, function error(response) {
callback('err');
});
};
this.download = function(app, callback) {
var requestData = {
download: [app]
};
$http({
method: 'POST',
url: '/api/download',
data: JSON.stringify(requestData)
}).then(function success(response) {
callback(response.data);
}, function error(response) {
callback('err');
});
};
this.remove = function(app, callback) {
var requestData = {
delete: app
};
$http({
method: 'DELETE',
url: '/api/delete',
data: JSON.stringify(requestData)
}).then(function success(response) {
loginHandler(response);
callback(response.data);
}, function error(response) {
callback('err');
});
};
this.fdroid = function(callback) {
$http({
method: 'GET',
url: '/api/fdroid'
}).then(function success(response) {
callback(response.data);
}, function error(response) {
callback('err');
});
};
this.fdroidUpdate = function(callback) {
$http({
method: 'POST',
url: '/api/fdroid'
}).then(function success(response) {
loginHandler(response);
callback(response.data);
}, function error(response) {
callback('err');
});
};
this.login = function(email, password, callback) {
$http({
method: 'POST',
url: '/api/login',
data: JSON.stringify({
email: email,
password: password
})
}).then(function success(response) {
callback(response.data);
}, function error(response) {
callback('err');
});
};
}]);

View File

@@ -0,0 +1,5 @@
$(document).on('click', '.navbar-collapse.in', function(e) {
if($(e.target).is('a')) {
$(this).collapse('hide');
}
});

View File

@@ -0,0 +1,58 @@
/*
AngularJS v1.7.5
(c) 2010-2018 Google, Inc. http://angularjs.org
License: MIT
*/
(function(Y,z){'use strict';function Fa(a,b,c){if(!a)throw Pa("areq",b||"?",c||"required");return a}function Ga(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;Z(a)&&(a=a.join(" "));Z(b)&&(b=b.join(" "));return a+" "+b}function Qa(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function $(a,b,c){var d="";a=Z(a)?a:a&&G(a)&&a.length?a.split(/\s+/):[];s(a,function(a,k){a&&0<a.length&&(d+=0<k?" ":"",d+=c?b+a:a+b)});return d}function Ha(a){if(a instanceof A)switch(a.length){case 0:return a;
case 1:if(1===a[0].nodeType)return a;break;default:return A(va(a))}if(1===a.nodeType)return A(a)}function va(a){if(!a[0])return a;for(var b=0;b<a.length;b++){var c=a[b];if(1===c.nodeType)return c}}function Ra(a,b,c){s(b,function(b){a.addClass(b,c)})}function Sa(a,b,c){s(b,function(b){a.removeClass(b,c)})}function aa(a){return function(b,c){c.addClass&&(Ra(a,b,c.addClass),c.addClass=null);c.removeClass&&(Sa(a,b,c.removeClass),c.removeClass=null)}}function pa(a){a=a||{};if(!a.$$prepared){var b=a.domOperation||
N;a.domOperation=function(){a.$$domOperationFired=!0;b();b=N};a.$$prepared=!0}return a}function ha(a,b){Ia(a,b);Ja(a,b)}function Ia(a,b){b.from&&(a.css(b.from),b.from=null)}function Ja(a,b){b.to&&(a.css(b.to),b.to=null)}function T(a,b,c){var d=b.options||{};c=c.options||{};var f=(d.addClass||"")+" "+(c.addClass||""),k=(d.removeClass||"")+" "+(c.removeClass||"");a=Ta(a.attr("class"),f,k);c.preparationClasses&&(d.preparationClasses=ba(c.preparationClasses,d.preparationClasses),delete c.preparationClasses);
f=d.domOperation!==N?d.domOperation:null;wa(d,c);f&&(d.domOperation=f);d.addClass=a.addClass?a.addClass:null;d.removeClass=a.removeClass?a.removeClass:null;b.addClass=d.addClass;b.removeClass=d.removeClass;return d}function Ta(a,b,c){function d(a){G(a)&&(a=a.split(" "));var c={};s(a,function(a){a.length&&(c[a]=!0)});return c}var f={};a=d(a);b=d(b);s(b,function(a,c){f[c]=1});c=d(c);s(c,function(a,c){f[c]=1===f[c]?null:-1});var k={addClass:"",removeClass:""};s(f,function(c,b){var d,f;1===c?(d="addClass",
f=!a[b]||a[b+"-remove"]):-1===c&&(d="removeClass",f=a[b]||a[b+"-add"]);f&&(k[d].length&&(k[d]+=" "),k[d]+=b)});return k}function K(a){return a instanceof A?a[0]:a}function Ua(a,b,c,d){a="";c&&(a=$(c,"ng-",!0));d.addClass&&(a=ba(a,$(d.addClass,"-add")));d.removeClass&&(a=ba(a,$(d.removeClass,"-remove")));a.length&&(d.preparationClasses=a,b.addClass(a))}function qa(a,b){var c=b?"-"+b+"s":"";ma(a,[na,c]);return[na,c]}function xa(a,b){var c=b?"paused":"",d=ca+"PlayState";ma(a,[d,c]);return[d,c]}function ma(a,
b){a.style[b[0]]=b[1]}function ba(a,b){return a?b?a+" "+b:a:b}function Ka(a,b,c){var d=Object.create(null),f=a.getComputedStyle(b)||{};s(c,function(a,c){var b=f[a];if(b){var L=b.charAt(0);if("-"===L||"+"===L||0<=L)b=Va(b);0===b&&(b=null);d[c]=b}});return d}function Va(a){var b=0;a=a.split(/\s*,\s*/);s(a,function(a){"s"===a.charAt(a.length-1)&&(a=a.substring(0,a.length-1));a=parseFloat(a)||0;b=b?Math.max(a,b):a});return b}function ya(a){return 0===a||null!=a}function La(a,b){var c=M,d=a+"s";b?c+="Duration":
d+=" linear all";return[c,d]}function Ma(a,b,c){s(c,function(c){a[c]=za(a[c])?a[c]:b.style.getPropertyValue(c)})}var M,Aa,ca,Ba;void 0===Y.ontransitionend&&void 0!==Y.onwebkittransitionend?(M="WebkitTransition",Aa="webkitTransitionEnd transitionend"):(M="transition",Aa="transitionend");void 0===Y.onanimationend&&void 0!==Y.onwebkitanimationend?(ca="WebkitAnimation",Ba="webkitAnimationEnd animationend"):(ca="animation",Ba="animationend");var ra=ca+"Delay",Ca=ca+"Duration",na=M+"Delay",Na=M+"Duration",
Pa=z.$$minErr("ng"),Wa={transitionDuration:Na,transitionDelay:na,transitionProperty:M+"Property",animationDuration:Ca,animationDelay:ra,animationIterationCount:ca+"IterationCount"},Xa={transitionDuration:Na,transitionDelay:na,animationDuration:Ca,animationDelay:ra},Da,wa,s,Z,za,sa,Ea,ta,G,R,A,N;z.module("ngAnimate",[],function(){N=z.noop;Da=z.copy;wa=z.extend;A=z.element;s=z.forEach;Z=z.isArray;G=z.isString;ta=z.isObject;R=z.isUndefined;za=z.isDefined;Ea=z.isFunction;sa=z.isElement}).info({angularVersion:"1.7.5"}).directive("ngAnimateSwap",
["$animate",function(a){return{restrict:"A",transclude:"element",terminal:!0,priority:600,link:function(b,c,d,f,k){var e,Q;b.$watchCollection(d.ngAnimateSwap||d["for"],function(b){e&&a.leave(e);Q&&(Q.$destroy(),Q=null);(b||0===b)&&k(function(b,d){e=b;Q=d;a.enter(b,null,c)})})}}}]).directive("ngAnimateChildren",["$interpolate",function(a){return{link:function(b,c,d){function f(a){c.data("$$ngAnimateChildren","on"===a||"true"===a)}var k=d.ngAnimateChildren;G(k)&&0===k.length?c.data("$$ngAnimateChildren",
!0):(f(a(k)(b)),d.$observe("ngAnimateChildren",f))}}}]).factory("$$rAFScheduler",["$$rAF",function(a){function b(a){d=d.concat(a);c()}function c(){if(d.length){for(var b=d.shift(),e=0;e<b.length;e++)b[e]();f||a(function(){f||c()})}}var d,f;d=b.queue=[];b.waitUntilQuiet=function(b){f&&f();f=a(function(){f=null;b();c()})};return b}]).provider("$$animateQueue",["$animateProvider",function(a){function b(a){return{addClass:a.addClass,removeClass:a.removeClass,from:a.from,to:a.to}}function c(a){if(!a)return null;
a=a.split(" ");var b=Object.create(null);s(a,function(a){b[a]=!0});return b}function d(a,b){if(a&&b){var d=c(b);return a.split(" ").some(function(a){return d[a]})}}function f(a,b,c){return e[a].some(function(a){return a(b,c)})}function k(a,b){var c=0<(a.addClass||"").length,d=0<(a.removeClass||"").length;return b?c&&d:c||d}var e=this.rules={skip:[],cancel:[],join:[]};e.join.push(function(a,b){return!a.structural&&k(a)});e.skip.push(function(a,b){return!a.structural&&!k(a)});e.skip.push(function(a,
b){return"leave"===b.event&&a.structural});e.skip.push(function(a,b){return b.structural&&2===b.state&&!a.structural});e.cancel.push(function(a,b){return b.structural&&a.structural});e.cancel.push(function(a,b){return 2===b.state&&a.structural});e.cancel.push(function(a,b){if(b.structural)return!1;var c=a.addClass,f=a.removeClass,k=b.addClass,e=b.removeClass;return R(c)&&R(f)||R(k)&&R(e)?!1:d(c,e)||d(f,k)});this.$get=["$$rAF","$rootScope","$rootElement","$document","$$Map","$$animation","$$AnimateRunner",
"$templateRequest","$$jqLite","$$forceReflow","$$isDocumentHidden",function(c,d,e,C,U,oa,H,u,t,I,da){function ia(a){O.delete(a.target)}function v(){var a=!1;return function(b){a?b():d.$$postDigest(function(){a=!0;b()})}}function ua(a,b,c){var g=[],l=m[c];l&&s(l,function(l){Oa.call(l.node,b)?g.push(l.callback):"leave"===c&&Oa.call(l.node,a)&&g.push(l.callback)});return g}function h(a,b,c){var l=va(b);return a.filter(function(a){return!(a.node===l&&(!c||a.callback===c))})}function q(a,J,w){function e(a,
b,l,g){u(function(){var a=ua(ia,m,b);a.length?c(function(){s(a,function(a){a(h,l,g)});"close"!==l||m.parentNode||D.off(m)}):"close"!==l||m.parentNode||D.off(m)});a.progress(b,l,g)}function I(a){var b=h,c=n;c.preparationClasses&&(b.removeClass(c.preparationClasses),c.preparationClasses=null);c.activeClasses&&(b.removeClass(c.activeClasses),c.activeClasses=null);W(h,n);ha(h,n);n.domOperation();q.complete(!a)}var n=Da(w),h=Ha(a),m=K(h),ia=m&&m.parentNode,n=pa(n),q=new H,u=v();Z(n.addClass)&&(n.addClass=
n.addClass.join(" "));n.addClass&&!G(n.addClass)&&(n.addClass=null);Z(n.removeClass)&&(n.removeClass=n.removeClass.join(" "));n.removeClass&&!G(n.removeClass)&&(n.removeClass=null);n.from&&!ta(n.from)&&(n.from=null);n.to&&!ta(n.to)&&(n.to=null);if(!(B&&m&&fa(m,J,w)&&Ya(m,n)))return I(),q;var x=0<=["enter","move","leave"].indexOf(J),r=da(),P=r||O.get(m);w=!P&&y.get(m)||{};var p=!!w.state;P||p&&1===w.state||(P=!E(m,ia,J));if(P)return r&&e(q,J,"start",b(n)),I(),r&&e(q,J,"close",b(n)),q;x&&F(m);r={structural:x,
element:h,event:J,addClass:n.addClass,removeClass:n.removeClass,close:I,options:n,runner:q};if(p){if(f("skip",r,w)){if(2===w.state)return I(),q;T(h,w,r);return w.runner}if(f("cancel",r,w))if(2===w.state)w.runner.end();else if(w.structural)w.close();else return T(h,w,r),w.runner;else if(f("join",r,w))if(2===w.state)T(h,r,{});else return Ua(t,h,x?J:null,n),J=r.event=w.event,n=T(h,w,r),w.runner}else T(h,r,{});(p=r.structural)||(p="animate"===r.event&&0<Object.keys(r.options.to||{}).length||k(r));if(!p)return I(),
g(m),q;var C=(w.counter||0)+1;r.counter=C;l(m,1,r);d.$$postDigest(function(){h=Ha(a);var c=y.get(m),d=!c,c=c||{},t=0<(h.parent()||[]).length&&("animate"===c.event||c.structural||k(c));if(d||c.counter!==C||!t){d&&(W(h,n),ha(h,n));if(d||x&&c.event!==J)n.domOperation(),q.end();t||g(m)}else J=!c.structural&&k(c,!0)?"setClass":c.event,l(m,2),c=oa(h,J,c.options),q.setHost(c),e(q,J,"start",b(n)),c.done(function(a){I(!a);(a=y.get(m))&&a.counter===C&&g(m);e(q,J,"close",b(n))})});return q}function F(a){a=a.querySelectorAll("[data-ng-animate]");
s(a,function(a){var b=parseInt(a.getAttribute("data-ng-animate"),10),c=y.get(a);if(c)switch(b){case 2:c.runner.end();case 1:y.delete(a)}})}function g(a){a.removeAttribute("data-ng-animate");y.delete(a)}function E(a,b,c){c=C[0].body;var l=K(e),g=a===c||"HTML"===a.nodeName,d=a===l,t=!1,m=O.get(a),h;for((a=A.data(a,"$ngAnimatePin"))&&(b=K(a));b;){d||(d=b===l);if(1!==b.nodeType)break;a=y.get(b)||{};if(!t){var f=O.get(b);if(!0===f&&!1!==m){m=!0;break}else!1===f&&(m=!1);t=a.structural}if(R(h)||!0===h)a=
A.data(b,"$$ngAnimateChildren"),za(a)&&(h=a);if(t&&!1===h)break;g||(g=b===c);if(g&&d)break;if(!d&&(a=A.data(b,"$ngAnimatePin"))){b=K(a);continue}b=b.parentNode}return(!t||h)&&!0!==m&&d&&g}function l(a,b,c){c=c||{};c.state=b;a.setAttribute("data-ng-animate",b);c=(b=y.get(a))?wa(b,c):c;y.set(a,c)}var y=new U,O=new U,B=null,P=d.$watch(function(){return 0===u.totalPendingRequests},function(a){a&&(P(),d.$$postDigest(function(){d.$$postDigest(function(){null===B&&(B=!0)})}))}),m=Object.create(null);U=a.customFilter();
var la=a.classNameFilter();I=function(){return!0};var fa=U||I,Ya=la?function(a,b){var c=[a.getAttribute("class"),b.addClass,b.removeClass].join(" ");return la.test(c)}:I,W=aa(t),Oa=Y.Node.prototype.contains||function(a){return this===a||!!(this.compareDocumentPosition(a)&16)},D={on:function(a,b,c){var l=va(b);m[a]=m[a]||[];m[a].push({node:l,callback:c});A(b).on("$destroy",function(){y.get(l)||D.off(a,b,c)})},off:function(a,b,c){if(1!==arguments.length||G(arguments[0])){var l=m[a];l&&(m[a]=1===arguments.length?
null:h(l,b,c))}else for(l in b=arguments[0],m)m[l]=h(m[l],b)},pin:function(a,b){Fa(sa(a),"element","not an element");Fa(sa(b),"parentElement","not an element");a.data("$ngAnimatePin",b)},push:function(a,b,c,l){c=c||{};c.domOperation=l;return q(a,b,c)},enabled:function(a,b){var c=arguments.length;if(0===c)b=!!B;else if(sa(a)){var l=K(a);if(1===c)b=!O.get(l);else{if(!O.has(l))A(a).on("$destroy",ia);O.set(l,!b)}}else b=B=!!a;return b}};return D}]}]).provider("$$animateCache",function(){var a=0,b=Object.create(null);
this.$get=[function(){return{cacheKey:function(b,d,f,k){var e=b.parentNode;b=[e.$$ngAnimateParentKey||(e.$$ngAnimateParentKey=++a),d,b.getAttribute("class")];f&&b.push(f);k&&b.push(k);return b.join(" ")},containsCachedAnimationWithoutDuration:function(a){return(a=b[a])&&!a.isValid||!1},flush:function(){b=Object.create(null)},count:function(a){return(a=b[a])?a.total:0},get:function(a){return(a=b[a])&&a.value},put:function(a,d,f){b[a]?(b[a].total++,b[a].value=d):b[a]={total:1,value:d,isValid:f}}}}]}).provider("$$animation",
["$animateProvider",function(a){var b=this.drivers=[];this.$get=["$$jqLite","$rootScope","$injector","$$AnimateRunner","$$Map","$$rAFScheduler","$$animateCache",function(a,d,f,k,e,Q,L){function x(a){function b(a){if(a.processed)return a;a.processed=!0;var d=a.domNode,t=d.parentNode;f.set(d,a);for(var h;t;){if(h=f.get(t)){h.processed||(h=b(h));break}t=t.parentNode}(h||c).children.push(a);return a}var c={children:[]},d,f=new e;for(d=0;d<a.length;d++){var da=a[d];f.set(da.domNode,a[d]={domNode:da.domNode,
element:da.element,fn:da.fn,children:[]})}for(d=0;d<a.length;d++)b(a[d]);return function(a){var b=[],c=[],d;for(d=0;d<a.children.length;d++)c.push(a.children[d]);a=c.length;var t=0,f=[];for(d=0;d<c.length;d++){var g=c[d];0>=a&&(a=t,t=0,b.push(f),f=[]);f.push(g);g.children.forEach(function(a){t++;c.push(a)});a--}f.length&&b.push(f);return b}(c)}var C=[],U=aa(a);return function(e,H,u){function t(a){a=a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];s(a,function(a){var c=
a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function I(a){var b=[],c={};s(a,function(a,d){var l=K(a.element),g=0<=["enter","move"].indexOf(a.event),l=a.structural?t(l):[];if(l.length){var f=g?"to":"from";s(l,function(a){var b=a.getAttribute("ng-animate-ref");c[b]=c[b]||{};c[b][f]={animationID:d,element:A(a)}})}else b.push(a)});var d={},g={};s(c,function(c,t){var f=c.from,e=c.to;if(f&&e){var h=a[f.animationID],k=a[e.animationID],E=f.animationID.toString();if(!g[E]){var I=g[E]=
{structural:!0,beforeStart:function(){h.beforeStart();k.beforeStart()},close:function(){h.close();k.close()},classes:da(h.classes,k.classes),from:h,to:k,anchors:[]};I.classes.length?b.push(I):(b.push(h),b.push(k))}g[E].anchors.push({out:f.element,"in":e.element})}else f=f?f.animationID:e.animationID,e=f.toString(),d[e]||(d[e]=!0,b.push(a[f]))});return b}function da(a,b){a=a.split(" ");b=b.split(" ");for(var c=[],d=0;d<a.length;d++){var g=a[d];if("ng-"!==g.substring(0,3))for(var t=0;t<b.length;t++)if(g===
b[t]){c.push(g);break}}return c.join(" ")}function ia(a){for(var c=b.length-1;0<=c;c--){var d=f.get(b[c])(a);if(d)return d}}function v(a,b){function c(a){(a=a.data("$$animationRunner"))&&a.setHost(b)}a.from&&a.to?(c(a.from.element),c(a.to.element)):c(a.element)}function ua(){var a=e.data("$$animationRunner");!a||"leave"===H&&u.$$domOperationFired||a.end()}function h(b){e.off("$destroy",ua);e.removeData("$$animationRunner");U(e,u);ha(e,u);u.domOperation();E&&a.removeClass(e,E);F.complete(!b)}u=pa(u);
var q=0<=["enter","move","leave"].indexOf(H),F=new k({end:function(){h()},cancel:function(){h(!0)}});if(!b.length)return h(),F;var g=Ga(e.attr("class"),Ga(u.addClass,u.removeClass)),E=u.tempClasses;E&&(g+=" "+E,u.tempClasses=null);q&&e.data("$$animatePrepareClasses","ng-"+H+"-prepare");e.data("$$animationRunner",F);C.push({element:e,classes:g,event:H,structural:q,options:u,beforeStart:function(){E=(E?E+" ":"")+"ng-animate";a.addClass(e,E);var b=e.data("$$animatePrepareClasses");b&&a.removeClass(e,
b)},close:h});e.on("$destroy",ua);if(1<C.length)return F;d.$$postDigest(function(){var b=[];s(C,function(a){a.element.data("$$animationRunner")?b.push(a):a.close()});C.length=0;var d=I(b),g=[];s(d,function(a){var b=a.from?a.from.element:a.element,c=u.addClass,d=L.cacheKey(b[0],a.event,(c?c+" ":"")+"ng-animate",u.removeClass);g.push({element:b,domNode:K(b),fn:function(){var b,c=a.close;if(L.containsCachedAnimationWithoutDuration(d))c();else{a.beforeStart();if((a.anchors?a.from.element||a.to.element:
a.element).data("$$animationRunner")){var g=ia(a);g&&(b=g.start)}b?(b=b(),b.done(function(a){c(!a)}),v(a,b)):c()}}})});for(var d=x(g),t=0;t<d.length;t++)for(var f=d[t],e=0;e<f.length;e++){var h=f[e],k=h.element;d[t][e]=h.fn;0===t?k.removeData("$$animatePrepareClasses"):(h=k.data("$$animatePrepareClasses"))&&a.addClass(k,h)}Q(d)});return F}}]}]).provider("$animateCss",["$animateProvider",function(a){this.$get=["$window","$$jqLite","$$AnimateRunner","$timeout","$$animateCache","$$forceReflow","$sniffer",
"$$rAFScheduler","$$animateQueue",function(a,c,d,f,k,e,Q,L,x){function C(d,f,e,x){var v,s="stagger-"+e;0<k.count(e)&&(v=k.get(s),v||(f=$(f,"-stagger"),c.addClass(d,f),v=Ka(a,d,x),v.animationDuration=Math.max(v.animationDuration,0),v.transitionDuration=Math.max(v.transitionDuration,0),c.removeClass(d,f),k.put(s,v,!0)));return v||{}}function U(a){u.push(a);L.waitUntilQuiet(function(){k.flush();for(var a=e(),b=0;b<u.length;b++)u[b](a);u.length=0})}function z(c,d,f,e){d=k.get(f);d||(d=Ka(a,c,Wa),"infinite"===
d.animationIterationCount&&(d.animationIterationCount=1));k.put(f,d,e||0<d.transitionDuration||0<d.animationDuration);c=d;f=c.animationDelay;e=c.transitionDelay;c.maxDelay=f&&e?Math.max(f,e):f||e;c.maxDuration=Math.max(c.animationDuration*c.animationIterationCount,c.transitionDuration);return c}var H=aa(c),u=[];return function(a,b){function e(){v()}function L(){v(!0)}function v(b){if(!(P||la&&m)){P=!0;m=!1;V&&!g.$$skipPreparationClasses&&c.removeClass(a,V);ba&&c.removeClass(a,ba);xa(l,!1);qa(l,!1);
s(y,function(a){l.style[a[0]]=""});H(a,g);ha(a,g);Object.keys(E).length&&s(E,function(a,b){a?l.style.setProperty(b,a):l.style.removeProperty(b)});if(g.onDone)g.onDone();w&&w.length&&a.off(w.join(" "),q);var d=a.data("$$animateCss");d&&(f.cancel(d[0].timer),a.removeData("$$animateCss"));fa&&fa.complete(!b)}}function u(a){p.blockTransition&&qa(l,a);p.blockKeyframeAnimation&&xa(l,!!a)}function h(){fa=new d({end:e,cancel:L});U(N);v();return{$$willAnimate:!1,start:function(){return fa},end:e}}function q(a){a.stopPropagation();
var b=a.originalEvent||a;b.target===l&&(a=b.$manualTimeStamp||Date.now(),b=parseFloat(b.elapsedTime.toFixed(3)),Math.max(a-J,0)>=G&&b>=D&&(la=!0,v()))}function F(){function b(){if(!P){u(!1);s(y,function(a){l.style[a[0]]=a[1]});H(a,g);c.addClass(a,ba);if(p.recalculateTimingStyles){T=l.getAttribute("class")+" "+V;ka=k.cacheKey(l,ja,g.addClass,g.removeClass);r=z(l,T,ka,!1);ga=r.maxDelay;W=Math.max(ga,0);D=r.maxDuration;if(0===D){v();return}p.hasTransitions=0<r.transitionDuration;p.hasAnimations=0<r.animationDuration}p.applyAnimationDelay&&
(ga="boolean"!==typeof g.delay&&ya(g.delay)?parseFloat(g.delay):ga,W=Math.max(ga,0),r.animationDelay=ga,ea=[ra,ga+"s"],y.push(ea),l.style[ea[0]]=ea[1]);G=1E3*W;R=1E3*D;if(g.easing){var e,h=g.easing;p.hasTransitions&&(e=M+"TimingFunction",y.push([e,h]),l.style[e]=h);p.hasAnimations&&(e=ca+"TimingFunction",y.push([e,h]),l.style[e]=h)}r.transitionDuration&&w.push(Aa);r.animationDuration&&w.push(Ba);J=Date.now();var m=G+1.5*R;e=J+m;var h=a.data("$$animateCss")||[],F=!0;if(h.length){var n=h[0];(F=e>n.expectedEndTime)?
f.cancel(n.timer):h.push(v)}F&&(m=f(d,m,!1),h[0]={timer:m,expectedEndTime:e},h.push(v),a.data("$$animateCss",h));if(w.length)a.on(w.join(" "),q);g.to&&(g.cleanupStyles&&Ma(E,l,Object.keys(g.to)),Ja(a,g))}}function d(){var b=a.data("$$animateCss");if(b){for(var c=1;c<b.length;c++)b[c]();a.removeData("$$animateCss")}}if(!P)if(l.parentNode){var e=function(a){if(la)m&&a&&(m=!1,v());else if(m=!a,r.animationDuration)if(a=xa(l,m),m)y.push(a);else{var b=y,c=b.indexOf(a);0<=a&&b.splice(c,1)}},h=0<aa&&(r.transitionDuration&&
0===X.transitionDuration||r.animationDuration&&0===X.animationDuration)&&Math.max(X.animationDelay,X.transitionDelay);h?f(b,Math.floor(h*aa*1E3),!1):b();A.resume=function(){e(!0)};A.pause=function(){e(!1)}}else v()}var g=b||{};g.$$prepared||(g=pa(Da(g)));var E={},l=K(a);if(!l||!l.parentNode||!x.enabled())return h();var y=[],O=a.attr("class"),B=Qa(g),P,m,la,fa,A,W,G,D,R,J,w=[];if(0===g.duration||!Q.animations&&!Q.transitions)return h();var ja=g.event&&Z(g.event)?g.event.join(" "):g.event,Y=ja&&g.structural,
n="",S="";Y?n=$(ja,"ng-",!0):ja&&(n=ja);g.addClass&&(S+=$(g.addClass,"-add"));g.removeClass&&(S.length&&(S+=" "),S+=$(g.removeClass,"-remove"));g.applyClassesEarly&&S.length&&H(a,g);var V=[n,S].join(" ").trim(),T=O+" "+V,O=B.to&&0<Object.keys(B.to).length;if(!(0<(g.keyframeStyle||"").length||O||V))return h();var X,ka=k.cacheKey(l,ja,g.addClass,g.removeClass);if(k.containsCachedAnimationWithoutDuration(ka))return V=null,h();0<g.stagger?(B=parseFloat(g.stagger),X={transitionDelay:B,animationDelay:B,
transitionDuration:0,animationDuration:0}):X=C(l,V,ka,Xa);g.$$skipPreparationClasses||c.addClass(a,V);g.transitionStyle&&(B=[M,g.transitionStyle],ma(l,B),y.push(B));0<=g.duration&&(B=0<l.style[M].length,B=La(g.duration,B),ma(l,B),y.push(B));g.keyframeStyle&&(B=[ca,g.keyframeStyle],ma(l,B),y.push(B));var aa=X?0<=g.staggerIndex?g.staggerIndex:k.count(ka):0;(n=0===aa)&&!g.skipBlocking&&qa(l,9999);var r=z(l,T,ka,!Y),ga=r.maxDelay;W=Math.max(ga,0);D=r.maxDuration;var p={};p.hasTransitions=0<r.transitionDuration;
p.hasAnimations=0<r.animationDuration;p.hasTransitionAll=p.hasTransitions&&"all"===r.transitionProperty;p.applyTransitionDuration=O&&(p.hasTransitions&&!p.hasTransitionAll||p.hasAnimations&&!p.hasTransitions);p.applyAnimationDuration=g.duration&&p.hasAnimations;p.applyTransitionDelay=ya(g.delay)&&(p.applyTransitionDuration||p.hasTransitions);p.applyAnimationDelay=ya(g.delay)&&p.hasAnimations;p.recalculateTimingStyles=0<S.length;if(p.applyTransitionDuration||p.applyAnimationDuration)D=g.duration?parseFloat(g.duration):
D,p.applyTransitionDuration&&(p.hasTransitions=!0,r.transitionDuration=D,B=0<l.style[M+"Property"].length,y.push(La(D,B))),p.applyAnimationDuration&&(p.hasAnimations=!0,r.animationDuration=D,y.push([Ca,D+"s"]));if(0===D&&!p.recalculateTimingStyles)return h();var ba=$(V,"-active");if(null!=g.delay){var ea;"boolean"!==typeof g.delay&&(ea=parseFloat(g.delay),W=Math.max(ea,0));p.applyTransitionDelay&&y.push([na,ea+"s"]);p.applyAnimationDelay&&y.push([ra,ea+"s"])}null==g.duration&&0<r.transitionDuration&&
(p.recalculateTimingStyles=p.recalculateTimingStyles||n);G=1E3*W;R=1E3*D;g.skipBlocking||(p.blockTransition=0<r.transitionDuration,p.blockKeyframeAnimation=0<r.animationDuration&&0<X.animationDelay&&0===X.animationDuration);g.from&&(g.cleanupStyles&&Ma(E,l,Object.keys(g.from)),Ia(a,g));p.blockTransition||p.blockKeyframeAnimation?u(D):g.skipBlocking||qa(l,!1);return{$$willAnimate:!0,end:e,start:function(){if(!P)return A={end:e,cancel:L,resume:null,pause:null},fa=new d(A),U(F),fa}}}}]}]).provider("$$animateCssDriver",
["$$animationProvider",function(a){a.drivers.push("$$animateCssDriver");this.$get=["$animateCss","$rootScope","$$AnimateRunner","$rootElement","$sniffer","$$jqLite","$document",function(a,c,d,f,k,e,Q){function L(a){return a.replace(/\bng-\S+\b/g,"")}function x(a,b){G(a)&&(a=a.split(" "));G(b)&&(b=b.split(" "));return a.filter(function(a){return-1===b.indexOf(a)}).join(" ")}function C(c,e,f){function k(a){var b={},c=K(a).getBoundingClientRect();s(["width","height","top","left"],function(a){var d=c[a];
switch(a){case "top":d+=H.scrollTop;break;case "left":d+=H.scrollLeft}b[a]=Math.floor(d)+"px"});return b}function v(){var c=L(f.attr("class")||""),d=x(c,q),c=x(q,c),d=a(h,{to:k(f),addClass:"ng-anchor-in "+d,removeClass:"ng-anchor-out "+c,delay:!0});return d.$$willAnimate?d:null}function C(){h.remove();e.removeClass("ng-animate-shim");f.removeClass("ng-animate-shim")}var h=A(K(e).cloneNode(!0)),q=L(h.attr("class")||"");e.addClass("ng-animate-shim");f.addClass("ng-animate-shim");h.addClass("ng-anchor");
u.append(h);var F;c=function(){var c=a(h,{addClass:"ng-anchor-out",delay:!0,from:k(e)});return c.$$willAnimate?c:null}();if(!c&&(F=v(),!F))return C();var g=c||F;return{start:function(){function a(){c&&c.end()}var b,c=g.start();c.done(function(){c=null;if(!F&&(F=v()))return c=F.start(),c.done(function(){c=null;C();b.complete()}),c;C();b.complete()});return b=new d({end:a,cancel:a})}}}function z(a,b,c,e){var f=oa(a,N),k=oa(b,N),h=[];s(e,function(a){(a=C(c,a.out,a["in"]))&&h.push(a)});if(f||k||0!==h.length)return{start:function(){function a(){s(b,
function(a){a.end()})}var b=[];f&&b.push(f.start());k&&b.push(k.start());s(h,function(a){b.push(a.start())});var c=new d({end:a,cancel:a});d.all(b,function(a){c.complete(a)});return c}}}function oa(c){var d=c.element,e=c.options||{};c.structural&&(e.event=c.event,e.structural=!0,e.applyClassesEarly=!0,"leave"===c.event&&(e.onDone=e.domOperation));e.preparationClasses&&(e.event=ba(e.event,e.preparationClasses));c=a(d,e);return c.$$willAnimate?c:null}if(!k.animations&&!k.transitions)return N;var H=
Q[0].body;c=K(f);var u=A(c.parentNode&&11===c.parentNode.nodeType||H.contains(c)?c:H);return function(a){return a.from&&a.to?z(a.from,a.to,a.classes,a.anchors):oa(a)}}]}]).provider("$$animateJs",["$animateProvider",function(a){this.$get=["$injector","$$AnimateRunner","$$jqLite",function(b,c,d){function f(c){c=Z(c)?c:c.split(" ");for(var d=[],f={},k=0;k<c.length;k++){var s=c[k],z=a.$$registeredAnimations[s];z&&!f[s]&&(d.push(b.get(z)),f[s]=!0)}return d}var k=aa(d);return function(a,b,d,x){function C(){x.domOperation();
k(a,x)}function z(a,b,d,f,e){switch(d){case "animate":b=[b,f.from,f.to,e];break;case "setClass":b=[b,t,I,e];break;case "addClass":b=[b,t,e];break;case "removeClass":b=[b,I,e];break;default:b=[b,e]}b.push(f);if(a=a.apply(a,b))if(Ea(a.start)&&(a=a.start()),a instanceof c)a.done(e);else if(Ea(a))return a;return N}function A(a,b,d,e,f){var h=[];s(e,function(e){var l=e[f];l&&h.push(function(){var e,f,h=!1,k=function(a){h||(h=!0,(f||N)(a),e.complete(!a))};e=new c({end:function(){k()},cancel:function(){k(!0)}});
f=z(l,a,b,d,function(a){k(!1===a)});return e})});return h}function H(a,b,d,e,f){var h=A(a,b,d,e,f);if(0===h.length){var k,q;"beforeSetClass"===f?(k=A(a,"removeClass",d,e,"beforeRemoveClass"),q=A(a,"addClass",d,e,"beforeAddClass")):"setClass"===f&&(k=A(a,"removeClass",d,e,"removeClass"),q=A(a,"addClass",d,e,"addClass"));k&&(h=h.concat(k));q&&(h=h.concat(q))}if(0!==h.length)return function(a){var b=[];h.length&&s(h,function(a){b.push(a())});b.length?c.all(b,a):a();return function(a){s(b,function(b){a?
b.cancel():b.end()})}}}var u=!1;3===arguments.length&&ta(d)&&(x=d,d=null);x=pa(x);d||(d=a.attr("class")||"",x.addClass&&(d+=" "+x.addClass),x.removeClass&&(d+=" "+x.removeClass));var t=x.addClass,I=x.removeClass,G=f(d),K,v;if(G.length){var M,h;"leave"===b?(h="leave",M="afterLeave"):(h="before"+b.charAt(0).toUpperCase()+b.substr(1),M=b);"enter"!==b&&"move"!==b&&(K=H(a,b,x,G,h));v=H(a,b,x,G,M)}if(K||v){var q;return{$$willAnimate:!0,end:function(){q?q.end():(u=!0,C(),ha(a,x),q=new c,q.complete(!0));
return q},start:function(){function b(c){u=!0;C();ha(a,x);q.complete(c)}if(q)return q;q=new c;var d,f=[];K&&f.push(function(a){d=K(a)});f.length?f.push(function(a){C();a(!0)}):C();v&&f.push(function(a){d=v(a)});q.setHost({end:function(){u||((d||N)(void 0),b(void 0))},cancel:function(){u||((d||N)(!0),b(!0))}});c.chain(f,b);return q}}}}}]}]).provider("$$animateJsDriver",["$$animationProvider",function(a){a.drivers.push("$$animateJsDriver");this.$get=["$$animateJs","$$AnimateRunner",function(a,c){function d(c){return a(c.element,
c.event,c.classes,c.options)}return function(a){if(a.from&&a.to){var b=d(a.from),e=d(a.to);if(b||e)return{start:function(){function a(){return function(){s(d,function(a){a.end()})}}var d=[];b&&d.push(b.start());e&&d.push(e.start());c.all(d,function(a){f.complete(a)});var f=new c({end:a(),cancel:a()});return f}}}else return d(a)}}]}])})(window,window.angular);
//# sourceMappingURL=angular-animate.min.js.map

View File

@@ -0,0 +1,17 @@
/*
AngularJS v1.7.5
(c) 2010-2018 Google, Inc. http://angularjs.org
License: MIT
*/
(function(I,b){'use strict';function z(b,h){var d=[],c=b.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[?*])?/g,function(b,c,h,k){b="?"===k||"*?"===k;k="*"===k||"*?"===k;d.push({name:h,optional:b});c=c||"";return(b?"(?:"+c:c+"(?:")+(k?"(.+?)":"([^/]+)")+(b?"?)?":")")}).replace(/([/$*])/g,"\\$1");h.ignoreTrailingSlashes&&(c=c.replace(/\/+$/,"")+"/*");return{keys:d,regexp:new RegExp("^"+c+"(?:[?#]|$)",h.caseInsensitiveMatch?"i":"")}}function A(b){p&&b.get("$route")}function v(u,h,d){return{restrict:"ECA",
terminal:!0,priority:400,transclude:"element",link:function(c,f,g,l,k){function q(){r&&(d.cancel(r),r=null);m&&(m.$destroy(),m=null);s&&(r=d.leave(s),r.done(function(b){!1!==b&&(r=null)}),s=null)}function C(){var g=u.current&&u.current.locals;if(b.isDefined(g&&g.$template)){var g=c.$new(),l=u.current;s=k(g,function(g){d.enter(g,null,s||f).done(function(d){!1===d||!b.isDefined(w)||w&&!c.$eval(w)||h()});q()});m=l.scope=g;m.$emit("$viewContentLoaded");m.$eval(p)}else q()}var m,s,r,w=g.autoscroll,p=g.onload||
"";c.$on("$routeChangeSuccess",C);C()}}}function x(b,h,d){return{restrict:"ECA",priority:-400,link:function(c,f){var g=d.current,l=g.locals;f.html(l.$template);var k=b(f.contents());if(g.controller){l.$scope=c;var q=h(g.controller,l);g.controllerAs&&(c[g.controllerAs]=q);f.data("$ngControllerController",q);f.children().data("$ngControllerController",q)}c[g.resolveAs||"$resolve"]=l;k(c)}}}var D,E,F,G,y=b.module("ngRoute",[]).info({angularVersion:"1.7.5"}).provider("$route",function(){function u(d,
c){return b.extend(Object.create(d),c)}D=b.isArray;E=b.isObject;F=b.isDefined;G=b.noop;var h={};this.when=function(d,c){var f;f=void 0;if(D(c)){f=f||[];for(var g=0,l=c.length;g<l;g++)f[g]=c[g]}else if(E(c))for(g in f=f||{},c)if("$"!==g.charAt(0)||"$"!==g.charAt(1))f[g]=c[g];f=f||c;b.isUndefined(f.reloadOnUrl)&&(f.reloadOnUrl=!0);b.isUndefined(f.reloadOnSearch)&&(f.reloadOnSearch=!0);b.isUndefined(f.caseInsensitiveMatch)&&(f.caseInsensitiveMatch=this.caseInsensitiveMatch);h[d]=b.extend(f,{originalPath:d},
d&&z(d,f));d&&(g="/"===d[d.length-1]?d.substr(0,d.length-1):d+"/",h[g]=b.extend({originalPath:d,redirectTo:d},z(g,f)));return this};this.caseInsensitiveMatch=!1;this.otherwise=function(b){"string"===typeof b&&(b={redirectTo:b});this.when(null,b);return this};p=!0;this.eagerInstantiationEnabled=function(b){return F(b)?(p=b,this):p};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce","$browser",function(d,c,f,g,l,k,q,p){function m(a){var e=t.current;n=A();(x=
!B&&n&&e&&n.$$route===e.$$route&&(!n.reloadOnUrl||!n.reloadOnSearch&&b.equals(n.pathParams,e.pathParams)))||!e&&!n||d.$broadcast("$routeChangeStart",n,e).defaultPrevented&&a&&a.preventDefault()}function s(){var a=t.current,e=n;if(x)a.params=e.params,b.copy(a.params,f),d.$broadcast("$routeUpdate",a);else if(e||a){B=!1;t.current=e;var c=g.resolve(e);p.$$incOutstandingRequestCount("$route");c.then(r).then(w).then(function(g){return g&&c.then(y).then(function(c){e===t.current&&(e&&(e.locals=c,b.copy(e.params,
f)),d.$broadcast("$routeChangeSuccess",e,a))})}).catch(function(b){e===t.current&&d.$broadcast("$routeChangeError",e,a,b)}).finally(function(){p.$$completeOutstandingRequest(G,"$route")})}}function r(a){var e={route:a,hasRedirection:!1};if(a)if(a.redirectTo)if(b.isString(a.redirectTo))e.path=v(a.redirectTo,a.params),e.search=a.params,e.hasRedirection=!0;else{var d=c.path(),f=c.search();a=a.redirectTo(a.pathParams,d,f);b.isDefined(a)&&(e.url=a,e.hasRedirection=!0)}else if(a.resolveRedirectTo)return g.resolve(l.invoke(a.resolveRedirectTo)).then(function(a){b.isDefined(a)&&
(e.url=a,e.hasRedirection=!0);return e});return e}function w(a){var b=!0;if(a.route!==t.current)b=!1;else if(a.hasRedirection){var g=c.url(),d=a.url;d?c.url(d).replace():d=c.path(a.path).search(a.search).replace().url();d!==g&&(b=!1)}return b}function y(a){if(a){var e=b.extend({},a.resolve);b.forEach(e,function(a,c){e[c]=b.isString(a)?l.get(a):l.invoke(a,null,null,c)});a=z(a);b.isDefined(a)&&(e.$template=a);return g.all(e)}}function z(a){var e,c;b.isDefined(e=a.template)?b.isFunction(e)&&(e=e(a.params)):
b.isDefined(c=a.templateUrl)&&(b.isFunction(c)&&(c=c(a.params)),b.isDefined(c)&&(a.loadedTemplateUrl=q.valueOf(c),e=k(c)));return e}function A(){var a,e;b.forEach(h,function(d,g){var f;if(f=!e){var h=c.path();f=d.keys;var l={};if(d.regexp)if(h=d.regexp.exec(h)){for(var k=1,p=h.length;k<p;++k){var m=f[k-1],n=h[k];m&&n&&(l[m.name]=n)}f=l}else f=null;else f=null;f=a=f}f&&(e=u(d,{params:b.extend({},c.search(),a),pathParams:a}),e.$$route=d)});return e||h[null]&&u(h[null],{params:{},pathParams:{}})}function v(a,
c){var d=[];b.forEach((a||"").split(":"),function(a,b){if(0===b)d.push(a);else{var f=a.match(/(\w+)(?:[?*])?(.*)/),g=f[1];d.push(c[g]);d.push(f[2]||"");delete c[g]}});return d.join("")}var B=!1,n,x,t={routes:h,reload:function(){B=!0;var a={defaultPrevented:!1,preventDefault:function(){this.defaultPrevented=!0;B=!1}};d.$evalAsync(function(){m(a);a.defaultPrevented||s()})},updateParams:function(a){if(this.current&&this.current.$$route)a=b.extend({},this.current.params,a),c.path(v(this.current.$$route.originalPath,
a)),c.search(a);else throw H("norout");}};d.$on("$locationChangeStart",m);d.$on("$locationChangeSuccess",s);return t}]}).run(A),H=b.$$minErr("ngRoute"),p;A.$inject=["$injector"];y.provider("$routeParams",function(){this.$get=function(){return{}}});y.directive("ngView",v);y.directive("ngView",x);v.$inject=["$route","$anchorScroll","$animate"];x.$inject=["$compile","$controller","$route"]})(window,window.angular);
//# sourceMappingURL=angular-route.min.js.map

View File

@@ -0,0 +1,10 @@
/*
AngularJS v1.7.5
(c) 2010-2018 Google, Inc. http://angularjs.org
License: MIT
*/
(function(t,p){'use strict';function q(g,h,s){n.directive(g,["$parse","$swipe",function(a,b){return function(c,e,f){function k(a){if(!d)return!1;var b=Math.abs(a.y-d.y);a=(a.x-d.x)*h;return l&&75>b&&0<a&&30<a&&.3>b/a}var m=a(f[g]),d,l,r=["touch"];p.isDefined(f.ngSwipeDisableMouse)||r.push("mouse");b.bind(e,{start:function(a,b){d=a;l=!0},cancel:function(a){l=!1},end:function(a,b){k(a)&&c.$apply(function(){e.triggerHandler(s);m(c,{$event:b})})}},r)}}])}var n=p.module("ngTouch",[]);n.info({angularVersion:"1.7.5"});
n.factory("$swipe",[function(){function g(a){a=a.originalEvent||a;var b=a.touches&&a.touches.length?a.touches:[a];a=a.changedTouches&&a.changedTouches[0]||b[0];return{x:a.clientX,y:a.clientY}}function h(a,b){var c=[];p.forEach(a,function(a){(a=n[a][b])&&c.push(a)});return c.join(" ")}var n={mouse:{start:"mousedown",move:"mousemove",end:"mouseup"},touch:{start:"touchstart",move:"touchmove",end:"touchend",cancel:"touchcancel"},pointer:{start:"pointerdown",move:"pointermove",end:"pointerup",cancel:"pointercancel"}};
return{bind:function(a,b,c){var e,f,k,m,d=!1;c=c||["mouse","touch","pointer"];a.on(h(c,"start"),function(a){k=g(a);d=!0;f=e=0;m=k;b.start&&b.start(k,a)});var l=h(c,"cancel");if(l)a.on(l,function(a){d=!1;b.cancel&&b.cancel(a)});a.on(h(c,"move"),function(a){if(d&&k){var c=g(a);e+=Math.abs(c.x-m.x);f+=Math.abs(c.y-m.y);m=c;10>e&&10>f||(f>e?(d=!1,b.cancel&&b.cancel(a)):(a.preventDefault(),b.move&&b.move(c,a)))}});a.on(h(c,"end"),function(a){d&&(d=!1,b.end&&b.end(g(a),a))})}}}]);q("ngSwipeLeft",-1,"swipeleft");
q("ngSwipeRight",1,"swiperight")})(window,window.angular);
//# sourceMappingURL=angular-touch.min.js.map

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,61 @@
<div>
<div class="sidebar">
<div class="sidebar-list">
<div class="sidebar-list-el">
Update applications <a ng-click="$ctrl.check()" class=
"btn btn-default sidebar-button">
<i class="fas fa-sync-alt" aria-hidden="true"></i>
</a>
</div>
<div class="sidebar-list-el">
Update fdroid repo <a ng-click="$ctrl.fdroid()" class=
"btn btn-default">
<i class="fab fa-android" aria-hidden="true"></i>
</a>
</div>
<div class="sidebar-list-el">
URL: <code>{{$ctrl.baseUrl}}/fdroid</code>
</div>
<div class="sidebar-list-el">
<strong>Last update:</strong> {{$ctrl.lastFdroidUpdate}}
</div>
</div>
</div>
<div class="app-container">
<div ng-repeat="app in $ctrl.apps" class="col-xs-12 col-md-12 col-lg-6">
<div class="panel panel-default">
<div class="panel-body">
<img ng-if="$ctrl.desktop" width="100" height="100" ng-src=
"{{app.previewImage[0].imageUrl}}">
<div class="apk-info">
<strong><span class=
"apk-item-title">{{app.title}}</span></strong>
<i ng-repeat="val in app.starList" ng-class=
"val.full ? 'fas fa-star' : 'far fa-star'"
aria-hidden="true"></i>
<strong>{{app.formattedStars}}</strong><br>
<span><strong>Developer:</strong> {{app.creator}}</span><br>
<span><strong>Version:</strong> {{app.details.appDetails.versionCode}}</span><br>
<span><strong>Files:</strong> {{app.details.appDetails.file.length}}</span><br>
<span ng-if="app.formattedSize !== undefined"><strong>Size:</strong> {{app.formattedSize}} MB</span>
<span ng-if="app.formattedSize === undefined"><strong>Size:</strong> unknown</span><br>
<span><strong>PackageId:</strong> {{app.docid}}</span><br>
</div>
</div><!-- panel-body -->
<div ng-hide="!app.updating" class="apk-progress">
<div class="progress">
<div class="progress-bar progress-bar-striped active" role=
"progressbar" aria-valuenow="100" aria-valuemin="0"
aria-valuemax="100" style="width:100%">
<span class="sr-only">Updating</span>
</div><!-- progress-bar -->
</div><!-- progress -->
</div><!-- apk-progress -->
<div ng-hide="app.updating" class="apk-buttons">
<a ng-click="$ctrl.delete(app)" class=
"btn btn-raised btn-danger"> delete</a>
</div>
</div><!-- panel -->
</div><!-- main -->
</div>
</div>

View File

@@ -0,0 +1,42 @@
<div class="view-container">
<form>
<div class="login-body">
<h2>First time initialization</h2>
<p>In order to initalize the server, you need to enter your google
credentials. They are sent to the server, which will perform the login
procedure and fetch an authorization token, and then they are
discarded. To secure communucation between client and server you should
configure playmaker with https, like explained <a href=
"https://github.com/NoMore201/playmaker#usage">here</a></p>
<div class="form-group">
<label for="emailInput">Email address</label> <input type="text"
class="form-control" id="emailInput" placeholder="Email" ng-model=
"user.email" uib-tooltip=
"Enter a valid username (with or without '@gmail.com')"
tooltip-enable="$ctrl.badUsername" tooltip-placement="top"
tooltip-is-open="$ctrl.badUsername" ng-disabled="$ctrl.loggingIn">
</div>
<div class="form-group">
<label for="passwordInput">Password</label> <input type="password"
class="form-control" id="passwordInput" placeholder="Password"
ng-model="user.password" uib-tooltip="Enter a valid password"
tooltip-enable="$ctrl.badPassword" tooltip-placement="bottom"
tooltip-is-open="$ctrl.badPassword" ng-disabled="$ctrl.loggingIn">
</div><button type="submit" class="btn btn-default" ng-click=
"$ctrl.login(user)" ng-disabled="$ctrl.loggingIn">Submit</button>
<a class="btn btn-warning"
href="https://accounts.google.com/b/0/DisplayUnlockCaptcha"
target="_blank"
ng-disabled="!$ctrl.securityCheck">Security check</a>
</div>
</form>
<div ng-hide="!$ctrl.loggingIn" class="row loading-login">
<div class="loading-login-text">
Loading apps into cache, please wait
</div>
<uib-progressbar class="progress-striped active" max="$ctrl.max" value=
"$ctrl.current"><span style=
"color:white; white-space:nowrap;">{{$ctrl.formattedPercent}}%</span>
</uib-progressbar>
</div>
</div>

View File

@@ -0,0 +1,101 @@
<div class="view-container">
<div id="search-area" class="input-group input-group-lg">
<span class="input-group-addon" id="search-input-label">
<i class="fas fa-search"></i>
</span>
<input ng-model="searchString" on-enter="$ctrl.search(searchString)"
type="text" class="form-control" id="search-input" placeholder="Search"
aria-describedby="search-input-label">
</div>
<div ng-hide="!$ctrl.searching" id="search-progress" class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar"
aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width:100%">
<span class="sr-only">Loading results</span>
</div>
</div><!-- progress -->
<div ng-if="$ctrl.desktop" class="row" id="table-box">
<table class="table" ng-hide="$ctrl.results.length === 0">
<thead>
<tr>
<th></th>
<th>Title</th>
<th>Developer</th>
<th>Id</th>
<th>Version</th>
<th>Size</th>
<th>Stars</th>
</tr>
</thead>
<tbody id="table-body">
<tr ng-repeat="app in $ctrl.results">
<td id="dl-button-td">
<a ng-click="$ctrl.download(app)" ng-class=
"{'dl-button-disabled': app.disabled, 'dl-button': !app.disabled}">
<div ng-hide="app.downloading" class="fa-2x">
<i class="fas fa-download"></i>
</div>
<div ng-hide="!app.downloading" class="fa-2x">
<i class="fas fa-cog fa-spin"></i>
</div>
</a>
</td>
<td>{{app.title}}</td>
<td>{{app.creator}}</td>
<td>{{app.docid}}</td>
<td>{{app.details.appDetails.versionCode}}</td>
<td>{{ ((app.details.appDetails.file[0].size)/(1024*1024)).toFixed(2) }} MB</td>
<td>{{app.aggregateRating.starRating.toFixed(2)}} Stars</td>
</tr>
</tbody>
</table>
</div>
<div ng-if="$ctrl.mobile" class="row" id="table-box">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3 class="modal-title" id="modal-title">{{app.title}}</h3>
</div>
<div class="modal-body" id="modal-body">
<strong>Id:</strong> {{app.docid}}<br>
<strong>Developer:</strong> {{app.creator}}<br>
<strong>Version:</strong> {{app.details.appDetails.versionCode}}<br>
<strong>Size:</strong> {{ ((app.details.appDetails.file[0].size)/(1024*1024)).toFixed(2) }} MB<br>
<strong>Stars:</strong> {{app.aggregateRating.starRating.toFixed(2)}}
</div>
<div class="modal-footer">
<button class="btn btn-default" type="button" ng-click="$close()">Close</button>
</div>
</script>
<table class="table" ng-hide="$ctrl.results.length === 0">
<thead>
<tr>
<th></th>
<th>Title</th>
<th>Info</th>
</tr>
</thead>
<tbody id="table-body">
<tr ng-repeat="app in $ctrl.results">
<td id="dl-button-td">
<a ng-click="$ctrl.download(app)" ng-class=
"{'dl-button-disabled': app.disabled, 'dl-button': !app.disabled}">
<div ng-hide="app.downloading" class="fa-2x">
<i class="fas fa-download"></i>
</div>
<div ng-hide="!app.downloading" class="fa-2x">
<i class="fas fa-cog fa-spin"></i>
</div>
</a>
</td>
<td>{{app.title}}</td>
<td>
<a ng-click="$ctrl.modalOpen(app)" class="dl-button">
<i class="fas fa-info-circle fa-2x" data-toggle="popover"
data-html="true" title="" data-placement="left"
data-content="" aria-hidden="true"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div><!-- row -->
</div><!-- container -->