@@ -13,8 +13,8 @@
|
||||
// Define globals exposed by Node.js.
|
||||
"node": true,
|
||||
|
||||
// Allow ES6.
|
||||
"esversion": 7,
|
||||
// Allow ES8.
|
||||
"esversion": 9,
|
||||
|
||||
/*
|
||||
* ENFORCING OPTIONS
|
||||
|
||||
11
app/cron.ini
11
app/cron.ini
@@ -1,7 +1,8 @@
|
||||
[CRON]
|
||||
log = TRUE
|
||||
cli = TRUE
|
||||
web = FALSE
|
||||
log = TRUE
|
||||
cli = TRUE
|
||||
web = FALSE
|
||||
silent = TRUE
|
||||
|
||||
[CRON.presets]
|
||||
; run every minute
|
||||
@@ -51,13 +52,13 @@ deleteMapData = Cron\MapUpdate->deleteMapData, @downtime
|
||||
deleteAuthenticationData = Cron\CharacterUpdate->deleteAuthenticationData, @downtime
|
||||
|
||||
; delete expired cache files
|
||||
deleteExpiredCacheData = Cron\Cache->deleteExpiredData, @downtime
|
||||
deleteExpiredCacheData = Cron\Cache->deleteExpiredCacheData, @downtime
|
||||
|
||||
; delete old statistics (activity log) data
|
||||
deleteStatisticsData = Cron\StatisticsUpdate->deleteStatisticsData, @weekly
|
||||
|
||||
; truncate map history log files
|
||||
truncateMapHistoryLogFiles = Cron\MapHistory->truncateFiles, @halfHour
|
||||
truncateMapHistoryLogFiles = Cron\MapHistory->truncateMapHistoryLogFiles, @halfHour
|
||||
|
||||
; sync "sovereignty" and "faction warfare" data from CCP´s ESI API
|
||||
updateSovereigntyData = Cron\Universe->updateSovereigntyData, @halfPastHour
|
||||
|
||||
@@ -10,10 +10,93 @@ namespace Controller\Api;
|
||||
|
||||
use Controller;
|
||||
use lib\Config;
|
||||
use lib\Cron;
|
||||
use Model;
|
||||
|
||||
class Setup extends Controller\Controller {
|
||||
|
||||
/**
|
||||
* get HTML table <tr>´s for all cronjobs
|
||||
* @param \Base $f3
|
||||
*/
|
||||
public function cronTable(\Base $f3){
|
||||
$return = (object) [];
|
||||
$return->error = [];
|
||||
$return->jobsData = Cron::instance()->getJobsConfig();
|
||||
$return->html = $this->getCronHtml($return->jobsData);
|
||||
echo json_encode($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle "isPaused" for a cronjob by its name
|
||||
* @param \Base $f3
|
||||
*/
|
||||
public function cronPause(\Base $f3){
|
||||
$postData = (array)$f3->get('POST');
|
||||
$return = (object) [];
|
||||
$return->error = [];
|
||||
|
||||
if($jobName = (string)$postData['job']){
|
||||
$cron = Cron::instance();
|
||||
if($job = $cron->getJob($jobName)){
|
||||
if($job->valid()){
|
||||
$job->isPaused = !$job->isPaused;
|
||||
$job->save();
|
||||
|
||||
$return->jobsData = $cron->getJobsConfig([$jobName]);
|
||||
$return->html = $this->getCronHtml($return->jobsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* execute a cronjob by its name
|
||||
* -> runs sync
|
||||
* -> max execution time might be lower than CLI calls!
|
||||
* @param \Base $f3
|
||||
*/
|
||||
public function cronExecute(\Base $f3){
|
||||
$postData = (array)$f3->get('POST');
|
||||
$return = (object) [];
|
||||
$return->error = [];
|
||||
|
||||
if($jobName = (string)$postData['job']){
|
||||
$cron = Cron::instance();
|
||||
if($job = $cron->getJob($jobName)){
|
||||
if($job->valid()){
|
||||
$cron->execute($jobName, false);
|
||||
|
||||
$return->jobsData = $cron->getJobsConfig([$jobName]);
|
||||
$return->html = $this->getCronHtml($return->jobsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* get HTML for cronJobs
|
||||
* @param array $jobsData
|
||||
* @return string
|
||||
*/
|
||||
protected function getCronHtml(array $jobsData) : string {
|
||||
$tplData = [
|
||||
'cronConfig' => [
|
||||
'jobs' => $jobsData,
|
||||
'settings' => $this->getF3()->constants(Cron::instance(), 'DEFAULT_')
|
||||
],
|
||||
'tplCounter' => $this->counter(),
|
||||
'tplConvertBytes' => function(){
|
||||
return call_user_func_array([\lib\format\Number::instance(), 'bytesToString'], func_get_args());
|
||||
}
|
||||
];
|
||||
return \Template::instance()->render('templates/ui/cron_table_row.html', null, $tplData, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* build search index from existing data (e.g. Systems)
|
||||
* OR import data from ESI (e.g. Structures)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace Controller\Api;
|
||||
|
||||
use Controller;
|
||||
use lib\Config;
|
||||
use Model\Pathfinder;
|
||||
|
||||
class System extends Controller\AccessController {
|
||||
@@ -39,7 +40,7 @@ class System extends Controller\AccessController {
|
||||
$cacheResponse = false;
|
||||
|
||||
// number of log entries in each table per system (24 = 24h)
|
||||
$logEntryCount = 24;
|
||||
$logEntryCount = Pathfinder\AbstractSystemApiBasicModel::DATA_COLUMN_COUNT;
|
||||
|
||||
$ttl = 60 * 10;
|
||||
|
||||
@@ -51,47 +52,47 @@ class System extends Controller\AccessController {
|
||||
'factionKills' => 'SystemFactionKillModel'
|
||||
];
|
||||
|
||||
$exists = false;
|
||||
|
||||
foreach($systemIds as $systemId){
|
||||
$cacheKey = $this->getSystemGraphCacheKey($systemId);
|
||||
if( !$f3->exists($cacheKey, $graphData )){
|
||||
if(!$exists = $f3->exists($cacheKey, $graphData)){
|
||||
$graphData = [];
|
||||
$cacheSystem = false;
|
||||
|
||||
foreach($logTables as $label => $ModelClass){
|
||||
$systemLogModel = Pathfinder\AbstractPathfinderModel::getNew($ModelClass);
|
||||
foreach($logTables as $label => $className){
|
||||
$systemLogModel = Pathfinder\AbstractSystemApiBasicModel::getNew($className);
|
||||
$systemLogExists = false;
|
||||
|
||||
// 10min cache (could be up to 1h cache time)
|
||||
$systemLogModel->getByForeignKey('systemId', $systemId);
|
||||
if( !$systemLogModel->dry() ){
|
||||
if($systemLogModel->valid()){
|
||||
$systemLogExists = true;
|
||||
$cacheSystem = true;
|
||||
$cacheResponse = true;
|
||||
}
|
||||
|
||||
$systemLogData = $systemLogModel->getData();
|
||||
|
||||
// podKills share graph with shipKills -> skip
|
||||
if($label != 'podKills'){
|
||||
$graphData[$label]['logExists'] = $systemLogExists;
|
||||
$graphData[$label]['updated'] = $systemLogData->updated;
|
||||
}
|
||||
|
||||
$counter = 0;
|
||||
for( $i = $logEntryCount; $i >= 1; $i--){
|
||||
$column = 'value' . $i;
|
||||
$value = $systemLogExists ? $systemLogModel->$column : 0;
|
||||
|
||||
// ship and pod kills should be merged into one table
|
||||
$logValueCount = range(0, $logEntryCount - 1);
|
||||
foreach($logValueCount as $i){
|
||||
if($label == 'podKills'){
|
||||
$graphData['shipKills']['data'][$counter]['z'] = $value;
|
||||
$graphData['shipKills']['data'][$i]['z'] = $systemLogData->values[$i];
|
||||
}else{
|
||||
$dataSet = [
|
||||
'x' => ($i - 1) . 'h',
|
||||
'y' => $value
|
||||
$graphData[$label]['data'][] = [
|
||||
'x' => ($logEntryCount - $i - 1) . 'h',
|
||||
'y' => $systemLogData->values[$i]
|
||||
];
|
||||
$graphData[$label]['data'][] = $dataSet;
|
||||
}
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
|
||||
if($cacheSystem){
|
||||
$f3->set($cacheKey, $graphData, $ttl);
|
||||
}
|
||||
@@ -104,7 +105,7 @@ class System extends Controller\AccessController {
|
||||
|
||||
if($cacheResponse){
|
||||
// send client cache header
|
||||
$f3->expire($ttl);
|
||||
$f3->expire(Config::ttlLeft($exists, $ttl));
|
||||
}
|
||||
|
||||
echo json_encode($graphsData);
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Controller\Api;
|
||||
|
||||
|
||||
use Controller;
|
||||
use lib\Config;
|
||||
use Model\Pathfinder;
|
||||
use Exception;
|
||||
|
||||
@@ -119,6 +120,7 @@ class User extends Controller\Controller{
|
||||
$cookieName = (string)$data['cookie'];
|
||||
|
||||
$return = (object) [];
|
||||
$return->ccpImageServer = Config::getPathfinderData('api.ccp_image_server');
|
||||
$return->error = [];
|
||||
|
||||
if( !empty($cookieData = $this->getCookieByName($cookieName) )){
|
||||
|
||||
@@ -54,7 +54,7 @@ class AppController extends Controller {
|
||||
parent::afterroute($f3);
|
||||
|
||||
// clear all SSO related temp data
|
||||
if( $f3->exists(Ccp\Sso::SESSION_KEY_SSO) ){
|
||||
if($f3->exists(Ccp\Sso::SESSION_KEY_SSO)){
|
||||
$f3->clear(Ccp\Sso::SESSION_KEY_SSO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,7 +468,7 @@ class Sso extends Api\User{
|
||||
*/
|
||||
$corporation = Pathfinder\AbstractPathfinderModel::getNew('CorporationModel');
|
||||
$corporation->getById($corporationId, 0);
|
||||
if( !$corporation->dry() ){
|
||||
if($corporation->valid()){
|
||||
$characterData->corporation = $corporation;
|
||||
}
|
||||
}
|
||||
@@ -479,7 +479,7 @@ class Sso extends Api\User{
|
||||
*/
|
||||
$alliance = Pathfinder\AbstractPathfinderModel::getNew('AllianceModel');
|
||||
$alliance->getById($allianceId, 0);
|
||||
if( !$alliance->dry() ){
|
||||
if($alliance->valid()){
|
||||
$characterData->alliance = $alliance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,10 @@ class Controller {
|
||||
}else{
|
||||
$this->initResource($f3);
|
||||
|
||||
$this->setTemplate( Config::getPathfinderData('view.index') );
|
||||
$this->setTemplate(Config::getPathfinderData('view.index'));
|
||||
|
||||
|
||||
$f3->set('tplImage', \lib\format\Image::instance());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -165,7 +168,7 @@ class Controller {
|
||||
'font' => $f3->get('BASE') . '/public/fonts',
|
||||
'document' => $f3->get('BASE') . '/public/templates',
|
||||
'image' => $f3->get('BASE') . '/public/img'
|
||||
]);
|
||||
], true);
|
||||
|
||||
$resource->register('style', 'pathfinder');
|
||||
|
||||
@@ -178,6 +181,10 @@ class Controller {
|
||||
$resource->register('font', 'fa-solid-900');
|
||||
$resource->register('font', 'fa-brands-400');
|
||||
|
||||
$resource->register('url', self::getEnvironmentData('CCP_SSO_URL'), 'prerender');
|
||||
$resource->register('url', Config::getPathfinderData('api.ccp_image_server'), 'dns-prefetch');
|
||||
$resource->register('url', '//i.ytimg.com', 'dns-prefetch'); // YouTube tiny embed domain
|
||||
|
||||
$f3->set('tplResource', $resource);
|
||||
}
|
||||
|
||||
@@ -805,6 +812,26 @@ class Controller {
|
||||
Monolog::instance()->log();
|
||||
}
|
||||
|
||||
/**
|
||||
* simple counter with "static" store
|
||||
* -> called within tpl render
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function counter() : \Closure {
|
||||
$store = [];
|
||||
|
||||
return function(string $action = 'increment', string $type = 'default', $val = 0) use (&$store){
|
||||
$return = null;
|
||||
switch($action){
|
||||
case 'increment': $store[$type]++; break;
|
||||
case 'add': $store[$type] += (int)$val; break;
|
||||
case 'get': $return = $store[$type] ? : null; break;
|
||||
case 'reset': unset($store[$type]); break;
|
||||
}
|
||||
return $return;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get controller by class name
|
||||
* -> controller class is searched within all controller directories
|
||||
|
||||
@@ -13,6 +13,7 @@ use data\filesystem\Search;
|
||||
use DB\SQL\Schema;
|
||||
use DB\SQL\MySQL as MySQL;
|
||||
use lib\Config;
|
||||
use lib\Cron;
|
||||
use lib\Util;
|
||||
use Model\Pathfinder;
|
||||
use Model\Universe;
|
||||
@@ -59,6 +60,7 @@ class Setup extends Controller {
|
||||
'PF' => [
|
||||
'info' => [],
|
||||
'models' => [
|
||||
'Model\Pathfinder\CronModel',
|
||||
'Model\Pathfinder\UserModel',
|
||||
'Model\Pathfinder\AllianceModel',
|
||||
'Model\Pathfinder\CorporationModel',
|
||||
@@ -167,17 +169,10 @@ class Setup extends Controller {
|
||||
// js view (file)
|
||||
$f3->set('tplJsView', 'setup');
|
||||
|
||||
// simple counter (called within template)
|
||||
$counter = [];
|
||||
$f3->set('tplCounter', function(string $action = 'increment', string $type = 'default', $val = 0) use (&$counter){
|
||||
$return = null;
|
||||
switch($action){
|
||||
case 'increment': $counter[$type]++; break;
|
||||
case 'add': $counter[$type] += (int)$val; break;
|
||||
case 'get': $return = $counter[$type]? : null; break;
|
||||
case 'reset': unset($counter[$type]); break;
|
||||
}
|
||||
return $return;
|
||||
$f3->set('tplCounter', $this->counter());
|
||||
|
||||
$f3->set('tplConvertBytes', function(){
|
||||
return call_user_func_array([\lib\format\Number::instance(), 'bytesToString'], func_get_args());
|
||||
});
|
||||
|
||||
// render view
|
||||
@@ -264,6 +259,9 @@ class Setup extends Controller {
|
||||
// WebSocket information
|
||||
$f3->set('socketInformation', $this->getSocketInformation($f3));
|
||||
|
||||
// Cronjob ----------------------------------------------------------------------------------------------------
|
||||
$f3->set('cronConfig', $this->getCronConfig($f3));
|
||||
|
||||
// Administration ---------------------------------------------------------------------------------------------
|
||||
// Index information
|
||||
$f3->set('indexInformation', $this->getIndexData($f3));
|
||||
@@ -296,9 +294,12 @@ class Setup extends Controller {
|
||||
'socket' => [
|
||||
'icon' => 'fa-exchange-alt'
|
||||
],
|
||||
'cronjob' => [
|
||||
'icon' => 'fa-user-clock'
|
||||
],
|
||||
'administration' => [
|
||||
'icon' => 'fa-wrench'
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
return $config;
|
||||
@@ -761,20 +762,20 @@ class Setup extends Controller {
|
||||
],
|
||||
'maxMemory' => [
|
||||
'label' => 'maxmemory',
|
||||
'required' => $this->convertBytes($f3->get('REQUIREMENTS.REDIS.MAX_MEMORY')),
|
||||
'version' => $this->convertBytes($redisMemoryInfo['maxmemory']),
|
||||
'required' => \lib\format\Number::instance()->bytesToString($f3->get('REQUIREMENTS.REDIS.MAX_MEMORY')),
|
||||
'version' => \lib\format\Number::instance()->bytesToString($redisMemoryInfo['maxmemory']),
|
||||
'check' => $redisMemoryInfo['maxmemory'] >= $f3->get('REQUIREMENTS.REDIS.MAX_MEMORY'),
|
||||
'tooltip' => 'Max memory limit for Redis'
|
||||
],
|
||||
'usedMemory' => [
|
||||
'label' => 'used_memory',
|
||||
'version' => $this->convertBytes($redisMemoryInfo['used_memory']),
|
||||
'version' => \lib\format\Number::instance()->bytesToString($redisMemoryInfo['used_memory']),
|
||||
'check' => $redisMemoryInfo['used_memory'] < $redisMemoryInfo['maxmemory'],
|
||||
'tooltip' => 'Current memory used by Redis'
|
||||
],
|
||||
'usedMemoryPeak' => [
|
||||
'label' => 'used_memory_peak',
|
||||
'version' => $this->convertBytes($redisMemoryInfo['used_memory_peak']),
|
||||
'version' => \lib\format\Number::instance()->bytesToString($redisMemoryInfo['used_memory_peak']),
|
||||
'check' => $redisMemoryInfo['used_memory_peak'] <= $redisMemoryInfo['maxmemory'],
|
||||
'tooltip' => 'Peak memory used by Redis'
|
||||
],
|
||||
@@ -1139,6 +1140,8 @@ class Setup extends Controller {
|
||||
'fieldConf' => $tableConfig['fieldConf'],
|
||||
'exists' => false,
|
||||
'empty' => true,
|
||||
'requiredCharset' => $tableConfig['charset'],
|
||||
'requiredCollation' => $tableConfig['charset'] . '_unicode_ci',
|
||||
'foreignKeys' => []
|
||||
];
|
||||
}
|
||||
@@ -1157,7 +1160,8 @@ class Setup extends Controller {
|
||||
|
||||
// check each table for changes
|
||||
foreach($requiredTables as $requiredTableName => $data){
|
||||
|
||||
$tableCharset = null;
|
||||
$tableCollation = null;
|
||||
$tableExists = false;
|
||||
$tableRows = 0;
|
||||
// Check if table status is OK (no errors/warnings,..)
|
||||
@@ -1173,6 +1177,14 @@ class Setup extends Controller {
|
||||
// get row count
|
||||
$tableRows = $db->getRowCount($requiredTableName);
|
||||
|
||||
$tableStatus = $db->getTableStatus($requiredTableName);
|
||||
if(
|
||||
!empty($tableStatus['Collation']) &&
|
||||
($statusVal = strstr($tableStatus['Collation'], '_', true)) !== false
|
||||
){
|
||||
$tableCharset = $statusVal;
|
||||
$tableCollation = $tableStatus['Collation'];
|
||||
}
|
||||
|
||||
// find deprecated columns that are no longer needed ------------------------------------------
|
||||
$deprecatedColumnNames = array_diff(array_keys($currentColumns), array_keys($data['fieldConf']), ['id']);
|
||||
@@ -1365,6 +1377,8 @@ class Setup extends Controller {
|
||||
}
|
||||
|
||||
$dbStatusCheckCount += $tableStatusCheckCount;
|
||||
$requiredTables[$requiredTableName]['currentCharset'] = $tableCharset;
|
||||
$requiredTables[$requiredTableName]['currentCollation'] = $tableCollation;
|
||||
$requiredTables[$requiredTableName]['rows'] = $tableRows;
|
||||
$requiredTables[$requiredTableName]['exists'] = $tableExists;
|
||||
$requiredTables[$requiredTableName]['statusCheckCount'] = $tableStatusCheckCount;
|
||||
@@ -1659,13 +1673,59 @@ class Setup extends Controller {
|
||||
return $socketInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* get cronjob config
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
*/
|
||||
protected function getCronConfig(\Base $f3) : array {
|
||||
$cron = Cron::instance();
|
||||
|
||||
$cronConf = [
|
||||
'log' => [
|
||||
'label' => 'LOG',
|
||||
'required' => $f3->get('REQUIREMENTS.CRON.LOG'),
|
||||
'version' => $f3->get('CRON.log'),
|
||||
'check' => $f3->get('CRON.log') == $f3->get('REQUIREMENTS.CRON.LOG'),
|
||||
'tooltip' => 'Write default cron.log'
|
||||
],
|
||||
'cli' => [
|
||||
'label' => 'CLI',
|
||||
'required' => $f3->get('REQUIREMENTS.CRON.CLI'),
|
||||
'version' => $f3->get('CRON.cli'),
|
||||
'check' => $f3->get('CRON.cli') == $f3->get('REQUIREMENTS.CRON.CLI'),
|
||||
'tooltip' => 'Jobs can be triggered by CLI. Must be set on Unix where "crontab -e" config is used'
|
||||
],
|
||||
'web' => [
|
||||
'label' => 'WEB',
|
||||
'version' => (int)$f3->get('CRON.web'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Jobs can be triggered by URL. Could be useful if jobs should be triggered by e.g. 3rd party app. Secure "/cron" url if active!'
|
||||
],
|
||||
'silent' => [
|
||||
'label' => 'SILENT',
|
||||
'version' => (int)$f3->get('CRON.silent'),
|
||||
'check' => true,
|
||||
'tooltip' => 'Write job execution status to STDOUT if job completes'
|
||||
]
|
||||
];
|
||||
|
||||
$config = [
|
||||
'checkCronConfig' => $cronConf,
|
||||
'settings' => $f3->constants($cron, 'DEFAULT_'),
|
||||
'jobs' => $cron->getJobsConfig()
|
||||
];
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* get indexed (cache) data information
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getIndexData(\Base $f3){
|
||||
protected function getIndexData(\Base $f3) : array {
|
||||
// active DB and tables are required for obtain index data
|
||||
if(!$this->databaseHasError){
|
||||
/**
|
||||
@@ -1890,7 +1950,7 @@ class Setup extends Controller {
|
||||
}
|
||||
$bytesAll += $bytes;
|
||||
|
||||
$dirAll[$key]['size'] = ($maxHit ? '>' : '') . $this->convertBytes($bytes);
|
||||
$dirAll[$key]['size'] = ($maxHit ? '>' : '') . \lib\format\Number::instance()->bytesToString($bytes);
|
||||
$dirAll[$key]['task'] = [
|
||||
[
|
||||
'action' => http_build_query([
|
||||
@@ -1905,7 +1965,7 @@ class Setup extends Controller {
|
||||
}
|
||||
|
||||
return [
|
||||
'sizeAll' => ($maxHitAll ? '>' : '') . $this->convertBytes($bytesAll),
|
||||
'sizeAll' => ($maxHitAll ? '>' : '') . \lib\format\Number::instance()->bytesToString($bytesAll),
|
||||
'dirAll' => $dirAll
|
||||
];
|
||||
}
|
||||
@@ -1957,20 +2017,4 @@ class Setup extends Controller {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* convert Bytes to string + suffix
|
||||
* @param int $bytes
|
||||
* @param int $precision
|
||||
* @return string
|
||||
*/
|
||||
protected function convertBytes($bytes, $precision = 2){
|
||||
$result = '0';
|
||||
if($bytes){
|
||||
$base = log($bytes, 1024);
|
||||
$suffixes = array('', 'KB', 'M', 'GB', 'TB');
|
||||
$result = round(pow(1024, $base - floor($base)), $precision) .''. $suffixes[(int)floor($base)];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,19 @@
|
||||
|
||||
namespace cron;
|
||||
|
||||
use Model\Pathfinder;
|
||||
|
||||
abstract class AbstractCron {
|
||||
|
||||
/**
|
||||
* default "base" log message text
|
||||
* -> generic information data
|
||||
*/
|
||||
const LOG_TEXT_BASE = '%4s/%-4s %6s done, %5s total, %8s peak, %9s exec';
|
||||
|
||||
/**
|
||||
* default max_execution_time for cronJobs
|
||||
// -> should be less then execution period
|
||||
* -> should be less then execution period
|
||||
*/
|
||||
const DEFAULT_MAX_EXECUTION_TIME = 50;
|
||||
|
||||
@@ -22,6 +30,19 @@ abstract class AbstractCron {
|
||||
*/
|
||||
const DEFAULT_EXECUTION_TIME_THRESHOLD = 3;
|
||||
|
||||
/**
|
||||
* started jobs
|
||||
* @var Pathfinder\CronModel[]
|
||||
*/
|
||||
protected $activeCron = [];
|
||||
|
||||
/**
|
||||
* disables log file write entry for some cronJobs
|
||||
* -> either job runs too frequently, or no relevant data available for logging
|
||||
* @var array
|
||||
*/
|
||||
protected $logDisabled = [];
|
||||
|
||||
/**
|
||||
* set max execution time for cronJbs
|
||||
* -> Default CLI execution time is 0 == infinite!
|
||||
@@ -61,4 +82,129 @@ abstract class AbstractCron {
|
||||
return $timeLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* log cronjob exec state on start
|
||||
* @param string $job
|
||||
* @param bool $logging
|
||||
*/
|
||||
protected function logStart(string $job, bool $logging = true){
|
||||
$this->setMaxExecutionTime();
|
||||
|
||||
$cron = \lib\Cron::instance();
|
||||
if(isset($cron->jobs[$job])){
|
||||
// set "start" date for current cronjob
|
||||
$jobConf = $cron->getJobDataFromConf($cron->jobs[$job]);
|
||||
$jobConf['lastExecStart'] = $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
if(($cronModel = $cron->registerJob($job, $jobConf)) instanceof Pathfinder\CronModel){
|
||||
$this->activeCron[$job] = $cronModel;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$logging){
|
||||
$this->logDisabled[] = $job;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* log cronjob exec state on finish
|
||||
* @param string $job
|
||||
* @param int $total
|
||||
* @param int $count
|
||||
* @param int $importCount
|
||||
* @param int $offset
|
||||
* @param string $logText
|
||||
*/
|
||||
protected function logEnd(string $job, int $total = 0, int $count = 0, int $importCount = 0, int $offset = 0, string $logText = ''){
|
||||
$execEnd = microtime(true);
|
||||
$memPeak = memory_get_peak_usage();
|
||||
$state = [
|
||||
'total' => $total,
|
||||
'count' => $count,
|
||||
'importCount' => $importCount,
|
||||
'offset' => $offset,
|
||||
'loop' => 1,
|
||||
'percent' => $total ? round(100 / $total * ($count + $offset), 1) : 100
|
||||
];
|
||||
|
||||
if(isset($this->activeCron[$job])){
|
||||
if($lastState = $this->activeCron[$job]->lastExecState){
|
||||
if(isset($lastState['loop']) && $offset){
|
||||
$state['loop'] = (int)$lastState['loop'] + 1;
|
||||
}
|
||||
}
|
||||
|
||||
$jobConf = [
|
||||
'lastExecEnd' => $execEnd,
|
||||
'lastExecMemPeak' => $memPeak,
|
||||
'lastExecState' => $state
|
||||
];
|
||||
$this->activeCron[$job]->setData($jobConf);
|
||||
$this->activeCron[$job]->save();
|
||||
unset($this->activeCron[$job]);
|
||||
}
|
||||
|
||||
if(!in_array($job, $this->logDisabled)){
|
||||
$this->writeLog($job, $memPeak, $execEnd, $state, $logText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get either CLI GET params OR
|
||||
* check for params from last run -> incremental import
|
||||
* @param string $job
|
||||
* @return array
|
||||
*/
|
||||
protected function getParams(string $job) : array {
|
||||
$params = [];
|
||||
|
||||
// check for CLI GET params
|
||||
$f3 = \Base::instance();
|
||||
if($getParams = (array)$f3->get('GET')){
|
||||
if(isset($getParams['offset'])){
|
||||
$params['offset'] = (int)$getParams['offset'];
|
||||
}
|
||||
if(isset($getParams['length']) && (int)$getParams['length'] > 0){
|
||||
$params['length'] = (int)$getParams['length'];
|
||||
}
|
||||
}
|
||||
|
||||
// .. or check for logged params from last exec state (DB entry)
|
||||
if(empty($params) && isset($this->activeCron[$job])){
|
||||
if($lastState = $this->activeCron[$job]->lastExecState){
|
||||
if(isset($lastState['offset'])){
|
||||
$params['offset'] = (int)$lastState['offset'];
|
||||
}
|
||||
if(isset($lastState['count'])){
|
||||
$params['offset'] = (int)$params['offset'] + (int)$lastState['count'];
|
||||
}
|
||||
if(isset($lastState['loop'])){
|
||||
$params['loop'] = (int)$lastState['loop'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* write log file for $job
|
||||
* @param string $job
|
||||
* @param int $memPeak
|
||||
* @param float $execEnd
|
||||
* @param array $state
|
||||
* @param string $logText for custom text
|
||||
*/
|
||||
private function writeLog(string $job, int $memPeak = 0, float $execEnd = 0, array $state = [], string $logText = ''){
|
||||
$percent = number_format($state['percent'], 1) . '%';
|
||||
$duration = number_format(round($execEnd - $_SERVER['REQUEST_TIME_FLOAT'], 3), 3) . 's';
|
||||
$log = new \Log('cron_' . $job . '.log');
|
||||
|
||||
$text = sprintf(self::LOG_TEXT_BASE,
|
||||
$state['count'], $state['importCount'], $percent, $state['total'],
|
||||
\lib\format\Number::instance()->bytesToString($memPeak), $duration
|
||||
);
|
||||
|
||||
$text .= $logText ? $logText: '';
|
||||
$log->write($text);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,10 @@ use data\filesystem\Search;
|
||||
|
||||
class Cache extends AbstractCron {
|
||||
|
||||
const LOG_TEXT = '%s [%\'_10s] files, size [%\'_10s] byte, not writable [%\'_10s] files, errors [%\'_10s], exec (%.3Fs)';
|
||||
/**
|
||||
* log text
|
||||
*/
|
||||
const LOG_TEXT = ', size [%\'_10s] byte, not writable [%\'_10s] files, errors [%\'_10s]';
|
||||
|
||||
/**
|
||||
* default max expire for files (seconds)
|
||||
@@ -33,9 +36,8 @@ class Cache extends AbstractCron {
|
||||
* >> php index.php "/cron/deleteExpiredCacheData"
|
||||
* @param \Base $f3
|
||||
*/
|
||||
function deleteExpiredData(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$time_start = microtime(true);
|
||||
function deleteExpiredCacheData(\Base $f3){
|
||||
$this->logStart(__FUNCTION__);
|
||||
|
||||
// cache dir (dir is recursively searched...)
|
||||
$cacheDir = $f3->get('TEMP');
|
||||
@@ -43,6 +45,7 @@ class Cache extends AbstractCron {
|
||||
$filterTime = (int)strtotime('-' . $this->getExpireMaxTime($f3) . ' seconds');
|
||||
$expiredFiles = Search::getFilesByMTime($cacheDir, $filterTime, Search::DEFAULT_FILE_LIMIT);
|
||||
|
||||
$totalFiles = 0;
|
||||
$deletedFiles = 0;
|
||||
$deletedSize = 0;
|
||||
$notWritableFiles = 0;
|
||||
@@ -52,7 +55,8 @@ class Cache extends AbstractCron {
|
||||
* @var $file \SplFileInfo
|
||||
*/
|
||||
if($file->isFile()){
|
||||
if( $file->isWritable() ){
|
||||
$totalFiles++;
|
||||
if($file->isWritable()){
|
||||
$tmpSize = $file->getSize();
|
||||
if( unlink($file->getRealPath()) ){
|
||||
$deletedSize += $tmpSize;
|
||||
@@ -66,11 +70,13 @@ class Cache extends AbstractCron {
|
||||
}
|
||||
}
|
||||
|
||||
$execTime = microtime(true) - $time_start;
|
||||
// Log --------------------------------------------------------------------------------------------------------
|
||||
$total = $totalFiles;
|
||||
$importCount = $total;
|
||||
$count = $deletedFiles;
|
||||
|
||||
// Log ------------------------
|
||||
$log = new \Log('cron_' . __FUNCTION__ . '.log');
|
||||
$log->write( sprintf(self::LOG_TEXT, __FUNCTION__, $deletedFiles, $deletedSize, $notWritableFiles, $deleteErrors, $execTime) );
|
||||
$text = sprintf(self::LOG_TEXT, $deletedSize, $notWritableFiles, $deleteErrors);
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount, 0, $text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ use lib\db\SQL;
|
||||
|
||||
class CcpSystemsUpdate extends AbstractCron {
|
||||
|
||||
const LOG_TEXT = '%s prepare table (%.3F s), jump (%.3F s), kill (%.3F s), update all (%.3F s)';
|
||||
/**
|
||||
* log text
|
||||
*/
|
||||
const LOG_TEXT = ' → [%.3Fs prepare table, %.3Fs jump, %.3Fs kill, %.3Fs update all]';
|
||||
|
||||
/**
|
||||
* table names for all system log tables
|
||||
@@ -37,47 +40,50 @@ class CcpSystemsUpdate extends AbstractCron {
|
||||
/**
|
||||
* check all system log tables for the correct number of system entries that will be locked
|
||||
* @param \Base $f3
|
||||
* @return array
|
||||
* @return int[]
|
||||
*/
|
||||
private function prepareSystemLogTables(\Base $f3) : array {
|
||||
$systemsData = [];
|
||||
$systemIds = [];
|
||||
|
||||
// get all available systems from "universe" DB
|
||||
$universeDB = $f3->DB->getDB('UNIVERSE');
|
||||
|
||||
if($this->tableExists($universeDB, 'system')){
|
||||
$systemsData = $universeDB->exec('SELECT
|
||||
`id`
|
||||
FROM
|
||||
`system`
|
||||
WHERE
|
||||
`security` = :ns OR
|
||||
`security` = :ls OR
|
||||
`security` = :hs
|
||||
',
|
||||
$systemsData = $universeDB->exec('
|
||||
SELECT
|
||||
`id`
|
||||
FROM
|
||||
`system`
|
||||
WHERE
|
||||
`security` = :ns OR
|
||||
`security` = :ls OR
|
||||
`security` = :hs
|
||||
',
|
||||
[':ns' => '0.0', ':ls' => 'L', ':hs' => 'H']
|
||||
);
|
||||
|
||||
$systemIds = array_map('intval', array_column($systemsData, 'id'));
|
||||
sort($systemIds, SORT_NUMERIC);
|
||||
|
||||
$pfDB = $f3->DB->getDB('PF');
|
||||
|
||||
// insert systems into each log table if not exist
|
||||
$pfDB->begin();
|
||||
foreach($this->logTables as $tableName){
|
||||
$pfDB->begin();
|
||||
// insert systems into jump log table
|
||||
$sqlInsertSystem = "INSERT IGNORE INTO " . $tableName . " (systemId)
|
||||
VALUES(:systemId)";
|
||||
$sqlInsertSystem = "INSERT IGNORE INTO " . $tableName . " (`systemId`) VALUES (:systemId)";
|
||||
|
||||
foreach($systemsData as $systemData){
|
||||
$pfDB->exec($sqlInsertSystem, array(
|
||||
':systemId' => $systemData['id']
|
||||
), 0, false);
|
||||
foreach($systemIds as $systemId){
|
||||
$pfDB->exec($sqlInsertSystem, [
|
||||
':systemId' => $systemId
|
||||
], 0, false);
|
||||
}
|
||||
|
||||
$pfDB->commit();
|
||||
}
|
||||
$pfDB->commit();
|
||||
}
|
||||
|
||||
return $systemsData;
|
||||
return $systemIds;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,15 +93,26 @@ class CcpSystemsUpdate extends AbstractCron {
|
||||
* @param \Base $f3
|
||||
*/
|
||||
function importSystemData(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__);
|
||||
$params = $this->getParams(__FUNCTION__);
|
||||
|
||||
|
||||
// prepare system jump log table ------------------------------------------------------------------------------
|
||||
$time_start = microtime(true);
|
||||
$systemsData = $this->prepareSystemLogTables($f3);
|
||||
$systemIds = $this->prepareSystemLogTables($f3);
|
||||
$time_end = microtime(true);
|
||||
$execTimePrepareSystemLogTables = $time_end - $time_start;
|
||||
|
||||
$total = count($systemIds);
|
||||
$offset = ($params['offset'] > 0 && $params['offset'] < $total) ? $params['offset'] : 0;
|
||||
$systemIds = array_slice($systemIds, $offset, $params['length']);
|
||||
$importCount = count($systemIds);
|
||||
$count = 0;
|
||||
|
||||
// switch DB for data import..
|
||||
/**
|
||||
* @var $pfDB SQL
|
||||
*/
|
||||
$pfDB = $f3->DB->getDB('PF');
|
||||
|
||||
// get current jump data --------------------------------------------------------------------------------------
|
||||
@@ -115,64 +132,57 @@ class CcpSystemsUpdate extends AbstractCron {
|
||||
|
||||
// update system log tables -----------------------------------------------------------------------------------
|
||||
$time_start = microtime(true);
|
||||
$pfDB->begin();
|
||||
|
||||
$logTableCount = count($this->logTables);
|
||||
$logTableCounter = 0;
|
||||
foreach($this->logTables as $key => $tableName){
|
||||
$sql = "UPDATE
|
||||
$tableName
|
||||
SET
|
||||
updated = now(),
|
||||
value24 = value23,
|
||||
value23 = value22,
|
||||
value22 = value21,
|
||||
value21 = value20,
|
||||
value20 = value19,
|
||||
value19 = value18,
|
||||
value18 = value17,
|
||||
value17 = value16,
|
||||
value16 = value15,
|
||||
value15 = value14,
|
||||
value14 = value13,
|
||||
value13 = value12,
|
||||
value12 = value11,
|
||||
value11 = value10,
|
||||
value10 = value9,
|
||||
value9 = value8,
|
||||
value8 = value7,
|
||||
value7 = value6,
|
||||
value6 = value5,
|
||||
value5 = value4,
|
||||
value4 = value3,
|
||||
value3 = value2,
|
||||
value2 = value1,
|
||||
value1 = :value
|
||||
WHERE
|
||||
systemId = :systemId
|
||||
";
|
||||
$logTableCounter++;
|
||||
$pfDB->begin();
|
||||
|
||||
foreach($systemsData as $systemData){
|
||||
$systemId = $systemData['id'];
|
||||
$sqlUpdateColumn = vsprintf("SELECT `systemId`, IF (lastUpdatedValue, lastUpdatedValue, DEFAULT (lastUpdatedValue)) AS `updateColumn` FROM %s", [
|
||||
$pfDB->quotekey($tableName)
|
||||
]);
|
||||
$resUpdateColumns = $pfDB->exec($sqlUpdateColumn, null, 0);
|
||||
$resUpdateColumns = array_column($resUpdateColumns, 'updateColumn', 'systemId');
|
||||
|
||||
foreach($systemIds as $systemId){
|
||||
$column = 1;
|
||||
if(isset($resUpdateColumns[$systemId])){
|
||||
$column = (int)$resUpdateColumns[$systemId];
|
||||
$column = (++$column > 24) ? 1 : $column;
|
||||
}
|
||||
|
||||
// update data (if available)
|
||||
$currentData = 0;
|
||||
if( isset($systemValues[$systemId][$key]) ){
|
||||
if(isset($systemValues[$systemId][$key])){
|
||||
$currentData = (int)$systemValues[$systemId][$key];
|
||||
}
|
||||
|
||||
$sql = vsprintf("UPDATE %s SET `updated` = NOW(), %s = :value, `lastUpdatedValue` = :updateColumn WHERE systemId = :systemId", [
|
||||
$pfDB->quotekey($tableName),
|
||||
$pfDB->quotekey('value' . $column)
|
||||
]);
|
||||
|
||||
$pfDB->exec($sql, [
|
||||
':systemId' => $systemId,
|
||||
':updateColumn' => $column,
|
||||
':value' => $currentData
|
||||
], 0, false);
|
||||
}
|
||||
}
|
||||
] , 0);
|
||||
|
||||
$pfDB->commit();
|
||||
// system import is done if ALL related tables (4) were updated
|
||||
if($logTableCounter === $logTableCount){
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$pfDB->commit();
|
||||
}
|
||||
|
||||
$time_end = microtime(true);
|
||||
$execTimeUpdateTables = $time_end - $time_start;
|
||||
|
||||
// Log --------------------------------------------------------------------------------------------------------
|
||||
$log = new \Log('cron_' . __FUNCTION__ . '.log');
|
||||
$log->write( sprintf(self::LOG_TEXT, __FUNCTION__, $execTimePrepareSystemLogTables, $execTimeGetJumpData, $execTimeGetKillData, $execTimeUpdateTables) );
|
||||
$text = sprintf(self::LOG_TEXT, $execTimePrepareSystemLogTables, $execTimeGetJumpData, $execTimeGetKillData, $execTimeUpdateTables);
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount, $offset, $text);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ class CharacterUpdate extends AbstractCron {
|
||||
* @throws \Exception
|
||||
*/
|
||||
function deleteLogData(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__, false);
|
||||
$logInactiveTime = $this->getCharacterLogInactiveTime($f3);
|
||||
|
||||
/**
|
||||
@@ -58,7 +58,11 @@ class CharacterUpdate extends AbstractCron {
|
||||
'limit' => self::CHARACTERS_UPDATE_LOGS_MAX
|
||||
]);
|
||||
|
||||
$total = 0;
|
||||
$count = 0;
|
||||
|
||||
if(is_object($characterLogs)){
|
||||
$total = count($characterLogs);
|
||||
foreach($characterLogs as $characterLog){
|
||||
/**
|
||||
* @var $characterLog Pathfinder\CharacterLogModel
|
||||
@@ -72,13 +76,22 @@ class CharacterUpdate extends AbstractCron {
|
||||
}else{
|
||||
$characterLog->erase();
|
||||
}
|
||||
}else{
|
||||
// no valid $accessToken. (e.g. ESI is down; or invalid `refresh_token` found
|
||||
$characterLog->erase();
|
||||
}
|
||||
}else{
|
||||
// character_log does not have a character assigned -> delete
|
||||
$characterLog->erase();
|
||||
}
|
||||
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$importCount = $total;
|
||||
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +101,7 @@ class CharacterUpdate extends AbstractCron {
|
||||
* @throws \Exception
|
||||
*/
|
||||
function cleanUpCharacterData(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__, false);
|
||||
|
||||
/**
|
||||
* @var $characterModel Pathfinder\CharacterModel
|
||||
@@ -109,6 +122,8 @@ class CharacterUpdate extends AbstractCron {
|
||||
$character->save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->logEnd(__FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +134,7 @@ class CharacterUpdate extends AbstractCron {
|
||||
* @throws \Exception
|
||||
*/
|
||||
function deleteAuthenticationData(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__, false);
|
||||
|
||||
/**
|
||||
* @var $authenticationModel Pathfinder\CharacterAuthenticationModel
|
||||
@@ -136,6 +151,8 @@ class CharacterUpdate extends AbstractCron {
|
||||
$authentication->erase();
|
||||
}
|
||||
}
|
||||
|
||||
$this->logEnd(__FUNCTION__);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,9 +15,9 @@ use data\filesystem\Search;
|
||||
class MapHistory extends AbstractCron {
|
||||
|
||||
/**
|
||||
* log msg for truncateFiles() cronjob
|
||||
* log msg for truncateMapHistoryLogFiles() cronjob
|
||||
*/
|
||||
const LOG_TEXT = '%s [%4s] log files, [%s] truncated, [%4s] not writable, [%4s] read error, [%4s] write error, [%4s] rename error, [%4s] delete error, exec (%.3Fs)';
|
||||
const LOG_TEXT = ', [%4s] log files, [%s] truncated, [%4s] not writable, [%4s] read error, [%4s] write error, [%4s] rename error, [%4s] delete error';
|
||||
|
||||
/**
|
||||
* default log file size limit before truncate, bytes (1MB)
|
||||
@@ -59,8 +59,8 @@ class MapHistory extends AbstractCron {
|
||||
* >> php index.php "/cron/truncateMapHistoryLogFiles"
|
||||
* @param \Base $f3
|
||||
*/
|
||||
function truncateFiles(\Base $f3){
|
||||
$timeStart = microtime(true);
|
||||
function truncateMapHistoryLogFiles(\Base $f3){
|
||||
$this->logStart(__FUNCTION__);
|
||||
|
||||
$largeFiles = 0;
|
||||
$notWritableFiles = 0;
|
||||
@@ -121,10 +121,11 @@ class MapHistory extends AbstractCron {
|
||||
}
|
||||
}
|
||||
|
||||
$execTime = microtime(true) - $timeStart;
|
||||
$importCount = $total = $largeFiles;
|
||||
$count = count($truncatedFileNames);
|
||||
|
||||
// Log ------------------------
|
||||
$log = new \Log('cron_' . __FUNCTION__ . '.log');
|
||||
$log->write(sprintf(self::LOG_TEXT, __FUNCTION__, $largeFiles, implode(', ', $truncatedFileNames), $notWritableFiles, $readErrors, $writeErrors, $renameErrors, $deleteErrors, $execTime));
|
||||
// Log --------------------------------------------------------------------------------------------------------
|
||||
$text = sprintf(self::LOG_TEXT, $largeFiles, implode(', ', $truncatedFileNames), $notWritableFiles, $readErrors, $writeErrors, $renameErrors, $deleteErrors);
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount, 0, $text);
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,14 @@ use Model\Pathfinder;
|
||||
|
||||
class MapUpdate extends AbstractCron {
|
||||
|
||||
const LOG_TEXT_MAPS = '%s (%d maps)';
|
||||
/**
|
||||
* log text
|
||||
*/
|
||||
const LOG_TEXT_MAPS_DELETED = ', %3s maps deleted';
|
||||
|
||||
// disabled maps will be fully deleted after (x) days
|
||||
/**
|
||||
* disabled maps will be fully deleted after (x) days
|
||||
*/
|
||||
const DAYS_UNTIL_MAP_DELETION = 30;
|
||||
|
||||
/**
|
||||
@@ -25,7 +30,7 @@ class MapUpdate extends AbstractCron {
|
||||
* @param \Base $f3
|
||||
*/
|
||||
function deactivateMapData(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__, false);
|
||||
$privateMapLifetime = (int)Config::getMapsDefaultConfig('private.lifetime');
|
||||
|
||||
if($privateMapLifetime > 0){
|
||||
@@ -40,6 +45,8 @@ class MapUpdate extends AbstractCron {
|
||||
$pfDB->exec($sqlDeactivateExpiredMaps, ['lifetime' => $privateMapLifetime]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logEnd(__FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,8 +56,8 @@ class MapUpdate extends AbstractCron {
|
||||
* @throws \Exception
|
||||
*/
|
||||
function deleteMapData(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$deletedMapsCount = 0;
|
||||
$this->logStart(__FUNCTION__);
|
||||
$total = 0;
|
||||
|
||||
if($pfDB = $f3->DB->getDB('PF')){
|
||||
$sqlDeleteDisabledMaps = "SELECT
|
||||
@@ -63,11 +70,11 @@ class MapUpdate extends AbstractCron {
|
||||
|
||||
$disabledMaps = $pfDB->exec($sqlDeleteDisabledMaps, ['deletion_time' => self::DAYS_UNTIL_MAP_DELETION]);
|
||||
|
||||
if($deletedMapsCount = $pfDB->count()){
|
||||
if($total = $pfDB->count()){
|
||||
$mapModel = Pathfinder\AbstractPathfinderModel::getNew('MapModel');
|
||||
foreach($disabledMaps as $data){
|
||||
$mapModel->getById( (int)$data['id'], 3, false );
|
||||
if( !$mapModel->dry() ){
|
||||
if($mapModel->valid()){
|
||||
$mapModel->erase();
|
||||
}
|
||||
$mapModel->reset();
|
||||
@@ -75,9 +82,11 @@ class MapUpdate extends AbstractCron {
|
||||
}
|
||||
}
|
||||
|
||||
// Log ------------------------
|
||||
$log = new \Log('cron_' . __FUNCTION__ . '.log');
|
||||
$log->write( sprintf(self::LOG_TEXT_MAPS, __FUNCTION__, $deletedMapsCount) );
|
||||
$count = $importCount = $total;
|
||||
|
||||
// Log --------------------------------------------------------------------------------------------------------
|
||||
$text = sprintf(self::LOG_TEXT_MAPS_DELETED, $total);
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount, 0, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,9 +96,11 @@ class MapUpdate extends AbstractCron {
|
||||
* @throws \Exception
|
||||
*/
|
||||
function deleteEolConnections(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__, false);
|
||||
$eolExpire = (int)$f3->get('PATHFINDER.CACHE.EXPIRE_CONNECTIONS_EOL');
|
||||
|
||||
$total = 0;
|
||||
$count = 0;
|
||||
if($eolExpire > 0){
|
||||
if($pfDB = $f3->DB->getDB('PF')){
|
||||
$sql = "SELECT
|
||||
@@ -109,19 +120,25 @@ class MapUpdate extends AbstractCron {
|
||||
]);
|
||||
|
||||
if($connectionsData){
|
||||
$total = count($connectionsData);
|
||||
/**
|
||||
* @var $connection Pathfinder\ConnectionModel
|
||||
*/
|
||||
$connection = Pathfinder\AbstractPathfinderModel::getNew('ConnectionModel');
|
||||
foreach($connectionsData as $data){
|
||||
$connection->getById( (int)$data['id'] );
|
||||
if( !$connection->dry() ){
|
||||
if($connection->valid()){
|
||||
$connection->erase();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$importCount = $total;
|
||||
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +148,11 @@ class MapUpdate extends AbstractCron {
|
||||
* @throws \Exception
|
||||
*/
|
||||
function deleteExpiredConnections(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__, false);
|
||||
|
||||
$total = 0;
|
||||
$count = 0;
|
||||
|
||||
$whExpire = (int)$f3->get('PATHFINDER.CACHE.EXPIRE_CONNECTIONS_WH');
|
||||
|
||||
if($whExpire > 0){
|
||||
@@ -155,19 +176,25 @@ class MapUpdate extends AbstractCron {
|
||||
]);
|
||||
|
||||
if($connectionsData){
|
||||
$total = count($connectionsData);
|
||||
/**
|
||||
* @var $connection Pathfinder\ConnectionModel
|
||||
*/
|
||||
$connection = Pathfinder\AbstractPathfinderModel::getNew('ConnectionModel');
|
||||
foreach($connectionsData as $data){
|
||||
$connection->getById( (int)$data['id'] );
|
||||
if( !$connection->dry() ){
|
||||
if($connection->valid()){
|
||||
$connection->erase();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$importCount = $total;
|
||||
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,9 +203,10 @@ class MapUpdate extends AbstractCron {
|
||||
* @param \Base $f3
|
||||
*/
|
||||
function deleteSignatures(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__, false);
|
||||
$signatureExpire = (int)$f3->get('PATHFINDER.CACHE.EXPIRE_SIGNATURES');
|
||||
|
||||
$count = 0;
|
||||
if($signatureExpire > 0){
|
||||
if($pfDB = $f3->DB->getDB('PF')){
|
||||
$sqlDeleteExpiredSignatures = "DELETE `sigs` FROM
|
||||
@@ -190,9 +218,13 @@ class MapUpdate extends AbstractCron {
|
||||
TIMESTAMPDIFF(SECOND, `sigs`.`updated`, NOW() ) > :lifetime
|
||||
";
|
||||
|
||||
$pfDB->exec($sqlDeleteExpiredSignatures, ['lifetime' => $signatureExpire]);
|
||||
$count = $pfDB->exec($sqlDeleteExpiredSignatures, ['lifetime' => $signatureExpire]);
|
||||
}
|
||||
}
|
||||
|
||||
$importCount = $total = $count;
|
||||
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,10 @@ namespace cron;
|
||||
|
||||
class StatisticsUpdate extends AbstractCron {
|
||||
|
||||
const LOG_TEXT_STATISTICS = '%s (%d rows)';
|
||||
/**
|
||||
* log text
|
||||
*/
|
||||
const LOG_TEXT_STATISTICS = ', %5s rows deleted';
|
||||
|
||||
/**
|
||||
* delete old statistics
|
||||
@@ -19,7 +22,7 @@ class StatisticsUpdate extends AbstractCron {
|
||||
* @param \Base $f3
|
||||
*/
|
||||
function deleteStatisticsData(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__);
|
||||
|
||||
$currentYear = (int)date('o');
|
||||
$currentWeek = (int)date('W');
|
||||
@@ -40,8 +43,10 @@ class StatisticsUpdate extends AbstractCron {
|
||||
|
||||
$deletedLogsCount = $pfDB->count();
|
||||
|
||||
// Log ------------------------
|
||||
$log = new \Log('cron_' . __FUNCTION__ . '.log');
|
||||
$log->write( sprintf(self::LOG_TEXT_STATISTICS, __FUNCTION__, $deletedLogsCount) );
|
||||
// Log --------------------------------------------------------------------------------------------------------
|
||||
$total = $count = $importCount = $deletedLogsCount;
|
||||
|
||||
$text = sprintf(self::LOG_TEXT_STATISTICS, $deletedLogsCount);
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount, 0, $text);
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,15 @@ use Model;
|
||||
|
||||
class Universe extends AbstractCron {
|
||||
|
||||
/**
|
||||
* log text
|
||||
*/
|
||||
const LOG_TEXT = '%s type: %s %s/%s peak: %s total: %s msg: %s';
|
||||
const LOG_TEXT_SOV_FW = '%s %4s/%-4s checked, %s peak, %s total, %4s updated [%4s sovChanges, %4s fwChanges], msg: %s';
|
||||
|
||||
/**
|
||||
* log text
|
||||
*/
|
||||
const LOG_TEXT_SOV_FW = ', %4s changes → [%4s sovChanges, %4s fwChanges], msg: %s';
|
||||
|
||||
/**
|
||||
* format counter for output (min length)
|
||||
@@ -292,7 +298,9 @@ class Universe extends AbstractCron {
|
||||
* @throws \Exception
|
||||
*/
|
||||
function updateSovereigntyData(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__);
|
||||
$params = $this->getParams(__FUNCTION__);
|
||||
|
||||
$timeTotalStart = microtime(true);
|
||||
$msg = '';
|
||||
|
||||
@@ -306,13 +314,15 @@ class Universe extends AbstractCron {
|
||||
$fwSystems = $fwSystems['systems'];
|
||||
$ids = !empty($sovData = $sovData['map']) ? array_keys($sovData): [];
|
||||
sort($ids, SORT_NUMERIC);
|
||||
|
||||
$total = count($ids);
|
||||
$offset = ($params['offset'] > 0 && $params['offset'] < $total) ? $params['offset'] : 0;
|
||||
$ids = array_slice($ids, $offset, $params['length']);
|
||||
$importCount = count($ids);
|
||||
$count = 0;
|
||||
|
||||
$changes = [];
|
||||
foreach($ids as $id){
|
||||
$count++;
|
||||
|
||||
// skip wormhole systems -> can not have sov data
|
||||
// -> even though they are returned from sovereignty/map endpoint?!
|
||||
if(
|
||||
@@ -336,11 +346,12 @@ class Universe extends AbstractCron {
|
||||
}
|
||||
$system->reset();
|
||||
|
||||
$count++;
|
||||
|
||||
// stop loop if runtime gets close to "max_execution_time"
|
||||
// -> we need some time for writing *.log file
|
||||
if(!$this->isExecutionTimeLeft($timeTotalStart)){
|
||||
$msg = 'Script execution stopped due to "max_execution_time" limit reached';
|
||||
// TODO store current loop index and use it as new "offset" for next call
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -349,13 +360,12 @@ class Universe extends AbstractCron {
|
||||
return array_unique(array_merge($reducedIds, $changedIds));
|
||||
}, []);
|
||||
|
||||
// Log ------------------------
|
||||
$log = new \Log('cron_' . __FUNCTION__ . '.log');
|
||||
$log->write(sprintf(self::LOG_TEXT_SOV_FW, __FUNCTION__,
|
||||
$count, $importCount, $this->formatMemoryValue(memory_get_peak_usage ()),
|
||||
$this->formatSeconds(microtime(true) - $timeTotalStart),
|
||||
// Log --------------------------------------------------------------------------------------------------------
|
||||
$text = sprintf(self::LOG_TEXT_SOV_FW,
|
||||
count($changedIds), count($changes['sovereignty'] ? : []), count($changes['factionWarfare'] ? : []),
|
||||
$msg));
|
||||
$msg);
|
||||
|
||||
$this->logEnd(__FUNCTION__, $total, $count, $importCount, $offset, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,7 +376,7 @@ class Universe extends AbstractCron {
|
||||
* @throws \Exception
|
||||
*/
|
||||
function updateUniverseSystems(\Base $f3){
|
||||
$this->setMaxExecutionTime();
|
||||
$this->logStart(__FUNCTION__);
|
||||
/**
|
||||
* @var $systemModel Model\Universe\SystemModel
|
||||
* @var $system Model\Universe\SystemModel
|
||||
@@ -378,6 +388,8 @@ class Universe extends AbstractCron {
|
||||
$system->updateModel();
|
||||
}
|
||||
}
|
||||
|
||||
$this->logEnd(__FUNCTION__);
|
||||
}
|
||||
|
||||
}
|
||||
155
app/main/lib/Cron.php
Normal file
155
app/main/lib/Cron.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace lib;
|
||||
|
||||
use Model\Pathfinder;
|
||||
|
||||
/**
|
||||
* Class Cron
|
||||
* @package lib
|
||||
*
|
||||
* @property array $jobs
|
||||
*/
|
||||
class Cron extends \Cron {
|
||||
|
||||
/**
|
||||
* execution time buffer in percent
|
||||
* -> cronJobs that exceed avg. exec. time + DEFAULT_BUFFER_EXEC_TIME show warnings
|
||||
*/
|
||||
const DEFAULT_BUFFER_EXEC_TIME = 20;
|
||||
|
||||
/**
|
||||
* execution memory buffer in percent
|
||||
* -> cronJobs that exceed avg. mem. peak + DEFAULT_BUFFER_MEM_PEAK show warnings
|
||||
*/
|
||||
const DEFAULT_BUFFER_MEM_PEAK = 20;
|
||||
|
||||
/**
|
||||
* extends parent::isDue()
|
||||
* -> adds check for "paused" jobs
|
||||
* @param string $job
|
||||
* @param int $time
|
||||
* @return bool
|
||||
*/
|
||||
public function isDue($job, $time){
|
||||
if($isDue = parent::isDue($job, $time)){
|
||||
// check if job is not paused
|
||||
if($job = $this->getJob($job)){
|
||||
if($job->valid() && $job->isPaused){
|
||||
$isDue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $isDue;
|
||||
}
|
||||
|
||||
public function execute($job, $async = true) {
|
||||
return parent::execute($job, $async);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return string
|
||||
*/
|
||||
public function __get($name){
|
||||
if(in_array($name, ['jobs'])){
|
||||
return $this->$name;
|
||||
}else{
|
||||
return parent::__get($name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $jobConf
|
||||
* @return array
|
||||
*/
|
||||
public function getJobDataFromConf(array $jobConf) : array {
|
||||
return ['handler' => $jobConf[0], 'expr' => $jobConf[1]];
|
||||
}
|
||||
|
||||
/**
|
||||
* get all configured cronjobs (read from cron.ini)
|
||||
* @param array $names
|
||||
* @return array
|
||||
*/
|
||||
public function getJobsConfig(array $names = []) : array {
|
||||
$config = [];
|
||||
|
||||
$jobs = array_filter($this->jobs, function(string $name) use ($names) : bool {
|
||||
return !empty($names) ? in_array($name, $names) : true;
|
||||
}, ARRAY_FILTER_USE_KEY );
|
||||
|
||||
foreach($jobs as $name => $jobConf){
|
||||
$jobConf = $this->getJobDataFromConf($jobConf);
|
||||
if($job = $this->registerJob($name, $jobConf)){
|
||||
// get job config from DB
|
||||
$config[$name] = $job->getData();
|
||||
}else{
|
||||
// job registration failed (e.g. DB connect failed) -> return min config from cron.ini
|
||||
$jobConf = (object)$jobConf;
|
||||
$jobConf->status = ['dbError' => Pathfinder\CronModel::STATUS['dbError']];
|
||||
$jobConf->history = [];
|
||||
$config[$name] = $jobConf;
|
||||
}
|
||||
$config[$name]->exprPreset = $this->checkPreset($config[$name]->expr);
|
||||
}
|
||||
ksort($config);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $jobConf
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function registerJob(string $name, array $jobConf){
|
||||
if($job = $this->getJob($name)){
|
||||
if($job->dry()){
|
||||
$job->name = $name;
|
||||
}
|
||||
$job->setData($jobConf);
|
||||
return $job->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find CronModel by job $name
|
||||
* @param string $name
|
||||
* @return Pathfinder\CronModel|null
|
||||
*/
|
||||
public function getJob(string $name) : ?Pathfinder\CronModel {
|
||||
$job = null;
|
||||
try{
|
||||
/**
|
||||
* @var $job Pathfinder\CronModel
|
||||
*/
|
||||
$jobModel = Pathfinder\AbstractPathfinderModel::getNew('CronModel');
|
||||
// we need to check if table exists here
|
||||
// if not we get an error for later insert/update SQL actions
|
||||
// -> e.g. if job is triggered manually on CLI
|
||||
if($jobModel->tableExists()){
|
||||
$jobModel->getByForeignKey('name', $name);
|
||||
$job = $jobModel;
|
||||
}
|
||||
}catch(\Exception $e){
|
||||
// Cron DB table not exists or other DB issues...
|
||||
}
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* check expression for a preset
|
||||
* @param string $expr
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkPreset(string $expr){
|
||||
if(preg_match('/^@(\w+)$/', $expr,$m)){
|
||||
if(!isset($this->presets[$m[1]]))
|
||||
return false;
|
||||
return $this->presets[$m[1]];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,32 @@ class SQL extends \DB\SQL {
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $table
|
||||
* @return array|null
|
||||
*/
|
||||
public function getTableStatus(?string $table) : ?array {
|
||||
$status = null;
|
||||
$sql = "SHOW TABLE STATUS";
|
||||
$args = null;
|
||||
if(!empty($table)){
|
||||
$sql .= " LIKE :table";
|
||||
$args = [
|
||||
':table' => $table
|
||||
];
|
||||
}
|
||||
|
||||
if(!empty($statusRes = $this->exec($sql, $args))){
|
||||
if(!empty($table)){
|
||||
$status = reset($statusRes);
|
||||
}else{
|
||||
$status = $statusRes;
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* set some default config for this DB
|
||||
* @param string $characterSetDatabase
|
||||
|
||||
58
app/main/lib/format/Image.php
Normal file
58
app/main/lib/format/Image.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace lib\format;
|
||||
|
||||
|
||||
use lib\Config;
|
||||
|
||||
class Image extends \Prefab {
|
||||
|
||||
/**
|
||||
* default image parameter for CCPs image server
|
||||
* @see https://developers.eveonline.com/blog/article/from-image-server-to-a-whole-new-image-service-1
|
||||
*/
|
||||
const DEFAULT_EVE_SRC_CONFIG = [
|
||||
'alliances' => [
|
||||
'variant' => 'logo',
|
||||
'size' => 64 // 64 is less 'blurry' with CSS downscale to 32 than native 32
|
||||
],
|
||||
'corporations' => [
|
||||
'variant' => 'logo',
|
||||
'size' => 64 // 64 is less 'blurry' with CSS downscale to 32 than native 32
|
||||
],
|
||||
'characters' => [
|
||||
'variant' => 'portrait',
|
||||
'size' => 32 // 32 is fine here, no visual difference to 64
|
||||
],
|
||||
'types' => [
|
||||
'variant' => 'icon', // 'render' also works, 64px size is max for 'icon'
|
||||
'size' => 64 // 64 is less 'blurry' with CSS downscale to 32 than native 32
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* build image server src URL
|
||||
* @param string $resourceType
|
||||
* @param int $resourceId
|
||||
* @param int|null $size
|
||||
* @param string|null $resourceVariant
|
||||
* @return string|null
|
||||
*/
|
||||
public function eveSrcUrl(string $resourceType, int $resourceId, ?int $size = null, ?string $resourceVariant = null) : ?string {
|
||||
$url = null;
|
||||
if(
|
||||
$resourceId &&
|
||||
($serviceUrl = rtrim(Config::getPathfinderData('api.ccp_image_server'), '/')) &&
|
||||
($defaults = static::DEFAULT_EVE_SRC_CONFIG[$resourceType])
|
||||
){
|
||||
$parts = [$serviceUrl, $resourceType, $resourceId, $resourceVariant ? : $defaults['variant']];
|
||||
$url = implode('/', $parts);
|
||||
|
||||
$params = ['size' => $size ? : $defaults['size']];
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
24
app/main/lib/format/Number.php
Normal file
24
app/main/lib/format/Number.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace lib\format;
|
||||
|
||||
|
||||
class Number extends \Prefab {
|
||||
|
||||
/**
|
||||
* convert Bytes to string + suffix
|
||||
* @param int $bytes
|
||||
* @param int $precision
|
||||
* @return string
|
||||
*/
|
||||
public function bytesToString($bytes,$precision = 2) : string {
|
||||
$result = '0';
|
||||
if($bytes){
|
||||
$base = log($bytes, 1024);
|
||||
$suffixes = ['', 'KB', 'M', 'GB', 'TB'];
|
||||
$result = round(pow(1024, $base - floor($base)), $precision) .''. $suffixes[(int)floor($base)];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,8 @@ class Resource extends \Prefab {
|
||||
'script' => 'script',
|
||||
'font' => 'font',
|
||||
'document' => 'document',
|
||||
'image' => 'image'
|
||||
'image' => 'image',
|
||||
'url' => ''
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -51,7 +52,8 @@ class Resource extends \Prefab {
|
||||
'script' => '',
|
||||
'font' => '',
|
||||
'document' => '',
|
||||
'image' => ''
|
||||
'image' => '',
|
||||
'url' => ''
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -83,12 +85,13 @@ class Resource extends \Prefab {
|
||||
private $resources = [];
|
||||
|
||||
/**
|
||||
* set option
|
||||
* set or extend option
|
||||
* @param string $option
|
||||
* @param $value
|
||||
* @param bool $extend
|
||||
*/
|
||||
public function setOption(string $option, $value){
|
||||
$this->$option = $value;
|
||||
public function setOption(string $option, $value, bool $extend = false){
|
||||
$this->$option = ($extend && is_array($value) && is_array($this->$option)) ? array_merge($this->$option, $value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +120,8 @@ class Resource extends \Prefab {
|
||||
* @return string
|
||||
*/
|
||||
public function getLink(string $group, string $file) : string {
|
||||
$link = $this->getPath($group) . '/' . $file;
|
||||
// $group 'url' expect full qualified URLs
|
||||
$link = ($group == 'url' ? '' : $this->getPath($group) . '/') . $file;
|
||||
// add extension if not already part of the file
|
||||
// -> allows switching between extensions (e.g. .jpg, .png) for the same image
|
||||
$link .= empty(pathinfo($file, PATHINFO_EXTENSION)) ? '.' . $this->getFileExtension($group) : '';
|
||||
@@ -191,8 +195,8 @@ class Resource extends \Prefab {
|
||||
if( empty($conf['options']['rel']) ){
|
||||
$conf['options']['rel'] = self::ATTR_REL;
|
||||
}
|
||||
if( empty($conf['options']['as']) ){
|
||||
$conf['options']['as'] = $group;
|
||||
if( empty($conf['options']['as']) && !empty($attrAs = $this->getLinkAttrAs($group)) ){
|
||||
$conf['options']['as'] = $attrAs;
|
||||
}
|
||||
if( empty($conf['options']['type']) && !empty($attrType = $this->getLinkAttrType($group)) ){
|
||||
$conf['options']['type'] = $attrType;
|
||||
@@ -202,7 +206,6 @@ class Resource extends \Prefab {
|
||||
$conf['options'] = $conf['options'] + $additionalAttr;
|
||||
}
|
||||
}
|
||||
unset($options); // unset ref
|
||||
}
|
||||
unset($resources); // unset ref
|
||||
}
|
||||
@@ -237,6 +240,12 @@ class Resource extends \Prefab {
|
||||
return isset(self::ATTR_ADD[$group]) ? self::ATTR_ADD[$group] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* get file extension by $group
|
||||
* -> e.g. or fonts
|
||||
* @param string $group
|
||||
* @return string
|
||||
*/
|
||||
protected function getFileExtension(string $group) : string {
|
||||
return isset($this->fileExt[$group]) ? $this->fileExt[$group] : '';
|
||||
}
|
||||
|
||||
@@ -88,6 +88,12 @@ abstract class AbstractModel extends Cortex {
|
||||
*/
|
||||
protected $validationError = [];
|
||||
|
||||
|
||||
/**
|
||||
* default charset for table
|
||||
*/
|
||||
const DEFAULT_CHARSET = 'utf8mb4';
|
||||
|
||||
/**
|
||||
* default caching time of field schema - seconds
|
||||
*/
|
||||
@@ -137,8 +143,9 @@ abstract class AbstractModel extends Cortex {
|
||||
* @param null $table
|
||||
* @param null $fluid
|
||||
* @param int $ttl
|
||||
* @param string $charset
|
||||
*/
|
||||
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = self::DEFAULT_TTL){
|
||||
public function __construct($db = null, $table = null, $fluid = null, $ttl = self::DEFAULT_TTL, $charset = self::DEFAULT_CHARSET){
|
||||
|
||||
if(!is_object($db)){
|
||||
$db = self::getF3()->DB->getDB(static::DB_ALIAS);
|
||||
@@ -149,6 +156,9 @@ abstract class AbstractModel extends Cortex {
|
||||
self::getF3()->set('HALT', true);
|
||||
}
|
||||
|
||||
// set charset -> used during table setup()
|
||||
$this->charset = $charset;
|
||||
|
||||
$this->addStaticFieldConfig();
|
||||
|
||||
parent::__construct($db, $table, $fluid, $ttl);
|
||||
@@ -181,6 +191,14 @@ abstract class AbstractModel extends Cortex {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* checks whether table exists on DB
|
||||
* @return bool
|
||||
*/
|
||||
public function tableExists() : bool {
|
||||
return is_object($this->db) ? $this->db->tableExists($this->table) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear existing table Schema cache
|
||||
* @return bool
|
||||
@@ -912,7 +930,7 @@ abstract class AbstractModel extends Cortex {
|
||||
*/
|
||||
protected function isOutdated() : bool {
|
||||
$outdated = true;
|
||||
if(!$this->dry()){
|
||||
if($this->valid()){
|
||||
try{
|
||||
$timezone = $this->getF3()->get('getTimeZone')();
|
||||
$currentTime = new \DateTime('now', $timezone);
|
||||
|
||||
@@ -12,23 +12,83 @@ use DB\SQL\Schema;
|
||||
|
||||
abstract class AbstractSystemApiBasicModel extends AbstractPathfinderModel {
|
||||
|
||||
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = 0){
|
||||
/**
|
||||
* column count for data
|
||||
*/
|
||||
const DATA_COLUMN_COUNT = 24;
|
||||
|
||||
/**
|
||||
* column name prefix for data columns
|
||||
*/
|
||||
const DATA_COLUMN_PREFIX = 'value';
|
||||
|
||||
/**
|
||||
* AbstractSystemApiBasicModel constructor.
|
||||
* @param null $db
|
||||
* @param null $table
|
||||
* @param null $fluid
|
||||
* @param int $ttl
|
||||
*/
|
||||
public function __construct($db = null, $table = null, $fluid = null, $ttl = 0){
|
||||
$this->addStaticKillFieldConfig();
|
||||
|
||||
parent::__construct($db, $table, $fluid, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* get data
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function getData(){
|
||||
$data = (object)[];
|
||||
$data->systemId = $this->getRaw('systemId');
|
||||
$data->values = $this->getValues();
|
||||
$data->updated = strtotime($this->updated);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* get all "valX" column data as array
|
||||
* -> "start" (most recent) value is stored in column name stored in "lastUpdatedValue" column
|
||||
* @return array
|
||||
*/
|
||||
protected function getValues() : array {
|
||||
$valueColumnNames = range(1, static::DATA_COLUMN_COUNT);
|
||||
$preFixer = function(&$value, $key, $prefix){
|
||||
$value = $prefix . $value;
|
||||
};
|
||||
array_walk($valueColumnNames, $preFixer, static::DATA_COLUMN_PREFIX);
|
||||
|
||||
$valueColumns = array_intersect_key($this->cast(null, 0), array_flip($valueColumnNames));
|
||||
$lastUpdatedValue = $this->lastUpdatedValue ? : 1;
|
||||
|
||||
// bring values in correct order based on "lastUpdatedValue"
|
||||
$valueColumnsA = array_slice($valueColumns, $lastUpdatedValue - static::DATA_COLUMN_COUNT , null, true);
|
||||
$valueColumnsB = array_slice($valueColumns, 0, $lastUpdatedValue, true);
|
||||
|
||||
return array_values($valueColumnsA + $valueColumnsB);
|
||||
}
|
||||
|
||||
/**
|
||||
* extent the fieldConf Array with static fields for each table
|
||||
*/
|
||||
private function addStaticKillFieldConfig(){
|
||||
if(is_array($this->fieldConf)){
|
||||
$staticFieldConfig = [];
|
||||
for($i = 1; $i <= 24; $i++){
|
||||
$staticFieldConfig['value' . $i] = [
|
||||
|
||||
$staticFieldConfig['lastUpdatedValue'] = [
|
||||
'type' => Schema::DT_INT,
|
||||
'nullable' => false,
|
||||
'default' => 1,
|
||||
'index' => true
|
||||
];
|
||||
|
||||
for($i = 1; $i <= static::DATA_COLUMN_COUNT; $i++){
|
||||
$staticFieldConfig[static::DATA_COLUMN_PREFIX . $i] = [
|
||||
'type' => Schema::DT_INT,
|
||||
'nullable' => false,
|
||||
'default' => 0,
|
||||
'default' => 0
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1243,7 +1243,8 @@ class CharacterModel extends AbstractPathfinderModel {
|
||||
$skipRest = false;
|
||||
$logHistoryData = $this->filterLogsHistory(function(array $historyEntry) use ($mapId, $systemId, &$skipRest) : bool {
|
||||
$addEntry = false;
|
||||
if(in_array($mapId, (array)$historyEntry['mapIds'], true)){
|
||||
//if(in_array($mapId, (array)$historyEntry['mapIds'], true)){ // $historyEntry is checked by EACH map -> would auto add system on map switch! #827
|
||||
if(!empty((array)$historyEntry['mapIds'])){ // if $historyEntry was already checked by ANY other map -> no further checks
|
||||
$skipRest = true;
|
||||
}
|
||||
|
||||
|
||||
287
app/main/model/pathfinder/cronmodel.php
Normal file
287
app/main/model/pathfinder/cronmodel.php
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Model\Pathfinder;
|
||||
|
||||
use DB\SQL\Schema;
|
||||
|
||||
class CronModel extends AbstractPathfinderModel {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'cron';
|
||||
|
||||
const STATUS = [
|
||||
'unknown' => [
|
||||
'type' => 'warning',
|
||||
'icon' => 'question',
|
||||
'msg' => 'No status information available'
|
||||
],
|
||||
'dbError' => [
|
||||
'type' => 'warning',
|
||||
'icon' => 'fa-exclamation-triangle',
|
||||
'msg' => 'Failed to sync job data with DB'
|
||||
],
|
||||
'notExecuted' => [
|
||||
'type' => 'hint',
|
||||
'icon' => 'fa-bolt',
|
||||
'msg' => 'Has not been executed'
|
||||
],
|
||||
'notFinished' => [
|
||||
'type' => 'danger',
|
||||
'icon' => 'fa-clock',
|
||||
'msg' => 'Not finished within max exec. time'
|
||||
],
|
||||
'inProgress' => [
|
||||
'type' => 'success',
|
||||
'icon' => 'fa-play',
|
||||
'msg' => 'Started. In execution…'
|
||||
],
|
||||
'isPaused' => [
|
||||
'type' => 'warning',
|
||||
'icon' => 'fa-pause',
|
||||
'msg' => 'Paused. No execution on next time trigger (skip further execution)'
|
||||
],
|
||||
'onHold' => [
|
||||
'type' => 'information',
|
||||
'icon' => 'fa-history fa-flip-horizontal',
|
||||
'msg' => 'Is active. Waiting for next trigger…'
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldConf = [
|
||||
'name' => [
|
||||
'type' => Schema::DT_VARCHAR128,
|
||||
'nullable' => false,
|
||||
'default' => '',
|
||||
'index' => true,
|
||||
'unique' => true,
|
||||
'validate' => 'notEmpty'
|
||||
],
|
||||
'handler' => [
|
||||
'type' => Schema::DT_VARCHAR256,
|
||||
'nullable' => false,
|
||||
'default' => '',
|
||||
'index' => true,
|
||||
'unique' => true,
|
||||
'validate' => 'notEmpty'
|
||||
],
|
||||
'expr' => [
|
||||
'type' => Schema::DT_VARCHAR128,
|
||||
'nullable' => false,
|
||||
'default' => '',
|
||||
'validate' => 'notEmpty'
|
||||
],
|
||||
'isPaused' => [
|
||||
'type' => Schema::DT_BOOL,
|
||||
'nullable' => false,
|
||||
'default' => 0
|
||||
],
|
||||
'lastExecStart' => [
|
||||
'type' => Schema::DT_DOUBLE,
|
||||
'nullable' => true,
|
||||
'default' => null
|
||||
],
|
||||
'lastExecEnd' => [
|
||||
'type' => Schema::DT_DOUBLE,
|
||||
'nullable' => true,
|
||||
'default' => null
|
||||
],
|
||||
'lastExecMemPeak' => [
|
||||
'type' => Schema::DT_FLOAT,
|
||||
'nullable' => true,
|
||||
'default' => null
|
||||
],
|
||||
'lastExecState' => [
|
||||
'type' => self::DT_JSON
|
||||
],
|
||||
'history' => [
|
||||
'type' => self::DT_JSON
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* set data by associative array
|
||||
* @param array $data
|
||||
*/
|
||||
public function setData(array $data){
|
||||
$this->copyfrom($data, ['handler', 'expr', 'lastExecStart', 'lastExecEnd', 'lastExecMemPeak', 'lastExecState']);
|
||||
}
|
||||
|
||||
/**
|
||||
* get data
|
||||
* @return object
|
||||
*/
|
||||
public function getData(){
|
||||
$data = (object) [];
|
||||
$data->id = $this->_id;
|
||||
$data->name = $this->name;
|
||||
$data->handler = $this->handler;
|
||||
$data->expr = $this->expr;
|
||||
$data->logFile = $this->logFileExists();
|
||||
|
||||
$data->lastExecStart = $this->lastExecStart;
|
||||
$data->lastExecEnd = $this->lastExecEnd;
|
||||
$data->lastExecMemPeak = $this->lastExecMemPeak;
|
||||
$data->lastExecDuration = $this->getExecDuration();
|
||||
$data->lastExecState = $this->lastExecState;
|
||||
|
||||
$data->isPaused = $this->isPaused;
|
||||
$data->status = $this->getStatus();
|
||||
$data->history = $this->getHistory(true);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* setter for system alias
|
||||
* @param string $lastExecStart
|
||||
* @return string
|
||||
*/
|
||||
public function set_lastExecStart($lastExecStart){
|
||||
$this->logState();
|
||||
return $lastExecStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* log execution "state" for prev run in 'history' column
|
||||
*/
|
||||
protected function logState(){
|
||||
$this->history = $this->getHistory() ? : null;
|
||||
// reset data from last run
|
||||
$this->lastExecEnd = null;
|
||||
$this->lastExecMemPeak = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $addLastIfFinished
|
||||
* @return array
|
||||
*/
|
||||
protected function getHistory(bool $addLastIfFinished = false) : array {
|
||||
$history = $this->history ? : [];
|
||||
|
||||
if(!is_null($this->lastExecStart)){
|
||||
if(!$addLastIfFinished || !is_null($this->lastExecEnd)){
|
||||
array_unshift($history, [
|
||||
'lastExecStart' => $this->lastExecStart,
|
||||
'lastExecMemPeak' => $this->lastExecMemPeak,
|
||||
'lastExecDuration' => (!$this->inExec() && !$this->isTimedOut()) ? $this->getExecDuration() : 0,
|
||||
'status' => array_intersect(array_keys($this->getStatus()), ['inProgress', 'notFinished'])
|
||||
]);
|
||||
$history = array_slice($history, 0, 10);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $history;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current job status based on its current data
|
||||
* @return array
|
||||
*/
|
||||
protected function getStatus() : array {
|
||||
$status = [];
|
||||
|
||||
if($this->isPaused){
|
||||
$status['isPaused'] = self::STATUS['isPaused'];
|
||||
}
|
||||
|
||||
if($this->inExec() && !$this->isTimedOut()){
|
||||
$status['inProgress'] = self::STATUS['inProgress'];
|
||||
}
|
||||
|
||||
if(empty($status)){
|
||||
$status['onHold'] = self::STATUS['onHold'];
|
||||
}
|
||||
|
||||
if($this->isTimedOut()){
|
||||
$status['notFinished'] = self::STATUS['notFinished'];
|
||||
}
|
||||
|
||||
if(!$this->lastExecStart){
|
||||
$status['notExecuted'] = self::STATUS['notExecuted'];
|
||||
}
|
||||
|
||||
return empty($status) ? ['unknown' => self::STATUS['unknown']] : array_reverse($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* based on the data on DB, job is marked at "in progress"
|
||||
* @return bool
|
||||
*/
|
||||
protected function inExec() : bool {
|
||||
return $this->lastExecStart && !$this->lastExecEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function isTimedOut() : bool {
|
||||
$timedOut = false;
|
||||
if($this->lastExecStart){
|
||||
$timezone = self::getF3()->get('getTimeZone')();
|
||||
$startTime = \DateTime::createFromFormat(
|
||||
'U.u',
|
||||
number_format($this->lastExecStart, 6, '.', ''),
|
||||
$timezone
|
||||
);
|
||||
|
||||
$timeBuffer = 60 * 60;
|
||||
$startTime->add(new \DateInterval('PT' . $timeBuffer . 'S'));
|
||||
|
||||
if($this->lastExecEnd){
|
||||
$endTime = \DateTime::createFromFormat(
|
||||
'U.u',
|
||||
number_format($this->lastExecEnd, 6, '.', ''),
|
||||
$timezone
|
||||
);
|
||||
}else{
|
||||
$endTime = new \DateTime('now', $timezone);
|
||||
}
|
||||
|
||||
$timedOut = $startTime < $endTime;
|
||||
}
|
||||
|
||||
return $timedOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|null
|
||||
*/
|
||||
protected function getExecDuration() : ?float {
|
||||
$duration = null;
|
||||
if($this->lastExecStart && $this->lastExecEnd){
|
||||
$duration = (float)$this->lastExecEnd - (float)$this->lastExecStart;
|
||||
}
|
||||
|
||||
return $duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* extract function name from $this->handler
|
||||
* -> it is used for the log file name
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getLogFileName() : ?string {
|
||||
return ($this->handler && preg_match('/^.*->(\w+)$/', $this->handler,$m)) ? 'cron_' . $m[1] . '.log' : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks whether a log file exists for this cronjob
|
||||
* -> will be created after job execution
|
||||
* @return string
|
||||
*/
|
||||
protected function logFileExists() : ?string {
|
||||
$filePath = null;
|
||||
if($file = $this->getLogFileName()){
|
||||
$filePath = is_file($path = self::getF3()->get('LOGS') . $file) ? $path : null;
|
||||
}
|
||||
return $filePath;
|
||||
}
|
||||
}
|
||||
@@ -134,11 +134,12 @@ class StructureModel extends AbstractPathfinderModel {
|
||||
if($corporationId){
|
||||
if($corporationId !== $oldCorporationId){
|
||||
// make sure there is already corporation data available for new corporationId
|
||||
// -> $ttl = 0 is important! Otherwise "bulk" update for structures could fail
|
||||
/**
|
||||
* @var CorporationModel $corporation
|
||||
*/
|
||||
$corporation = $this->rel('corporationId');
|
||||
$corporation->getById($corporationId);
|
||||
$corporation->getById($corporationId, 0);
|
||||
if($corporation->dry()){
|
||||
$corporationId = null;
|
||||
}
|
||||
@@ -234,7 +235,7 @@ class StructureModel extends AbstractPathfinderModel {
|
||||
* @param int $systemId
|
||||
*/
|
||||
public function getByName(CorporationModel $corporation, string $name, int $systemId){
|
||||
if( !$corporation->dry() && $name){
|
||||
if($corporation->valid() && $name){
|
||||
$this->has('structureCorporations', ['corporationId = :corporationId', ':corporationId' => $corporation->_id]);
|
||||
$this->load(['name = :name AND systemId = :systemId AND active = :active',
|
||||
':name' => $name,
|
||||
|
||||
@@ -12,8 +12,14 @@ use DB\SQL\Schema;
|
||||
|
||||
class SystemFactionKillModel extends AbstractSystemApiBasicModel {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'system_kills_factions';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldConf = [
|
||||
'active' => [
|
||||
'type' => Schema::DT_BOOL,
|
||||
|
||||
@@ -12,8 +12,14 @@ use DB\SQL\Schema;
|
||||
|
||||
class SystemJumpModel extends AbstractSystemApiBasicModel {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'system_jumps';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldConf = [
|
||||
'active' => [
|
||||
'type' => Schema::DT_BOOL,
|
||||
|
||||
@@ -12,8 +12,14 @@ use DB\SQL\Schema;
|
||||
|
||||
class SystemPodKillModel extends AbstractSystemApiBasicModel {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'system_kills_pods';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldConf = [
|
||||
'active' => [
|
||||
'type' => Schema::DT_BOOL,
|
||||
|
||||
@@ -12,8 +12,14 @@ use DB\SQL\Schema;
|
||||
|
||||
class SystemShipKillModel extends AbstractSystemApiBasicModel {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'system_kills_ships';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldConf = [
|
||||
'active' => [
|
||||
'type' => Schema::DT_BOOL,
|
||||
|
||||
@@ -167,7 +167,7 @@ class SystemSignatureModel extends AbstractMapTrackingModel {
|
||||
*/
|
||||
protected function validate_name(string $key, string $val): bool {
|
||||
$valid = true;
|
||||
if(mb_strlen($val) < 3){
|
||||
if(!mb_ereg('^[a-zA-Z]{3}-\d{3}$', $val)){
|
||||
$valid = false;
|
||||
$this->throwValidationException($key);
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ class SystemModel extends AbstractUniverseModel {
|
||||
// 'Shattered' systems have ONLY planets named with '(shattered)'
|
||||
// -> system 'Thera' has '(shattered)' AND other planets -> not shattered.
|
||||
// -> system 'J164104, 'J115422' - the only non-shattered wormholes which have a shattered planet -> not shattered.
|
||||
$data->shattered = count(array_filter($planetsData, function(object $planetData){
|
||||
$data->shattered = count(array_filter($planetsData, function($planetData){
|
||||
return property_exists($planetData, 'type') &&
|
||||
(strpos(strtolower($planetData->type->name), '(shattered)') !== false);
|
||||
})) == count($planetsData);
|
||||
|
||||
@@ -13,8 +13,8 @@ NAME = Pathfinder
|
||||
; Version is used for CSS/JS cache busting and is part of the URL for static resources:
|
||||
; e.g. public/js/vX.X.X/app.js
|
||||
; Syntax: String (current version)
|
||||
; Default: v1.5.4
|
||||
VERSION = v1.5.4
|
||||
; Default: v1.5.5
|
||||
VERSION = v1.5.5
|
||||
|
||||
; Contact information [optional]
|
||||
; Shown on 'licence', 'contact' page.
|
||||
@@ -375,7 +375,7 @@ LOG_LINES = 1000
|
||||
|
||||
; API =============================================================================================
|
||||
[PATHFINDER.API]
|
||||
CCP_IMAGE_SERVER = https://image.eveonline.com
|
||||
CCP_IMAGE_SERVER = https://images.evetech.net
|
||||
Z_KILLBOARD = https://zkillboard.com/api
|
||||
EVEEYE = https://eveeye.com
|
||||
DOTLAN = http://evemaps.dotlan.net
|
||||
|
||||
@@ -80,5 +80,9 @@ MAXMEMORY_POLICY = allkeys-lru
|
||||
NODE = 6.0
|
||||
NPM = 3.10.0
|
||||
|
||||
[REQUIREMENTS.CRON]
|
||||
CLI = 1
|
||||
LOG = 1
|
||||
|
||||
[REQUIREMENTS.DATA]
|
||||
NEIGHBOURS = 5201
|
||||
|
||||
@@ -15,6 +15,6 @@ $f3->config('app/config.ini', true);
|
||||
lib\Config::instance($f3);
|
||||
|
||||
// initiate cron-jobs
|
||||
Cron::instance();
|
||||
lib\Cron::instance();
|
||||
|
||||
$f3->run();
|
||||
@@ -37,6 +37,7 @@ requirejs.config({
|
||||
slidebars: 'lib/slidebars', // v2.0.2 Slidebars - side menu plugin https://www.adchsm.com/slidebars/
|
||||
jsPlumb: 'lib/jsplumb', // v2.9.3 jsPlumb main map draw plugin http://jsplumb.github.io/jsplumb/home.html
|
||||
farahey: 'lib/farahey', // v1.1.2 jsPlumb "magnetizing" plugin extension - https://github.com/ThomasChan/farahey
|
||||
easyTimer: 'lib/easytimer.min', // v4.0.2 EasyTimer - Timer/Chronometer/Countdown library - http://albert-gonzalez.github.io/easytimer.js
|
||||
customScrollbar: 'lib/jquery.mCustomScrollbar.min', // v3.1.5 Custom scroll bars - http://manos.malihu.gr
|
||||
mousewheel: 'lib/jquery.mousewheel.min', // v3.1.13 Mousewheel - https://github.com/jquery/jquery-mousewheel
|
||||
xEditable: 'lib/bootstrap-editable.min', // v1.5.1 X-editable - in placed editing
|
||||
@@ -44,8 +45,7 @@ requirejs.config({
|
||||
raphael: 'lib/raphael.min', // v2.2.8 Raphaël - required for morris - https://dmitrybaranovskiy.github.io/raphael
|
||||
bootbox: 'lib/bootbox.min', // v5.2.0 Bootbox.js - custom dialogs - http://bootboxjs.com
|
||||
easyPieChart: 'lib/jquery.easypiechart.min', // v2.1.6 Easy Pie Chart - HTML 5 pie charts - http://rendro.github.io/easy-pie-chart
|
||||
peityInlineChart: 'lib/jquery.peity.min', // v3.2.1 Inline Chart - http://benpickles.github.io/peity/
|
||||
dragToSelect: 'lib/jquery.dragToSelect', // v1.1 Drag to Select - http://andreaslagerkvist.com/jquery/drag-to-select
|
||||
peityInlineChart: 'lib/jquery.peity.min', // v3.3.0 Inline Chart - http://benpickles.github.io/peity/
|
||||
hoverIntent: 'lib/jquery.hoverIntent.min', // v1.10.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
|
||||
select2: 'lib/select2.min', // v4.0.3 Drop Down customization - https://select2.github.io
|
||||
validator: 'lib/validator.min', // v0.10.1 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
|
||||
@@ -53,7 +53,7 @@ requirejs.config({
|
||||
blueImpGallery: 'lib/blueimp-gallery', // v2.21.3 Image Gallery - https://github.com/blueimp/Gallery
|
||||
blueImpGalleryHelper: 'lib/blueimp-helper', // helper function for Blue Imp Gallery
|
||||
blueImpGalleryBootstrap: 'lib/bootstrap-image-gallery', // v3.4.2 Bootstrap extension for Blue Imp Gallery - https://blueimp.github.io/Bootstrap-Image-Gallery
|
||||
bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.5 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
|
||||
bootstrapConfirmation: 'lib/bootstrap-confirmation.min', // v1.0.7 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
|
||||
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
|
||||
lazyload: 'lib/jquery.lazyload.min', // v1.9.7 LazyLoader images - https://appelsiini.net/projects/lazyload/
|
||||
sortable: 'lib/sortable.min', // v1.6.0 Sortable - drag&drop reorder - https://github.com/rubaxa/Sortable
|
||||
@@ -147,9 +147,6 @@ requirejs.config({
|
||||
peityInlineChart: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
dragToSelect: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
hoverIntent: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
|
||||
@@ -1,21 +1,118 @@
|
||||
/**
|
||||
* Created by exodus4d on 06.07.2015.
|
||||
* Created by exodus4d
|
||||
* static signature types
|
||||
*
|
||||
* (*) marked fields are in-game verified and
|
||||
* proofed, signature names (copy & paste from scanning window)
|
||||
*/
|
||||
|
||||
define(['jquery'], ($) => {
|
||||
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
// signature sources
|
||||
// http://de.sistersprobe.wikia.com/wiki/EVE_Sister_Core_Scanner_Probe_Wiki
|
||||
|
||||
// Combat sites ===================================================================================================
|
||||
|
||||
let c1Combat = {
|
||||
1: 'Perimeter Ambush Point',
|
||||
2: 'Perimeter Camp',
|
||||
3: 'Phase Catalyst Node',
|
||||
4: 'The Line'
|
||||
};
|
||||
|
||||
let c2Combat = {
|
||||
1: 'Perimeter Checkpoint',
|
||||
2: 'Perimeter Hangar',
|
||||
3: 'The Ruins of Enclave Cohort 27',
|
||||
4: 'Sleeper Data Sanctuary'
|
||||
};
|
||||
|
||||
let c3Combat = {
|
||||
1: 'Fortification Frontier Stronghold',
|
||||
2: 'Outpost Frontier Stronghold',
|
||||
3: 'Solar Cell',
|
||||
4: 'The Oruze Construct'
|
||||
};
|
||||
|
||||
let c4Combat = {
|
||||
1: 'Frontier Barracks',
|
||||
2: 'Frontier Command Post',
|
||||
3: 'Integrated Terminus',
|
||||
4: 'Sleeper Information Sanctum'
|
||||
};
|
||||
|
||||
let c5Combat = {
|
||||
1: 'Core Garrison', //*
|
||||
2: 'Core Stronghold', //*
|
||||
3: 'Oruze Osobnyk', //*
|
||||
4: 'Quarantine Area'
|
||||
};
|
||||
|
||||
let c6Combat = {
|
||||
1: 'Core Citadel', //*
|
||||
2: 'Core Bastion', //*
|
||||
3: 'Strange Energy Readings', //*
|
||||
4: 'The Mirror' //*
|
||||
};
|
||||
|
||||
// Thera WH
|
||||
let c12Combat = {
|
||||
1: 'Epicenter',
|
||||
2: 'Expedition Command Outpost Wreck',
|
||||
3: 'Planetary Colonization Office Wreck',
|
||||
4: 'Testing Facilities'
|
||||
};
|
||||
|
||||
// Drifter Sentinel WH
|
||||
let c14Combat = {
|
||||
1: 'Monolith',
|
||||
2: 'Wormhole in Rock Circle',
|
||||
3: 'Opposing Spatial Rifts',
|
||||
4: 'Sleeper Enclave Debris',
|
||||
5: 'Crystal Resource'
|
||||
};
|
||||
|
||||
// Drifter Barbican WH
|
||||
let c15Combat = {
|
||||
1: 'Wrecked Ships',
|
||||
2: 'Unstable Wormhole',
|
||||
3: 'Spatial Rift',
|
||||
4: 'Heavily Guarded Spatial Rift',
|
||||
5: 'Crystals'
|
||||
};
|
||||
|
||||
// Drifter Vidette WH
|
||||
let c16Combat = {
|
||||
1: 'Ship Graveyard',
|
||||
2: 'Sleeper Engineering Station',
|
||||
3: 'Spatial Rift',
|
||||
4: 'Sleeper Enclave in Coral Rock',
|
||||
5: 'Crystals and Stone Circle'
|
||||
};
|
||||
|
||||
// Drifter Conflux WH
|
||||
let c17Combat = {
|
||||
1: 'Monolith',
|
||||
2: 'Caged Wormhole',
|
||||
3: 'Rock Formation and Wormhole',
|
||||
4: 'Particle Acceleration Array',
|
||||
5: 'Guarded Asteroid Station'
|
||||
};
|
||||
|
||||
// Drifter Redoubt WH
|
||||
let c18Combat = {
|
||||
1: 'Ship Graveyard',
|
||||
2: 'Caged Wormhole',
|
||||
3: 'Spatial Rift Generator',
|
||||
4: 'Sleeper Enclave',
|
||||
5: 'Hollow Asteroid'
|
||||
};
|
||||
|
||||
// Relic sites ====================================================================================================
|
||||
|
||||
// NullSec Relic sites, which can also spawn in C1, C2, C3 wormholes
|
||||
let nullSecRelicSites = {
|
||||
let nullRelic = {
|
||||
10: 'Ruined Angel Crystal Quarry',
|
||||
11: 'Ruined Angel Monument Site',
|
||||
12: 'Ruined Angel Science Outpost',
|
||||
@@ -38,8 +135,40 @@ define(['jquery'], ($) => {
|
||||
29: 'Ruined Serpentis Temple Site'
|
||||
};
|
||||
|
||||
let c1Relic = Object.assign({}, nullRelic, {
|
||||
1: 'Forgotten Perimeter Coronation Platform', //*
|
||||
2: 'Forgotten Perimeter Power Array' //*
|
||||
});
|
||||
|
||||
let c2Relic = Object.assign({}, nullRelic, {
|
||||
1: 'Forgotten Perimeter Gateway', //*
|
||||
2: 'Forgotten Perimeter Habitation Coils' //*
|
||||
});
|
||||
|
||||
let c3Relic = Object.assign({}, nullRelic, {
|
||||
1: 'Forgotten Frontier Quarantine Outpost', //*
|
||||
2: 'Forgotten Frontier Recursive Depot' //*
|
||||
});
|
||||
|
||||
let c4Relic = {
|
||||
1: 'Forgotten Frontier Conversion Module',
|
||||
2: 'Forgotten Frontier Evacuation Center'
|
||||
};
|
||||
|
||||
let c5Relic = {
|
||||
1: 'Forgotten Core Data Field',
|
||||
2: 'Forgotten Core Information Pen'
|
||||
};
|
||||
|
||||
let c6Relic = {
|
||||
1: 'Forgotten Core Assembly Hall', //*
|
||||
2: 'Forgotten Core Circuitry Disassembler' //*
|
||||
};
|
||||
|
||||
// Data sites =====================================================================================================
|
||||
|
||||
// NulSec Data sites, which can also spawn in C1, C2, C3 wormholes
|
||||
let nullSecDataSites = {
|
||||
let nullData = {
|
||||
10: 'Abandoned Research Complex DA005',
|
||||
11: 'Abandoned Research Complex DA015',
|
||||
12: 'Abandoned Research Complex DC007',
|
||||
@@ -68,413 +197,390 @@ define(['jquery'], ($) => {
|
||||
35: 'Central Serpentis Survey Site'
|
||||
};
|
||||
|
||||
let c1Data = Object.assign({}, nullData, {
|
||||
1: 'Unsecured Perimeter Amplifier', //*
|
||||
2: 'Unsecured Perimeter Information Center' //*
|
||||
});
|
||||
|
||||
let c2Data = Object.assign({}, nullData, {
|
||||
1: 'Unsecured Perimeter Comms Relay', //*
|
||||
2: 'Unsecured Perimeter Transponder Farm' //*
|
||||
});
|
||||
|
||||
let c3Data = Object.assign({}, nullData, {
|
||||
1: 'Unsecured Frontier Database', //*
|
||||
2: 'Unsecured Frontier Receiver' //*
|
||||
});
|
||||
|
||||
let c4Data = {
|
||||
1: 'Unsecured Frontier Digital Nexus',
|
||||
2: 'Unsecured Frontier Trinary Hub'
|
||||
};
|
||||
|
||||
let c5Data = {
|
||||
1: 'Unsecured Frontier Enclave Relay',
|
||||
2: 'Unsecured Frontier Server Bank'
|
||||
};
|
||||
|
||||
let c6Data = {
|
||||
1: 'Unsecured Core Backup Array', //*
|
||||
2: 'Unsecured Core Emergence' //*
|
||||
};
|
||||
|
||||
// Gas sites ======================================================================================================
|
||||
|
||||
let c1Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir' //*
|
||||
};
|
||||
|
||||
let c2Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir' //*
|
||||
};
|
||||
|
||||
let c3Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir', //*
|
||||
6: 'Bountiful Frontier Reservoir', //*
|
||||
7: 'Vast Frontier Reservoir' //*
|
||||
};
|
||||
|
||||
let c4Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir', //*
|
||||
6: 'Vast Frontier Reservoir', //*
|
||||
7: 'Bountiful Frontier Reservoir' //*
|
||||
};
|
||||
|
||||
let c5Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Minor Perimeter Reservoir', //*
|
||||
3: 'Ordinary Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Token Perimeter Reservoir', //*
|
||||
6: 'Bountiful Frontier Reservoir', //*
|
||||
7: 'Vast Frontier Reservoir', //*
|
||||
8: 'Instrumental Core Reservoir', //*
|
||||
9: 'Vital Core Reservoir' //*
|
||||
};
|
||||
|
||||
let c6Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Minor Perimeter Reservoir', //*
|
||||
3: 'Ordinary Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Token Perimeter Reservoir', //*
|
||||
6: 'Bountiful Frontier Reservoir', //*
|
||||
7: 'Vast Frontier Reservoir', //*
|
||||
8: 'Instrumental Core Reservoir', //*
|
||||
9: 'Vital Core Reservoir' //*
|
||||
};
|
||||
|
||||
// Ore sites ======================================================================================================
|
||||
|
||||
let c1Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Isolated Core Deposit', //*
|
||||
6: 'Uncommon Core Deposit' //*
|
||||
};
|
||||
|
||||
let c2Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Isolated Core Deposit', //*
|
||||
6: 'Uncommon Core Deposit' //*
|
||||
};
|
||||
|
||||
let c3Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Infrequent Core Deposit', //*
|
||||
6: 'Unusual Core Deposit' //*
|
||||
};
|
||||
|
||||
let c4Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Unusual Core Deposit', //*
|
||||
6: 'Infrequent Core Deposit' //*
|
||||
};
|
||||
|
||||
let c5Ore = {
|
||||
1: 'Average Frontier Deposit', //*
|
||||
2: 'Unexceptional Frontier Deposit', //*
|
||||
3: 'Uncommon Core Deposit', //*
|
||||
4: 'Ordinary Perimeter Deposit', //*
|
||||
5: 'Common Perimeter Deposit', //*
|
||||
6: 'Exceptional Core Deposit', //*
|
||||
7: 'Infrequent Core Deposit', //*
|
||||
8: 'Unusual Core Deposit', //*
|
||||
9: 'Rarified Core Deposit', //*
|
||||
10: 'Isolated Core Deposit' //*
|
||||
};
|
||||
|
||||
let c6Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Rarified Core Deposit' //*
|
||||
};
|
||||
|
||||
let c13Ore = {
|
||||
1: 'Shattered Debris Field',
|
||||
2: 'Shattered Ice Field'
|
||||
};
|
||||
|
||||
// Wormholes ======================================================================================================
|
||||
|
||||
// all k-space exits are static or K162
|
||||
let c1WH = {
|
||||
1: 'H121 - C1',
|
||||
2: 'C125 - C2',
|
||||
3: 'O883 - C3',
|
||||
4: 'M609 - C4',
|
||||
5: 'L614 - C5',
|
||||
6: 'S804 - C6',
|
||||
7: 'F353 - C12 Thera'
|
||||
};
|
||||
|
||||
// all w-space -> w-space are statics or K162
|
||||
let c2WH = {
|
||||
1: 'Z647 - C1',
|
||||
2: 'D382 - C2',
|
||||
3: 'O477 - C3',
|
||||
4: 'Y683 - C4',
|
||||
5: 'N062 - C5',
|
||||
6: 'R474 - C6',
|
||||
7: 'F135 - C12 Thera'
|
||||
};
|
||||
|
||||
// all k-space exits are static or K162
|
||||
let c3WH = {
|
||||
1: 'V301 - C1',
|
||||
2: 'I182 - C2',
|
||||
3: 'N968 - C3',
|
||||
4: 'T405 - C4',
|
||||
5: 'N770 - C5',
|
||||
6: 'A982 - C6',
|
||||
7: 'F135 - C12 Thera'
|
||||
};
|
||||
|
||||
// no *wandering* w-space -> w-space
|
||||
// all holes are statics or K162
|
||||
let c4WH = {
|
||||
1: 'S047 - H',
|
||||
2: 'N290 - L',
|
||||
3: 'K329 - 0.0'
|
||||
};
|
||||
|
||||
let c5WH = {
|
||||
1: 'D792 - H',
|
||||
2: 'C140 - L',
|
||||
3: 'Z142 - 0.0'
|
||||
};
|
||||
|
||||
let c6WH = {
|
||||
1: 'B520 - H',
|
||||
2: 'D792 - H',
|
||||
3: 'C140 - L',
|
||||
4: 'C391 - L',
|
||||
5: 'C248 - 0.0',
|
||||
6: 'Z142 - 0.0'
|
||||
};
|
||||
|
||||
// Shattered WH (some of them are static)
|
||||
let c13WH = {
|
||||
1: 'P060 - C1',
|
||||
2: 'Z647 - C1',
|
||||
3: 'D382 - C2',
|
||||
4: 'L005 - C2',
|
||||
5: 'N766 - C2',
|
||||
6: 'C247 - C3',
|
||||
7: 'M267 - C3',
|
||||
8: 'O477 - C3',
|
||||
9: 'X877 - C4',
|
||||
10: 'Y683 - C4',
|
||||
11: 'H296 - C5',
|
||||
12: 'H900 - C5',
|
||||
13: 'H296 - C5',
|
||||
14: 'N062 - C5', // ??
|
||||
15: 'V911 - C5',
|
||||
16: 'U574 - C6',
|
||||
17: 'V753 - C6',
|
||||
18: 'W237 - C6',
|
||||
19: 'B274 - H',
|
||||
20: 'D792 - H',
|
||||
21: 'D845 - H',
|
||||
22: 'N110 - H',
|
||||
23: 'A239 - L',
|
||||
24: 'C391 - L',
|
||||
25: 'J244 - L',
|
||||
26: 'U201 - L', // ??
|
||||
27: 'U210 - L',
|
||||
28: 'C248 - 0.0',
|
||||
29: 'E545 - 0.0',
|
||||
30: 'K346 - 0.0',
|
||||
31: 'Z060 - 0.0'
|
||||
};
|
||||
|
||||
let hsWH = {
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
3: 'X702 - C3',
|
||||
4: 'O128 - C4',
|
||||
5: 'M555 - C5',
|
||||
6: 'B041 - C6',
|
||||
7: 'A641 - H',
|
||||
8: 'R051 - L',
|
||||
9: 'V283 - 0.0',
|
||||
10: 'T458 - C12 Thera'
|
||||
};
|
||||
|
||||
let lsWH = {
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
3: 'X702 - C3',
|
||||
4: 'O128 - C4',
|
||||
5: 'N432 - C5',
|
||||
6: 'U319 - C6',
|
||||
7: 'B449 - H',
|
||||
8: 'N944 - L',
|
||||
9: 'S199 - 0.0',
|
||||
10: 'M164 - C12 Thera'
|
||||
};
|
||||
|
||||
let nullWH = {
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
3: 'X702 - C3',
|
||||
4: 'O128 - C4',
|
||||
5: 'N432 - C5',
|
||||
6: 'U319 - C6',
|
||||
7: 'B449 - H',
|
||||
8: 'N944 - L',
|
||||
9: 'S199 - 0.0',
|
||||
10: 'L031 - C12 Thera'
|
||||
};
|
||||
|
||||
// ================================================================================================================
|
||||
// Signature types
|
||||
// ================================================================================================================
|
||||
|
||||
// signature types
|
||||
let signatureTypes = {
|
||||
return {
|
||||
1: { // system type (wh)
|
||||
1: { // C1 (area id)
|
||||
1: { // Combat
|
||||
1: 'Perimeter Ambush Point',
|
||||
2: 'Perimeter Camp',
|
||||
3: 'Phase Catalyst Node',
|
||||
4: 'The Line'
|
||||
},
|
||||
2: $.extend({}, nullSecRelicSites, { // Relic
|
||||
1: 'Forgotten Perimeter Coronation Platform', //*
|
||||
2: 'Forgotten Perimeter Power Array' //*
|
||||
}),
|
||||
3: $.extend({}, nullSecDataSites, { // Data
|
||||
1: 'Unsecured Perimeter Amplifier', //*
|
||||
2: 'Unsecured Perimeter Information Center' //*
|
||||
}),
|
||||
4: { // Gas
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir' //*
|
||||
},
|
||||
5: { // Wormhole
|
||||
// all k-space exits are static or K162
|
||||
1: 'H121 - C1',
|
||||
2: 'C125 - C2',
|
||||
3: 'O883 - C3',
|
||||
4: 'M609 - C4',
|
||||
5: 'L614 - C5',
|
||||
6: 'S804 - C6',
|
||||
7: 'F353 - C12 Thera'
|
||||
},
|
||||
6: { // ORE
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Isolated Core Deposit', //*
|
||||
6: 'Uncommon Core Deposit' //*
|
||||
},
|
||||
7: { // Ghost
|
||||
|
||||
}
|
||||
1: c1Combat,
|
||||
2: c1Relic,
|
||||
3: c1Data,
|
||||
4: c1Gas,
|
||||
5: c1WH,
|
||||
6: c1Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
2: { // C2
|
||||
1: { // Combat
|
||||
1: 'Perimeter Checkpoint',
|
||||
2: 'Perimeter Hangar',
|
||||
3: 'The Ruins of Enclave Cohort 27',
|
||||
4: 'Sleeper Data Sanctuary'
|
||||
},
|
||||
2: $.extend({}, nullSecRelicSites, { // Relic
|
||||
1: 'Forgotten Perimeter Gateway', //*
|
||||
2: 'Forgotten Perimeter Habitation Coils' //*
|
||||
}),
|
||||
3: $.extend({}, nullSecDataSites, { // Data
|
||||
1: 'Unsecured Perimeter Comms Relay', //*
|
||||
2: 'Unsecured Perimeter Transponder Farm' //*
|
||||
}),
|
||||
4: { // Gas
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir' //*
|
||||
},
|
||||
5: { // Wormhole
|
||||
// all w-space -> w-space are statics or K162
|
||||
1: 'Z647 - C1',
|
||||
2: 'D382 - C2',
|
||||
3: 'O477 - C3',
|
||||
4: 'Y683 - C4',
|
||||
5: 'N062 - C5',
|
||||
6: 'R474 - C6',
|
||||
7: 'F135 - C12 Thera'
|
||||
},
|
||||
6: { // ORE
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Isolated Core Deposit', //*
|
||||
6: 'Uncommon Core Deposit' //*
|
||||
},
|
||||
7: { // Ghost
|
||||
|
||||
}
|
||||
1: c2Combat,
|
||||
2: c2Relic,
|
||||
3: c2Data,
|
||||
4: c2Gas,
|
||||
5: c2WH,
|
||||
6: c2Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
3: { // C3
|
||||
1: { // Combat
|
||||
1: 'Fortification Frontier Stronghold',
|
||||
2: 'Outpost Frontier Stronghold',
|
||||
3: 'Solar Cell',
|
||||
4: 'The Oruze Construct'
|
||||
},
|
||||
2: $.extend({}, nullSecRelicSites, { // Relic
|
||||
1: 'Forgotten Frontier Quarantine Outpost', //*
|
||||
2: 'Forgotten Frontier Recursive Depot' //*
|
||||
}),
|
||||
3: $.extend({}, nullSecDataSites, { // Data
|
||||
1: 'Unsecured Frontier Database', //*
|
||||
2: 'Unsecured Frontier Receiver' //*
|
||||
}),
|
||||
4: { // Gas
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir', //*
|
||||
6: 'Bountiful Frontier Reservoir', //*
|
||||
7: 'Vast Frontier Reservoir' //*
|
||||
},
|
||||
5: { // Wormhole
|
||||
// all k-space exits are static or K162
|
||||
1: 'V301 - C1',
|
||||
2: 'I182 - C2',
|
||||
3: 'N968 - C3',
|
||||
4: 'T405 - C4',
|
||||
5: 'N770 - C5',
|
||||
6: 'A982 - C6',
|
||||
7: 'F135 - C12 Thera'
|
||||
},
|
||||
6: { // ORE
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Infrequent Core Deposit', //*
|
||||
6: 'Unusual Core Deposit' //*
|
||||
},
|
||||
7: { // Ghost
|
||||
|
||||
}
|
||||
1: c3Combat,
|
||||
2: c3Relic,
|
||||
3: c3Data,
|
||||
4: c3Gas,
|
||||
5: c3WH,
|
||||
6: c3Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
4: { // C4
|
||||
1: { // Combat
|
||||
1: 'Frontier Barracks',
|
||||
2: 'Frontier Command Post',
|
||||
3: 'Integrated Terminus',
|
||||
4: 'Sleeper Information Sanctum'
|
||||
},
|
||||
2: { // Relic
|
||||
1: 'Forgotten Frontier Conversion Module',
|
||||
2: 'Forgotten Frontier Evacuation Center'
|
||||
},
|
||||
3: { // Data
|
||||
1: 'Unsecured Frontier Digital Nexus',
|
||||
2: 'Unsecured Frontier Trinary Hub'
|
||||
},
|
||||
4: { // Gas
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir', //*
|
||||
6: 'Vast Frontier Reservoir', //*
|
||||
7: 'Bountiful Frontier Reservoir' //*
|
||||
},
|
||||
5: { // Wormhole
|
||||
// no *wandering* w-space -> w-space
|
||||
// all holes are statics or K162
|
||||
1: 'S047 - H',
|
||||
2: 'N290 - L',
|
||||
3: 'K329 - 0.0'
|
||||
},
|
||||
6: { // ORE
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Unusual Core Deposit', //*
|
||||
6: 'Infrequent Core Deposit' //*
|
||||
},
|
||||
7: { // Ghost
|
||||
|
||||
}
|
||||
1: c4Combat,
|
||||
2: c4Relic,
|
||||
3: c4Data,
|
||||
4: c4Gas,
|
||||
5: c4WH,
|
||||
6: c4Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
5: { // C5
|
||||
1: { // Combat
|
||||
1: 'Core Garrison', //*
|
||||
2: 'Core Stronghold', //*
|
||||
3: 'Oruze Osobnyk', //*
|
||||
4: 'Quarantine Area'
|
||||
},
|
||||
2: { // Relic
|
||||
1: 'Forgotten Core Data Field',
|
||||
2: 'Forgotten Core Information Pen'
|
||||
},
|
||||
3: { // Data
|
||||
1: 'Unsecured Frontier Enclave Relay',
|
||||
2: 'Unsecured Frontier Server Bank'
|
||||
},
|
||||
4: { // Gas
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Minor Perimeter Reservoir', //*
|
||||
3: 'Ordinary Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Token Perimeter Reservoir', //*
|
||||
6: 'Bountiful Frontier Reservoir', //*
|
||||
7: 'Vast Frontier Reservoir', //*
|
||||
8: 'Instrumental Core Reservoir', //*
|
||||
9: 'Vital Core Reservoir' //*
|
||||
},
|
||||
5: { // Wormhole
|
||||
1: 'D792 - H',
|
||||
2: 'C140 - L',
|
||||
3: 'Z142 - 0.0'
|
||||
},
|
||||
6: { // ORE
|
||||
1: 'Average Frontier Deposit', //*
|
||||
2: 'Unexceptional Frontier Deposit', //*
|
||||
3: 'Uncommon Core Deposit', //*
|
||||
4: 'Ordinary Perimeter Deposit', //*
|
||||
5: 'Common Perimeter Deposit', //*
|
||||
6: 'Exceptional Core Deposit', //*
|
||||
7: 'Infrequent Core Deposit', //*
|
||||
8: 'Unusual Core Deposit', //*
|
||||
9: 'Rarified Core Deposit', //*
|
||||
10: 'Isolated Core Deposit' //*
|
||||
},
|
||||
7: { // Ghost
|
||||
|
||||
}
|
||||
1: c5Combat,
|
||||
2: c5Relic,
|
||||
3: c5Data,
|
||||
4: c5Gas,
|
||||
5: c5WH,
|
||||
6: c5Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
6: { // C6
|
||||
1: { // Combat
|
||||
1: 'Core Citadel', //*
|
||||
2: 'Core Bastion', //*
|
||||
3: 'Strange Energy Readings', //*
|
||||
4: 'The Mirror' //*
|
||||
},
|
||||
2: { // Relic
|
||||
1: 'Forgotten Core Assembly Hall', //*
|
||||
2: 'Forgotten Core Circuitry Disassembler' //*
|
||||
},
|
||||
3: { // Data
|
||||
1: 'Unsecured Core Backup Array', //*
|
||||
2: 'Unsecured Core Emergence' //*
|
||||
},
|
||||
4: { // Gas
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Minor Perimeter Reservoir', //*
|
||||
3: 'Ordinary Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Token Perimeter Reservoir', //*
|
||||
6: 'Bountiful Frontier Reservoir', //*
|
||||
7: 'Vast Frontier Reservoir', //*
|
||||
8: 'Instrumental Core Reservoir', //*
|
||||
9: 'Vital Core Reservoir' //*
|
||||
},
|
||||
5: { // Wormhole
|
||||
1: 'B520 - H',
|
||||
2: 'D792 - H',
|
||||
3: 'C140 - L',
|
||||
4: 'C391 - L',
|
||||
5: 'C248 - 0.0',
|
||||
6: 'Z142 - 0.0'
|
||||
},
|
||||
6: { // ORE
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Rarified Core Deposit' //*
|
||||
},
|
||||
1: c6Combat,
|
||||
2: c6Relic,
|
||||
3: c6Data,
|
||||
4: c6Gas,
|
||||
5: c6WH,
|
||||
6: c6Ore,
|
||||
7: { // Ghost
|
||||
1: 'Superior Blood Raider Covert Research Facility' //*
|
||||
}
|
||||
},
|
||||
12: { // Thera wormhole
|
||||
1: { // Combat
|
||||
1: 'Epicenter',
|
||||
2: 'Expedition Command Outpost Wreck',
|
||||
3: 'Planetary Colonization Office Wreck',
|
||||
4: 'Testing Facilities'
|
||||
}
|
||||
12: { // Thera WH
|
||||
1: c12Combat
|
||||
},
|
||||
13: { // Shattered Wormholes
|
||||
5: { // Wormhole (some of them are static)
|
||||
1: 'P060 - C1',
|
||||
2: 'Z647 - C1',
|
||||
3: 'D382 - C2',
|
||||
4: 'L005 - C2',
|
||||
5: 'N766 - C2',
|
||||
6: 'C247 - C3',
|
||||
7: 'M267 - C3',
|
||||
8: 'O477 - C3',
|
||||
9: 'X877 - C4',
|
||||
10: 'Y683 - C4',
|
||||
11: 'H296 - C5',
|
||||
12: 'H900 - C5',
|
||||
13: 'H296 - C5',
|
||||
14: 'N062 - C5', // ??
|
||||
15: 'V911 - C5',
|
||||
16: 'U574 - C6',
|
||||
17: 'V753 - C6',
|
||||
18: 'W237 - C6',
|
||||
19: 'B274 - H',
|
||||
20: 'D792 - H',
|
||||
21: 'D845 - H',
|
||||
22: 'N110 - H',
|
||||
23: 'A239 - L',
|
||||
24: 'C391 - L',
|
||||
25: 'J244 - L',
|
||||
26: 'U201 - L', // ??
|
||||
27: 'U210 - L',
|
||||
28: 'C248 - 0.0',
|
||||
29: 'E545 - 0.0',
|
||||
30: 'K346 - 0.0',
|
||||
31: 'Z060 - 0.0'
|
||||
}
|
||||
13: { // Shattered WH
|
||||
5: c13WH,
|
||||
6: c13Ore
|
||||
},
|
||||
14: { // Drifter Sentinel WH
|
||||
1: { // Combat
|
||||
1: 'Monolith',
|
||||
2: 'Wormhole in Rock Circle',
|
||||
3: 'Opposing Spatial Rifts',
|
||||
4: 'Sleeper Enclave Debris',
|
||||
5: 'Crystal Resource'
|
||||
}
|
||||
1: c14Combat
|
||||
},
|
||||
15: { // Drifter Barbican WH
|
||||
1: { // Combat
|
||||
1: 'Wrecked Ships',
|
||||
2: 'Unstable Wormhole',
|
||||
3: 'Spatial Rift',
|
||||
4: 'Heavily Guarded Spatial Rift',
|
||||
5: 'Crystals'
|
||||
}
|
||||
1: c15Combat
|
||||
},
|
||||
16: { // Drifter Vidette WH
|
||||
1: { // Combat
|
||||
1: 'Ship Graveyard',
|
||||
2: 'Sleeper Engineering Station',
|
||||
3: 'Spatial Rift',
|
||||
4: 'Sleeper Enclave in Coral Rock',
|
||||
5: 'Crystals and Stone Circle'
|
||||
}
|
||||
1: c16Combat
|
||||
},
|
||||
17: { // Drifter Conflux WH
|
||||
1: { // Combat
|
||||
1: 'Monolith',
|
||||
2: 'Caged Wormhole',
|
||||
3: 'Rock Formation and Wormhole',
|
||||
4: 'Particle Acceleration Array',
|
||||
5: 'Guarded Asteroid Station'
|
||||
}
|
||||
1: c17Combat
|
||||
},
|
||||
18: { // Drifter Redoubt WH
|
||||
1: { // Combat
|
||||
1: 'Ship Graveyard',
|
||||
2: 'Caged Wormhole',
|
||||
3: 'Spatial Rift Generator',
|
||||
4: 'Sleeper Enclave',
|
||||
5: 'Hollow Asteroid'
|
||||
}
|
||||
1: c18Combat
|
||||
}
|
||||
}, // system type (k-space)
|
||||
2: {
|
||||
2: {
|
||||
30: { // High Sec
|
||||
5: { // Wormhole
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
3: 'X702 - C3',
|
||||
4: 'O128 - C4',
|
||||
5: 'M555 - C5',
|
||||
6: 'B041 - C6',
|
||||
7: 'A641 - H',
|
||||
8: 'R051 - L',
|
||||
9: 'V283 - 0.0',
|
||||
10: 'T458 - C12 Thera'
|
||||
}
|
||||
5: hsWH
|
||||
},
|
||||
31: { // Low Sec
|
||||
5: { // Wormhole
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
3: 'X702 - C3',
|
||||
4: 'O128 - C4',
|
||||
5: 'N432 - C5',
|
||||
6: 'U319 - C6',
|
||||
7: 'B449 - H',
|
||||
8: 'N944 - L',
|
||||
9: 'S199 - 0.0',
|
||||
10: 'M164 - C12 Thera'
|
||||
}
|
||||
5: lsWH
|
||||
},
|
||||
32: { // 0.0
|
||||
5: { // Wormhole
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
3: 'X702 - C3',
|
||||
4: 'O128 - C4',
|
||||
5: 'N432 - C5',
|
||||
6: 'U319 - C6',
|
||||
7: 'B449 - H',
|
||||
8: 'N944 - L',
|
||||
9: 'S199 - 0.0',
|
||||
10: 'L031 - C12 Thera'
|
||||
}
|
||||
5: nullWH
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return signatureTypes;
|
||||
});
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util'
|
||||
], ($, Init, Util) => {
|
||||
'app/util',
|
||||
'app/lib/cron'
|
||||
], ($, Util, Cron) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
counterDigitSmallClass: 'pf-digit-counter-small',
|
||||
counterDigitLargeClass: 'pf-digit-counter-large'
|
||||
counterTaskAttr: 'data-counter-task', // element attr name with initialized counter name
|
||||
counterStopClass: 'stopCounter', // class for counter elements where counter should be destroyed
|
||||
counterDigitSmallClass: 'pf-digit-counter-small', // class for 'small' counter DOM elements (e.g. 'hour' number)
|
||||
counterDigitLargeClass: 'pf-digit-counter-large' // class for 'large' counter DOM elements (e.g. 'days' number)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -57,73 +59,58 @@ define([
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
element.html(parts.join(' '));
|
||||
};
|
||||
|
||||
/**
|
||||
* destroy all active counter recursive
|
||||
*/
|
||||
$.fn.destroyTimestampCounter = function(recursive){
|
||||
return this.each(function(){
|
||||
let element = $(this);
|
||||
let counterSelector = '[data-counter="init"]';
|
||||
let counterElements = element.filter(counterSelector);
|
||||
if(recursive){
|
||||
counterElements = counterElements.add(element.find(counterSelector));
|
||||
}
|
||||
let destroyTimestampCounter = (element, recursive) => {
|
||||
let counterTaskSelector = '[' + config.counterTaskAttr + ']';
|
||||
let counterElements = element.filter(counterTaskSelector);
|
||||
if(recursive){
|
||||
counterElements = counterElements.add(element.find(counterTaskSelector));
|
||||
}
|
||||
|
||||
counterElements.each(function(){
|
||||
let element = $(this);
|
||||
let interval = element.data('interval');
|
||||
if(interval){
|
||||
clearInterval(interval);
|
||||
element.removeAttr('data-counter').removeData('interval').removeClass('stopCounter');
|
||||
}
|
||||
});
|
||||
counterElements.each(function(){
|
||||
let element = $(this);
|
||||
let taskName = element.attr(config.counterTaskAttr);
|
||||
|
||||
if(Cron.delete(taskName)){
|
||||
element.removeAttr(config.counterTaskAttr).removeClass(config.counterStopClass);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a live counter based on a unix timestamp
|
||||
* @param round string e.g. 'd' => round days
|
||||
* @param element
|
||||
* @param round e.g. 'd' => round days
|
||||
* @returns {void|*|undefined}
|
||||
*/
|
||||
$.fn.initTimestampCounter = function(round){
|
||||
return this.each(function(){
|
||||
let element = $(this);
|
||||
let timestamp = parseInt( element.text() );
|
||||
let initTimestampCounter = (element, round) => {
|
||||
let timestamp = parseInt(element.text());
|
||||
// do not init twice
|
||||
if(timestamp > 0){
|
||||
let taskName = element.attr('id') || Util.getRandomString();
|
||||
let date = new Date( timestamp * 1000);
|
||||
updateDateDiff(element, date, round);
|
||||
|
||||
// do not init twice
|
||||
if(timestamp > 0){
|
||||
// mark as init
|
||||
element.attr('data-counter', 'init');
|
||||
// show element (if invisible) after first update
|
||||
element.css({'visibility': 'initial'});
|
||||
|
||||
let date = new Date( timestamp * 1000);
|
||||
let counterTask = Cron.new(taskName, {precision: 'seconds', interval: 1, timeout: 100});
|
||||
counterTask.task = () => {
|
||||
if(element.hasClass(config.counterStopClass)){
|
||||
destroyTimestampCounter(element);
|
||||
}else{
|
||||
updateDateDiff(element, date, round);
|
||||
}
|
||||
};
|
||||
Cron.set(counterTask);
|
||||
|
||||
updateDateDiff(element, date, round);
|
||||
|
||||
// show element (if invisible) after first update
|
||||
element.css({'visibility': 'initial'});
|
||||
|
||||
// calc ms until next second
|
||||
// -> makes sure all counter update in sync no matter when init
|
||||
let msUntilSecond = 1500 - new Date().getMilliseconds();
|
||||
setTimeout(function(){
|
||||
let refreshIntervalId = window.setInterval(function(){
|
||||
|
||||
// update element with current time
|
||||
if( !element.hasClass('stopCounter')){
|
||||
updateDateDiff(element, date, round);
|
||||
}else{
|
||||
clearInterval( element.data('interval') );
|
||||
}
|
||||
}, 500);
|
||||
|
||||
element.data('interval', refreshIntervalId);
|
||||
}, msUntilSecond);
|
||||
}
|
||||
});
|
||||
element.attr(config.counterTaskAttr, taskName);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -134,30 +121,33 @@ define([
|
||||
*/
|
||||
let initTableCounter = (tableElement, columnSelector, round) => {
|
||||
let tableApi = tableElement.api();
|
||||
let taskName = tableElement.attr('id');
|
||||
|
||||
// mark as init
|
||||
tableElement.attr('data-counter', 'init');
|
||||
|
||||
let updateTableCount = () => {
|
||||
tableApi.cells(null, columnSelector).every(function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
|
||||
let cell = this;
|
||||
let node = cell.node();
|
||||
let data = cell.data();
|
||||
if(data && Number.isInteger(data) && !node.classList.contains('stopCounter')){
|
||||
// timestamp expected int > 0
|
||||
let date = new Date(data * 1000);
|
||||
updateDateDiff( cell.nodes().to$(), date, round);
|
||||
}
|
||||
});
|
||||
let cellUpdate = function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
|
||||
let cell = this;
|
||||
let node = cell.node();
|
||||
let data = cell.data();
|
||||
if(data && Number.isInteger(data) && !node.classList.contains(config.counterStopClass)){
|
||||
// timestamp expected int > 0
|
||||
let date = new Date(data * 1000);
|
||||
updateDateDiff(cell.nodes().to$(), date, round);
|
||||
}
|
||||
};
|
||||
|
||||
let refreshIntervalId = window.setInterval(updateTableCount, 500);
|
||||
let counterTask = Cron.new(taskName, {precision: 'seconds', interval: 1, timeout: 100});
|
||||
counterTask.task = timer => {
|
||||
tableApi.cells(null, columnSelector).every(cellUpdate);
|
||||
};
|
||||
Cron.set(counterTask);
|
||||
|
||||
tableElement.data('interval', refreshIntervalId);
|
||||
tableElement.attr(config.counterTaskAttr, taskName);
|
||||
};
|
||||
|
||||
return {
|
||||
config: config,
|
||||
updateDateDiff: updateDateDiff,
|
||||
initTableCounter: initTableCounter
|
||||
initTimestampCounter: initTimestampCounter,
|
||||
initTableCounter: initTableCounter,
|
||||
destroyTimestampCounter: destroyTimestampCounter
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/counter',
|
||||
'app/promises/promise.deferred',
|
||||
'app/promises/promise.timeout',
|
||||
'datatables.net',
|
||||
@@ -8,7 +9,7 @@ define([
|
||||
'datatables.net-buttons-html',
|
||||
'datatables.net-responsive',
|
||||
'datatables.net-select'
|
||||
], ($, Init, DeferredPromise, TimeoutPromise) => {
|
||||
], ($, Init, Counter, DeferredPromise, TimeoutPromise) => {
|
||||
'use strict';
|
||||
|
||||
// all Datatables stuff is available...
|
||||
@@ -42,7 +43,7 @@ define([
|
||||
}
|
||||
|
||||
// remove all active counters in table
|
||||
table.destroyTimestampCounter(true);
|
||||
Counter.destroyTimestampCounter(table, true);
|
||||
});
|
||||
|
||||
// Status Plugin ==============================================================================================
|
||||
|
||||
169
js/app/init.js
169
js/app/init.js
@@ -3,10 +3,20 @@
|
||||
*/
|
||||
|
||||
define([], () => {
|
||||
|
||||
'use strict';
|
||||
|
||||
let Config = {
|
||||
let frigWH = {
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
};
|
||||
|
||||
return {
|
||||
path: {
|
||||
img: '/public/img/', // path for images
|
||||
api: '/api/rest', //ajax URL - REST API
|
||||
@@ -49,12 +59,12 @@ define([], () => {
|
||||
gitHubReleases: '/api/github/releases' // ajax URL - get release info from GitHub
|
||||
},
|
||||
breakpoints: [
|
||||
{ name: 'screen-xl', width: Infinity },
|
||||
{ name: 'screen-l', width: 1600 },
|
||||
{ name: 'screen-m', width: 1200 },
|
||||
{ name: 'screen-d', width: 1000 },
|
||||
{ name: 'screen-s', width: 780 },
|
||||
{ name: 'screen-xs', width: 480 }
|
||||
{name: 'screen-xl', width: Infinity},
|
||||
{name: 'screen-l', width: 1600},
|
||||
{name: 'screen-m', width: 1200},
|
||||
{name: 'screen-d', width: 1000},
|
||||
{name: 'screen-s', width: 780},
|
||||
{name: 'screen-xs', width: 480}
|
||||
],
|
||||
animationSpeed: {
|
||||
splashOverlay: 300, // "splash" loading overlay
|
||||
@@ -95,63 +105,63 @@ define([], () => {
|
||||
class: 'fa-desktop',
|
||||
label: 'desktop',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-space-shuttle',
|
||||
label: 'space shuttle',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-anchor',
|
||||
label: 'anchor',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-satellite',
|
||||
label: 'satellite',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-skull-crossbones',
|
||||
label: 'skull crossbones',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-fire',
|
||||
label: 'fire',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-bookmark',
|
||||
label: 'bookmark',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-cube',
|
||||
label: 'cube',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-star',
|
||||
label: 'star',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-hat-wizard',
|
||||
label: 'hat wizard',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-plane',
|
||||
label: 'plane',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-globe',
|
||||
label: 'globe',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-rocket',
|
||||
label: 'rocket',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-life-ring',
|
||||
label: 'life ring',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-heart',
|
||||
label: 'heart',
|
||||
unicode: ''
|
||||
},{
|
||||
}, {
|
||||
class: 'fa-poop',
|
||||
label: 'poop',
|
||||
unicode: ''
|
||||
@@ -541,106 +551,16 @@ define([], () => {
|
||||
},
|
||||
// frigate wormholes
|
||||
frigateWormholes: {
|
||||
1: { // C1
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
2: { // C2
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
3: { // C3
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
4: { // C4
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
5: { // C5
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
6: { // C6
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
13: { // Shattered Wormholes (some of them are static)
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
30: { // High Sec
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
31: { // Low Sec
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
},
|
||||
32: { // 0.0
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
}
|
||||
1: frigWH, // C1
|
||||
2: frigWH, // C2
|
||||
3: frigWH, // C3
|
||||
4: frigWH, // C4
|
||||
5: frigWH, // C5
|
||||
6: frigWH, // C6
|
||||
13: frigWH, // Shattered Wormholes (some of them are static)
|
||||
30: frigWH, // High Sec
|
||||
31: frigWH, // Low Sec
|
||||
32: frigWH // 0.0
|
||||
},
|
||||
// Drifter wormholes (can only appear in k-space)
|
||||
drifterWormholes: {
|
||||
@@ -660,8 +580,5 @@ define([], () => {
|
||||
6: 'K162 - 0.0',
|
||||
7: 'K162 - C12 Thera'
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return Config;
|
||||
});
|
||||
|
||||
275
js/app/lib/cache.js
Normal file
275
js/app/lib/cache.js
Normal file
@@ -0,0 +1,275 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Abstract Cache Strategy class
|
||||
* @type {AbstractStrategy}
|
||||
*/
|
||||
let AbstractStrategy = class AbstractStrategy {
|
||||
constructor(){
|
||||
if(new.target === AbstractStrategy){
|
||||
throw new TypeError('Cannot construct AbstractStrategy instances directly');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* factory for Strategy* instances
|
||||
* @returns {AbstractStrategy}
|
||||
*/
|
||||
static create(){
|
||||
return new this();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* LIFO Cache Strategy - First In First Out
|
||||
* -> The cache evicts the entries in the order they were added,
|
||||
* without any regard to how often or how many times they were accessed before.
|
||||
* @type {StrategyFIFO}
|
||||
*/
|
||||
let StrategyFIFO = class StrategyFIFO extends AbstractStrategy {
|
||||
valueToCompare(metaData){
|
||||
return metaData.age();
|
||||
}
|
||||
|
||||
compare(a, b){
|
||||
return b - a;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* LFU Cache Strategy - Least Frequently Used
|
||||
* -> The cache evicts the entries in order how often have been accessed.
|
||||
* Those that are used least often are discarded first
|
||||
* @type {StrategyLFU}
|
||||
*/
|
||||
let StrategyLFU = class StrategyLFU extends AbstractStrategy {
|
||||
valueToCompare(metaData){
|
||||
return metaData.hitCount;
|
||||
}
|
||||
|
||||
compare(a, b){
|
||||
return a - b;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* LRU Cache Strategy - Least Recently Used
|
||||
* -> The cache evicts entries that have not been used for the longest amount of time.
|
||||
* No matter how often they have been accessed.
|
||||
* @type {StrategyLRU}
|
||||
*/
|
||||
let StrategyLRU = class StrategyLRU extends AbstractStrategy {
|
||||
valueToCompare(metaData){
|
||||
return metaData.hits[metaData.hits.length - 1] || metaData.set;
|
||||
}
|
||||
|
||||
compare(a, b){
|
||||
return a - b;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Each entry in cache also has its own instance of CacheEntryMeta
|
||||
* -> The configured Cache Strategy use this meta data for eviction policy
|
||||
* @type {CacheEntryMeta}
|
||||
*/
|
||||
let CacheEntryMeta = class CacheEntryMeta {
|
||||
constructor(ttl, tSet){
|
||||
this.ttl = ttl;
|
||||
this.tSet = tSet || this.constructor.now();
|
||||
this.tHits = [];
|
||||
}
|
||||
|
||||
get set(){
|
||||
return this.tSet;
|
||||
}
|
||||
|
||||
get hits(){
|
||||
return this.tHits;
|
||||
}
|
||||
|
||||
get hitCount(){
|
||||
return this.hits.length;
|
||||
}
|
||||
|
||||
newHit(current){
|
||||
this.tHits.push(current || this.constructor.now());
|
||||
}
|
||||
|
||||
age(current){
|
||||
return (current || this.constructor.now()) - this.tSet;
|
||||
}
|
||||
|
||||
expired(current){
|
||||
return this.ttl < this.age(current);
|
||||
}
|
||||
|
||||
static now(){
|
||||
return new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
static create(ttl, tSet){
|
||||
return new this(ttl, tSet);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Each instance of Cache represents a key value in memory data store
|
||||
* -> Name should be set to identify current Cache instance
|
||||
* -> Default ttl can be overwritten by individual entries
|
||||
* -> Cache Strategy handles eviction policy
|
||||
* -> Buffer Size (in percent) can be used to remove e.g. 10% of all entries
|
||||
* if cache reaches maxSize limit, to increase performance.
|
||||
* @type {Cache}
|
||||
*/
|
||||
let Cache = class Cache {
|
||||
|
||||
constructor(config){
|
||||
this.config = Object.assign({},{
|
||||
name: 'Default', // custom name for identification
|
||||
ttl: 3600, // default ttl for cache entries
|
||||
maxSize: 600, // max cache entries
|
||||
bufferSize: 10, // cache entry count in percent to be removed if maxSize reached
|
||||
strategy: 'FIFO', // cache strategy policy
|
||||
debug: false // debug output in console
|
||||
}, config);
|
||||
|
||||
this.store = new Map();
|
||||
this.metaStore = new WeakMap();
|
||||
this.strategy = this.constructor.setStrategy(this.config.strategy);
|
||||
|
||||
this.debug = (msg,...data) => {
|
||||
if(this.config.debug){
|
||||
data = (data || []);
|
||||
data.unshift(this.config.name);
|
||||
console.debug('debug: CACHE %o | ' + msg, ...data);
|
||||
}
|
||||
};
|
||||
|
||||
this.debug('New Cache instance');
|
||||
}
|
||||
|
||||
get size(){
|
||||
return this.store.size;
|
||||
}
|
||||
|
||||
isFull(){
|
||||
return this.size>= this.config.maxSize;
|
||||
}
|
||||
|
||||
set(key, value, ttl){
|
||||
if(this.store.has(key)){
|
||||
this.debug('SET key %o, UPDATE value %o', key, value);
|
||||
this.store.set(key, value);
|
||||
}else{
|
||||
this.debug('SET key %o, NEW value %o', key, value);
|
||||
if(this.isFull()){
|
||||
this.debug(' ↪ FULL trim cache…');
|
||||
this.trim(this.trimCount(1));
|
||||
}
|
||||
this.store.set(key, value);
|
||||
}
|
||||
|
||||
this.metaStore.set(value, CacheEntryMeta.create(ttl || this.config.ttl));
|
||||
}
|
||||
|
||||
get(key){
|
||||
if(this.store.has(key)){
|
||||
let value = this.store.get(key);
|
||||
if(value){
|
||||
let metaData = this.metaStore.get(value);
|
||||
if(metaData.expired()){
|
||||
this.debug('EXPIRED key %o delete', key);
|
||||
this.delete(key);
|
||||
}else{
|
||||
this.debug('HIT key %o', key);
|
||||
metaData.newHit();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.debug('MISS key %o', key);
|
||||
}
|
||||
|
||||
getOrDefault(key, def){
|
||||
return this.get(key) || def;
|
||||
}
|
||||
|
||||
keysForTrim(count){
|
||||
let trimKeys = [];
|
||||
let compare = [];
|
||||
for(let [key, value] of this.store){
|
||||
let metaData = this.metaStore.get(value);
|
||||
if(metaData.expired()){
|
||||
trimKeys.push(key);
|
||||
if(count === trimKeys.length){
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
compare.push({
|
||||
key: key,
|
||||
value: this.strategy.valueToCompare(metaData)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let countLeft = count - trimKeys.length;
|
||||
if(countLeft > 0){
|
||||
compare = compare.sort((a, b) => this.strategy.compare(a.value, b.value));
|
||||
trimKeys = trimKeys.concat(compare.splice(0, countLeft).map(a => a.key));
|
||||
}
|
||||
|
||||
return trimKeys;
|
||||
}
|
||||
|
||||
keys(){
|
||||
return this.store.keys();
|
||||
}
|
||||
|
||||
delete(key){
|
||||
return this.store.delete(key);
|
||||
}
|
||||
|
||||
clear(){
|
||||
this.store.clear();
|
||||
}
|
||||
|
||||
trimCount(spaceLeft){
|
||||
let bufferSize = Math.max(Math.round(this.config.maxSize / 100 * this.config.bufferSize), spaceLeft);
|
||||
return Math.min(Math.max(this.size - this.config.maxSize + bufferSize, 0), this.size);
|
||||
}
|
||||
|
||||
trim(count){
|
||||
if(count > 0){
|
||||
let trimKeys = this.keysForTrim(count);
|
||||
if(count > trimKeys.length){
|
||||
console.warn(' ↪ Failed to trim(%i) entries. Only %i in store', count, this.size);
|
||||
}
|
||||
this.debug(' ↪ DELETE min %i keys: %o', count, trimKeys);
|
||||
trimKeys.forEach(key => this.delete(key));
|
||||
}
|
||||
}
|
||||
|
||||
status(){
|
||||
return {
|
||||
config: this.config,
|
||||
store: this.store,
|
||||
metaStore: this.metaStore
|
||||
};
|
||||
}
|
||||
|
||||
static setStrategy(name){
|
||||
switch(name){
|
||||
case 'FIFO': return StrategyFIFO.create();
|
||||
case 'LFU': return StrategyLFU.create();
|
||||
case 'LRU': return StrategyLRU.create();
|
||||
default:
|
||||
throw new ReferenceError('Unknown cache strategy name: ' + name);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Cache;
|
||||
});
|
||||
@@ -29,6 +29,9 @@ define([], () => {
|
||||
'line-height': '19px',
|
||||
'font-family': '"Fira Code", "Lucida Console"',
|
||||
},
|
||||
'debug': {
|
||||
'color': '#d747d6'
|
||||
},
|
||||
'ok': {
|
||||
'color': '#5cb85c'
|
||||
},
|
||||
@@ -113,8 +116,8 @@ define([], () => {
|
||||
let setLineStyleByLogType = (logType, args) => {
|
||||
if(args.length){
|
||||
let lineStyle = getStyleByLogType('global') + getStyleByLogType(logType);
|
||||
lineStyle += ['ok', 'log', 'info', 'pf'].includes(logType) ? getStyleByLogType('indentDefault') : '';
|
||||
let bullet = ['ok', 'log', 'info', 'pf'].includes(logType) ? '●' : '';
|
||||
lineStyle += ['debug', 'ok', 'log', 'info', 'pf'].includes(logType) ? getStyleByLogType('indentDefault') : '';
|
||||
let bullet = ['debug', 'ok', 'log', 'info', 'pf'].includes(logType) ? '●' : '';
|
||||
|
||||
if(typeof args[0] === 'string'){
|
||||
// prepend placeholder to existing message
|
||||
@@ -150,6 +153,12 @@ define([], () => {
|
||||
}
|
||||
};
|
||||
|
||||
origConsole.debug = (...args) => {
|
||||
setMessageStyleByLogType('debug', args);
|
||||
setLineStyleByLogType('debug', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.ok = (...args) => {
|
||||
setMessageStyleByLogType('ok', args);
|
||||
setLineStyleByLogType('ok', args);
|
||||
322
js/app/lib/cron.js
Normal file
322
js/app/lib/cron.js
Normal file
@@ -0,0 +1,322 @@
|
||||
define([
|
||||
'easyTimer',
|
||||
'app/promises/promise.timeout',
|
||||
], (easytimer, TimeoutPromise) => {
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
Example1 run task every second ------------------------------------------------------------------------------------
|
||||
let task1 = Cron.new('task1', {precision: 'seconds', interval: 1, timeout: 100});
|
||||
task1.task = (timer) => {
|
||||
console.info('task1 function():', timer.getTotalTimeValues());
|
||||
return 'OK';
|
||||
};
|
||||
Cron.set(task1);
|
||||
|
||||
Example2 run task every 3 seconds ---------------------------------------------------------------------------------
|
||||
let task1 = Cron.new('task1', {precision: 'seconds', interval: 3, timeout: 100});
|
||||
task1.task = (timer) => {
|
||||
console.info('task1 function():', timer.getTotalTimeValues());
|
||||
return 'OK';
|
||||
};
|
||||
Cron.set(task1);
|
||||
|
||||
Example3 resolve Promise on run ----------------------------------------------------------------------------------
|
||||
let task1 = Cron.new('task1', {precision: 'seconds', interval: 1, timeout: 100});
|
||||
task1.task = (timer, task) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.info('task1 Promise1():', timer.getTotalTimeValues(), task.get('interval'));
|
||||
//task.set('interval', task.get('interval') + 1) // increase run interval every time by 1s
|
||||
resolve('OK1');
|
||||
}).then(payload => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.info('task2 Promise2():', timer.getTotalTimeValues(), payload);
|
||||
resolve('OK2');
|
||||
});
|
||||
});
|
||||
};
|
||||
Cron.set(task1);
|
||||
|
||||
Example4 run task once at given Date() --------------------------------------------------------------------------
|
||||
let dueDate = new Date();
|
||||
dueDate.setSeconds(dueDate.getSeconds() + 5);
|
||||
let task2 = Cron.new('task2', {precision: 'seconds', timeout: 100, dueDate: dueDate});
|
||||
task2.task = () => 'OK task2';
|
||||
Cron.set(task2);
|
||||
*/
|
||||
|
||||
/**
|
||||
* Task instances represent a task that should be executed at a given interval or dueDate
|
||||
* -> Task´s are managed by CronManager()
|
||||
* @type {Task}
|
||||
*/
|
||||
let Task = class Task {
|
||||
constructor(name, config){
|
||||
if(typeof name !== 'string'){
|
||||
throw new TypeError('Task "name" must be instance of String, Type of "' + typeof name + '" given');
|
||||
}
|
||||
this._config = Object.assign({}, this.constructor.defaultConfig, config);
|
||||
this._name = name; // unique name for identification
|
||||
this._task = undefined; // task to run, instanceof Function, can also return a Promise
|
||||
this._manager = undefined; // reference to CronManager() that handles this task
|
||||
this._runQueue = new Map(); // current run() processes. > 1 requires config.isParallel: true
|
||||
this._runCount = 0; // total run counter for this task
|
||||
this._lastTotalTimeValues = undefined; // time values of last run()
|
||||
}
|
||||
|
||||
get name(){
|
||||
return this._name;
|
||||
}
|
||||
|
||||
get task(){
|
||||
return this._task;
|
||||
}
|
||||
|
||||
get runCount(){
|
||||
return this._runCount;
|
||||
}
|
||||
|
||||
get precision(){
|
||||
return this._config.precision;
|
||||
}
|
||||
|
||||
set task(task){
|
||||
if(task instanceof Function){
|
||||
this._task = task;
|
||||
}else{
|
||||
throw new TypeError('Task "task" must be instance of "function", Type of "' + typeof task + '" given');
|
||||
}
|
||||
}
|
||||
|
||||
get(option){
|
||||
return this._config[option];
|
||||
}
|
||||
|
||||
set(option, value){
|
||||
this._config[option] = value;
|
||||
}
|
||||
|
||||
setManager(manager){
|
||||
this._manager = manager;
|
||||
}
|
||||
|
||||
isRunning(){
|
||||
return !!this._runQueue.size;
|
||||
}
|
||||
|
||||
delete(){
|
||||
let isDeleted = false;
|
||||
if(this._manager){
|
||||
isDeleted = this._manager.delete(this.name);
|
||||
}
|
||||
return isDeleted;
|
||||
}
|
||||
|
||||
isDue(timer){
|
||||
if(this._config.dueDate instanceof Date){
|
||||
// run once at dueDate
|
||||
if(new Date().getTime() >= this._config.dueDate.getTime()){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
// periodic execution
|
||||
let totalTimeValues = timer.getTotalTimeValues();
|
||||
let totalTimeValuePrecision = totalTimeValues[this.precision];
|
||||
totalTimeValuePrecision -= this._lastTotalTimeValues ? this._lastTotalTimeValues[this.precision] : 0;
|
||||
if(
|
||||
this._config.interval === 1 ||
|
||||
totalTimeValuePrecision % this._config.interval === 0
|
||||
){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
invoke(timer){
|
||||
if(
|
||||
this.isDue(timer) &&
|
||||
(!this.isRunning() || this._config.isParallel)
|
||||
){
|
||||
this.run(timer);
|
||||
}
|
||||
}
|
||||
|
||||
run(timer){
|
||||
this._lastTotalTimeValues = Object.assign({}, timer.getTotalTimeValues());
|
||||
let runId = 'run_' + (++this._runCount);
|
||||
let runExec = resolve => {
|
||||
resolve(this.task(timer, this));
|
||||
};
|
||||
|
||||
let myProm = this._config.timeout > 0 ? new TimeoutPromise(runExec, this._config.timeout) : new Promise(runExec);
|
||||
myProm.then(payload => {
|
||||
// resolved within timeout -> wait for finally() block
|
||||
}).catch(error => {
|
||||
if(error instanceof Error){
|
||||
// either timeout error or error from rejected deferredPromise
|
||||
console.warn(error);
|
||||
}
|
||||
}).finally(() => {
|
||||
// no matter if TimeoutPromise is resolved or rejected
|
||||
// -> remove from _runQueue
|
||||
this._runQueue.delete(runId);
|
||||
|
||||
// remove this task from store after run
|
||||
if(this._config.dueDate instanceof Date){
|
||||
this.delete();
|
||||
}
|
||||
});
|
||||
|
||||
this._runQueue.set(runId, myProm);
|
||||
}
|
||||
};
|
||||
|
||||
Task.defaultConfig = {
|
||||
precision: 'seconds', // updateEvent this tasked will be subscribed to
|
||||
isParallel: false, // if true this task can run parallel, e.g. if prev execution has not finished
|
||||
interval: 1, // relates to 'precision'. 'interval' = 3 and 'precision' = "seconds" -> run every 3 seconds
|
||||
dueDate: undefined, // if Date() instance is set, task only runs once at dueDate
|
||||
timeout: 50 // if > 0, execution time that exceeds timeout (ms) throw error
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An instance of CronManager() handles multiple Task()´s
|
||||
* -> Task()´s can be set()/delete() from CronManager() instance
|
||||
* @type {CronManager}
|
||||
*/
|
||||
let CronManager = class CronManager {
|
||||
|
||||
constructor(config){
|
||||
this._config = Object.assign({}, this.constructor.defaultConfig, config);
|
||||
this._timerConfig = Object.assign({}, this.constructor.defaultTimerConfig);
|
||||
|
||||
this._tasks = new Map();
|
||||
this._timer = new easytimer.Timer();
|
||||
|
||||
// init Easytimer update events
|
||||
this._config.precisions.map(precision => precision + 'Updated').forEach(eventName => {
|
||||
this._timer.on(eventName, e => {
|
||||
let precision = e.type.substring(0, e.type.indexOf('Updated'));
|
||||
this.tasksByPrecision(precision).forEach(task => task.invoke(e.detail.timer));
|
||||
});
|
||||
});
|
||||
|
||||
this.debug = (msg,...data) => {
|
||||
if(this._config.debug){
|
||||
data = (data || []);
|
||||
console.debug(msg, ...data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
new(name, config){
|
||||
return new Task(name, config);
|
||||
}
|
||||
|
||||
set(task){
|
||||
if(task instanceof Task){
|
||||
// check for unique task name, or update existing task
|
||||
if(!this.has(task.name) || (this.get(task.name) === task)){
|
||||
// set new or update existing task
|
||||
task.setManager(this);
|
||||
this._tasks.set(task.name, task);
|
||||
this.debug('SET/UPDATE task: %o config: %o', task.name, task);
|
||||
// start timer (if it is not already running)
|
||||
this.auto();
|
||||
}else{
|
||||
console.warn('FAILED to set task. Task name %o already exists', task.name);
|
||||
}
|
||||
}else{
|
||||
throw new TypeError('Parameter must be instance of Task');
|
||||
}
|
||||
}
|
||||
|
||||
setNew(name, config){
|
||||
this.set(this.new(name, config));
|
||||
}
|
||||
|
||||
get(name){
|
||||
return this._tasks.get(name);
|
||||
}
|
||||
|
||||
has(name){
|
||||
return this._tasks.has(name);
|
||||
}
|
||||
|
||||
delete(name){
|
||||
let isDeleted = this._tasks.delete(name);
|
||||
if(isDeleted){
|
||||
this.debug('DELETE task: %o', name);
|
||||
this.auto();
|
||||
}
|
||||
return isDeleted;
|
||||
}
|
||||
|
||||
clear(){
|
||||
this.debug('CLEAR all %o task(s)', this._tasks.size);
|
||||
this._tasks.clear();
|
||||
this.auto();
|
||||
}
|
||||
|
||||
tasksByPrecision(precision){
|
||||
let tasks = [];
|
||||
this._tasks.forEach(task => {
|
||||
if(precision === task.precision){
|
||||
tasks.push(task);
|
||||
}
|
||||
});
|
||||
return tasks;
|
||||
}
|
||||
|
||||
// EasyTimer controls -----------------------------------------------------------------------------------------
|
||||
start(){
|
||||
this._timer.start(this._timerConfig);
|
||||
}
|
||||
|
||||
stop(){
|
||||
this._timer.stop();
|
||||
}
|
||||
|
||||
pause(){
|
||||
this._timer.pause();
|
||||
}
|
||||
|
||||
reset(){
|
||||
this._timer.reset();
|
||||
}
|
||||
|
||||
auto(){
|
||||
if(this._tasks.size){
|
||||
if(!this._timer.isRunning()){
|
||||
this.start();
|
||||
this.debug('START [auto] timer. %o task(s) found.', this._tasks.size);
|
||||
}
|
||||
}else{
|
||||
this.stop();
|
||||
this.debug('STOP [auto] timer. No tasks set.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CronManager.defaultConfig = {
|
||||
precisions: [
|
||||
'secondTenths',
|
||||
'seconds',
|
||||
'minutes',
|
||||
'hours',
|
||||
'days'
|
||||
],
|
||||
debug: false // debug output in console
|
||||
};
|
||||
|
||||
CronManager.defaultTimerConfig = {
|
||||
precision: 'secondTenths', // Timer update frequency. Values: 'secondTenths', 'seconds', 'minutes', 'hours'
|
||||
countdown: false // If true, the timer is a countdown
|
||||
};
|
||||
|
||||
return new CronManager({
|
||||
debug: false
|
||||
});
|
||||
});
|
||||
406
js/app/lib/dragSelect.js
Normal file
406
js/app/lib/dragSelect.js
Normal file
@@ -0,0 +1,406 @@
|
||||
define(['app/lib/eventHandler'], (EventHandler) => {
|
||||
'use strict';
|
||||
|
||||
let DragSelect = class DragSelect {
|
||||
|
||||
constructor(config){
|
||||
this._config = Object.assign({}, this.constructor.defaultConfig, config);
|
||||
this._instanceId = ++this.constructor.instanceCount;
|
||||
this._instanceName = [this._config.namespace, this._instanceId].join('-');
|
||||
this._animationFrameId = null;
|
||||
this._mouseIsDown = false;
|
||||
this._cursorPosition = {x: 0, y: 0};
|
||||
this._selectBoxDimHash = undefined;
|
||||
this._deselectedElements = [];
|
||||
|
||||
this._targetDim = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 10,
|
||||
height: 10
|
||||
};
|
||||
|
||||
this._selectBoxOrigin = {
|
||||
left: 0,
|
||||
top: 0
|
||||
};
|
||||
|
||||
this.init();
|
||||
|
||||
this.debug = (msg,...data) => {
|
||||
if(this._config.debug){
|
||||
data = (data || []);
|
||||
console.debug(msg, ...data);
|
||||
}
|
||||
};
|
||||
|
||||
this.debugEvent = e => {
|
||||
if(this._config.debugEvents){
|
||||
let arrow = '?';
|
||||
switch(e.type){
|
||||
case 'mousedown': arrow = '⯆'; break;
|
||||
case 'mousemove': arrow = '⯈'; break;
|
||||
case 'mouseup': arrow = '⯅'; break;
|
||||
}
|
||||
this.debug('ON ' + arrow + ' %s currentTarget: %o event: %o', e.type, e.currentTarget, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* set/update target target element dimension
|
||||
* -> must be updated if target element is scrolled/or pushed by e.g. slide menu
|
||||
*/
|
||||
setTargetDimensions(){
|
||||
let domRect = this._config.target.getBoundingClientRect();
|
||||
Object.assign(this._targetDim, this.filterDomRect(domRect));
|
||||
}
|
||||
|
||||
/**
|
||||
* set/update boundary element dimension [optional]
|
||||
* -> required for 'intersection' check e.g. for scrollable target
|
||||
* @returns {DOMRect}
|
||||
*/
|
||||
setBoundaryDimensions(){
|
||||
if(this._config.boundary){
|
||||
let boundary = this._config.target.closest(this._config.boundary);
|
||||
if(boundary){
|
||||
return (this._boundaryDim = boundary.getBoundingClientRect());
|
||||
}
|
||||
}
|
||||
delete this._boundaryDim;
|
||||
}
|
||||
|
||||
/**
|
||||
* set/update current cursor coordinates
|
||||
* @param e
|
||||
*/
|
||||
setCursorPosition(e){
|
||||
Object.assign(this._cursorPosition, {
|
||||
x: e.pageX,
|
||||
y: e.pageY
|
||||
});
|
||||
}
|
||||
|
||||
init(){
|
||||
this.initSelectBox();
|
||||
this.setTargetDimensions();
|
||||
|
||||
EventHandler.addEventListener(this._config.target, this.getNamespaceEvent('mousedown'), this.onMouseDown.bind(this), {passive: false});
|
||||
EventHandler.addEventListener(this._config.container, this.getNamespaceEvent('mousemove'), this.onMouseMove.bind(this), {passive: false});
|
||||
EventHandler.addEventListener(this._config.container, this.getNamespaceEvent('mouseup'), this.onMouseUp.bind(this), {passive: true});
|
||||
}
|
||||
|
||||
// Mouse events -----------------------------------------------------------------------------------------------
|
||||
onMouseDown(e){
|
||||
if(e.which === 1){
|
||||
this.debugEvent(e);
|
||||
this.setTargetDimensions();
|
||||
this.showSelectBox(e);
|
||||
this._mouseIsDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove(e){
|
||||
if(this._mouseIsDown){
|
||||
e.preventDefault();
|
||||
|
||||
if(this._animationFrameId){
|
||||
cancelAnimationFrame(this._animationFrameId);
|
||||
}
|
||||
|
||||
this._animationFrameId = requestAnimationFrame(() => {
|
||||
this.debugEvent(e);
|
||||
this.setCursorPosition(e);
|
||||
this.update();
|
||||
this._animationFrameId = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUp(e){
|
||||
this.debugEvent(e);
|
||||
this.selectElements();
|
||||
this.hideSelectBox();
|
||||
this._mouseIsDown = false;
|
||||
this._deselectedElements = [];
|
||||
}
|
||||
|
||||
// SelectBox handler ------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* create new selectBox and append to DOM
|
||||
* -> hidden by CSS until selectBox gets updated
|
||||
*/
|
||||
initSelectBox(){
|
||||
this._selectBox = document.createElement('div');
|
||||
this._selectBox.id = this._instanceName;
|
||||
this._selectBox.classList.add(this._config.selectBoxClass);
|
||||
this._selectBox.style.position = 'absolute';
|
||||
this._config.target.after(this._selectBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* show selectBox -> apply CSS for positioning and dimension
|
||||
* @param e
|
||||
*/
|
||||
showSelectBox(e){
|
||||
Object.assign(this._selectBoxOrigin, {
|
||||
left: e.pageX - this._targetDim.left,
|
||||
top: e.pageY - this._targetDim.top
|
||||
});
|
||||
|
||||
Object.assign(this._selectBox.style,{
|
||||
left: this._selectBoxOrigin.left + 'px',
|
||||
top: this._selectBoxOrigin.top + 'px',
|
||||
width: '1px',
|
||||
height: '1px'
|
||||
});
|
||||
|
||||
this._selectBox.classList.add(this._config.activeClass);
|
||||
|
||||
this.callback('onShow');
|
||||
}
|
||||
|
||||
/**
|
||||
* update selectBox position and dimension based on cursorPosition
|
||||
* @returns {boolean}
|
||||
*/
|
||||
updateSelectBox(){
|
||||
let updated = false;
|
||||
if(!this.isActiveSelectBox()){
|
||||
return updated;
|
||||
}
|
||||
|
||||
let left = this._cursorPosition.x - this._targetDim.left;
|
||||
let top = this._cursorPosition.y - this._targetDim.top;
|
||||
let tempWidth = this._selectBoxOrigin.left - left;
|
||||
let tempHeight = this._selectBoxOrigin.top - top;
|
||||
|
||||
let newLeft = this._selectBoxOrigin.left;
|
||||
let newTop = this._selectBoxOrigin.top;
|
||||
let newWidth = left - this._selectBoxOrigin.left;
|
||||
let newHeight = top - this._selectBoxOrigin.top;
|
||||
|
||||
if(newWidth < 0){
|
||||
newLeft = newLeft - tempWidth;
|
||||
newWidth = newWidth * -1;
|
||||
}
|
||||
|
||||
if(newHeight < 0){
|
||||
newTop = newTop - tempHeight;
|
||||
newHeight = newHeight * -1;
|
||||
}
|
||||
|
||||
// check if dimension has changed -> improve performance
|
||||
let dimensionHash = [newWidth, newHeight].join('_');
|
||||
if(this._selectBoxDimHash !== dimensionHash){
|
||||
this._selectBoxDimHash = dimensionHash;
|
||||
Object.assign(this._selectBox.style,{
|
||||
left: newLeft + 'px',
|
||||
top: newTop + 'px',
|
||||
width: newWidth + 'px',
|
||||
height: newHeight + 'px'
|
||||
});
|
||||
|
||||
// set drag position data (which corner)
|
||||
this._selectBox.dataset.origin = this.getSelectBoxDragOrigin(left, top, newLeft, newTop).join('|');
|
||||
|
||||
updated = true;
|
||||
this.callback('onUpdate');
|
||||
this.dispatch(this._selectBox, 'update', this);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* hide selectBox
|
||||
*/
|
||||
hideSelectBox(){
|
||||
if(!this.isActiveSelectBox()){
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.callback('onHide', this._deselectedElements) !== false){
|
||||
this._selectBox.classList.remove(this._config.activeClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cursor position corner for selectBox
|
||||
* @param left
|
||||
* @param top
|
||||
* @param newLeft
|
||||
* @param newTop
|
||||
* @returns {[string, string]}
|
||||
*/
|
||||
getSelectBoxDragOrigin(left, top, newLeft, newTop){
|
||||
let position = [];
|
||||
if(
|
||||
left === newLeft &&
|
||||
top === newTop
|
||||
){
|
||||
position = ['top', 'left'];
|
||||
}else if(top === newTop){
|
||||
position = ['top', 'right'];
|
||||
}else if(left === newLeft){
|
||||
position = ['bottom', 'left'];
|
||||
}else{
|
||||
position = ['bottom', 'right'];
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if there is currently an active selectBox visible
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isActiveSelectBox(){
|
||||
return this._selectBox.classList.contains(this._config.activeClass) &&
|
||||
!this._config.target.classList.contains(this._config.disabledClass);
|
||||
}
|
||||
|
||||
// Element select methods -------------------------------------------------------------------------------------
|
||||
selectableElements(){
|
||||
return this._config.target.querySelectorAll(this._config.selectables);
|
||||
}
|
||||
|
||||
selectElements(){
|
||||
if(!this.isActiveSelectBox()){
|
||||
return;
|
||||
}
|
||||
|
||||
let selectables = this.selectableElements();
|
||||
let selectBoxDim = this.filterDomRect(this._selectBox.getBoundingClientRect());
|
||||
|
||||
selectables.forEach(el => {
|
||||
let elDim = this.filterDomRect(el.getBoundingClientRect());
|
||||
|
||||
if(this.percentCovered(selectBoxDim, elDim) > this._config.percentCovered){
|
||||
el.classList.add(this._config.selectedClass);
|
||||
// remove element from "deselected" elements (e.g on add -> remove -> add scenario)
|
||||
this._deselectedElements = this._deselectedElements.filter(tempEl => tempEl !== el);
|
||||
}else{
|
||||
if(el.classList.contains(this._config.selectedClass)){
|
||||
el.classList.remove(this._config.selectedClass);
|
||||
// add to "deselected" elements, if not already in array
|
||||
if(this._deselectedElements.findIndex(tempEl => tempEl === el) === -1){
|
||||
this._deselectedElements.push(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
percentCovered(dim1, dim2){
|
||||
if(
|
||||
(dim1.left <= dim2.left) &&
|
||||
(dim1.top <= dim2.top) &&
|
||||
((dim1.left + dim1.width) >= (dim2.left + dim2.width)) &&
|
||||
((dim1.top + dim1.height) > (dim2.top + dim2.height))
|
||||
){
|
||||
// The whole thing is covering the whole other thing
|
||||
return 100;
|
||||
}else{
|
||||
// Only parts may be covered, calculate percentage
|
||||
dim1.right = dim1.left + dim1.width;
|
||||
dim1.bottom = dim1.top + dim1.height;
|
||||
dim2.right = dim2.left + dim2.width;
|
||||
dim2.bottom = dim2.top + dim2.height;
|
||||
|
||||
let l = Math.max(dim1.left, dim2.left);
|
||||
let r = Math.min(dim1.right, dim2.right);
|
||||
let t = Math.max(dim1.top, dim2.top);
|
||||
let b = Math.min(dim1.bottom, dim2.bottom);
|
||||
|
||||
if(b >= t && r >= l){
|
||||
return (((r - l) * (b - t)) / (dim2.width * dim2.height)) * 100;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Boundary intersection methods ------------------------------------------------------------------------------
|
||||
getIntersection(){
|
||||
let intersection = [];
|
||||
if(this.isActiveSelectBox && this._boundaryDim){
|
||||
let selectBoxDim = this._selectBox.getBoundingClientRect();
|
||||
if(this._boundaryDim.top > selectBoxDim.top){
|
||||
intersection.push('top');
|
||||
}
|
||||
if(this._boundaryDim.bottom < selectBoxDim.bottom){
|
||||
intersection.push('bottom');
|
||||
}
|
||||
if(this._boundaryDim.left > selectBoxDim.left){
|
||||
intersection.push('left');
|
||||
}
|
||||
if(this._boundaryDim.right < selectBoxDim.right){
|
||||
intersection.push('right');
|
||||
}
|
||||
}
|
||||
return intersection;
|
||||
}
|
||||
|
||||
// Util methods -----------------------------------------------------------------------------------------------
|
||||
update(){
|
||||
this.setTargetDimensions();
|
||||
this.setBoundaryDimensions();
|
||||
|
||||
if(this.updateSelectBox() && this._config.selectOnDrag){
|
||||
this.selectElements();
|
||||
}
|
||||
}
|
||||
|
||||
getNamespaceEvent(type){
|
||||
return [type, this._instanceName].join('.');
|
||||
}
|
||||
|
||||
filterDomRect(domRect, filteredKeys = ['left', 'top', 'width', 'height']){
|
||||
let obj = {};
|
||||
filteredKeys.forEach(key => {
|
||||
if(domRect[key] !== undefined){
|
||||
obj[key] = domRect[key];
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
//return filteredKeys.reduce((obj, key) => ({ ...obj, [key]: domRect[key] }), {}); // same result but uses "object destruction" ES5
|
||||
}
|
||||
|
||||
callback(callback, ...args){
|
||||
if(this._config[callback] instanceof Function){
|
||||
return this._config[callback](...args);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(target, type, data = null){
|
||||
let event = new CustomEvent([type, this._config.namespace].join(':'), {
|
||||
bubbles: true,
|
||||
detail: data
|
||||
});
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
DragSelect.defaultConfig = {
|
||||
container: document,
|
||||
target: document.body,
|
||||
namespace: 'dragSelect',
|
||||
activeClass: 'active',
|
||||
disabledClass: 'disabled',
|
||||
selectables: 'div',
|
||||
selectedClass: 'dragSelect-selected',
|
||||
selectBoxClass: 'dragSelect-selectBox',
|
||||
boundary: undefined, // optional selector for boundary parent box (e.g. scrollable viewport)
|
||||
selectOnDrag: true,
|
||||
percentCovered: 25,
|
||||
onShow: undefined,
|
||||
onHide: undefined,
|
||||
onUpdate: undefined,
|
||||
debug: false,
|
||||
debugEvents: false
|
||||
};
|
||||
|
||||
DragSelect.instanceCount = 0;
|
||||
|
||||
return DragSelect;
|
||||
});
|
||||
29
js/app/lib/eventHandler.js
Normal file
29
js/app/lib/eventHandler.js
Normal file
@@ -0,0 +1,29 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
let EventHandler = class EventHandler {
|
||||
|
||||
constructor(){
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
addEventListener(target, type, listener, options){
|
||||
this._listeners.set(type, listener);
|
||||
target.addEventListener(this.constructor.eventParts(type).event, listener, options);
|
||||
}
|
||||
|
||||
removeEventListener(target, type){
|
||||
target.removeEventListener(this.constructor.eventParts(type).event, this._listeners.get(type));
|
||||
this._listeners.delete(type);
|
||||
}
|
||||
|
||||
static eventParts(type){
|
||||
return type.split('.').reduce((acc, val, i) => {
|
||||
acc[i ? 'namespace' : 'event'] = val;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
};
|
||||
|
||||
return new EventHandler();
|
||||
});
|
||||
95
js/app/lib/prototypes.js
vendored
Normal file
95
js/app/lib/prototypes.js
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Array diff
|
||||
* [1,2,3,4,5].diff([4,5,6]) => [1,2,3]
|
||||
* @param a
|
||||
* @returns {*[]}
|
||||
*/
|
||||
Array.prototype.diff = function(a){
|
||||
return this.filter(i => !a.includes(i));
|
||||
};
|
||||
|
||||
/**
|
||||
* Array intersect
|
||||
* [1,2,3,4,5].intersect([4,5,6]) => [4,5]
|
||||
* @param a
|
||||
* @returns {*[]}
|
||||
*/
|
||||
Array.prototype.intersect = function(a){
|
||||
return this.filter(i => a.includes(i));
|
||||
};
|
||||
|
||||
/**
|
||||
* compares two arrays if all elements in a are also in b
|
||||
* element order is ignored
|
||||
* @param a
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Array.prototype.equalValues = function(a){
|
||||
return this.diff(a).concat(a.diff(this)).length === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* like Array.concat() + remove duplicate values
|
||||
* @see https://stackoverflow.com/a/38940354/4329969
|
||||
* @param a
|
||||
* @returns {*[]}
|
||||
*/
|
||||
Array.prototype.concatFilter = function(a){
|
||||
return [...new Set([...this,...a])];
|
||||
};
|
||||
|
||||
/**
|
||||
* sort array of objects by property name
|
||||
* @param p
|
||||
* @returns {Array.<T>}
|
||||
*/
|
||||
Array.prototype.sortBy = function(p){
|
||||
return this.slice(0).sort((a,b) => {
|
||||
return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* capitalize first letter
|
||||
* @returns {string}
|
||||
*/
|
||||
String.prototype.capitalize = function(){
|
||||
return this.charAt(0).toUpperCase() + this.slice(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* get hash from string
|
||||
* @returns {number}
|
||||
*/
|
||||
String.prototype.hashCode = function(){
|
||||
let hash = 0, i, chr;
|
||||
if(this.length === 0) return hash;
|
||||
for(i = 0; i < this.length; i++){
|
||||
chr = this.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
String.prototype.trimLeftChars = function(charList){
|
||||
if(charList === undefined)
|
||||
charList = '\\s';
|
||||
return this.replace(new RegExp('^[' + charList + ']+'), '');
|
||||
};
|
||||
|
||||
String.prototype.trimRightChars = function(charList){
|
||||
if(charList === undefined)
|
||||
charList = '\\s';
|
||||
return this.replace(new RegExp('[' + charList + ']+$'), '');
|
||||
};
|
||||
|
||||
String.prototype.trimChars = function(charList){
|
||||
return this.trimLeftChars(charList).trimRightChars(charList);
|
||||
};
|
||||
|
||||
return {};
|
||||
});
|
||||
@@ -6,8 +6,9 @@ define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/counter',
|
||||
'bootbox'
|
||||
], ($, Init, Util, bootbox) => {
|
||||
], ($, Init, Util, Counter, bootbox) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -31,11 +32,11 @@ define([
|
||||
* updated "sync status" dynamic dialog area
|
||||
*/
|
||||
let updateSyncStatus = () => {
|
||||
|
||||
// check if task manager dialog is open
|
||||
let logDialog = $('#' + config.taskDialogId);
|
||||
if(logDialog.length){
|
||||
// dialog is open
|
||||
let statusArea = logDialog.find('.' + config.taskDialogStatusAreaClass);
|
||||
requirejs(['text!templates/modules/sync_status.html', 'mustache'], (templateSyncStatus, Mustache) => {
|
||||
let data = {
|
||||
timestampCounterClass: config.timestampCounterClass,
|
||||
@@ -49,10 +50,12 @@ define([
|
||||
};
|
||||
|
||||
let syncStatusElement = $(Mustache.render(templateSyncStatus, data ));
|
||||
Counter.destroyTimestampCounter(statusArea, true);
|
||||
|
||||
logDialog.find('.' + config.taskDialogStatusAreaClass).html( syncStatusElement );
|
||||
statusArea.html(syncStatusElement);
|
||||
|
||||
logDialog.find('.' + config.timestampCounterClass).initTimestampCounter();
|
||||
let counterElements = syncStatusElement.find('.' + config.timestampCounterClass);
|
||||
Counter.initTimestampCounter(counterElements);
|
||||
|
||||
syncStatusElement.initTooltips({
|
||||
placement: 'right'
|
||||
|
||||
@@ -689,6 +689,7 @@ define([
|
||||
link: this.characterElement.data('href'),
|
||||
cookieName: this.cookieName,
|
||||
browserTabId: this.browserTabId,
|
||||
ccpImageServer: responseData.ccpImageServer,
|
||||
character: responseData.character,
|
||||
isManager: Util.getObjVal(responseData, 'character.role.name') === 'CORPORATION',
|
||||
isAdmin: Util.getObjVal(responseData, 'character.role.name') === 'SUPER',
|
||||
|
||||
@@ -15,6 +15,9 @@ define([
|
||||
endpointContextMenuId: 'pf-map-endpoint-contextmenu', // id for "endpoints" context menu
|
||||
systemContextMenuId: 'pf-map-system-contextmenu', // id for "systems" context menu
|
||||
|
||||
contextMenuClass: 'dropdown-menu', // class for all context menus
|
||||
subMenuLeftClass: 'dropdown-submenu-left', // class moves submenus to the left side
|
||||
|
||||
animationInType: 'transition.flipXIn',
|
||||
animationInDuration: 150,
|
||||
animationOutType: 'transition.flipXOut',
|
||||
@@ -29,14 +32,20 @@ define([
|
||||
*/
|
||||
let getMenuLeftCoordinate = (e, menuWidth) => {
|
||||
let mouseWidth = e.pageX;
|
||||
let pageWidth = $(window).width();
|
||||
|
||||
// opening menu would pass the side of the page
|
||||
if(mouseWidth + menuWidth > pageWidth &&
|
||||
menuWidth < mouseWidth){
|
||||
return mouseWidth - menuWidth;
|
||||
let openSubLeft = false;
|
||||
if(mouseWidth + menuWidth > window.innerWidth && menuWidth < mouseWidth){
|
||||
// opening menu would pass the side of the page
|
||||
openSubLeft = true;
|
||||
//return mouseWidth - menuWidth;
|
||||
mouseWidth -= menuWidth;
|
||||
}else if(mouseWidth + menuWidth * 2 > window.innerWidth && menuWidth * 2 < mouseWidth){
|
||||
// opening submenu would pass the side of the page
|
||||
openSubLeft = true;
|
||||
}
|
||||
return mouseWidth;
|
||||
return {
|
||||
left: mouseWidth,
|
||||
openSubLeft: openSubLeft
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -47,14 +56,13 @@ define([
|
||||
*/
|
||||
let getMenuTopCoordinate = (e, menuHeight) => {
|
||||
let mouseHeight = e.pageY;
|
||||
let pageHeight = $(window).height();
|
||||
|
||||
// opening menu would pass the bottom of the page
|
||||
if(mouseHeight + menuHeight > pageHeight &&
|
||||
menuHeight < mouseHeight){
|
||||
return mouseHeight - menuHeight;
|
||||
if(mouseHeight + menuHeight > window.innerHeight && menuHeight < mouseHeight){
|
||||
// opening menu would pass the bottom of the page
|
||||
mouseHeight -= menuHeight;
|
||||
}
|
||||
return mouseHeight;
|
||||
return {
|
||||
top: mouseHeight
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -98,7 +106,7 @@ define([
|
||||
{icon: 'fa-reply fa-rotate-180', action: 'change_status', text: 'mass status', subitems: [
|
||||
{subIcon: 'fa-circle', subIconClass: 'txt-color txt-color-gray', subAction: 'status_fresh', subText: 'stage 1 (fresh)'},
|
||||
{subIcon: 'fa-circle', subIconClass: 'txt-color txt-color-orange', subAction: 'status_reduced', subText: 'stage 2 (reduced)'},
|
||||
{subIcon: 'fa-circle', subIconClass: 'txt-color txt-color-redDarker', subAction: 'status_critical', subText: 'stage 3 (critical)'}
|
||||
{subIcon: 'fa-circle', subIconClass: 'txt-color txt-color-redDark', subAction: 'status_critical', subText: 'stage 3 (critical)'}
|
||||
|
||||
]},
|
||||
{icon: 'fa-reply fa-rotate-180', action: 'wh_jump_mass_change', text: 'ship size', subitems: [
|
||||
@@ -213,7 +221,7 @@ define([
|
||||
* @param excludeMenu
|
||||
*/
|
||||
let closeMenus = excludeMenu => {
|
||||
let allMenus = $('.dropdown-menu[role="menu"]');
|
||||
let allMenus = $('.' + config.contextMenuClass + '[role="menu"]');
|
||||
if(excludeMenu){
|
||||
allMenus = allMenus.not(excludeMenu);
|
||||
}
|
||||
@@ -243,10 +251,13 @@ define([
|
||||
// hide/activate/disable
|
||||
menuElement = prepareMenu(menuElement, menuConfig.hidden, menuConfig.active, menuConfig.disabled);
|
||||
|
||||
menuElement.css({
|
||||
let {left, openSubLeft} = getMenuLeftCoordinate(e, menuElement.width());
|
||||
let {top} = getMenuTopCoordinate(e, menuElement.height());
|
||||
|
||||
menuElement.toggleClass(config.subMenuLeftClass, openSubLeft).css({
|
||||
position: 'absolute',
|
||||
left: getMenuLeftCoordinate(e, menuElement.width()),
|
||||
top: getMenuTopCoordinate(e, menuElement.height())
|
||||
left: left,
|
||||
top: top
|
||||
}).velocity(config.animationInType, {
|
||||
duration: config.animationInDuration,
|
||||
complete: function(){
|
||||
|
||||
@@ -7,8 +7,9 @@ define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/map/util'
|
||||
], function($, Init, Util, MapUtil){
|
||||
'app/map/util',
|
||||
'app/map/overlay/util'
|
||||
], function($, Init, Util, MapUtil, MapOverlayUtil){
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
@@ -93,6 +94,7 @@ define([
|
||||
let setOverlayObserver = (overlay, mapId) => {
|
||||
let overlayMain = overlay.find('.' + config.overlayLocalMainClass);
|
||||
|
||||
// open/close toggle ------------------------------------------------------------------------------------------
|
||||
overlayMain.on('click', function(){
|
||||
let overlayMain = $(this).parent('.' + config.overlayLocalClass);
|
||||
let isOpenStatus = isOpen(overlayMain);
|
||||
@@ -108,6 +110,15 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
// trigger table re-draw() ------------------------------------------------------------------------------------
|
||||
let mapWrapper = overlay.parents('.' + MapUtil.config.mapWrapperClass);
|
||||
mapWrapper.on('pf:mapResize', function(e){
|
||||
let tableElement = overlay.find('.' + config.overlayLocalTableClass);
|
||||
let tableApi = tableElement.DataTable();
|
||||
tableApi.draw('full-hold');
|
||||
});
|
||||
|
||||
// tooltips ---------------------------------------------------------------------------------------------------
|
||||
overlayMain.initTooltips({
|
||||
container: 'body',
|
||||
placement: 'bottom'
|
||||
@@ -229,7 +240,7 @@ define([
|
||||
let indexesRemove = filterRows(localTable, 'id', characterAllIds, false);
|
||||
localTable.rows(indexesRemove).remove();
|
||||
|
||||
localTable.draw();
|
||||
localTable.draw('full-hold');
|
||||
|
||||
// update system relevant data in overlay -----------------------------------------------------------------
|
||||
updateLocaleHeadline(overlay, systemData, characterAllIds.length, characterLocalIds.length);
|
||||
@@ -339,12 +350,53 @@ define([
|
||||
overlay.append(overlayMain);
|
||||
overlay.append(content);
|
||||
|
||||
parentElement.append(overlay);
|
||||
|
||||
// set observer
|
||||
setOverlayObserver(overlay, mapId);
|
||||
|
||||
parentElement.append(overlay);
|
||||
|
||||
// init local table ---------------------------------------------------------------------------------------
|
||||
table.on('preDraw.dt', function(e, settings){
|
||||
let table = $(this);
|
||||
let mapWrapper = table.parents('.' + MapUtil.config.mapWrapperClass);
|
||||
|
||||
// mapWrapper should always exist
|
||||
if(mapWrapper && mapWrapper.length) {
|
||||
// check available maxHeight for "locale" table based on current map height (resizable)
|
||||
let mapHeight = mapWrapper[0].offsetHeight;
|
||||
let localOverlay = MapOverlayUtil.getMapOverlay(table, 'local');
|
||||
let paginationElement = localOverlay.find('.dataTables_paginate');
|
||||
|
||||
let tableApi = table.DataTable();
|
||||
let pageInfo = tableApi.page.info();
|
||||
let localTableRowHeight = 26;
|
||||
|
||||
let localTop = localOverlay[0].offsetTop;
|
||||
let bottomSpace = 38 + 10; // "timer" overlay + some spacing top
|
||||
bottomSpace += 16 + 5 + 5; // horizontal scrollBar height + some spacing top + bottom
|
||||
let localHeightMax = mapHeight - bottomSpace - localTop; // max available for local overlay
|
||||
|
||||
let localTableBodyMaxHeight = localHeightMax - 53; // - headline height + <thead> height
|
||||
let newPageLength = Math.floor(localTableBodyMaxHeight / localTableRowHeight);
|
||||
if(pageInfo.recordsDisplay > newPageLength){
|
||||
// show pagination and limit page length
|
||||
localTableBodyMaxHeight -= 30; // - pagination height
|
||||
newPageLength = Math.floor(localTableBodyMaxHeight / localTableRowHeight);
|
||||
}
|
||||
|
||||
if(pageInfo.length !== newPageLength){
|
||||
tableApi.page.len(newPageLength);
|
||||
|
||||
// page length changed -> show/hide pagination
|
||||
pageInfo = tableApi.page.info();
|
||||
if(pageInfo.pages <= 1){
|
||||
paginationElement.hide();
|
||||
}else{
|
||||
paginationElement.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
table.on('draw.dt', function(e, settings){
|
||||
// init table tooltips
|
||||
@@ -352,15 +404,6 @@ define([
|
||||
container: 'body',
|
||||
placement: 'left'
|
||||
});
|
||||
|
||||
// hide pagination in case of only one page
|
||||
let paginationElement = overlay.find('.dataTables_paginate');
|
||||
let pageElements = paginationElement.find('span .paginate_button');
|
||||
if(pageElements.length <= 1){
|
||||
paginationElement.hide();
|
||||
}else{
|
||||
paginationElement.show();
|
||||
}
|
||||
});
|
||||
|
||||
// table init complete
|
||||
@@ -372,9 +415,10 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
let localTable = table.DataTable( {
|
||||
pageLength: 13, // hint: if pagination visible => we need space to show it
|
||||
let localTable = table.DataTable({
|
||||
pageLength: 5,
|
||||
paging: true,
|
||||
pagingType: 'simple',
|
||||
lengthChange: false,
|
||||
ordering: true,
|
||||
order: [ 0, 'asc' ],
|
||||
@@ -386,7 +430,11 @@ define([
|
||||
return 'pf-local-row_' + rowData.id; // characterId
|
||||
},
|
||||
language: {
|
||||
emptyTable: '<span>You are alone</span>'
|
||||
emptyTable: '<span>You are alone</span>',
|
||||
paginate: {
|
||||
next: ' ',
|
||||
previous: ' '
|
||||
}
|
||||
},
|
||||
columnDefs: [
|
||||
{
|
||||
@@ -422,7 +470,7 @@ define([
|
||||
_: (data, type, row, meta) => {
|
||||
let value = data.typeName;
|
||||
if(type === 'display'){
|
||||
value = '<img src="' + Util.eveImageUrl('render', data.typeId) + '"/>';
|
||||
value = '<img src="' + Util.eveImageUrl('types', data.typeId) + '"/>';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ define([
|
||||
* @private
|
||||
*/
|
||||
let _dragFilter = systemId => {
|
||||
let filterClasses = ['jtk-drag', 'pf-system-locked'];
|
||||
let filterClasses = ['jtk-drag', MapUtil.config.systemLockedClass];
|
||||
return ![...document.getElementById(systemId).classList].some(className => filterClasses.indexOf(className) >= 0);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ define([
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/key',
|
||||
'app/lib/dragSelect',
|
||||
'app/lib/eventHandler',
|
||||
'bootbox',
|
||||
'app/map/util',
|
||||
'app/map/contextmenu',
|
||||
@@ -16,9 +18,8 @@ define([
|
||||
'app/map/layout',
|
||||
'app/map/magnetizing',
|
||||
'app/map/scrollbar',
|
||||
'dragToSelect',
|
||||
'app/map/local'
|
||||
], ($, Init, Util, Key, bootbox, MapUtil, MapContextMenu, MapOverlay, MapOverlayUtil, System, Layout, Magnetizer, Scrollbar) => {
|
||||
], ($, Init, Util, Key, DragSelect, EventHandler, bootbox, MapUtil, MapContextMenu, MapOverlay, MapOverlayUtil, System, Layout, Magnetizer, Scrollbar) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -33,7 +34,6 @@ define([
|
||||
systemClass: 'pf-system', // class for all systems
|
||||
systemActiveClass: 'pf-system-active', // class for an active system on a map
|
||||
systemSelectedClass: 'pf-system-selected', // class for selected systems on a map
|
||||
systemLockedClass: 'pf-system-locked', // class for locked systems on a map
|
||||
systemHeadClass: 'pf-system-head', // class for system head
|
||||
systemHeadNameClass: 'pf-system-head-name', // class for system name
|
||||
systemHeadCounterClass: 'pf-system-head-counter', // class for system user counter
|
||||
@@ -644,7 +644,7 @@ define([
|
||||
case 'add_first_waypoint':
|
||||
case 'add_last_waypoint':
|
||||
systemData = system.getSystemData();
|
||||
Util.setDestination(systemData, action);
|
||||
Util.setDestination(action, 'system', {id: systemData.systemId, name: systemData.name});
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -1109,6 +1109,8 @@ define([
|
||||
width = parseInt(width.substring(0, width.length - 2)) || 0;
|
||||
height = parseInt(height.substring(0, height.length - 2)) || 0;
|
||||
|
||||
mapWrapper.trigger('pf:mapResize');
|
||||
|
||||
let promiseStore = MapUtil.getLocaleData('map', mapConfig.config.id );
|
||||
promiseStore.then((data) => {
|
||||
let storeData = true;
|
||||
@@ -1988,13 +1990,10 @@ define([
|
||||
}
|
||||
dragSystem.find('.' + config.systemHeadNameClass).editable('option', 'placement', placement);
|
||||
|
||||
// drag system is not always selected
|
||||
let selectedSystems = mapContainer.getSelectedSystems().get();
|
||||
selectedSystems = selectedSystems.concat(dragSystem.get());
|
||||
selectedSystems = $.unique( selectedSystems );
|
||||
|
||||
// repaint connections (and overlays) -> just in case something fails...
|
||||
revalidate(map, selectedSystems);
|
||||
// update all dragged systems -> added to DragSelection
|
||||
params.selection.forEach(elData => {
|
||||
MapUtil.markAsChanged($(elData[0]));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2112,7 +2111,7 @@ define([
|
||||
|
||||
if( system.data('locked') === true ){
|
||||
system.data('locked', false);
|
||||
system.removeClass( config.systemLockedClass );
|
||||
system.removeClass(MapUtil.config.systemLockedClass);
|
||||
|
||||
// enable draggable
|
||||
map.setDraggable(system, true);
|
||||
@@ -2122,7 +2121,7 @@ define([
|
||||
}
|
||||
}else{
|
||||
system.data('locked', true);
|
||||
system.addClass( config.systemLockedClass );
|
||||
system.addClass(MapUtil.config.systemLockedClass);
|
||||
|
||||
// enable draggable
|
||||
map.setDraggable(system, false);
|
||||
@@ -2431,11 +2430,17 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
// init drag-frame selection
|
||||
mapContainer.dragToSelect({
|
||||
selectOnMove: true,
|
||||
selectables: '.' + config.systemClass,
|
||||
onHide: function(selectBox, deselectedSystems){
|
||||
// init drag-frame selection ----------------------------------------------------------------------------------
|
||||
let dragSelect = new DragSelect({
|
||||
target: mapContainer[0],
|
||||
selectables: '.' + config.systemClass + ':not(.' + MapUtil.config.systemLockedClass + '):not(.' + MapUtil.config.systemHiddenClass + ')',
|
||||
selectedClass: MapUtil.config.systemSelectedClass,
|
||||
selectBoxClass: 'pf-map-drag-to-select',
|
||||
boundary: '.mCSB_container_wrapper',
|
||||
onShow: () => {
|
||||
Util.triggerMenuAction(document, 'Close');
|
||||
},
|
||||
onHide: (deselectedSystems) => {
|
||||
let selectedSystems = mapContainer.getSelectedSystems();
|
||||
|
||||
if(selectedSystems.length > 0){
|
||||
@@ -2444,23 +2449,19 @@ define([
|
||||
|
||||
// convert former group draggable systems so single draggable
|
||||
for(let i = 0; i < selectedSystems.length; i++){
|
||||
map.addToDragSelection( selectedSystems[i] );
|
||||
map.addToDragSelection(selectedSystems[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// convert former group draggable systems so single draggable
|
||||
for(let j = 0; j < deselectedSystems.length; j++){
|
||||
map.removeFromDragSelection( deselectedSystems[j] );
|
||||
for(let i = 0; i < deselectedSystems.length; i++){
|
||||
map.removeFromDragSelection(deselectedSystems[i]);
|
||||
}
|
||||
},
|
||||
onShow: function(){
|
||||
Util.triggerMenuAction(document, 'Close');
|
||||
},
|
||||
onRefresh: function(){
|
||||
}
|
||||
debug: false,
|
||||
debugEvents: false
|
||||
});
|
||||
|
||||
|
||||
// system body expand -----------------------------------------------------------------------------------------
|
||||
mapContainer.hoverIntent({
|
||||
over: function(e){
|
||||
@@ -3177,9 +3178,35 @@ define([
|
||||
let mapElement = mapWrapper.find('.' + config.mapClass);
|
||||
let mapId = mapElement.data('id');
|
||||
|
||||
let dragSelect;
|
||||
Scrollbar.initScrollbar(mapWrapper, {
|
||||
callbacks: {
|
||||
onInit: function(){
|
||||
let scrollWrapper = this;
|
||||
|
||||
// ++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
EventHandler.addEventListener(this, 'update:dragSelect', function(e){
|
||||
e.stopPropagation();
|
||||
dragSelect = e.detail;
|
||||
let intersection = dragSelect.getIntersection();
|
||||
let originData = dragSelect._selectBox.dataset.origin;
|
||||
let dragOrigin = originData ? originData.split('|', 2) : [];
|
||||
let position = [null, null];
|
||||
let inverseDirection = (directions, i) => directions[((i + 2) % directions.length + directions.length) % directions.length];
|
||||
|
||||
let allDirections = ['top', 'right', 'bottom', 'left'];
|
||||
allDirections.forEach((direction, i, allDirections) => {
|
||||
if(dragOrigin.includes(direction) && intersection.includes(direction)){
|
||||
position[i % 2] = direction;
|
||||
}else if(dragOrigin.includes(direction) && intersection.includes(inverseDirection(allDirections, i))){
|
||||
// reverse scroll (e.g. 1. drag&select scroll bottom end then move back to top)
|
||||
position[i % 2] = inverseDirection(allDirections, i);
|
||||
}
|
||||
});
|
||||
|
||||
Scrollbar.autoScroll(scrollWrapper, position);
|
||||
}, {capture: true});
|
||||
|
||||
// init 'space' key + 'mouse' down for map scroll -------------------------------------------------
|
||||
let scrollStart = [0, 0];
|
||||
let mouseStart = [0, 0];
|
||||
@@ -3263,11 +3290,11 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
this.addEventListener('keydown', keyDownHandler, { capture: false });
|
||||
this.addEventListener('keyup', keyUpHandler, { capture: false });
|
||||
this.addEventListener('mousemove', mouseMoveHandler, { capture: false });
|
||||
this.addEventListener('mousedown', mouseDownHandler, { capture: false });
|
||||
this.addEventListener('mouseup', mouseUpHandler, { capture: false });
|
||||
this.addEventListener('keydown', keyDownHandler, {capture: false});
|
||||
this.addEventListener('keyup', keyUpHandler, {capture: false});
|
||||
this.addEventListener('mousemove', mouseMoveHandler, {capture: false});
|
||||
this.addEventListener('mousedown', mouseDownHandler, {capture: false});
|
||||
this.addEventListener('mouseup', mouseUpHandler, {capture: false});
|
||||
},
|
||||
onScroll: function(){
|
||||
// scroll complete
|
||||
@@ -3287,6 +3314,11 @@ define([
|
||||
|
||||
// hide all system head tooltips
|
||||
$(this).find('.' + config.systemHeadClass + ' .fa').tooltip('hide');
|
||||
},
|
||||
whileScrolling: function(){
|
||||
if(dragSelect){
|
||||
dragSelect.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -222,21 +222,6 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* format json object with "time parts" into string
|
||||
* @param parts
|
||||
* @returns {string}
|
||||
*/
|
||||
let formatTimeParts = parts => {
|
||||
let label = '';
|
||||
if(parts.days){
|
||||
label += parts.days + 'd ';
|
||||
}
|
||||
label += ('00' + parts.hours).slice(-2);
|
||||
label += ':' + ('00' + parts.min).slice(-2);
|
||||
return label;
|
||||
};
|
||||
|
||||
/**
|
||||
* hide default icon and replace it with "loading" icon
|
||||
* @param iconElement
|
||||
@@ -429,8 +414,8 @@ define([
|
||||
|
||||
// format overlay label
|
||||
let labels = [
|
||||
formatTimeParts(createdDiff) + ' <i class="fas fa-fw fa-plus-square"></i>',
|
||||
formatTimeParts(updatedDiff) + ' <i class="fas fa-fw fa-pen-square"></i>'
|
||||
Util.formatTimeParts(createdDiff) + ' <i class="fas fa-fw fa-plus-square"></i>',
|
||||
Util.formatTimeParts(updatedDiff) + ' <i class="fas fa-fw fa-pen-square"></i>'
|
||||
];
|
||||
|
||||
// add label overlay --------------------------------------------------------------------------
|
||||
@@ -474,7 +459,7 @@ define([
|
||||
connection.addOverlay([
|
||||
'Label',
|
||||
{
|
||||
label: '<i class="fas fa-fw fa-hourglass-end"></i> ' + formatTimeParts(diff),
|
||||
label: '<i class="fas fa-fw fa-hourglass-end"></i> ' + Util.formatTimeParts(diff),
|
||||
id: MapOverlayUtil.config.connectionOverlayEolId,
|
||||
cssClass: [MapOverlayUtil.config.componentOverlayClass, 'eol'].join(' '),
|
||||
location: 0.25
|
||||
|
||||
@@ -5,6 +5,14 @@ define([
|
||||
], ($) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
autoScrollClass: 'auto-scroll',
|
||||
autoScrollTopClass: 'auto-scroll-top',
|
||||
autoScrollLeftClass: 'auto-scroll-left',
|
||||
autoScrollBottomClass: 'auto-scroll-bottom',
|
||||
autoScrollRightClass: 'auto-scroll-right',
|
||||
};
|
||||
|
||||
let defaultConfig = {
|
||||
axis: 'yx',
|
||||
theme: 'light-3' ,
|
||||
@@ -18,7 +26,12 @@ define([
|
||||
callbacks: {
|
||||
onTotalScrollOffset: 0,
|
||||
onTotalScrollBackOffset: 0,
|
||||
alwaysTriggerOffsets: true
|
||||
alwaysTriggerOffsets: true,
|
||||
onScroll: function(){
|
||||
if($(this).data('mCS').trigger === 'internal'){
|
||||
autoScrollOff(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
advanced: {
|
||||
@@ -44,15 +57,99 @@ define([
|
||||
autoHideScrollbar: false
|
||||
};
|
||||
|
||||
let defaultScrollToOptions = {
|
||||
scrollInertia: 2000,
|
||||
scrollEasing: 'easeInOutSmooth',
|
||||
timeout: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* init map scrollbar
|
||||
* @param scrollWrapper
|
||||
* @param config
|
||||
* @param customConfig
|
||||
*/
|
||||
let initScrollbar = (scrollWrapper, config) => {
|
||||
config = $.extend(true, {}, defaultConfig, config);
|
||||
let initScrollbar = (scrollWrapper, customConfig) => {
|
||||
customConfig = $.extend(true, {}, defaultConfig, customConfig);
|
||||
// wrap callbacks -> callbacks from defaultConfig should run first
|
||||
customConfig.callbacks = wrapObjectFunctions(customConfig.callbacks, defaultConfig.callbacks);
|
||||
scrollWrapper.mCustomScrollbar(customConfig);
|
||||
};
|
||||
|
||||
scrollWrapper.mCustomScrollbar(config);
|
||||
/**
|
||||
* @param scrollWrapper
|
||||
* @param position
|
||||
* @param options
|
||||
*/
|
||||
let autoScroll = (scrollWrapper, position, options) => {
|
||||
if(position.some(position => position !== null)){
|
||||
// scroll position -> start auto scroll
|
||||
autoScrollOn(scrollWrapper, position, options);
|
||||
}else{
|
||||
// no scroll position -> stop auto scroll
|
||||
autoScrollOff(scrollWrapper);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param scrollWrapper
|
||||
* @param position
|
||||
* @param options
|
||||
*/
|
||||
let autoScrollOn = (scrollWrapper, position, options) => {
|
||||
let scrollToOptions = Object.assign({}, defaultScrollToOptions, options);
|
||||
let scrollInertia = 0;
|
||||
let autoScrollClasses = [];
|
||||
['top', 'left', 'bottom', 'right'].forEach((direction, i) => {
|
||||
if(position.includes(direction)){
|
||||
autoScrollClasses.push(config['autoScroll' + direction.capitalize() + 'Class']);
|
||||
if(i % 2){ // left || right
|
||||
scrollInertia = scrollToOptions.scrollInertia * scrollWrapper.mcs.leftPct / 100;
|
||||
}else{ // top || bottom
|
||||
scrollInertia = scrollToOptions.scrollInertia * scrollWrapper.mcs.topPct / 100;
|
||||
}
|
||||
if(i === 2 || i === 3){ // bottom || right
|
||||
scrollInertia = scrollToOptions.scrollInertia - scrollInertia;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(autoScrollClasses.length){
|
||||
// scroll position -> check if scroll direction changed
|
||||
let compareClasses = getAutoScrollClasses();
|
||||
let currentClasses = [...scrollWrapper.classList].filter(cls => compareClasses.includes(cls));
|
||||
let newClasses = autoScrollClasses.diff(currentClasses);
|
||||
let oldClasses = currentClasses.diff(autoScrollClasses);
|
||||
|
||||
if(newClasses.length || oldClasses.length){
|
||||
// changed scroll direction (e.g. null -> y; y -> x; y -> xy, xy -> null)
|
||||
// -> stop current autos scroll and start with new scroll direction
|
||||
autoScrollOff(scrollWrapper, oldClasses);
|
||||
|
||||
scrollWrapper.classList.add(...newClasses);
|
||||
scrollToOptions.scrollInertia = scrollInertia;
|
||||
$(scrollWrapper).mCustomScrollbar('scrollTo', position, scrollToOptions);
|
||||
}
|
||||
}else{
|
||||
// no scroll position -> stop auto scroll
|
||||
autoScrollOff(scrollWrapper);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param scrollWrapper
|
||||
* @param classes
|
||||
*/
|
||||
let autoScrollOff = (scrollWrapper, classes) => {
|
||||
classes = classes || getAutoScrollClasses();
|
||||
scrollWrapper.classList.remove(...classes);
|
||||
$(scrollWrapper).mCustomScrollbar('stop');
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {[string, string, string, string]}
|
||||
*/
|
||||
let getAutoScrollClasses = () => {
|
||||
return [config.autoScrollTopClass, config.autoScrollLeftClass, config.autoScrollBottomClass, config.autoScrollRightClass];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -174,6 +271,28 @@ define([
|
||||
*/
|
||||
let adjustPos = (position, dimension) => mapObject(roundPos(position), (v, k) => Math.max(1, Math.min(dimension[k], v)) );
|
||||
|
||||
/**
|
||||
* wrap functions that exists in both objects (same key)
|
||||
* -> o2 fkt run 1st (returned value ignored)
|
||||
* -> o1 fkt run 2nd
|
||||
* @param o1
|
||||
* @param o2
|
||||
* @returns {any}
|
||||
*/
|
||||
let wrapObjectFunctions = (o1 = {}, o2 = {}) => {
|
||||
return mapObject(o1, function(v1, k1){
|
||||
// check both obj has the same key and are functions
|
||||
if([v1, o2[k1]].every(v => typeof v === 'function')){
|
||||
return function(...args){
|
||||
// run 'default' fkt first, then orig fkt
|
||||
o2[k1].apply(this, ...args);
|
||||
return v1.apply(this, ...args);
|
||||
};
|
||||
}
|
||||
return v1;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* like Array.map() for objects
|
||||
* -> callback f is called for each property
|
||||
@@ -187,6 +306,7 @@ define([
|
||||
return {
|
||||
initScrollbar: initScrollbar,
|
||||
scrollToPosition: scrollToPosition,
|
||||
scrollToCenter: scrollToCenter
|
||||
scrollToCenter: scrollToCenter,
|
||||
autoScroll: autoScroll
|
||||
};
|
||||
});
|
||||
@@ -33,6 +33,7 @@ define([
|
||||
systemClass: 'pf-system', // class for all systems
|
||||
systemActiveClass: 'pf-system-active', // class for an active system on a map
|
||||
systemSelectedClass: 'pf-system-selected', // class for selected systems on on map
|
||||
systemLockedClass: 'pf-system-locked', // class for locked systems on a map
|
||||
systemHiddenClass: 'pf-system-hidden', // class for hidden (filtered) systems
|
||||
|
||||
// dataTable
|
||||
@@ -530,7 +531,7 @@ define([
|
||||
// ... get endpoint label for source || target system
|
||||
if(tmpSystem && tmpSystem){
|
||||
// ... get all available signature type (wormholes) names
|
||||
let availableSigTypeNames = SystemSignatures.getAllSignatureNamesBySystem(tmpSystem, 5);
|
||||
let availableSigTypeNames = SystemSignatures.getSignatureTypeOptionsBySystem(tmpSystem, 5);
|
||||
let flattenSigTypeNames = Util.flattenXEditableSelectArray(availableSigTypeNames);
|
||||
|
||||
if(flattenSigTypeNames.hasOwnProperty(signatureData.typeId)){
|
||||
|
||||
@@ -8,8 +8,7 @@ define([
|
||||
'pnotify.desktop',
|
||||
//'pnotify.history',
|
||||
'pnotify.callbacks'
|
||||
], function($, Init, PNotify){
|
||||
|
||||
], ($, Init, PNotify) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
@@ -76,7 +75,7 @@ define([
|
||||
* @param customConfig
|
||||
* @param settings
|
||||
*/
|
||||
let showNotify = function(customConfig, settings){
|
||||
let showNotify = (customConfig, settings) => {
|
||||
customConfig = $.extend(true, {}, config, customConfig );
|
||||
|
||||
// desktop notification
|
||||
@@ -149,7 +148,7 @@ define([
|
||||
* change document.title and make the browsers tab blink
|
||||
* @param blinkTitle
|
||||
*/
|
||||
let startTabBlink = function(blinkTitle){
|
||||
let startTabBlink = blinkTitle => {
|
||||
let initBlink = (function(blinkTitle){
|
||||
|
||||
// count blinks if tab is currently active
|
||||
@@ -174,7 +173,7 @@ define([
|
||||
blinkTimer = setInterval(blink, 1000);
|
||||
}
|
||||
};
|
||||
}( blinkTitle ));
|
||||
}(blinkTitle));
|
||||
|
||||
initBlink();
|
||||
};
|
||||
@@ -182,7 +181,7 @@ define([
|
||||
/**
|
||||
* stop blinking document.title
|
||||
*/
|
||||
let stopTabBlink = function(){
|
||||
let stopTabBlink = () => {
|
||||
if(blinkTimer){
|
||||
clearInterval(blinkTimer);
|
||||
document.title = initialPageTitle;
|
||||
|
||||
@@ -6,6 +6,7 @@ define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/counter',
|
||||
'app/logging',
|
||||
'mustache',
|
||||
'app/map/util',
|
||||
@@ -26,7 +27,7 @@ define([
|
||||
'dialog/credit',
|
||||
'xEditable',
|
||||
'app/module_map'
|
||||
], ($, Init, Util, Logging, Mustache, MapUtil, MapContextMenu, SlideBars, TplHead, TplFooter) => {
|
||||
], ($, Init, Util, Counter, Logging, Mustache, MapUtil, MapContextMenu, SlideBars, TplHead, TplFooter) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -873,13 +874,12 @@ define([
|
||||
let popoverElement = $(e.target).data('bs.popover').tip();
|
||||
|
||||
// destroy all active tooltips inside this popover
|
||||
popoverElement.destroyTooltip(true);
|
||||
popoverElement.destroyTooltips(true);
|
||||
});
|
||||
|
||||
// global "modal" callback --------------------------------------------------------------------------------
|
||||
bodyElement.on('hide.bs.modal', '> .modal', e => {
|
||||
let modalElement = $(e.target);
|
||||
modalElement.destroyTimestampCounter(true);
|
||||
|
||||
// destroy all form validators
|
||||
// -> does not work properly. validation functions still used (js error) after 'destroy'
|
||||
@@ -892,6 +892,14 @@ define([
|
||||
modalElement.find('.' + Util.config.select2Class)
|
||||
.filter((i, element) => $(element).data('select2'))
|
||||
.select2('destroy');
|
||||
|
||||
// destroy DataTable instances
|
||||
for(let table of modalElement.find('table.dataTable')){
|
||||
$(table).DataTable().destroy(true);
|
||||
}
|
||||
|
||||
// destroy counter
|
||||
Counter.destroyTimestampCounter(modalElement, true);
|
||||
});
|
||||
|
||||
// global "close" trigger for context menus ---------------------------------------------------------------
|
||||
@@ -1060,7 +1068,7 @@ define([
|
||||
if(changedCharacter){
|
||||
// current character changed
|
||||
userInfoElement.find('span').text(Util.getObjVal(userData, 'character.name'));
|
||||
userInfoElement.find('img').attr('src', Util.eveImageUrl('character', Util.getObjVal(userData, 'character.id')));
|
||||
userInfoElement.find('img').attr('src', Util.eveImageUrl('characters', Util.getObjVal(userData, 'character.id')));
|
||||
}
|
||||
// init "character switch" popover
|
||||
userInfoElement.initCharacterSwitchPopover(userData);
|
||||
@@ -1145,7 +1153,7 @@ define([
|
||||
if(isCurrentLocation && shipTypeId){
|
||||
// show ship image
|
||||
breadcrumbHtml += '<img class="pf-head-image --right" ';
|
||||
breadcrumbHtml += 'src="' + Util.eveImageUrl('render', shipTypeId) + '" ';
|
||||
breadcrumbHtml += 'src="' + Util.eveImageUrl('types', shipTypeId) + '" ';
|
||||
breadcrumbHtml += 'title="' + shipTypeName + '" ';
|
||||
breadcrumbHtml += '>';
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ define([], () => {
|
||||
constructor(callback, timeout = 6000){
|
||||
let timer;
|
||||
let promise = callback[Symbol.toStringTag] === 'Promise' ? callback : new Promise(callback);
|
||||
//let promise = new Promise(callback);
|
||||
|
||||
let wrapperPromise = Promise.race([
|
||||
promise,
|
||||
@@ -63,7 +62,6 @@ define([], () => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
250
js/app/setup.js
250
js/app/setup.js
@@ -6,9 +6,10 @@ define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/render',
|
||||
'app/map/worker',
|
||||
'mustache',
|
||||
], function($, Init, Util, MapWorker, Mustache){
|
||||
'peityInlineChart',
|
||||
], ($, Init, Util, Render, MapWorker) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
@@ -17,8 +18,20 @@ define([
|
||||
// navigation
|
||||
navigationElementId: 'pf-navbar', // id for navbar element
|
||||
|
||||
// sticky panel
|
||||
stickyPanelClass: 'pf-landing-sticky-panel', // class for sticky panels
|
||||
|
||||
hiddenByAttributeClass: 'pf-hidden-by-attr', // class for elements that are hidden/shown by [data-attr] value
|
||||
shownByAttributeClass: 'pf-shown-by-attr', // class for elements that are hidden/shown by [data-attr] value
|
||||
|
||||
webSocketStatsId: 'pf-setup-webSocket-stats', // id for webSocket "stats" panel
|
||||
webSocketRefreshStatsId: 'pf-setup-webSocket-stats-refresh' // class for "reload stats" button
|
||||
webSocketRefreshStatsId: 'pf-setup-webSocket-stats-refresh', // class for "reload stats" button
|
||||
|
||||
cronRowActiveClass: 'pf-cron-row-active', // class for "active" (e.g. "inProgress") table row
|
||||
|
||||
jsonPopoverClass: 'pf-json-popover', // class for "json" popover elements
|
||||
barChartClass: 'pf-bar-chart', // class for "bar" chart elements
|
||||
lineChartClass: 'pf-line-chart' // class for "line" chart elements
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -75,35 +88,96 @@ define([
|
||||
// collapse ---------------------------------------------------------------------------------------------------
|
||||
setCollapseObserver(body, '[data-toggle="collapse"]');
|
||||
|
||||
// panel actions ----------------------------------------------------------------------------------------------
|
||||
let collapsedState = false;
|
||||
$('.' + config.stickyPanelClass + ' a[data-panel-action="collapse-all"]').on('click', function(e){
|
||||
e.preventDefault();
|
||||
$('.row.collapse').collapse(collapsedState ? 'show' : 'hide');
|
||||
collapsedState = !collapsedState;
|
||||
});
|
||||
|
||||
// buttons ----------------------------------------------------------------------------------------------------
|
||||
// exclude "download" && "navigation" buttons
|
||||
body.find('.btn')
|
||||
.not('.navbar-fixed-bottom .btn')
|
||||
.not('[data-action="clearIndex"]')
|
||||
.not('[data-action="buildIndex"]')
|
||||
.not('[href^="?export"]').on('click', function(e){
|
||||
$('.' + config.splashOverlayClass).showSplashOverlay();
|
||||
});
|
||||
.not('[data-action]')
|
||||
.not('[href^="?export"]')
|
||||
.on('click', function(){
|
||||
$('.' + config.splashOverlayClass).showSplashOverlay();
|
||||
});
|
||||
|
||||
// build/clear index buttons ----------------------------------------------------------------------------------
|
||||
// clear index buttons ----------------------------------------------------------------------------------------
|
||||
body.find('.btn[data-action="buildIndex"], .btn[data-action="clearIndex"]').on('click', function(e){
|
||||
body.on('click', '.btn[data-action]', function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let element = $(this);
|
||||
let url = '/api/setup/' + element.attr('data-action');
|
||||
sendRequest(url, {
|
||||
type: element.attr('data-type'),
|
||||
countAll: element.attr('data-countall'),
|
||||
count: 0,
|
||||
offset: 0
|
||||
}, {
|
||||
let payload = element.attr('data-payload');
|
||||
let callStartName = element.attr('data-callstart');
|
||||
let callBackName = element.attr('data-callback');
|
||||
let callBack = config[callBackName] instanceof Function ? config[callBackName] : () => {
|
||||
console.warn('Invalid callback function name: %s', callBackName);
|
||||
};
|
||||
|
||||
let requestData = {};
|
||||
try{
|
||||
requestData = Object.assign({}, requestData, JSON.parse(payload));
|
||||
}catch(error){
|
||||
console.error('Failed to parse button payload: %s ', payload);
|
||||
}
|
||||
|
||||
let context = {
|
||||
target: element,
|
||||
url: url
|
||||
}, updateIndexCount);
|
||||
};
|
||||
|
||||
if(config[callStartName] instanceof Function){
|
||||
config[callStartName](context);
|
||||
}
|
||||
|
||||
sendRequest(url, requestData, context, callBack);
|
||||
});
|
||||
|
||||
// bar charts (e.g. cronjob table) ----------------------------------------------------------------------------
|
||||
initBarCharts($('.' + config.barChartClass));
|
||||
|
||||
// Json popovers ----------------------------------------------------------------------------------------------
|
||||
body.on('mouseenter', '.' + config.jsonPopoverClass, function(e){
|
||||
let element = $(this);
|
||||
if(!element.data('bs.popover')){
|
||||
let json = element.data('json');
|
||||
let jsonHighlighted = Render.highlightJson(json);
|
||||
let content = '<pre><code>' + jsonHighlighted + '</code></pre>';
|
||||
|
||||
element.popover({
|
||||
placement: 'left',
|
||||
html: true,
|
||||
trigger: 'hover',
|
||||
content: content,
|
||||
container: 'body',
|
||||
title: 'Last exec. state',
|
||||
delay: {
|
||||
show: 180,
|
||||
hide: 0
|
||||
}
|
||||
});
|
||||
|
||||
element.popover('show');
|
||||
}
|
||||
});
|
||||
|
||||
// tooltips ---------------------------------------------------------------------------------------------------
|
||||
body.find('[title]').tooltip();
|
||||
body.initTooltips({
|
||||
container: 'body'
|
||||
});
|
||||
|
||||
body.on('show.bs.tooltip', e => {
|
||||
let element = $(e.target);
|
||||
let level = element.attr('data-level');
|
||||
if(level && level.length){
|
||||
element.data('bs.tooltip').$tip.find('.tooltip-inner').addClass('txt-color txt-color-' + level);
|
||||
}
|
||||
});
|
||||
|
||||
// change url (remove logout parameter)
|
||||
if(history.pushState){
|
||||
@@ -111,12 +185,150 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* init "Peity" charts
|
||||
* @param elements
|
||||
* @param options
|
||||
*/
|
||||
let initBarCharts = (elements, options = {}) => {
|
||||
let barDangerColor = '#a52521';
|
||||
let barWarningColor = '#e28a0d';
|
||||
let barDefaultColor = '#568a89';
|
||||
|
||||
// defaultOptions for bar AND line charts
|
||||
let defaultOptions = {
|
||||
fill: function(val, i, all){
|
||||
if(val <= 0){
|
||||
return barDangerColor;
|
||||
}else{
|
||||
// fade out older bars
|
||||
let opacity = i / all.length;
|
||||
// limit opacity to min 0.3
|
||||
opacity = Math.max(opacity, 0.3);
|
||||
let alphaHex = Math.round(opacity * 255).toString(16);
|
||||
|
||||
// set color based on average difference
|
||||
let avg = this.$el.data('avg') || 0;
|
||||
let avgBuffer = this.$el.data('avg-buffer') || 0;
|
||||
let barColor = (val > avg + avgBuffer ) ? barWarningColor : barDefaultColor;
|
||||
|
||||
return barColor + (alphaHex.length === 1 ? '0' : '') + alphaHex;
|
||||
}
|
||||
},
|
||||
height: 18,
|
||||
min: -1,
|
||||
//max: 2
|
||||
//width: '100%'
|
||||
width: '65px'
|
||||
};
|
||||
|
||||
elements.peity('bar', Object.assign({}, defaultOptions, {
|
||||
padding: 0.1 // bar chart specific
|
||||
}, options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param container
|
||||
*/
|
||||
let updateStatusBar = container => {
|
||||
let statusConfig = ['information', 'hint', 'warning', 'danger', 'success'].map(
|
||||
type => ({
|
||||
type: type,
|
||||
target: container.find('.' + config.hiddenByAttributeClass + ', .' + config.shownByAttributeClass)
|
||||
.filter('[data-attr-type="' + type + '"]'),
|
||||
count: container.find('[data-type="' + type + '"]').length
|
||||
})
|
||||
);
|
||||
|
||||
// show/hide target elements by CSS by attribute update
|
||||
let typesCheckForSuccess = ['warning', 'danger'];
|
||||
let checkCount = 0;
|
||||
for(let config of statusConfig){
|
||||
// "success" type depends on empty count for checked types
|
||||
if(typesCheckForSuccess.includes(config.type)){
|
||||
checkCount += config.count;
|
||||
}
|
||||
|
||||
let count = config.type === 'success' ? checkCount : config.count;
|
||||
|
||||
config.target.attr('data-attr', count || '');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* update cronJob table row
|
||||
* @type {updateCronjob}
|
||||
*/
|
||||
let updateCronjob = config.updateCronjob = (context, responseData) => {
|
||||
context.target.button('reset');
|
||||
|
||||
let jobsData = Util.getObjVal(responseData, 'jobsData') || {};
|
||||
let html = Util.getObjVal(responseData, 'html');
|
||||
let jobsCount = Object.keys(jobsData).length;
|
||||
|
||||
if(jobsCount && html){
|
||||
// replace full table tbody or just a single row
|
||||
let elPanel = context.target.closest('.panel');
|
||||
let elOld;
|
||||
let elNew;
|
||||
if(jobsCount === 1){
|
||||
// replace single: <tr>
|
||||
elOld = context.target.closest('tr');
|
||||
elNew = $(html);
|
||||
}else{
|
||||
// replace all: <tbody>
|
||||
elOld = context.target.closest('table').find('tbody');
|
||||
elNew = $('<tbody>' + html + '</tbody>');
|
||||
}
|
||||
|
||||
if(elOld && elNew){
|
||||
elOld.destroyTooltips(true);
|
||||
elOld.replaceWith(elNew);
|
||||
elNew.initTooltips({
|
||||
container: 'body'
|
||||
});
|
||||
initBarCharts(elNew.find('.' + config.barChartClass));
|
||||
updateStatusBar(elPanel);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* mark cronJob table row + status icons as "inProgress"
|
||||
* @type {startCronjob}
|
||||
*/
|
||||
let startCronjob = config.startCronjob = context => {
|
||||
let row = context.target.closest('tr');
|
||||
let statusCell = row.children().first().children().first();
|
||||
|
||||
// change row to "inProgress"
|
||||
row.addClass(config.cronRowActiveClass);
|
||||
|
||||
// change status icons to "inProgress"
|
||||
let mapAttr = path => '[data-name="' + path + '"]';
|
||||
let removeStatusNames = ['notExecuted', 'notFinished', 'onHold'];
|
||||
statusCell.find(removeStatusNames.map(mapAttr).join(', ')).remove();
|
||||
|
||||
let addStatusName = 'inProgress';
|
||||
let addStatusType = 'success';
|
||||
if(!statusCell.find([addStatusName].map(mapAttr).join(', ')).length){
|
||||
statusCell.append(
|
||||
$('<i>', {
|
||||
class: 'fas fa-fw fa-play pf-help txt-color txt-color-success'
|
||||
})
|
||||
.attr('title', 'Started. In execution…')
|
||||
.attr('data-name', addStatusName)
|
||||
.attr('data-type', addStatusType)
|
||||
).initTooltips({container: 'body'});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* update data count label for "indexed data"
|
||||
* @param context
|
||||
* @param responseData
|
||||
*/
|
||||
let updateIndexCount = (context, responseData) => {
|
||||
let updateIndexCount = config.updateIndexCount = (context, responseData) => {
|
||||
let countElement = context.target.closest('tr').children().eq(1).find('kbd');
|
||||
countElement.text(responseData.countBuildAll + '/' + responseData.countAll);
|
||||
countElement.removeClass('txt-color-success txt-color-danger txt-color-warning');
|
||||
|
||||
@@ -7,10 +7,10 @@ define([
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/render',
|
||||
'bootbox',
|
||||
'app/counter',
|
||||
'bootbox',
|
||||
'app/map/util'
|
||||
], ($, Init, Util, Render, bootbox, Counter, MapUtil) => {
|
||||
], ($, Init, Util, Render, Counter, bootbox, MapUtil) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -182,7 +182,7 @@ define([
|
||||
mapElement.append(dlElementRight);
|
||||
|
||||
// init map lifetime counter
|
||||
$('.' + config.mapInfoLifetimeCounterClass).initTimestampCounter();
|
||||
Counter.initTimestampCounter($('.' + config.mapInfoLifetimeCounterClass));
|
||||
|
||||
mapElement.find('.' + config.textActionIconCopyClass).on('click', function(){
|
||||
let mapUrl = $(this).find('span').text().trim();
|
||||
@@ -456,7 +456,7 @@ define([
|
||||
width: 10,
|
||||
className: ['text-center', config.tableCellActionClass].join(' '),
|
||||
data: null,
|
||||
defaultContent: '<i class="fas fa-times txt-color txt-color-redDarker"></i>',
|
||||
defaultContent: '<i class="fas fa-times txt-color txt-color-redDark"></i>',
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
let tempTableElement = this;
|
||||
|
||||
@@ -657,7 +657,7 @@ define([
|
||||
width: 10,
|
||||
className: ['text-center', config.tableCellActionClass].join(' '),
|
||||
data: null,
|
||||
defaultContent: '<i class="fas fa-times txt-color txt-color-redDarker"></i>',
|
||||
defaultContent: '<i class="fas fa-times txt-color txt-color-redDark"></i>',
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
let tempTableElement = this;
|
||||
|
||||
@@ -764,7 +764,7 @@ define([
|
||||
_: function(data, type, row, meta){
|
||||
let value = data;
|
||||
if(data && type === 'display'){
|
||||
value = '<img src="' + Util.eveImageUrl('render', value.typeId) + '" title="' + value.typeName + '" data-toggle="tooltip" />';
|
||||
value = '<img src="' + Util.eveImageUrl('types', value.typeId) + '" title="' + value.typeName + '" data-toggle="tooltip" />';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -802,7 +802,7 @@ define([
|
||||
_: function(data, type, row, meta){
|
||||
let value = data;
|
||||
if(type === 'display'){
|
||||
value = '<img src="' + Util.eveImageUrl('character', value) + '"/>';
|
||||
value = '<img src="' + Util.eveImageUrl('characters', value) + '"/>';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -842,7 +842,7 @@ define([
|
||||
_: function(data, type, row, meta){
|
||||
let value = data;
|
||||
if(type === 'display'){
|
||||
value = '<img src="' + Util.eveImageUrl('corporation', value.id) + '"/>';
|
||||
value = '<img src="' + Util.eveImageUrl('corporations', value.id) + '"/>';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -1125,7 +1125,7 @@ define([
|
||||
_: function(data, type, row, meta){
|
||||
let value = data;
|
||||
if(type === 'display'){
|
||||
value = '<img src="' + Util.eveImageUrl('character', value) + '"/>';
|
||||
value = '<img src="' + Util.eveImageUrl('characters', value) + '"/>';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -1167,7 +1167,6 @@ define([
|
||||
_: function(data, type, row, meta){
|
||||
let value = data;
|
||||
if(type === 'display'){
|
||||
// txt-color-redDarker
|
||||
value = '<i class="fas fa-code ' + config.tableCellActionIconClass + '"></i>';
|
||||
}
|
||||
return value;
|
||||
@@ -1250,7 +1249,7 @@ define([
|
||||
class: config.tableToolsClass
|
||||
}));
|
||||
|
||||
let buttons = new $.fn.dataTable.Buttons( logDataTable, {
|
||||
let buttons = new $.fn.dataTable.Buttons(logDataTable, {
|
||||
buttons: [
|
||||
{
|
||||
className: 'btn btn-sm btn-default',
|
||||
|
||||
@@ -8,9 +8,10 @@ define([
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/render',
|
||||
'app/counter',
|
||||
'bootbox',
|
||||
'peityInlineChart'
|
||||
], ($, Init, Util, Render, bootbox) => {
|
||||
], ($, Init, Util, Render, Counter, bootbox) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
@@ -37,7 +38,7 @@ define([
|
||||
* init blank statistics dataTable
|
||||
* @param dialogElement
|
||||
*/
|
||||
let initStatsTable = function(dialogElement){
|
||||
let initStatsTable = dialogElement => {
|
||||
let columnNumberWidth = 28;
|
||||
let cellPadding = 4;
|
||||
let lineChartWidth = columnNumberWidth + (2 * cellPadding);
|
||||
@@ -114,6 +115,7 @@ define([
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 0,
|
||||
name: 'rowIndex',
|
||||
title: '<i class="fas fa-hashtag"></i>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -122,6 +124,7 @@ define([
|
||||
data: 'character.id'
|
||||
},{
|
||||
targets: 1,
|
||||
name: 'image',
|
||||
title: '',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -130,11 +133,12 @@ define([
|
||||
data: 'character',
|
||||
render: {
|
||||
_: function(data, type, row, meta){
|
||||
return '<img src="' + Util.eveImageUrl('character', parseInt(data.id)) + '"/>';
|
||||
return '<img src="' + Util.eveImageUrl('characters', parseInt(data.id)) + '"/>';
|
||||
}
|
||||
}
|
||||
},{
|
||||
targets: 2,
|
||||
name: 'name',
|
||||
title: 'name',
|
||||
width: 200,
|
||||
data: 'character',
|
||||
@@ -144,20 +148,16 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 3,
|
||||
name: 'lastLogin',
|
||||
title: 'last login',
|
||||
searchable: false,
|
||||
width: 70,
|
||||
className: ['text-right', 'separator-right'].join(' '),
|
||||
data: 'character',
|
||||
render: {
|
||||
_: 'lastLogin',
|
||||
sort: 'lastLogin'
|
||||
},
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
$(cell).initTimestampCounter();
|
||||
}
|
||||
data: 'character.lastLogin',
|
||||
defaultContent: ''
|
||||
},{
|
||||
targets: 4,
|
||||
name: 'mapCreate',
|
||||
title: '<span title="created" data-toggle="tooltip">C </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -169,6 +169,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 5,
|
||||
name: 'mapUpdate',
|
||||
title: '<span title="updated" data-toggle="tooltip">U </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -180,6 +181,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 6,
|
||||
name: 'mapDelete',
|
||||
title: '<span title="deleted" data-toggle="tooltip">D </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -191,6 +193,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 7,
|
||||
name: 'mapSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
@@ -201,6 +204,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 8,
|
||||
name: 'systemCreate',
|
||||
title: '<span title="created" data-toggle="tooltip">C </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -212,6 +216,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 9,
|
||||
name: 'systemUpdate',
|
||||
title: '<span title="updated" data-toggle="tooltip">U </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -223,6 +228,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 10,
|
||||
name: 'systemDelete',
|
||||
title: '<span title="deleted" data-toggle="tooltip">D </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -234,6 +240,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 11,
|
||||
name: 'systemSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
@@ -244,6 +251,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 12,
|
||||
name: 'connectionCreate',
|
||||
title: '<span title="created" data-toggle="tooltip">C </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -255,6 +263,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 13,
|
||||
name: 'connectionUpdate',
|
||||
title: '<span title="updated" data-toggle="tooltip">U </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -266,6 +275,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 14,
|
||||
name: 'connectionDelete',
|
||||
title: '<span title="deleted" data-toggle="tooltip">D </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -277,6 +287,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 15,
|
||||
name: 'connectionSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
@@ -287,6 +298,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 16,
|
||||
name: 'signatureCreate',
|
||||
title: '<span title="created" data-toggle="tooltip">C </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -298,6 +310,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 17,
|
||||
name: 'signatureUpdate',
|
||||
title: '<span title="updated" data-toggle="tooltip">U </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -309,6 +322,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 18,
|
||||
name: 'signatureDelete',
|
||||
title: '<span title="deleted" data-toggle="tooltip">D </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
@@ -320,6 +334,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 19,
|
||||
name: 'signatureSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
@@ -330,6 +345,7 @@ define([
|
||||
}
|
||||
},{
|
||||
targets: 20,
|
||||
name: 'totalSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
@@ -346,6 +362,8 @@ define([
|
||||
// initial statistics data request
|
||||
let requestData = getRequestDataFromTabPanels(dialogElement);
|
||||
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
|
||||
|
||||
Counter.initTableCounter(this, ['lastLogin:name']);
|
||||
},
|
||||
drawCallback: function(settings){
|
||||
this.api().rows().nodes().to$().each(function(i, row){
|
||||
@@ -374,7 +392,7 @@ define([
|
||||
});
|
||||
|
||||
$(sumColumnIndexes).each(function(index, value){
|
||||
$( api.column( value ).footer() ).text( renderNumericColumn(pageTotalColumns[index], 'display') );
|
||||
$(api.column(value).footer()).text( renderNumericColumn(pageTotalColumns[index], 'display') );
|
||||
});
|
||||
},
|
||||
data: [] // will be added dynamic
|
||||
@@ -404,8 +422,7 @@ define([
|
||||
* @param requestData
|
||||
* @param context
|
||||
*/
|
||||
let getStatsData = function(requestData, context){
|
||||
|
||||
let getStatsData = (requestData, context) => {
|
||||
context.dynamicArea = $('#' + config.statsContainerId + ' .' + Util.config.dynamicAreaClass);
|
||||
context.dynamicArea.showLoadingAnimation();
|
||||
|
||||
@@ -418,7 +435,7 @@ define([
|
||||
}).done(function(data){
|
||||
this.dynamicArea.hideLoadingAnimation();
|
||||
|
||||
this.callback(data);
|
||||
this.callback(data, this);
|
||||
}).fail(function(jqXHR, status, error){
|
||||
let reason = status + ' ' + error;
|
||||
Util.showNotify({title: jqXHR.status + ': loadStatistics', text: reason, type: 'warning'});
|
||||
@@ -430,7 +447,7 @@ define([
|
||||
* update "header"/"filter" elements in dialog
|
||||
* @param responseData
|
||||
*/
|
||||
let drawStatsTable = function(responseData){
|
||||
let drawStatsTable = (responseData, context) => {
|
||||
let dialogElement = $('#' + config.statsDialogId);
|
||||
|
||||
// update filter/header -----------------------------------------------------------------------------
|
||||
@@ -467,8 +484,8 @@ define([
|
||||
|
||||
// clear and (re)-fill table ------------------------------------------------------------------------
|
||||
let formattedData = formatStatisticsData(responseData);
|
||||
this.tableApi.clear();
|
||||
this.tableApi.rows.add(formattedData).draw();
|
||||
context.tableApi.clear();
|
||||
context.tableApi.rows.add(formattedData).draw();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -477,7 +494,7 @@ define([
|
||||
* @param statsData
|
||||
* @returns {Array}
|
||||
*/
|
||||
let formatStatisticsData = function(statsData){
|
||||
let formatStatisticsData = statsData => {
|
||||
let formattedData = [];
|
||||
let yearStart = statsData.start.year;
|
||||
let weekStart = statsData.start.week;
|
||||
@@ -687,7 +704,7 @@ define([
|
||||
* @param dialogElement
|
||||
* @returns {{}}
|
||||
*/
|
||||
let getRequestDataFromTabPanels = function(dialogElement){
|
||||
let getRequestDataFromTabPanels = dialogElement => {
|
||||
let requestData = {};
|
||||
|
||||
// get data from "tab" panel links ------------------------------------------------------------------
|
||||
|
||||
@@ -38,19 +38,19 @@ define([
|
||||
|
||||
switch(data.categoryType){
|
||||
case 'character':
|
||||
imagePath = Util.eveImageUrl('character', data.id);
|
||||
imagePath = Util.eveImageUrl('characters', data.id);
|
||||
break;
|
||||
case 'corporation':
|
||||
imagePath = Util.eveImageUrl('corporation', data.id);
|
||||
imagePath = Util.eveImageUrl('corporations', data.id);
|
||||
break;
|
||||
case 'alliance':
|
||||
imagePath = Util.eveImageUrl('alliance', data.id);
|
||||
imagePath = Util.eveImageUrl('alliances', data.id);
|
||||
break;
|
||||
case 'inventoryType':
|
||||
imagePath = Util.eveImageUrl('type', data.id);
|
||||
imagePath = Util.eveImageUrl('types', data.id);
|
||||
break;
|
||||
case 'render':
|
||||
imagePath = Util.eveImageUrl('render', data.id);
|
||||
imagePath = Util.eveImageUrl('types', data.id);
|
||||
break;
|
||||
case 'station':
|
||||
iconName = 'fa-home';
|
||||
@@ -77,14 +77,23 @@ define([
|
||||
/**
|
||||
* format results data for signature type select
|
||||
* @param state
|
||||
* @returns {*|jQuery|HTMLElement}
|
||||
* @param container
|
||||
* @param customOptions
|
||||
* @returns {*|k.fn.init|jQuery|HTMLElement}
|
||||
*/
|
||||
let formatSignatureTypeSelectionData = state => {
|
||||
let formatSignatureTypeSelectionData = (state, container, customOptions) => {
|
||||
let parts = state.text.split(' - ');
|
||||
|
||||
let markup = '';
|
||||
if(parts.length === 2){
|
||||
// wormhole data -> 2 columns
|
||||
let name = parts[0];
|
||||
let sizeLabel;
|
||||
if(Util.getObjVal(customOptions, 'showWhSizeLabel')){
|
||||
let wormholeSizeData = Util.getObjVal(Init, 'wormholes.' + name + '.size');
|
||||
sizeLabel = Util.getObjVal(wormholeSizeData, 'label') || '';
|
||||
}
|
||||
|
||||
let securityClass = Util.getSecurityClassForSystem(getSystemSecurityFromLabel(parts[1]));
|
||||
// some labels have a "suffix" label that should not have the securityClass
|
||||
let labelParts = parts[1].split(/\s(.+)/);
|
||||
@@ -93,9 +102,14 @@ define([
|
||||
|
||||
let classes = [securityClass, Util.config.popoverTriggerClass, Util.config.helpDefaultClass];
|
||||
|
||||
markup += '<span>' + parts[0] + '</span> ';
|
||||
markup += '<span>' + name + '</span>';
|
||||
if(sizeLabel !== undefined){
|
||||
markup += '<span><kbd>' + sizeLabel + '</kbd></span>';
|
||||
}else{
|
||||
markup += ' ';
|
||||
}
|
||||
markup += '<i class="fas fa-long-arrow-alt-right txt-color txt-color-grayLight"></i>';
|
||||
markup += '<span class="' + classes.join(' ') + '" data-name="' + parts[0] + '"> ' + label + '</span>';
|
||||
markup += '<span class="' + classes.join(' ') + '" data-name="' + name + '"> ' + label + '</span>';
|
||||
if(suffix.length){
|
||||
markup += ' <span>' + suffix + '</span>';
|
||||
}
|
||||
@@ -343,15 +357,15 @@ define([
|
||||
let hideShatteredClass = !data.shattered ? 'hide' : '';
|
||||
|
||||
let markup = '<div class="clearfix ' + config.resultOptionImageClass + '">';
|
||||
markup += '<div class="col-sm-4 pf-select-item-anchor ' + systemNameClass + '">' + data.text + '</div>';
|
||||
markup += '<div class="col-sm-2 text-right ' + data.effectClass + '">';
|
||||
markup += '<div class="col-xs-4 pf-select-item-anchor ' + systemNameClass + '">' + data.text + '</div>';
|
||||
markup += '<div class="col-xs-2 text-right ' + data.effectClass + '">';
|
||||
markup += '<i class="fas fa-fw fa-square ' + hideEffectClass + '"></i>';
|
||||
markup += '</div>';
|
||||
markup += '<div class="col-sm-2 text-right ' + data.secClass + '">' + data.security + '</div>';
|
||||
markup += '<div class="col-sm-2 text-right ' + shatteredClass + '">';
|
||||
markup += '<div class="col-xs-2 text-right ' + data.secClass + '">' + data.security + '</div>';
|
||||
markup += '<div class="col-xs-2 text-right ' + shatteredClass + '">';
|
||||
markup += '<i class="fas fa-fw fa-chart-pie ' + hideShatteredClass + '"></i>';
|
||||
markup += '</div>';
|
||||
markup += '<div class="col-sm-2 text-right ' + data.trueSecClass + '">' + data.trueSec + '</div></div>';
|
||||
markup += '<div class="col-xs-2 text-right ' + data.trueSecClass + '">' + data.trueSec + '</div></div>';
|
||||
|
||||
return markup;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ define([
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox',
|
||||
'app/counter',
|
||||
'app/map/util'
|
||||
], ($, Init, Util, bootbox, MapUtil) => {
|
||||
], ($, Init, Util, bootbox, Counter, MapUtil) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
@@ -774,7 +775,7 @@ define([
|
||||
_: function(data, type, row){
|
||||
let value = data.typeId;
|
||||
if(type === 'display'){
|
||||
value = '<img src="' + Util.eveImageUrl('render', value) + '" title="' + data.typeName + '" data-toggle="tooltip" />';
|
||||
value = '<img src="' + Util.eveImageUrl('types', value) + '" title="' + data.typeName + '" data-toggle="tooltip" />';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -794,7 +795,7 @@ define([
|
||||
_: (cellData, type, rowData, meta) => {
|
||||
let value = cellData.name;
|
||||
if(type === 'display'){
|
||||
value = '<img src="' + Util.eveImageUrl('character', cellData.id) + '" title="' + value + '" data-toggle="tooltip" />';
|
||||
value = '<img src="' + Util.eveImageUrl('characters', cellData.id) + '" title="' + value + '" data-toggle="tooltip" />';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -827,10 +828,7 @@ define([
|
||||
title: 'log',
|
||||
width: 55,
|
||||
className: ['text-right', config.tableCellCounterClass].join(' '),
|
||||
data: 'created.created',
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
$(cell).initTimestampCounter('d');
|
||||
}
|
||||
data: 'created.created'
|
||||
},{
|
||||
targets: 5,
|
||||
name: 'edit',
|
||||
@@ -873,7 +871,7 @@ define([
|
||||
display: data => {
|
||||
let val = '<i class="fas fa-plus"></i>';
|
||||
if(data){
|
||||
val = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
|
||||
val = '<i class="fas fa-times txt-color txt-color-redDark"></i>';
|
||||
}
|
||||
return val;
|
||||
}
|
||||
@@ -931,6 +929,9 @@ define([
|
||||
}
|
||||
}
|
||||
],
|
||||
initComplete: function(settings, json){
|
||||
Counter.initTableCounter(this, ['created:name']);
|
||||
},
|
||||
drawCallback: function(settings){
|
||||
let animationRows = this.api().rows().nodes().to$().filter(function(a,b ){
|
||||
return (
|
||||
|
||||
@@ -29,7 +29,8 @@ define([
|
||||
ykeys: ['y'],
|
||||
labels: ['Jumps'],
|
||||
lineColors: ['#375959'],
|
||||
pointFillColors: ['#477372']
|
||||
pointFillColors: ['#477372'],
|
||||
infoLabels: ['Avg. jumps']
|
||||
},
|
||||
shipKills: {
|
||||
headline: 'Ship/POD Kills',
|
||||
@@ -37,7 +38,8 @@ define([
|
||||
ykeys: ['y', 'z'],
|
||||
labels: ['Ships', 'PODs'],
|
||||
lineColors: ['#375959', '#477372'],
|
||||
pointFillColors: ['#477372', '#568a89']
|
||||
pointFillColors: ['#477372', '#568a89'],
|
||||
infoLabels: ['Avg. ship kills', 'Avg. pod kills']
|
||||
},
|
||||
factionKills: {
|
||||
headline: 'NPC Kills',
|
||||
@@ -45,7 +47,8 @@ define([
|
||||
ykeys: ['y'],
|
||||
labels: ['NPCs'],
|
||||
lineColors: ['#375959'],
|
||||
pointFillColors: ['#477372']
|
||||
pointFillColors: ['#477372'],
|
||||
infoLabels: ['Avg. NPC kills']
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -69,8 +72,11 @@ define([
|
||||
* @param graphKey
|
||||
* @param graphData
|
||||
* @param eventLine
|
||||
* @returns {Array|null}
|
||||
*/
|
||||
let initGraph = (graphElement, graphKey, graphData, eventLine) => {
|
||||
let tooltipData = null;
|
||||
|
||||
if(
|
||||
graphData.logExists &&
|
||||
graphData.data &&
|
||||
@@ -100,6 +106,7 @@ define([
|
||||
// ... calc average
|
||||
let goals = Object.values(sum).map(value => Math.floor(value / dataLength));
|
||||
|
||||
// init Morris chart --------------------------------------------------------------------------------------
|
||||
let graphConfig = {
|
||||
element: graphElement,
|
||||
data: graphData.data,
|
||||
@@ -111,11 +118,11 @@ define([
|
||||
yLabelFormat: value => Math.round(value),
|
||||
padding: 8,
|
||||
hideHover: true,
|
||||
pointSize: 3,
|
||||
pointSize: 2.5,
|
||||
lineColors: getInfoForGraph(graphKey, 'lineColors'),
|
||||
pointFillColors: getInfoForGraph(graphKey, 'pointFillColors'),
|
||||
pointStrokeColors: ['#141519'],
|
||||
lineWidth: 2,
|
||||
lineWidth: 1.5,
|
||||
grid: true,
|
||||
gridStrokeWidth: 0.3,
|
||||
gridTextSize: 9,
|
||||
@@ -125,7 +132,7 @@ define([
|
||||
goals: goals,
|
||||
goalStrokeWidth: 1,
|
||||
goalLineColors: ['#c2760c'],
|
||||
smooth: true,
|
||||
smooth: false,
|
||||
fillOpacity: 0.2,
|
||||
resize: true,
|
||||
redraw: true,
|
||||
@@ -133,15 +140,36 @@ define([
|
||||
eventLineColors: ['#63676a']
|
||||
};
|
||||
|
||||
if(eventLine >= 0){
|
||||
if(eventLine > 0){
|
||||
graphConfig.events = [eventLine];
|
||||
}
|
||||
|
||||
Morris.Area(graphConfig);
|
||||
|
||||
// data for info "popover" --------------------------------------------------------------------------------
|
||||
tooltipData = {};
|
||||
let tooltipRows = [];
|
||||
let infoLabels = getInfoForGraph(graphKey, 'infoLabels');
|
||||
goals.forEach((goal, i) => {
|
||||
tooltipRows.push({
|
||||
label: infoLabels[i],
|
||||
value: goal,
|
||||
class: 'txt-color txt-color-orangeDark'
|
||||
});
|
||||
});
|
||||
tooltipData.rows = tooltipRows;
|
||||
|
||||
let serverDate = Util.getServerTime();
|
||||
let updatedDate = Util.convertTimestampToServerTime(graphData.updated);
|
||||
let updatedDiff = Util.getTimeDiffParts(updatedDate, serverDate);
|
||||
|
||||
tooltipData.title = '<i class="fas fa-download"></i><span class="pull-right ">' + Util.formatTimeParts(updatedDiff) + '</span>';
|
||||
}else{
|
||||
// make container a bit smaller -> no graph shown
|
||||
graphElement.css('height', '22px').text('No data');
|
||||
}
|
||||
|
||||
return tooltipData;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -190,7 +218,7 @@ define([
|
||||
*/
|
||||
let addGraphData = (context, graphData) => {
|
||||
|
||||
// calculate time offset until system created -----------------------------------------------------------------
|
||||
// calculate time offset until system updated -----------------------------------------------------------------
|
||||
let serverData = Util.getServerTime();
|
||||
let timestampNow = Math.floor(serverData.getTime() / 1000);
|
||||
let timeSinceUpdate = timestampNow - context.systemData.updated.updated;
|
||||
@@ -198,19 +226,87 @@ define([
|
||||
let timeInHours = Math.floor(timeSinceUpdate / 3600);
|
||||
let timeInMinutes = Math.floor((timeSinceUpdate % 3600) / 60);
|
||||
let timeInMinutesPercent = parseFloat((timeInMinutes / 60).toFixed(2));
|
||||
let eventLine = timeInHours * timeInMinutesPercent;
|
||||
|
||||
// graph is from right to left -> convert event line
|
||||
eventLine = 23 - eventLine;
|
||||
let eventLine = Math.max(parseFloat((24 - timeInHours - timeInMinutesPercent).toFixed(2)), 0);
|
||||
|
||||
// update graph data ------------------------------------------------------------------------------------------
|
||||
for(let [systemId, graphsData] of Object.entries(graphData)){
|
||||
for(let [graphKey, graphData] of Object.entries(graphsData)){
|
||||
let graphElement = context.moduleElement.find('[data-graph="' + graphKey + '"]');
|
||||
let graphColElement = context.moduleElement.find('[data-graph="' + graphKey + '"]');
|
||||
let graphElement = graphColElement.find('.' + config.systemGraphClass);
|
||||
graphElement.hideLoadingAnimation();
|
||||
initGraph(graphElement, graphKey, graphData, eventLine);
|
||||
|
||||
graphColElement.data('infoData', initGraph(graphElement, graphKey, graphData, eventLine));
|
||||
}
|
||||
}
|
||||
|
||||
setModuleObserver(context.moduleElement);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param moduleElement
|
||||
*/
|
||||
let setModuleObserver = moduleElement => {
|
||||
moduleElement.hoverIntent({
|
||||
over: function(e){
|
||||
let element = $(this);
|
||||
let tooltipData = element.parents('[data-graph]').data('infoData');
|
||||
if(tooltipData){
|
||||
element.addSystemGraphTooltip(tooltipData.rows, {
|
||||
trigger: 'manual',
|
||||
title: tooltipData.title
|
||||
}).popover('show');
|
||||
}
|
||||
},
|
||||
out: function(e){
|
||||
$(this).destroyPopover();
|
||||
},
|
||||
selector: '.' + Util.config.popoverTriggerClass
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* add info tooltip for graphs
|
||||
* @param tooltipData
|
||||
* @param options
|
||||
* @returns {void|*|undefined}
|
||||
*/
|
||||
$.fn.addSystemGraphTooltip = function(tooltipData, options = {}){
|
||||
|
||||
let table = '<table>';
|
||||
for(let data of tooltipData){
|
||||
let css = data.class || '';
|
||||
|
||||
table += '<tr>';
|
||||
table += '<td>';
|
||||
table += data.label;
|
||||
table += '</td>';
|
||||
table += '<td class="text-right ' + css + '">';
|
||||
table += data.value;
|
||||
table += '</td>';
|
||||
table += '</tr>';
|
||||
}
|
||||
table += '</table>';
|
||||
|
||||
let defaultOptions = {
|
||||
placement: 'top',
|
||||
html: true,
|
||||
trigger: 'hover',
|
||||
container: 'body',
|
||||
title: 'Info',
|
||||
content: table,
|
||||
delay: {
|
||||
show: 0,
|
||||
hide: 0
|
||||
},
|
||||
};
|
||||
|
||||
options = $.extend({}, defaultOptions, options);
|
||||
|
||||
return this.each(function(){
|
||||
$(this).popover(options);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -262,11 +358,11 @@ define([
|
||||
class: 'row'
|
||||
});
|
||||
|
||||
for(let [graphKey, graphConfig] of Object.entries(config.systemGraphs)){
|
||||
for(let graphKey of Object.keys(config.systemGraphs)){
|
||||
rowElement.append(
|
||||
$('<div>', {
|
||||
class: ['col-xs-12', 'col-sm-4'].join(' ')
|
||||
}).append(
|
||||
}).attr('data-graph', graphKey).append(
|
||||
$('<div>', {
|
||||
class: config.moduleHeadClass
|
||||
}).append(
|
||||
@@ -275,11 +371,18 @@ define([
|
||||
}),
|
||||
$('<h5>', {
|
||||
text: getInfoForGraph(graphKey, 'headline')
|
||||
})
|
||||
}),
|
||||
$('<h5>', {
|
||||
class: 'pull-right'
|
||||
}).append(
|
||||
$('<small>', {
|
||||
html: '<i class="fas fa-fw fa-question-circle pf-help ' + Util.config.popoverTriggerClass + '"></i>'
|
||||
})
|
||||
)
|
||||
),
|
||||
$('<div>', {
|
||||
class: config.systemGraphClass
|
||||
}).attr('data-graph', graphKey)
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ define([
|
||||
|
||||
// headline toolbar
|
||||
moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head
|
||||
textActionIconCopyClass: 'pf-module-icon-button-copy', // class for text action "copy"
|
||||
|
||||
// breadcrumb
|
||||
constellationLinkClass: 'pf-system-info-constellation', // class for "constellation" name
|
||||
regionLinkClass: 'pf-system-info-region', // class for "region" name
|
||||
typeLinkClass: 'pf-system-info-type', // class for "type" name
|
||||
urlLinkClass: 'pf-system-info-url', // class for "url" copy link
|
||||
|
||||
// info col/table
|
||||
systemInfoSectionClass: 'pf-system-info-section', // class for system info section
|
||||
@@ -166,7 +166,7 @@ define([
|
||||
fwContestedRow.find('.' + config.systemSovFwPercentageClass).text(percentage);
|
||||
fwContestedRow.show();
|
||||
|
||||
let occupierFactionImage = Util.eveImageUrl('alliance', (occupierFaction ? occupierFaction.id : 0), 64);
|
||||
let occupierFactionImage = Util.eveImageUrl('factions', (occupierFaction ? occupierFaction.id : 0), 64);
|
||||
let occupierFactionName = occupierFaction ? occupierFaction.name : '';
|
||||
|
||||
fwOccupationRow.find('.' + config.systemSovFwOccupationImageClass)[0].style.setProperty('--bg-image', 'url(\'' + occupierFactionImage + '\')');
|
||||
@@ -193,20 +193,25 @@ define([
|
||||
* @param systemData
|
||||
*/
|
||||
let getThirdPartySystemLinks = (pages, systemData) => {
|
||||
let urls = {};
|
||||
let links = [];
|
||||
let isWormhole = MapUtil.getSystemTypeInfo(Util.getObjVal(systemData, 'type.id'), 'name') === 'w-space';
|
||||
let systemName = Util.getObjVal(systemData, 'name') || '';
|
||||
let regionName = Util.getObjVal(systemData, 'region.name') || '';
|
||||
|
||||
let validUrls = 0;
|
||||
let setDestination = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
Util.setDestination('set_destination', 'system', {id: systemData.systemId, name: systemData.name});
|
||||
};
|
||||
|
||||
for(let i = 0; i < pages.length; i++){
|
||||
let url = false;
|
||||
let link = null;
|
||||
let showInModuleHead = true;
|
||||
let domain = Util.getObjVal(Init, 'url.' + pages[i]);
|
||||
if(domain || pages[i] === 'eve'){
|
||||
if(domain){
|
||||
// linkOut url
|
||||
let url = false;
|
||||
switch(pages[i]){
|
||||
case 'eve':
|
||||
url = 'https://client'; // fake url
|
||||
break;
|
||||
case 'dotlan':
|
||||
let systemNameTemp = systemName.replace(/ /g, '_');
|
||||
let regionNameTemp = regionName.replace(/ /g, '_');
|
||||
@@ -231,16 +236,41 @@ define([
|
||||
|
||||
if(url){
|
||||
let urlObj = new URL(url);
|
||||
urls[++validUrls + '_url'] = {
|
||||
page: pages[i],
|
||||
domain: urlObj.hostname,
|
||||
link = {
|
||||
title: urlObj.hostname,
|
||||
url: url
|
||||
};
|
||||
}
|
||||
}else{
|
||||
// custom callback
|
||||
let action = false;
|
||||
let title = false;
|
||||
switch(pages[i]){
|
||||
case 'eve':
|
||||
action = setDestination;
|
||||
title = 'set destination';
|
||||
showInModuleHead = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(action){
|
||||
link = {
|
||||
title: title|| pages[i],
|
||||
action: action
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(link){
|
||||
links.push(Object.assign({}, link, {
|
||||
page: pages[i],
|
||||
showInModuleHead: showInModuleHead
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return urls;
|
||||
return links;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -280,7 +310,7 @@ define([
|
||||
if(sovDataFact){
|
||||
sovereigntyPrimary = {
|
||||
row1Val: 'Faction',
|
||||
row1Img: Util.eveImageUrl('alliance', sovDataFact.id, 64),
|
||||
row1Img: Util.eveImageUrl('factions', sovDataFact.id, 64),
|
||||
row1ImgTitle: sovDataFact.name,
|
||||
row2Val: sovDataFact.name
|
||||
};
|
||||
@@ -288,7 +318,7 @@ define([
|
||||
if(sovDataAlly){
|
||||
sovereigntyPrimary = {
|
||||
row1Val: 'Alliance',
|
||||
row1Img: Util.eveImageUrl('alliance', sovDataAlly.id, 64),
|
||||
row1Img: Util.eveImageUrl('alliances', sovDataAlly.id, 64),
|
||||
row1ImgTitle: sovDataAlly.name,
|
||||
row2Val: '<' + sovDataAlly.ticker + '>',
|
||||
row3Label: 'Ally',
|
||||
@@ -299,7 +329,7 @@ define([
|
||||
sovereigntySecondary = {
|
||||
row1Label: 'Corp',
|
||||
row1Val: sovDataCorp.name,
|
||||
row1Img: Util.eveImageUrl('corporation', sovDataCorp.id, 64)
|
||||
row1Img: Util.eveImageUrl('corporations', sovDataCorp.id, 64)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -326,6 +356,7 @@ define([
|
||||
sovereigntySecondary: sovereigntySecondary ? Object.assign({}, sovereigntyDefault, sovereigntySecondary) : undefined,
|
||||
static: staticsData,
|
||||
moduleHeadlineIconClass: config.moduleHeadlineIconClass,
|
||||
textActionIconCopyClass: config.textActionIconCopyClass,
|
||||
infoSectionClass: config.systemInfoSectionClass,
|
||||
descriptionSectionClass: config.descriptionSectionClass,
|
||||
sovSectionClass: config.systemSovSectionClass,
|
||||
@@ -378,9 +409,9 @@ define([
|
||||
systemConstellationLinkClass: config.constellationLinkClass,
|
||||
systemRegionLinkClass: config.regionLinkClass,
|
||||
systemTypeLinkClass: config.typeLinkClass,
|
||||
systemUrlLinkClass: config.urlLinkClass,
|
||||
systemUrlLinkClass: config.textActionIconCopyClass,
|
||||
ccpImageServerUrl: Init.url.ccpImageServer,
|
||||
thirdPartyLinks: getThirdPartySystemLinks(['eve', 'dotlan', 'eveeye', 'anoik'], systemData)
|
||||
thirdPartyLinks: getThirdPartySystemLinks(['dotlan', 'eveeye', 'anoik', 'eve'], systemData)
|
||||
};
|
||||
|
||||
requirejs(['text!templates/modules/system_info.html', 'mustache', 'summernote.loader'], (template, Mustache, Summernote) => {
|
||||
@@ -526,7 +557,7 @@ define([
|
||||
}
|
||||
|
||||
// copy system deeplink URL -------------------------------------------------------------------------------
|
||||
moduleElement.find('.' + config.urlLinkClass).on('click', function(){
|
||||
moduleElement.find('.' + config.textActionIconCopyClass).on('click', function(){
|
||||
let mapUrl = $(this).attr('data-url');
|
||||
Util.copyToClipboard(mapUrl).then(payload => {
|
||||
if(payload.data){
|
||||
@@ -562,6 +593,19 @@ define([
|
||||
return 'Loading...';
|
||||
};
|
||||
|
||||
// 3rd party click callbacks ------------------------------------------------------------------------------
|
||||
moduleElement.on('click', '[data-link]', e => {
|
||||
for(let link of data.thirdPartyLinks){
|
||||
if(
|
||||
e.target.dataset.link === link.page &&
|
||||
typeof link.action === 'function'
|
||||
){
|
||||
link.action(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// init tooltips ------------------------------------------------------------------------------------------
|
||||
let tooltipElements = moduleElement.find('[data-toggle="tooltip"]');
|
||||
tooltipElements.tooltip({
|
||||
|
||||
@@ -24,9 +24,6 @@ define([
|
||||
|
||||
// headline toolbar
|
||||
moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head
|
||||
moduleHeadlineIconAddClass: 'pf-module-icon-button-add', // class for "add structure" icon
|
||||
moduleHeadlineIconReaderClass: 'pf-module-icon-button-reader', // class for "dScan reader" icon
|
||||
moduleHeadlineIconRefreshClass: 'pf-module-icon-button-refresh', // class for "refresh" icon
|
||||
|
||||
// system intel module
|
||||
intelTableId: 'pf-intel-table-', // id prefix for all tables in module
|
||||
@@ -49,7 +46,8 @@ define([
|
||||
tableCellEllipsisClass: 'pf-table-cell-ellipses-auto', // class for table "ellipsis" cells
|
||||
tableCellActionClass: 'pf-table-action-cell', // class for "action" cells
|
||||
tableCellActionIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
|
||||
tableCellServicesClass: 'pf-table-services-cell' // class for table station "services" cells
|
||||
tableCellServicesClass: 'pf-table-services-cell', // class for table station "services" cells
|
||||
tableCellPopoverClass: 'pf-table-popover-cell' // class for table cells with a "popover"
|
||||
};
|
||||
|
||||
let maxDescriptionLength = 512;
|
||||
@@ -71,6 +69,15 @@ define([
|
||||
return '<i class="fas fa-fw fa-id-card ' + config.tableCellActionIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
|
||||
};
|
||||
|
||||
/**
|
||||
* get a dataTableApi instance from global cache
|
||||
* @param mapId
|
||||
* @param systemId
|
||||
* @param tableType
|
||||
* @returns {*}
|
||||
*/
|
||||
let getDataTableInstance = (mapId, systemId, tableType) => Util.getDataTableInstance(config.intelTableId, mapId, systemId, tableType);
|
||||
|
||||
/**
|
||||
* get dataTable id
|
||||
* @param mapId
|
||||
@@ -284,8 +291,9 @@ define([
|
||||
* @param tableApi
|
||||
* @param systemId
|
||||
* @param structureData
|
||||
* @param bulkData
|
||||
*/
|
||||
let showStructureDialog = (moduleElement, tableApi, systemId, structureData) => {
|
||||
let showStructureDialog = (moduleElement, tableApi, systemId, structureData = null, bulkData = null) => {
|
||||
let structureStatusData = Util.getObjVal(Init, 'structureStatus');
|
||||
|
||||
let statusData = Object.keys(structureStatusData).map((k) => {
|
||||
@@ -322,6 +330,7 @@ define([
|
||||
let data = {
|
||||
id: config.structureDialogId,
|
||||
structureData: structureData,
|
||||
bulkData: bulkData,
|
||||
structureStatus: statusData,
|
||||
nameInputId: config.nameInputId,
|
||||
statusSelectId: config.statusSelectId,
|
||||
@@ -334,9 +343,13 @@ define([
|
||||
|
||||
requirejs(['text!templates/dialog/structure.html', 'mustache'], (template, Mustache) => {
|
||||
let content = Mustache.render(template, data);
|
||||
let title = 'Structure';
|
||||
if(bulkData){
|
||||
title += ' <span class="txt-color txt-color-warning"> (' + bulkData.length + ' rows)</span> ';
|
||||
}
|
||||
|
||||
let structureDialog = bootbox.dialog({
|
||||
title: 'Structure',
|
||||
title: title,
|
||||
message: content,
|
||||
show: false,
|
||||
buttons: {
|
||||
@@ -346,7 +359,9 @@ define([
|
||||
},
|
||||
autoFill: {
|
||||
label: buttonLabelAutoFill,
|
||||
className: 'btn-primary' + (disableButtonAutoFill ? ' pf-font-italic disabled' : ''),
|
||||
className: 'btn-primary' +
|
||||
(disableButtonAutoFill ? ' pf-font-italic disabled' : '') +
|
||||
(bulkData ? ' hidden' : ''),
|
||||
callback: function(){
|
||||
let form = this.find('form');
|
||||
form.find('#' + config.nameInputId).val(characterStructureName);
|
||||
@@ -378,7 +393,20 @@ define([
|
||||
moduleElement.showLoadingAnimation();
|
||||
|
||||
let method = formData.id ? 'PATCH' : 'PUT';
|
||||
Util.request(method, 'structure', formData.id, formData,
|
||||
let ids = formData.id;
|
||||
let data = formData;
|
||||
|
||||
if(bulkData){
|
||||
// bulk update multiple rows
|
||||
method = 'POST';
|
||||
ids = [];
|
||||
data = bulkData.map(structureData => {
|
||||
structureData.corporationId = formData.corporationId;
|
||||
return structureData;
|
||||
});
|
||||
}
|
||||
|
||||
Util.request(method, 'structure', ids, data,
|
||||
{
|
||||
moduleElement: moduleElement,
|
||||
tableApi: tableApi
|
||||
@@ -524,7 +552,7 @@ define([
|
||||
if (!last || last.id !== group.id) {
|
||||
// "stations" are grouped by "raceId" with its "factionId"
|
||||
// "structures" are grouped by "corporationId" that ADDED it (not the ingame "owner" of it)
|
||||
let imgType = 'stations' === group.groupedDataKey ? 'alliance' : 'corporation';
|
||||
let imgType = 'stations' === group.groupedDataKey ? 'factions' : 'corporations';
|
||||
|
||||
$(rows).eq(i).before(
|
||||
'<tr class="group">' +
|
||||
@@ -573,22 +601,6 @@ define([
|
||||
$('<h5>', {
|
||||
class: config.moduleHandlerClass
|
||||
}),
|
||||
$('<h5>', {
|
||||
class: 'pull-right'
|
||||
}).append(
|
||||
$('<i>', {
|
||||
class: ['fas', 'fa-fw', 'fa-plus', config.moduleHeadlineIconClass, config.moduleHeadlineIconAddClass].join(' '),
|
||||
title: 'add'
|
||||
}).attr('data-html', 'true').attr('data-toggle', 'tooltip'),
|
||||
$('<i>', {
|
||||
class: ['fas', 'fa-fw', 'fa-paste', config.moduleHeadlineIconClass, config.moduleHeadlineIconReaderClass].join(' '),
|
||||
title: 'D-Scan reader'
|
||||
}).attr('data-html', 'true').attr('data-toggle', 'tooltip'),
|
||||
$('<i>', {
|
||||
class: ['fas', 'fa-fw', 'fa-sync', config.moduleHeadlineIconClass, config.moduleHeadlineIconRefreshClass].join(' '),
|
||||
title: 'refresh all'
|
||||
}).attr('data-html', 'true').attr('data-toggle', 'tooltip')
|
||||
),
|
||||
$('<h5>', {
|
||||
text: 'Structures'
|
||||
})
|
||||
@@ -608,6 +620,10 @@ define([
|
||||
},
|
||||
order: [[10, 'desc' ], [0, 'asc' ]],
|
||||
rowId: rowData => getRowId('structures', rowData.id),
|
||||
select: {
|
||||
style: 'os',
|
||||
selector: 'td:not(.' + config.tableCellActionClass + ')'
|
||||
},
|
||||
language: {
|
||||
emptyTable: 'No structures recorded',
|
||||
info: '_START_ to _END_ of _MAX_',
|
||||
@@ -641,7 +657,7 @@ define([
|
||||
_: function(data, type, row, meta){
|
||||
let value = data;
|
||||
if(type === 'display' && value){
|
||||
value = '<img src="' + Util.eveImageUrl('type', value, 64) +'"/>';
|
||||
value = '<img src="' + Util.eveImageUrl('types', value, 64) +'"/>';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -675,7 +691,7 @@ define([
|
||||
let value = data;
|
||||
if(type === 'display' && value){
|
||||
value = '<a href="https://zkillboard.com/corporation/' + data + '/" target="_blank" rel="noopener">';
|
||||
value += '<img src="' + Util.eveImageUrl('corporation', data, 64) + '"/>';
|
||||
value += '<img src="' + Util.eveImageUrl('corporations', data, 64) + '"/>';
|
||||
value += '</a>';
|
||||
}
|
||||
return value;
|
||||
@@ -693,7 +709,7 @@ define([
|
||||
targets: 6,
|
||||
name: 'note',
|
||||
title: 'note',
|
||||
className: [config.tableCellEllipsisClass, 'all'].join(' '),
|
||||
className: [config.tableCellEllipsisClass, 'all', Util.config.popoverTriggerClass, config.tableCellPopoverClass].join(' '),
|
||||
data: 'description'
|
||||
},{
|
||||
targets: 7,
|
||||
@@ -726,10 +742,19 @@ define([
|
||||
$(cell).removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
|
||||
}else{
|
||||
$(cell).on('click', function(e){
|
||||
// get current row data (important!)
|
||||
// -> "rowData" param is not current state, values are "on createCell()" state
|
||||
rowData = tableApi.row( $(cell).parents('tr')).data();
|
||||
showStructureDialog(moduleElement, tableApi, systemData.systemId, rowData);
|
||||
let rowData = null;
|
||||
let bulkData = null;
|
||||
// check if multiple rows are selected + current row is one of them -> bulk edit
|
||||
let rowsSelected = tableApi.rows({selected: true});
|
||||
if(rowsSelected.count() && tableApi.row(rowIndex, {selected: true}).count()){
|
||||
bulkData = [...new Set(rowsSelected.data().toArray().map(rowData => ({id: rowData.id})))];
|
||||
}else{
|
||||
// get current row data (important!)
|
||||
// -> "rowData" param is not current state, values are "on createCell()" state
|
||||
rowData = tableApi.row( $(cell).parents('tr')).data();
|
||||
}
|
||||
|
||||
showStructureDialog(moduleElement, tableApi, systemData.systemId, rowData, bulkData);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -743,7 +768,7 @@ define([
|
||||
data: null,
|
||||
render: {
|
||||
display: data => {
|
||||
let icon = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
|
||||
let icon = '<i class="fas fa-times txt-color txt-color-redDark"></i>';
|
||||
if(data.rowGroupData.id !== corporationId){
|
||||
icon = '<i class="fas fa-ban txt-color txt-color-grayLight" title="restricted" data-placement="left"></i>';
|
||||
}
|
||||
@@ -812,9 +837,9 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
$.extend(true, structureDataTableOptions, getDataTableDefaults());
|
||||
let tableApiStructure = structureTable.DataTable(structureDataTableOptions);
|
||||
let tableApiStructure = structureTable.DataTable($.extend(true, {}, getDataTableDefaults(), structureDataTableOptions));
|
||||
|
||||
// "Responsive" Datatables Plugin
|
||||
new $.fn.dataTable.Responsive(tableApiStructure);
|
||||
|
||||
tableApiStructure.on('responsive-resize', function(e, tableApi, columns){
|
||||
@@ -822,6 +847,107 @@ define([
|
||||
tableApi.draw();
|
||||
});
|
||||
|
||||
// "Select" Datatables Plugin
|
||||
tableApiStructure.select();
|
||||
|
||||
// "Buttons" Datatables Plugin
|
||||
tableApiStructure.on('user-select', function(e, tableApi, type, cell, originalEvent){
|
||||
let rowData = tableApi.row(cell.index().row).data();
|
||||
if(Util.getObjVal(rowData, 'rowGroupData.id') !== corporationId){
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
let buttons = new $.fn.dataTable.Buttons(tableApiStructure, {
|
||||
dom: {
|
||||
container: {
|
||||
tag: 'h5',
|
||||
className: 'pull-right'
|
||||
},
|
||||
button: {
|
||||
tag: 'i',
|
||||
className: ['fas', 'fa-fw', config.moduleHeadlineIconClass].join(' '),
|
||||
},
|
||||
buttonLiner: {
|
||||
tag: null
|
||||
}
|
||||
},
|
||||
name: 'tableTools',
|
||||
buttons: [
|
||||
{
|
||||
name: 'selectToggle',
|
||||
className: ['fa-check-double'].join(' '),
|
||||
titleAttr: 'select all',
|
||||
attr: {
|
||||
'data-toggle': 'tooltip',
|
||||
'data-html': true
|
||||
},
|
||||
action: function(e, tableApi, node, config){
|
||||
let indexes = tableApi.rows().eq(0).filter(rowIdx => {
|
||||
return Util.getObjVal(tableApi.cell(rowIdx, 'rowGroupData:name').data(), 'id') === corporationId;
|
||||
});
|
||||
|
||||
let rowCountAll = tableApi.rows(indexes).count();
|
||||
let rowCountSelected = tableApi.rows({selected: true}).count();
|
||||
if(rowCountSelected && (rowCountSelected >= rowCountAll)){
|
||||
tableApi.rows().deselect();
|
||||
node.removeClass('active');
|
||||
}else{
|
||||
tableApi.rows(indexes).select();
|
||||
node.addClass('active');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'add',
|
||||
className: 'fa-plus',
|
||||
titleAttr: 'add',
|
||||
attr: {
|
||||
'data-toggle': 'tooltip',
|
||||
'data-html': true
|
||||
},
|
||||
action: function(e, tableApi, node, config){
|
||||
showStructureDialog(moduleElement, tableApi, systemData.systemId);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'dScan',
|
||||
className: 'fa-paste',
|
||||
titleAttr: 'D-Scan reader',
|
||||
attr: {
|
||||
'data-toggle': 'tooltip',
|
||||
'data-html': true
|
||||
},
|
||||
action: function(e, tableApi, node, config ){
|
||||
showDscanReaderDialog(moduleElement, tableApi, systemData);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'refresh',
|
||||
className: 'fa-sync',
|
||||
titleAttr: 'refresh all',
|
||||
attr: {
|
||||
'data-toggle': 'tooltip',
|
||||
'data-html': true
|
||||
},
|
||||
action: function(e, tableApi, node, config ){
|
||||
moduleElement.showLoadingAnimation();
|
||||
|
||||
Util.request('GET', 'system', systemData.id, {mapId: mapId},
|
||||
{
|
||||
moduleElement: moduleElement,
|
||||
tableApi: tableApi,
|
||||
removeMissing: true
|
||||
},
|
||||
context => context.moduleElement.hideLoadingAnimation()
|
||||
).then(payload => callbackUpdateTableRows(payload.context, Util.getObjVal(payload.data, 'structures')));
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
tableApiStructure.buttons().container().appendTo(moduleElement.find('.' + config.moduleHeadClass));
|
||||
|
||||
if(showStationTable){
|
||||
// "Stations" table ---------------------------------------------------------------------------------------
|
||||
|
||||
@@ -869,7 +995,7 @@ define([
|
||||
_: function(data, type, row, meta){
|
||||
let value = data;
|
||||
if(type === 'display' && value){
|
||||
value = '<img src="' + Util.eveImageUrl('type', value, 64) +'"/>';
|
||||
value = '<img src="' + Util.eveImageUrl('types', value, 64) +'"/>';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -965,7 +1091,7 @@ define([
|
||||
let value = data;
|
||||
if(type === 'display' && value){
|
||||
value = '<a href="https://zkillboard.com/corporation/' + data + '/" target="_blank" rel="noopener">';
|
||||
value += '<img src="' + Util.eveImageUrl('corporation', data, 64) + '"/>';
|
||||
value += '<img src="' + Util.eveImageUrl('corporations', data, 64) + '"/>';
|
||||
value += '</a>';
|
||||
}
|
||||
return value;
|
||||
@@ -1057,8 +1183,7 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
$.extend(true, stationDataTableOptions, getDataTableDefaults());
|
||||
let tableApiStation = stationTable.DataTable(stationDataTableOptions);
|
||||
let tableApiStation = stationTable.DataTable($.extend(true, {}, getDataTableDefaults(), stationDataTableOptions));
|
||||
|
||||
new $.fn.dataTable.Responsive(tableApiStation);
|
||||
|
||||
@@ -1221,30 +1346,6 @@ define([
|
||||
let structureTableElement = moduleElement.find('.' + config.systemStructuresTableClass);
|
||||
let tableApi = structureTableElement.DataTable();
|
||||
|
||||
// init structure dialog --------------------------------------------------------------------------------------
|
||||
moduleElement.find('.' + config.moduleHeadlineIconAddClass).on('click', function(e){
|
||||
showStructureDialog(moduleElement, tableApi, systemData.systemId);
|
||||
});
|
||||
|
||||
// init structure dialog --------------------------------------------------------------------------------------
|
||||
moduleElement.find('.' + config.moduleHeadlineIconReaderClass).on('click', function(e){
|
||||
showDscanReaderDialog(moduleElement, tableApi, systemData);
|
||||
});
|
||||
|
||||
// init refresh button ----------------------------------------------------------------------------------------
|
||||
moduleElement.find('.' + config.moduleHeadlineIconRefreshClass).on('click', function(e){
|
||||
moduleElement.showLoadingAnimation();
|
||||
|
||||
Util.request('GET', 'system', systemData.id, {mapId: mapId},
|
||||
{
|
||||
moduleElement: moduleElement,
|
||||
tableApi: tableApi,
|
||||
removeMissing: true
|
||||
},
|
||||
context => context.moduleElement.hideLoadingAnimation()
|
||||
).then(payload => callbackUpdateTableRows(payload.context, Util.getObjVal(payload.data, 'structures')));
|
||||
});
|
||||
|
||||
// init listener for global "past" dScan into this page -------------------------------------------------------
|
||||
moduleElement.on('pf:updateIntelModuleByClipboard', function(e, clipboard){
|
||||
updateStructureTableByClipboard(systemData, clipboard, {
|
||||
@@ -1252,6 +1353,38 @@ define([
|
||||
tableApi: tableApi
|
||||
});
|
||||
});
|
||||
|
||||
// init popovers for some table cells -------------------------------------------------------------------------
|
||||
moduleElement.hoverIntent({
|
||||
over: function(e){
|
||||
let tableApi = getDataTableInstance(mapId, systemData.id, 'structure');
|
||||
|
||||
// simple <table> for layout (CSS)
|
||||
let cellData = tableApi.cell(this).data();
|
||||
if(cellData && cellData.length){
|
||||
let content = '<table><tr><td>' + cellData.replace(/\r?\n/g, '<br />') + '</td></tr></table>';
|
||||
|
||||
let options = {
|
||||
placement: 'top',
|
||||
html: true,
|
||||
trigger: 'manual',
|
||||
container: 'body',
|
||||
title: '',
|
||||
content: content,
|
||||
delay: {
|
||||
show: 0,
|
||||
hide: 0
|
||||
},
|
||||
};
|
||||
|
||||
$(this).popover(options).popover('show');
|
||||
}
|
||||
},
|
||||
out: function(e){
|
||||
$(this).destroyPopover();
|
||||
},
|
||||
selector: '.' + config.tableCellPopoverClass
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1263,8 +1396,8 @@ define([
|
||||
let stationTable = moduleElement.find('.' + config.systemStationsTableClass);
|
||||
let tableApiStructure = structureTable.DataTable();
|
||||
let tableApiStation = stationTable.DataTable();
|
||||
tableApiStructure.destroy();
|
||||
tableApiStation.destroy();
|
||||
tableApiStructure.destroy(true);
|
||||
tableApiStation.destroy(true);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -6,8 +6,8 @@ define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'morris'
|
||||
], ($, Init, Util, Morris) => {
|
||||
'app/lib/cache'
|
||||
], ($, Init, Util, Cache) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
@@ -39,7 +39,12 @@ define([
|
||||
maxCountKills: 43
|
||||
};
|
||||
|
||||
let cache = {};
|
||||
let cache = new Cache({
|
||||
name: 'killboardModule',
|
||||
ttl: 60 * 60,
|
||||
maxSize: 600,
|
||||
debug: false
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -59,9 +64,10 @@ define([
|
||||
*/
|
||||
let loadKillmailData = (requestData, context, callback) => {
|
||||
let cacheKey = 'killmail_' + requestData.killId;
|
||||
if(cache[cacheKey]){
|
||||
let responseData = cache.get(cacheKey);
|
||||
if(responseData){
|
||||
// ... already cached -> return from cache
|
||||
callback(context, cache[cacheKey])
|
||||
callback(context, responseData)
|
||||
.then(payload => showKills(payload.data.killboardElement, payload.data.systemId, payload.data.chunkSize));
|
||||
}else{
|
||||
// ...not cached -> request data
|
||||
@@ -73,7 +79,7 @@ define([
|
||||
dataType: 'json',
|
||||
context: context
|
||||
}).done(function(responseData){
|
||||
cache[cacheKey] = responseData;
|
||||
cache.set(cacheKey, responseData);
|
||||
|
||||
callback(this, responseData)
|
||||
.then(payload => showKills(payload.data.killboardElement, payload.data.systemId, payload.data.chunkSize));
|
||||
@@ -93,12 +99,14 @@ define([
|
||||
*/
|
||||
let showKills = (killboardElement, systemId, chunkSize) => {
|
||||
if(chunkSize){
|
||||
let cacheKey = 'zkb_' + systemId;
|
||||
let data = cache.getOrDefault(cacheKey, []);
|
||||
if(
|
||||
killboardElement.children().length < config.maxCountKills &&
|
||||
cache['zkb_' + systemId].length
|
||||
data.length
|
||||
){
|
||||
// next killmail to load
|
||||
let nextZkb = cache['zkb_' + systemId].shift();
|
||||
let nextZkb = data.shift();
|
||||
|
||||
loadKillmailData({
|
||||
killId: parseInt(nextZkb.killmail_id) || 0,
|
||||
@@ -219,7 +227,7 @@ define([
|
||||
}).done(function(result){
|
||||
// zkb result needs to be cached and becomes reduced on "load more"
|
||||
let cacheKey = 'zkb_' + systemData.systemId;
|
||||
cache[cacheKey] = result;
|
||||
cache.set(cacheKey, result);
|
||||
|
||||
if(result.length){
|
||||
// kills found -> insert hidden warning for recent kills
|
||||
|
||||
@@ -761,7 +761,7 @@ define([
|
||||
let flagButton = '<i class="fas ' + ['fa-shield-alt', 'txt-color', flagButtonClass].join(' ') + '"></i>';
|
||||
let reloadButton = '<i class="fas ' + ['fa-sync'].join(' ') + '"></i>';
|
||||
let searchButton = '<i class="fas ' + ['fa-search'].join(' ') + '"></i>';
|
||||
let deleteButton = '<i class="fas ' + ['fa-times', 'txt-color', 'txt-color-redDarker'].join(' ') + '"></i>';
|
||||
let deleteButton = '<i class="fas ' + ['fa-times', 'txt-color', 'txt-color-redDark'].join(' ') + '"></i>';
|
||||
|
||||
// default row data (e.g. no route found)
|
||||
let tableRowData = {
|
||||
@@ -1240,10 +1240,10 @@ define([
|
||||
popoverRoot.data('bs.popover').tip().find('a').on('click', function(){
|
||||
// hint: "data" attributes should be in lower case!
|
||||
let systemData = {
|
||||
systemId: $(this).data('systemid'),
|
||||
id: $(this).data('systemid'),
|
||||
name: $(this).data('name')
|
||||
};
|
||||
Util.setDestination(systemData, 'set_destination');
|
||||
Util.setDestination('set_destination', 'system', systemData);
|
||||
|
||||
// close popover
|
||||
popoverRoot.popover('hide');
|
||||
|
||||
@@ -10,8 +10,9 @@ define([
|
||||
'app/counter',
|
||||
'app/map/map',
|
||||
'app/map/util',
|
||||
'app/lib/cache',
|
||||
'app/ui/form_element'
|
||||
], ($, Init, Util, bootbox, Counter, Map, MapUtil, FormElement) => {
|
||||
], ($, Init, Util, bootbox, Counter, Map, MapUtil, Cache, FormElement) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
@@ -51,6 +52,7 @@ define([
|
||||
|
||||
sigTableEditSigNameInput: 'pf-sig-table-edit-name-input', // class for editable fields (sig name)
|
||||
|
||||
tableCellTypeClass: 'pf-table-type-cell', // class for "type" cells
|
||||
tableCellConnectionClass: 'pf-table-connection-cell', // class for "connection" cells
|
||||
tableCellFocusClass: 'pf-table-focus-cell', // class for "tab-able" cells. enable focus()
|
||||
tableCellCounterClass: 'pf-table-counter-cell', // class for "counter" cells
|
||||
@@ -76,7 +78,12 @@ define([
|
||||
sigInfoCountConDeleteId: 'pf-sig-info-count-con-delete' // id for "connection delete" counter
|
||||
};
|
||||
|
||||
let sigNameCache = {}; // cache signature names
|
||||
let sigTypeOptionsCache = new Cache({ // cache signature names
|
||||
name: 'sigTypeOptions',
|
||||
ttl: 60 * 5,
|
||||
maxSize: 100,
|
||||
debug: false
|
||||
});
|
||||
|
||||
let validSignatureNames = [ // allowed signature type/names
|
||||
'Cosmic Anomaly',
|
||||
@@ -135,7 +142,7 @@ define([
|
||||
reason = jqXHR.statusText;
|
||||
}else if(jqXHR.name){
|
||||
// validation error new sig (new row data save function)
|
||||
reason = jqXHR.name;
|
||||
reason = jqXHR.name.msg;
|
||||
// re-open "name" fields (its a collection of fields but we need "id" field)
|
||||
jqXHR.name.field.$element.editable('show');
|
||||
}else{
|
||||
@@ -221,6 +228,36 @@ define([
|
||||
return sum;
|
||||
};
|
||||
|
||||
/**
|
||||
* Some signatures types can spawn in more than one 'areaId' for a 'groupId'
|
||||
* -> e.g. a 'shattered' C3 WHs have Combat/Relic/.. sites from C2, C3, c4!
|
||||
* https://github.com/exodus4d/pathfinder/issues/875
|
||||
* @param systemTypeId
|
||||
* @param areaId
|
||||
* @param groupId
|
||||
* @param shattered
|
||||
* @returns {[*]}
|
||||
*/
|
||||
let getAreaIdsForSignatureTypeOptions = (systemTypeId, areaId, groupId, shattered = false) => {
|
||||
let areaIds = [areaId];
|
||||
|
||||
if(
|
||||
systemTypeId === 1 && shattered &&
|
||||
[1, 2, 3, 4, 5, 6].includes(areaId) &&
|
||||
[1, 2, 3].includes(groupId) // Combat, Relic, Data
|
||||
){
|
||||
areaIds = [areaId - 1, areaId, areaId + 1].filter(areaId => areaId >= 1 && areaId <= 6);
|
||||
}else if(
|
||||
systemTypeId === 1 && shattered &&
|
||||
[1, 2, 3, 4, 5, 6].includes(areaId) &&
|
||||
[4, 6].includes(groupId) // Gas, Ore
|
||||
){
|
||||
areaIds = [1, 2, 3, 4, 5, 6, 13];
|
||||
}
|
||||
|
||||
return areaIds;
|
||||
};
|
||||
|
||||
/**
|
||||
* get possible frig holes that could spawn in a system
|
||||
* filtered by "systemTypeId"
|
||||
@@ -238,13 +275,14 @@ define([
|
||||
/**
|
||||
* get all signature types that can exist for a given system
|
||||
* -> result is partially cached
|
||||
* @param systemData
|
||||
* @param systemTypeId
|
||||
* @param areaId
|
||||
* @param groupId
|
||||
* @returns {Array}
|
||||
* @param statics
|
||||
* @param shattered
|
||||
* @returns {[]|*}
|
||||
*/
|
||||
let getAllSignatureNames = (systemData, systemTypeId, areaId, groupId) => {
|
||||
let getSignatureTypeOptions = (systemTypeId, areaId, groupId, {statics = null, shattered = false} = {}) => {
|
||||
systemTypeId = parseInt(systemTypeId || 0);
|
||||
areaId = parseInt(areaId || 0);
|
||||
groupId = parseInt(groupId || 0);
|
||||
@@ -255,18 +293,23 @@ define([
|
||||
return newSelectOptions;
|
||||
}
|
||||
|
||||
let cacheKey = [systemTypeId, areaId, groupId].join('_');
|
||||
// check if sig types require more than one 'areaId' to be checked
|
||||
let areaIds = getAreaIdsForSignatureTypeOptions(systemTypeId, areaId, groupId, shattered);
|
||||
|
||||
let cacheKey = [systemTypeId, ...areaIds, groupId].join('_');
|
||||
|
||||
newSelectOptions = sigTypeOptionsCache.getOrDefault(cacheKey, []);
|
||||
|
||||
// check for cached signature names
|
||||
if(sigNameCache.hasOwnProperty( cacheKey )){
|
||||
if(newSelectOptions.length){
|
||||
// cached signatures do not include static WHs!
|
||||
// -> ".slice(0)" creates copy
|
||||
newSelectOptions = sigNameCache[cacheKey].slice(0);
|
||||
newSelectOptions = newSelectOptions.slice(0);
|
||||
newSelectOptionsCount = getOptionsCount('children', newSelectOptions);
|
||||
}else{
|
||||
// get new Options ----------
|
||||
// get all possible "static" signature names by the selected groupId
|
||||
let tempSelectOptions = Util.getAllSignatureNames(systemTypeId, areaId, groupId);
|
||||
let tempSelectOptions = Util.getSignatureTypeNames(systemTypeId, areaIds, groupId);
|
||||
|
||||
// format options into array with objects advantages: keep order, add more options (whs), use optgroup
|
||||
if(tempSelectOptions){
|
||||
@@ -303,7 +346,7 @@ define([
|
||||
frigateHoles.hasOwnProperty(frigKey)
|
||||
){
|
||||
newSelectOptionsCount++;
|
||||
frigateWHData.push( {value: newSelectOptionsCount, text: frigateHoles[frigKey]} );
|
||||
frigateWHData.push({value: newSelectOptionsCount, text: frigateHoles[frigKey]});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +363,7 @@ define([
|
||||
Init.drifterWormholes.hasOwnProperty(drifterKey)
|
||||
){
|
||||
newSelectOptionsCount++;
|
||||
drifterWHData.push( {value: newSelectOptionsCount, text: Init.drifterWormholes[drifterKey]} );
|
||||
drifterWHData.push({value: newSelectOptionsCount, text: Init.drifterWormholes[drifterKey]});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,12 +380,12 @@ define([
|
||||
Init.incomingWormholes.hasOwnProperty(incomingKey)
|
||||
){
|
||||
newSelectOptionsCount++;
|
||||
incomingWHData.push( {value: newSelectOptionsCount, text: Init.incomingWormholes[incomingKey]} );
|
||||
incomingWHData.push({value: newSelectOptionsCount, text: Init.incomingWormholes[incomingKey]});
|
||||
}
|
||||
}
|
||||
|
||||
if(incomingWHData.length > 0){
|
||||
newSelectOptions.push({ text: 'Incoming', children: incomingWHData});
|
||||
newSelectOptions.push({text: 'Incoming', children: incomingWHData});
|
||||
}
|
||||
}else{
|
||||
// groups without "children" (optgroup) should be sorted by "value"
|
||||
@@ -351,20 +394,20 @@ define([
|
||||
}
|
||||
|
||||
// update cache (clone array) -> further manipulation to this array, should not be cached
|
||||
sigNameCache[cacheKey] = newSelectOptions.slice(0);
|
||||
sigTypeOptionsCache.set(cacheKey, newSelectOptions.slice(0));
|
||||
}
|
||||
|
||||
// static wormholes (DO NOT CACHE) (not all C2 WHs have the same statics,...
|
||||
// static wormholes (DO NOT CACHE) (not all C2 WHs have the same statics..)
|
||||
if(groupId === 5){
|
||||
// add static WH(s) for this system
|
||||
if(systemData.statics){
|
||||
if(statics){
|
||||
let staticWHData = [];
|
||||
for(let wormholeName of systemData.statics){
|
||||
for(let wormholeName of statics){
|
||||
let wormholeData = Object.assign({}, Init.wormholes[wormholeName]);
|
||||
let staticWHName = wormholeData.name + ' - ' + wormholeData.security;
|
||||
|
||||
newSelectOptionsCount++;
|
||||
staticWHData.push( {value: newSelectOptionsCount, text: staticWHName} );
|
||||
staticWHData.push({value: newSelectOptionsCount, text: staticWHName});
|
||||
}
|
||||
|
||||
if(staticWHData.length > 0){
|
||||
@@ -382,11 +425,11 @@ define([
|
||||
* @param groupId
|
||||
* @returns {Array}
|
||||
*/
|
||||
let getAllSignatureNamesBySystem = (systemElement, groupId) => {
|
||||
let getSignatureTypeOptionsBySystem = (systemElement, groupId) => {
|
||||
let systemTypeId = systemElement.data('typeId');
|
||||
let areaId = Util.getAreaIdBySecurity(systemElement.data('security'));
|
||||
let systemData = {statics: systemElement.data('statics')};
|
||||
return getAllSignatureNames(systemData, systemTypeId, areaId, groupId);
|
||||
return getSignatureTypeOptions(systemTypeId, areaId, groupId, systemData);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -473,7 +516,7 @@ define([
|
||||
// hide row
|
||||
|
||||
// stop sig counter by adding a stopClass to each <td>, remove padding
|
||||
cellElements.addClass('stopCounter')
|
||||
cellElements.addClass(Counter.config.counterStopClass)
|
||||
.velocity({
|
||||
paddingTop: [0, '4px'],
|
||||
paddingBottom: [0, '4px'],
|
||||
@@ -882,11 +925,11 @@ define([
|
||||
// try to get "typeId" from description string
|
||||
let sigDescriptionLowerCase = sigDescription.toLowerCase();
|
||||
|
||||
let typeOptions = getAllSignatureNames(
|
||||
systemData,
|
||||
let typeOptions = getSignatureTypeOptions(
|
||||
systemData.type.id,
|
||||
Util.getAreaIdBySecurity(systemData.security),
|
||||
sigGroupId
|
||||
sigGroupId,
|
||||
systemData
|
||||
);
|
||||
|
||||
for(let [key, name] of Object.entries(Util.flattenXEditableSelectArray(typeOptions))){
|
||||
@@ -1196,12 +1239,16 @@ define([
|
||||
*/
|
||||
let activateCell = (cell) => {
|
||||
let cellElement = cell.nodes().to$();
|
||||
// NO xEditable
|
||||
cellElement.focus();
|
||||
// check if cell is visible and not e.g. immediately filtered out by a search filter
|
||||
// -> https://github.com/exodus4d/pathfinder/issues/865
|
||||
if(cellElement.is(':visible')){
|
||||
// NO xEditable
|
||||
cellElement.focus();
|
||||
|
||||
if( cellElement.data('editable') ){
|
||||
// cell is xEditable field -> show xEditable form
|
||||
cellElement.editable('show');
|
||||
if( cellElement.data('editable') ){
|
||||
// cell is xEditable field -> show xEditable form
|
||||
cellElement.editable('show');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1594,11 +1641,9 @@ define([
|
||||
},
|
||||
validate: function(value){
|
||||
let msg = false;
|
||||
let mbLength = [...$.trim(value)].length; // unicode beware
|
||||
if(mbLength < 3){
|
||||
msg = 'Id is less than min of "3"';
|
||||
}else if(mbLength > 10){
|
||||
msg = 'Id is more than max of "10"';
|
||||
//let mbLength = [...$.trim(value)].length; // unicode beware
|
||||
if(! value.trimChars().match(/^[a-zA-Z]{3}-\d{3}$/)){
|
||||
msg = 'ID format invalid. E.g.: ABC-123';
|
||||
}
|
||||
|
||||
if(msg){
|
||||
@@ -1717,7 +1762,7 @@ define([
|
||||
title: 'type',
|
||||
type: 'string', // required for sort/filter because initial data type is numeric
|
||||
width: 180,
|
||||
class: [config.tableCellFocusClass].join(' '),
|
||||
class: [config.tableCellFocusClass, config.tableCellTypeClass].join(' '),
|
||||
data: 'typeId',
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
let tableApi = this.api();
|
||||
@@ -1745,18 +1790,18 @@ define([
|
||||
// -> "rowData" param is not current state, values are "on createCell()" state
|
||||
let rowData = tableApi.row($(cell).parents('tr')).data();
|
||||
|
||||
let typeOptions = getAllSignatureNames(
|
||||
systemData,
|
||||
let typeOptions = getSignatureTypeOptions(
|
||||
systemData.type.id,
|
||||
Util.getAreaIdBySecurity(systemData.security),
|
||||
rowData.groupId
|
||||
rowData.groupId,
|
||||
systemData
|
||||
);
|
||||
return typeOptions;
|
||||
},
|
||||
display: function(value, sourceData){
|
||||
let selected = $.fn.editableutils.itemsByValue(value, sourceData);
|
||||
if(selected.length && selected[0].value > 0){
|
||||
$(this).html(FormElement.formatSignatureTypeSelectionData({text: selected[0].text}));
|
||||
$(this).html(FormElement.formatSignatureTypeSelectionData({text: selected[0].text}, undefined, {showWhSizeLabel: true}));
|
||||
}else{
|
||||
$(this).empty();
|
||||
}
|
||||
@@ -1917,16 +1962,7 @@ define([
|
||||
width: 80,
|
||||
className: ['text-right', config.tableCellCounterClass, 'min-screen-d'].join(' '),
|
||||
data: 'updated.updated',
|
||||
defaultContent: '',
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
// highlight cell
|
||||
let diff = Math.floor((new Date()).getTime()) - cellData * 1000;
|
||||
|
||||
// age > 1 day
|
||||
if(diff > 86400000){
|
||||
$(cell).addClass('txt-color txt-color-warning');
|
||||
}
|
||||
}
|
||||
defaultContent: ''
|
||||
},{
|
||||
targets: 8,
|
||||
name: 'info',
|
||||
@@ -1957,7 +1993,7 @@ define([
|
||||
display: (cellData, type, rowData, meta) => {
|
||||
let val = '<i class="fas fa-plus"></i>';
|
||||
if(rowData.id){
|
||||
val = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
|
||||
val = '<i class="fas fa-times txt-color txt-color-redDark"></i>';
|
||||
}
|
||||
return val;
|
||||
}
|
||||
@@ -2096,6 +2132,20 @@ define([
|
||||
$(this).trigger('click');
|
||||
}
|
||||
});
|
||||
},
|
||||
rowCallback: function(){
|
||||
let tableApi = this.api();
|
||||
let time = Math.floor((new Date()).getTime());
|
||||
|
||||
tableApi.cells(null, ['updated:name']).every(function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
|
||||
let cell = this;
|
||||
let node = cell.node();
|
||||
let cellData = cell.data();
|
||||
let diff = time - cellData * 1000;
|
||||
|
||||
// highlight cell: age > 1 day
|
||||
$(node).toggleClass('txt-color txt-color-warning', diff > 86400000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2359,7 +2409,7 @@ define([
|
||||
let getIconByAction = action => {
|
||||
switch(action){
|
||||
case 'add': return 'fa-plus txt-color-green';
|
||||
case 'delete': return 'fa-times txt-color-redDarker';
|
||||
case 'delete': return 'fa-times txt-color-redDark';
|
||||
case 'edit': return 'fa-pen txt-color-orangeDark';
|
||||
case 'undo': return 'fa-undo txt-color-grayLight';
|
||||
case 'sync': return 'fa-exchange-alt txt-color-orangeDark';
|
||||
@@ -2600,6 +2650,7 @@ define([
|
||||
keyNavigation(tableApi, e);
|
||||
});
|
||||
|
||||
|
||||
Counter.initTableCounter(this, ['created:name', 'updated:name']);
|
||||
}
|
||||
};
|
||||
@@ -3303,6 +3354,6 @@ define([
|
||||
updateModule: updateModule,
|
||||
beforeHide: beforeHide,
|
||||
beforeDestroy: beforeDestroy,
|
||||
getAllSignatureNamesBySystem: getAllSignatureNamesBySystem
|
||||
getSignatureTypeOptionsBySystem: getSignatureTypeOptionsBySystem
|
||||
};
|
||||
});
|
||||
223
js/app/util.js
223
js/app/util.js
@@ -4,7 +4,8 @@
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/console',
|
||||
'app/lib/prototypes',
|
||||
'app/lib/console',
|
||||
'conf/system_effect',
|
||||
'conf/signature_type',
|
||||
'bootbox',
|
||||
@@ -19,7 +20,7 @@ define([
|
||||
'bootstrapConfirmation',
|
||||
'bootstrapToggle',
|
||||
'select2'
|
||||
], ($, Init, Con, SystemEffect, SignatureType, bootbox, localforage) => {
|
||||
], ($, Init, Proto, Con, SystemEffect, SignatureType, bootbox, localforage) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -448,7 +449,7 @@ define([
|
||||
* @param recursive
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.destroyTooltip = function(recursive){
|
||||
$.fn.destroyTooltips = function(recursive){
|
||||
return this.each(function(){
|
||||
let element = $(this);
|
||||
let tooltipSelector = '[title]';
|
||||
@@ -554,7 +555,7 @@ define([
|
||||
userData: userData,
|
||||
otherCharacters: () => {
|
||||
return userData.characters.filter((character, i) => {
|
||||
let characterImage = eveImageUrl('character', character.id);
|
||||
let characterImage = eveImageUrl('characters', character.id);
|
||||
// preload image (prevent UI flicker
|
||||
let img= new Image();
|
||||
img.src = characterImage;
|
||||
@@ -742,7 +743,7 @@ define([
|
||||
|
||||
let defaultOptions = {
|
||||
dismissible: true,
|
||||
messageId: 'pf-alert-' + Math.random().toString(36).substring(7),
|
||||
messageId: getRandomString('pf-alert-'),
|
||||
messageTypeClass: messageTypeClass,
|
||||
messageTextClass: messageTextClass,
|
||||
insertElement: 'replace'
|
||||
@@ -856,17 +857,39 @@ define([
|
||||
|
||||
/**
|
||||
* get CCP image URLs for
|
||||
* @param type 'alliance'|'corporation'|'character'|'type'|'render'
|
||||
* @param id
|
||||
* @param resourceType 'alliances'|'corporations'|'characters'|'types'
|
||||
* @param $resourceId
|
||||
* @param size
|
||||
* @param resourceVariant
|
||||
* @returns {boolean}
|
||||
*/
|
||||
let eveImageUrl = (type, id, size = 32) => {
|
||||
let eveImageUrl = (resourceType, $resourceId, size = 32, resourceVariant = undefined) => {
|
||||
let url = false;
|
||||
if(typeof type === 'string' && typeof id === 'number' && typeof size === 'number'){
|
||||
type = type.capitalize();
|
||||
let format = type === 'Character' ? 'jpg' : 'png';
|
||||
url = Init.url.ccpImageServer + '/' + type + '/' + id + '_' + size + '.' + format;
|
||||
if(
|
||||
typeof resourceType === 'string' &&
|
||||
typeof $resourceId === 'number' &&
|
||||
typeof size === 'number'
|
||||
){
|
||||
resourceType = resourceType.toLowerCase();
|
||||
|
||||
if(!resourceVariant){
|
||||
switch(resourceType){
|
||||
// faction icons are on 'corporations' endpoint.. CCP fail?!
|
||||
case 'factions': resourceType = 'corporations'; // jshint ignore:line
|
||||
case 'alliances':
|
||||
case 'corporations': resourceVariant = 'logo'; break;
|
||||
case 'characters': resourceVariant = 'portrait'; break;
|
||||
case 'types': resourceVariant = 'icon'; break;
|
||||
default:
|
||||
console.warn('Invalid resourceType: %o for in eveImageUrl()', resourceType);
|
||||
}
|
||||
}
|
||||
|
||||
url = [Init.url.ccpImageServer, resourceType, $resourceId, resourceVariant].join('/');
|
||||
|
||||
let params = {size: size};
|
||||
let searchParams = new URLSearchParams(params); // jshint ignore:line
|
||||
url += '?' + searchParams.toString();
|
||||
}
|
||||
return url;
|
||||
};
|
||||
@@ -951,85 +974,6 @@ define([
|
||||
*/
|
||||
let initPrototypes = () => {
|
||||
|
||||
/**
|
||||
* Array diff
|
||||
* [1,2,3,4,5].diff([4,5,6]) => [1,2,3]
|
||||
* @param a
|
||||
* @returns {*[]}
|
||||
*/
|
||||
Array.prototype.diff = function(a){
|
||||
return this.filter(i => !a.includes(i));
|
||||
};
|
||||
|
||||
/**
|
||||
* Array intersect
|
||||
* [1,2,3,4,5].intersect([4,5,6]) => [4,5]
|
||||
* @param a
|
||||
* @returns {*[]}
|
||||
*/
|
||||
Array.prototype.intersect = function(a){
|
||||
return this.filter(i => a.includes(i));
|
||||
};
|
||||
|
||||
/**
|
||||
* compares two arrays if all elements in a are also in b
|
||||
* element order is ignored
|
||||
* @param a
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Array.prototype.equalValues = function(a){
|
||||
return this.diff(a).concat(a.diff(this)).length === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* sort array of objects by property name
|
||||
* @param p
|
||||
* @returns {Array.<T>}
|
||||
*/
|
||||
Array.prototype.sortBy = function(p){
|
||||
return this.slice(0).sort((a,b) => {
|
||||
return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* capitalize first letter
|
||||
* @returns {string}
|
||||
*/
|
||||
String.prototype.capitalize = function(){
|
||||
return this.charAt(0).toUpperCase() + this.slice(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* get hash from string
|
||||
* @returns {number}
|
||||
*/
|
||||
String.prototype.hashCode = function(){
|
||||
let hash = 0, i, chr;
|
||||
if(this.length === 0) return hash;
|
||||
for(i = 0; i < this.length; i++){
|
||||
chr = this.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
String.prototype.trimLeftChars = function(charList){
|
||||
if(charList === undefined)
|
||||
charList = '\\s';
|
||||
return this.replace(new RegExp('^[' + charList + ']+'), '');
|
||||
};
|
||||
|
||||
String.prototype.trimRightChars = function(charList){
|
||||
if(charList === undefined)
|
||||
charList = '\\s';
|
||||
return this.replace(new RegExp('[' + charList + ']+$'), '');
|
||||
};
|
||||
|
||||
String.prototype.trimChars = function(charList){
|
||||
return this.trimLeftChars(charList).trimRightChars(charList);
|
||||
};
|
||||
|
||||
initPassiveEvents();
|
||||
};
|
||||
@@ -1559,6 +1503,14 @@ define([
|
||||
return Init.timer[updateKey].CURRENT_DELAY;
|
||||
};
|
||||
|
||||
/**
|
||||
* get a random string
|
||||
* -> e.g. as for Ids
|
||||
* @param prefix
|
||||
* @returns {string}
|
||||
*/
|
||||
let getRandomString = (prefix = 'id_') => prefix + Math.random().toString(36).substring(2,10);
|
||||
|
||||
/**
|
||||
* get date obj with current EVE Server Time.
|
||||
* @returns {Date}
|
||||
@@ -1622,6 +1574,21 @@ define([
|
||||
return parts;
|
||||
};
|
||||
|
||||
/**
|
||||
* format json object with "time parts" into string
|
||||
* @param parts
|
||||
* @returns {string}
|
||||
*/
|
||||
let formatTimeParts = parts => {
|
||||
let label = '';
|
||||
if(parts.days){
|
||||
label += parts.days + 'd ';
|
||||
}
|
||||
label += ('00' + parts.hours).slice(-2);
|
||||
label += ':' + ('00' + parts.min).slice(-2);
|
||||
return label;
|
||||
};
|
||||
|
||||
/**
|
||||
* start time measurement by a unique string identifier
|
||||
* @param timerName
|
||||
@@ -1669,23 +1636,25 @@ define([
|
||||
* @param maxCharLength
|
||||
*/
|
||||
let updateCounter = (field, charCounterElement, maxCharLength) => {
|
||||
let value = field.val();
|
||||
let inputLength = value.length;
|
||||
if(field.length){
|
||||
let value = field.val();
|
||||
let inputLength = value.length;
|
||||
|
||||
// line breaks are 2 characters!
|
||||
let newLines = value.match(/(\r\n|\n|\r)/g);
|
||||
let addition = 0;
|
||||
if(newLines != null){
|
||||
addition = newLines.length;
|
||||
}
|
||||
inputLength += addition;
|
||||
// line breaks are 2 characters!
|
||||
let newLines = value.match(/(\r\n|\n|\r)/g);
|
||||
let addition = 0;
|
||||
if(newLines != null){
|
||||
addition = newLines.length;
|
||||
}
|
||||
inputLength += addition;
|
||||
|
||||
charCounterElement.text(maxCharLength - inputLength);
|
||||
charCounterElement.text(maxCharLength - inputLength);
|
||||
|
||||
if(maxCharLength <= inputLength){
|
||||
charCounterElement.toggleClass('txt-color-red', true);
|
||||
}else{
|
||||
charCounterElement.toggleClass('txt-color-red', false);
|
||||
if(maxCharLength <= inputLength){
|
||||
charCounterElement.toggleClass('txt-color-red', true);
|
||||
}else{
|
||||
charCounterElement.toggleClass('txt-color-red', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1819,7 +1788,7 @@ define([
|
||||
let key = 'tabId';
|
||||
let tabId = sessionStorage.getItem(key);
|
||||
if(tabId === null){
|
||||
tabId = Math.random().toString(36).substr(2, 5);
|
||||
tabId = getRandomString();
|
||||
sessionStorage.setItem(key, tabId);
|
||||
}
|
||||
return tabId;
|
||||
@@ -2543,23 +2512,16 @@ define([
|
||||
};
|
||||
|
||||
/**
|
||||
* get Signature names out of global
|
||||
* @param systemTypeId
|
||||
* @param areaId
|
||||
* @param sigGroupId
|
||||
* get signature 'type' options for a systemTypeId
|
||||
* -> areaIds is array! This is used for "Shattered WHs" where e.g.:
|
||||
* Combat/Relic/.. sites from multiple areaIds (C1, C2, C3) can spawn in a C2,...
|
||||
* @param systemTypeId 1 == w-space; 2 == k-space; 3 == a-space
|
||||
* @param areaIds 1 == c1; 2 == c2; 12 == Thera; 13 == Shattered Frig;...
|
||||
* @param sigGroupId 1 == Combat; 2 == Relic; 3 == Data; ...
|
||||
* @returns {{}}
|
||||
*/
|
||||
let getAllSignatureNames = (systemTypeId, areaId, sigGroupId) => {
|
||||
let signatureNames = {};
|
||||
if(
|
||||
SignatureType[systemTypeId] &&
|
||||
SignatureType[systemTypeId][areaId] &&
|
||||
SignatureType[systemTypeId][areaId][sigGroupId]
|
||||
){
|
||||
signatureNames = SignatureType[systemTypeId][areaId][sigGroupId];
|
||||
}
|
||||
|
||||
return signatureNames;
|
||||
let getSignatureTypeNames = (systemTypeId, areaIds, sigGroupId) => {
|
||||
return objCombine(...areaIds.map(areaId => getObjVal(SignatureType, [systemTypeId, areaId, sigGroupId].join('.')) || {}));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -3349,6 +3311,21 @@ define([
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
/**
|
||||
* combines multiple objects into one object
|
||||
* -> removes duplicate values
|
||||
* -> properties are indexed 1, 2,..n
|
||||
* @param objects
|
||||
* @returns {{[p: string]: *}}
|
||||
*/
|
||||
let objCombine = (...objects) => {
|
||||
let combined = objects.reduce((acc, obj) => acc.concatFilter(Object.values(obj)), []);
|
||||
combined.unshift(''); // properties should start at 1 (not 0)
|
||||
combined = Object.assign({}, combined);
|
||||
delete combined[0];
|
||||
return combined;
|
||||
};
|
||||
|
||||
/**
|
||||
* get deep json object value if exists
|
||||
* -> e.g. key = 'first.last.third' string
|
||||
@@ -3483,9 +3460,11 @@ define([
|
||||
initDefaultSelect2Config: initDefaultSelect2Config,
|
||||
initDefaultEditableConfig: initDefaultEditableConfig,
|
||||
getCurrentTriggerDelay: getCurrentTriggerDelay,
|
||||
getRandomString: getRandomString,
|
||||
getServerTime: getServerTime,
|
||||
convertTimestampToServerTime: convertTimestampToServerTime,
|
||||
getTimeDiffParts: getTimeDiffParts,
|
||||
formatTimeParts: formatTimeParts,
|
||||
timeStart: timeStart,
|
||||
timeStop: timeStop,
|
||||
updateCounter: updateCounter,
|
||||
@@ -3515,7 +3494,7 @@ define([
|
||||
getTrueSecClassForSystem: getTrueSecClassForSystem,
|
||||
getStatusInfoForSystem: getStatusInfoForSystem,
|
||||
getSignatureGroupOptions: getSignatureGroupOptions,
|
||||
getAllSignatureNames: getAllSignatureNames,
|
||||
getSignatureTypeNames: getSignatureTypeNames,
|
||||
getAreaIdBySecurity: getAreaIdBySecurity,
|
||||
setCurrentMapUserData: setCurrentMapUserData,
|
||||
getCurrentMapUserData: getCurrentMapUserData,
|
||||
|
||||
256
js/lib/bootstrap-confirmation.js
vendored
256
js/lib/bootstrap-confirmation.js
vendored
@@ -1,256 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap Confirmation v1.0.7
|
||||
* https://github.com/tavicu/bs-confirmation
|
||||
*/
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
//var for check event at body can have only one.
|
||||
var event_body = false;
|
||||
|
||||
// CONFIRMATION PUBLIC CLASS DEFINITION
|
||||
// ===============================
|
||||
var Confirmation = function (element, options) {
|
||||
var that = this;
|
||||
|
||||
this.init('confirmation', element, options);
|
||||
|
||||
if (options.selector) {
|
||||
$(element).on('click.bs.confirmation', options.selector, function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
} else {
|
||||
$(element).on('show.bs.confirmation', function(event) {
|
||||
that.runCallback(that.options.onShow, event, that.$element);
|
||||
|
||||
that.$element.addClass('open');
|
||||
|
||||
if (that.options.singleton) {
|
||||
$(that.options.all_selector).not(that.$element).each(function() {
|
||||
if ($(this).hasClass('open')) {
|
||||
$(this).confirmation('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
}).on('hide.bs.confirmation', function(event) {
|
||||
that.runCallback(that.options.onHide, event, that.$element);
|
||||
|
||||
that.$element.removeClass('open');
|
||||
}).on('shown.bs.confirmation', function(e) {
|
||||
if (!that.isPopout() && !event_body) {
|
||||
return;
|
||||
}
|
||||
|
||||
event_body = $('body').on('click', function (e) {
|
||||
if (that.$element.is(e.target)) return;
|
||||
if (that.$element.has(e.target).length) return;
|
||||
if ($('.popover').has(e.target).length) return;
|
||||
|
||||
that.hide();
|
||||
that.inState.click = false;
|
||||
|
||||
$('body').unbind(e);
|
||||
|
||||
event_body = false;
|
||||
|
||||
return;
|
||||
});
|
||||
}).on('click.bs.confirmation', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!$.fn.popover || !$.fn.tooltip) throw new Error('Confirmation requires popover.js and tooltip.js');
|
||||
|
||||
Confirmation.VERSION = '1.0.7'
|
||||
|
||||
Confirmation.DEFAULTS = $.extend({}, $.fn.popover.Constructor.DEFAULTS, {
|
||||
placement : 'right',
|
||||
title : 'Are you sure?',
|
||||
btnOkClass : 'btn btn-sm btn-danger',
|
||||
btnOkLabel : 'Delete',
|
||||
btnOkIcon : 'glyphicon glyphicon-ok',
|
||||
btnCancelClass : 'btn btn-sm btn-default',
|
||||
btnCancelLabel : 'Cancel',
|
||||
btnCancelIcon : 'glyphicon glyphicon-remove',
|
||||
href : '#',
|
||||
target : '_self',
|
||||
singleton : true,
|
||||
popout : true,
|
||||
onShow : function(event, element) {},
|
||||
onHide : function(event, element) {},
|
||||
onConfirm : function(event, element) {},
|
||||
onCancel : function(event, element) {},
|
||||
template : '<div class="popover"><div class="arrow"></div>'
|
||||
+ '<h3 class="popover-title"></h3>'
|
||||
+ '<div class="popover-content">'
|
||||
+ ' <a data-apply="confirmation">Yes</a>'
|
||||
+ ' <a data-dismiss="confirmation">No</a>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
});
|
||||
|
||||
|
||||
// NOTE: CONFIRMATION EXTENDS popover.js
|
||||
// ================================
|
||||
Confirmation.prototype = $.extend({}, $.fn.popover.Constructor.prototype);
|
||||
|
||||
Confirmation.prototype.constructor = Confirmation;
|
||||
|
||||
Confirmation.prototype.getDefaults = function () {
|
||||
return Confirmation.DEFAULTS;
|
||||
}
|
||||
|
||||
Confirmation.prototype.setContent = function () {
|
||||
var that = this;
|
||||
var $tip = this.tip();
|
||||
var title = this.getTitle();
|
||||
var $btnOk = $tip.find('[data-apply="confirmation"]');
|
||||
var $btnCancel = $tip.find('[data-dismiss="confirmation"]');
|
||||
var options = this.options
|
||||
|
||||
$btnOk.addClass(this.getBtnOkClass())
|
||||
.html(this.getBtnOkLabel())
|
||||
.prepend($('<i></i>').addClass(this.getBtnOkIcon()), " ")
|
||||
.attr('href', this.getHref())
|
||||
.attr('target', this.getTarget())
|
||||
.off('click').on('click', function(event) {
|
||||
that.runCallback(that.options.onConfirm, event, that.$element);
|
||||
|
||||
// If the button is a submit one
|
||||
if (that.$element.attr('type') == 'submit') {
|
||||
var form = that.$element.closest('form');
|
||||
var novalidate = form.attr('novalidate') !== undefined;
|
||||
|
||||
if (novalidate || form[0].checkValidity()) {
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
that.hide();
|
||||
that.inState.click = false;
|
||||
|
||||
that.$element.trigger($.Event('confirm.bs.confirmation'));
|
||||
});
|
||||
|
||||
$btnCancel.addClass(this.getBtnCancelClass())
|
||||
.html(this.getBtnCancelLabel())
|
||||
.prepend($('<i></i>').addClass(this.getBtnCancelIcon()), " ")
|
||||
.off('click').on('click', function(event) {
|
||||
that.runCallback(that.options.onCancel, event, that.$element);
|
||||
|
||||
that.hide();
|
||||
that.inState.click = false;
|
||||
|
||||
that.$element.trigger($.Event('cancel.bs.confirmation'));
|
||||
});
|
||||
|
||||
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title);
|
||||
|
||||
$tip.removeClass('fade top bottom left right in');
|
||||
|
||||
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
|
||||
// this manually by checking the contents.
|
||||
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide();
|
||||
}
|
||||
|
||||
Confirmation.prototype.getBtnOkClass = function () {
|
||||
return this.$element.data('btnOkClass') ||
|
||||
(typeof this.options.btnOkClass == 'function' ? this.options.btnOkClass.call(this, this.$element) : this.options.btnOkClass);
|
||||
}
|
||||
|
||||
Confirmation.prototype.getBtnOkLabel = function () {
|
||||
return this.$element.data('btnOkLabel') ||
|
||||
(typeof this.options.btnOkLabel == 'function' ? this.options.btnOkLabel.call(this, this.$element) : this.options.btnOkLabel);
|
||||
}
|
||||
|
||||
Confirmation.prototype.getBtnOkIcon = function () {
|
||||
return this.$element.data('btnOkIcon') ||
|
||||
(typeof this.options.btnOkIcon == 'function' ? this.options.btnOkIcon.call(this, this.$element) : this.options.btnOkIcon);
|
||||
}
|
||||
|
||||
Confirmation.prototype.getBtnCancelClass = function () {
|
||||
return this.$element.data('btnCancelClass') ||
|
||||
(typeof this.options.btnCancelClass == 'function' ? this.options.btnCancelClass.call(this, this.$element) : this.options.btnCancelClass);
|
||||
}
|
||||
|
||||
Confirmation.prototype.getBtnCancelLabel = function () {
|
||||
return this.$element.data('btnCancelLabel') ||
|
||||
(typeof this.options.btnCancelLabel == 'function' ? this.options.btnCancelLabel.call(this, this.$element) : this.options.btnCancelLabel);
|
||||
}
|
||||
|
||||
Confirmation.prototype.getBtnCancelIcon = function () {
|
||||
return this.$element.data('btnCancelIcon') ||
|
||||
(typeof this.options.btnCancelIcon == 'function' ? this.options.btnCancelIcon.call(this, this.$element) : this.options.btnCancelIcon);
|
||||
}
|
||||
|
||||
Confirmation.prototype.getTitle = function () {
|
||||
return this.$element.data('confirmation-title') ||
|
||||
this.$element.data('title') ||
|
||||
this.$element.attr('title') ||
|
||||
(typeof this.options.title == 'function' ? this.options.title.call(this, this.$element) : this.options.title);
|
||||
}
|
||||
|
||||
Confirmation.prototype.getHref = function () {
|
||||
return this.$element.data('href') ||
|
||||
this.$element.attr('href') ||
|
||||
(typeof this.options.href == 'function' ? this.options.href.call(this, this.$element) : this.options.href);
|
||||
}
|
||||
|
||||
Confirmation.prototype.getTarget = function () {
|
||||
return this.$element.data('target') ||
|
||||
this.$element.attr('target') ||
|
||||
(typeof this.options.target == 'function' ? this.options.target.call(this, this.$element) : this.options.target);
|
||||
}
|
||||
|
||||
Confirmation.prototype.isPopout = function () {
|
||||
var popout = this.$element.data('popout') ||
|
||||
(typeof this.options.popout == 'function' ? this.options.popout.call(this, this.$element) : this.options.popout);
|
||||
|
||||
if (popout == 'false') popout = false;
|
||||
|
||||
return popout
|
||||
}
|
||||
|
||||
Confirmation.prototype.runCallback = function (callback, event, element) {
|
||||
if (typeof callback == 'function') {
|
||||
callback.call(this, event, element);
|
||||
} else if (typeof callback == 'string') {
|
||||
eval(callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CONFIRMATION PLUGIN DEFINITION
|
||||
// =========================
|
||||
var old = $.fn.confirmation;
|
||||
|
||||
$.fn.confirmation = function (option) {
|
||||
var that = this;
|
||||
|
||||
return this.each(function () {
|
||||
var $this = $(this);
|
||||
var data = $this.data('bs.confirmation');
|
||||
var options = typeof option == 'object' && option;
|
||||
|
||||
options = options || {};
|
||||
options.all_selector = that.selector;
|
||||
|
||||
if (!data && option == 'destroy') return;
|
||||
if (!data) $this.data('bs.confirmation', (data = new Confirmation(this, options)));
|
||||
if (typeof option == 'string') data[option]();
|
||||
});
|
||||
}
|
||||
|
||||
$.fn.confirmation.Constructor = Confirmation
|
||||
|
||||
|
||||
// CONFIRMATION NO CONFLICT
|
||||
// ===================
|
||||
$.fn.confirmation.noConflict = function () {
|
||||
$.fn.confirmation = old;
|
||||
|
||||
return this;
|
||||
}
|
||||
}(jQuery);
|
||||
5
js/lib/bootstrap-confirmation.min.js
vendored
Normal file
5
js/lib/bootstrap-confirmation.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
js/lib/easytimer.min.js
vendored
Normal file
7
js/lib/easytimer.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,358 +0,0 @@
|
||||
/*
|
||||
@title:
|
||||
Drag to Select
|
||||
|
||||
@version:
|
||||
1.1
|
||||
|
||||
@author:
|
||||
Andreas Lagerkvist
|
||||
|
||||
@date:
|
||||
2009-04-06
|
||||
|
||||
@url:
|
||||
http://andreaslagerkvist.com/jquery/drag-to-select/
|
||||
|
||||
@license:
|
||||
http://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
@copyright:
|
||||
2008 Andreas Lagerkvist (andreaslagerkvist.com)
|
||||
|
||||
@requires:
|
||||
jquery, jquery.dragToSelect.css
|
||||
|
||||
@does:
|
||||
Use this plug-in to allow your users to select certain elements by dragging a "select box". Works very similar to how you can drag-n-select files and folders in most OS:es.
|
||||
|
||||
@howto:
|
||||
$('#my-files').dragToSelect(selectables: 'li'); would make every li in the #my-files-element selectable by dragging. The li:s will recieve a "selected"-class when they are within range of the select box when user drops.
|
||||
|
||||
Make sure a parent-element of the selectables has position: relative as well as overflow: auto or scroll.
|
||||
|
||||
@exampleHTML:
|
||||
<ul>
|
||||
<li><img src="http://exscale.se/__files/3d/lamp-and-mates/lamp-and-mates-01_small.jpg" alt="Lamp and Mates" /></li>
|
||||
<li><img src="http://exscale.se/__files/3d/stugan-winter_small.jpg" alt="The Cottage - Winter time" /></li>
|
||||
<li><img src="http://exscale.se/__files/3d/ps2_small.jpg" alt="PS2" /></li>
|
||||
</ul>
|
||||
|
||||
@exampleJS:
|
||||
$('#jquery-drag-to-select-example').dragToSelect({
|
||||
selectables: 'li',
|
||||
onHide: function () {
|
||||
alert($('#jquery-drag-to-select-example li.selected').length + ' selected');
|
||||
}
|
||||
});
|
||||
*/
|
||||
$.fn.dragToSelect = function (conf) {
|
||||
var c = typeof(conf) == 'object' ? conf : {};
|
||||
|
||||
// Config
|
||||
var config = $.extend({
|
||||
className: 'pf-map-drag-to-select',
|
||||
activeClass: 'active',
|
||||
disabledClass: 'disabled',
|
||||
selectedClass: 'pf-system-selected',
|
||||
ignoreLockedClass: 'pf-system-locked', // do not select locked systems
|
||||
ignoreVisibleClass: 'pf-system-hidden', // do not select invisible systems
|
||||
scrollTH: 10,
|
||||
percentCovered: 25,
|
||||
selectables: false,
|
||||
autoScroll: false,
|
||||
selectOnMove: false,
|
||||
onShow: function () {return true;},
|
||||
onHide: function () {return true;},
|
||||
onRefresh: function () {return true;}
|
||||
}, c);
|
||||
|
||||
var realParent = $(this);
|
||||
var parent = realParent;
|
||||
|
||||
// container for lasso element
|
||||
// -> the only reason for NOT using the .pf-map is because of the zoom [scale()] feature or .pf-map
|
||||
var lassoContainer = realParent.parent();
|
||||
|
||||
var animationFrameId;
|
||||
var mouseIsDown = false;
|
||||
var lastMousePosition = { x: 0, y: 0 };
|
||||
|
||||
// deselected items
|
||||
var deselectedItems = $();
|
||||
|
||||
/*
|
||||
do {
|
||||
if (/auto|scroll|hidden/.test(parent.css('overflow'))) {
|
||||
break;
|
||||
}
|
||||
parent = parent.parent();
|
||||
} while (parent[0].parentNode);
|
||||
*/
|
||||
// Does user want to disable dragToSelect
|
||||
if (conf == 'disable') {
|
||||
parent.addClass(config.disabledClass);
|
||||
return this;
|
||||
}
|
||||
else if (conf == 'enable') {
|
||||
parent.removeClass(config.disabledClass);
|
||||
return this;
|
||||
}
|
||||
|
||||
var parentDim = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 10,
|
||||
height: 10
|
||||
};
|
||||
|
||||
// set parent dimensions
|
||||
// -> should be updated in case of left/right menu is open
|
||||
var setParentDimensions = (parent) => {
|
||||
var parentOffset = parent.offset();
|
||||
parentDim = {
|
||||
left: parentOffset.left,
|
||||
top: parentOffset.top,
|
||||
width: parent.width(),
|
||||
height: parent.height()
|
||||
};
|
||||
}
|
||||
|
||||
setParentDimensions(parent);
|
||||
|
||||
// Current origin of select box
|
||||
var selectBoxOrigin = {
|
||||
left: 0,
|
||||
top: 0
|
||||
};
|
||||
|
||||
// Create select box
|
||||
var selectBox = $('<div>')
|
||||
.appendTo(lassoContainer)
|
||||
.attr('class', config.className)
|
||||
.css('position', 'absolute');
|
||||
|
||||
// Shows the select box
|
||||
var showSelectBox = function (e) {
|
||||
if (parent.is('.' + config.disabledClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectBoxOrigin.left = e.pageX - parentDim.left + parent[0].scrollLeft;
|
||||
selectBoxOrigin.top = e.pageY - parentDim.top + parent[0].scrollTop;
|
||||
|
||||
var css = {
|
||||
left: selectBoxOrigin.left + 'px',
|
||||
top: selectBoxOrigin.top + 'px',
|
||||
width: '1px',
|
||||
height: '1px'
|
||||
};
|
||||
selectBox.addClass(config.activeClass).css(css);
|
||||
|
||||
config.onShow();
|
||||
};
|
||||
|
||||
// Refreshes the select box dimensions and possibly position
|
||||
var refreshSelectBox = function () {
|
||||
var refreshed = false;
|
||||
|
||||
if (!selectBox.is('.' + config.activeClass) || parent.is('.' + config.disabledClass)) {
|
||||
return refreshed;
|
||||
}
|
||||
|
||||
var left = lastMousePosition.x - parentDim.left + parent[0].scrollLeft;
|
||||
var top = lastMousePosition.y - parentDim.top + parent[0].scrollTop;
|
||||
var tempWidth = selectBoxOrigin.left - left;
|
||||
var tempHeight = selectBoxOrigin.top - top;
|
||||
|
||||
let newLeft = selectBoxOrigin.left;// - leftScroll;
|
||||
let newTop = selectBoxOrigin.top;// - topScroll;
|
||||
var newWidth = left - selectBoxOrigin.left;
|
||||
var newHeight = top - selectBoxOrigin.top;
|
||||
|
||||
if(newWidth < 0){
|
||||
newLeft = newLeft - tempWidth;
|
||||
newWidth = newWidth * -1;
|
||||
}
|
||||
|
||||
if(newHeight < 0){
|
||||
newTop = newTop - tempHeight;
|
||||
newHeight = newHeight * -1;
|
||||
}
|
||||
|
||||
// check if dimension has changed -> save performance
|
||||
var dimensionHash = [newWidth, newHeight].join('_');
|
||||
|
||||
if(selectBox.data('dimension-hash') !== dimensionHash){
|
||||
selectBox.data('dimension-hash', dimensionHash);
|
||||
var css = {
|
||||
left: newLeft + 'px',
|
||||
top: newTop + 'px',
|
||||
width: newWidth + 'px',
|
||||
height: newHeight + 'px'
|
||||
};
|
||||
|
||||
selectBox.css(css);
|
||||
config.onRefresh();
|
||||
refreshed = true;
|
||||
}
|
||||
|
||||
return refreshed;
|
||||
};
|
||||
|
||||
// Hides the select box
|
||||
var hideSelectBox = function () {
|
||||
if (!selectBox.is('.' + config.activeClass) || parent.is('.' + config.disabledClass)) {
|
||||
return;
|
||||
}
|
||||
if (config.onHide(selectBox, deselectedItems) !== false) {
|
||||
selectBox.removeClass(config.activeClass);
|
||||
}
|
||||
};
|
||||
|
||||
// Selects all the elements in the select box's range
|
||||
var selectElementsInRange = function () {
|
||||
if (!selectBox.is('.' + config.activeClass) || parent.is('.' + config.disabledClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selectables = realParent.find(config.selectables + ':not(.' + config.ignoreLockedClass + ')'+ ':not(.' + config.ignoreVisibleClass + ')');
|
||||
var selectBoxOffset = selectBox.offset();
|
||||
var selectBoxDim = {
|
||||
left: selectBoxOffset.left,
|
||||
top: selectBoxOffset.top,
|
||||
width: selectBox.width(),
|
||||
height: selectBox.height()
|
||||
};
|
||||
|
||||
selectables.each(function (i) {
|
||||
var el = $(this);
|
||||
var elOffset = el.offset();
|
||||
var elDim = {
|
||||
left: elOffset.left,
|
||||
top: elOffset.top,
|
||||
width: el.width(),
|
||||
height: el.height()
|
||||
};
|
||||
|
||||
if (percentCovered(selectBoxDim, elDim) > config.percentCovered) {
|
||||
el.addClass(config.selectedClass);
|
||||
// remove element from "deselected" elements (e.g on add -> remove -> add scenario)
|
||||
deselectedItems = deselectedItems.not(el);
|
||||
}else {
|
||||
if(el.hasClass(config.selectedClass)){
|
||||
el.removeClass(config.selectedClass);
|
||||
deselectedItems = deselectedItems.add(el);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// Returns the amount (in %) that dim1 covers dim2
|
||||
var percentCovered = function (dim1, dim2) {
|
||||
// The whole thing is covering the whole other thing
|
||||
if (
|
||||
(dim1.left <= dim2.left) &&
|
||||
(dim1.top <= dim2.top) &&
|
||||
((dim1.left + dim1.width) >= (dim2.left + dim2.width)) &&
|
||||
((dim1.top + dim1.height) > (dim2.top + dim2.height))
|
||||
) {
|
||||
return 100;
|
||||
}
|
||||
// Only parts may be covered, calculate percentage
|
||||
else {
|
||||
dim1.right = dim1.left + dim1.width;
|
||||
dim1.bottom = dim1.top + dim1.height;
|
||||
dim2.right = dim2.left + dim2.width;
|
||||
dim2.bottom = dim2.top + dim2.height;
|
||||
|
||||
var l = Math.max(dim1.left, dim2.left);
|
||||
var r = Math.min(dim1.right, dim2.right);
|
||||
var t = Math.max(dim1.top, dim2.top);
|
||||
var b = Math.min(dim1.bottom, dim2.bottom);
|
||||
|
||||
if (b >= t && r >= l) {
|
||||
/* $('<div/>').appendTo(document.body).css({
|
||||
background: 'red',
|
||||
position: 'absolute',
|
||||
left: l + 'px',
|
||||
top: t + 'px',
|
||||
width: (r - l) + 'px',
|
||||
height: (b - t) + 'px',
|
||||
zIndex: 100
|
||||
}); */
|
||||
|
||||
var percent = (((r - l) * (b - t)) / (dim2.width * dim2.height)) * 100;
|
||||
|
||||
// alert(percent + '% covered')
|
||||
|
||||
return percent;
|
||||
}
|
||||
}
|
||||
// Nothing covered, return 0
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Event functions ----------------------------------------------------------------------------
|
||||
var mousemoveCallback = function(){
|
||||
if(mouseIsDown){
|
||||
var refreshed = refreshSelectBox();
|
||||
|
||||
if(refreshed && config.selectables && config.selectOnMove){
|
||||
selectElementsInRange();
|
||||
}
|
||||
|
||||
// recursive re-call on next render
|
||||
animationFrameId = requestAnimationFrame(mousemoveCallback);
|
||||
}
|
||||
}
|
||||
|
||||
var mouseupCallback = function(){
|
||||
if(config.selectables){
|
||||
selectElementsInRange();
|
||||
}
|
||||
hideSelectBox();
|
||||
|
||||
// stop animation frame and "reset" to default
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
mouseIsDown = false;
|
||||
// reset deselected item array
|
||||
deselectedItems = $();
|
||||
}
|
||||
|
||||
// Do the right stuff then return this --------------------------------------------------------
|
||||
|
||||
selectBox.mousemove(function(e){
|
||||
setParentDimensions(parent);
|
||||
lastMousePosition.x = e.pageX;
|
||||
lastMousePosition.y = e.pageY;
|
||||
e.preventDefault();
|
||||
}).mouseup(mouseupCallback);
|
||||
|
||||
parent.mousedown(function(e){
|
||||
if(
|
||||
e.which === 1 && // left mouse down
|
||||
e.target === realParent[0] // prevent while dragging a system :)
|
||||
){
|
||||
// Make sure user isn't clicking scrollbar (or disallow clicks far to the right actually)
|
||||
if ((e.pageX + 20) > $(document.body).width()) {
|
||||
return;
|
||||
}
|
||||
|
||||
showSelectBox(e);
|
||||
mouseIsDown = true;
|
||||
animationFrameId = requestAnimationFrame(mousemoveCallback);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
}).mousemove(function(e){
|
||||
setParentDimensions(parent);
|
||||
lastMousePosition.x = e.pageX;
|
||||
lastMousePosition.y = e.pageY;
|
||||
e.preventDefault();
|
||||
}).mouseup(mouseupCallback);
|
||||
|
||||
// Be nice
|
||||
return this;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
/*!
|
||||
* hoverIntent v1.9.0 // 2017.09.01 // jQuery v1.7.0+
|
||||
* http://briancherne.github.io/jquery-hoverIntent/
|
||||
*
|
||||
* You may use hoverIntent under the terms of the MIT license. Basically that
|
||||
* means you are free to use hoverIntent as long as this header is left intact.
|
||||
* Copyright 2007-2017 Brian Cherne
|
||||
*/
|
||||
!function(factory){"use strict";"function"==typeof define&&define.amd?define(["jquery"],factory):jQuery&&!jQuery.fn.hoverIntent&&factory(jQuery)}(function($){"use strict";var cX,cY,_cfg={interval:100,sensitivity:6,timeout:0},INSTANCE_COUNT=0,track=function(ev){cX=ev.pageX,cY=ev.pageY},compare=function(ev,$el,s,cfg){if(Math.sqrt((s.pX-cX)*(s.pX-cX)+(s.pY-cY)*(s.pY-cY))<cfg.sensitivity)return $el.off(s.event,track),delete s.timeoutId,s.isActive=!0,ev.pageX=cX,ev.pageY=cY,delete s.pX,delete s.pY,cfg.over.apply($el[0],[ev]);s.pX=cX,s.pY=cY,s.timeoutId=setTimeout(function(){compare(ev,$el,s,cfg)},cfg.interval)},delay=function(ev,$el,s,out){return delete $el.data("hoverIntent")[s.id],out.apply($el[0],[ev])};$.fn.hoverIntent=function(handlerIn,handlerOut,selector){var instanceId=INSTANCE_COUNT++,cfg=$.extend({},_cfg);$.isPlainObject(handlerIn)?(cfg=$.extend(cfg,handlerIn),$.isFunction(cfg.out)||(cfg.out=cfg.over)):cfg=$.isFunction(handlerOut)?$.extend(cfg,{over:handlerIn,out:handlerOut,selector:selector}):$.extend(cfg,{over:handlerIn,out:handlerIn,selector:handlerOut});var handleHover=function(e){var ev=$.extend({},e),$el=$(this),hoverIntentData=$el.data("hoverIntent");hoverIntentData||$el.data("hoverIntent",hoverIntentData={});var state=hoverIntentData[instanceId];state||(hoverIntentData[instanceId]=state={id:instanceId}),state.timeoutId&&(state.timeoutId=clearTimeout(state.timeoutId));var mousemove=state.event="mousemove.hoverIntent.hoverIntent"+instanceId;if("mouseenter"===e.type){if(state.isActive)return;state.pX=ev.pageX,state.pY=ev.pageY,$el.off(mousemove,track).on(mousemove,track),state.timeoutId=setTimeout(function(){compare(ev,$el,state,cfg)},cfg.interval)}else{if(!state.isActive)return;$el.off(mousemove,track),state.timeoutId=setTimeout(function(){delay(ev,$el,state,cfg.out)},cfg.timeout)}};return this.on({"mouseenter.hoverIntent":handleHover,"mouseleave.hoverIntent":handleHover},cfg.selector)}});
|
||||
12
js/lib/jquery.peity.min.js
vendored
12
js/lib/jquery.peity.min.js
vendored
@@ -1,13 +1,7 @@
|
||||
// Peity jQuery plugin version 3.2.1
|
||||
// (c) 2016 Ben Pickles
|
||||
// Peity jQuery plugin version 3.3.0
|
||||
// (c) 2018 Ben Pickles
|
||||
//
|
||||
// http://benpickles.github.io/peity
|
||||
//
|
||||
// Released under MIT license.
|
||||
(function(k,w,h,v){var d=k.fn.peity=function(a,b){y&&this.each(function(){var e=k(this),c=e.data("_peity");c?(a&&(c.type=a),k.extend(c.opts,b)):(c=new x(e,a,k.extend({},d.defaults[a],e.data("peity"),b)),e.change(function(){c.draw()}).data("_peity",c));c.draw()});return this},x=function(a,b,e){this.$el=a;this.type=b;this.opts=e},o=x.prototype,q=o.svgElement=function(a,b){return k(w.createElementNS("http://www.w3.org/2000/svg",a)).attr(b)},y="createElementNS"in w&&q("svg",{})[0].createSVGRect;o.draw=
|
||||
function(){var a=this.opts;d.graphers[this.type].call(this,a);a.after&&a.after.call(this,a)};o.fill=function(){var a=this.opts.fill;return k.isFunction(a)?a:function(b,e){return a[e%a.length]}};o.prepare=function(a,b){this.$svg||this.$el.hide().after(this.$svg=q("svg",{"class":"peity"}));return this.$svg.empty().data("peity",this).attr({height:b,width:a})};o.values=function(){return k.map(this.$el.text().split(this.opts.delimiter),function(a){return parseFloat(a)})};d.defaults={};d.graphers={};d.register=
|
||||
function(a,b,e){this.defaults[a]=b;this.graphers[a]=e};d.register("pie",{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(a){if(!a.delimiter){var b=this.$el.text().match(/[^0-9\.]/);a.delimiter=b?b[0]:","}b=k.map(this.values(),function(a){return 0<a?a:0});if("/"==a.delimiter)var e=b[0],b=[e,h.max(0,b[1]-e)];for(var c=0,e=b.length,t=0;c<e;c++)t+=b[c];t||(e=2,t=1,b=[0,1]);var l=2*a.radius,l=this.prepare(a.width||l,a.height||l),c=l.width(),f=l.height(),j=c/2,d=f/2,f=h.min(j,d),a=a.innerRadius;
|
||||
"donut"==this.type&&!a&&(a=0.5*f);for(var r=h.PI,s=this.fill(),g=this.scale=function(a,b){var c=a/t*r*2-r/2;return[b*h.cos(c)+j,b*h.sin(c)+d]},m=0,c=0;c<e;c++){var u=b[c],i=u/t;if(0!=i){if(1==i)if(a)var i=j-0.01,p=d-f,n=d-a,i=q("path",{d:["M",j,p,"A",f,f,0,1,1,i,p,"L",i,n,"A",a,a,0,1,0,j,n].join(" ")});else i=q("circle",{cx:j,cy:d,r:f});else p=m+u,n=["M"].concat(g(m,f),"A",f,f,0,0.5<i?1:0,1,g(p,f),"L"),a?n=n.concat(g(p,a),"A",a,a,0,0.5<i?1:0,0,g(m,a)):n.push(j,d),m+=u,i=q("path",{d:n.join(" ")});
|
||||
i.attr("fill",s.call(this,u,c,b));l.append(i)}}});d.register("donut",k.extend(!0,{},d.defaults.pie),function(a){d.graphers.pie.call(this,a)});d.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(a){var b=this.values();1==b.length&&b.push(b[0]);for(var e=h.max.apply(h,a.max==v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=a.strokeWidth,f=d.width(),j=d.height()-l,k=e-c,e=this.x=function(a){return a*
|
||||
(f/(b.length-1))},r=this.y=function(a){var b=j;k&&(b-=(a-c)/k*j);return b+l/2},s=r(h.max(c,0)),g=[0,s],m=0;m<b.length;m++)g.push(e(m),r(b[m]));g.push(f,s);a.fill&&d.append(q("polygon",{fill:a.fill,points:g.join(" ")}));l&&d.append(q("polyline",{fill:"none",points:g.slice(2,g.length-2).join(" "),stroke:a.stroke,"stroke-width":l,"stroke-linecap":"square"}))});d.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:0.1,width:32},function(a){for(var b=this.values(),e=h.max.apply(h,a.max==
|
||||
v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=d.width(),f=d.height(),j=e-c,a=a.padding,k=this.fill(),r=this.x=function(a){return a*l/b.length},s=this.y=function(a){return f-(j?(a-c)/j*f:1)},g=0;g<b.length;g++){var m=r(g+a),u=r(g+1-a)-m,i=b[g],p=s(i),n=p,o;j?0>i?n=s(h.min(e,0)):p=s(h.max(c,0)):o=1;o=p-n;0==o&&(o=1,0<e&&j&&n--);d.append(q("rect",{fill:k.call(this,i,g,b),x:m,y:n,width:u,height:o}))}})})(jQuery,document,Math);
|
||||
!function(t,i,e,n){var a=t.fn.peity=function(i,e){return l&&this.each(function(){var n=t(this),h=n.data("_peity");h?(i&&(h.type=i),t.extend(h.opts,e)):(h=new r(n,i,t.extend({},a.defaults[i],n.data("peity"),e)),n.change(function(){h.draw()}).data("_peity",h)),h.draw()}),this},r=function(t,i,e){this.$el=t,this.type=i,this.opts=e},h=r.prototype,s=h.svgElement=function(e,n){return t(i.createElementNS("http://www.w3.org/2000/svg",e)).attr(n)},l="createElementNS"in i&&s("svg",{})[0].createSVGRect;h.draw=function(){var t=this.opts;a.graphers[this.type].call(this,t),t.after&&t.after.call(this,t)},h.fill=function(){var i=this.opts.fill;return t.isFunction(i)?i:function(t,e){return i[e%i.length]}},h.prepare=function(t,i){return this.$svg||this.$el.hide().after(this.$svg=s("svg",{class:"peity"})),this.$svg.empty().data("_peity",this).attr({height:i,width:t})},h.values=function(){return t.map(this.$el.text().split(this.opts.delimiter),function(t){return parseFloat(t)})},a.defaults={},a.graphers={},a.register=function(t,i,e){this.defaults[t]=i,this.graphers[t]=e},a.register("pie",{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(i){if(!i.delimiter){var n=this.$el.text().match(/[^0-9\.]/);i.delimiter=n?n[0]:","}var a=t.map(this.values(),function(t){return t>0?t:0});if("/"==i.delimiter){var r=a[0],h=a[1];a=[r,e.max(0,h-r)]}for(var l=0,p=a.length,o=0;l<p;l++)o+=a[l];o||(p=2,o=1,a=[0,1]);var f=2*i.radius,c=this.prepare(i.width||f,i.height||f),u=c.width()/2,d=c.height()/2,g=e.min(u,d),v=i.innerRadius;"donut"!=this.type||v||(v=.5*g);var m=e.PI,y=this.fill(),w=this.scale=function(t,i){var n=t/o*m*2-m/2;return[i*e.cos(n)+u,i*e.sin(n)+d]},x=0;for(l=0;l<p;l++){var k,$=a[l],j=$/o;if(0!=j){if(1==j)if(v){var A=u-.01,E=d-g,F=d-v;k=s("path",{d:["M",u,E,"A",g,g,0,1,1,A,E,"L",A,F,"A",v,v,0,1,0,u,F].join(" "),"data-value":$})}else k=s("circle",{cx:u,cy:d,"data-value":$,r:g});else{var M=x+$,S=["M"].concat(w(x,g),"A",g,g,0,j>.5?1:0,1,w(M,g),"L");v?S=S.concat(w(M,v),"A",v,v,0,j>.5?1:0,0,w(x,v)):S.push(u,d),x+=$,k=s("path",{d:S.join(" "),"data-value":$})}k.attr("fill",y.call(this,$,l,a)),c.append(k)}}}),a.register("donut",t.extend(!0,{},a.defaults.pie),function(t){a.graphers.pie.call(this,t)}),a.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(t){var i=this.values();1==i.length&&i.push(i[0]);for(var a=e.max.apply(e,t.max==n?i:i.concat(t.max)),r=e.min.apply(e,t.min==n?i:i.concat(t.min)),h=this.prepare(t.width,t.height),l=t.strokeWidth,p=h.width(),o=h.height()-l,f=a-r,c=this.x=function(t){return t*(p/(i.length-1))},u=this.y=function(t){var i=o;return f&&(i-=(t-r)/f*o),i+l/2},d=u(e.max(r,0)),g=[0,d],v=0;v<i.length;v++)g.push(c(v),u(i[v]));g.push(p,d),t.fill&&h.append(s("polygon",{fill:t.fill,points:g.join(" ")})),l&&h.append(s("polyline",{fill:"none",points:g.slice(2,g.length-2).join(" "),stroke:t.stroke,"stroke-width":l,"stroke-linecap":"square"}))}),a.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:.1,width:32},function(t){for(var i=this.values(),a=e.max.apply(e,t.max==n?i:i.concat(t.max)),r=e.min.apply(e,t.min==n?i:i.concat(t.min)),h=this.prepare(t.width,t.height),l=h.width(),p=h.height(),o=a-r,f=t.padding,c=this.fill(),u=this.x=function(t){return t*l/i.length},d=this.y=function(t){return p-(o?(t-r)/o*p:1)},g=0;g<i.length;g++){var v,m=u(g+f),y=u(g+1-f)-m,w=i[g],x=d(w),k=x,$=x;o?w<0?k=d(e.min(a,0)):$=d(e.max(r,0)):v=1,0==(v=$-k)&&(v=1,a>0&&o&&k--),h.append(s("rect",{"data-value":w,fill:c.call(this,w,g,i),x:m,y:k,width:y,height:v}))}})}(jQuery,document,Math);
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"gulp-requirejs-optimize": "1.3.x",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"jshint": "^2.10.2",
|
||||
"jshint": "^2.10.3",
|
||||
"jshint-stylish": "^2.x.x",
|
||||
"lodash.padend": "4.6.x",
|
||||
"node-notifier": "^5.4.0",
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
public/css/v1.5.5/pathfinder.css.br
Normal file
BIN
public/css/v1.5.5/pathfinder.css.br
Normal file
Binary file not shown.
7
public/css/v1.5.5/pathfinder.css.map
Normal file
7
public/css/v1.5.5/pathfinder.css.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
"use strict";var mainScriptPath=document.body.getAttribute("data-script"),jsBaseUrl=document.body.getAttribute("data-js-path");requirejs.config({baseUrl:"js",paths:{conf:"app/conf",dialog:"app/ui/dialog",layout:"app/ui/layout",module:"app/ui/module",templates:"../../templates",img:"../../img",login:"./app/login",mappage:"./app/mappage",setup:"./app/setup",admin:"./app/admin",notification:"./app/notification",jquery:"lib/jquery-3.4.1.min",bootstrap:"lib/bootstrap.min",text:"lib/requirejs/text",mustache:"lib/mustache.min",localForage:"lib/localforage.min",velocity:"lib/velocity.min",velocityUI:"lib/velocity.ui.min",slidebars:"lib/slidebars",jsPlumb:"lib/jsplumb",farahey:"lib/farahey",customScrollbar:"lib/jquery.mCustomScrollbar.min",mousewheel:"lib/jquery.mousewheel.min",xEditable:"lib/bootstrap-editable.min",morris:"lib/morris.min",raphael:"lib/raphael.min",bootbox:"lib/bootbox.min",easyPieChart:"lib/jquery.easypiechart.min",peityInlineChart:"lib/jquery.peity.min",dragToSelect:"lib/jquery.dragToSelect",hoverIntent:"lib/jquery.hoverIntent.min",select2:"lib/select2.min",validator:"lib/validator.min",lazylinepainter:"lib/jquery.lazylinepainter-1.5.1.min",blueImpGallery:"lib/blueimp-gallery",blueImpGalleryHelper:"lib/blueimp-helper",blueImpGalleryBootstrap:"lib/bootstrap-image-gallery",bootstrapConfirmation:"lib/bootstrap-confirmation",bootstrapToggle:"lib/bootstrap-toggle.min",lazyload:"lib/jquery.lazyload.min",sortable:"lib/sortable.min","summernote.loader":"./app/summernote.loader",summernote:"lib/summernote/summernote.min",easePack:"lib/EasePack.min",tweenLite:"lib/TweenLite.min","datatables.loader":"./app/datatables.loader","datatables.net":"lib/datatables/DataTables-1.10.18/js/jquery.dataTables.min","datatables.net-buttons":"lib/datatables/Buttons-1.5.6/js/dataTables.buttons.min","datatables.net-buttons-html":"lib/datatables/Buttons-1.5.6/js/buttons.html5.min","datatables.net-responsive":"lib/datatables/Responsive-2.2.2/js/dataTables.responsive.min","datatables.net-select":"lib/datatables/Select-1.3.0/js/dataTables.select.min","datatables.plugins.render.ellipsis":"lib/datatables/plugins/render/ellipsis",pnotify:"lib/pnotify/pnotify","pnotify.buttons":"lib/pnotify/pnotify.buttons","pnotify.confirm":"lib/pnotify/pnotify.confirm","pnotify.nonblock":"lib/pnotify/pnotify.nonblock","pnotify.desktop":"lib/pnotify/pnotify.desktop","pnotify.history":"lib/pnotify/pnotify.history","pnotify.callbacks":"lib/pnotify/pnotify.callbacks","pnotify.reference":"lib/pnotify/pnotify.reference"},shim:{bootstrap:{deps:["jquery"]},farahey:{deps:["jsPlumb"]},velocity:{deps:["jquery"]},velocityUI:{deps:["velocity"]},slidebars:{deps:["jquery"]},customScrollbar:{deps:["jquery","mousewheel"]},"datatables.loader":{deps:["jquery"]},"datatables.net":{deps:["jquery"]},"datatables.net-buttons":{deps:["datatables.net"]},"datatables.net-buttons-html":{deps:["datatables.net-buttons"]},"datatables.net-responsive":{deps:["datatables.net"]},"datatables.net-select":{deps:["datatables.net"]},"datatables.plugins.render.ellipsis":{deps:["datatables.net"]},xEditable:{deps:["bootstrap"]},bootbox:{deps:["jquery","bootstrap"],exports:"bootbox"},morris:{deps:["jquery","raphael"],exports:"Morris",init:function(e,t){window.Raphael=t}},pnotify:{deps:["jquery"]},easyPieChart:{deps:["jquery"]},peityInlineChart:{deps:["jquery"]},dragToSelect:{deps:["jquery"]},hoverIntent:{deps:["jquery"]},select2:{deps:["jquery","mousewheel"],exports:"Select2"},validator:{deps:["jquery","bootstrap"]},lazylinepainter:{deps:["jquery","bootstrap"]},blueImpGallery:{deps:["jquery"]},bootstrapConfirmation:{deps:["bootstrap"]},bootstrapToggle:{deps:["jquery"]},lazyload:{deps:["jquery"]},summernote:{deps:["jquery"]}}}),require.config({baseUrl:jsBaseUrl}),requirejs([mainScriptPath]);
|
||||
//# sourceMappingURL=app.js.map
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["app.js"],"names":["mainScriptPath","document","body","getAttribute","jsBaseUrl","requirejs","config","baseUrl","paths","conf","dialog","layout","module","templates","img","login","mappage","setup","admin","notification","jquery","bootstrap","text","mustache","localForage","velocity","velocityUI","slidebars","jsPlumb","farahey","customScrollbar","mousewheel","xEditable","morris","raphael","bootbox","easyPieChart","peityInlineChart","dragToSelect","hoverIntent","select2","validator","lazylinepainter","blueImpGallery","blueImpGalleryHelper","blueImpGalleryBootstrap","bootstrapConfirmation","bootstrapToggle","lazyload","sortable","summernote.loader","summernote","easePack","tweenLite","datatables.loader","datatables.net","datatables.net-buttons","datatables.net-buttons-html","datatables.net-responsive","datatables.net-select","datatables.plugins.render.ellipsis","pnotify","pnotify.buttons","pnotify.confirm","pnotify.nonblock","pnotify.desktop","pnotify.history","pnotify.callbacks","pnotify.reference","shim","deps","exports","init","$","Raphael","window","require"],"mappings":"AAAA,aAGA,IAAIA,eAAiBC,SAASC,KAAKC,aAAa,eAI5CC,UAAYH,SAASC,KAAKC,aAAa,gBAG3CE,UAAUC,QACNC,QAAS,KAETC,OACIC,KAAM,WACNC,OAAQ,gBACRC,OAAQ,gBACRC,OAAQ,gBAERC,UAAW,kBACXC,IAAK,YAGLC,MAAO,cACPC,QAAS,gBACTC,MAAO,cACPC,MAAO,cACPC,aAAc,qBAEdC,OAAQ,uBACRC,UAAW,oBACXC,KAAM,qBACNC,SAAU,mBACVC,YAAa,sBACbC,SAAU,mBACVC,WAAY,sBACZC,UAAW,gBACXC,QAAS,cACTC,QAAS,cACTC,gBAAiB,kCACjBC,WAAY,4BACZC,UAAW,6BACXC,OAAQ,iBACRC,QAAS,kBACTC,QAAS,kBACTC,aAAc,8BACdC,iBAAkB,uBAClBC,aAAc,0BACdC,YAAa,6BACbC,QAAS,kBACTC,UAAW,oBACXC,gBAAiB,uCACjBC,eAAgB,sBAChBC,qBAAsB,qBACtBC,wBAAyB,8BACzBC,sBAAuB,6BACvBC,gBAAiB,2BACjBC,SAAU,0BACVC,SAAU,mBAEVC,oBAAqB,0BACrBC,WAAc,gCAGdC,SAAU,mBACVC,UAAW,oBAGXC,oBAAqB,0BACrBC,iBAAkB,6DAClBC,yBAA0B,yDAC1BC,8BAA+B,oDAC/BC,4BAA6B,+DAC7BC,wBAAyB,uDACzBC,qCAAsC,yCAGtCC,QAAS,sBACTC,kBAAmB,8BACnBC,kBAAmB,8BACnBC,mBAAoB,+BACpBC,kBAAmB,8BACnBC,kBAAmB,8BACnBC,oBAAqB,gCACrBC,oBAAqB,iCAEzBC,MACIhD,WACIiD,MAAO,WAEXzC,SACIyC,MAAO,YAEX7C,UACI6C,MAAO,WAEX5C,YACI4C,MAAO,aAEX3C,WACI2C,MAAO,WAEXxC,iBACIwC,MAAO,SAAU,eAErBhB,qBACIgB,MAAO,WAEXf,kBACIe,MAAO,WAEXd,0BACIc,MAAO,mBAEXb,+BACIa,MAAO,2BAEXZ,6BACIY,MAAO,mBAEXX,yBACIW,MAAO,mBAEXV,sCACIU,MAAO,mBAEXtC,WACIsC,MAAO,cAEXnC,SACImC,MAAO,SAAU,aACjBC,QAAS,WAEbtC,QACIqC,MAAO,SAAU,WACjBC,QAAS,SACTC,KAAM,SAAUC,EAAGC,GACfC,OAAOD,QAAUA,IAGzBb,SACIS,MAAO,WAEXlC,cACIkC,MAAO,WAEXjC,kBACIiC,MAAO,WAEXhC,cACIgC,MAAO,WAEX/B,aACI+B,MAAO,WAEX9B,SACI8B,MAAO,SAAU,cACjBC,QAAS,WAEb9B,WACI6B,MAAO,SAAU,cAErB5B,iBACI4B,MAAO,SAAU,cAErB3B,gBACI2B,MAAO,WAEXxB,uBACIwB,MAAO,cAEXvB,iBACIuB,MAAO,WAEXtB,UACIsB,MAAO,WAEXnB,YACImB,MAAO,cAQnBM,QAAQtE,QACJC,QAASH,YAIbC,WAAYL","file":"app.js","sourceRoot":"/js"}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user