- New "station" data added, closed #858

- Improved system deeplinks. EveEye.com added, closed #859
- Improved cronjob performance for "SovereigntyData" import, closed #853
- Updated static DB dump for `eve_universe.sql`
This commit is contained in:
Mark Friedrich
2019-09-30 19:36:39 +02:00
parent d45fa9b527
commit 1de67f8dbf
88 changed files with 2465 additions and 565 deletions

View File

@@ -14,7 +14,7 @@
"node": true,
// Allow ES6.
"esversion": 6,
"esversion": 7,
/*
* ENFORCING OPTIONS

View File

@@ -59,9 +59,13 @@ deleteStatisticsData = Cron\StatisticsUpdate->deleteStatisticsD
; truncate map history log files
truncateMapHistoryLogFiles = Cron\MapHistory->truncateFiles, @halfHour
; updates small amount of static system data from CCP API
;updateUniverseSystems = Cron\Universe->updateUniverseSystems, @instant
;updateSovereigntyData = Cron\Universe->updateSovereigntyData, @instant
; sync "sovereignty" and "faction warfare" data from CCP´s ESI API
updateSovereigntyData = Cron\Universe->updateSovereigntyData, @halfPastHour
; setup universe DB with static data from ESI
; sync static system data from CCP´s ESI API
; -> Job is WIP!
;updateUniverseSystems = Cron\Universe->updateUniverseSystems, @instant
; bootstrap job for "eve_universe" DB from CCP´s ESI API
; -> Only for development! This job is used to build the initial export/sql/eve_universe.sql
;setup = Cron\Universe->setup, @instant

View File

@@ -176,7 +176,10 @@ class Map extends Controller\AccessController {
// get third party APIs -----------------------------------------------------------------------------------
$return->url = [
'ccpImageServer' => Config::getPathfinderData('api.ccp_image_server'),
'zKillboard' => Config::getPathfinderData('api.z_killboard')
'zKillboard' => Config::getPathfinderData('api.z_killboard'),
'eveeye' => Config::getPathfinderData('api.eveeye'),
'dotlan' => Config::getPathfinderData('api.dotlan'),
'anoik' => Config::getPathfinderData('api.anoik')
];
// Character default config -------------------------------------------------------------------------------

View File

@@ -38,6 +38,7 @@ class System extends AbstractRestController {
$systemData->signatures = $system->getSignaturesData();
$systemData->sigHistory = $system->getSignaturesHistory();
$systemData->structures = $system->getStructuresData();
$systemData->stations = $system->getStationsData();
}
}

View File

@@ -111,7 +111,7 @@ class System extends Controller\AccessController {
}
/**
* set destination for specific systemIds
* set destination for system, station or structure
* @param \Base $f3
* @throws \Exception
*/
@@ -120,25 +120,25 @@ class System extends Controller\AccessController {
$return = (object) [];
$return->error = [];
$return->systemData = [];
$return->destData = [];
if( !empty($postData['systemData'] )){
if(!empty($destData = (array)$postData['destData'])){
$activeCharacter = $this->getCharacter();
$return->clearOtherWaypoints = (bool)$postData['clearOtherWaypoints'];
$return->first = (bool)$postData['first'];
if( $accessToken = $activeCharacter->getAccessToken() ){
if($accessToken = $activeCharacter->getAccessToken()){
$options = [
'clearOtherWaypoints' => $return->clearOtherWaypoints,
'addToBeginning' => $return->first,
];
foreach($postData['systemData'] as $systemData){
$response = $f3->ccpClient()->setWaypoint($systemData['systemId'], $accessToken, $options);
foreach($destData as $data){
$response = $f3->ccpClient()->setWaypoint((int)$data['id'], $accessToken, $options);
if(empty($response)){
$return->systemData[] = $systemData;
$return->destData[] = $data;
}else{
$error = (object) [];
$error->type = 'error';

View File

@@ -161,7 +161,7 @@ class Universe extends Controller {
$system = Model\Universe\AbstractUniverseModel::getNew('SystemModel');
$indexData = [];
foreach($systemIds as $systemId){
$system->getById($systemId);
$system->getById($systemId, 0);
if($hashKeyId = $system->getHashKey()){
$indexData[$hashKeyId] = $system->getData();
}

View File

@@ -112,6 +112,8 @@ class Setup extends Controller {
'Model\Universe\FactionModel',
'Model\Universe\AllianceModel',
'Model\Universe\CorporationModel',
'Model\Universe\RaceModel',
'Model\Universe\StationModel',
'Model\Universe\StructureModel',
'Model\Universe\StargateModel',
'Model\Universe\StarModel',

View File

@@ -10,13 +10,21 @@ namespace cron;
abstract class AbstractCron {
// default max_execution_time for cronJobs
/**
* default max_execution_time for cronJobs
// -> should be less then execution period
*/
const DEFAULT_MAX_EXECUTION_TIME = 50;
/**
* set max execution time for cronjobs
* -> Default CLI execution time is "0" -> infinite!
* default threshold time in seconds before a running script (e.g. a large loop) should stop
* -> so there is some time or e.g. logging,... left
*/
const DEFAULT_EXECUTION_TIME_THRESHOLD = 3;
/**
* set max execution time for cronJbs
* -> Default CLI execution time is 0 == infinite!
* php.ini settings are ignored! http://php.net/manual/en/info.configuration.php#ini.max-execution-time
* @param int $time
*/
@@ -24,4 +32,33 @@ abstract class AbstractCron {
ini_set('max_execution_time', $time);
}
/**
* get max execution time
* -> 0 means == infinite!
* @return int
*/
protected function getMaxExecutionTime() : int {
return (int)ini_get('max_execution_time');
}
/**
* checks execution time of a "long" running script
* -> returns false if execution time is close to maxExecutionTime
* @param float $timeTotalStart
* @param float|null $timeCheck
* @param int $timeThreshold
* @return bool
*/
protected function isExecutionTimeLeft(float $timeTotalStart, float $timeCheck = null, int $timeThreshold = self::DEFAULT_EXECUTION_TIME_THRESHOLD) : bool {
$timeLeft = true;
if($timeTotalMax = $this->getMaxExecutionTime()){
$timeTotalMaxThreshold = $timeTotalStart + $timeTotalMax - $timeThreshold;
$timeCheck = $timeCheck ? : microtime(true);
if($timeCheck >= $timeTotalMaxThreshold){
$timeLeft = false;
}
}
return $timeLeft;
}
}

View File

@@ -13,6 +13,7 @@ use Model;
class Universe extends AbstractCron {
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';
/**
@@ -133,10 +134,11 @@ class Universe extends AbstractCron {
* @param float $timeTotalStart
*/
private function echoLoaded(int $importCount, int $id, float $timeLoopStart, float $timeTotalStart){
$time = microtime(true);
echo '[' . date('H:i:s') . '] loaded ' . str_pad('', strlen($importCount), ' ') . ' id: ' . $this->formatIdValue($id) .
' memory: ' . $this->formatMemoryValue(memory_get_usage()) .
' time: ' . $this->formatSeconds(microtime(true) - $timeLoopStart) .
' total: ' . $this->formatSeconds(microtime(true) - $timeTotalStart) . PHP_EOL;
' time: ' . $this->formatSeconds($time - $timeLoopStart) .
' total: ' . $this->formatSeconds($time - $timeTotalStart) . PHP_EOL;
$this->echoFlush();
}
@@ -169,8 +171,8 @@ class Universe extends AbstractCron {
$msg = '';
$ids = [];
$importCount = 0;
$count = 0;
$importCount = [];
$modelClass = '';
$setupModel = function(Model\Universe\AbstractUniverseModel &$model, int $id){};
@@ -193,6 +195,18 @@ class Universe extends AbstractCron {
$model->loadStargatesData();
};
break;
case 'station':
$ids = $f3->ccpClient()->getUniverseSystems();
$modelClass = 'SystemModel';
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
if($model->getById($id)){
$model->loadStationsData();
}else{
echo 'NOT VALID ' . $id . PHP_EOL;
die();
}
};
break;
case 'sovereignty':
// load sovereignty map data. Systems must be present first!
$sovData = $f3->ccpClient()->getSovereigntyMap();
@@ -243,6 +257,7 @@ class Universe extends AbstractCron {
sort($ids, SORT_NUMERIC);
$ids = array_slice($ids, $offset, $length);
$importCount = count($ids);
$count = 0;
$this->echoInfo($total, $offset, $importCount, $ids);
$this->echoStart();
@@ -264,9 +279,9 @@ class Universe extends AbstractCron {
// Log --------------------------------------------------------------------------------------------------------
$log = new \Log('cron_' . __FUNCTION__ . '.log');
$log->write( sprintf(self::LOG_TEXT, __FUNCTION__, $type,
$log->write(sprintf(self::LOG_TEXT, __FUNCTION__, $type,
$this->formatCounterValue($count), $importCount, $this->formatMemoryValue(memory_get_peak_usage ()),
$this->formatSeconds(microtime(true) - $timeTotalStart), $msg) );
$this->formatSeconds(microtime(true) - $timeTotalStart), $msg));
}
/**
@@ -278,23 +293,69 @@ class Universe extends AbstractCron {
*/
function updateSovereigntyData(\Base $f3){
$this->setMaxExecutionTime();
$timeTotalStart = microtime(true);
$msg = '';
/**
* @var $system Model\Universe\SystemModel
*/
$system = Model\Universe\AbstractUniverseModel::getNew('SystemModel');
$sovData = $f3->ccpClient()->getSovereigntyMap();
$fwSystems = $f3->ccpClient()->getFactionWarSystems();
$fwSystems = $fwSystems['systems'];
$ids = !empty($sovData = $sovData['map']) ? array_keys($sovData): [];
sort($ids, SORT_NUMERIC);
$importCount = count($ids);
$count = 0;
$changes = [];
foreach($ids as $id){
if($system->getById($id)){
$system->updateSovereigntyData($sovData[$id]);
$count++;
if(is_array($fwSystems[$id])){
$system->updateFactionWarData($fwSystems[$id]);
// skip wormhole systems -> can not have sov data
// -> even though they are returned from sovereignty/map endpoint?!
if(
$system->getById($id, 0) &&
strpos($system->security, 'C') === false
){
if($changedSovData = $system->updateSovereigntyData($sovData[$id])){
$changes['sovereignty'][] = $id;
}
$system->reset();
$changedFwData = false;
if(is_array($fwSystems[$id])){
if($changedFwData = $system->updateFactionWarData($fwSystems[$id])){
$changes['factionWarfare'][] = $id;
}
}
if($changedSovData || $changedFwData){
$system->buildIndex();
}
}
$system->reset();
// 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;
}
}
$changedIds = array_reduce($changes, function(array $reducedIds, array $changedIds) : array {
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),
count($changedIds), count($changes['sovereignty'] ? : []), count($changes['factionWarfare'] ? : []),
$msg));
}
/**

View File

@@ -0,0 +1,124 @@
<?php
namespace lib;
class PriorityCacheStore {
/**
* default max entry limit before store gets truncated
*/
const DEFAULT_ENTRY_LIMIT = 100;
/**
* default cleanup interval
* -> truncate store after 10 inserts. Max store entries:
* DEFAULT_ENTRY_LIMIT + DEFAULT_CLEANUP_INTERVAL - 1
*/
const DEFAULT_CLEANUP_INTERVAL = 10;
/**
* @var int
*/
protected $entryLimit;
/**
* @var int
*/
protected $cleanupInterval;
/**
* @var array
*/
protected $store;
/**
* @var \SplPriorityQueue
*/
protected $priorityQueue;
/**
* @var int
*/
protected $priority = 0;
/**
* PriorityCacheStore constructor.
* @param int $entryLimit
* @param int $cleanupInterval
*/
function __construct(int $entryLimit = self::DEFAULT_ENTRY_LIMIT, int $cleanupInterval = self::DEFAULT_CLEANUP_INTERVAL){
$this->cleanupInterval = $cleanupInterval;
$this->entryLimit = $entryLimit;
$this->store = [];
$this->priorityQueue = new \SplPriorityQueue ();
$this->priorityQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
}
/**
* @param $key
* @param $data
*/
public function set($key, $data){
if(!$this->exists($key)){
$this->priorityQueue->insert($key, $this->priority--);
}
$this->store[$key] = $data;
// check cleanup interval and cleanup Store
$this->cleanupInterval();
}
/**
* @param $key
* @return mixed|null
*/
public function get($key){
return $this->exists($key) ? $this->store[$key] : null;
}
/**
* @param $key
* @return bool
*/
public function exists($key){
return isset($this->store[$key]);
}
public function cleanupInterval() : void {
if(
!$this->priorityQueue->isEmpty() && $this->cleanupInterval &&
($this->priorityQueue->count() % $this->cleanupInterval === 0)
){
$this->cleanup();
}
}
public function cleanup(){
while(
$this->entryLimit < $this->priorityQueue->count() &&
$this->priorityQueue->valid()
){
if($this->exists($key = $this->priorityQueue->extract()['data'])){
unset($this->store[$key]);
}
}
}
public function clear(){
$limit = $this->entryLimit;
$this->entryLimit = 0;
$this->cleanup();
// restore entryLimit for next data
$this->entryLimit = $limit;
}
/**
* @return string
*/
public function __toString(){
return 'Store count: ' . count($this->store) . ' priorityQueue count: ' . $this->priorityQueue->count();
}
}

View File

@@ -74,7 +74,7 @@ class Util {
* @param mixed $array
* @return bool
*/
static function is_assoc($array): bool {
static function is_assoc($array) : bool {
$isAssoc = false;
if(
is_array($array) &&
@@ -127,7 +127,7 @@ class Util {
* @param int $maxHideChars
* @return string
*/
static function obscureString(string $string, int $maxHideChars = 10): string {
static function obscureString(string $string, int $maxHideChars = 10) : string {
$formatted = '';
$length = mb_strlen((string)$string);
if($length > 0){
@@ -143,7 +143,7 @@ class Util {
* @param array $scopes
* @return string
*/
static function getHashFromScopes($scopes){
static function getHashFromScopes($scopes) : string {
$scopes = (array)$scopes;
sort($scopes);
return md5(serialize($scopes));

View File

@@ -521,7 +521,7 @@ abstract class AbstractModel extends Cortex {
* @return bool
*/
public function getById(int $id, int $ttl = self::DEFAULT_SQL_TTL, bool $isActive = true) : bool {
return $this->getByForeignKey('id', $id, ['limit' => 1], $ttl, $isActive);
return $this->getByForeignKey($this->primary, $id, ['limit' => 1], $ttl, $isActive);
}
/**
@@ -534,10 +534,7 @@ abstract class AbstractModel extends Cortex {
* @return bool
*/
public function getByForeignKey(string $key, $value, array $options = [], int $ttl = 0, bool $isActive = true) : bool {
$filters = [];
if($this->exists($key)){
$filters[] = [$key . ' = :' . $key, ':' . $key => $value];
}
$filters = [self::getFilter($key, $value)];
if($isActive && $this->exists('active')){
$filters[] = self::getFilter('active', true);
@@ -975,6 +972,15 @@ abstract class AbstractModel extends Cortex {
return \Base::instance();
}
/**
* get model data as array
* @param $data
* @return array
*/
public static function toArray($data) : array {
return json_decode(json_encode($data), true);
}
/**
* get new filter array representation
* -> $suffix can be used fore unique placeholder,

View File

@@ -12,8 +12,14 @@ use DB\SQL\Schema;
class AllianceMapModel extends AbstractPathfinderModel {
/**
* @var string
*/
protected $table = 'alliance_map';
/**
* @var array
*/
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,

View File

@@ -96,6 +96,15 @@ class CharacterLogModel extends AbstractPathfinderModel {
'default' => '',
'activity-log' => true
],
'structureTypeId' => [
'type' => Schema::DT_INT,
'index' => true
],
'structureTypeName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'structureId' => [
'type' => Schema::DT_BIGINT,
'index' => true,
@@ -146,9 +155,13 @@ class CharacterLogModel extends AbstractPathfinderModel {
}
if( isset($logData['structure']) ){
$this->structureTypeId = (int)$logData['structure']['type']['id'];
$this->structureTypeName = $logData['structure']['type']['name'];
$this->structureId = (int)$logData['structure']['id'];
$this->structureName = $logData['structure']['name'];
}else{
$this->structureTypeId = null;
$this->structureTypeName = '';
$this->structureId = null;
$this->structureName = '';
}
@@ -178,20 +191,15 @@ class CharacterLogModel extends AbstractPathfinderModel {
$logData->station->name = $this->stationName;
$logData->structure = (object) [];
$logData->structure->type = (object) [];
$logData->structure->type->id = $this->structureTypeId;
$logData->structure->type->name = $this->structureTypeName;
$logData->structure->id = (int)$this->structureId;
$logData->structure->name = $this->structureName;
return $logData;
}
/**
* get 'character log' data as array
* @return array
*/
public function getDataAsArray() : array {
return json_decode(json_encode($this->getData()), true);
}
/**
* Event "Hook" function
* return false will stop any further action

View File

@@ -857,23 +857,22 @@ class CharacterModel extends AbstractPathfinderModel {
if($this->isOnline($accessToken)){
$locationData = self::getF3()->ccpClient()->getCharacterLocationData($this->_id, $accessToken);
if( !empty($locationData['system']['id']) ){
if(!empty($locationData['system']['id'])){
// character is currently in-game
// get current $characterLog or get new ---------------------------------------------------
if( !($characterLog = $this->getLog()) ){
// get current $characterLog or get new -------------------------------------------------------
if(!$characterLog = $this->getLog()){
// create new log
$characterLog = $this->rel('characterLog');
}
// get current log data and modify on change
$logData = $characterLog->getDataAsArray();
$logData = $characterLog::toArray($characterLog->getData());
// check system and station data for changes ----------------------------------------------
// check system and station data for changes --------------------------------------------------
// IDs for "systemId", "stationId" that require more data
$lookupUniverseIds = [];
if(
empty($logData['system']['name']) ||
$logData['system']['id'] !== $locationData['system']['id']
@@ -882,33 +881,18 @@ class CharacterModel extends AbstractPathfinderModel {
$lookupUniverseIds[] = $locationData['system']['id'];
}
if( !empty($locationData['station']['id']) ){
if(
empty($logData['station']['name']) ||
$logData['station']['id'] !== $locationData['station']['id']
){
// station changed -> request "station name" for current station
$lookupUniverseIds[] = $locationData['station']['id'];
}
}else{
unset($logData['station']);
}
$logData = array_replace_recursive($logData, $locationData);
// get "more" data for systemId and/or stationId -----------------------------------------
if( !empty($lookupUniverseIds) ){
// get "more" data for systemId ---------------------------------------------------------------
if(!empty($lookupUniverseIds)){
// get "more" information for some Ids (e.g. name)
$universeData = self::getF3()->ccpClient()->getUniverseNamesData($lookupUniverseIds);
if( !empty($universeData) && !isset($universeData['error']) ){
if(!empty($universeData) && !isset($universeData['error'])){
// We expect max ONE system AND/OR station data, not an array of e.g. systems
if(!empty($universeData['system'])){
$universeData['system'] = reset($universeData['system']);
}
if(!empty($universeData['station'])){
$universeData['station'] = reset($universeData['station']);
}
$logData = array_replace_recursive($logData, $universeData);
}else{
@@ -917,32 +901,63 @@ class CharacterModel extends AbstractPathfinderModel {
}
}
// check structure data for changes -------------------------------------------------------
// check station data for changes -------------------------------------------------------------
if(!$deleteLog){
// IDs for "stationId" that require more data
$lookupStationId = 0;
if(!empty($locationData['station']['id'])){
if(
empty($logData['station']['name']) ||
$logData['station']['id'] !== $locationData['station']['id']
){
// station changed -> request station data
$lookupStationId = $locationData['station']['id'];
}
}else{
unset($logData['station']);
}
// get "more" data for stationId
if($lookupStationId > 0){
/**
* @var $stationModel Universe\StationModel
*/
$stationModel = Universe\AbstractUniverseModel::getNew('StationModel');
$stationModel->loadById($lookupStationId, $accessToken, $additionalOptions);
if($stationModel->valid()){
$stationData['station'] = $stationModel::toArray($stationModel->getData());
$logData = array_replace_recursive($logData, $stationData);
}else{
unset($logData['station']);
}
}
}
// check structure data for changes -----------------------------------------------------------
if(!$deleteLog){
// IDs for "structureId" that require more data
$lookupStructureId = 0;
if( !empty($locationData['structure']['id']) ){
if(!empty($locationData['structure']['id'])){
if(
empty($logData['structure']['name']) ||
$logData['structure']['id'] !== $locationData['structure']['id']
){
// structure changed -> request "structure name" for current station
// structure changed -> request structure data
$lookupStructureId = $locationData['structure']['id'];
}
}else{
unset($logData['structure']);
}
// get "more" data for structureId ---------------------------------------------------
// get "more" data for structureId
if($lookupStructureId > 0){
/**
* @var $structureModel Universe\StructureModel
*/
$structureModel = Universe\AbstractUniverseModel::getNew('StructureModel');
$structureModel->loadById($lookupStructureId, $accessToken, $additionalOptions);
if(!$structureModel->dry()){
$structureData['structure'] = (array)$structureModel->getData();
if($structureModel->valid()){
$structureData['structure'] = $structureModel::toArray($structureModel->getData());
$logData = array_replace_recursive($logData, $structureData);
}else{
unset($logData['structure']);
@@ -950,13 +965,13 @@ class CharacterModel extends AbstractPathfinderModel {
}
}
// check ship data for changes ------------------------------------------------------------
if( !$deleteLog ){
// check ship data for changes ----------------------------------------------------------------
if(!$deleteLog){
$shipData = self::getF3()->ccpClient()->getCharacterShipData($this->_id, $accessToken);
// IDs for "shipTypeId" that require more data
$lookupShipTypeId = 0;
if( !empty($shipData['ship']['typeId']) ){
if(!empty($shipData['ship']['typeId'])){
if(
empty($logData['ship']['typeName']) ||
$logData['ship']['typeId'] !== $shipData['ship']['typeId']
@@ -973,7 +988,7 @@ class CharacterModel extends AbstractPathfinderModel {
$invalidResponse = true;
}
// get "more" data for shipTypeId ----------------------------------------------------
// get "more" data for shipTypeId
if($lookupShipTypeId > 0){
/**
* @var $typeModel Universe\TypeModel
@@ -990,7 +1005,7 @@ class CharacterModel extends AbstractPathfinderModel {
}
}
if( !$deleteLog ){
if(!$deleteLog){
// mark log as "updated" even if no changes were made
if($additionalOptions['markUpdated'] === true){
$characterLog->touch('updated');
@@ -1079,7 +1094,7 @@ class CharacterModel extends AbstractPathfinderModel {
){
$task = 'add';
$mapIds = [];
$historyLog = $characterLog->getDataAsArray();
$historyLog = $characterLog::toArray($characterLog->getData());
if($logHistoryData = $this->getLogsHistory()){
// skip logging if no relevant fields changed

View File

@@ -12,8 +12,14 @@ use DB\SQL\Schema;
class CorporationMapModel extends AbstractPathfinderModel {
/**
* @var string
*/
protected $table = 'corporation_map';
/**
* @var array
*/
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,

View File

@@ -125,15 +125,15 @@ class StructureModel extends AbstractPathfinderModel {
/**
* set corporationId (owner) for this structure
* -> if corporation does not exists in DB -> load from API
* @param int $corporationId
* @param int|null $corporationId
* @return int|null
*/
public function set_corporationId(int $corporationId) : ?int {
public function set_corporationId(?int $corporationId) : ?int {
$oldCorporationId = $this->get('corporationId', true) ? : 0;
if($corporationId){
if($corporationId !== $oldCorporationId){
// make sure there is already corporation data stored for new corporationId
// make sure there is already corporation data available for new corporationId
/**
* @var CorporationModel $corporation
*/
@@ -151,26 +151,20 @@ class StructureModel extends AbstractPathfinderModel {
}
/**
* validates systemId
* -> a structure always belongs to the same system
* @param string $key
* @param string $val
* @return bool
*/
protected function validate_systemId(string $key, string $val): bool {
$valid = true;
if( !$this->dry() && $this->systemId !== (int)$val ){
// structure always belongs to the same system
$valid = false;
}
return $valid;
protected function validate_systemId(string $key, string $val) : bool {
return !($this->valid() && $this->systemId !== (int)$val);
}
/**
* check whether this model is valid or not
* @return bool
*/
public function isValid(): bool {
public function isValid() : bool {
if($valid = parent::isValid()){
// structure always belongs to a systemId
if(!(int)$this->systemId){
@@ -239,7 +233,7 @@ class StructureModel extends AbstractPathfinderModel {
* @param string $name
* @param int $systemId
*/
public function getByName(CorporationModel $corporation, string $name, int $systemId) {
public function getByName(CorporationModel $corporation, string $name, int $systemId){
if( !$corporation->dry() && $name){
$this->has('structureCorporations', ['corporationId = :corporationId', ':corporationId' => $corporation->_id]);
$this->load(['name = :name AND systemId = :systemId AND active = :active',

View File

@@ -13,8 +13,14 @@ use DB\SQL\Schema;
class StructureStatusModel extends AbstractPathfinderModel {
/**
* @var string
*/
protected $table = 'structure_status';
/**
* @var array
*/
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
@@ -42,6 +48,9 @@ class StructureStatusModel extends AbstractPathfinderModel {
]
];
/**
* @var array
*/
protected static $tableData = [
[
'id' => 1,

View File

@@ -10,6 +10,7 @@ namespace Model\Pathfinder;
use DB\SQL\Schema;
use lib\logging;
use lib\PriorityCacheStore;
use Controller\Ccp\Universe;
class SystemModel extends AbstractMapTrackingModel {
@@ -39,16 +40,16 @@ class SystemModel extends AbstractMapTrackingModel {
*/
const DATA_CACHE_KEY_SIGNATURES_HISTORY = 'HISTORY_SIGNATURES';
/**
* @var PriorityCacheStore
*/
protected static $priorityCacheStore;
/**
* @var string
*/
protected $table = 'system';
/**
* @var array
*/
protected $staticSystemDataCache = [];
/**
* @var array
*/
@@ -242,14 +243,19 @@ class SystemModel extends AbstractMapTrackingModel {
*/
private function getStaticSystemData(){
$staticData = null;
if( !empty($this->staticSystemDataCache[$this->systemId]) ){
$staticData = $this->staticSystemDataCache[$this->systemId];
if(!is_object(self::$priorityCacheStore)){
self::$priorityCacheStore = new PriorityCacheStore();
}
if(self::$priorityCacheStore->exists($this->systemId)){
$staticData = self::$priorityCacheStore->get($this->systemId);
}else{
$staticData = (new Universe())->getSystemData($this->systemId);
if($staticData){
$this->staticSystemDataCache = [$this->systemId => $staticData];
self::$priorityCacheStore->set($this->systemId, $staticData);
}
}
return $staticData;
}
@@ -457,6 +463,10 @@ class SystemModel extends AbstractMapTrackingModel {
return $this->getStaticSystemValue('planets');
}
public function get_stations(){
return $this->getStaticSystemValue('stations');
}
public function get_sovereignty(){
return $this->getStaticSystemValue('sovereignty');
}
@@ -657,6 +667,14 @@ class SystemModel extends AbstractMapTrackingModel {
return $this->getMap()->getStructuresData($this->systemId);
}
/**
* get data for all stations in this system
* @return array
*/
public function getStationsData() : array {
return $this->stations ? : [];
}
/**
* check whether this system is in w-space
* @return bool

View File

@@ -36,6 +36,21 @@ abstract class AbstractUniverseModel extends AbstractModel {
return null;
}
/**
* setter for positions array (x/y/z)
* @param $position
* @return null
*/
public function set_position($position){
$position = (array)$position;
if(count($position) === 3){
$this->x = $position['x'];
$this->y = $position['y'];
$this->z = $position['z'];
}
return null;
}
/**
* Event "Hook" function
* return false will stop any further action

View File

@@ -74,21 +74,6 @@ class ConstellationModel extends AbstractUniverseModel {
return $constellationData;
}
/**
* setter for positions array (x/y/z)
* @param $position
* @return null
*/
public function set_position($position){
$position = (array)$position;
if(count($position) === 3){
$this->x = $position['x'];
$this->y = $position['y'];
$this->z = $position['z'];
}
return null;
}
/**
* @param int $id
* @param string $accessToken

View File

@@ -64,6 +64,9 @@ class CorporationModel extends AbstractUniverseModel {
],
'sovereigntySystems' => [
'has-many' => ['Model\Universe\SovereigntyMapModel', 'corporationId']
],
'stations' => [
'has-many' => ['Model\Universe\StationModel', 'corporationId']
]
];
@@ -102,7 +105,7 @@ class CorporationModel extends AbstractUniverseModel {
if($data['allianceId']){
/**
* @var $faction AllianceModel
* @var $alliance AllianceModel
*/
$alliance = $this->rel('allianceId');
$alliance->loadById($data['allianceId'], $accessToken, $additionalOptions);

View File

@@ -89,7 +89,7 @@ class DogmaAttributeModel extends AbstractUniverseModel {
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient()->getDogmaAttributeData($id);
if(!empty($data)){
if(!empty($data) && !isset($data['error'])){
$this->copyfrom($data, ['id', 'name', 'displayName', 'description', 'published', 'stackable', 'highIsGood', 'defaultValue', 'iconId', 'unitId']);
$this->save();
}

View File

@@ -46,6 +46,9 @@ class FactionModel extends AbstractUniverseModel {
'nullable' => false,
'default' => 0
],
'race' => [ // faction API endpoint dont have "raceId" data, but race API endpoint has
'has-one' => ['Model\Universe\RaceModel', 'factionId']
],
'alliances' => [
'has-many' => ['Model\Universe\AllianceModel', 'factionId']
],
@@ -82,7 +85,7 @@ class FactionModel extends AbstractUniverseModel {
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient()->getUniverseFactionData($id);
if(!empty($data)){
if(!empty($data) && !isset($data['error'])){
$this->copyfrom($data, ['id', 'name', 'description', 'sizeFactor', 'stationCount', 'stationSystemCount']);
$this->save();
}

View File

@@ -75,21 +75,6 @@ class PlanetModel extends AbstractUniverseModel {
return $data;
}
/**
* setter for positions array (x/y/z)
* @param $position
* @return null
*/
public function set_position($position){
$position = (array)$position;
if(count($position) === 3){
$this->x = $position['x'];
$this->y = $position['y'];
$this->z = $position['z'];
}
return null;
}
/**
* @param int $id
* @param string $accessToken

View File

@@ -0,0 +1,82 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 22.10.2019
* Time: 03:21
*/
namespace Model\Universe;
use DB\SQL\Schema;
class RaceModel extends AbstractUniverseModel {
/**
* @var string
*/
protected $table = 'race';
/**
* @var array
*/
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'description' => [
'type' => Schema::DT_TEXT
],
'factionId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\FactionModel',
'constraint' => [
[
'table' => 'faction',
'on-delete' => 'CASCADE'
]
],
'validate' => 'notDry'
],
'stations' => [
'has-many' => ['Model\Universe\StationModel', 'raceId']
]
];
/**
* get data
* @return \stdClass
*/
public function getData(){
$data = (object) [];
$data->id = $this->_id;
$data->name = $this->name;
$data->faction = $this->factionId->getData();
return $data;
}
/**
* load data from API into $this and save $this
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient()->getUniverseRaceData($id);
if(!empty($data) && !isset($data['error'])){
/**
* @var $faction FactionModel
*/
$faction = $this->rel('factionId');
$faction->loadById($data['factionId'], $accessToken, $additionalOptions);
$data['factionId'] = $faction;
$this->copyfrom($data, ['id', 'name', 'description', 'factionId']);
$this->save();
}
}
}

View File

@@ -74,7 +74,6 @@ class StargateModel extends AbstractUniverseModel {
];
public function getData(){
$stargateData = (object) [];
$stargateData->id = $this->_id;
$stargateData->type = $this->typeId->name;
@@ -83,21 +82,6 @@ class StargateModel extends AbstractUniverseModel {
return $stargateData;
}
/**
* setter for positions array (x/y/z)
* @param $position
* @return null
*/
public function set_position($position){
$position = (array)$position;
if(count($position) === 3){
$this->x = $position['x'];
$this->y = $position['y'];
$this->z = $position['z'];
}
return null;
}
/**
* @param int $id
* @param string $accessToken

View File

@@ -0,0 +1,165 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 22.10.2019
* Time: 03:00
*/
namespace Model\Universe;
use DB\SQL\Schema;
class StationModel extends AbstractUniverseModel {
/**
* @var string
*/
protected $table = 'station';
/**
* @var array
*/
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'systemId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\SystemModel',
'constraint' => [
[
'table' => 'system',
'on-delete' => 'CASCADE'
]
],
'validate' => 'notDry'
],
'typeId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\TypeModel',
'constraint' => [
[
'table' => 'type',
'on-delete' => 'CASCADE'
]
],
'validate' => 'notDry'
],
'corporationId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\CorporationModel',
'constraint' => [
[
'table' => 'corporation',
'on-delete' => 'CASCADE'
]
]
],
'raceId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\RaceModel',
'constraint' => [
[
'table' => 'race',
'on-delete' => 'CASCADE'
]
]
],
'services' => [
'type' => self::DT_JSON
],
'x' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'y' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
],
'z' => [
'type' => Schema::DT_BIGINT,
'nullable' => false,
'default' => 0
]
];
/**
* get data
* @return \stdClass
*/
public function getData(){
$data = (object) [];
$data->id = $this->_id;
$data->name = $this->name;
$data->type = $this->typeId->getData();
$data->services = $this->services ? : [];
// according to ESIs Swagger conf, "raceId" AND "corporationId"(= "owner") are optional
// -> I haven´t seen any imported NPC station data where "raceId" OR "corporationId" not exist
if($this->corporationId){
$data->corporation = $this->corporationId->getData();
}
if($this->raceId){
$data->race = $this->raceId->getData();
}
return $data;
}
/**
* load data from API into $this and save $this
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient()->getUniverseStationData($id);
if(!empty($data) && !isset($data['error'])){
/**
* @var $system SystemModel
*/
$system = $this->rel('systemId');
$system->loadById($data['systemId'], $accessToken, $additionalOptions);
$data['systemId'] = $system;
/**
* @var $type TypeModel
*/
$type = $this->rel('typeId');
$type->loadById($data['typeId'], $accessToken, $additionalOptions);
$data['typeId'] = $type;
if($data['corporationId']){
/**
* @var $faction CorporationModel
*/
$corporation = $this->rel('corporationId');
$corporation->loadById($data['corporationId'], $accessToken, $additionalOptions);
$data['corporationId'] = $corporation;
}
if($data['raceId']){
/**
* @var $race RaceModel
*/
$race = $this->rel('raceId');
$race->loadById($data['raceId'], $accessToken, $additionalOptions);
$data['raceId'] = $race;
}
$this->copyfrom($data, ['id', 'name', 'systemId', 'typeId', 'corporationId', 'raceId', 'services', 'position']);
$this->save();
}
}
}

View File

@@ -13,8 +13,14 @@ use DB\SQL\Schema;
class StructureModel extends AbstractUniverseModel {
/**
* @var string
*/
protected $table = 'structure';
/**
* @var array
*/
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
@@ -23,9 +29,15 @@ class StructureModel extends AbstractUniverseModel {
],
'systemId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0,
'index' => true
'index' => true,
'belongs-to-one' => 'Model\Universe\SystemModel',
'constraint' => [
[
'table' => 'system',
'on-delete' => 'CASCADE'
]
],
'validate' => 'notDry'
],
'typeId' => [
'type' => Schema::DT_INT,
@@ -63,9 +75,10 @@ class StructureModel extends AbstractUniverseModel {
*/
public function getData(): \stdClass {
$data = (object) [];
if(!$this->dry()){
$data->id = $this->_id;
if($this->valid()){
$data->id = $this->_id;
$data->name = $this->name;
$data->type = $this->typeId->getData();
}
return $data;
}
@@ -78,7 +91,7 @@ class StructureModel extends AbstractUniverseModel {
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient()->getUniverseStructureData($id, $accessToken);
if(!empty($data)){
if(!empty($data) && !isset($data['error'])){
/**
* @var $type TypeModel
*/

View File

@@ -93,6 +93,9 @@ class SystemModel extends AbstractUniverseModel {
'stargates' => [
'has-many' => ['Model\Universe\StargateModel', 'systemId']
],
'stations' => [
'has-many' => ['Model\Universe\StationModel', 'systemId']
],
'structures' => [
'has-many' => ['Model\Universe\StructureModel', 'systemId']
],
@@ -154,6 +157,10 @@ class SystemModel extends AbstractUniverseModel {
$data->stargates = $stargatesData;
}
if( !empty($stationsData = $this->getStationsData()) ){
$data->stations = $stationsData;
}
return $data;
}
@@ -227,25 +234,12 @@ class SystemModel extends AbstractUniverseModel {
return $effect ? : null;
}
/**
* setter for positions array (x/y/z)
* @param $position
* @return null
*/
public function set_position($position){
$position = (array)$position;
if(count($position) === 3){
$this->x = $position['x'];
$this->y = $position['y'];
$this->z = $position['z'];
}
return null;
}
/**
* @param array $sovData
* @return bool true if sovereignty data changed
*/
public function updateSovereigntyData(array $sovData = []){
public function updateSovereigntyData(array $sovData = []) : bool {
$hasChanged = false;
$systemId = (int)$sovData['systemId'];
$factionId = (int)$sovData['factionId'];
$allianceId = (int)$sovData['allianceId'];
@@ -260,7 +254,7 @@ class SystemModel extends AbstractUniverseModel {
/**
* @var $sovereignty SovereigntyMapModel
*/
if( !($sovereignty = $this->sovereignty) ){
if(!$sovereignty = $this->sovereignty){
// insert new sovereignty data
$sovereignty = $this->rel('sovereignty');
}
@@ -305,20 +299,31 @@ class SystemModel extends AbstractUniverseModel {
}
$sovereignty->copyfrom($sovData, ['systemId', 'factionId', 'allianceId', 'corporationId']);
// must be called before save(). Changed fields get reset after save() is called!
if($sovereignty->changed()){
$hasChanged = true;
}
$sovereignty->save();
}elseif($this->sovereignty){
// delete existing sov data
// -> hint: WH - systems never have sovereignty data
$this->sovereignty->erase();
$hasChanged = true;
}
}
}
return $hasChanged;
}
/**
* @param array $fwData
* @return bool true if faction warfare data changed
*/
public function updateFactionWarData(array $fwData = []){
public function updateFactionWarData(array $fwData = []) : bool {
$hasChanged = false;
$systemId = (int)$fwData['systemId'];
$ownerFactionId = (int)$fwData['ownerFactionId'];
$occupierFactionId = (int)$fwData['occupierFactionId'];
@@ -328,7 +333,7 @@ class SystemModel extends AbstractUniverseModel {
/**
* @var $factionWar FactionWarSystemModel
*/
if( !($factionWar = $this->factionWar) ){
if(!$factionWar = $this->factionWar){
// insert new faction war data
$factionWar = $this->rel('factionWar');
}
@@ -354,9 +359,17 @@ class SystemModel extends AbstractUniverseModel {
}
$factionWar->copyfrom($fwData, ['systemId', 'ownerFactionId', 'occupierFactionId', 'contested', 'victoryPoints', 'victoryPointsThreshold']);
// must be called before save(). Changed fields get reset after save() is called!
if($factionWar->changed()){
$hasChanged = true;
}
$factionWar->save();
}
}
return $hasChanged;
}
/**
@@ -379,10 +392,10 @@ class SystemModel extends AbstractUniverseModel {
$planetsData = [];
if($this->planets){
/**
* @var $planet PlanetModel
*/
foreach($this->planets as &$planet){
/**
* @var $planet PlanetModel
*/
$planetsData[] = $planet->getData();
}
}
@@ -397,10 +410,10 @@ class SystemModel extends AbstractUniverseModel {
$staticsData = [];
if($this->statics){
/**
* @var $static SystemStaticModel
*/
foreach($this->statics as &$static){
/**
* @var $static SystemStaticModel
*/
$staticsData[] = $static->getData();
}
}
@@ -415,16 +428,53 @@ class SystemModel extends AbstractUniverseModel {
$stargatesData = [];
if($this->stargates){
/**
* @var $stargate StargateModel
*/
foreach($this->stargates as &$stargate){
/**
* @var $stargate StargateModel
*/
$stargatesData[] = $stargate->getData();
}
}
return $stargatesData;
}
/**
* get data from all stations
* @return array
*/
protected function getStationsData() : array {
$stationsData = [];
if($this->stations){
/**
* @var $station StationModel
*/
foreach($this->stations as &$station){
$data = $station->getData();
if(!$data->race){
// should never happen NPC stations always have a owning race
$data->race = (object) [];
$data->race->id = 0;
$data->race->name = 'unknown';
$data->race->faction = (object) [];
$data->race->faction->id = 0;
$data->race->faction->name = 'unknown';
}
if(!array_key_exists($data->race->faction->id, $stationsData)){
$stationsData[$data->race->faction->id] = [
'id' => $data->race->faction->id,
'name' => $data->race->name,
'stations' => []
];
}
$stationsData[$data->race->faction->id]['stations'][] = $data;
}
}
return $stationsData;
}
/**
* update system from ESI
*/
@@ -505,4 +555,23 @@ class SystemModel extends AbstractUniverseModel {
}
}
}
/**
* load NPC owned stations for this system
*/
public function loadStationsData(){
if($this->valid()){
$data = self::getF3()->ccpClient()->getUniverseSystemData($this->_id);
if($data['stations']){
foreach((array)$data['stations'] as $stationId){
/**
* @var $station SystemModel
*/
$station = $this->rel('stations');
$station->loadById($stationId);
$station->reset();
}
}
}
}
}

View File

@@ -103,6 +103,9 @@ class TypeModel extends AbstractUniverseModel {
'default' => 0,
'index' => true
],
'stations' => [
'has-many' => ['Model\Universe\StationModel', 'typeId']
],
'structures' => [
'has-many' => ['Model\Universe\StructureModel', 'typeId']
],
@@ -220,7 +223,7 @@ class TypeModel extends AbstractUniverseModel {
* -> more fields can be added in here if needed
* @return \stdClass
*/
public function getShipData(): \stdClass {
public function getShipData() : \stdClass {
$shipData = (object) [];
if($this->valid()){
$shipData->typeId = $this->_id;

View File

@@ -377,6 +377,9 @@ LOG_LINES = 1000
[PATHFINDER.API]
CCP_IMAGE_SERVER = https://image.eveonline.com
Z_KILLBOARD = https://zkillboard.com/api
EVEEYE = https://eveeye.com
DOTLAN = http://evemaps.dotlan.net
ANOIK = http://anoik.is
; GitHub Developer API
GIT_HUB = https://api.github.com

Binary file not shown.

View File

@@ -137,7 +137,8 @@ define([
// mark as init
tableElement.attr('data-counter', 'init');
let refreshIntervalId = window.setInterval(() => {
let updateTableCount = () => {
tableApi.cells(null, columnSelector).every(function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
let cell = this;
let node = cell.node();
@@ -148,7 +149,9 @@ define([
updateDateDiff( cell.nodes().to$(), date, round);
}
});
}, 500);
};
let refreshIntervalId = window.setInterval(updateTableCount, 500);
tableElement.data('interval', refreshIntervalId);
};

View File

@@ -163,7 +163,7 @@ define([
{icon: 'fa-route', action: 'find_route', text: 'find route'},
{icon: 'fa-object-group', action: 'select_connections', text: 'select connections'},
{icon: 'fa-reply fa-rotate-180', text: 'waypoints', subitems: [
{subIcon: 'fa-flag-checkered', subAction: 'set_destination', subText: 'set destination'},
{subIcon: 'fa-flag', subAction: 'set_destination', subText: 'set destination'},
{subDivider: true, action: ''},
{subIcon: 'fa-step-backward', subAction: 'add_first_waypoint', subText: 'add new [start]'},
{subIcon: 'fa-step-forward', subAction: 'add_last_waypoint', subText: 'add new [end]'}

View File

@@ -422,7 +422,7 @@ define([
_: (data, type, row, meta) => {
let value = data.typeName;
if(type === 'display'){
value = '<img src="' + Util.eveImageUrl('render', data.typeId) + '_32.png"/>';
value = '<img src="' + Util.eveImageUrl('render', data.typeId) + '"/>';
}
return value;
}

View File

@@ -1763,6 +1763,91 @@ define([
});
};
/**
* add station services tooltip
* @param services
* @param options
* @returns {*}
*/
$.fn.addStationServiceTooltip = function(services, options){
let getServiceIcon = service => {
switch(service){
case 'bounty-missions': return false;
case 'assasination-missions': return false;
case 'courier-missions': return false;
case 'interbus': return false;
case 'reprocessing-plant': return 'reprocess';
case 'refinery': return false;
case 'market': return 'market';
case 'black-market': return false;
case 'stock-exchange': return false;
case 'cloning': return 'clonebay';
case 'surgery': return false;
case 'dna-therapy': return false;
case 'repair-facilities': return 'repairshop';
case 'factory': return 'industry';
case 'labratory': return 'research';
case 'gambling': return false;
case 'fitting': return 'fitting';
case 'paintshop': return 'skins';
case 'news': return false;
case 'storage': return false;
case 'insurance': return 'insurance';
case 'docking': return 'docking';
case 'office-rental': return false;
case 'jump-clone-facility': return 'jumpclones';
case 'loyalty-point-store': return 'lpstore';
case 'navy-offices': return 'factionalwarfare';
case 'security-offices': return 'concord';
default: return false;
}
};
let getStationServicesTable = services => {
let content = '';
for(let i = 0; i < services.length; i++){
let icon = getServiceIcon(services[i]);
if(icon){
content += '<img class="' + Util.config.popoverListIconClass + '" src="/public/img/icons/client/ui/window/' + icon + '.png" alt="' + services[i] + '">';
}
}
return content;
};
let content = getStationServicesTable(services);
let title = '<i class="fas fa-tools fa-fw"></i>&nbsp;Services';
let defaultOptions = {
placement: 'top',
html: true,
trigger: 'hover',
container: 'body',
title: title,
content: '',
delay: {
show: 150,
hide: 0
},
};
options = $.extend({}, defaultOptions, options);
return this.each(function(){
let element = $(this);
element.popover(options);
// set new popover content
let popover = element.data('bs.popover');
popover.options.content = content;
popover.tip().addClass(Util.config.popoverClass);
if(options.show){
element.popover('show');
}
});
};
/**
* add system effect tooltip
* @param security
@@ -1807,7 +1892,6 @@ define([
* @returns {*}
*/
$.fn.addSystemPlanetsTooltip = function(planets, options){
let content = Util.getSystemPlanetsTable(planets);
let defaultOptions = {

View File

@@ -1012,7 +1012,13 @@ define([
if(changes.charactersIds){
updateTasks.push(updateHeaderCharacterSwitch(userData, changes.characterId));
}
if(changes.characterSystemId || changes.characterShipType || changes.characterLogHistory){
if(
changes.characterSystemId ||
changes.characterShipType ||
changes.characterStationId ||
changes.characterStructureId ||
changes.characterLogHistory
){
updateTasks.push(updateHeaderCharacterLocation(userData, changes.characterShipType));
}
@@ -1087,6 +1093,16 @@ define([
let shipTypeId = Util.getObjVal(shipData, 'typeId') || 0;
let shipTypeName = Util.getObjVal(shipData, 'typeName') || '';
let stationData = Util.getObjVal(userData, 'character.log.station');
let stationId = Util.getObjVal(stationData, 'id') || 0;
let stationName = Util.getObjVal(stationData, 'name') || '';
let structureData = Util.getObjVal(userData, 'character.log.structure');
let structureTypeId = Util.getObjVal(structureData, 'type.id') || 0;
let structureTypeName = Util.getObjVal(structureData, 'type.name') || '';
let structureId = Util.getObjVal(structureData, 'id') || 0;
let structureName = Util.getObjVal(structureData, 'name') || '';
logDataAll.push(logData);
// check for log history data as well
@@ -1116,6 +1132,12 @@ define([
if(isCurrentLocation){
breadcrumbHtml += '<i class="fas fa-fw fa-map-marker-alt" title="current location"></i>';
if(stationId > 0){
breadcrumbHtml += '<i class="fas fa-home" title="' + stationName + '"></i>';
}else if(structureId > 0){
breadcrumbHtml += '<i class="fas fa-industry" title="' + structureTypeName + ' &quot;' + structureName + '&quot;"></i>';
}
}
breadcrumbHtml += systemName;

View File

@@ -205,7 +205,7 @@ define([
let systemsElement = $(this).empty();
let systemTable = $('<table>', {
id: Util.getTableId(config.tableId, mapData.config.id, '', 'systems'),
id: Util.getTableId(config.tableId, 'systems', mapData.config.id, ''),
class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ')
});
systemsElement.append(systemTable);
@@ -864,7 +864,7 @@ define([
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
// open corporation information window (ingame)
$(cell).on('click', { tableApi: this.api() }, function(e){
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(cellData.id);

View File

@@ -188,6 +188,61 @@ define([
moduleElement.find('.' + config.systemSovSectionClass + ' .' + Util.config.dynamicAreaClass).hideLoadingAnimation();
};
/**
* @param pages
* @param systemData
*/
let getThirdPartySystemLinks = (pages, systemData) => {
let urls = {};
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;
for(let i = 0; i < pages.length; i++){
let url = false;
let domain = Util.getObjVal(Init, 'url.' + pages[i]);
if(domain || pages[i] === 'eve'){
switch(pages[i]){
case 'eve':
url = 'https://client'; // fake url
break;
case 'dotlan':
systemName = systemName.replace(/ /g, '_');
regionName = regionName.replace(/ /g, '_');
if(isWormhole){
url = domain + '/system/' + systemName;
}else{
url = domain + '/map/' + regionName + '/' + systemName;
}
break;
case 'eveeye':
if(!isWormhole){
url = domain + '/?m=' + encodeURIComponent(regionName) + '&s=' + encodeURIComponent(systemName.replace(/ /g, '_'));
url += '&t=eswkc&o=thera,con_svc,node_sov,sub_sec,sector_fac,tag_mk';
}
break;
case 'anoik':
if(isWormhole){
url = domain + '/systems/' + systemName;
}
break;
}
if(url){
let urlObj = new URL(url);
urls[++validUrls + '_url'] = {
page: pages[i],
domain: urlObj.hostname,
url: url
};
}
}
}
return urls;
};
/**
* get module element
* @param parentElement
@@ -294,7 +349,6 @@ define([
systemUrl: MapUtil.getMapDeeplinkUrl(mapId, systemData.id),
systemTypeName: MapUtil.getSystemTypeInfo(systemData.type.id, 'name'),
systemIsWormhole: MapUtil.getSystemTypeInfo(systemData.type.id, 'name') === 'w-space',
systemStatusId: systemData.status.id,
systemStatusClass: Util.getStatusInfoForSystem(systemData.status.id, 'class'),
systemStatusLabel: Util.getStatusInfoForSystem(systemData.status.id, 'label'),
@@ -326,6 +380,7 @@ define([
systemTypeLinkClass: config.typeLinkClass,
systemUrlLinkClass: config.urlLinkClass,
ccpImageServerUrl: Init.url.ccpImageServer,
thirdPartyLinks: getThirdPartySystemLinks(['eve', 'dotlan', 'eveeye', 'anoik'], systemData)
};
requirejs(['text!templates/modules/system_info.html', 'mustache', 'summernote.loader'], (template, Mustache, Summernote) => {

View File

@@ -7,8 +7,9 @@ define([
'app/init',
'app/util',
'bootbox',
'app/counter'
], ($, Init, Util, bootbox, Counter) => {
'app/counter',
'app/map/util',
], ($, Init, Util, bootbox, Counter, MapUtil) => {
'use strict';
let config = {
@@ -18,7 +19,7 @@ define([
moduleHeadClass: 'pf-module-head', // class for module header
moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler
// system info module
// system intel module
moduleTypeClass: 'pf-system-intel-module', // class for this module
// headline toolbar
@@ -28,10 +29,14 @@ define([
moduleHeadlineIconRefreshClass: 'pf-module-icon-button-refresh', // class for "refresh" icon
// system intel module
systemStructuresTableClass: 'pf-system-structure-table', // class for route tables
intelTableId: 'pf-intel-table-', // id prefix for all tables in module
intelTableRowIdPrefix: 'pf-intel-row-', // id prefix for table rows
systemStationsTableClass: 'pf-system-station-table', // class for NPC owned stations table
systemStructuresTableClass: 'pf-system-structure-table', // class for player owned structures table
// structure dialog
structureDialogId: 'pf-structure-dialog', // id for "structure" dialog
nameInputId: 'pf-structure-dialog-name-input', // id for "name" input
statusSelectId: 'pf-structure-dialog-status-select', // id for "status" select
typeSelectId: 'pf-structure-dialog-type-select', // id for "type" select
corporationSelectId: 'pf-structure-dialog-corporation-select', // id for "corporation" select
@@ -39,11 +44,12 @@ define([
descriptionTextareaCharCounter: 'pf-form-field-char-count', // class for "character counter" element for form field
// dataTable
tableRowIdPrefix: 'pf-structure-row_', // id prefix for table rows
tableCellImageClass: 'pf-table-image-smaller-cell', // class for table "image" cells
tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells
tableCellEllipsisClass: 'pf-table-cell-ellipses-auto', // class for table "ellipsis" cells
dataTableActionCellClass: 'pf-table-action-cell' // class for "action" 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
};
let maxDescriptionLength = 512;
@@ -53,26 +59,102 @@ define([
* @param statusData
* @returns {string}
*/
let getStatusData = statusData => {
let getIconForStatusData = statusData => {
return '<i class="fas fa-fw fa-circle ' + statusData.class + '" title="' + statusData.label + '"></i>';
};
/**
* get icon that marks a table cell as clickable
* @returns {string}
*/
let getIconForInformationWindow = () => {
return '<i class="fas fa-fw fa-id-card ' + config.tableCellActionIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
};
/**
* get dataTable id
* @param mapId
* @param systemId
* @param tableType
* @returns {string}
*/
let getTableId = (tableType, mapId, systemId) => Util.getTableId(config.intelTableId, tableType, mapId, systemId);
/**
* get dataTable row id
* @param tableType
* @param id
* @returns {string}
*/
let getRowId = (tableType, id) => Util.getTableRowId(config.intelTableRowIdPrefix, tableType, id);
/**
* get <tr> DOM id by id
* @param tableApi
* @param id
* @returns {*}
*/
let getRowId = (tableApi, id) => {
return tableApi.rows().ids().toArray().find(rowId => rowId === config.tableRowIdPrefix + id);
let getRowById = (tableApi, id) => {
return tableApi.rows().ids().toArray().find(rowId => rowId === getRowId(Util.getObjVal(getTableMetaData(tableApi), 'type'), id));
};
/**
* callback -> add structure rows from systemData
* @param context
* @param systemData
* get custom "metaData" from dataTables API
* @param tableApi
* @returns {*}
*/
let callbackUpdateStructureRows = (context, systemData) => {
let getTableMetaData = tableApi => {
let data = null;
if(tableApi){
data = tableApi.init().pfMeta;
}
return data;
};
/**
* vormat roman numeric string to int
* -> e.g. 'VII' => 7
* @param str
* @returns {number}
*/
let romanToInt = str => {
let charToTnt = char => {
switch (char) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return -1;
}
};
if(str == null) return -1;
let num = charToTnt(str.charAt(0));
let pre, curr;
for(let i = 1; i < str.length; i++){
curr = charToTnt(str.charAt(i));
pre = charToTnt(str.charAt(i - 1));
if(curr <= pre){
num += curr;
}else{
num = num - pre * 2 + curr;
}
}
return num;
};
/**
* callback -> add table rows from grouped tableData
* @param context
* @param tableData
* @param groupedDataKey
*/
let callbackUpdateTableRows = (context, tableData, groupedDataKey = 'structures') => {
let touchedRows = [];
let hadData = context.tableApi.rows().any();
let notificationCounter = {
@@ -81,41 +163,39 @@ define([
deleted: 0
};
if(systemData){
let corporations = Util.getObjVal(systemData, 'structures');
if(corporations){
for(let [corporationId, corporationData] of Object.entries(corporations)){
if(corporationData.structures && corporationData.structures.length){
for(let structureData of corporationData.structures){
let rowId = getRowId(context.tableApi, structureData.id);
if(tableData){
for(let [rowGroupId, rowGroupData] of Object.entries(tableData)){
if(rowGroupData[groupedDataKey] && rowGroupData[groupedDataKey].length){
for(let rowData of rowGroupData[groupedDataKey]){
let rowId = getRowById(context.tableApi, rowData.id);
// add corporation data
structureData.corporation = {
id: corporationData.id,
name: corporationData.name
};
// add rowGroupData as well to each rowData
rowData.rowGroupData = {
id: rowGroupData.id,
name: rowGroupData.name,
groupedDataKey: groupedDataKey
};
if(rowId){
// update row
let api = context.tableApi.row('#' + rowId);
let rowData = api.data();
if(rowId){
// update row
let api = context.tableApi.row('#' + rowId);
let rowDataCurrent = api.data();
// check for update
if(rowData.updated.updated !== structureData.updated.updated){
// row data changed -> update
api.data(structureData);
notificationCounter.changed++;
}
touchedRows.push(api.id());
}else{
// insert new row
let api = context.tableApi.row.add(structureData);
api.nodes().to$().data('animationStatus', 'added');
notificationCounter.added++;
touchedRows.push(api.id());
// check for update
if(rowDataCurrent.updated.updated !== rowData.updated.updated){
// row data changed -> update
api.data(rowData);
notificationCounter.changed++;
}
touchedRows.push(api.id());
}else{
// insert new row
let api = context.tableApi.row.add(rowData);
api.nodes().to$().data('animationStatus', 'added');
notificationCounter.added++;
touchedRows.push(api.id());
}
}
}
@@ -156,7 +236,7 @@ define([
let deletedCounter = 0;
if(structureIds && structureIds.length){
for(let structureId of structureIds){
let rowId = getRowId(context.tableApi, structureId);
let rowId = getRowById(context.tableApi, structureId);
if(rowId){
context.tableApi.row('#' + rowId).remove();
deletedCounter++;
@@ -214,10 +294,36 @@ define([
return data;
});
// if current user is currently docked at a structure (not station)
// -> add a modal button for pre-fill modal with it
// -> systemId must match systemId from current character log
let currentUserData = Util.getCurrentUserData();
let isCurrentLocation = false;
let characterStructureId = Util.getObjVal(currentUserData, 'character.log.structure.id') || 0;
let characterStructureName = Util.getObjVal(currentUserData, 'character.log.structure.name') || '';
let characterStructureTypeId = Util.getObjVal(currentUserData, 'character.log.structure.type.id') || 0;
let characterStructureTypeName = Util.getObjVal(currentUserData, 'character.log.structure.type.name') || '';
if(systemId === Util.getObjVal(currentUserData, 'character.log.system.id')){
isCurrentLocation = true;
}
let disableButtonAutoFill = true;
let buttonLabelAutoFill = '<i class="fas fa-fw fa-map-marker-alt"></i>&nbsp;';
if(characterStructureId){
buttonLabelAutoFill += characterStructureTypeName + ' "' + characterStructureName + '"';
if(isCurrentLocation){
disableButtonAutoFill = false;
}
}else{
buttonLabelAutoFill += 'unknown structure';
}
let data = {
id: config.structureDialogId,
structureData: structureData,
structureStatus: statusData,
nameInputId: config.nameInputId,
statusSelectId: config.statusSelectId,
typeSelectId: config.typeSelectId,
corporationSelectId: config.corporationSelectId,
@@ -236,7 +342,18 @@ define([
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
className: 'btn-default pull-left'
},
autoFill: {
label: buttonLabelAutoFill,
className: 'btn-primary' + (disableButtonAutoFill ? ' pf-font-italic disabled' : ''),
callback: function(){
let form = this.find('form');
form.find('#' + config.nameInputId).val(characterStructureName);
form.find('#' + config.statusSelectId).val(2).trigger('change');
form.find('#' + config.typeSelectId).val(characterStructureTypeId).trigger('change');
return false;
}
},
success: {
label: '<i class="fas fa-fw fa-check"></i>&nbsp;save',
@@ -268,7 +385,7 @@ define([
},
context => context.moduleElement.hideLoadingAnimation()
).then(
payload => callbackUpdateStructureRows(payload.context, {structures: payload.data}),
payload => callbackUpdateTableRows(payload.context, payload.data),
Util.handleAjaxErrorResponse
);
}else{
@@ -361,6 +478,83 @@ define([
});
};
/**
* init station services tooltips
* @param element
* @param tableApi
*/
let initStationServiceTooltip = (element, tableApi) => {
element.hoverIntent({
over: function(e){
let cellElement = $(this);
let rowData = tableApi.row(cellElement.parents('tr')).data();
cellElement.addStationServiceTooltip(Util.getObjVal(rowData, 'services'), {
placement: 'left',
trigger: 'manual',
show: true
});
},
out: function(e){
$(this).destroyPopover();
},
selector: 'td.' + config.tableCellServicesClass
});
};
/**
* get dataTables default options for intel tables
* @returns {*}
*/
let getDataTableDefaults = () => {
return {
paging: false,
lengthChange: false,
ordering: true,
info: false,
searching: false,
hover: false,
autoWidth: false,
drawCallback: function (settings) {
let tableApi = this.api();
let columnCount = tableApi.columns(':visible').count();
let rows = tableApi.rows({page: 'current'}).nodes();
let last = null;
tableApi.column('rowGroupData:name', {page: 'current'}).data().each(function (group, i) {
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';
$(rows).eq(i).before(
'<tr class="group">' +
'<td></td>' +
'<td class="text-right ' + config.tableCellImageClass + '">' +
'<img src="' + Util.eveImageUrl(imgType, group.id) + '"/>' +
'</td>' +
'<td colspan="' + Math.max((columnCount - 2), 1) + '">' + group.name + '</td>' +
'</tr>'
);
last = group;
}
});
let animationRows = rows.to$().filter(function () {
return (
$(this).data('animationStatus') ||
$(this).data('animationTimer')
);
});
for (let i = 0; i < animationRows.length; i++) {
let animationRow = $(animationRows[i]);
animationRow.pulseBackgroundColor(animationRow.data('animationStatus'));
animationRow.removeData('animationStatus');
}
}
};
};
/**
* get module element
* @param parentElement
@@ -369,6 +563,7 @@ define([
* @returns {jQuery}
*/
let getModule = (parentElement, mapId, systemData) => {
let showStationTable = ['H', 'L', '0.0', 'C12'].includes(Util.getObjVal(systemData, 'security'));
let corporationId = Util.getCurrentUserInfo('corporationId');
let moduleElement = $('<div>').append(
@@ -395,35 +590,29 @@ define([
}).attr('data-html', 'true').attr('data-toggle', 'tooltip')
),
$('<h5>', {
text: 'Intel'
text: 'Structures'
})
)
);
let table = $('<table>', {
// "Structures" table -----------------------------------------------------------------------------------------
let structureTable = $('<table>', {
id: getTableId('structure', mapId, systemData.id),
class: ['compact', 'stripe', 'order-column', 'row-border', 'pf-table-fixed', config.systemStructuresTableClass].join(' ')
});
moduleElement.append(table);
moduleElement.append(structureTable);
let tableApi = table.DataTable({
paging: false,
lengthChange: false,
ordering: true,
order: [[ 10, 'desc' ], [ 0, 'asc' ]],
info: false,
searching: false,
hover: false,
autoWidth: false,
rowId: rowData => config.tableRowIdPrefix + rowData.id,
let structureDataTableOptions = {
pfMeta: {
type: 'structures'
},
order: [[10, 'desc' ], [0, 'asc' ]],
rowId: rowData => getRowId('structures', rowData.id),
language: {
emptyTable: 'No structures recorded',
info: '_START_ to _END_ of _MAX_',
infoEmpty: ''
},
rowGroup: {
enable: true,
dataSrc: 'systemId'
},
columnDefs: [
{
targets: 0,
@@ -433,17 +622,17 @@ define([
className: ['text-center', 'all'].join(' '),
data: 'status',
render: {
display: data => getStatusData(data),
display: data => getIconForStatusData(data),
sort: data => data.id
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).find('i').tooltip();
$(cell).find('i').tooltip();
}
},{
targets: 1,
name: 'structureImage',
title: '',
width: 26,
width: 24,
orderable: false,
className: [config.tableCellImageClass, 'text-center', 'all'].join(' '),
data: 'structure.id',
@@ -476,7 +665,7 @@ define([
targets: 4,
name: 'ownerImage',
title: '',
width: 26,
width: 24,
orderable: false,
className: [config.tableCellImageClass, 'text-center', 'all'].join(' '),
data: 'owner.id',
@@ -519,12 +708,12 @@ define([
title: '',
orderable: false,
width: 10,
className: ['text-center', config.dataTableActionCellClass, config.moduleHeadlineIconClass, 'all'].join(' '),
className: ['text-center', config.tableCellActionClass, config.moduleHeadlineIconClass, 'all'].join(' '),
data: null,
render: {
display: data => {
let icon = '<i class="fas fa-pen"></i>';
if(data.corporation.id !== corporationId){
if(data.rowGroupData.id !== corporationId){
icon = '';
}
return icon;
@@ -534,7 +723,7 @@ define([
let tableApi = this.api();
if($(cell).is(':empty')){
$(cell).removeClass(config.dataTableActionCellClass + ' ' + config.moduleHeadlineIconClass);
$(cell).removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
}else{
$(cell).on('click', function(e){
// get current row data (important!)
@@ -550,12 +739,12 @@ define([
title: '',
orderable: false,
width: 10,
className: ['text-center', config.dataTableActionCellClass, 'all'].join(' '),
className: ['text-center', config.tableCellActionClass, 'all'].join(' '),
data: null,
render: {
display: data => {
let icon = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
if(data.corporation.id !== corporationId){
if(data.rowGroupData.id !== corporationId){
icon = '<i class="fas fa-ban txt-color txt-color-grayLight" title="restricted" data-placement="left"></i>';
}
return icon;
@@ -565,7 +754,7 @@ define([
let tableApi = this.api();
if($(cell).find('.fa-ban').length){
$(cell).removeClass(config.dataTableActionCellClass + ' ' + config.moduleHeadlineIconClass);
$(cell).removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
$(cell).find('i').tooltip();
}else{
let confirmationSettings = {
@@ -602,9 +791,9 @@ define([
}
},{
targets: 10,
name: 'corporation',
name: 'rowGroupData',
className: 'never', // never show this column. see: https://datatables.net/extensions/responsive/classes
data: 'corporation',
data: 'rowGroupData',
visible: false,
render: {
sort: function(data){
@@ -613,40 +802,6 @@ define([
}
}
],
drawCallback: function(settings){
let tableApi = this.api();
let columnCount = tableApi.columns(':visible').count();
let rows = tableApi.rows( {page:'current'} ).nodes();
let last= null;
tableApi.column('corporation:name', {page:'current'} ).data().each( function(group, i ){
if( !last || last.id !== group.id ){
$(rows).eq(i).before(
'<tr class="group">' +
'<td></td>' +
'<td class="' + config.tableCellImageClass + '">' +
'<img src="' + Util.eveImageUrl('corporation', group.id) + '"/>' +
'</td>' +
'<td colspan="' + (columnCount - 2 ) + '">' + group.name + '</td>' +
'</tr>'
);
last = group;
}
});
let animationRows = rows.to$().filter(function(){
return (
$(this).data('animationStatus') ||
$(this).data('animationTimer')
);
});
for(let i = 0; i < animationRows.length; i++){
let animationRow = $(animationRows[i]);
animationRow.pulseBackgroundColor(animationRow.data('animationStatus'));
animationRow.removeData('animationStatus');
}
},
initComplete: function(settings){
// table data is load in updateModule() method
// -> no need to trigger additional ajax call here for data
@@ -655,15 +810,259 @@ define([
Counter.initTableCounter(this, ['updated:name'], 'd');
}
});
};
new $.fn.dataTable.Responsive(tableApi);
$.extend(true, structureDataTableOptions, getDataTableDefaults());
let tableApiStructure = structureTable.DataTable(structureDataTableOptions);
tableApi.on('responsive-resize', function(e, tableApi, columns){
new $.fn.dataTable.Responsive(tableApiStructure);
tableApiStructure.on('responsive-resize', function(e, tableApi, columns){
// rowGroup length changes as well -> trigger draw() updates rowGroup length (see drawCallback())
tableApi.draw();
});
if(showStationTable){
// "Stations" table ---------------------------------------------------------------------------------------
moduleElement.append(
$('<div>', {
class: config.moduleHeadClass
}).append(
$('<h5>', {
class: config.moduleHandlerClass
}),
$('<h5>', {
text: 'Stations'
})
)
);
let stationTable = $('<table>', {
id: getTableId('station', mapId, systemData.id),
class: ['compact', 'stripe', 'order-column', 'row-border', 'pf-table-fixed', config.systemStationsTableClass].join(' ')
});
moduleElement.append(stationTable);
let stationDataTableOptions = {
pfMeta: {
type: 'stations'
},
order: [[1, 'asc' ], [8, 'asc' ]],
rowId: rowData => getRowId('stations', rowData.id),
language: {
emptyTable: 'No stations found',
info: '_START_ to _END_ of _MAX_',
infoEmpty: ''
},
columnDefs: [
{
targets: 0,
name: 'stationImage',
title: '',
width: 24,
orderable: false,
className: [config.tableCellImageClass, 'text-center', 'all'].join(' '),
data: 'type.id',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display' && value){
value = '<img src="' + Util.eveImageUrl('type', value) +'"/>';
}
return value;
}
}
},{
targets: 1,
name: 'count',
title: '',
width: 5,
className: ['text-center', 'all'].join(' '),
data: 'name',
render: {
_: function(cellData, type, rowData, meta){
let value = '';
if(cellData){
let matches = /^(?<system>[a-z0-9\s\-]+) (?<count>[MDCLXVI]+) .*$/i.exec(cellData);
let count = Util.getObjVal(matches, 'groups.count');
if(type === 'display'){
value = count || 0;
}else{
value = romanToInt(count) || '';
}
}
return value;
}
}
},{
targets: 2,
name: 'name',
title: 'station',
className: [config.tableCellEllipsisClass, 'all'].join(' '),
data: 'name',
render: {
_: function(cellData, type, rowData, meta){
let value = cellData;
if(cellData){
let matches = /^(?<system>[a-z0-9\s\-]+) (?<count>[MDCLXVI]+) (?<label>\(\w+\)\s)?\- (?<moon>moon (?<moonCount>\d)+)?.*$/i.exec(cellData);
let systemName = Util.getObjVal(matches, 'groups.system');
let count = Util.getObjVal(matches, 'groups.count');
let moon = Util.getObjVal(matches, 'groups.moon');
if(systemName === (Util.getObjVal(systemData, 'name') || '')){
value = value.slice(systemName.length).trim();
if(count){
value = value.slice(count.length).trimLeftChars(' \-');
}
if(moon){
let moonCount = Util.getObjVal(matches, 'groups.moonCount');
value = value.replace(moon, 'M' + moonCount);
}
}
}
return value;
}
}
},{
targets: 3,
name: 'stationType',
title: 'type',
width: 100,
className: [config.tableCellEllipsisClass, 'not-screen-l'].join(' '),
data: 'type.name',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
display: (cellData, type, rowData, meta) => {
let value = cellData;
if(value){
let rowGroupDataName = Util.getObjVal(rowData, 'rowGroupData.name') || '';
if(value.indexOf(rowGroupDataName) === 0){
value = value.slice(rowGroupDataName.length).trim();
}
}
return value;
}
}
},{
targets: 4,
name: 'ownerImage',
title: '',
width: 24,
orderable: false,
className: [config.tableCellImageClass, 'text-center', 'all'].join(' '),
data: 'corporation.id',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
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) + '"/>';
value += '</a>';
}
return value;
}
}
},{
targets: 5,
name: 'ownerName',
title: 'owner',
width: 80,
className: [config.tableCellActionClass, config.tableCellEllipsisClass, 'all'].join(' '),
data: 'corporation',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
let value = data.name;
if(type === 'display'){
value += '&nbsp;' + getIconForInformationWindow();
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open corporation information window (ingame)
$(cell).on('click', { tableApi: this.api() }, function(e){
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(cellData.id);
});
}
},{
targets: 6,
title: '<i title="set&nbsp;destination" data-toggle="tooltip" class="fas fa-flag text-right"></i>',
orderable: false,
searchable: false,
width: 10,
class: [config.tableCellActionClass, config.moduleHeadlineIconClass, 'text-center', 'all'].join(' '),
data: 'id',
render: {
display: (cellData, type, rowData, meta) => {
if(cellData){
return '<i class="fas fa-flag"></i>';
}
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).on('click', function(e){
Util.setDestination('set_destination', 'station', {id: cellData, name: rowData.name});
});
}
},{
targets: 7,
title: '<i title="services" data-toggle="tooltip" class="fas fa-tools text-right"></i>',
orderable: false,
searchable: false,
width: 10,
class: [config.tableCellActionClass, config.moduleHeadlineIconClass, config.tableCellServicesClass, Util.config.popoverTriggerClass, 'text-center', 'all'].join(' '),
data: 'services',
defaultContent: '<i class="fas fa-ban txt-color txt-color-grayLight"></i>',
render: {
display: (cellData, type, rowData, meta) => {
if(cellData && cellData.length){
return '<i class="fas fa-tools"></i>';
}
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let cellElement = $(cell);
if(cellElement.find('.fa-ban').length){
cellElement.removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
}
}
},{
targets: 8,
name: 'rowGroupData',
className: 'never', // never show this column. see: https://datatables.net/extensions/responsive/classes
data: 'rowGroupData',
visible: false,
render: {
sort: function(data){
return data.name;
}
}
}
],
initComplete: function(settings, json){
let tableApi = this.api();
initStationServiceTooltip(this, tableApi);
}
};
$.extend(true, stationDataTableOptions, getDataTableDefaults());
let tableApiStation = stationTable.DataTable(stationDataTableOptions);
new $.fn.dataTable.Responsive(tableApiStation);
tableApiStation.on('responsive-resize', function(e, tableApi, columns){
// rowGroup length changes as well -> trigger draw() updates rowGroup length (see drawCallback())
tableApi.draw();
});
}
// init tooltips for this module
let tooltipElements = moduleElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip({
@@ -743,7 +1142,7 @@ define([
Util.request('POST', 'structure', [], structureData, context, context => context.moduleElement.hideLoadingAnimation())
.then(
payload => callbackUpdateStructureRows(payload.context, {structures: payload.data}),
payload => callbackUpdateTableRows(payload.context, payload.data),
Util.handleAjaxErrorResponse
);
};
@@ -782,16 +1181,27 @@ define([
*/
let updateModule = (moduleElement, systemData) => {
// update structure table data
let structureTableElement = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApi = structureTableElement.DataTable();
// update structure table data --------------------------------------------------------------------------------
let structureTable = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApiStructure = structureTable.DataTable();
let context = {
tableApi: tableApi,
let structureContext = {
tableApi: tableApiStructure,
removeMissing: true
};
callbackUpdateStructureRows(context, systemData);
callbackUpdateTableRows(structureContext, Util.getObjVal(systemData, 'structures'));
// update station table data ----------------------------------------------------------------------------------
let stationTable = moduleElement.find('.' + config.systemStationsTableClass);
let tableApiStation = stationTable.DataTable();
let stationContext = {
tableApi: tableApiStation,
removeMissing: false
};
callbackUpdateTableRows(stationContext, Util.getObjVal(systemData, 'stations'), 'stations');
moduleElement.hideLoadingAnimation();
};
@@ -803,7 +1213,6 @@ define([
* @param systemData
*/
let initModule = (moduleElement, mapId, systemData) => {
let structureTableElement = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApi = structureTableElement.DataTable();
@@ -828,7 +1237,7 @@ define([
removeMissing: true
},
context => context.moduleElement.hideLoadingAnimation()
).then(payload => callbackUpdateStructureRows(payload.context, payload.data));
).then(payload => callbackUpdateTableRows(payload.context, Util.getObjVal(payload.data, 'structures')));
});
// init listener for global "past" dScan into this page -------------------------------------------------------
@@ -845,9 +1254,12 @@ define([
* @param moduleElement
*/
let beforeDestroy = moduleElement => {
let structureTableElement = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApi = structureTableElement.DataTable();
tableApi.destroy();
let structureTable = moduleElement.find('.' + config.systemStructuresTableClass);
let stationTable = moduleElement.find('.' + config.systemStationsTableClass);
let tableApiStructure = structureTable.DataTable();
let tableApiStation = stationTable.DataTable();
tableApiStructure.destroy();
tableApiStation.destroy();
};
return {

View File

@@ -1219,7 +1219,7 @@ define([
placement: 'top',
container: 'body',
content: content
}).data('bs.popover').tip().addClass('pf-popover');
}).data('bs.popover').tip().addClass(Util.config.popoverClass);
});
// set popup "close" observer

View File

@@ -169,7 +169,7 @@ define([
* @param tableType
* @returns {string}
*/
let getTableId = (mapId, systemId, tableType) => Util.getTableId(config.sigTableId, mapId, systemId, tableType);
let getTableId = (tableType, mapId, systemId) => Util.getTableId(config.sigTableId, tableType, mapId, systemId);
/**
* get a dataTableApi instance from global cache
@@ -2169,7 +2169,7 @@ define([
let infoElement = $(dialogElement).find('#' + config.sigInfoId);
let table = $('<table>', {
id: getTableId(mapId, systemData.id, 'info'),
id: getTableId('info', mapId, systemData.id),
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTableInfoClass].join(' ')
});
@@ -2216,7 +2216,7 @@ define([
// create "empty table for new signature
let table = $('<table>', {
id: getTableId(mapId, systemData.id, 'secondary'),
id: getTableId('secondary', mapId, systemData.id),
class: ['stripe', 'row-border', 'compact', 'nowrap', config.sigTableClass, config.sigTableSecondaryClass].join(' ')
});
@@ -2497,7 +2497,7 @@ define([
};
/**
* init character info Tooltips
* init character info tooltips
* -> e.g. table cell 'question mark' icon
* @param element
* @param tableApi
@@ -2528,7 +2528,7 @@ define([
*/
let drawSignatureTable = (moduleElement, mapId, systemData) => {
let table = $('<table>', {
id: getTableId(mapId, systemData.id, 'primary'),
id: getTableId('primary', mapId, systemData.id),
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTablePrimaryClass].join(' ')
});

View File

@@ -78,9 +78,11 @@ define([
animationPulseClassPrefix: 'pf-animation-pulse-', // class prefix for "pulse" background animation
// popover
popoverClass: 'pf-popover', // class for "popover" - custom modifier
popoverTriggerClass: 'pf-popover-trigger', // class for "popover" trigger elements
popoverSmallClass: 'popover-small', // class for small "popover"
popoverCharacterClass: 'pf-popover-character', // class for character "popover"
popoverListIconClass: 'pf-popover-list-icon', // class for list "icon"s in "
// Summernote
summernoteClass: 'pf-summernote', // class for Summernote "WYSIWYG" elements
@@ -606,7 +608,7 @@ define([
container: 'body',
content: content,
animation: false
}).data('bs.popover').tip().addClass('pf-popover');
}).data('bs.popover').tip().addClass(config.popoverClass);
button.popover('show');
@@ -1013,6 +1015,22 @@ define([
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();
};
@@ -1786,6 +1804,8 @@ define([
characterLogLocation: valueChanged('character.logLocation'),
characterSystemId: valueChanged('character.log.system.id'),
characterShipType: valueChanged('character.log.ship.typeId'),
characterStationId: valueChanged('character.log.station.id'),
characterStructureId: valueChanged('character.log.structure.id'),
charactersIds: oldCharactersIds.toString() !== newCharactersIds.toString(),
characterLogHistory: oldHistoryLogStamps.toString() !== newHistoryLogStamps.toString()
};
@@ -2886,11 +2906,12 @@ define([
};
/**
* set new destination for a system
* @param systemData
* set new destination for a system/station/structure
* @param type
* @param destType
* @param destData
*/
let setDestination = (systemData, type) => {
let setDestination = (type, destType, destData) => {
let description = '';
switch(type){
case 'set_destination':
@@ -2910,22 +2931,20 @@ define([
data: {
clearOtherWaypoints: (type === 'set_destination') ? 1 : 0,
first: (type === 'add_last_waypoint') ? 0 : 1,
systemData: [{
systemId: systemData.systemId,
name: systemData.name
}]
destData: [destData]
},
context: {
destType: destType,
description: description
},
dataType: 'json'
}).done(function(responseData){
if(
responseData.systemData &&
responseData.systemData.length > 0
responseData.destData &&
responseData.destData.length > 0
){
for(let j = 0; j < responseData.systemData.length; j++){
showNotify({title: this.description, text: 'System: ' + responseData.systemData[j].name, type: 'success'});
for(let j = 0; j < responseData.destData.length; j++){
showNotify({title: this.description, text: this.destType + ': ' + responseData.destData[j].name, type: 'success'});
}
}
@@ -2934,7 +2953,7 @@ define([
responseData.error.length > 0
){
for(let i = 0; i < responseData.error.length; i++){
showNotify({title: this.description + ' error', text: 'System: ' + responseData.error[i].message, type: 'error'});
showNotify({title: this.description + ' error', text: this.destType + ': ' + responseData.error[i].message, type: 'error'});
}
}
@@ -3254,7 +3273,16 @@ define([
* @param tableType
* @returns {string}
*/
let getTableId = (prefix, mapId, systemId, tableType) => prefix + [mapId, systemId, tableType].join('-');
let getTableId = (prefix, tableType, mapId, systemId) => prefix + [tableType, mapId, systemId].join('-');
/**
* get dataTable row id
* @param prefix
* @param tableType
* @param rowId
* @returns {string}
*/
let getTableRowId = (prefix, tableType, rowId) => prefix + [tableType, rowId].join('-');
/**
* get a dataTableApi instance from global cache
@@ -3266,7 +3294,7 @@ define([
*/
let getDataTableInstance = (prefix, mapId, systemId, tableType) => {
let instance = null;
let table = $.fn.dataTable.tables({ visible: false, api: true }).table('#' + getTableId(prefix, mapId, systemId, tableType));
let table = $.fn.dataTable.tables({ visible: false, api: true }).table('#' + getTableId(prefix, tableType, mapId, systemId));
if(table.node()){
instance = table;
}
@@ -3522,6 +3550,7 @@ define([
getBrowserTabId: getBrowserTabId,
singleDoubleClick: singleDoubleClick,
getTableId: getTableId,
getTableRowId: getTableRowId,
getDataTableInstance: getDataTableInstance,
htmlEncode: htmlEncode,
htmlDecode: htmlDecode,

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.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -137,7 +137,8 @@ define([
// mark as init
tableElement.attr('data-counter', 'init');
let refreshIntervalId = window.setInterval(() => {
let updateTableCount = () => {
tableApi.cells(null, columnSelector).every(function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
let cell = this;
let node = cell.node();
@@ -148,7 +149,9 @@ define([
updateDateDiff( cell.nodes().to$(), date, round);
}
});
}, 500);
};
let refreshIntervalId = window.setInterval(updateTableCount, 500);
tableElement.data('interval', refreshIntervalId);
};

View File

@@ -163,7 +163,7 @@ define([
{icon: 'fa-route', action: 'find_route', text: 'find route'},
{icon: 'fa-object-group', action: 'select_connections', text: 'select connections'},
{icon: 'fa-reply fa-rotate-180', text: 'waypoints', subitems: [
{subIcon: 'fa-flag-checkered', subAction: 'set_destination', subText: 'set destination'},
{subIcon: 'fa-flag', subAction: 'set_destination', subText: 'set destination'},
{subDivider: true, action: ''},
{subIcon: 'fa-step-backward', subAction: 'add_first_waypoint', subText: 'add new [start]'},
{subIcon: 'fa-step-forward', subAction: 'add_last_waypoint', subText: 'add new [end]'}

View File

@@ -422,7 +422,7 @@ define([
_: (data, type, row, meta) => {
let value = data.typeName;
if(type === 'display'){
value = '<img src="' + Util.eveImageUrl('render', data.typeId) + '_32.png"/>';
value = '<img src="' + Util.eveImageUrl('render', data.typeId) + '"/>';
}
return value;
}

View File

@@ -1763,6 +1763,91 @@ define([
});
};
/**
* add station services tooltip
* @param services
* @param options
* @returns {*}
*/
$.fn.addStationServiceTooltip = function(services, options){
let getServiceIcon = service => {
switch(service){
case 'bounty-missions': return false;
case 'assasination-missions': return false;
case 'courier-missions': return false;
case 'interbus': return false;
case 'reprocessing-plant': return 'reprocess';
case 'refinery': return false;
case 'market': return 'market';
case 'black-market': return false;
case 'stock-exchange': return false;
case 'cloning': return 'clonebay';
case 'surgery': return false;
case 'dna-therapy': return false;
case 'repair-facilities': return 'repairshop';
case 'factory': return 'industry';
case 'labratory': return 'research';
case 'gambling': return false;
case 'fitting': return 'fitting';
case 'paintshop': return 'skins';
case 'news': return false;
case 'storage': return false;
case 'insurance': return 'insurance';
case 'docking': return 'docking';
case 'office-rental': return false;
case 'jump-clone-facility': return 'jumpclones';
case 'loyalty-point-store': return 'lpstore';
case 'navy-offices': return 'factionalwarfare';
case 'security-offices': return 'concord';
default: return false;
}
};
let getStationServicesTable = services => {
let content = '';
for(let i = 0; i < services.length; i++){
let icon = getServiceIcon(services[i]);
if(icon){
content += '<img class="' + Util.config.popoverListIconClass + '" src="/public/img/icons/client/ui/window/' + icon + '.png" alt="' + services[i] + '">';
}
}
return content;
};
let content = getStationServicesTable(services);
let title = '<i class="fas fa-tools fa-fw"></i>&nbsp;Services';
let defaultOptions = {
placement: 'top',
html: true,
trigger: 'hover',
container: 'body',
title: title,
content: '',
delay: {
show: 150,
hide: 0
},
};
options = $.extend({}, defaultOptions, options);
return this.each(function(){
let element = $(this);
element.popover(options);
// set new popover content
let popover = element.data('bs.popover');
popover.options.content = content;
popover.tip().addClass(Util.config.popoverClass);
if(options.show){
element.popover('show');
}
});
};
/**
* add system effect tooltip
* @param security
@@ -1807,7 +1892,6 @@ define([
* @returns {*}
*/
$.fn.addSystemPlanetsTooltip = function(planets, options){
let content = Util.getSystemPlanetsTable(planets);
let defaultOptions = {

View File

@@ -1012,7 +1012,13 @@ define([
if(changes.charactersIds){
updateTasks.push(updateHeaderCharacterSwitch(userData, changes.characterId));
}
if(changes.characterSystemId || changes.characterShipType || changes.characterLogHistory){
if(
changes.characterSystemId ||
changes.characterShipType ||
changes.characterStationId ||
changes.characterStructureId ||
changes.characterLogHistory
){
updateTasks.push(updateHeaderCharacterLocation(userData, changes.characterShipType));
}
@@ -1087,6 +1093,16 @@ define([
let shipTypeId = Util.getObjVal(shipData, 'typeId') || 0;
let shipTypeName = Util.getObjVal(shipData, 'typeName') || '';
let stationData = Util.getObjVal(userData, 'character.log.station');
let stationId = Util.getObjVal(stationData, 'id') || 0;
let stationName = Util.getObjVal(stationData, 'name') || '';
let structureData = Util.getObjVal(userData, 'character.log.structure');
let structureTypeId = Util.getObjVal(structureData, 'type.id') || 0;
let structureTypeName = Util.getObjVal(structureData, 'type.name') || '';
let structureId = Util.getObjVal(structureData, 'id') || 0;
let structureName = Util.getObjVal(structureData, 'name') || '';
logDataAll.push(logData);
// check for log history data as well
@@ -1116,6 +1132,12 @@ define([
if(isCurrentLocation){
breadcrumbHtml += '<i class="fas fa-fw fa-map-marker-alt" title="current location"></i>';
if(stationId > 0){
breadcrumbHtml += '<i class="fas fa-home" title="' + stationName + '"></i>';
}else if(structureId > 0){
breadcrumbHtml += '<i class="fas fa-industry" title="' + structureTypeName + ' &quot;' + structureName + '&quot;"></i>';
}
}
breadcrumbHtml += systemName;

View File

@@ -205,7 +205,7 @@ define([
let systemsElement = $(this).empty();
let systemTable = $('<table>', {
id: Util.getTableId(config.tableId, mapData.config.id, '', 'systems'),
id: Util.getTableId(config.tableId, 'systems', mapData.config.id, ''),
class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ')
});
systemsElement.append(systemTable);
@@ -864,7 +864,7 @@ define([
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
// open corporation information window (ingame)
$(cell).on('click', { tableApi: this.api() }, function(e){
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(cellData.id);

View File

@@ -188,6 +188,61 @@ define([
moduleElement.find('.' + config.systemSovSectionClass + ' .' + Util.config.dynamicAreaClass).hideLoadingAnimation();
};
/**
* @param pages
* @param systemData
*/
let getThirdPartySystemLinks = (pages, systemData) => {
let urls = {};
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;
for(let i = 0; i < pages.length; i++){
let url = false;
let domain = Util.getObjVal(Init, 'url.' + pages[i]);
if(domain || pages[i] === 'eve'){
switch(pages[i]){
case 'eve':
url = 'https://client'; // fake url
break;
case 'dotlan':
systemName = systemName.replace(/ /g, '_');
regionName = regionName.replace(/ /g, '_');
if(isWormhole){
url = domain + '/system/' + systemName;
}else{
url = domain + '/map/' + regionName + '/' + systemName;
}
break;
case 'eveeye':
if(!isWormhole){
url = domain + '/?m=' + encodeURIComponent(regionName) + '&s=' + encodeURIComponent(systemName.replace(/ /g, '_'));
url += '&t=eswkc&o=thera,con_svc,node_sov,sub_sec,sector_fac,tag_mk';
}
break;
case 'anoik':
if(isWormhole){
url = domain + '/systems/' + systemName;
}
break;
}
if(url){
let urlObj = new URL(url);
urls[++validUrls + '_url'] = {
page: pages[i],
domain: urlObj.hostname,
url: url
};
}
}
}
return urls;
};
/**
* get module element
* @param parentElement
@@ -294,7 +349,6 @@ define([
systemUrl: MapUtil.getMapDeeplinkUrl(mapId, systemData.id),
systemTypeName: MapUtil.getSystemTypeInfo(systemData.type.id, 'name'),
systemIsWormhole: MapUtil.getSystemTypeInfo(systemData.type.id, 'name') === 'w-space',
systemStatusId: systemData.status.id,
systemStatusClass: Util.getStatusInfoForSystem(systemData.status.id, 'class'),
systemStatusLabel: Util.getStatusInfoForSystem(systemData.status.id, 'label'),
@@ -326,6 +380,7 @@ define([
systemTypeLinkClass: config.typeLinkClass,
systemUrlLinkClass: config.urlLinkClass,
ccpImageServerUrl: Init.url.ccpImageServer,
thirdPartyLinks: getThirdPartySystemLinks(['eve', 'dotlan', 'eveeye', 'anoik'], systemData)
};
requirejs(['text!templates/modules/system_info.html', 'mustache', 'summernote.loader'], (template, Mustache, Summernote) => {

View File

@@ -7,8 +7,9 @@ define([
'app/init',
'app/util',
'bootbox',
'app/counter'
], ($, Init, Util, bootbox, Counter) => {
'app/counter',
'app/map/util',
], ($, Init, Util, bootbox, Counter, MapUtil) => {
'use strict';
let config = {
@@ -18,7 +19,7 @@ define([
moduleHeadClass: 'pf-module-head', // class for module header
moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler
// system info module
// system intel module
moduleTypeClass: 'pf-system-intel-module', // class for this module
// headline toolbar
@@ -28,10 +29,14 @@ define([
moduleHeadlineIconRefreshClass: 'pf-module-icon-button-refresh', // class for "refresh" icon
// system intel module
systemStructuresTableClass: 'pf-system-structure-table', // class for route tables
intelTableId: 'pf-intel-table-', // id prefix for all tables in module
intelTableRowIdPrefix: 'pf-intel-row-', // id prefix for table rows
systemStationsTableClass: 'pf-system-station-table', // class for NPC owned stations table
systemStructuresTableClass: 'pf-system-structure-table', // class for player owned structures table
// structure dialog
structureDialogId: 'pf-structure-dialog', // id for "structure" dialog
nameInputId: 'pf-structure-dialog-name-input', // id for "name" input
statusSelectId: 'pf-structure-dialog-status-select', // id for "status" select
typeSelectId: 'pf-structure-dialog-type-select', // id for "type" select
corporationSelectId: 'pf-structure-dialog-corporation-select', // id for "corporation" select
@@ -39,11 +44,12 @@ define([
descriptionTextareaCharCounter: 'pf-form-field-char-count', // class for "character counter" element for form field
// dataTable
tableRowIdPrefix: 'pf-structure-row_', // id prefix for table rows
tableCellImageClass: 'pf-table-image-smaller-cell', // class for table "image" cells
tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells
tableCellEllipsisClass: 'pf-table-cell-ellipses-auto', // class for table "ellipsis" cells
dataTableActionCellClass: 'pf-table-action-cell' // class for "action" 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
};
let maxDescriptionLength = 512;
@@ -53,26 +59,102 @@ define([
* @param statusData
* @returns {string}
*/
let getStatusData = statusData => {
let getIconForStatusData = statusData => {
return '<i class="fas fa-fw fa-circle ' + statusData.class + '" title="' + statusData.label + '"></i>';
};
/**
* get icon that marks a table cell as clickable
* @returns {string}
*/
let getIconForInformationWindow = () => {
return '<i class="fas fa-fw fa-id-card ' + config.tableCellActionIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
};
/**
* get dataTable id
* @param mapId
* @param systemId
* @param tableType
* @returns {string}
*/
let getTableId = (tableType, mapId, systemId) => Util.getTableId(config.intelTableId, tableType, mapId, systemId);
/**
* get dataTable row id
* @param tableType
* @param id
* @returns {string}
*/
let getRowId = (tableType, id) => Util.getTableRowId(config.intelTableRowIdPrefix, tableType, id);
/**
* get <tr> DOM id by id
* @param tableApi
* @param id
* @returns {*}
*/
let getRowId = (tableApi, id) => {
return tableApi.rows().ids().toArray().find(rowId => rowId === config.tableRowIdPrefix + id);
let getRowById = (tableApi, id) => {
return tableApi.rows().ids().toArray().find(rowId => rowId === getRowId(Util.getObjVal(getTableMetaData(tableApi), 'type'), id));
};
/**
* callback -> add structure rows from systemData
* @param context
* @param systemData
* get custom "metaData" from dataTables API
* @param tableApi
* @returns {*}
*/
let callbackUpdateStructureRows = (context, systemData) => {
let getTableMetaData = tableApi => {
let data = null;
if(tableApi){
data = tableApi.init().pfMeta;
}
return data;
};
/**
* vormat roman numeric string to int
* -> e.g. 'VII' => 7
* @param str
* @returns {number}
*/
let romanToInt = str => {
let charToTnt = char => {
switch (char) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return -1;
}
};
if(str == null) return -1;
let num = charToTnt(str.charAt(0));
let pre, curr;
for(let i = 1; i < str.length; i++){
curr = charToTnt(str.charAt(i));
pre = charToTnt(str.charAt(i - 1));
if(curr <= pre){
num += curr;
}else{
num = num - pre * 2 + curr;
}
}
return num;
};
/**
* callback -> add table rows from grouped tableData
* @param context
* @param tableData
* @param groupedDataKey
*/
let callbackUpdateTableRows = (context, tableData, groupedDataKey = 'structures') => {
let touchedRows = [];
let hadData = context.tableApi.rows().any();
let notificationCounter = {
@@ -81,41 +163,39 @@ define([
deleted: 0
};
if(systemData){
let corporations = Util.getObjVal(systemData, 'structures');
if(corporations){
for(let [corporationId, corporationData] of Object.entries(corporations)){
if(corporationData.structures && corporationData.structures.length){
for(let structureData of corporationData.structures){
let rowId = getRowId(context.tableApi, structureData.id);
if(tableData){
for(let [rowGroupId, rowGroupData] of Object.entries(tableData)){
if(rowGroupData[groupedDataKey] && rowGroupData[groupedDataKey].length){
for(let rowData of rowGroupData[groupedDataKey]){
let rowId = getRowById(context.tableApi, rowData.id);
// add corporation data
structureData.corporation = {
id: corporationData.id,
name: corporationData.name
};
// add rowGroupData as well to each rowData
rowData.rowGroupData = {
id: rowGroupData.id,
name: rowGroupData.name,
groupedDataKey: groupedDataKey
};
if(rowId){
// update row
let api = context.tableApi.row('#' + rowId);
let rowData = api.data();
if(rowId){
// update row
let api = context.tableApi.row('#' + rowId);
let rowDataCurrent = api.data();
// check for update
if(rowData.updated.updated !== structureData.updated.updated){
// row data changed -> update
api.data(structureData);
notificationCounter.changed++;
}
touchedRows.push(api.id());
}else{
// insert new row
let api = context.tableApi.row.add(structureData);
api.nodes().to$().data('animationStatus', 'added');
notificationCounter.added++;
touchedRows.push(api.id());
// check for update
if(rowDataCurrent.updated.updated !== rowData.updated.updated){
// row data changed -> update
api.data(rowData);
notificationCounter.changed++;
}
touchedRows.push(api.id());
}else{
// insert new row
let api = context.tableApi.row.add(rowData);
api.nodes().to$().data('animationStatus', 'added');
notificationCounter.added++;
touchedRows.push(api.id());
}
}
}
@@ -156,7 +236,7 @@ define([
let deletedCounter = 0;
if(structureIds && structureIds.length){
for(let structureId of structureIds){
let rowId = getRowId(context.tableApi, structureId);
let rowId = getRowById(context.tableApi, structureId);
if(rowId){
context.tableApi.row('#' + rowId).remove();
deletedCounter++;
@@ -214,10 +294,36 @@ define([
return data;
});
// if current user is currently docked at a structure (not station)
// -> add a modal button for pre-fill modal with it
// -> systemId must match systemId from current character log
let currentUserData = Util.getCurrentUserData();
let isCurrentLocation = false;
let characterStructureId = Util.getObjVal(currentUserData, 'character.log.structure.id') || 0;
let characterStructureName = Util.getObjVal(currentUserData, 'character.log.structure.name') || '';
let characterStructureTypeId = Util.getObjVal(currentUserData, 'character.log.structure.type.id') || 0;
let characterStructureTypeName = Util.getObjVal(currentUserData, 'character.log.structure.type.name') || '';
if(systemId === Util.getObjVal(currentUserData, 'character.log.system.id')){
isCurrentLocation = true;
}
let disableButtonAutoFill = true;
let buttonLabelAutoFill = '<i class="fas fa-fw fa-map-marker-alt"></i>&nbsp;';
if(characterStructureId){
buttonLabelAutoFill += characterStructureTypeName + ' "' + characterStructureName + '"';
if(isCurrentLocation){
disableButtonAutoFill = false;
}
}else{
buttonLabelAutoFill += 'unknown structure';
}
let data = {
id: config.structureDialogId,
structureData: structureData,
structureStatus: statusData,
nameInputId: config.nameInputId,
statusSelectId: config.statusSelectId,
typeSelectId: config.typeSelectId,
corporationSelectId: config.corporationSelectId,
@@ -236,7 +342,18 @@ define([
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
className: 'btn-default pull-left'
},
autoFill: {
label: buttonLabelAutoFill,
className: 'btn-primary' + (disableButtonAutoFill ? ' pf-font-italic disabled' : ''),
callback: function(){
let form = this.find('form');
form.find('#' + config.nameInputId).val(characterStructureName);
form.find('#' + config.statusSelectId).val(2).trigger('change');
form.find('#' + config.typeSelectId).val(characterStructureTypeId).trigger('change');
return false;
}
},
success: {
label: '<i class="fas fa-fw fa-check"></i>&nbsp;save',
@@ -268,7 +385,7 @@ define([
},
context => context.moduleElement.hideLoadingAnimation()
).then(
payload => callbackUpdateStructureRows(payload.context, {structures: payload.data}),
payload => callbackUpdateTableRows(payload.context, payload.data),
Util.handleAjaxErrorResponse
);
}else{
@@ -361,6 +478,83 @@ define([
});
};
/**
* init station services tooltips
* @param element
* @param tableApi
*/
let initStationServiceTooltip = (element, tableApi) => {
element.hoverIntent({
over: function(e){
let cellElement = $(this);
let rowData = tableApi.row(cellElement.parents('tr')).data();
cellElement.addStationServiceTooltip(Util.getObjVal(rowData, 'services'), {
placement: 'left',
trigger: 'manual',
show: true
});
},
out: function(e){
$(this).destroyPopover();
},
selector: 'td.' + config.tableCellServicesClass
});
};
/**
* get dataTables default options for intel tables
* @returns {*}
*/
let getDataTableDefaults = () => {
return {
paging: false,
lengthChange: false,
ordering: true,
info: false,
searching: false,
hover: false,
autoWidth: false,
drawCallback: function (settings) {
let tableApi = this.api();
let columnCount = tableApi.columns(':visible').count();
let rows = tableApi.rows({page: 'current'}).nodes();
let last = null;
tableApi.column('rowGroupData:name', {page: 'current'}).data().each(function (group, i) {
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';
$(rows).eq(i).before(
'<tr class="group">' +
'<td></td>' +
'<td class="text-right ' + config.tableCellImageClass + '">' +
'<img src="' + Util.eveImageUrl(imgType, group.id) + '"/>' +
'</td>' +
'<td colspan="' + Math.max((columnCount - 2), 1) + '">' + group.name + '</td>' +
'</tr>'
);
last = group;
}
});
let animationRows = rows.to$().filter(function () {
return (
$(this).data('animationStatus') ||
$(this).data('animationTimer')
);
});
for (let i = 0; i < animationRows.length; i++) {
let animationRow = $(animationRows[i]);
animationRow.pulseBackgroundColor(animationRow.data('animationStatus'));
animationRow.removeData('animationStatus');
}
}
};
};
/**
* get module element
* @param parentElement
@@ -369,6 +563,7 @@ define([
* @returns {jQuery}
*/
let getModule = (parentElement, mapId, systemData) => {
let showStationTable = ['H', 'L', '0.0', 'C12'].includes(Util.getObjVal(systemData, 'security'));
let corporationId = Util.getCurrentUserInfo('corporationId');
let moduleElement = $('<div>').append(
@@ -395,35 +590,29 @@ define([
}).attr('data-html', 'true').attr('data-toggle', 'tooltip')
),
$('<h5>', {
text: 'Intel'
text: 'Structures'
})
)
);
let table = $('<table>', {
// "Structures" table -----------------------------------------------------------------------------------------
let structureTable = $('<table>', {
id: getTableId('structure', mapId, systemData.id),
class: ['compact', 'stripe', 'order-column', 'row-border', 'pf-table-fixed', config.systemStructuresTableClass].join(' ')
});
moduleElement.append(table);
moduleElement.append(structureTable);
let tableApi = table.DataTable({
paging: false,
lengthChange: false,
ordering: true,
order: [[ 10, 'desc' ], [ 0, 'asc' ]],
info: false,
searching: false,
hover: false,
autoWidth: false,
rowId: rowData => config.tableRowIdPrefix + rowData.id,
let structureDataTableOptions = {
pfMeta: {
type: 'structures'
},
order: [[10, 'desc' ], [0, 'asc' ]],
rowId: rowData => getRowId('structures', rowData.id),
language: {
emptyTable: 'No structures recorded',
info: '_START_ to _END_ of _MAX_',
infoEmpty: ''
},
rowGroup: {
enable: true,
dataSrc: 'systemId'
},
columnDefs: [
{
targets: 0,
@@ -433,17 +622,17 @@ define([
className: ['text-center', 'all'].join(' '),
data: 'status',
render: {
display: data => getStatusData(data),
display: data => getIconForStatusData(data),
sort: data => data.id
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).find('i').tooltip();
$(cell).find('i').tooltip();
}
},{
targets: 1,
name: 'structureImage',
title: '',
width: 26,
width: 24,
orderable: false,
className: [config.tableCellImageClass, 'text-center', 'all'].join(' '),
data: 'structure.id',
@@ -476,7 +665,7 @@ define([
targets: 4,
name: 'ownerImage',
title: '',
width: 26,
width: 24,
orderable: false,
className: [config.tableCellImageClass, 'text-center', 'all'].join(' '),
data: 'owner.id',
@@ -519,12 +708,12 @@ define([
title: '',
orderable: false,
width: 10,
className: ['text-center', config.dataTableActionCellClass, config.moduleHeadlineIconClass, 'all'].join(' '),
className: ['text-center', config.tableCellActionClass, config.moduleHeadlineIconClass, 'all'].join(' '),
data: null,
render: {
display: data => {
let icon = '<i class="fas fa-pen"></i>';
if(data.corporation.id !== corporationId){
if(data.rowGroupData.id !== corporationId){
icon = '';
}
return icon;
@@ -534,7 +723,7 @@ define([
let tableApi = this.api();
if($(cell).is(':empty')){
$(cell).removeClass(config.dataTableActionCellClass + ' ' + config.moduleHeadlineIconClass);
$(cell).removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
}else{
$(cell).on('click', function(e){
// get current row data (important!)
@@ -550,12 +739,12 @@ define([
title: '',
orderable: false,
width: 10,
className: ['text-center', config.dataTableActionCellClass, 'all'].join(' '),
className: ['text-center', config.tableCellActionClass, 'all'].join(' '),
data: null,
render: {
display: data => {
let icon = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
if(data.corporation.id !== corporationId){
if(data.rowGroupData.id !== corporationId){
icon = '<i class="fas fa-ban txt-color txt-color-grayLight" title="restricted" data-placement="left"></i>';
}
return icon;
@@ -565,7 +754,7 @@ define([
let tableApi = this.api();
if($(cell).find('.fa-ban').length){
$(cell).removeClass(config.dataTableActionCellClass + ' ' + config.moduleHeadlineIconClass);
$(cell).removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
$(cell).find('i').tooltip();
}else{
let confirmationSettings = {
@@ -602,9 +791,9 @@ define([
}
},{
targets: 10,
name: 'corporation',
name: 'rowGroupData',
className: 'never', // never show this column. see: https://datatables.net/extensions/responsive/classes
data: 'corporation',
data: 'rowGroupData',
visible: false,
render: {
sort: function(data){
@@ -613,40 +802,6 @@ define([
}
}
],
drawCallback: function(settings){
let tableApi = this.api();
let columnCount = tableApi.columns(':visible').count();
let rows = tableApi.rows( {page:'current'} ).nodes();
let last= null;
tableApi.column('corporation:name', {page:'current'} ).data().each( function(group, i ){
if( !last || last.id !== group.id ){
$(rows).eq(i).before(
'<tr class="group">' +
'<td></td>' +
'<td class="' + config.tableCellImageClass + '">' +
'<img src="' + Util.eveImageUrl('corporation', group.id) + '"/>' +
'</td>' +
'<td colspan="' + (columnCount - 2 ) + '">' + group.name + '</td>' +
'</tr>'
);
last = group;
}
});
let animationRows = rows.to$().filter(function(){
return (
$(this).data('animationStatus') ||
$(this).data('animationTimer')
);
});
for(let i = 0; i < animationRows.length; i++){
let animationRow = $(animationRows[i]);
animationRow.pulseBackgroundColor(animationRow.data('animationStatus'));
animationRow.removeData('animationStatus');
}
},
initComplete: function(settings){
// table data is load in updateModule() method
// -> no need to trigger additional ajax call here for data
@@ -655,15 +810,259 @@ define([
Counter.initTableCounter(this, ['updated:name'], 'd');
}
});
};
new $.fn.dataTable.Responsive(tableApi);
$.extend(true, structureDataTableOptions, getDataTableDefaults());
let tableApiStructure = structureTable.DataTable(structureDataTableOptions);
tableApi.on('responsive-resize', function(e, tableApi, columns){
new $.fn.dataTable.Responsive(tableApiStructure);
tableApiStructure.on('responsive-resize', function(e, tableApi, columns){
// rowGroup length changes as well -> trigger draw() updates rowGroup length (see drawCallback())
tableApi.draw();
});
if(showStationTable){
// "Stations" table ---------------------------------------------------------------------------------------
moduleElement.append(
$('<div>', {
class: config.moduleHeadClass
}).append(
$('<h5>', {
class: config.moduleHandlerClass
}),
$('<h5>', {
text: 'Stations'
})
)
);
let stationTable = $('<table>', {
id: getTableId('station', mapId, systemData.id),
class: ['compact', 'stripe', 'order-column', 'row-border', 'pf-table-fixed', config.systemStationsTableClass].join(' ')
});
moduleElement.append(stationTable);
let stationDataTableOptions = {
pfMeta: {
type: 'stations'
},
order: [[1, 'asc' ], [8, 'asc' ]],
rowId: rowData => getRowId('stations', rowData.id),
language: {
emptyTable: 'No stations found',
info: '_START_ to _END_ of _MAX_',
infoEmpty: ''
},
columnDefs: [
{
targets: 0,
name: 'stationImage',
title: '',
width: 24,
orderable: false,
className: [config.tableCellImageClass, 'text-center', 'all'].join(' '),
data: 'type.id',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display' && value){
value = '<img src="' + Util.eveImageUrl('type', value) +'"/>';
}
return value;
}
}
},{
targets: 1,
name: 'count',
title: '',
width: 5,
className: ['text-center', 'all'].join(' '),
data: 'name',
render: {
_: function(cellData, type, rowData, meta){
let value = '';
if(cellData){
let matches = /^(?<system>[a-z0-9\s\-]+) (?<count>[MDCLXVI]+) .*$/i.exec(cellData);
let count = Util.getObjVal(matches, 'groups.count');
if(type === 'display'){
value = count || 0;
}else{
value = romanToInt(count) || '';
}
}
return value;
}
}
},{
targets: 2,
name: 'name',
title: 'station',
className: [config.tableCellEllipsisClass, 'all'].join(' '),
data: 'name',
render: {
_: function(cellData, type, rowData, meta){
let value = cellData;
if(cellData){
let matches = /^(?<system>[a-z0-9\s\-]+) (?<count>[MDCLXVI]+) (?<label>\(\w+\)\s)?\- (?<moon>moon (?<moonCount>\d)+)?.*$/i.exec(cellData);
let systemName = Util.getObjVal(matches, 'groups.system');
let count = Util.getObjVal(matches, 'groups.count');
let moon = Util.getObjVal(matches, 'groups.moon');
if(systemName === (Util.getObjVal(systemData, 'name') || '')){
value = value.slice(systemName.length).trim();
if(count){
value = value.slice(count.length).trimLeftChars(' \-');
}
if(moon){
let moonCount = Util.getObjVal(matches, 'groups.moonCount');
value = value.replace(moon, 'M' + moonCount);
}
}
}
return value;
}
}
},{
targets: 3,
name: 'stationType',
title: 'type',
width: 100,
className: [config.tableCellEllipsisClass, 'not-screen-l'].join(' '),
data: 'type.name',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
display: (cellData, type, rowData, meta) => {
let value = cellData;
if(value){
let rowGroupDataName = Util.getObjVal(rowData, 'rowGroupData.name') || '';
if(value.indexOf(rowGroupDataName) === 0){
value = value.slice(rowGroupDataName.length).trim();
}
}
return value;
}
}
},{
targets: 4,
name: 'ownerImage',
title: '',
width: 24,
orderable: false,
className: [config.tableCellImageClass, 'text-center', 'all'].join(' '),
data: 'corporation.id',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
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) + '"/>';
value += '</a>';
}
return value;
}
}
},{
targets: 5,
name: 'ownerName',
title: 'owner',
width: 80,
className: [config.tableCellActionClass, config.tableCellEllipsisClass, 'all'].join(' '),
data: 'corporation',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
let value = data.name;
if(type === 'display'){
value += '&nbsp;' + getIconForInformationWindow();
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open corporation information window (ingame)
$(cell).on('click', { tableApi: this.api() }, function(e){
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(cellData.id);
});
}
},{
targets: 6,
title: '<i title="set&nbsp;destination" data-toggle="tooltip" class="fas fa-flag text-right"></i>',
orderable: false,
searchable: false,
width: 10,
class: [config.tableCellActionClass, config.moduleHeadlineIconClass, 'text-center', 'all'].join(' '),
data: 'id',
render: {
display: (cellData, type, rowData, meta) => {
if(cellData){
return '<i class="fas fa-flag"></i>';
}
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).on('click', function(e){
Util.setDestination('set_destination', 'station', {id: cellData, name: rowData.name});
});
}
},{
targets: 7,
title: '<i title="services" data-toggle="tooltip" class="fas fa-tools text-right"></i>',
orderable: false,
searchable: false,
width: 10,
class: [config.tableCellActionClass, config.moduleHeadlineIconClass, config.tableCellServicesClass, Util.config.popoverTriggerClass, 'text-center', 'all'].join(' '),
data: 'services',
defaultContent: '<i class="fas fa-ban txt-color txt-color-grayLight"></i>',
render: {
display: (cellData, type, rowData, meta) => {
if(cellData && cellData.length){
return '<i class="fas fa-tools"></i>';
}
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let cellElement = $(cell);
if(cellElement.find('.fa-ban').length){
cellElement.removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
}
}
},{
targets: 8,
name: 'rowGroupData',
className: 'never', // never show this column. see: https://datatables.net/extensions/responsive/classes
data: 'rowGroupData',
visible: false,
render: {
sort: function(data){
return data.name;
}
}
}
],
initComplete: function(settings, json){
let tableApi = this.api();
initStationServiceTooltip(this, tableApi);
}
};
$.extend(true, stationDataTableOptions, getDataTableDefaults());
let tableApiStation = stationTable.DataTable(stationDataTableOptions);
new $.fn.dataTable.Responsive(tableApiStation);
tableApiStation.on('responsive-resize', function(e, tableApi, columns){
// rowGroup length changes as well -> trigger draw() updates rowGroup length (see drawCallback())
tableApi.draw();
});
}
// init tooltips for this module
let tooltipElements = moduleElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip({
@@ -743,7 +1142,7 @@ define([
Util.request('POST', 'structure', [], structureData, context, context => context.moduleElement.hideLoadingAnimation())
.then(
payload => callbackUpdateStructureRows(payload.context, {structures: payload.data}),
payload => callbackUpdateTableRows(payload.context, payload.data),
Util.handleAjaxErrorResponse
);
};
@@ -782,16 +1181,27 @@ define([
*/
let updateModule = (moduleElement, systemData) => {
// update structure table data
let structureTableElement = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApi = structureTableElement.DataTable();
// update structure table data --------------------------------------------------------------------------------
let structureTable = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApiStructure = structureTable.DataTable();
let context = {
tableApi: tableApi,
let structureContext = {
tableApi: tableApiStructure,
removeMissing: true
};
callbackUpdateStructureRows(context, systemData);
callbackUpdateTableRows(structureContext, Util.getObjVal(systemData, 'structures'));
// update station table data ----------------------------------------------------------------------------------
let stationTable = moduleElement.find('.' + config.systemStationsTableClass);
let tableApiStation = stationTable.DataTable();
let stationContext = {
tableApi: tableApiStation,
removeMissing: false
};
callbackUpdateTableRows(stationContext, Util.getObjVal(systemData, 'stations'), 'stations');
moduleElement.hideLoadingAnimation();
};
@@ -803,7 +1213,6 @@ define([
* @param systemData
*/
let initModule = (moduleElement, mapId, systemData) => {
let structureTableElement = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApi = structureTableElement.DataTable();
@@ -828,7 +1237,7 @@ define([
removeMissing: true
},
context => context.moduleElement.hideLoadingAnimation()
).then(payload => callbackUpdateStructureRows(payload.context, payload.data));
).then(payload => callbackUpdateTableRows(payload.context, Util.getObjVal(payload.data, 'structures')));
});
// init listener for global "past" dScan into this page -------------------------------------------------------
@@ -845,9 +1254,12 @@ define([
* @param moduleElement
*/
let beforeDestroy = moduleElement => {
let structureTableElement = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApi = structureTableElement.DataTable();
tableApi.destroy();
let structureTable = moduleElement.find('.' + config.systemStructuresTableClass);
let stationTable = moduleElement.find('.' + config.systemStationsTableClass);
let tableApiStructure = structureTable.DataTable();
let tableApiStation = stationTable.DataTable();
tableApiStructure.destroy();
tableApiStation.destroy();
};
return {

View File

@@ -1219,7 +1219,7 @@ define([
placement: 'top',
container: 'body',
content: content
}).data('bs.popover').tip().addClass('pf-popover');
}).data('bs.popover').tip().addClass(Util.config.popoverClass);
});
// set popup "close" observer

View File

@@ -169,7 +169,7 @@ define([
* @param tableType
* @returns {string}
*/
let getTableId = (mapId, systemId, tableType) => Util.getTableId(config.sigTableId, mapId, systemId, tableType);
let getTableId = (tableType, mapId, systemId) => Util.getTableId(config.sigTableId, tableType, mapId, systemId);
/**
* get a dataTableApi instance from global cache
@@ -2169,7 +2169,7 @@ define([
let infoElement = $(dialogElement).find('#' + config.sigInfoId);
let table = $('<table>', {
id: getTableId(mapId, systemData.id, 'info'),
id: getTableId('info', mapId, systemData.id),
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTableInfoClass].join(' ')
});
@@ -2216,7 +2216,7 @@ define([
// create "empty table for new signature
let table = $('<table>', {
id: getTableId(mapId, systemData.id, 'secondary'),
id: getTableId('secondary', mapId, systemData.id),
class: ['stripe', 'row-border', 'compact', 'nowrap', config.sigTableClass, config.sigTableSecondaryClass].join(' ')
});
@@ -2497,7 +2497,7 @@ define([
};
/**
* init character info Tooltips
* init character info tooltips
* -> e.g. table cell 'question mark' icon
* @param element
* @param tableApi
@@ -2528,7 +2528,7 @@ define([
*/
let drawSignatureTable = (moduleElement, mapId, systemData) => {
let table = $('<table>', {
id: getTableId(mapId, systemData.id, 'primary'),
id: getTableId('primary', mapId, systemData.id),
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTablePrimaryClass].join(' ')
});

View File

@@ -78,9 +78,11 @@ define([
animationPulseClassPrefix: 'pf-animation-pulse-', // class prefix for "pulse" background animation
// popover
popoverClass: 'pf-popover', // class for "popover" - custom modifier
popoverTriggerClass: 'pf-popover-trigger', // class for "popover" trigger elements
popoverSmallClass: 'popover-small', // class for small "popover"
popoverCharacterClass: 'pf-popover-character', // class for character "popover"
popoverListIconClass: 'pf-popover-list-icon', // class for list "icon"s in "
// Summernote
summernoteClass: 'pf-summernote', // class for Summernote "WYSIWYG" elements
@@ -606,7 +608,7 @@ define([
container: 'body',
content: content,
animation: false
}).data('bs.popover').tip().addClass('pf-popover');
}).data('bs.popover').tip().addClass(config.popoverClass);
button.popover('show');
@@ -1013,6 +1015,22 @@ define([
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();
};
@@ -1786,6 +1804,8 @@ define([
characterLogLocation: valueChanged('character.logLocation'),
characterSystemId: valueChanged('character.log.system.id'),
characterShipType: valueChanged('character.log.ship.typeId'),
characterStationId: valueChanged('character.log.station.id'),
characterStructureId: valueChanged('character.log.structure.id'),
charactersIds: oldCharactersIds.toString() !== newCharactersIds.toString(),
characterLogHistory: oldHistoryLogStamps.toString() !== newHistoryLogStamps.toString()
};
@@ -2886,11 +2906,12 @@ define([
};
/**
* set new destination for a system
* @param systemData
* set new destination for a system/station/structure
* @param type
* @param destType
* @param destData
*/
let setDestination = (systemData, type) => {
let setDestination = (type, destType, destData) => {
let description = '';
switch(type){
case 'set_destination':
@@ -2910,22 +2931,20 @@ define([
data: {
clearOtherWaypoints: (type === 'set_destination') ? 1 : 0,
first: (type === 'add_last_waypoint') ? 0 : 1,
systemData: [{
systemId: systemData.systemId,
name: systemData.name
}]
destData: [destData]
},
context: {
destType: destType,
description: description
},
dataType: 'json'
}).done(function(responseData){
if(
responseData.systemData &&
responseData.systemData.length > 0
responseData.destData &&
responseData.destData.length > 0
){
for(let j = 0; j < responseData.systemData.length; j++){
showNotify({title: this.description, text: 'System: ' + responseData.systemData[j].name, type: 'success'});
for(let j = 0; j < responseData.destData.length; j++){
showNotify({title: this.description, text: this.destType + ': ' + responseData.destData[j].name, type: 'success'});
}
}
@@ -2934,7 +2953,7 @@ define([
responseData.error.length > 0
){
for(let i = 0; i < responseData.error.length; i++){
showNotify({title: this.description + ' error', text: 'System: ' + responseData.error[i].message, type: 'error'});
showNotify({title: this.description + ' error', text: this.destType + ': ' + responseData.error[i].message, type: 'error'});
}
}
@@ -3254,7 +3273,16 @@ define([
* @param tableType
* @returns {string}
*/
let getTableId = (prefix, mapId, systemId, tableType) => prefix + [mapId, systemId, tableType].join('-');
let getTableId = (prefix, tableType, mapId, systemId) => prefix + [tableType, mapId, systemId].join('-');
/**
* get dataTable row id
* @param prefix
* @param tableType
* @param rowId
* @returns {string}
*/
let getTableRowId = (prefix, tableType, rowId) => prefix + [tableType, rowId].join('-');
/**
* get a dataTableApi instance from global cache
@@ -3266,7 +3294,7 @@ define([
*/
let getDataTableInstance = (prefix, mapId, systemId, tableType) => {
let instance = null;
let table = $.fn.dataTable.tables({ visible: false, api: true }).table('#' + getTableId(prefix, mapId, systemId, tableType));
let table = $.fn.dataTable.tables({ visible: false, api: true }).table('#' + getTableId(prefix, tableType, mapId, systemId));
if(table.node()){
instance = table;
}
@@ -3522,6 +3550,7 @@ define([
getBrowserTabId: getBrowserTabId,
singleDoubleClick: singleDoubleClick,
getTableId: getTableId,
getTableRowId: getTableRowId,
getDataTableInstance: getDataTableInstance,
htmlEncode: htmlEncode,
htmlDecode: htmlDecode,

View File

@@ -340,7 +340,7 @@
</ul>
</div>
<div class="col-xs-12 col-md-6">
<h4 id="pf-manual-scrollspy-anchor-system-waypoint"><i class="fas fa-flag-checkered fa-fw"></i> Waypoints</h4>
<h4 id="pf-manual-scrollspy-anchor-system-waypoint"><i class="fas fa-flag fa-fw"></i> Waypoints</h4>
<p>
Waypoints can be set to systems. Waypoint options are identical to their in game options.
</p>

View File

@@ -4,9 +4,9 @@
<div class="row">
<div class="col-xs-6 ">
<div class="form-group">
<label for="form_name" class="col-sm-2 control-label">Name</label>
<label for="{{nameInputId}}" class="col-sm-2 control-label">Name</label>
<div class="col-sm-10">
<input name="name" type="text" class="form-control" id="form_name" value="{{structureData.name}}" data-error="Name required" data-minlength="3" data-minlength-error="Min. of 3 characters" required>
<input name="name" type="text" class="form-control" id="{{nameInputId}}" value="{{structureData.name}}" data-error="Name required" data-minlength="3" data-minlength-error="Min. of 3 characters" required>
<span class="note help-block with-errors"></span>
</div>
</div>

View File

@@ -20,17 +20,6 @@
<i class="fas fa-fw fa-lock" data-toggle="tooltip" title="locked"></i>
{{/system.locked}}
</h5>
{{#systemIsWormhole}}
<a href="{{#formatUrl}}http://evemaps.dotlan.net/system/{{system.name}}{{/formatUrl}}" class="pf-icon pf-icon-dotlan" data-toggle="tooltip" title="dotlan" target="_blank" rel="noopener"></a>
{{/systemIsWormhole}}
{{^systemIsWormhole}}
<a href="{{#formatUrl}}http://evemaps.dotlan.net/map/{{system.region.name}}/{{system.name}}{{/formatUrl}}" class="pf-icon pf-icon-dotlan" data-toggle="tooltip" title="dotlan" target="_blank" rel="noopener"></a>
{{/systemIsWormhole}}
{{#systemIsWormhole}}
<a href="{{#formatUrl}}http://anoik.is/systems/{{system.name}}{{/formatUrl}}" class="pf-icon pf-icon-anoik" data-toggle="tooltip" title="anoik" target="_blank" rel="noopener"></a>
{{/systemIsWormhole}}
</div>
<div class="row">
@@ -41,7 +30,7 @@
{{systemStatusLabel}}
</span>
<div class="pf-dynamic-area">
<div class="pf-dynamic-area" data-resizebar>
<table class="table table-condensed pf-table-fixed {{infoTableClass}}">
<thead>
<tr>
@@ -90,6 +79,49 @@
{{/static}}
</tbody>
</table>
<div class="pf-resizearea">
<table class="table table-condensed pf-table-fixed {{infoTableClass}}">
<colgroup>
<col/>
<col style="width: 40px;"/>
<col style="width: 40px;"/>
<col style="width: 40px;"/>
</colgroup>
<thead>
<tr>
<th>Links</th>
<th class="pf-table-cell-bg-image" rowspan="2">
{{#thirdPartyLinks.1_url}}
<a class="pf-table-cell-bg-image-wrapper smaller" style="--bg-image: url('/public/img/icons/logo_{{page}}.png');" data-toggle="tooltip" title="{{ domain }}" href="{{ url }}" target="_blank" rel="noopener"></a>
{{/thirdPartyLinks.1_url}}
</th>
<th class="pf-table-cell-bg-image" rowspan="2">
{{#thirdPartyLinks.2_url}}
<a class="pf-table-cell-bg-image-wrapper smaller" style="--bg-image: url('/public/img/icons/logo_{{page}}.png');" data-toggle="tooltip" title="{{ domain }}" href="{{ url }}" target="_blank" rel="noopener"></a>
{{/thirdPartyLinks.2_url}}
</th>
<th class="pf-table-cell-bg-image" rowspan="2">
{{#thirdPartyLinks.3_url}}
<a class="pf-table-cell-bg-image-wrapper smaller" style="--bg-image: url('/public/img/icons/logo_{{page}}.png');" data-toggle="tooltip" title="{{ domain }}" href="{{ url }}" target="_blank" rel="noopener"></a>
{{/thirdPartyLinks.3_url}}
</th>
</tr>
<tr>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="pf-dynamic-area-statusbar">
<div class="pf-dynamic-area-resizebar">
<div class="pf-resizebar-bar"></div>
<div class="pf-resizebar-bar"></div>
<div class="pf-resizebar-bar"></div>
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<div class="list-group">
<a class="list-group-item" href="javascript:void(0);" data-name="{{ systemToData.name }}" data-systemid="{{ systemToData.systemId }}">
<i class="fas fa-flag-checkered fa-fw"></i>set destination
<i class="fas fa-flag fa-fw"></i>set destination
</a>
</div>

View File

@@ -8,7 +8,6 @@
@import "_map";
@import "_system-info";
@import "_forms";
@import "_images";
@import "_dialogs";
@import "_log";
@import "_timeline";

View File

@@ -95,7 +95,7 @@ $breadcrumb-arrow-size: 15px;
margin-right: 5px;
}
&>.fas{
&>.fas:last-of-type{
margin-right: 5px !important;
}
}

View File

@@ -1,38 +0,0 @@
.pf-icon{
display: inline-block;
&.disabled{
opacity: 0.5;
color: $gray-light;
}
}
.pf-icon-dotlan,.pf-icon-anoik{
position: relative;
display: inline-block;
width: 17px;
height: 17px;
opacity: 0.8;
margin: -5px 0px 0 10px;
}
.pf-icon-dotlan:after{
content: '';
position: absolute;
left: 0;
right: 0;
height: 17px;
width: 17px;
margin-top: 4px;
background: inline-image("/icons/dotlan_logo.png") no-repeat;
}
.pf-icon-anoik:after{
content: '';
position: absolute;
left: 0;
right: 0;
height: 17px;
width: 17px;
margin-top: 4px;
background: inline-image("/icons/anoik_logo.png") no-repeat;
}

View File

@@ -32,7 +32,8 @@ a, .pf-link{
}
}
em{
em,
.pf-font-italic{
font-style: italic;
&.pf-brand{
@@ -428,6 +429,10 @@ select:active, select:hover {
font-size: 9px !important; // should not be "so big" (10px default)
}
> .label{
font-size: 80%;
}
&.pf-table-link-cell{
cursor: pointer;
@extend .txt-color;
@@ -479,11 +484,12 @@ select:active, select:hover {
&.pf-table-image-smaller-cell{
padding: 0 !important;
border-right: 1px solid transparent;
img{
width: 25px; // smaller image (default 32)
width: 23px; // smaller image (default 32)
// overwrite "default" border for image cells
border-left: 1px solid transparent;
border-right: 1px solid transparent;
//border-left: 1px solid transparent;
//border-right: 1px solid transparent;
}
}
@@ -633,6 +639,7 @@ table{
border-top-color: transparent !important;
.pf-table-cell-bg-image-wrapper{
display: block;
background-size: contain;
background-origin: content-box;
background-repeat: no-repeat;
@@ -642,6 +649,10 @@ table{
width: auto;
height: 40px;
padding: 2px 0;
&.smaller{
height: 30px;
}
}
}
}
@@ -897,6 +908,7 @@ table{
will-change: height, transform, opacity;
overflow: hidden;
@include border-radius(5px);
border-top-left-radius: 0;
&:before{
content: '';
@@ -1533,6 +1545,64 @@ td.pf-popover-trigger{
> [class~='alert']:last-of-type{
margin-bottom: 0;
}
&[data-resizebar]{
padding-bottom: 15px;
&:hover{
.pf-resizearea{
@include transition-delay(.15s);
height: 40px;
opacity: 1;
}
.pf-resizebar-bar{
border-top: 1px solid $orange !important;
}
}
}
&:not([data-resizebar]){
.pf-dynamic-area-resizebar{
display: none;
}
}
.pf-resizearea{
height: 0;
opacity: 0;
@include transition(height .12s ease-out, opacity .12s ease-out);
will-change: height, opacity;
}
.pf-dynamic-area-statusbar{
position: absolute;
left: 0;
bottom: 0;
width: 100%;
background-color: $gray-dark;
padding: 1px 0;
.pf-dynamic-area-resizebar{
width: 100%;
height: 9px;
padding-top: 1px;
cursor: ns-resize;
&:hover{
.pf-resizebar-bar{
border-top: 1px solid $orange;
}
}
.pf-resizebar-bar{
border-top: 1px solid $gray-light;
transition: border-top 0.15s ease-out;
width: 20px;
margin: 1px auto;
}
}
}
}
// code highlighting ==============================================================================

View File

@@ -131,6 +131,19 @@
}
}
}
.pf-popover-list-icon{
width: 28px;
margin: 5px 0;
&:first-child{
margin-left: 8px;
}
&:last-child{
margin-right: 8px;
}
}
}
// character popover --------------------------------------------------------------------------------------------------

View File

@@ -1,5 +1,5 @@
// system info module ======================================================
$pfSystemSectionMinHeight: 124px;
$pfSystemSectionMinHeight: 123px;
.pf-system-info-module{
@@ -8,20 +8,25 @@ $pfSystemSectionMinHeight: 124px;
text-transform: capitalize;
}
.pf-system-sov-section{
@media (max-width: $screen-xs-max) {
margin: 10px 0;
}
@media (min-width: $screen-sm-min) {
> .row {
> div:not(:first-child){
padding-left: 0;
padding-right: 0;
}
}
.pf-system-sov-section{
.pf-dynamic-area{
min-height: $pfSystemSectionMinHeight;
}
}
.pf-system-description-section{
@media (max-width: $screen-xs-max) {
padding-left: 10px !important;
margin-top: 10px;
}
}
.pf-system-info-name-cell{
user-select: all;
}
@@ -287,9 +292,14 @@ $pfSystemSectionMinHeight: 124px;
// intel module ============================================================
.pf-system-intel-module{
.pf-system-station-table,
.pf-system-structure-table{
font-size: 10px;
}
.pf-module-head:not(:first-child){
margin-top: 15px;
}
}
// killboard module ========================================================