Merge pull request #485 from exodus4d/develop

v1.2.2
This commit is contained in:
Mark Friedrich
2017-05-01 20:18:57 +02:00
committed by GitHub
97 changed files with 5372 additions and 3243 deletions

2
.gitignore vendored
View File

@@ -50,3 +50,5 @@ Temporary Items
.sass-cache
.usage
*.gz
/composer-dev.lock
/composer.lock

View File

@@ -26,14 +26,15 @@ DB_CCP_NAME = eve_citadel_min
DB_CCP_USER = root
DB_CCP_PASS =
; CCP SSO settings (OAuth2) - visit: https://developers.eveonline.com/applications
CCP_CREST_URL = https://api-sisi.testeveonline.com
SSO_CCP_URL = https://sisilogin.testeveonline.com
SSO_CCP_CLIENT_ID =
SSO_CCP_SECRET_KEY =
; CCP SSO (OAuth2) - visit: https://developers.eveonline.com/applications
CCP_SSO_URL = https://sisilogin.testeveonline.com
CCP_SSO_CLIENT_ID =
CCP_SSO_SECRET_KEY =
; CCP XML APIv2
CCP_XML = https://api.testeveonline.com
; CCP ESI API
CCP_ESI_URL = https://esi.tech.ccp.is
CCP_ESI_DATASOURCE = singularity
CCP_ESI_SCOPES = esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1
; SMTP settings (optional)
SMTP_HOST = localhost
@@ -70,14 +71,15 @@ DB_CCP_NAME =
DB_CCP_USER =
DB_CCP_PASS =
; CCP SSO settings
CCP_CREST_URL = https://crest-tq.eveonline.com
SSO_CCP_URL = https://login.eveonline.com
SSO_CCP_CLIENT_ID =
SSO_CCP_SECRET_KEY =
; CCP SSO
CCP_SSO_URL = https://login.eveonline.com
CCP_SSO_CLIENT_ID =
CCP_SSO_SECRET_KEY =
; CCP XML APIv2
CCP_XML = https://api.eveonline.com
; CCP ESI API
CCP_ESI_URL = https://esi.tech.ccp.is
CCP_ESI_DATASOURCE = tranquility
CCP_ESI_SCOPES = esi-location.read_location.v1,esi-location.read_ship_type.v1,esi-ui.write_waypoint.v1,esi-ui.open_window.v1
; SMTP settings (optional)
SMTP_HOST = localhost

View File

@@ -109,15 +109,18 @@ class Route extends Controller\AccessController {
* @param array $filterData
*/
private function setDynamicJumpData($mapIds = [], $filterData = []){
// make sure, mapIds are integers (protect against SQL injections)
$mapIds = array_unique( array_map('intval', $mapIds), SORT_NUMERIC);
if( !empty($mapIds) ){
// make sure, mapIds are integers (protect against SQL injections)
$mapIds = array_map('intval', $mapIds);
// map filter ---------------------------------------------------------------------------------------------
$whereMapIdsQuery = (count($mapIds) == 1) ? " = " . reset($mapIds) : " IN (" . implode(', ', $mapIds) . ")";
// connection filter --------------------------------------------------------
// connection filter --------------------------------------------------------------------------------------
$whereQuery = "";
$includeScopes = [];
$includeTypes = [];
$excludeTypes = [];
$includeEOL = true;
if( $filterData['stargates'] === true){
@@ -147,16 +150,24 @@ class Route extends Controller\AccessController {
$includeTypes[] = 'wh_critical';
}
if( $filterData['wormholesFrigate'] !== true ){
$excludeTypes[] = 'frigate';
}
if( $filterData['wormholesEOL'] === false ){
$includeEOL = false;
}
}
// search connections -------------------------------------------------------
// search connections -------------------------------------------------------------------------------------
if( !empty($includeScopes) ){
$whereQuery .= " `connection`.`scope` IN ('" . implode("', '", $includeScopes) . "') AND ";
if( !empty($excludeTypes) ){
$whereQuery .= " `connection`.`type` NOT REGEXP '" . implode("|", $excludeTypes) . "' AND ";
}
if( !empty($includeTypes) ){
$whereQuery .= " `connection`.`type` REGEXP '" . implode("|", $includeTypes) . "' AND ";
}
@@ -179,7 +190,7 @@ class Route extends Controller\AccessController {
`system_tar`.`id` = `connection`.`source` OR
`system_tar`.`id` = `connection`.`target`
WHERE
`connection`.`mapId` IN (" . implode(', ', $mapIds) . ") AND
`connection`.`mapId` " . $whereMapIdsQuery . " AND
`connection`.`active` = 1 AND
(
`connection`.`source` = `system_src`.`id` OR
@@ -195,7 +206,7 @@ class Route extends Controller\AccessController {
`map` ON
`map`.`id` = `system_src`.`mapId`
WHERE
`system_src`.`mapId` IN (" . implode(', ', $mapIds) . ") AND
`system_src`.`mapId` " . $whereMapIdsQuery . " AND
`system_src`.`active` = 1 AND
`map`.`active` = 1
HAVING
@@ -228,7 +239,7 @@ class Route extends Controller\AccessController {
$systemId = (int)$row['systemId'];
$secStatus = (float)$row['trueSec'];
// fill "nameArray" data ----------------------------------------------------
// fill "nameArray" data ----------------------------------------------------------------------------------
if( !isset($this->nameArray[$systemId]) ){
$this->nameArray[$systemId][0] = $systemName;
$this->nameArray[$systemId][1] = $regionId;
@@ -236,12 +247,12 @@ class Route extends Controller\AccessController {
$this->nameArray[$systemId][3] = $secStatus;
}
// fill "idArray" data ------------------------------------------------------
// fill "idArray" data ------------------------------------------------------------------------------------
if( !isset($this->idArray[$systemName]) ){
$this->idArray[$systemName] = $systemId;
}
// fill "jumpArray" data ----------------------------------------------------
// fill "jumpArray" data ----------------------------------------------------------------------------------
if( !is_array($this->jumpArray[$systemName]) ){
$this->jumpArray[$systemName] = [];
}
@@ -527,7 +538,7 @@ class Route extends Controller\AccessController {
$mapData = (array)$routeData['mapIds'];
$mapData = array_flip( array_map('intval', $mapData) );
// check map access (filter requested mapIDs and format) --------------------
// check map access (filter requested mapIDs and format) ----------------------------------------------
array_walk($mapData, function(&$item, &$key, $data){
if( isset($data[1][$key]) ){
@@ -561,6 +572,7 @@ class Route extends Controller\AccessController {
'wormholes' => (bool) $routeData['wormholes'],
'wormholesReduced' => (bool) $routeData['wormholesReduced'],
'wormholesCritical' => (bool) $routeData['wormholesCritical'],
'wormholesFrigate' => (bool) $routeData['wormholesFrigate'],
'wormholesEOL' => (bool) $routeData['wormholesEOL'],
'safer' => (bool) $routeData['safer']
];

View File

@@ -130,13 +130,9 @@ class System extends Controller\AccessController {
* @return array
*/
public function getSystems(){
$ccpDB = $this->getDB('CCP');
$query = $this->_getQuery();
$rows = $ccpDB->exec($query, null, 60 * 60 * 24);
// format result
$mapper = new Mapper\CcpSystemsMapper($rows);
@@ -385,20 +381,25 @@ class System extends Controller\AccessController {
$return->clearOtherWaypoints = (bool)$postData['clearOtherWaypoints'];
$return->first = (bool)$postData['first'];
/**
* @var Sso $ssoController
*/
$ssoController = self::getController('Sso');
foreach($postData['systemData'] as $systemData){
$waypointData = $ssoController->setWaypoint($activeCharacter, $systemData['systemId'], [
if( $accessToken = $activeCharacter->getAccessToken() ){
$options = [
'clearOtherWaypoints' => $return->clearOtherWaypoints,
'first' => $return->first,
]);
if($waypointData['systemId']){
$return->systemData[] = $systemData;
}elseif( isset($waypointData['error']) ){
$return->error[] = $waypointData['error'];
'addToBeginning' => $return->first,
];
foreach($postData['systemData'] as $systemData){
$response = $f3->ccpClient->setWaypoint($systemData['systemId'], $accessToken, $options);
if(empty($response)){
$return->systemData[] = $systemData;
}else{
$error = (object) [];
$error->type = 'error';
$error->message = $response['error'];
$return->error[] = $error;
}
}
}
}
@@ -428,14 +429,14 @@ class System extends Controller\AccessController {
if( $system = $map->getSystemById($systemId) ){
// check whether system should be deleted OR set "inactive"
if(
empty($system->alias) &&
empty($system->description)
!empty($system->description) ||
( !empty($system->alias) && ($system->alias != $system->name) )
){
$system->erase();
}else{
// keep data -> set "inactive"
$system->setActive(false);
$system->save();
}else{
$system->erase();
}
$system->reset();
@@ -452,23 +453,3 @@ class System extends Controller\AccessController {
}
}

View File

@@ -119,7 +119,7 @@ class User extends Controller\Controller{
}else{
$characterError = (object) [];
$characterError->type = 'warning';
$characterError->message = 'This can happen through "invalid cookie data", "login restrictions", "CREST problems".';
$characterError->message = 'This can happen through "invalid cookies(SSO)", "login restrictions", "ESI problems".';
$return->error[] = $characterError;
}
}
@@ -196,10 +196,39 @@ class User extends Controller\Controller{
echo json_encode($return);
}
/**
* remote open ingame information window (character, corporation or alliance) Id
* -> the type is auto-recognized by CCP
* @param \Base $f3
*/
public function openIngameWindow(\Base $f3){
$data = $f3->get('POST');
$return = (object) [];
$return->error = [];
if( $targetId = (int)$data['targetId']){
$activeCharacter = $this->getCharacter();
$response = $f3->ccpClient->openWindow($targetId, $activeCharacter->getAccessToken());
if(empty($response)){
$return->targetId = $targetId;
}else{
$error = (object) [];
$error->type = 'error';
$error->message = $response['error'];
$return->error[] = $error;
}
}
echo json_encode($return);
}
/**
* update user account data
* -> a fresh user automatically generated on first login with a new character
* -> see CREST SSO login
* -> see SSO login
* @param \Base $f3
*/
public function saveAccount(\Base $f3){
@@ -340,25 +369,23 @@ class User extends Controller\Controller{
$user = $activeCharacter->getUser();
if($user){
// send delete account mail
// try to send delete account mail
$msg = 'Hello ' . $user->name . ',<br><br>';
$msg .= 'your account data has been successfully deleted.';
$mailController = new MailController();
$status = $mailController->sendDeleteAccount($user->email, $msg);
$mailController->sendDeleteAccount($user->email, $msg);
if($status){
// save log
self::getLogger('DELETE_ACCOUNT')->write(
sprintf(self::LOG_DELETE_ACCOUNT, $user->id, $user->name)
);
// save log
self::getLogger('DELETE_ACCOUNT')->write(
sprintf(self::LOG_DELETE_ACCOUNT, $user->id, $user->name)
);
// remove user
$user->erase();
// remove user
$user->erase();
$this->logout($f3);
die();
}
$this->logout($f3);
die();
}
}else{
// captcha not valid -> return error

View File

@@ -5,7 +5,7 @@
* Date: 23.01.2016
* Time: 17:18
*
* Handles access to EVE-Online "CREST API" and "SSO" auth functions
* Handles access to EVE-Online "ESI API" and "SSO" auth functions
* - Add your API credentials in "environment.ini"
* - Check "PATHFINDER.API" in "pathfinder.ini" for correct API URLs
* Hint: \Web::instance()->request automatically caches responses by their response "Cache-Control" header!
@@ -14,7 +14,6 @@
namespace Controller\Ccp;
use Controller;
use Controller\Api as Api;
use Data\Mapper as Mapper;
use Model;
use Lib;
@@ -23,7 +22,7 @@ class Sso extends Api\User{
/**
* @var int timeout (seconds) for API calls
*/
const CREST_TIMEOUT = 4;
const SSO_TIMEOUT = 4;
/**
* @var int expire time (seconds) for an valid "accessToken"
@@ -37,32 +36,16 @@ class Sso extends Api\User{
const SESSION_KEY_SSO_FROM_MAP = 'SESSION.SSO.FROM_MAP';
// error messages
const ERROR_CCP_SSO_URL = 'Invalid "ENVIRONMENT.[ENVIRONMENT].SSO_CCP_URL" url. %s';
const ERROR_CCP_CREST_URL = 'Invalid "ENVIRONMENT.[ENVIRONMENT].CCP_CREST_URL" url. %s';
const ERROR_CCP_CLIENT_ID = 'Missing "ENVIRONMENT.[ENVIRONMENT].SSO_CCP_CLIENT_ID".';
const ERROR_RESOURCE_DEPRECATED = 'Resource: %s has been marked as deprecated. %s';
const ERROR_CCP_SSO_URL = 'Invalid "ENVIRONMENT.[ENVIRONMENT].CCP_SSO_URL" url. %s';
const ERROR_CCP_CLIENT_ID = 'Missing "ENVIRONMENT.[ENVIRONMENT].CCP_SSO_CLIENT_ID".';
const ERROR_ACCESS_TOKEN = 'Unable to get a valid "access_token. %s';
const ERROR_VERIFY_CHARACTER = 'Unable to verify character data. %s';
const ERROR_GET_ENDPOINT = 'Unable to get endpoint data. $s';
const ERROR_FIND_ENDPOINT = 'Unable to find endpoint: %s';
const ERROR_LOGIN_FAILED = 'Failed authentication due to technical problems: %s';
const ERROR_CHARACTER_VERIFICATION = 'Character verification failed from CREST';
const ERROR_CHARACTER_VERIFICATION = 'Character verification failed by SSP SSO';
const ERROR_CHARACTER_FORBIDDEN = 'Character "%s" is not authorized to log in';
const ERROR_SERVICE_TIMEOUT = 'CCP SSO service timeout (%ss). Try again later';
const ERROR_COOKIE_LOGIN = 'Login from Cookie failed. Please retry by CCP SSO';
/**
* CREST "Scopes" are used by pathfinder
* -> Enable scopes: https://developers.eveonline.com
* @var array
*/
private $requestScopes = [
// 'characterFittingsRead',
// 'characterFittingsWrite',
'characterLocationRead',
'characterNavigationWrite'
];
/**
* redirect user to CCP SSO page and request authorization
* -> cf. Controller->getCookieCharacters() ( equivalent cookie based login)
@@ -70,7 +53,7 @@ class Sso extends Api\User{
*/
public function requestAuthorization($f3){
if( !empty($ssoCcpClientId = Controller\Controller::getEnvironmentData('SSO_CCP_CLIENT_ID')) ){
if( !empty($ssoCcpClientId = Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID')) ){
$params = $f3->get('GET');
if(
@@ -94,8 +77,8 @@ class Sso extends Api\User{
($activeCharacter->getUser()->_id === $character->getUser()->_id)
){
// requested character belongs to current user
// -> update character vom CREST (e.g. corp changed,..)
$updateStatus = $character->updateFromCrest();
// -> update character vom ESI (e.g. corp changed,..)
$updateStatus = $character->updateFromESI();
if( empty($updateStatus) ){
@@ -111,7 +94,7 @@ class Sso extends Api\User{
if($loginCheck){
// set "login" cookie
$this->setLoginCookie($character);
$this->setLoginCookie($character, $this->getRequestedScopeHash());
// -> pass current character data to target page
$f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $character->_id);
@@ -135,9 +118,9 @@ class Sso extends Api\User{
$urlParams = [
'response_type' => 'code',
'redirect_uri' => Controller\Controller::getEnvironmentData('URL') . $f3->build('/sso/callbackAuthorization'),
'client_id' => Controller\Controller::getEnvironmentData('SSO_CCP_CLIENT_ID'),
'scope' => implode(' ', $this->requestScopes),
'redirect_uri' => Controller\Controller::getEnvironmentData('URL') . Controller\Controller::getEnvironmentData('BASE') . $f3->build('/sso/callbackAuthorization'),
'client_id' => Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID'),
'scope' => implode(' ', Controller\Controller::getEnvironmentData('CCP_ESI_SCOPES')),
'state' => $state
];
@@ -149,7 +132,7 @@ class Sso extends Api\User{
}else{
// SSO clientId missing
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_CCP_CLIENT_ID);
self::getCrestLogger()->write(self::ERROR_CCP_CLIENT_ID);
self::getSSOLogger()->write(self::ERROR_CCP_CLIENT_ID);
$f3->reroute('@login');
}
}
@@ -184,7 +167,7 @@ class Sso extends Api\User{
$f3->clear(self::SESSION_KEY_SSO_STATE);
$f3->clear(self::SESSION_KEY_SSO_FROM_MAP);
$accessData = $this->getCrestAccessData($getParams['code']);
$accessData = $this->getSsoAccessData($getParams['code']);
if(
isset($accessData->accessToken) &&
@@ -199,11 +182,11 @@ class Sso extends Api\User{
// verification available data. Data is needed for "ownerHash" check
// get character data from CREST
$characterData = $this->getCharacterData($accessData->accessToken);
// get character data from ESI
$characterData = $this->getCharacterData($verificationCharacterData->CharacterID);
if( isset($characterData->character) ){
// add "ownerHash" and CREST tokens
// add "ownerHash" and SSO tokens
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
$characterData->character['crestAccessToken'] = $accessData->accessToken;
$characterData->character['crestRefreshToken'] = $accessData->refreshToken;
@@ -253,7 +236,7 @@ class Sso extends Api\User{
if($loginCheck){
// set "login" cookie
$this->setLoginCookie($characterModel);
$this->setLoginCookie($characterModel, $this->getRequestedScopeHash());
// -> pass current character data to target page
$f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $characterModel->_id);
@@ -270,15 +253,15 @@ class Sso extends Api\User{
}
}
}else{
// failed to verify character by CREST
// failed to verify character by CCP SSO
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_CHARACTER_VERIFICATION);
}
}else{
// CREST "accessData" missing (e.g. timeout)
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_SERVICE_TIMEOUT, self::CREST_TIMEOUT));
// SSO "accessData" missing (e.g. timeout)
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_SERVICE_TIMEOUT, self::SSO_TIMEOUT));
}
}else{
// invalid CREST response
// invalid SSO response
$f3->set(self::SESSION_KEY_SSO_ERROR, sprintf(self::ERROR_LOGIN_FAILED, 'Invalid response'));
}
}
@@ -337,7 +320,7 @@ class Sso extends Api\User{
* @param bool $authCode
* @return null|\stdClass
*/
public function getCrestAccessData($authCode){
public function getSsoAccessData($authCode){
$accessData = null;
if( !empty($authCode) ){
@@ -345,7 +328,7 @@ class Sso extends Api\User{
$accessData = $this->verifyAuthorizationCode($authCode);
}else{
// Unable to get Token -> trigger error
self::getCrestLogger()->write(sprintf(self::ERROR_ACCESS_TOKEN, $authCode));
self::getSSOLogger()->write(sprintf(self::ERROR_ACCESS_TOKEN, $authCode));
}
return $accessData;
@@ -398,7 +381,7 @@ class Sso extends Api\User{
if($verifyAuthCodeUrlParts){
$contentType = 'application/x-www-form-urlencoded';
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'timeout' => self::SSO_TIMEOUT,
'method' => 'POST',
'user_agent' => $this->getUserAgent(),
'header' => [
@@ -428,7 +411,7 @@ class Sso extends Api\User{
}
}
}else{
self::getCrestLogger()->write(
self::getSSOLogger()->write(
sprintf(
self::ERROR_ACCESS_TOKEN,
print_r($requestParams, true)
@@ -436,7 +419,7 @@ class Sso extends Api\User{
);
}
}else{
self::getCrestLogger()->write(
self::getSSOLogger()->write(
sprintf(self::ERROR_CCP_SSO_URL, __METHOD__)
);
}
@@ -447,7 +430,7 @@ class Sso extends Api\User{
/**
* verify character data by "access_token"
* -> get some basic information (like character id)
* -> if more character information is required, use CREST endpoints request instead
* -> if more character information is required, use ESI "characters" endpoints request instead
* @param $accessToken
* @return mixed|null
*/
@@ -458,7 +441,7 @@ class Sso extends Api\User{
if($verifyUrlParts){
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'timeout' => self::SSO_TIMEOUT,
'method' => 'GET',
'user_agent' => $this->getUserAgent(),
'header' => [
@@ -472,271 +455,83 @@ class Sso extends Api\User{
if($apiResponse['body']){
$characterData = json_decode($apiResponse['body']);
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
self::getSSOLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
}
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_CCP_SSO_URL, __METHOD__));
self::getSSOLogger()->write(sprintf(self::ERROR_CCP_SSO_URL, __METHOD__));
}
return $characterData;
}
/**
* get all available Endpoints
* @param $accessToken
* @param array $additionalOptions
* @return mixed|null
*/
protected function getEndpoints($accessToken = '', $additionalOptions = []){
$crestUrl = self::getCrestEndpoint();
$additionalOptions['accept'] = 'application/vnd.ccp.eve.Api-v5+json';
$endpoint = $this->getEndpoint($crestUrl, $accessToken, $additionalOptions);
return $endpoint;
}
/**
* get a specific endpoint by its $resourceUrl
* @param string $resourceUrl endpoint API url
* @param string $accessToken CREST access token
* @param array $additionalOptions optional request options (pathfinder specific)
* @return mixed|null
*/
protected function getEndpoint($resourceUrl, $accessToken = '', $additionalOptions = []){
$resourceUrlParts = parse_url($resourceUrl);
$endpoint = null;
if($resourceUrlParts){
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'method' => 'GET',
'user_agent' => $this->getUserAgent(),
'header' => [
'Host: login.eveonline.com',
'Host: ' . $resourceUrlParts['host']
]
];
// some endpoints don´t require an "access_token" (e.g. public crest data)
if( !empty($accessToken) ){
$requestOptions['header'][] = 'Authorization: Bearer ' . $accessToken;
}
// if specific contentType is required -> add it to request header
// CREST versioning can be done by calling different "Accept:" Headers
if( isset($additionalOptions['accept']) ){
$requestOptions['header'][] = 'Accept: ' . $additionalOptions['accept'];
}
$apiResponse = Lib\Web::instance()->request($resourceUrl, $requestOptions, $additionalOptions);
if(
$apiResponse['timeout'] === false &&
$apiResponse['headers']
){
// check headers for error
$this->checkResponseHeaders($apiResponse['headers'], $requestOptions);
if($apiResponse['body']){
$endpoint = json_decode($apiResponse['body'], true);
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_GET_ENDPOINT, __METHOD__));
}
}
}else{
self::getCrestLogger()->write(sprintf(self::ERROR_CCP_CREST_URL, __METHOD__));
}
return $endpoint;
}
/**
* recursively walk down the CREST API tree by a given $path array
* -> return "leaf" endpoint
* @param $endpoint
* @param $accessToken
* @param array $path
* @param array $additionalOptions
* @return null
*/
protected function walkEndpoint($endpoint, $accessToken, $path = [], $additionalOptions = []){
$targetEndpoint = null;
if( !empty($path) ){
$newNode = array_shift($path);
if(isset($endpoint[$newNode])){
$currentEndpoint = $endpoint[$newNode];
if(isset($currentEndpoint['href'])){
$newEndpoint = $this->getEndpoint($currentEndpoint['href'], $accessToken, $additionalOptions);
$targetEndpoint = $this->walkEndpoint($newEndpoint, $accessToken, $path, $additionalOptions);
}else{
// leaf found
$targetEndpoint = $currentEndpoint;
}
}else{
// endpoint not found
self::getCrestLogger()->write(sprintf(self::ERROR_FIND_ENDPOINT, $newNode));
}
}else{
$targetEndpoint = $endpoint;
}
return $targetEndpoint;
}
/**
* get character data
* @param $accessToken
* @param array $additionalOptions
* @param int $characterId
* @return object
*/
public function getCharacterData($accessToken, $additionalOptions = []){
$endpoints = $this->getEndpoints($accessToken, $additionalOptions);
public function getCharacterData($characterId){
$characterData = (object) [];
$endpoint = $this->walkEndpoint($endpoints, $accessToken, [
'decode',
'character'
], $additionalOptions);
$characterDataBasic = $this->getF3()->ccpClient->getCharacterData($characterId);
if( !empty($endpoint) ){
$crestCharacterData = (new Mapper\CrestCharacter($endpoint))->getData();
$characterData->character = $crestCharacterData
;
if(isset($endpoint['corporation'])){
$characterData->corporation = (new Mapper\CrestCorporation($endpoint['corporation']))->getData();
}
if( !empty($characterDataBasic) ){
// remove some "unwanted" data -> not relevant for Pathfinder
$characterData->character = array_filter($characterDataBasic, function($key){
return in_array($key, ['id', 'name', 'securityStatus']);
}, ARRAY_FILTER_USE_KEY);
// IMPORTANT: alliance data is not yet available over CREST!
// -> we need to request them over the XML api
/*
if(isset($endpoint['alliance'])){
$characterData->alliance = (new Mapper\CrestAlliance($endpoint['alliance']))->getData();
}
*/
$characterData->corporation = null;
$characterData->alliance = null;
$xmlCharacterData = (new Xml())->getPublicCharacterData( (int)$crestCharacterData['id'] );
if(isset($xmlCharacterData['alli'])){
$characterData->alliance = $xmlCharacterData['alli'];
}
}
if(isset($characterDataBasic['corporation'])){
$corporationId = (int)$characterDataBasic['corporation']['id'];
return $characterData;
}
/**
* @var Model\CorporationModel $corporationModel
*/
$corporationModel = Model\BasicModel::getNew('CorporationModel');
$corporationModel->getById($corporationId, 0);
/**
* get current character location data (result is cached!)
* -> solarSystem data where character is currently active
* @param $accessToken
* @param array $additionalOptions
* @return array
*/
public function getCharacterLocationData($accessToken, $additionalOptions = []){
// null == CREST call failed (e.g. timeout)
$locationData = [
'timeout' => false
];
if($corporationModel->dry()){
// request corporation data
$corporationData = $this->getF3()->ccpClient->getCorporationData($corporationId);
$endpoints = $this->getEndpoints($accessToken, $additionalOptions);
if( !empty($corporationData) ){
// check for NPC corporation
$corporationData['isNPC'] = $this->getF3()->ccpClient->isNpcCorporation($corporationId);
$additionalOptions['accept'] = 'application/vnd.ccp.eve.CharacterLocation-v1+json';
$endpoint = $this->walkEndpoint($endpoints, $accessToken, [
'decode',
'character',
'location'
], $additionalOptions);
if( !is_null($endpoint) ){
// request succeeded (e.g. no timeout)
if(isset($endpoint['solarSystem'])){
$locationData['system'] = (new Mapper\CrestSystem($endpoint['solarSystem']))->getData();
}
if(isset($endpoint['station'])){
$locationData['station'] = (new Mapper\CrestStation($endpoint['station']))->getData();
}
}else{
// timeout
$locationData['timeout'] = true;
}
return $locationData;
}
/**
* set new ingame waypoint
* @param Model\CharacterModel $character
* @param int $systemId
* @param array $options
* @return array
*/
public function setWaypoint( Model\CharacterModel $character, $systemId, $options = []){
$crestUrlParts = parse_url( self::getCrestEndpoint() );
$waypointData = [];
if( $crestUrlParts ){
$accessToken = $character->getAccessToken();
$endpoints = $this->getEndpoints($accessToken);
// get endpoint list for "ui" endpoints
$uiEndpoints = $endpoint = $this->walkEndpoint($endpoints, $accessToken, [
'decode',
'character',
'ui'
]);
if(
isset($uiEndpoints['setWaypoints']) &&
isset($uiEndpoints['setWaypoints']['href'])
){
$endpointUrl = $uiEndpoints['setWaypoints']['href'];
$systemEndpoint = self::getCrestEndpoint() . '/solarsystems/' . $systemId . '/';
// request body
$content = [
'clearOtherWaypoints' => (bool)$options['clearOtherWaypoints'],
'first' => (bool)$options['first'],
'solarSystem' => [
'href' => $systemEndpoint,
'id' => (int)$systemId
]
];
$requestOptions = [
'timeout' => self::CREST_TIMEOUT,
'method' => 'POST',
'user_agent' => $this->getUserAgent(),
'header' => [
'Scope: characterNavigationWrite',
'Authorization: Bearer ' . $character->getAccessToken(),
'Host: ' . $crestUrlParts['host'],
'Content-Type: application/vnd.ccp.eve.PostWaypoint-v1+json;charset=utf-8',
],
'content' => json_encode($content, JSON_UNESCAPED_SLASHES)
];
$apiResponse = Lib\Web::instance()->request($endpointUrl, $requestOptions);
if( isset($apiResponse['body']) ){
$responseData = json_decode($apiResponse['body']);
if( empty($responseData) ){
$waypointData['systemId'] = (int)$systemId;
}elseif(
isset($responseData->message) &&
isset($responseData->key)
){
// waypoint could not be set...
$error = (object) [];
$error->type = 'error';
$error->message = $responseData->key;
$waypointData['error'] = $error;
$corporationModel->copyfrom($corporationData, ['id', 'name', 'isNPC']);
$characterData->corporation = $corporationModel->save();
}
}else{
$characterData->corporation = $corporationModel;
}
}
if(isset($characterDataBasic['alliance'])){
$allianceId = (int)$characterDataBasic['alliance']['id'];
/**
* @var Model\AllianceModel $allianceModel
*/
$allianceModel = Model\BasicModel::getNew('AllianceModel');
$allianceModel->getById($allianceId, 0);
if($allianceModel->dry()){
// request alliance data
$allianceData = $this->getF3()->ccpClient->getAllianceData($allianceId);
if( !empty($allianceData) ){
$allianceModel->copyfrom($allianceData, ['id', 'name']);
$characterData->alliance = $allianceModel->save();
}
}else{
$characterData->alliance = $allianceModel;
}
}
}
return $waypointData;
return $characterData;
}
/**
@@ -746,96 +541,24 @@ class Sso extends Api\User{
* @throws \Exception
*/
protected function updateCharacter($characterData){
$characterModel = null;
$corporationModel = null;
$allianceModel = null;
if( isset($characterData->corporation) ){
/**
* @var Model\CorporationModel $corporationModel
*/
$corporationModel = Model\BasicModel::getNew('CorporationModel');
$corporationModel->getById((int)$characterData->corporation['id'], 0);
$corporationModel->copyfrom($characterData->corporation);
$corporationModel->save();
}
if( !empty($characterData->character) ){
if( isset($characterData->alliance) ){
/**
* @var Model\AllianceModel $allianceModel
*/
$allianceModel = Model\BasicModel::getNew('AllianceModel');
$allianceModel->getById((int)$characterData->alliance['id'], 0);
$allianceModel->copyfrom($characterData->alliance);
$allianceModel->save();
}
if( isset($characterData->character) ){
/**
* @var Model\CharacterModel $characterModel
*/
$characterModel = Model\BasicModel::getNew('CharacterModel');
$characterModel->getById((int)$characterData->character['id'], 0);
$characterModel->copyfrom($characterData->character);
$characterModel->corporationId = $corporationModel;
$characterModel->allianceId = $allianceModel;
$characterModel->copyfrom($characterData->character, ['id', 'name', 'ownerHash', 'crestAccessToken', 'crestRefreshToken', 'securityStatus']);
$characterModel->corporationId = $characterData->corporation;
$characterModel->allianceId = $characterData->alliance;
$characterModel = $characterModel->save();
}
return $characterModel;
}
/**
* get CREST server status (online/offline)
* @return \stdClass object
*/
public function getCrestServerStatus(){
$endpoints = $this->getEndpoints();
// set default status e.g. Endpoints don´t work
$data = (object) [];
$data->crestOffline = true;
$data->serverName = 'EVE ONLINE';
$data->serviceStatus = [
'eve' => 'offline',
'server' => 'offline',
];
$data->userCounts = [
'eve' => 0
];
$endpoint = $this->walkEndpoint($endpoints, '', ['serverName']);
if( !empty($endpoint) ){
$data->crestOffline = false;
$data->serverName = (string) $endpoint;
}
$endpoint = $this->walkEndpoint($endpoints, '', ['serviceStatus']);
if( !empty($endpoint) ){
$data->crestOffline = false;
$data->serviceStatus = (string) $endpoint;
}
$endpoint = $this->walkEndpoint($endpoints, '', ['userCount_str']);
if( !empty($endpoint) ){
$data->crestOffline = false;
$data->userCounts = (string) $endpoint;
}
return $data;
}
/**
* check response "Header" data for errors
* @param $headers
* @param string $requestUrl
* @param string $contentType
*/
protected function checkResponseHeaders($headers, $requestUrl = '', $contentType = ''){
$headers = (array)$headers;
if( preg_grep('/^X-Deprecated/i', $headers) ){
self::getCrestLogger()->write(sprintf(self::ERROR_RESOURCE_DEPRECATED, $requestUrl, $contentType));
}
}
/**
* get "Authorization:" Header data
* -> This header is required for any Auth-required endpoints!
@@ -843,29 +566,11 @@ class Sso extends Api\User{
*/
protected function getAuthorizationHeader(){
return base64_encode(
Controller\Controller::getEnvironmentData('SSO_CCP_CLIENT_ID') . ':'
. Controller\Controller::getEnvironmentData('SSO_CCP_SECRET_KEY')
Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') . ':'
. Controller\Controller::getEnvironmentData('CCP_SSO_SECRET_KEY')
);
}
/**
* get CCP CREST url from configuration file
* -> throw error if url is broken/missing
* @return string
*/
static function getCrestEndpoint(){
$url = '';
if( \Audit::instance()->url(self::getEnvironmentData('CCP_CREST_URL')) ){
$url = self::getEnvironmentData('CCP_CREST_URL');
}else{
$error = sprintf(self::ERROR_CCP_CREST_URL, __METHOD__);
self::getCrestLogger()->write($error);
\Base::instance()->error(502, $error);
}
return $url;
}
/**
* get CCP SSO url from configuration file
* -> throw error if url is broken/missing
@@ -873,11 +578,11 @@ class Sso extends Api\User{
*/
static function getSsoUrlRoot(){
$url = '';
if( \Audit::instance()->url(self::getEnvironmentData('SSO_CCP_URL')) ){
$url = self::getEnvironmentData('SSO_CCP_URL');
if( \Audit::instance()->url(self::getEnvironmentData('CCP_SSO_URL')) ){
$url = self::getEnvironmentData('CCP_SSO_URL');
}else{
$error = sprintf(self::ERROR_CCP_SSO_URL, __METHOD__);
self::getCrestLogger()->write($error);
self::getSSOLogger()->write($error);
\Base::instance()->error(502, $error);
}
@@ -897,10 +602,10 @@ class Sso extends Api\User{
}
/**
* get logger for CREST logging
* get logger for SSO logging
* @return \Log
*/
static function getCrestLogger(){
return parent::getLogger('CREST');
static function getSSOLogger(){
return parent::getLogger('SSO');
}
}

View File

@@ -1,74 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 23.05.2016
* Time: 18:25
*/
namespace controller\ccp;
use Data\Mapper as Mapper;
use Controller;
use Lib;
class Xml extends Controller\Controller{
/**
* get HTTP request options for API (curl) request
* @return array
*/
protected function getRequestOptions(){
$requestOptions = [
'timeout' => 4,
'user_agent' => $this->getUserAgent(),
'follow_location' => false // otherwise CURLOPT_FOLLOWLOCATION will fail
];
return $requestOptions;
}
/**
* request character data from CCP API
* @param int $characterId
* @return array
*/
public function getPublicCharacterData($characterId){
$characterData = [];
$apiPath = self::getEnvironmentData('CCP_XML') . '/eve/CharacterInfo.xml.aspx';
$baseOptions = $this->getRequestOptions();
$requestOptions = [
'method' => 'GET',
'content' => [
'characterID' => (int)$characterId
]
];
$requestOptions = array_merge($baseOptions, $requestOptions);
$apiResponse = Lib\Web::instance()->request($apiPath, $requestOptions );
if(
$apiResponse['body'] &&
($xml = simplexml_load_string($apiResponse['body']))
){
if(
isset($xml->result) &&
is_object($rowApiData = $xml->result->children())
){
foreach($rowApiData as $item){
// map attributes to array
if(count($item->children()) == 0){
$characterData[$item->getName()] = strval($item);
}
}
}
}
$data = (new Mapper\CcpCharacterMapper($characterData))->getData();
return $data;
}
}

View File

@@ -8,10 +8,8 @@
namespace Controller;
use Controller\Api as Api;
use Controller\Ccp\Sso as Sso;
use lib\Config;
use lib\Socket;
use Lib\Util;
use Model;
use DB;
@@ -189,8 +187,9 @@ class Controller {
* set/update logged in cookie by character model
* -> store validation data in DB
* @param Model\CharacterModel $character
* @param string $scopeHash
*/
protected function setLoginCookie(Model\CharacterModel $character){
protected function setLoginCookie(Model\CharacterModel $character, $scopeHash = ''){
if( $this->getCookieState() ){
$expireSeconds = (int) $this->getF3()->get('PATHFINDER.LOGIN.COOKIE_EXPIRE');
@@ -222,7 +221,8 @@ class Controller {
'characterId' => $character,
'selector' => $selector,
'token' => $token,
'expires' => $expireTime->format('Y-m-d H:i:s')
'expires' => $expireTime->format('Y-m-d H:i:s'),
'scopeHash' => $scopeHash
];
$authenticationModel = $character->rel('characterAuthentications');
@@ -270,20 +270,20 @@ class Controller {
// cookie data is well formatted
$characterAuth->getByForeignKey('selector', $data[0], ['limit' => 1]);
// validate expire data
// validate token
// validate "scope hash", "expire data" and "validate token"
if( !$characterAuth->dry() ){
if(
$characterAuth->scopeHash === $this->getRequestedScopeHash() &&
strtotime($characterAuth->expires) >= $currentTime->getTimestamp() &&
hash_equals($characterAuth->token, hash('sha256', $data[1]))
){
// cookie information is valid
// -> try to update character information from CREST
// -> try to update character information from ESI
// e.g. Corp has changed, this also ensures valid "access_token"
/**
* @var $character Model\CharacterModel
*/
$updateStatus = $characterAuth->characterId->updateFromCrest();
$updateStatus = $characterAuth->characterId->updateFromESI();
if( empty($updateStatus) ){
// make sure character data is up2date!
@@ -293,13 +293,15 @@ class Controller {
// check if character still has user (is not the case of "ownerHash" changed
// check if character is still authorized to log in (e.g. corp/ally or config has changed
// -> do NOT remove cookie on failure. This can be a temporary problem (e.g. CREST is down,..)
// -> do NOT remove cookie on failure. This can be a temporary problem (e.g. ESI is down,..)
if(
$character->hasUserCharacter() &&
$character->isAuthorized()
){
$characters[$name] = $character;
}
}else{
$invalidCookie = true;
}
}else{
// clear existing authentication data from DB
@@ -449,6 +451,15 @@ class Controller {
return $user;
}
/**
* get a hash over all requested ESI scopes
* -> this helps to invalidate "authentication data" after scope change
* @return string
*/
protected function getRequestedScopeHash(){
return md5(serialize( self::getEnvironmentData('CCP_ESI_SCOPES') ));
}
/**
* log out current character
* @param \Base $f3
@@ -473,20 +484,37 @@ class Controller {
}
/**
* get EVE server status from CREST
* get EVE server status from ESI
* @param \Base $f3
*/
public function getEveServerStatus(\Base $f3){
// server status can be cached for some seconds
$cacheKey = 'eve_server_status';
if( !$f3->exists($cacheKey, $return) ){
$return = (object) [];
$return->error = [];
$return->status = [
'serverName' => strtoupper( self::getEnvironmentData('CCP_ESI_DATASOURCE') ),
'serviceStatus' => 'offline'
];
$sso = new Sso();
$return->status = $sso->getCrestServerStatus();
$response = $f3->ccpClient->getServerStatus();
if( !empty($response) ){
// calculate time diff since last server restart
$timezone = new \DateTimeZone( $f3->get('TZ') );
$dateNow = new \DateTime('now', $timezone);
$dateServerStart = new \DateTime($response['startTime']);
$interval = $dateNow->diff($dateServerStart);
$startTimestampFormat = $interval->format('%hh %im');
if($interval->days > 0){
$startTimestampFormat = $interval->days . 'd ' . $startTimestampFormat;
}
$response['serverName'] = strtoupper( self::getEnvironmentData('CCP_ESI_DATASOURCE') );
$response['serviceStatus'] = 'online';
$response['startTime'] = $startTimestampFormat;
$return->status = $response;
if( !$return->status->crestOffline ){
$f3->set($cacheKey, $return, 60);
}
}
@@ -797,7 +825,7 @@ class Controller {
/**
* get environment specific configuration data
* @param string $key
* @return string|null
* @return string|array|null
*/
static function getEnvironmentData($key){
return Config::getEnvironmentData($key);

View File

@@ -34,10 +34,14 @@ class MailController extends \SMTP{
* @return bool
*/
public function sendDeleteAccount($to, $msg){
$this->set('To', '<' . $to . '>');
$this->set('From', '"Pathfinder" <' . Controller::getEnvironmentData('SMTP_FROM') . '>');
$this->set('Subject', 'Account deleted');
$status = $this->send($msg);
$status = false;
if( !empty($to)){
$this->set('To', '<' . $to . '>');
$this->set('From', '"Pathfinder" <' . Controller::getEnvironmentData('SMTP_FROM') . '>');
$this->set('Subject', 'Account deleted');
$status = $this->send($msg);
}
return $status;
}
@@ -49,10 +53,27 @@ class MailController extends \SMTP{
* @return bool
*/
public function sendRallyPoint($to, $msg){
$this->set('To', '<' . $to . '>');
$this->set('From', '"Pathfinder" <' . Controller::getEnvironmentData('SMTP_FROM') . '>');
$this->set('Subject', 'PATHFINDER - New rally point');
$status = $this->send($msg);
$status = false;
if( !empty($to)){
$this->set('To', '<' . $to . '>');
$this->set('From', '"Pathfinder" <' . Controller::getEnvironmentData('SMTP_FROM') . '>');
$this->set('Subject', 'PATHFINDER - New rally point');
$status = $this->send($msg);
}
return $status;
}
public function send($message, $log = true, $mock = false){
$status = false;
if(
!empty($this->host) &&
!empty($this->port)
){
$status = parent::send($message, $log, $mock);
}
return $status;
}

View File

@@ -34,11 +34,11 @@ class Setup extends Controller {
'DB_CCP_NAME',
'DB_CCP_USER',
'DB_CCP_PASS',
'CCP_CREST_URL',
'SSO_CCP_URL',
'SSO_CCP_CLIENT_ID',
'SSO_CCP_SECRET_KEY',
'CCP_XML',
'CCP_SSO_URL',
'CCP_SSO_CLIENT_ID',
'CCP_SSO_SECRET_KEY',
'CCP_ESI_URL',
'CCP_ESI_DATASOURCE',
'SMTP_HOST',
'SMTP_PORT',
'SMTP_SCHEME',
@@ -183,6 +183,8 @@ class Setup extends Controller {
$this->exportTable($params['exportTable']);
}elseif( !empty($params['clearCache']) ){
$this->clearCache($f3);
}elseif( !empty($params['invalidateCookies']) ){
$this->invalidateCookies($f3);
}
// set template data ----------------------------------------------------------------
@@ -314,7 +316,7 @@ class Setup extends Controller {
];
// obscure some values
$obscureVars = ['SSO_CCP_CLIENT_ID', 'SSO_CCP_SECRET_KEY', 'SMTP_PASS'];
$obscureVars = ['CCP_SSO_CLIENT_ID', 'CCP_SSO_SECRET_KEY', 'SMTP_PASS'];
foreach($this->environmentVars as $var){
if( !in_array($var, $excludeVars) ){
@@ -1203,6 +1205,21 @@ class Setup extends Controller {
$f3->clear('CACHE');
}
/**
* clear all character authentication (Cookie) data
* @param \Base $f3
*/
protected function invalidateCookies(\Base $f3){
$this->getDB('PF');
$authentidationModel = Model\BasicModel::getNew('CharacterAuthenticationModel');
$results = $authentidationModel->find();
if($results){
foreach($results as $result){
$result->erase();
}
}
}
/**
* convert Bytes to string + suffix
* @param int $bytes

View File

@@ -9,7 +9,6 @@
namespace Cron;
use Controller;
use DB;
use lib\Config;
class CcpSystemsUpdate {
@@ -26,9 +25,9 @@ class CcpSystemsUpdate {
*/
protected $logTables = [
'jumps' => 'system_jumps',
'shipKills' => 'system_kills_ships',
'podKills' => 'system_kills_pods',
'factionKills' => 'system_kills_factions'
'ship_kills' => 'system_kills_ships',
'pod_kills' => 'system_kills_pods',
'npc_kills' => 'system_kills_factions'
];
/**
@@ -74,77 +73,37 @@ class CcpSystemsUpdate {
*/
function importSystemData($f3){
// prepare system jump log table ------------------------------------------------------------------------------
$time_start = microtime(true);
// prepare system jump log table
$systemsData = $this->prepareSystemLogTables();
$time_end = microtime(true);
$execTimePrepareSystemLogTables = $time_end - $time_start;
// switch DB for data import..
$pfDB = DB\Database::instance()->getDB('PF');
// get current jump Data -------------------------------------------------------
// get current jump data --------------------------------------------------------------------------------------
$time_start = microtime(true);
$apiPath = Config::getEnvironmentData('CCP_XML') . '/map/Jumps.xml.aspx';
$apiResponse = \Web::instance()->request($apiPath, $this->apiRequestOptions );
$jumpData = [];
$updateJumps = false;
if($apiResponse['body']){
$xml = simplexml_load_string($apiResponse['body']);
$rowApiData = $xml->result->rowset;
foreach($rowApiData->children() as $systemApiData){
$attributeApiData = $systemApiData->attributes();
$systemId = $attributeApiData->solarSystemID->__toString();
$shipJumps =$attributeApiData->shipJumps->__toString();
$jumpData[$systemId] = $shipJumps;
}
$updateJumps = true;
}
$jumpData = $f3->ccpClient->getUniverseJumps();
$time_end = microtime(true);
$execTimeGetJumpData = $time_end - $time_start;
// get current kill Data -------------------------------------------------------
// get current kill data --------------------------------------------------------------------------------------
$time_start = microtime(true);
$apiPath = Config::getEnvironmentData('CCP_XML') . '/map/Kills.xml.aspx';
$apiResponse = \Web::instance()->request($apiPath, $this->apiRequestOptions );
$killData = [];
$updateKills = false;
if($apiResponse['body']){
$xml = simplexml_load_string($apiResponse['body']);
$rowApiData = $xml->result->rowset;
foreach($rowApiData->children() as $systemApiData){
$attributeApiData = $systemApiData->attributes();
$systemId = $attributeApiData->solarSystemID->__toString();
$shipKills =$attributeApiData->shipKills->__toString();
$podKills =$attributeApiData->podKills->__toString();
$factionKills =$attributeApiData->factionKills->__toString();
$killData[$systemId] = [
'shipKills' => $shipKills,
'podKills' => $podKills,
'factionKills' => $factionKills,
];
}
$updateKills = true;
}
$killData = $f3->ccpClient->getUniverseKills();
$time_end = microtime(true);
$execTimeGetKillData = $time_end - $time_start;
// update system log tables -----------------------------------------------------
// merge both results
$systemValues = array_replace_recursive($jumpData, $killData);
// update system log tables -----------------------------------------------------------------------------------
$time_start = microtime(true);
// make sure last update is (at least) 1h ago
$pfDB->begin();
foreach($this->logTables as $key => $tableName){
$sql = "UPDATE
" . $tableName . "
$tableName
SET
updated = now(),
value24 = value23,
@@ -173,47 +132,30 @@ class CcpSystemsUpdate {
value1 = :value
WHERE
systemId = :systemId
";
";
foreach($systemsData as $systemData){
$systemId = $systemData['systemId'];
if(
$key == 'jumps' &&
$updateJumps
){
// update jump data (if available)
$currentJumps = 0;
if(array_key_exists($systemData['systemId'], $jumpData)){
$currentJumps = $jumpData[$systemData['systemId']];
}
$pfDB->exec($sql, array(
':systemId' => $systemData['systemId'],
':value' => $currentJumps
), 0, false);
}else if($updateKills){
// update kill data (if available)
$currentKills = 0;
if(array_key_exists($systemData['systemId'], $killData)){
$currentKillData = $killData[$systemData['systemId']];
$currentKills = $currentKillData[$key];
}
$pfDB->exec($sql, array(
':systemId' => $systemData['systemId'],
':value' => $currentKills
), 0, false);
// update data (if available)
$currentData = 0;
if( isset($systemValues[$systemId][$key]) ){
$currentData = (int)$systemValues[$systemId][$key];
}
$pfDB->exec($sql, [
':systemId' => $systemId,
':value' => $currentData
], 0, false);
}
}
$pfDB->commit();
$time_end = microtime(true);
$execTimeUpdateTables = $time_end - $time_start;
// Log ------------------------
// Log --------------------------------------------------------------------------------------------------------
$log = new \Log('cron_' . __FUNCTION__ . '.log');
$log->write( sprintf(self::LOG_TEXT, __FUNCTION__, $execTimePrepareSystemLogTables, $execTimeGetJumpData, $execTimeGetKillData, $execTimeUpdateTables) );
}

View File

@@ -1,37 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 23.05.2016
* Time: 20:32
*/
namespace data\mapper;
class CcpCharacterMapper extends AbstractIterator {
protected static $map = [
'characterID' => ['character' => 'id'],
'characterName' => ['character' => 'name'],
'race' => 'race',
'bloodlineID' => ['blood' => 'id'],
'bloodline' => ['blood' => 'name'],
'ancestryID' => ['origin' => 'id'],
'ancestry' => ['origin' => 'name'],
'corporationID' => ['corp' => 'id'],
'corporation' => ['corp' => 'name'],
'corporationDate' => ['corp' => 'date'],
'allianceID' => ['alli' => 'id'],
'alliance' => ['alli' => 'name'],
'allianceDate' => ['alli' => 'date'],
'securityStatus' => 'security'
];
}

View File

@@ -1,18 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 15.05.2016
* Time: 22:04
*/
namespace Data\Mapper;
class CrestAlliance extends AbstractIterator {
protected static $map = [
'id' => 'id',
'name' => 'name'
];
}

View File

@@ -1,19 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 31.01.2016
* Time: 19:09
*/
namespace Data\Mapper;
class CrestCharacter extends AbstractIterator {
protected static $map = [
'id' => 'id',
'name' => 'name'
];
}

View File

@@ -1,20 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 31.01.2016
* Time: 03:55
*/
namespace Data\Mapper;
class CrestCorporation extends AbstractIterator {
protected static $map = [
'id' => 'id',
'name' => 'name',
'isNPC' => 'isNPC'
];
}

View File

@@ -1,19 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 01.05.2016
* Time: 19:17
*/
namespace Data\Mapper;
class CrestServiceStatus extends AbstractIterator {
protected static $map = [
'dust' => 'dust',
'eve' => 'eve',
'server' => 'server'
];
}

View File

@@ -1,18 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 09.04.2016
* Time: 21:11
*/
namespace Data\Mapper;
class CrestStation extends AbstractIterator {
protected static $map = [
'id' => 'id',
'name' => 'name'
];
}

View File

@@ -1,18 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 07.02.2016
* Time: 14:34
*/
namespace Data\Mapper;
class CrestSystem extends AbstractIterator {
protected static $map = [
'id' => 'id',
'name' => 'name'
];
}

View File

@@ -1,18 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 01.05.2016
* Time: 19:42
*/
namespace Data\Mapper;
class CrestUserCounts extends AbstractIterator {
protected static $map = [
'dust' => 'dust',
'eve' => 'eve'
];
}

View File

@@ -98,17 +98,26 @@ class Database extends \Prefab {
*/
protected function connect($dns, $name, $user, $password){
$db = null;
$f3 = \Base::instance();
$options = [
\PDO::MYSQL_ATTR_COMPRESS => true,
\PDO::ATTR_TIMEOUT => \Base::instance()->get('REQUIREMENTS.MYSQL.PDO_TIMEOUT'),
];
// set ERRMODE depending on pathfinders global DEBUG level
if($f3->get('DEBUG') >= 3){
$options[\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_WARNING;
}else{
$options[\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_EXCEPTION;
}
try {
$db = new SQL(
$dns . $name,
$user,
$password,
[
\PDO::MYSQL_ATTR_COMPRESS => true,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_TIMEOUT => \Base::instance()->get('REQUIREMENTS.MYSQL.PDO_TIMEOUT'),
]
$options
);
}catch(\PDOException $e){
// DB connection error

View File

@@ -0,0 +1,96 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus4D
* Date: 26.03.2017
* Time: 19:17
*/
namespace Lib;
use controller\LogController;
use \Exodus4D\ESI\ESI as ApiClient;
class CcpClient extends \Prefab {
private $apiClient;
public function __construct(){
$f3 = \Base::instance();
$this->apiClient = $this->getClient($f3);
$f3->set('ccpClient', $this);
}
protected function getClient($f3){
$client = null;
if( !class_exists(ApiClient::class) ){
LogController::getLogger('ERROR')->write($this->getMissingClientError());
}else{
$client = new ApiClient($f3);
$client->setUrl( Config::getEnvironmentData('CCP_ESI_URL') );
$client->setDatasource( Config::getEnvironmentData('CCP_ESI_DATASOURCE') );
$client->setUserAgent($this->getUserAgent($f3));
}
return $client;
}
/**
* @param \Base $f3
* @return string
*/
protected function getUserAgent($f3){
$userAgent = '';
$userAgent .= $f3->get('PATHFINDER.NAME');
$userAgent .= ' - ' . $f3->get('PATHFINDER.VERSION');
$userAgent .= ' | ' . $f3->get('PATHFINDER.CONTACT');
$userAgent .= ' (' . $_SERVER['SERVER_NAME'] . ')';
return $userAgent;
}
/**
* get error msg for failed ApiClient() class -> Composer package not found
* @return string
*/
protected function getMissingClientError(){
return "Class '" . ApiClient::class . "' not found. -> Check installed Composer packages.'";
}
/**
* get error msg for undefined method in ApiClient() class
* @param $method
* @return string
*/
protected function getMissingMethodError($method){
return "Method '" . $method . "()' not found in class '" . get_class($this->apiClient) . "'. -> Check installed Composer package version.'";
}
/**
* call request API data
* @param $name
* @param $arguments
* @return array|mixed
*/
public function __call($name, $arguments){
$return = [];
if(is_object($this->apiClient)){
if( method_exists($this->apiClient, $name) ){
$return = call_user_func_array([$this->apiClient, $name], $arguments);
}else{
LogController::getLogger('ERROR')->write($this->getMissingMethodError($name));
\Base::instance()->error(501, $this->getMissingMethodError($name));
}
}else{
LogController::getLogger('ERROR')->write($this->getMissingClientError());
\Base::instance()->error(501, $this->getMissingClientError());
}
return $return;
}
}

View File

@@ -16,6 +16,12 @@ class Config extends \Prefab {
const HIVE_KEY_PATHFINDER = 'PATHFINDER';
const HIVE_KEY_ENVIRONMENT = 'ENVIRONMENT';
/**
* environment config keys that should be parsed as array
* -> use "," as delimiter in config files/data
*/
const ARRAY_KEYS = ['CCP_ESI_SCOPES'];
/**
* all environment data
* @var array
@@ -79,6 +85,12 @@ class Config extends \Prefab {
if( !empty($this->serverConfigData['ENV']) ){
// get environment config from $_SERVER data
$environmentData = (array)$this->serverConfigData['ENV'];
// some environment variables should be parsed as array
array_walk($environmentData, function(&$item, $key){
$item = (in_array($key, self::ARRAY_KEYS)) ? explode(',', $item) : $item;
});
$environmentData['TYPE'] = 'PHP: environment variables';
}else{
// get environment data from *.ini file config

View File

@@ -8,8 +8,6 @@
namespace lib;
use controller\LogController;
class Socket {
// max TTL time (ms)
@@ -106,7 +104,10 @@ class Socket {
$this->initSocket();
if( !$this->socket ){
if(
!$this->socket ||
!$this->socketUri
){
// Socket not active (e.g. URI missing)
return $response;
}

View File

@@ -15,8 +15,8 @@ class Web extends \Web {
const ERROR_STATUS_LOG = 'HTTP %s: \'%s\' | url: %s \'%s\'%s';
/**
* max number of CREST curls for a single endpoint until giving up...
* this is because CREST is not very stable
* max number of curls calls for a single resource until giving up...
* this is because SSO API is not very stable
*/
const RETRY_COUNT_MAX = 3;
@@ -98,7 +98,7 @@ class Web extends \Web {
* @param string $url
* @param array|null $options
* @param array $additionalOptions
* @param int $retryCount request counter for failed crest call
* @param int $retryCount request counter for failed call
* @return array|FALSE|mixed
*/
public function request($url,array $options = null, $additionalOptions = [], $retryCount = 0 ) {

View File

@@ -50,6 +50,11 @@ class CharacterAuthenticationModel extends BasicModel{
'type' => Schema::DT_TIMESTAMP,
'default' => Schema::DF_CURRENT_TIMESTAMP,
'index' => true
],
'scopeHash' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
]
];

View File

@@ -35,9 +35,6 @@ class CharacterLogModel extends BasicModel {
]
]
],
// --------------------------------------------------------------------
'systemId' => [
'type' => Schema::DT_INT,
'index' => true
@@ -47,26 +44,6 @@ class CharacterLogModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'constellationId' => [
'type' => Schema::DT_INT,
'index' => true
],
'constellationName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'regionId' => [
'type' => Schema::DT_INT,
'index' => true
],
'regionName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
// --------------------------------------------------------------------
'shipTypeId' => [
'type' => Schema::DT_INT,
'index' => true
@@ -110,24 +87,6 @@ class CharacterLogModel extends BasicModel {
$this->systemName = '';
}
if( isset($logData['constellation']) ){
$this->constellationId = (int)$logData['constellation']['id'];
$this->constellationName = $logData['constellation']['name'];
}else{
$this->constellationId = null;
$this->constellationName = '';
}
if( isset($logData['region']) ){
$this->regionId = (int)$logData['region']['id'];
$this->regionName = $logData['region']['name'];
}else{
$this->regionId = null;
$this->regionName = '';
}
// --------------------------------------------------------------------
if( isset($logData['ship']) ){
$this->shipTypeId = (int)$logData['ship']['typeId'];
$this->shipTypeName = $logData['ship']['typeName'];
@@ -161,16 +120,6 @@ class CharacterLogModel extends BasicModel {
$logData->system->id = (int)$this->systemId;
$logData->system->name = $this->systemName;
$logData->constellation = (object) [];
$logData->constellation->id = (int)$this->constellationId;
$logData->constellation->name = $this->constellationName;
$logData->region = (object) [];
$logData->region->id = (int)$this->regionId;
$logData->region->name = $this->regionName;
// --------------------------------------------------------------------
$logData->ship = (object) [];
$logData->ship->typeId = (int)$this->shipTypeId;
$logData->ship->typeName = $this->shipTypeName;

View File

@@ -75,15 +75,6 @@ class CharacterModel extends BasicModel {
]
]
],
'factionId' => [
'type' => Schema::DT_INT,
'index' => true
],
'factionName' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'shared' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
@@ -94,6 +85,11 @@ class CharacterModel extends BasicModel {
'nullable' => false,
'default' => 1
],
'securityStatus' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
],
'userCharacter' => [
'has-one' => ['Model\UserCharacterModel', 'characterId']
],
@@ -178,7 +174,7 @@ class CharacterModel extends BasicModel {
}
/**
* set CREST accessToken for current session
* set API accessToken for current session
* -> update "tokenUpdated" column on change
* -> this is required for expire checking!
* @param string $accessToken
@@ -191,6 +187,11 @@ class CharacterModel extends BasicModel {
return $accessToken;
}
/**
* logLocation specifies whether the current system should be tracked or not
* @param $logLocation
* @return bool
*/
public function set_logLocation($logLocation){
$logLocation = (bool)$logLocation;
if(
@@ -304,7 +305,7 @@ class CharacterModel extends BasicModel {
}
/**
* get CREST API "access_token" from OAuth
* get ESI API "access_token" from OAuth
* @return bool|string
*/
public function getAccessToken(){
@@ -365,13 +366,8 @@ class CharacterModel extends BasicModel {
$isAuthorized = false;
$f3 = self::getF3();
$whitelistCorporations = $whitelistAlliance = [];
if( !empty($f3->get('PATHFINDER.LOGIN.CORPORATION')) ){
$whitelistCorporations = array_map('trim',(array) $f3->get('PATHFINDER.LOGIN.CORPORATION') );
}
if( !empty($f3->get('PATHFINDER.LOGIN.ALLIANCE')) ){
$whitelistAlliance = array_map('trim',(array) $f3->get('PATHFINDER.LOGIN.ALLIANCE') );
}
$whitelistCorporations = array_filter( array_map('trim', (array)$f3->get('PATHFINDER.LOGIN.CORPORATION') ) );
$whitelistAlliance = array_filter( array_map('trim', (array)$f3->get('PATHFINDER.LOGIN.ALLIANCE') ) );
if(
empty($whitelistCorporations) &&
@@ -405,48 +401,113 @@ class CharacterModel extends BasicModel {
/**
* update character log (active system, ...)
* -> CREST API request for character log data
* -> API request for character log data
* @param array $additionalOptions (optional) request options for cURL request
* @return $this
*/
public function updateLog($additionalOptions = []){
$deleteLog = true;
//check if log update is enabled for this user
if( $this->logLocation ){
// Try to pull data from CREST
$ssoController = new Sso();
$logData = $ssoController->getCharacterLocationData($this->getAccessToken(), $additionalOptions);
// Try to pull data from API
if( $accessToken = $this->getAccessToken() ){
$locationData = self::getF3()->ccpClient->getCharacterLocationData($this->_id, $accessToken, $additionalOptions);
if($logData['timeout'] === false){
if( empty($logData['system']) ){
// character is not in-game
if( $this->hasLog() ){
// delete existing log
$this->characterLog->erase();
$this->save();
}
}else{
if( !empty($locationData['system']['id']) ){
// character is currently in-game
// IDs for "systemId", "stationId and "shipTypeId" that require more data
$lookupIds = [];
if( !$characterLog = $this->getLog() ){
// create new log
$characterLog = $this->rel('characterLog');
$characterLog->characterId = $this->_id;
}
$characterLog->setData($logData);
$characterLog->save();
$this->characterLog = $characterLog;
// get current log data and modify on change
$logData = json_decode(json_encode( $characterLog->getData()), true);
if(
empty($logData['system']['name']) ||
$logData['system']['id'] !== $locationData['system']['id']
){
// system changed -> request "system name" for current system
$lookupIds[] = $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
$lookupIds[] = $locationData['station']['id'];
}
}else{
unset($logData['station']);
}
$logData = array_replace_recursive($logData, $locationData);
// get current ship data
$shipData = self::getF3()->ccpClient->getCharacterShipData($this->_id, $accessToken, $additionalOptions);
if( !empty($shipData['ship']['typeId']) ){
if(
empty($logData['ship']['typeName']) ||
$logData['ship']['typeId'] !== $shipData['ship']['typeId']
){
// ship changed -> request "station name" for current station
$lookupIds[] = $shipData['ship']['typeId'];
}
// "shipName"/"shipId" could have changed...
$logData = array_replace_recursive($logData, $shipData);
}else{
unset($logData['ship']);
}
if( !empty($lookupIds) ){
// get "more" information for some Ids (e.g. name)
$universeData = self::getF3()->ccpClient->getUniverseNamesData($lookupIds, $additionalOptions);
if( !empty($universeData) ){
$deleteLog = false;
$logData = array_replace_recursive($logData, $universeData);
}
}else{
$deleteLog = false;
}
if( !$deleteLog ){
$characterLog->setData($logData);
$characterLog->save();
$this->characterLog = $characterLog;
}
}
}
}
if(
$deleteLog &&
$this->hasLog()
){
// delete existing log
$this->characterLog->erase();
$this->save();
}
return $this;
}
/**
* update character data from CCPs CREST API
* update character data from CCPs ESI API
* @return array (some status messages)
*/
public function updateFromCrest(){
public function updateFromESI(){
$status = [];
if( $accessToken = $this->getAccessToken() ){
@@ -458,36 +519,14 @@ class CharacterModel extends BasicModel {
!is_null( $verificationCharacterData = $ssoController->verifyCharacterData($accessToken) ) &&
$verificationCharacterData->CharacterID === $this->_id
){
// get character data from CREST
$characterData = $ssoController->getCharacterData($accessToken);
if( isset($characterData->character) ){
// get character data from API
$characterData = $ssoController->getCharacterData($this->_id);
if( !empty($characterData->character) ){
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
$corporation = null;
$alliance = null;
if( isset($characterData->corporation) ){
/**
* @var $corporation CorporationModel
*/
$corporation = $this->rel('corporationId');
$corporation->getById($characterData->corporation['id'], 0);
$corporation->copyfrom($characterData->corporation, ['id', 'name', 'isNPC']);
$corporation->save();
}
if( isset($characterData->alliance) ){
/**
* @var $alliance AllianceModel
*/
$alliance = $this->rel('allianceId');
$alliance->getById($characterData->alliance['id'], 0);
$alliance->copyfrom($characterData->alliance, ['id', 'name']);
$alliance->save();
}
$this->copyfrom($characterData->character, ['name', 'ownerHash']);
$this->set('corporationId', is_object($corporation) ? $corporation->get('id') : null);
$this->set('allianceId', is_object($alliance) ? $alliance->get('id') : null);
$this->copyfrom($characterData->character, ['ownerHash', 'securityStatus']);
$this->corporationId = $characterData->corporation;
$this->allianceId = $characterData->alliance;
$this->save();
}
}else{

View File

@@ -332,6 +332,10 @@ class ConnectionModel extends BasicModel{
return $signatures;
}
/**
* get all signature data linked to this connection
* @return array
*/
public function getSignaturesData(){
$signaturesData = [];
$signatures = $this->getSignatures();

View File

@@ -283,6 +283,22 @@ class SystemModel extends BasicModel {
return $systemData;
}
/**
* setter for system alias
* @param string $alias
* @return string
*/
public function set_alias($alias){
$alias = trim($alias);
// we don´t need redundant data. "name" is always preferred if "alias" is empty
if($alias === $this->name){
$alias = '';
}
return $alias;
}
/**
* setter for system security value
* @param float $trueSec

View File

@@ -3,7 +3,7 @@
[PATHFINDER]
NAME = Pathfinder
; installed version (used for CSS/JS cache busting)
VERSION = v1.2.1
VERSION = v1.2.2
; contact information [optional]
CONTACT = https://github.com/exodus4d
; public contact email [optional]
@@ -142,8 +142,8 @@ EXPIRE_SIGNATURES = 259200
[PATHFINDER.LOGFILES]
; error log
ERROR = error
; CREST error log
CREST = crest
; SSO error log
SSO = sso
; login information
LOGIN = login
; session warnings (suspect)

View File

@@ -39,7 +39,7 @@ ZMQ = 4.1.3
[REQUIREMENTS.MYSQL]
; min MySQL Version
; newer "deviation" of MySQL like "MariaDB" > 10.1 are recommended
VERSION = 5.6
VERSION = 5.7
; DB timeout (seconds)
PDO_TIMEOUT = 2

28
composer-dev.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "exodus4d/pathfinder",
"description": "Mapping tool for EVE ONLINE",
"minimum-stability": "stable",
"license": "MIT",
"authors": [
{
"name": "Mark Friedrich",
"email": "pathfinder@exodus4d.de"
}
],
"autoload": {
"psr-4": {
"Exodus4D\\Pathfinder\\": "app/main"
}
},
"repositories": [
{
"type": "vcs",
"url": "../pathfinder_esi"
}],
"require": {
"php-64bit": ">=7.0",
"ext-zmq": "1.1.*",
"react/zmq": "0.3.*",
"exodus4d/pathfinder_esi": "dev-develop as 0.0.x-dev"
}
}

View File

@@ -14,9 +14,15 @@
"Exodus4D\\Pathfinder\\": "app/main"
}
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/exodus4d/pathfinder_esi"
}],
"require": {
"php-64bit": ">=7.0",
"ext-zmq": "1.1.*",
"react/zmq": "0.3.*"
"react/zmq": "0.3.*",
"exodus4d/pathfinder_esi": "dev-master#v1.0.0"
}
}
}

View File

@@ -1,6 +1,12 @@
<?php
session_name('pathfinder_session');
$f3 = require('app/lib/base.php');
$composerAutoloader = 'vendor/autoload.php';
if(file_exists($composerAutoloader)){
require_once($composerAutoloader);
}
$f3 = require_once('app/lib/base.php');
// load main config
$f3->config('app/config.ini');
@@ -8,6 +14,9 @@ $f3->config('app/config.ini');
// load environment dependent config
lib\Config::instance();
// initiate CCP API Client (ESI)
lib\CcpClient::instance();
// initiate cron-jobs
Cron::instance();

View File

@@ -48,7 +48,7 @@ requirejs.config({
blueImpGallery: 'lib/blueimp-gallery', // v2.21.3 Image Gallery - https://github.com/blueimp/Gallery
blueImpGalleryHelper: 'lib/blueimp-helper', // helper function for Blue Imp Gallery
blueImpGalleryBootstrap: 'lib/bootstrap-image-gallery', // v3.4.2 Bootstrap extension for Blue Imp Gallery - https://blueimp.github.io/Bootstrap-Image-Gallery
bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.1 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.5 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
bootstrapToggle: 'lib/bootstrap2-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
lazyload: 'lib/jquery.lazyload.min', // v1.9.5 LazyLoader images - http://www.appelsiini.net/projects/lazyload

View File

@@ -16,6 +16,7 @@ define(['jquery'], function($) {
logIn: 'api/user/logIn', // ajax URL - login
logout: 'api/user/logout', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
openIngameWindow: 'api/user/openIngameWindow', // ajax URL - open inGame Window
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
// access API
@@ -65,6 +66,7 @@ define(['jquery'], function($) {
splashOverlay: 300, // "splash" loading overlay
headerLink: 100, // links in head bar
mapOverlay: 200, // show/hide duration for map overlays
mapOverlayLocal: 180, // show/hide duration for map "local" overlay
mapMoveSystem: 180, // system position has changed animation
mapDeleteSystem: 200, // remove system from map
mapModule: 200, // show/hide of an map module

View File

@@ -46,6 +46,9 @@ define([
// cookie hint
cookieHintId: 'pf-cookie-hint', // id for "cookie hint" element
// login
ssoButtonClass: 'pf-sso-login-button', // class for SSO login button
// character select
characterSelectionClass: 'pf-character-selection', // class for character panel wrapper
characterRowAnimateClass: 'pf-character-row-animate', // class for character panel row during animation
@@ -66,7 +69,9 @@ define([
serverPanelId: 'pf-server-panel', // id for EVE Online server status panel
// animation
animateElementClass: 'pf-animate-on-visible' // class for elements that will be animated to show
animateElementClass: 'pf-animate-on-visible', // class for elements that will be animated to show
defaultAcceptCookieExpire: 365 // default expire for "accept coolies" cookie
};
/**
@@ -123,10 +128,39 @@ define([
if(getCookie('cookie') !== '1'){
// hint not excepted
$('#' + config.cookieHintId).collapse('show');
// show Cookie accept hint on SSO login button
let confirmationSettings = {
container: 'body',
placement: 'bottom',
btnOkClass: 'btn btn-sm btn-default',
btnOkLabel: 'dismiss',
btnOkIcon: 'fa fa-fw fa-sign-in',
title: 'Accept cookies',
btnCancelClass: 'btn btn-sm btn-success',
btnCancelLabel: 'accept',
btnCancelIcon: 'fa fa-fw fa-check',
onCancel: function(e, target){
// "Accept cookies"
setCookie('cookie', 1, config.defaultAcceptCookieExpire);
// set "default" href
let href = $(target).data('bs.confirmation').getHref();
$(e.target).attr('href', href);
},
onConfirm : function(e, target){
// "NO cookies" => trigger "default" href link action
},
href: function(target){
return $(target).attr('href');
}
};
$('.' + config.ssoButtonClass).confirmation(confirmationSettings);
}
$('#' + config.cookieHintId + ' .btn-success').on('click', function(){
setCookie('cookie', 1, 365);
setCookie('cookie', 1, config.defaultAcceptCookieExpire);
});
// manual -------------------------------------------------------------

533
js/app/map/local.js Normal file
View File

@@ -0,0 +1,533 @@
/**
* map overlay functions for "Nearby" table
* Created by Exodus on 13.04.2017.
*/
define([
'jquery',
'app/init',
'app/util',
'app/map/util'
], function($, Init, Util, MapUtil) {
'use strict';
let config = {
overlayClass: 'pf-map-overlay', // class for all map overlays
overlayLocalClass: 'pf-map-overlay-local', // class for "local" overlay
// left section
overlayLocalContentClass: 'pf-map-overlay-local-content', // class for left area - content
overlayLocalHeadlineClass: 'pf-map-overlay-headline', // class for headline
overlayLocalTableClass: 'pf-local-table', // class for local tables
// right section
overlayLocalTriggerClass: 'pf-map-overlay-local-trigger', // class for open/close trigger icon
overlayLocalOpenClass: 'pf-map-overlay-local-open', // class for open status
overlayLocalMainClass: 'pf-map-overlay-local-main', // class for right area (always visible)
overlayLocalUsersClass: 'pf-map-overlay-local-users', // class for active user count
overlayLocalJumpsClass: 'pf-map-overlay-local-jumps', // class for jump distance for table results
// dataTable
tableImageCellClass: 'pf-table-image-cell', // class for table "image" cells
tableActionCellClass: 'pf-table-action-cell', // class for table "action" cells
tableActionCellIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
tableCellEllipsisClass: 'pf-table-cell-ellipsis',
tableCellEllipsis80Class: 'pf-table-cell-80',
tableCellEllipsis90Class: 'pf-table-cell-90'
};
/**
* checks whether overlay is currently open or not
* @param overlay
* @returns {*}
*/
let isOpen = (overlay) => {
return overlay.hasClass(config.overlayLocalOpenClass);
};
/**
* open overlay -> animation
* @param overlay
*/
let openOverlay = (overlay) => {
if( !isOpen(overlay) ){
let overlayMain = overlay.find('.' + config.overlayLocalMainClass);
overlayMain.find('.' + config.overlayLocalTriggerClass).addClass('right');
overlay.addClass(config.overlayLocalOpenClass);
overlay.velocity({
width: '350px'
},{
duration: Init.animationSpeed.mapOverlayLocal,
easing: 'easeOut'
});
}
};
/**
* close overlay -> animation
* @param overlay
*/
let closeOverlay = (overlay) => {
if( isOpen(overlay) ){
let overlayMain = overlay.find('.' + config.overlayLocalMainClass);
overlayMain.find('.' + config.overlayLocalTriggerClass).removeClass('right');
overlay.removeClass(config.overlayLocalOpenClass);
overlay.velocity({
width: '32px'
},{
duration: Init.animationSpeed.mapOverlayLocal,
easing: 'easeOut'
});
}
};
/**
* sets overlay observer
* @param overlay
* @param mapId
*/
let setOverlayObserver = (overlay, mapId) => {
let overlayMain = overlay.find('.' + config.overlayLocalMainClass);
overlayMain.on('click', function(){
let overlayMain = $(this).parent('.' + config.overlayLocalClass);
let isOpenStatus = isOpen(overlayMain);
// store current state in indexDB (client)
MapUtil.storeLocalData('map', mapId, 'showLocal', !isOpenStatus );
// trigger open/close
if( isOpenStatus ){
closeOverlay(overlay);
}else{
openOverlay(overlay);
}
});
overlayMain.initTooltips({
container: 'body',
placement: 'bottom'
});
};
/**
* filter DataTable rows by column data and return rowIds
* @param table
* @param data
* @param values
* @param checkExistence
*/
let filterRows = (table, data = 'id', values = [], checkExistence = true) => {
return table.rows().eq(0).filter( function (rowIdx) {
let rowExists = values.indexOf( table.row(rowIdx ).data()[data] ) !== -1;
if( !checkExistence ){
rowExists = !rowExists;
}
return rowExists;
});
};
/**
* Update the "headline" within the Overlay
* @param overlay
* @param systemData
* @param characterAll
* @param characterLocal
*/
let updateLocaleHeadline = (overlay, systemData, characterAll = 0, characterLocal = 0) => {
let headlineElement = overlay.find('.' + config.overlayLocalHeadlineClass);
let userCountElement = overlay.find('.' + config.overlayLocalUsersClass);
let secClassBase = Util.getSecurityClassForSystem('security');
let secClass = Util.getSecurityClassForSystem(systemData.security);
let childElements = headlineElement.children('span');
childElements.eq(1).removeClass().addClass(
[secClassBase, secClass].join(' ')
).text(systemData.security);
childElements.eq(2).text(systemData.alias ? systemData.alias : systemData.name);
// update userCount for "near by" count -------------------------------------------------------------------
if( characterAll > 0){
userCountElement.toggleClass( 'txt-color-green', true).toggleClass( 'txt-color-red', false);
}else{
userCountElement.toggleClass( 'txt-color-green', false).toggleClass( 'txt-color-red', true);
}
userCountElement.text(characterAll);
// update userCount in current system ---------------------------------------------------------------------
if( characterLocal > 0){
childElements.eq(3).toggleClass( 'txt-color-green', true).toggleClass( 'txt-color-red', false);
}else{
childElements.eq(3).toggleClass( 'txt-color-green', false).toggleClass( 'txt-color-red', true);
}
childElements.eq(3).text(characterLocal);
};
/**
* updates all changed table rows
* @param systemData
* @param userData
*/
$.fn.updateLocalTable = function(systemData, userData){
return this.each(function(){
let overlay = $(this);
let tableElement = overlay.find('.' + config.overlayLocalTableClass);
let localTable = tableElement.DataTable();
let mapId = systemData.mapId;
let characterAllIds = [];
let characterLocalIds = [];
// system is on map (just for security check)
for(let jumps in userData) {
if( userData.hasOwnProperty(jumps) ){
jumps = parseInt(jumps);
for(let j = 0; j < userData[jumps].length; j++){
// add jump distance
userData[jumps][j].jumps = jumps;
let rowData = userData[jumps][j];
// check for existing rows
let indexes = filterRows(localTable, 'id', [rowData.id]);
if(indexes.length > 0){
// row exists -> update
let changedRow = localTable.row( parseInt(indexes[0]) );
let changedRowElement = changedRow.nodes().to$();
// remove tooltips
changedRowElement.find('[title]').tooltip('hide').tooltip('destroy');
// update data
changedRow.data(rowData);
}else{
// new row
localTable.row.add(rowData);
}
if(jumps === 0){
characterLocalIds.push(rowData.id);
}
characterAllIds.push(rowData.id);
}
}
}
// remove rows that no longer exists ----------------------------------------------------------------------
let indexesRemove = filterRows(localTable, 'id', characterAllIds, false);
localTable.rows(indexesRemove).remove();
localTable.draw();
// update system relevant data in overlay -----------------------------------------------------------------
updateLocaleHeadline(overlay, systemData, characterAllIds.length, characterLocalIds.length);
// open Overlay -------------------------------------------------------------------------------------------
if( !isOpen(overlay) ){
let promiseStore = MapUtil.getLocaleData('map', mapId);
promiseStore.then(function(dataStore) {
if(
dataStore &&
dataStore.showLocal
){
openOverlay(overlay);
}
});
}
});
};
/**
* Access a nested JSON object by "dot.notation" syntax
* @param obj
* @param selector
* @returns {*}
*/
let getDescendantProp = (obj, selector) => {
return selector.split('.').reduce(function(a, b) {
return a[b];
}, obj);
};
/**
* init tooltip for a "DataTables" Cell
* @param api
* @param cell
* @param titleSelector
*/
let initCellTooltip = (api, cell, titleSelector = '') => {
$(cell).hover( function(e){
let rowIdx = api.cell(cell).index().row;
let rowData = api.row(rowIdx).data();
$(this).tooltip({
container: 'body',
title: String( getDescendantProp(rowData, titleSelector) ),
placement: 'left',
delay: 100
}).tooltip('show');
}, function(e){
$(this).tooltip('hide');
});
};
/**
* init all map local overlay on a "parent" element
* @returns {*}
*/
$.fn.initLocalOverlay = function(mapId){
return this.each(function(){
let parentElement = $(this);
let overlay = $('<div>', {
class: [config.overlayClass, config.overlayLocalClass].join(' ')
});
let content = $('<div>', {
class: [ 'text-right', config.overlayLocalContentClass].join(' ')
});
// crate new route table
let table = $('<table>', {
class: ['compact', 'order-column', config.overlayLocalTableClass].join(' ')
});
let overlayMain = $('<div>', {
text: '',
class: config.overlayLocalMainClass
}).append(
$('<i>', {
class: ['fa', 'fa-chevron-down', 'fa-fw', 'pf-animate-rotate', config.overlayLocalTriggerClass].join(' ')
}),
$('<span>', {
class: ['badge', 'txt-color', 'txt-color-red', config.overlayLocalUsersClass].join(' '),
text: 0
}),
$('<div>', {
class: config.overlayLocalJumpsClass
}).append(
$('<span>', {
class: ['badge', 'txt-color', 'txt-color-grayLight'].join(' '),
text: MapUtil.config.defaultLocalJumpRadius
}).attr('title', 'jumps')
)
);
let headline = $('<div>', {
class: config.overlayLocalHeadlineClass
}).append(
$('<span>', {
html: 'Nearby&nbsp;&nbsp;&nbsp;',
class: 'pull-left'
}),
$('<span>'),
$('<span>'),
$('<span>', {
class: ['badge', ' txt-color', 'txt-color-red'].join(' '),
text: 0
})
);
content.append(headline);
content.append(table);
overlay.append(overlayMain);
overlay.append(content);
// set observer
setOverlayObserver(overlay, mapId);
parentElement.append(overlay);
// init local table ---------------------------------------------------------------------------------------
table.on('draw.dt', function(e, settings){
// init table tooltips
$(this).find('td').initTooltips({
container: 'body',
placement: 'left'
});
// hide pagination in case of only one page
let paginationElement = overlay.find('.dataTables_paginate');
let pageElements = paginationElement.find('span .paginate_button');
if(pageElements.length <= 1){
paginationElement.hide();
}else{
paginationElement.show();
}
});
// table init complete
table.on( 'init.dt', function (){
// init table head tooltips
$(this).initTooltips({
container: 'body',
placement: 'top'
});
});
let localTable = table.DataTable( {
pageLength: 13, // hint: if pagination visible => we need space to show it
paging: true,
lengthChange: false,
ordering: true,
order: [ 0, 'asc' ],
info: false,
searching: false,
hover: false,
autoWidth: false,
rowId: function(rowData) {
return 'pf-local-row_' + rowData.id; // characterId
},
language: {
emptyTable: '<span>You&nbsp;are&nbsp;alone</span>'
},
columnDefs: [
{
targets: 0,
orderable: true,
title: '<span title="jumps" data-toggle="tooltip">&nbsp;</span>',
width: '1px',
className: ['pf-help-default', 'text-center'].join(' '),
data: 'jumps',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let api = this.DataTable();
initCellTooltip(api, cell, 'log.system.name');
}
},{
targets: 1,
orderable: false,
title: '',
width: '26px',
className: ['pf-help-default', 'text-center', config.tableImageCellClass].join(' '),
data: 'log.ship',
render: {
_: function(data, type, row, meta){
let value = data.typeName;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + 'Render/' + data.typeId + '_32.png"/>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let api = this.DataTable();
initCellTooltip(api, cell, 'log.ship.typeName');
}
}, {
targets: 2,
orderable: true,
title: 'ship&nbsp;name',
width: '80px',
data: 'log.ship',
render: {
_: function(data, type, row, meta){
let value = data.name;
if(type === 'display'){
value = '<div class="' + config.tableCellEllipsisClass + ' ' + config.tableCellEllipsis80Class + '">' + data.name + '</div>';
}
return value;
},
sort: 'name'
}
},{
targets: 3,
orderable: true,
title: 'pilot',
data: 'name',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<div class="' + config.tableCellEllipsisClass + ' ' + config.tableCellEllipsis90Class + '">' + data + '</div>';
}
return value;
}
}
},{
targets: 4,
orderable: false,
title: '<i title="docked station" data-toggle="tooltip" class="fa fa-home text-right"></i>',
width: '10px',
className: ['pf-help-default'].join(' '),
data: 'log.station',
render: {
_: function(data, type, row, meta){
let value = '';
if(
type === 'display' &&
data.id
){
value = '<i class="fa fa-home"></i>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let api = this.DataTable();
initCellTooltip(api, cell, 'log.station.name');
}
},{
targets: 5,
orderable: false,
title: '<i title="open ingame" data-toggle="tooltip" class="fa fa-id-card text-right"></i>',
width: '10px',
className: [config.tableActionCellClass].join(' '),
data: 'id',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value = '<i class="fa fa-id-card ' + config.tableActionCellIconClass + '"></i>';
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable(), cellData: cellData }, function(e){
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(e.data.cellData);
});
}
}
]
});
});
};
/**
* Clear Overlay and "Reset"
* @param mapId
*/
$.fn.clearLocalTable = function(mapId){
return this.each(function(){
let overlay = $(this);
// update locale overlay headline -------------------------------------------------------------------------
updateLocaleHeadline(overlay, {
name: 'unknown',
security: ''
});
// clear all table rows -----------------------------------------------------------------------------------
let tableElement = overlay.find('.' + config.overlayLocalTableClass);
let localTable = tableElement.DataTable();
localTable.rows().remove().draw();
});
};
});

View File

@@ -15,7 +15,8 @@ define([
'dragToSelect',
'select2',
'app/map/contextmenu',
'app/map/overlay'
'app/map/overlay',
'app/map/local'
], function($, Init, Util, Render, bootbox, MapUtil, System, MagnetizerWrapper) {
'use strict';
@@ -59,13 +60,7 @@ define([
systemDialogSelectClass: 'pf-system-dialog-select', // class for system select Element
// system security classes
systemSec: 'pf-system-sec',
systemSecHigh: 'pf-system-sec-highSec',
systemSecLow: 'pf-system-sec-lowSec',
systemSecNull: 'pf-system-sec-nullSec',
systemSecWHHeigh: 'pf-system-sec-high',
systemSecWHMid: 'pf-system-sec-mid',
systemSecWHLow: 'pf-system-sec-low'
systemSec: 'pf-system-sec'
};
// active jsPlumb instances currently running
@@ -1639,8 +1634,10 @@ define([
id: id ? id : 0,
source: parseInt( source.data('id') ),
sourceName: source.data('name'),
sourceAlias: source.getSystemInfo(['alias']),
target: parseInt( target.data('id') ),
targetName: target.data('name'),
targetAlias: target.getSystemInfo(['alias']),
scope: connection.scope,
type: connectionTypes,
updated: updated ? updated : 0
@@ -2719,7 +2716,7 @@ define([
break;
case 'info':
// open map info dialog
$(document).triggerMenuEvent('ShowMapInfo');
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'information'});
break;
}
@@ -2873,6 +2870,44 @@ define([
mapUpdateQueue.splice(mapQueueIndex, 1);
}
});
// update "local" overlay for this map
mapContainer.on('pf:updateLocal', function(e, userData){
let mapElement = $(this);
let mapOverlay = mapElement.getMapOverlay('local');
let currentCharacterLog = Util.getCurrentCharacterLog();
let currentMapData = Util.getCurrentMapData(userData.config.id);
let clearLocal = true;
if(
currentMapData &&
currentCharacterLog &&
currentCharacterLog.system
){
let currentSystemData = currentMapData.data.systems.filter(function (system) {
return system.systemId === currentCharacterLog.system.id;
});
if(currentSystemData.length){
// current user system is on this map
currentSystemData = currentSystemData[0];
// check for active users "nearby" (x jumps radius)
let nearBySystemData = Util.getNearBySystemData(currentSystemData, currentMapData, MapUtil.config.defaultLocalJumpRadius);
let nearByCharacterData = Util.getNearByCharacterData(nearBySystemData, userData.data.systems);
// update "local" table in overlay
mapOverlay.updateLocalTable(currentSystemData, nearByCharacterData);
clearLocal = false;
}
}
if(clearLocal){
mapOverlay.clearLocalTable();
}
});
};
/**
@@ -3007,15 +3042,16 @@ define([
// the current user can only be in a single system ------------------------------------------
if(
characterLogExists &&
!currentUserOnMap &&
currentCharacterLog.system.id === systemId
){
currentUserIsHere = true;
currentUserOnMap = true;
if( !currentUserOnMap ){
currentUserIsHere = true;
currentUserOnMap = true;
// set current location data for header update
headerUpdateData.currentSystemId = $(system).data('id');
//headerUpdateData.currentSystemName = currentCharacterLog.system.name;
// set current location data for header update
headerUpdateData.currentSystemId = $(system).data('id');
headerUpdateData.currentSystemName = currentCharacterLog.system.name;
}
}
system.updateSystemUserData(map, tempUserData, currentUserIsHere);
@@ -3289,15 +3325,17 @@ define([
*/
$.fn.initMapScrollbar = function(){
// get Map Scrollbar
let scrollableElement = $(this).find('.' + config.mapWrapperClass);
let mapTabContentElement = $(this);
let scrollableElement = mapTabContentElement.find('.' + config.mapWrapperClass);
let mapElement = mapTabContentElement.find('.' + config.mapClass);
let mapId = mapElement.data('id');
scrollableElement.initCustomScrollbar({
callbacks: {
onScroll: function(){
// scroll complete
let mapElement = $(this).find('.' + config.mapClass);
// store new map scrollOffset -> localDB
MapUtil.storeLocalData('map', mapElement.data('id'), 'offsetX', Math.abs(this.mcs.left) );
MapUtil.storeLocalData('map', mapId, 'offsetX', Math.abs(this.mcs.left) );
},
onScrollStart: function(){
// hide all open xEditable fields
@@ -3308,9 +3346,8 @@ define([
},
whileScrolling:function(){
// update scroll position for drag-frame-selection
let mapElement = $(scrollableElement).find('.' + config.mapClass);
$(mapElement).data('scrollLeft', this.mcs.left);
$(mapElement).data('scrollTop', this.mcs.top);
mapElement.data('scrollLeft', this.mcs.left);
mapElement.data('scrollTop', this.mcs.top);
}
}
});
@@ -3319,6 +3356,8 @@ define([
// add map overlays after scrollbar is initialized
// because of its absolute position
scrollableElement.initMapOverlays();
scrollableElement.initLocalOverlay(mapId);
};
return {

View File

@@ -13,16 +13,13 @@ define([
logTimerCount: 3, // map log timer in seconds
// map
mapClass: 'pf-map', // class for all maps
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
// map overlay positions
mapOverlayClass: 'pf-map-overlay', // class for all map overlays
mapOverlayTimerClass: 'pf-map-overlay-timer', // class for map overlay timer e.g. map timer
mapOverlayInfoClass: 'pf-map-overlay-info', // class for map overlay info e.g. map info
// connection overlays
overlayLocalClass: 'pf-map-overlay-local', // class for map overlay "local" table
// system
systemHeadClass: 'pf-system-head', // class for system head
@@ -40,16 +37,6 @@ define([
connectionOverlaySmallClass: 'pf-map-connection-small-overlay' // class for "smaller" overlays
};
/**
* get mapElement from overlay or any child of that
* @param mapOverlay
* @returns {JQuery}
*/
let getMapElementFromOverlay = (mapOverlay) => {
return $(mapOverlay).parents('.' + config.mapWrapperClass).find('.' + config.mapClass);
};
/**
* get MapObject (jsPlumb) from mapElement
* @param mapElement
@@ -66,7 +53,7 @@ define([
* @returns {*}
*/
let getMapObjectFromOverlayIcon = (overlayIcon) => {
let mapElement = getMapElementFromOverlay(overlayIcon);
let mapElement = Util.getMapElementFromOverlay(overlayIcon);
return getMapObjectFromMapElement( mapElement );
};
@@ -381,7 +368,7 @@ define([
iconClass: ['fa', 'fa-fw', 'fa-tags'],
hoverIntent: {
over: function(e){
let mapElement = getMapElementFromOverlay(this);
let mapElement = Util.getMapElementFromOverlay(this);
mapElement.find('.' + config.systemHeadClass).each(function(){
let system = $(this);
// init tooltip if not already exists
@@ -399,7 +386,7 @@ define([
});
},
out: function(e){
let mapElement = getMapElementFromOverlay(this);
let mapElement = Util.getMapElementFromOverlay(this);
mapElement.find('.' + config.systemHeadClass).tooltip('hide');
}
}
@@ -411,7 +398,7 @@ define([
iconClass: ['fa', 'fa-fw', 'fa-link'],
hoverIntent: {
over: function(e){
let mapElement = getMapElementFromOverlay(this);
let mapElement = Util.getMapElementFromOverlay(this);
mapElement.showEndpointOverlays();
},
out: function(e){
@@ -518,7 +505,6 @@ define([
* @returns {*}
*/
$.fn.getMapOverlay = function(overlayType){
let mapWrapperElement = $(this).parents('.' + config.mapWrapperClass);
let mapOverlay = null;
@@ -529,6 +515,9 @@ define([
case 'info':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayInfoClass);
break;
case 'local':
mapOverlay = mapWrapperElement.find('.' + config.overlayLocalClass);
break;
}
return mapOverlay;
@@ -626,7 +615,7 @@ define([
duration: Init.animationSpeed.mapOverlay,
complete: function(){
counterChart.data('interval', false);
getMapElementFromOverlay(mapOverlayTimer).trigger('pf:unlocked');
Util.getMapElementFromOverlay(mapOverlayTimer).trigger('pf:unlocked');
}
});
}

View File

@@ -12,6 +12,7 @@ define([
let config = {
mapSnapToGridDimension: 20, // px for grid snapping (grid YxY)
defaultLocalJumpRadius: 3, // default search radius (in jumps) for "nearby" pilots
// local storage
characterLocalStoragePrefix: 'character_', // prefix for character data local storage key

View File

@@ -119,7 +119,7 @@ define([
firstCell.drawSystemGraphModule(currentSystemData.systemData);
// draw signature table module
firstCell.drawSignatureTableModule(currentSystemData.systemData);
firstCell.drawSignatureTableModule(currentSystemData.mapId, currentSystemData.systemData);
// draw system routes module
secondCell.drawSystemRouteModule(currentSystemData.mapId, currentSystemData.systemData);
@@ -201,8 +201,12 @@ define([
let currentMapUserData = Util.getCurrentMapUserData(mapId);
// update map with current user data
if(currentMapUserData){
// trigger "update local" for this map => async
mapElement.trigger('pf:updateLocal', currentMapUserData);
// update map with current user data
mapElement.updateUserData(currentMapUserData);
}
}

View File

@@ -8,6 +8,7 @@ define([
'app/util',
'app/logging',
'mustache',
'app/map/util',
'text!img/logo.svg!strip',
'text!templates/modules/header.html',
'text!templates/modules/footer.html',
@@ -24,7 +25,7 @@ define([
'dialog/credit',
'slidebars',
'app/module_map'
], function($, Init, Util, Logging, Mustache, TplLogo, TplHead, TplFooter) {
], function($, Init, Util, Logging, Mustache, MapUtil, TplLogo, TplHead, TplFooter) {
'use strict';
@@ -135,26 +136,27 @@ define([
});
body.watchKey('signaturePaste', (e) => {
let moduleElement = $('.' + config.systemSigModuleClass);
// check if there is a signature module active (system clicked)
if(moduleElement.length){
e = e.originalEvent;
let targetElement = $(e.target);
// do not read clipboard if pasting into form elements
if(
targetElement.prop('tagName').toLowerCase() !== 'input' &&
targetElement.prop('tagName').toLowerCase() !== 'textarea' || (
targetElement.is('input[type="search"]') // Datatables "search" field bubbles `paste.DT` event :(
)
){
let clipboard = (e.originalEvent || e).clipboardData.getData('text/plain');
moduleElement.trigger('pf:updateSystemSignatureModuleByClipboard', [clipboard]);
// just send event to the current active map
let activeMap = Util.getMapModule().getActiveMap();
if(activeMap){
// look for active signature module (active system)
let signatureModuleElement = MapUtil.getTabContentElementByMapElement(activeMap).find('.' + config.systemSigModuleClass);
if(signatureModuleElement.length){
e = e.originalEvent;
let targetElement = $(e.target);
// do not read clipboard if pasting into form elements
if(
targetElement.prop('tagName').toLowerCase() !== 'input' &&
targetElement.prop('tagName').toLowerCase() !== 'textarea' || (
targetElement.is('input[type="search"]') // Datatables "search" field bubbles `paste.DT` event :(
)
){
let clipboard = (e.originalEvent || e).clipboardData.getData('text/plain');
signatureModuleElement.trigger('pf:updateSystemSignatureModuleByClipboard', [clipboard]);
}
}
}
});
});
};
@@ -321,7 +323,7 @@ define([
class: 'fa fa-street-view fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'information'});
})
).append(
getMenuHeadline('Settings')
@@ -494,7 +496,7 @@ define([
// active pilots
$('.' + config.headActiveUserClass).find('a').on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
$(document).triggerMenuEvent('ShowMapInfo', {tab: 'activity'});
});
// current location
@@ -659,9 +661,9 @@ define([
return false;
});
$(document).on('pf:menuShowMapInfo', function(e){
$(document).on('pf:menuShowMapInfo', function(e, data){
// show map information dialog
$.fn.showMapInfoDialog();
$.fn.showMapInfoDialog(data);
return false;
});

View File

@@ -35,6 +35,7 @@ define([
tableImageSmallCellClass: 'pf-table-image-small-cell', // class for table "small image" cells
tableActionCellClass: 'pf-table-action-cell', // class for table "action" cells
tableCounterCellClass: 'pf-table-counter-cell', // class for table "counter" cells
tableActionCellIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
loadingOptions: { // config for loading overlay
icon: {
@@ -625,8 +626,16 @@ define([
// table init complete
userTable.on( 'init.dt', function () {
usersElement.hideLoadingAnimation();
// init table tooltips
let tooltipElements = usersElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip();
});
let getIconForInformationWindow = () => {
return '<i class="fa fa-fw fa-id-card ' + config.tableActionCellIconClass + '" title="open ingame" data-toggle="tooltip"></i>';
};
// users table ================================================================================================
// prepare users data for dataTables
let currentMapUserData = Util.getCurrentMapUserData( mapData.config.id );
@@ -681,7 +690,9 @@ define([
searchable: true,
data: 'log.ship',
render: {
_: 'typeName',
_: function(data, type, row){
return data.typeName + '&nbsp;<i class="fa fa-fw fa-question-circle pf-help" title="' + data.name + '" data-toggle="tooltip"></i>';
},
sort: 'typeName'
}
},{
@@ -702,7 +713,24 @@ define([
title: 'pilot',
orderable: true,
searchable: true,
data: 'name'
className: [config.tableActionCellClass].join(' '),
data: 'name',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display'){
value += '&nbsp;' + getIconForInformationWindow();
}
return value;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
// open character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable() }, function(e) {
let rowData = e.data.tableApi.row(this).data();
Util.openIngameWindow(rowData.id);
});
}
},{
targets: 4,
title: '',
@@ -721,9 +749,23 @@ define([
title: 'corporation',
orderable: true,
searchable: true,
className: [config.tableActionCellClass].join(' '),
data: 'corporation',
render: {
_: 'name'
_: 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 character information window (ingame)
$(cell).on('click', { tableApi: this.DataTable() }, function(e) {
let cellData = e.data.tableApi.cell(this).data();
Util.openIngameWindow(cellData.id);
});
}
},{
targets: 6,
@@ -753,9 +795,9 @@ define([
/**
* shows the map information modal dialog
* @param options
*/
$.fn.showMapInfoDialog = function(){
$.fn.showMapInfoDialog = function(options){
let activeMap = Util.getMapModule().getActiveMap();
let mapData = activeMap.getMapDataFromClient({forceData: true});
@@ -770,7 +812,11 @@ define([
mapInfoId: config.mapInfoId,
mapInfoSystemsId: config.mapInfoSystemsId,
mapInfoConnectionsId: config.mapInfoConnectionsId,
mapInfoUsersId: config.mapInfoUsersId
mapInfoUsersId: config.mapInfoUsersId,
// default open tab ----------
openTabInformation: options.tab === 'information',
openTabActivity: options.tab === 'activity'
};
let content = Mustache.render(template, data);

View File

@@ -9,7 +9,7 @@ define([
'app/util',
'app/render',
'bootbox'
], function($, Init, Util, Render, bootbox, MapUtil) {
], function($, Init, Util, Render, bootbox) {
'use strict';
let config = {

View File

@@ -145,10 +145,21 @@ define([
}
}).on('change', function(e){
// select changed
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" whe it could be cleared manually
if(
options.maxSelectionLength === 1 &&
$(this).val() !== null
){
$(this).val('').trigger('change');
}
})
).done(function(){
// open select
selectElement.select2('open');
).done(function(a,b){
// open select if not already pre-selected
if($(this).val() === null){
selectElement.select2('open');
}
});
};

View File

@@ -11,7 +11,7 @@ define([
], function($, Init, Util, Render, MapUtil) {
'use strict';
var config = {
let config = {
// module info
moduleClass: 'pf-module', // class for each module
@@ -35,41 +35,38 @@ define([
descriptionArea: 'pf-system-info-description-area', // class for "description" area
addDescriptionButtonClass: 'pf-system-info-description-button', // class for "add description" button
moduleElementToolbarClass: 'pf-table-tools', // class for "module toolbar" element
moduleToolbarActionId: 'pf-system-info-collapse-container', // id for "module toolbar action" element
tableToolsActionClass: 'pf-table-tools-action', // class for "edit" action
descriptionTextareaElementClass: 'pf-system-info-description', // class for "description" textarea element (xEditable)
descriptionTextareaCharCounter: 'pf-form-field-char-count' // class for "character counter" element for form field
};
// disable Module update temporary (until. some requests/animations) are finished
var disableModuleUpdate = true;
let disableModuleUpdate = true;
// animation speed values
var animationSpeedToolbarAction = 200;
let animationSpeedToolbarAction = 200;
// max character length for system description
var maxDescriptionLength = 512;
let maxDescriptionLength = 512;
/**
* set module observer and look for relevant system data to update
*/
var setModuleObserver = function(moduleElement){
let setModuleObserver = function(moduleElement){
$(document).off('pf:updateSystemInfoModule').on('pf:updateSystemInfoModule', function(e, data){
if(data){
moduleElement.updateSystemInfoModule(data);
}
});
};
/**
* shows the tool action element by animation
* @param toolsActionElement
*/
var showToolsActionElement = function(){
// "toolbar action" element
var toolsActionElement = $('#' + config.moduleToolbarActionId);
let showToolsActionElement = function(toolsActionElement){
toolsActionElement.velocity('stop').velocity({
opacity: 1,
height: '100%'
@@ -82,12 +79,9 @@ define([
/**
* hides the tool action element by animation
* @param toolsActionElement
*/
var hideToolsActionElement = function(){
// "toolbar action" element
var toolsActionElement = $('#' + config.moduleToolbarActionId);
let hideToolsActionElement = function(toolsActionElement){
toolsActionElement.velocity('stop').velocity('reverse', {
display: 'none',
visibility: 'hidden'
@@ -106,24 +100,23 @@ define([
return;
}
var moduleElement = $(this);
let moduleElement = $(this);
var systemId = moduleElement.data('id');
let systemId = moduleElement.data('id');
if(systemId === systemData.id){
// update module
// system status =====================================================================================
var systemStatusLabelElement = moduleElement.find('.' + config.systemInfoStatusLabelClass);
var systemStatusId = parseInt( systemStatusLabelElement.attr( config.systemInfoStatusAttributeName ) );
let systemStatusLabelElement = moduleElement.find('.' + config.systemInfoStatusLabelClass);
let systemStatusId = parseInt( systemStatusLabelElement.attr( config.systemInfoStatusAttributeName ) );
if(systemStatusId !== systemData.status.id){
// status changed
var currentStatusClass = Util.getStatusInfoForSystem(systemStatusId, 'class');
var newStatusClass = Util.getStatusInfoForSystem(systemData.status.id, 'class');
var newStatusLabel = Util.getStatusInfoForSystem(systemData.status.id, 'label');
let currentStatusClass = Util.getStatusInfoForSystem(systemStatusId, 'class');
let newStatusClass = Util.getStatusInfoForSystem(systemData.status.id, 'class');
let newStatusLabel = Util.getStatusInfoForSystem(systemData.status.id, 'label');
systemStatusLabelElement.removeClass(currentStatusClass).addClass(newStatusClass).text(newStatusLabel);
@@ -132,43 +125,40 @@ define([
}
// description textarea element ======================================================================
var descriptionTextareaElement = moduleElement.find('.' + config.descriptionTextareaElementClass);
var description = descriptionTextareaElement.editable('getValue', true);
let descriptionTextareaElement = moduleElement.find('.' + config.descriptionTextareaElementClass);
let description = descriptionTextareaElement.editable('getValue', true);
if(description !== systemData.description){
// description changed
// description button
var descriptionButton = moduleElement.find('.' + config.addDescriptionButtonClass);
let descriptionButton = moduleElement.find('.' + config.addDescriptionButtonClass);
// set new value
descriptionTextareaElement.editable('setValue', systemData.description);
let actionElement = descriptionButton.siblings('.' + config.tableToolsActionClass);
if(systemData.description.length === 0){
// show/activate description field
// show button if value is empty
descriptionButton.show();
hideToolsActionElement();
hideToolsActionElement(actionElement);
}else{
// hide/disable description field
// hide tool button
descriptionButton.hide();
showToolsActionElement();
showToolsActionElement(actionElement);
}
}
// created/updated tooltip ===========================================================================
var nameRowElement = $(moduleElement).find('.' + config.systemInfoNameInfoClass);
let nameRowElement = $(moduleElement).find('.' + config.systemInfoNameInfoClass);
var tooltipData = {
let tooltipData = {
created: systemData.created,
updated: systemData.updated
};
@@ -176,7 +166,7 @@ define([
nameRowElement.addCharacterInfoTooltip( tooltipData );
}
$('.' + config.descriptionArea).hideLoadingAnimation();
moduleElement.find('.' + config.descriptionArea).hideLoadingAnimation();
};
/**
@@ -185,13 +175,13 @@ define([
* @param charCounterElement
* @param maxCharLength
*/
var updateCounter = function(field, charCounterElement, maxCharLength){
var value = field.val();
var inputLength = value.length;
let updateCounter = function(field, charCounterElement, maxCharLength){
let value = field.val();
let inputLength = value.length;
// line breaks are 2 characters!
var newLines = value.match(/(\r\n|\n|\r)/g);
var addition = 0;
let newLines = value.match(/(\r\n|\n|\r)/g);
let addition = 0;
if (newLines != null) {
addition = newLines.length;
}
@@ -212,10 +202,10 @@ define([
* @param mapId
* @param systemData
*/
var drawModule = function(parentElement, mapId, systemData){
let drawModule = function(parentElement, mapId, systemData){
// create new module container
var moduleElement = $('<div>', {
let moduleElement = $('<div>', {
class: [config.moduleClass, config.systemInfoModuleClass].join(' '),
css: {opacity: 0}
});
@@ -226,14 +216,14 @@ define([
parentElement.prepend(moduleElement);
// shattered wormhole info data
var shatteredWormholeInfo = false;
let shatteredWormholeInfo = false;
// add security class for statics
if(
systemData.statics &&
systemData.statics.length > 0
){
for(var i = 0; i < systemData.statics.length; i++){
for(let i = 0; i < systemData.statics.length; i++){
systemData.statics[i].class = Util.getSecurityClassForSystem( systemData.statics[i].security );
}
}else if(systemData.type.id === 1){
@@ -241,27 +231,26 @@ define([
shatteredWormholeInfo = true;
}
var effectName = MapUtil.getEffectInfoForSystem(systemData.effect, 'name');
var effectClass = MapUtil.getEffectInfoForSystem(systemData.effect, 'class');
let effectName = MapUtil.getEffectInfoForSystem(systemData.effect, 'name');
let effectClass = MapUtil.getEffectInfoForSystem(systemData.effect, 'class');
// systemInfo template config
var moduleConfig = {
let moduleConfig = {
name: 'modules/system_info',
position: moduleElement,
link: 'append',
functions: {
after: function(){
let tempModuleElement = parentElement.find('.' + config.systemInfoModuleClass);
// lock "description" field until first update
$('.' + config.descriptionArea).showLoadingAnimation();
var tempModuleElement = $('.' + config.systemInfoModuleClass);
tempModuleElement.find('.' + config.descriptionArea).showLoadingAnimation();
// "add description" button
var descriptionButton = tempModuleElement.find('.' + config.addDescriptionButtonClass);
let descriptionButton = tempModuleElement.find('.' + config.addDescriptionButtonClass);
// description textarea element
var descriptionTextareaElement = tempModuleElement.find('.' + config.descriptionTextareaElementClass);
let descriptionTextareaElement = tempModuleElement.find('.' + config.descriptionTextareaElementClass);
// init description textarea
descriptionTextareaElement.editable({
@@ -304,9 +293,8 @@ define([
Util.showNotify({title: 'System updated', text: 'Name: ' + response.name, type: 'success'});
},
error: function(jqXHR, newValue){
var reason = '';
var status = '';
let reason = '';
let status = '';
if(jqXHR.name){
// save error new sig (mass save)
reason = jqXHR.name;
@@ -324,13 +312,13 @@ define([
// on xEditable open -------------------------------------------------------------------------
descriptionTextareaElement.on('shown', function(e, editable){
var textarea = editable.input.$input;
let textarea = editable.input.$input;
// disable module update until description field is open
disableModuleUpdate = true;
// create character counter
var charCounter = $('<kbd>', {
let charCounter = $('<kbd>', {
class: [config.descriptionTextareaCharCounter, 'txt-color', 'text-right'].join(' ')
});
textarea.parent().next().append(charCounter);
@@ -345,11 +333,10 @@ define([
// on xEditable close ------------------------------------------------------------------------
descriptionTextareaElement.on('hidden', function(e){
var value = $(this).editable('getValue', true);
let value = $(this).editable('getValue', true);
if(value.length === 0){
// show button if value is empty
hideToolsActionElement();
hideToolsActionElement(descriptionButton.siblings('.' + config.tableToolsActionClass));
descriptionButton.show();
}
@@ -360,6 +347,7 @@ define([
// enable xEditable field on Button click ----------------------------------------------------
descriptionButton.on('click', function(e){
e.stopPropagation();
let descriptionButton = $(this);
// hide tool buttons
descriptionButton.hide();
@@ -367,24 +355,23 @@ define([
// show field *before* showing the element
descriptionTextareaElement.editable('show');
showToolsActionElement();
showToolsActionElement(descriptionButton.siblings('.' + config.tableToolsActionClass));
});
// init tooltips -----------------------------------------------------------------------------
var tooltipElements = $('.' + config.systemInfoModuleClass + ' [data-toggle="tooltip"]');
let tooltipElements = tempModuleElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip();
// init system effect popover ----------------------------------------------------------------
var infoEffectElement = $(moduleElement).find('.' + config.systemInfoEffectInfoClass);
let infoEffectElement = $(moduleElement).find('.' + config.systemInfoEffectInfoClass);
if(infoEffectElement.length){
// effect row exists -> get effect data
var systemEffectData = Util.getSystemEffectData( systemData.security, systemData.effect);
let systemEffectData = Util.getSystemEffectData( systemData.security, systemData.effect);
if(systemEffectData !== false){
// transform data into table
var systemEffectTable = Util.getSystemEffectTable( systemEffectData );
let systemEffectTable = Util.getSystemEffectTable( systemEffectData );
infoEffectElement.popover({
html: true,
@@ -403,9 +390,9 @@ define([
// init static wormhole information ----------------------------------------------------------
if(systemData.statics){
for(var i = 0; i < systemData.statics.length; i++){
var staticData = systemData.statics[i];
var staticRowElement = tempModuleElement.find('.' + config.systemInfoWormholeClass + staticData.name);
for(let i = 0; i < systemData.statics.length; i++){
let staticData = systemData.statics[i];
let staticRowElement = tempModuleElement.find('.' + config.systemInfoWormholeClass + staticData.name);
staticRowElement.addWormholeInfoTooltip(staticData);
}
}
@@ -424,13 +411,13 @@ define([
function details_in_popup(popoverElement){
popoverElement = $(popoverElement);
var popover = popoverElement.data('bs.popover');
let popover = popoverElement.data('bs.popover');
$.ajax({
url: popoverElement.data('url'),
success: function(data){
var systemEffectTable = Util.getSystemsInfoTable( data.systemData );
let systemEffectTable = Util.getSystemsInfoTable( data.systemData );
popover.options.content = systemEffectTable;
// reopen popover (new content size)
popover.show();
@@ -444,7 +431,7 @@ define([
}
};
var moduleData = {
let moduleData = {
system: systemData,
tableClass: config.systemInfoTableClass,
nameInfoClass: config.systemInfoNameInfoClass,
@@ -463,7 +450,7 @@ define([
effectClass: effectClass,
moduleToolbarClass: config.moduleElementToolbarClass,
descriptionButtonClass: config.addDescriptionButtonClass,
moduleToolbarActionId: config.moduleToolbarActionId,
tableToolsActionClass: config.tableToolsActionClass,
descriptionTextareaClass: config.descriptionTextareaElementClass,
shatteredWormholeInfo: shatteredWormholeInfo,
@@ -483,7 +470,7 @@ define([
* show system info module with animation
* @param moduleElement
*/
var showModule = function(moduleElement){
let showModule = function(moduleElement){
moduleElement.velocity('transition.slideDownIn', {
duration: Init.animationSpeed.mapModule,
delay: Init.animationSpeed.mapModule,
@@ -504,10 +491,10 @@ define([
*/
$.fn.drawSystemInfoModule = function(mapId, systemData){
var parentElement = $(this);
let parentElement = $(this);
// check if module already exists
var moduleElement = parentElement.find('.' + config.systemInfoModuleClass);
let moduleElement = parentElement.find('.' + config.systemInfoModuleClass);
if(moduleElement.length > 0){
moduleElement.velocity('transition.slideDownOut', {

View File

@@ -183,6 +183,7 @@ define([
wormholes: (rowData.hasOwnProperty('wormholes')) ? rowData.wormholes | 0 : 1,
wormholesReduced: (rowData.hasOwnProperty('wormholesReduced')) ? rowData.wormholesReduced | 0 : 1,
wormholesCritical: (rowData.hasOwnProperty('wormholesCritical')) ? rowData.wormholesCritical | 0 : 1,
wormholesFrigate: (rowData.hasOwnProperty('wormholesFrigate')) ? rowData.wormholesFrigate | 0 : 1,
wormholesEOL: (rowData.hasOwnProperty('wormholesEOL')) ? rowData.wormholesEOL | 0 : 1,
safer: (rowData.hasOwnProperty('safer')) ? rowData.safer.value | 0 : 0
};
@@ -273,6 +274,7 @@ define([
wormholes: routeDialogData.hasOwnProperty('wormholes') ? parseInt( routeDialogData.wormholes ) : 0,
wormholesReduced: routeDialogData.hasOwnProperty('wormholesReduced') ? parseInt( routeDialogData.wormholesReduced ) : 0,
wormholesCritical: routeDialogData.hasOwnProperty('wormholesCritical') ? parseInt( routeDialogData.wormholesCritical ) : 0,
wormholesFrigate: routeDialogData.hasOwnProperty('wormholesFrigate') ? parseInt( routeDialogData.wormholesFrigate ) : 0,
wormholesEOL: routeDialogData.hasOwnProperty('wormholesEOL') ? parseInt( routeDialogData.wormholesEOL ) : 0
}]
};
@@ -480,12 +482,14 @@ define([
let wormholeCheckbox = routeDialog.find('input[type="checkbox"][name="wormholes"]');
let wormholeReducedCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesReduced"]');
let wormholeCriticalCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesCritical"]');
let wormholeFrigateCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesFrigate"]');
let wormholeEolCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesEOL"]');
// store current "checked" state for each box ---------------------------------------------
let storeCheckboxStatus = function(){
wormholeReducedCheckbox.data('selectState', wormholeReducedCheckbox.prop('checked'));
wormholeCriticalCheckbox.data('selectState', wormholeCriticalCheckbox.prop('checked'));
wormholeFrigateCheckbox.data('selectState', wormholeFrigateCheckbox.prop('checked'));
wormholeEolCheckbox.data('selectState', wormholeEolCheckbox.prop('checked'));
};
@@ -495,10 +499,12 @@ define([
if( $(this).is(':checked') ){
wormholeReducedCheckbox.prop('disabled', false);
wormholeCriticalCheckbox.prop('disabled', false);
wormholeFrigateCheckbox.prop('disabled', false);
wormholeEolCheckbox.prop('disabled', false);
wormholeReducedCheckbox.prop('checked', wormholeReducedCheckbox.data('selectState'));
wormholeCriticalCheckbox.prop('checked', wormholeCriticalCheckbox.data('selectState'));
wormholeFrigateCheckbox.prop('checked', wormholeFrigateCheckbox.data('selectState'));
wormholeEolCheckbox.prop('checked', wormholeEolCheckbox.data('selectState'));
}else{
storeCheckboxStatus();
@@ -507,6 +513,8 @@ define([
wormholeReducedCheckbox.prop('disabled', true);
wormholeCriticalCheckbox.prop('checked', false);
wormholeCriticalCheckbox.prop('disabled', true);
wormholeFrigateCheckbox.prop('checked', false);
wormholeFrigateCheckbox.prop('disabled', true);
wormholeEolCheckbox.prop('checked', false);
wormholeEolCheckbox.prop('disabled', true);
}
@@ -580,6 +588,7 @@ define([
wormholes: routeData.wormholes,
wormholesReduced: routeData.wormholesReduced,
wormholesCritical: routeData.wormholesCritical,
wormholesFrigate: routeData.wormholesFrigate,
wormholesEOL: routeData.wormholesEOL,
safer: {
value: routeData.safer,

View File

@@ -64,8 +64,8 @@ define([
// disable "copy&paste" from clipboard (until request finished)
let disableCopyFromClipboard = false;
// cache for dataTable object
let signatureTable = null;
// cache for dataTable object6
let dataTableInstances = {};
// empty signatureData object -> for "add row" table
let emptySignatureData = {
@@ -106,16 +106,117 @@ define([
let signatureGroupsLabels = Util.getSignatureGroupInfo('label');
let signatureGroupsNames = Util.getSignatureGroupInfo('name');
/**
* check whether a dataTable API instance exists in the global cache
* args: 1. mapId, 2. systemId, 3, tableType (primary/secondary) string
* @param args
* @returns {boolean}
*/
let checkDataTableInstance = (...args) => {
let obj = dataTableInstances;
for(let arg of args){
if ( !obj || !obj.hasOwnProperty(arg) ){
return false;
}
obj = obj[arg];
}
return true;
};
/**
* stores a dataTableApi instance to global cache ( overwrites existing)
* @param mapId
* @param systemId
* @param tableType
* @param instance
*/
let setDataTableInstance = (mapId, systemId, tableType, instance) => {
let tmpObj = {
[mapId]: {
[systemId]: {
[tableType]: instance
}
}
};
$.extend(true, dataTableInstances, tmpObj);
};
/**
* get a dataTableApi instance from global cache
* @param mapId
* @param systemId
* @param tableType
* @returns {*}
*/
let getDataTableInstance = (mapId, systemId, tableType) => {
let instance = null;
if( checkDataTableInstance(mapId, systemId, tableType) ){
instance = dataTableInstances[mapId][systemId][tableType];
}
return instance;
};
/**
* get dataTable instance from "moduleElement" (DOM node)
* @param moduleElement
* @param tableType
* @returns {*}
*/
let getDataTableInstanceByModuleElement = (moduleElement, tableType) => {
return getDataTableInstance(moduleElement.data('mapId'), moduleElement.data('systemId'), tableType);
};
/**
* delete a dataTableApi instance from global cache
* -> see checkDataTableInstance() for parameter order
* @param args
*/
let deleteDataTableInstance = (...args) => {
// check if instance exists
if( checkDataTableInstance.apply(null, args) ){
// callback for "leaf" delete callback
let deleteCallback = (dataTableApi) => {
dataTableApi.destroy();
};
// recursive delete from dataTableInstances Object cache
let deepDelete = (target, obj, callback) => {
if(target.length > 1){
// remove first target param for next recursive call
let currentTarget = target.shift();
deepDelete(target, obj[currentTarget], callback);
// delete "parent" key when current key became empty
if( !Object.keys( obj[currentTarget] ).length ){
delete obj[currentTarget];
}
}else{
// check for callback function
if( typeof callback === 'function' ){
callback(obj[target]);
}
delete obj[target];
}
};
deepDelete(args, dataTableInstances, deleteCallback);
}
};
/**
* collect all data of all editable fields in a signature table
* @param tableApi
* @returns {Array}
*/
let getSignatureTableData = function(){
let signatureTableApi = signatureTable.api();
let getTableData = function(tableApi){
let tableData = [];
signatureTableApi.rows().eq(0).each(function(idx){
let row = signatureTableApi.row(idx);
tableApi.rows().eq(0).each(function(idx){
let row = tableApi.row(idx);
// default row data
let defaultRowData = row.data();
let rowElement = row.nodes().to$();
@@ -151,16 +252,14 @@ define([
/**
* updates a single cell with new data (e.g. "updated" cell)
* @param tableApi
* @param rowElement
* @param cellIndex
* @param data
*/
let updateSignatureCell = function(rowElement, cellIndex, data){
let signatureTableApi = signatureTable.api();
let rowIndex = signatureTableApi.row( rowElement ).index();
let updateCell = signatureTableApi.cell( rowIndex, cellIndex );
let updateSignatureCell = function(tableApi, rowElement, cellIndex, data){
let rowIndex = tableApi.row( rowElement ).index();
let updateCell = tableApi.cell( rowIndex, cellIndex );
let updateCellElement = updateCell.nodes().to$();
if(cellIndex === 7){
@@ -184,7 +283,6 @@ define([
* @param deleteOutdatedSignatures -> set to "true" if signatures should be deleted that are not included in "signatureData"
*/
$.fn.updateSignatureTable = function(signatureDataOrig, deleteOutdatedSignatures){
// check if table update is allowed
if(disableTableUpdate === true){
return;
@@ -199,13 +297,12 @@ define([
let moduleElement = $(this);
// get signature table API
let signatureTableApi = signatureTable.api();
let signatureTableApi = getDataTableInstanceByModuleElement(moduleElement, 'primary');
// get current system data
let currentSystemData = Util.getCurrentSystemData();
let tableData = getSignatureTableData();
let tableData = getTableData(signatureTableApi);
let notificationCounter = {
added: 0,
@@ -229,7 +326,7 @@ define([
signatureTableApi.row(currentRowElement).remove().draw();
// and add "new" row
let changedRowElement = addSignatureRow(currentSystemData.systemData, signatureData[i], false);
let changedRowElement = addSignatureRow(signatureTableApi, currentSystemData.systemData, signatureData[i], false);
// highlight
changedRowElement.pulseTableRow('changed');
@@ -272,7 +369,7 @@ define([
// add new signatures -----------------------------------------------------------------------------------------
for(let k = 0; k < signatureData.length; k++){
// and add "new" row
let newRowElement = addSignatureRow(currentSystemData.systemData, signatureData[k], false);
let newRowElement = addSignatureRow(signatureTableApi, currentSystemData.systemData, signatureData[k], false);
// highlight
newRowElement.pulseTableRow('added');
@@ -333,15 +430,15 @@ define([
* @param options
*/
$.fn.updateScannedSignaturesBar = function(options){
let moduleElement = $(this);
let signatureTableApi = getDataTableInstanceByModuleElement(moduleElement, 'primary');
// get progress bar
let progressBarWrapper = moduleElement.find('.' + config.signatureScannedProgressBarClass);
let progressBar = $(progressBarWrapper).find('.progress-bar');
let progressBarLabel = $(progressBarWrapper).find('.progress-label-right');
let tableData = getSignatureTableData();
let tableData = getTableData(signatureTableApi);
let sigCount = 0;
let percent = 0;
@@ -392,7 +489,6 @@ define([
}
}, 100);
};
/**
@@ -478,12 +574,15 @@ define([
type: 'POST',
url: Init.path.saveSignatureData,
data: requestData,
dataType: 'json'
dataType: 'json',
context: {
moduleElement: moduleElement
}
}).done(function(responseData){
unlockSignatureTable(true);
// updates table with new/updated signature information
moduleElement.updateSignatureTable(responseData.signatures, false);
this.moduleElement.updateSignatureTable(responseData.signatures, false);
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': Update signatures', text: reason, type: 'warning'});
@@ -816,47 +915,45 @@ define([
/**
* get all rows of a table
* @param table
* @param tableApi
* @returns {*}
*/
let getRows = function(table){
let tableApi = table.api();
let getRows = function(tableApi){
let rows = tableApi.rows();
return rows;
};
/**
* get all selected rows of a table
* @param table
* @param tableApi
* @returns {*}
*/
let getSelectedRows = function(table){
let tableApi = table.api();
let getSelectedRows = function(tableApi){
let selectedRows = tableApi.rows('.selected');
return selectedRows;
};
/**
* check the "delete signature" button. show/hide the button if a signature is selected
* @param moduleElement
*/
let checkDeleteSignaturesButton = function(){
let checkDeleteSignaturesButton = function(moduleElement){
moduleElement = $(moduleElement);
let signatureTableApi = getDataTableInstanceByModuleElement(moduleElement, 'primary');
let selectedRows = getSelectedRows(signatureTable);
let selectedRows = getSelectedRows(signatureTableApi);
let selectedRowCount = selectedRows.data().length;
let clearButton = $('.' + config.sigTableClearButtonClass);
let clearButton = moduleElement.find('.' + config.sigTableClearButtonClass);
if(selectedRowCount > 0){
let allRows = getRows(signatureTable);
let allRows = getRows(signatureTableApi);
let rowCount = allRows.data().length;
let badgetText = selectedRowCount;
let badgeText = selectedRowCount;
if(selectedRowCount >= rowCount){
badgetText = 'all';
badgeText = 'all';
}
clearButton.find('.badge').text( badgetText );
clearButton.find('.badge').text( badgeText );
// update clear signatures button text
clearButton.velocity('stop');
@@ -882,10 +979,10 @@ define([
/**
* draw signature table toolbar (add signature button, scan progress bar
* @param mapId
* @param systemData
*/
$.fn.drawSignatureTableToolbar = function(systemData){
$.fn.drawSignatureTableToolbar = function(mapId, systemData){
let moduleElement = $(this);
// add toolbar buttons for table ------------------------------------------------------------------------------
@@ -930,8 +1027,10 @@ define([
label: 'select all',
icon: 'fa-check-square',
onClick: function(){
let allRows = getRows(signatureTable);
let selectedRows = getSelectedRows(signatureTable);
let signatureTableApi = getDataTableInstanceByModuleElement(moduleElement, 'primary');
let allRows = getRows(signatureTableApi);
let selectedRows = getSelectedRows(signatureTableApi);
let allRowElements = allRows.nodes().to$();
if(allRows.data().length === selectedRows.data().length){
@@ -941,7 +1040,7 @@ define([
}
// check delete button
checkDeleteSignaturesButton();
checkDeleteSignaturesButton(moduleElement);
}
})
).append(
@@ -955,12 +1054,12 @@ define([
},
onClick: function(){
// delete all rows
let selectedRows = getSelectedRows(signatureTable);
let signatureTableApi = getDataTableInstanceByModuleElement(moduleElement, 'primary');
let selectedRows = getSelectedRows(signatureTableApi);
bootbox.confirm('Delete ' + selectedRows.data().length + ' signature?', function(result) {
if(result){
deleteSignatures(selectedRows);
deleteSignatures(signatureTableApi, selectedRows);
}
});
}
@@ -984,15 +1083,18 @@ define([
tableToolbar.after(tableToolbarAction);
let signatureData = formatSignatureData(systemData, [emptySignatureData], emptySignatureOptions);
table.dataTable( {
let signatureTable = table.dataTable( {
data: signatureData,
paging: false,
ordering: false,
info: false,
searching: false
} );
let signatureTableApi = signatureTable.api();
table.makeEditable(systemData);
setDataTableInstance(mapId, systemData.id, 'secondary', signatureTableApi);
table.makeEditable(signatureTableApi, systemData);
// scanned signatures progress bar ----------------------------------------------------------------------------
let moduleConfig = {
@@ -1019,18 +1121,16 @@ define([
* @param title
*/
let updateTooltip = function(element, title){
element = $(element);
element.attr('data-container', 'body').attr('title', title.toUpperCase()).tooltip('fixTitle')
$(element).attr('data-container', 'body').attr('title', title.toUpperCase()).tooltip('fixTitle')
.tooltip('setContent');
};
/**
* make a table or row editable
* @param tableApi
* @param systemData
*/
$.fn.makeEditable = function(systemData){
$.fn.makeEditable = function(tableApi, systemData){
// table element OR row element
let tableElement = $(this);
@@ -1055,7 +1155,7 @@ define([
// the current field is in the "primary" table (not the "add" new sig row)
if(
$(e.target).hasClass(config.sigTableEditSigGroupSelect) &&
tableElement.hasClass(config.sigTablePrimaryClass)
$(e.target).parents('.' + config.sigTableClass).hasClass(config.sigTablePrimaryClass)
){
currentField.parents('.' + config.moduleClass).updateScannedSignaturesBar({showNotice: true});
}
@@ -1142,7 +1242,7 @@ define([
updateTooltip(columnElement, newValue);
// update "updated" cell
updateSignatureCell(rowElement, 7, newRowData.updated);
updateSignatureCell(tableApi, rowElement, 7, newRowData.updated);
}
}
});
@@ -1179,7 +1279,7 @@ define([
let newRowData = response.signatures[0];
// update "updated" cell
updateSignatureCell(rowElement, 7, newRowData.updated);
updateSignatureCell(tableApi, rowElement, 7, newRowData.updated);
}
// find related "type" select (same row) and change options
@@ -1250,7 +1350,7 @@ define([
let newRowData = response.signatures[0];
// update "updated" cell
updateSignatureCell(rowElement, 7, newRowData.updated);
updateSignatureCell(tableApi, rowElement, 7, newRowData.updated);
}
}
});
@@ -1273,7 +1373,7 @@ define([
let newRowData = response.signatures[0];
// update "updated" cell
updateSignatureCell(rowElement, 7, newRowData.updated);
updateSignatureCell(tableApi, rowElement, 7, newRowData.updated);
}
}
});
@@ -1328,7 +1428,7 @@ define([
let newRowData = response.signatures[0];
// update "updated" cell
updateSignatureCell(rowElement, 7, newRowData.updated);
updateSignatureCell(tableApi, rowElement, 7, newRowData.updated);
}
}
});
@@ -1378,13 +1478,13 @@ define([
// take target...
connectionOptions.push({
value: connectionData.id,
text: connectionData.targetName
text: connectionData.targetAlias
});
}else if(systemData.id !== connectionData.source){
// take source...
connectionOptions.push({
value: connectionData.id,
text: connectionData.sourceName
text: connectionData.sourceAlias
});
}
}
@@ -1628,15 +1728,14 @@ define([
/**
* deletes signature rows from signature table
* @param tableApi
* @param rows
*/
let deleteSignatures = function(rows){
let deleteSignatures = function(tableApi, rows){
let deletedSignatures = 0;
let moduleElement = $('.' + config.systemSigModuleClass);
let data = rows.data();
let signatureTableApi = signatureTable.api();
let rowElements = rows.nodes().to$();
let signatureCount = data.length;
@@ -1652,7 +1751,7 @@ define([
// animation callback function
let removeCallback = function(rowElement){
// delete signature row
signatureTableApi.row(rowElement).remove().draw();
tableApi.row(rowElement).remove().draw();
deletedSignatures++;
@@ -1686,25 +1785,20 @@ define([
Util.showNotify({title: jqXHR.status + ': Delete signature', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
};
/**
* adds a new row to signature Table
* @param signatureTableApi
* @param systemData
* @param signatureData
* @param animate
* @returns {*}
*/
let addSignatureRow = function(systemData, signatureData, animate){
let addSignatureRow = function(signatureTableApi, systemData, signatureData, animate){
let newSignatureData = formatSignatureData(systemData, [signatureData], fullSignatureOptions);
// insert new row in main signature table
let tablePrimaryElement = $('.' + config.sigTablePrimaryClass);
let dataTablePrimary = tablePrimaryElement.DataTable();
let newRowNode = dataTablePrimary.row.add(newSignatureData.shift()).draw().nodes();
let newRowNode = signatureTableApi.row.add(newSignatureData.shift()).draw().nodes();
let newRowElement = newRowNode.to$();
if(animate === true){
@@ -1713,13 +1807,13 @@ define([
newRowElement.toggleTableRow(function(newRowElement){
// make new row editable
newRowElement.makeEditable(systemData);
newRowElement.makeEditable(signatureTableApi, systemData);
// update scan progress bar
newRowElement.parents('.' + config.moduleClass).updateScannedSignaturesBar({showNotice: true});
});
}else{
newRowElement.makeEditable(systemData);
newRowElement.makeEditable(signatureTableApi, systemData);
}
return newRowElement;
@@ -1732,10 +1826,8 @@ define([
$.fn.toggleTableRow = function(callback){
let rowElement = $(this);
let cellElements = rowElement.children('td');
let duration = 100;
// wrap each <td> into a container (for better animation performance)
// slideUp new wrapper divs
if(rowElement.is(':visible')){
@@ -1815,11 +1907,11 @@ define([
/**
* draw a signature table with data
* @param mapId
* @param signatureData
* @param systemData
* @returns {*}
*/
$.fn.drawSignatureTable = function(signatureData, systemData){
$.fn.drawSignatureTable = function(mapId, signatureData, systemData){
let moduleElement = $(this);
// setup filter select in footer
@@ -1886,14 +1978,14 @@ define([
};
// create signature table and store the jquery object global for this module
signatureTable = table.dataTable(dataTableOptions);
let signatureTable = table.dataTable(dataTableOptions);
let signatureTableApi = signatureTable.api();
setDataTableInstance(mapId, systemData.id, 'primary', signatureTableApi);
// make Table editable
signatureTable.makeEditable(systemData);
signatureTable.makeEditable(signatureTableApi, systemData);
moduleElement.updateScannedSignaturesBar({showNotice: true});
return signatureTable;
};
/**
@@ -2093,6 +2185,12 @@ define([
// submit all fields within a table row
let formFields = rowElement.find('.editable');
// get the current "primary table" for insert row on ajax callback
// -> important: in case of long response, target table might have changed...
let moduleElement = $(e.target).parents('.' + config.moduleClass);
let primaryTable = moduleElement.find('.' + config.sigTablePrimaryClass);
let secondaryTable = moduleElement.find('.' + config.sigTableSecondaryClass);
// the "hide" makes sure to take care about open editable fields (e.g. description)
// otherwise, changes would not be submitted in this field (not necessary)
formFields.editable('hide');
@@ -2104,6 +2202,10 @@ define([
dataType: 'json', //assuming json response
beforeSend: function( xhr, settings ){
lockSignatureTable();
},
context: {
primaryTable: primaryTable,
secondaryTable: secondaryTable
}
},
data: {
@@ -2112,21 +2214,22 @@ define([
},
error: $.fn.editable.defaults.error, // user default xEditable error function
success: function (data, editableConfig) {
let context = editableConfig.ajaxOptions.context;
let primaryTableApi = context.primaryTable.DataTable();
let secondaryTableApi = context.secondaryTable.DataTable();
unlockSignatureTable(false);
let newRowElement = addSignatureRow(systemData, data.signatures[0], true);
let newRowElement = addSignatureRow(primaryTableApi, systemData, data.signatures[0], true);
// highlight
newRowElement.pulseTableRow('added');
// prepare "add signature" table for new entry -> reset -------------------
let signatureData = formatSignatureData(systemData, [emptySignatureData], emptySignatureOptions);
let newAddRowElement = secondaryTableApi.clear().row.add(signatureData.shift()).draw().nodes();
let dataSecondaryElement = $('.' + config.sigTableSecondaryClass);
let dataTableSecondary = dataSecondaryElement.DataTable();
let newAddRowElement = dataTableSecondary.clear().row.add(signatureData.shift()).draw().nodes();
newAddRowElement.to$().makeEditable(systemData);
newAddRowElement.to$().makeEditable(secondaryTableApi, systemData);
Util.showNotify({
title: 'Signature added',
@@ -2149,20 +2252,21 @@ define([
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fa fa-fw fa-close',
onConfirm : function(e, target){
onConfirm: function(e, target){
// top scroll to top
e.preventDefault();
let tableApi = tempTableElement.DataTable();
let deleteRowElement = $(target).parents('tr');
let row = tempTableElement.DataTable().rows(deleteRowElement);
deleteSignatures(row);
let row = tableApi.rows(deleteRowElement);
deleteSignatures(tableApi, row);
}
};
// init confirmation dialog
$(cell).confirmation(confirmationSettings);
break;
}
@@ -2178,31 +2282,29 @@ define([
* @param systemData
*/
let setModuleObserver = function(moduleElement, systemData){
let tablePrimaryElement = $('.' + config.sigTablePrimaryClass);
let dataTablePrimary = signatureTable.DataTable();
let signatureTableApi = signatureTable.api();
let tablePrimaryElement = moduleElement.find('.' + config.sigTablePrimaryClass);
let signatureTableApi = getDataTableInstanceByModuleElement(moduleElement, 'primary');
$(document).off('pf:updateSystemSignatureModule').on('pf:updateSystemSignatureModule', function(e, data){
if(data.signatures){
moduleElement.updateSignatureTable(data.signatures, true);
}
});
// set multi row select ---------------------------------------------------------------------------------------
tablePrimaryElement.on('click', 'tr', function(e){
tablePrimaryElement.on('click', 'tr', {moduleElement: moduleElement}, function(e){
if(e.ctrlKey) {
$(this).toggleClass('selected');
// check delete button
checkDeleteSignaturesButton();
checkDeleteSignaturesButton(e.data.moduleElement);
}
});
// draw event for signature table -----------------------------------------------------------------------------
signatureTableApi.on('draw.dt', function(){
signatureTableApi.on('draw.dt', {moduleElement: moduleElement}, function(e, settings){
// check delete button
checkDeleteSignaturesButton();
checkDeleteSignaturesButton(e.data.moduleElement);
});
// event listener for global "paste" signatures into the page -------------------------------------------------
@@ -2214,10 +2316,11 @@ define([
/**
* get module element
* @param parentElement
* @param mapId
* @param systemData
* @returns {*|HTMLElement}
* @returns {*|jQuery|HTMLElement}
*/
let getModule = function(parentElement, systemData){
let getModule = function(parentElement, mapId, systemData){
// create new module container
let moduleElement = $('<div>', {
@@ -2225,6 +2328,9 @@ define([
css: {opacity: 0}
});
moduleElement.data('mapId', mapId);
moduleElement.data('systemId', systemData.id);
// headline
let headline = $('<h5>', {
text: 'Signatures'
@@ -2239,7 +2345,7 @@ define([
// draw "new signature" add table -----------------------------------------------------------------------------
moduleElement.drawSignatureTableToolbar(systemData);
moduleElement.drawSignatureTableToolbar(mapId, systemData);
// request signature data for system --------------------------------------------------------------------------
@@ -2253,14 +2359,14 @@ define([
data: requestData,
dataType: 'json',
context: {
mapId: mapId,
systemData: systemData
}
}).done(function(signatureData){
let signatureTableData = formatSignatureData(this.systemData, signatureData, fullSignatureOptions);
// draw signature table
moduleElement.drawSignatureTable(signatureTableData, this.systemData);
moduleElement.drawSignatureTable(this.mapId, signatureTableData, this.systemData);
// set module observer
setModuleObserver(moduleElement, this.systemData);
@@ -2275,9 +2381,10 @@ define([
/**
* main module load function
* @param mapId
* @param systemData
*/
$.fn.drawSignatureTableModule = function(systemData){
$.fn.drawSignatureTableModule = function(mapId, systemData){
let parentElement = $(this);
// show module
@@ -2317,13 +2424,17 @@ define([
moduleElement.velocity('transition.slideDownOut', {
duration: Init.animationSpeed.mapModule,
complete: function(tempElement){
tempElement = $(tempElement);
// Destroying the data tables throws
// save remove of all dataTables
signatureTable.api().destroy();
let mapId = tempElement.data('mapId');
let systemId = tempElement.data('systemId');
deleteDataTableInstance(mapId, systemId, 'primary');
deleteDataTableInstance(mapId, systemId, 'secondary');
$(tempElement).remove();
tempElement.remove();
moduleElement = getModule(parentElement, systemData);
moduleElement = getModule(parentElement, mapId, systemData);
// make modules appear "nice"
moduleElement.delay(150);
showModule(moduleElement);
@@ -2333,7 +2444,7 @@ define([
// init array prototype functions
initArrayFunctions();
moduleElement = getModule(parentElement, systemData);
moduleElement = getModule(parentElement, mapId, systemData);
showModule(moduleElement);
}
};

View File

@@ -53,6 +53,9 @@ define([
// map module
mapModuleId: 'pf-map-module', // id for main map module
mapTabBarId: 'pf-map-tabs', // id for map tab bar
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapClass: 'pf-map' , // class for all maps
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
@@ -79,40 +82,41 @@ define([
* displays a loading indicator on an element
*/
$.fn.showLoadingAnimation = function(options){
let loadingElement = $(this);
return this.each(function(){
let loadingElement = $(this);
let iconSize = 'fa-lg';
let iconSize = 'fa-lg';
// disable all events
loadingElement.css('pointer-events', 'none');
// disable all events
loadingElement.css('pointer-events', 'none');
if(options){
if(options.icon){
if(options.icon.size){
iconSize = options.icon.size;
if(options){
if(options.icon){
if(options.icon.size){
iconSize = options.icon.size;
}
}
}
}
let overlay = $('<div>', {
class: config.ajaxOverlayClass
}).append(
$('<div>', {
class: [config.ajaxOverlayWrapperClass].join(' ')
let overlay = $('<div>', {
class: config.ajaxOverlayClass
}).append(
$('<i>', {
class: ['fa', 'fa-fw', iconSize, 'fa-refresh', 'fa-spin'].join(' ')
})
)
);
$('<div>', {
class: [config.ajaxOverlayWrapperClass].join(' ')
}).append(
$('<i>', {
class: ['fa', 'fa-fw', iconSize, 'fa-refresh', 'fa-spin'].join(' ')
})
)
);
loadingElement.append(overlay);
loadingElement.append(overlay);
// fade in
$(overlay).velocity({
opacity: 0.6
},{
duration: 120
// fade in
$(overlay).velocity({
opacity: 0.6
},{
duration: 120
});
});
};
@@ -120,16 +124,20 @@ define([
* removes a loading indicator
*/
$.fn.hideLoadingAnimation = function(){
let loadingElement = $(this);
let overlay = loadingElement.find('.' + config.ajaxOverlayClass );
return this.each(function(){
let loadingElement = $(this);
let overlay = loadingElement.find('.' + config.ajaxOverlayClass );
// important: "stop" is required to stop "show" animation
// -> otherwise "complete" callback is not fired!
$(overlay).velocity('stop').velocity('reverse', {
complete: function(){
$(this).remove();
// enable all events
loadingElement.css('pointer-events', 'auto');
if(overlay.length){
// important: "stop" is required to stop "show" animation
// -> otherwise "complete" callback is not fired!
overlay.velocity('stop').velocity('reverse', {
complete: function(){
$(this).remove();
// enable all events
loadingElement.css('pointer-events', 'auto');
}
});
}
});
};
@@ -1265,6 +1273,15 @@ define([
return mapTabElements;
};
/**
* get mapElement from overlay or any child of that
* @param mapOverlay
* @returns {jQuery}
*/
let getMapElementFromOverlay = (mapOverlay) => {
return $(mapOverlay).parents('.' + config.mapWrapperClass).find('.' + config.mapClass);
};
/**
* get the map module object or create a new module
* @returns {*|HTMLElement}
@@ -1797,6 +1814,132 @@ define([
return userInfo;
};
/**
* get "nearBy" systemData based on a jump radius around a currentSystem
* @param currentSystemData
* @param currentMapData
* @param jumps
* @param foundSystemIds
* @returns {{systemData: *, tree: {}}}
*/
let getNearBySystemData = (currentSystemData, currentMapData, jumps, foundSystemIds = {}) => {
// look for systemData by ID
let getSystemData = (systemId) => {
for(let j = 0; j < currentMapData.data.systems.length; j++){
let systemData = currentMapData.data.systems[j];
if(systemData.id === systemId){
return systemData;
}
}
return false;
};
// skip systems that are already found in recursive calls
foundSystemIds[currentSystemData.id] = {distance: jumps};
let nearBySystems = {
systemData: currentSystemData,
tree: {}
};
jumps--;
if(jumps >= 0){
for(let i = 0; i < currentMapData.data.connections.length; i++){
let connectionData = currentMapData.data.connections[i];
let type = ''; // "source" OR "target"
if(connectionData.source === currentSystemData.id){
type = 'target';
}else if(connectionData.target === currentSystemData.id){
type = 'source';
}
if(
type &&
(
foundSystemIds[connectionData[type]] === undefined ||
foundSystemIds[connectionData[type]].distance < jumps
)
){
let newSystemData = getSystemData(connectionData[type]);
if(newSystemData){
nearBySystems.tree[connectionData[type]] = getNearBySystemData(newSystemData, currentMapData, jumps, foundSystemIds);
}
}
}
}
return nearBySystems;
};
/**
* get current character data from all characters who are "nearby" the current user
* -> see getNearBySystemData()
* @param nearBySystems
* @param userData
* @param jumps
* @param data
* @returns {{}}
*/
let getNearByCharacterData = (nearBySystems, userData, jumps = 0, data = {}) => {
let getCharacterDataBySystemId = (systemId) => {
for(let i = 0; i < userData.length; i++){
if(userData[i].id === systemId){
return userData[i].user;
}
}
return [];
};
let filterFinalCharData = function(tmpFinalCharData){
return this.id !== tmpFinalCharData.id;
};
let characterData = getCharacterDataBySystemId(nearBySystems.systemData.systemId);
if(characterData.length){
// filter (remove) characterData for "already" added chars
characterData = characterData.filter(function(tmpCharacterData, index, allData){
let keepData = true;
for(let tmpJump in data) {
// just scan systems with > jumps than current system
if(tmpJump > jumps){
let filteredFinalData = data[tmpJump].filter(filterFinalCharData, tmpCharacterData);
if(filteredFinalData.length > 0){
data[tmpJump] = filteredFinalData;
}else{
delete data[tmpJump];
}
}else{
for(let k = 0; k < data[tmpJump].length; k++){
if(data[tmpJump][k].id === tmpCharacterData.id){
keepData = false;
break;
}
}
}
}
return keepData;
});
data[jumps] = data[jumps] ? data[jumps] : [];
data[jumps] = [...data[jumps], ...characterData];
}
jumps++;
for(let prop in nearBySystems.tree) {
if( nearBySystems.tree.hasOwnProperty(prop) ){
let tmpSystemData = nearBySystems.tree[prop];
data = getNearByCharacterData(tmpSystemData, userData, jumps, data);
}
}
return data;
};
/**
* set new destination for a system
* -> CREST request
@@ -1894,6 +2037,34 @@ define([
return $('.' + config.dialogClass).filter(':visible');
};
/**
* send Ajax request that remote opens an ingame Window
* @param targetId
*/
let openIngameWindow = (targetId) => {
targetId = parseInt(targetId);
if(targetId > 0){
$.ajax({
type: 'POST',
url: Init.path.openIngameWindow,
data: {
targetId: targetId
},
dataType: 'json'
}).done(function(data){
if(data.error.length > 0){
showNotify({title: 'Open window in client', text: 'Remote window open failed', type: 'error'});
}else{
showNotify({title: 'Open window in client', text: 'Check your EVE client', type: 'success'});
}
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
showNotify({title: jqXHR.status + ': openWindow', text: reason, type: 'error'});
});
}
};
/**
* formats a price string into an ISK Price
* @param price
@@ -2031,6 +2202,7 @@ define([
setSyncStatus: setSyncStatus,
getSyncType: getSyncType,
isXHRAborted: isXHRAborted,
getMapElementFromOverlay: getMapElementFromOverlay,
getMapModule: getMapModule,
getSystemEffectData: getSystemEffectData,
getSystemEffectTable: getSystemEffectTable,
@@ -2058,9 +2230,12 @@ define([
getCurrentUserInfo: getCurrentUserInfo,
getCurrentCharacterLog: getCurrentCharacterLog,
flattenXEditableSelectArray: flattenXEditableSelectArray,
getNearBySystemData: getNearBySystemData,
getNearByCharacterData: getNearByCharacterData,
setDestination: setDestination,
convertDateToString: convertDateToString,
getOpenDialogs: getOpenDialogs,
openIngameWindow: openIngameWindow,
formatPrice: formatPrice,
getLocalStorage: getLocalStorage,
getDocumentPath: getDocumentPath,

View File

@@ -1,249 +1,263 @@
/*!
* Bootstrap Confirmation v1.0.5
* https://github.com/tavicu/bs-confirmation
*/
+function ($) {
'use strict';
'use strict';
//var for check event at body can have only one.
var event_body = false;
//var for check event at body can have only one.
var event_body = false;
// CONFIRMATION PUBLIC CLASS DEFINITION
// ===============================
var Confirmation = function (element, options) {
var that = this;
// CONFIRMATION PUBLIC CLASS DEFINITION
// ===============================
var Confirmation = function (element, options) {
var that = this;
this.init('confirmation', element, options);
this.init('confirmation', element, options);
$(element).on('show.bs.confirmation', function(e) {
that.options.onShow(e, this);
$(this).addClass('open');
var options = that.options;
var all = options.all_selector;
if(options.singleton)
{
$(all).not(that.$element).each(function()
{
if( $(this).hasClass('open') )
{
$(this).confirmation('hide');
}
});
}
});
$(element).on('hide.bs.confirmation', function(e) {
that.options.onHide(e, this);
$(this).removeClass('open');
});
$(element).on('shown.bs.confirmation', function(e) {
var options = that.options;
var all = options.all_selector;
if(that.isPopout()) {
if(!event_body) {
event_body = $('body').on('click', function (e) {
if(that.$element.is(e.target)) return;
if(that.$element.has(e.target).length) return;
if($('.popover').has(e.target).length) return;
that.hide();
that.inState.click = false;
$('body').unbind(e);
event_body = false;
return;
});
}
}
});
if(options.selector) {
$(element).on('click.bs.confirmation', options.selector, function(e) {
e.preventDefault();
});
} else {
$(element).on('click.bs.confirmation', function(e) {
e.preventDefault();
});
}
}
if (!$.fn.popover || !$.fn.tooltip) throw new Error('Confirmation requires popover.js and tooltip.js');
Confirmation.VERSION = '1.0.5'
Confirmation.DEFAULTS = $.extend({}, $.fn.popover.Constructor.DEFAULTS, {
placement : 'right',
title : 'Are you sure?',
btnOkClass : 'btn btn-sm btn-danger',
btnOkLabel : 'Delete',
btnOkIcon : 'glyphicon glyphicon-ok',
btnCancelClass : 'btn btn-sm btn-default',
btnCancelLabel : 'Cancel',
btnCancelIcon : 'glyphicon glyphicon-remove',
href : '#',
target : '_self',
singleton : true,
popout : true,
onShow : function(event, element){},
onHide : function(event, element){},
onConfirm : function(event, element){},
onCancel : function(event, element){},
template : '<div class="popover"><div class="arrow"></div>'
+ '<h3 class="popover-title"></h3>'
+ '<div class="popover-content">'
+ '<a data-apply="confirmation">Yes</a>'
+ '<a data-dismiss="confirmation">No</a>'
+ '</div>'
+ '</div>'
});
$(element).on('show.bs.confirmation', function(e) {
that.options.onShow(e, this);
// NOTE: CONFIRMATION EXTENDS popover.js
// ================================
Confirmation.prototype = $.extend({}, $.fn.popover.Constructor.prototype);
$(this).addClass('open');
Confirmation.prototype.constructor = Confirmation;
var options = that.options;
var all = options.all_selector;
Confirmation.prototype.getDefaults = function () {
return Confirmation.DEFAULTS;
}
if(options.singleton)
{
$(all).not(that.$element).each(function()
{
if( $(this).hasClass('open') )
{
$(this).confirmation('hide');
}
});
}
});
Confirmation.prototype.setContent = function () {
var that = this;
var $tip = this.tip();
var title = this.getTitle();
var $btnOk = $tip.find('[data-apply="confirmation"]');
var $btnCancel = $tip.find('[data-dismiss="confirmation"]');
var options = this.options
$(element).on('hide.bs.confirmation', function(e) {
that.options.onHide(e, this);
$btnOk.addClass(this.getBtnOkClass())
.html(this.getBtnOkLabel())
.prepend($('<i></i>').addClass(this.getBtnOkIcon()), " ")
.attr('href', this.getHref())
.attr('target', this.getTarget())
.off('click').on('click', function(event) {
options.onConfirm(event, that.$element);
$(this).removeClass('open');
});
// If the button is a submit one
if (that.$element.attr('type') == 'submit')
that.$element.closest('form:first').submit();
$(element).on('shown.bs.confirmation', function(e) {
var options = that.options;
var all = options.all_selector;
that.hide();
that.inState.click = false;
});
that.$element.on('click.dismiss.bs.confirmation', '[data-dismiss="confirmation"]', $.proxy(that.hide, that));
$btnCancel.addClass(this.getBtnCancelClass())
.html(this.getBtnCancelLabel())
.prepend($('<i></i>').addClass(this.getBtnCancelIcon()), " ")
.off('click').on('click', function(event){
options.onCancel(event, that.$element);
if(that.isPopout()) {
if(!event_body) {
event_body = $('body').on('click', function (e) {
if(that.$element.is(e.target)) return;
if(that.$element.has(e.target).length) return;
if($('.popover').has(e.target).length) return;
that.hide();
that.inState.click = false;
});
that.$element.confirmation('hide');
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title);
$('body').unbind(e);
$tip.removeClass('fade top bottom left right in');
event_body = false;
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide();
}
return;
});
}
}
});
Confirmation.prototype.getBtnOkClass = function () {
var $e = this.$element;
var o = this.options;
$(element).on('click', function(e) {
e.preventDefault();
});
}
return $e.attr('data-btnOkClass') || (typeof o.btnOkClass == 'function' ? o.btnOkClass.call(this, $e[0]) : o.btnOkClass);
}
if (!$.fn.popover || !$.fn.tooltip) throw new Error('Confirmation requires popover.js and tooltip.js');
Confirmation.prototype.getBtnOkLabel = function () {
var $e = this.$element;
var o = this.options;
Confirmation.DEFAULTS = $.extend({}, $.fn.popover.Constructor.DEFAULTS, {
placement : 'right',
title : 'Are you sure?',
btnOkClass : 'btn btn-sm btn-danger',
btnOkLabel : 'Delete',
btnOkIcon : 'glyphicon glyphicon-ok',
btnCancelClass : 'btn btn-sm btn-default',
btnCancelLabel : 'Cancel',
btnCancelIcon : 'glyphicon glyphicon-remove',
href : '#',
target : '_self',
singleton : true,
popout : true,
onShow : function(event, element){},
onHide : function(event, element){},
onConfirm : function(event, element){},
onCancel : function(event, element){},
template : '<div class="popover"><div class="arrow"></div>'
+ '<h3 class="popover-title"></h3>'
+ '<div class="popover-content">'
+ '<div class="btn-group">'
+ '<a data-dismiss="confirmation">No</a>'
+ '<a data-apply="confirmation">Yes</a>'
+ '</div>'
+ '</div>'
+ '</div>'
});
return $e.attr('data-btnOkLabel') || (typeof o.btnOkLabel == 'function' ? o.btnOkLabel.call(this, $e[0]) : o.btnOkLabel);
}
Confirmation.prototype.getBtnOkIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkIcon') || (typeof o.btnOkIcon == 'function' ? o.btnOkIcon.call(this, $e[0]) : o.btnOkIcon);
}
Confirmation.prototype.getBtnCancelClass = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelClass') || (typeof o.btnCancelClass == 'function' ? o.btnCancelClass.call(this, $e[0]) : o.btnCancelClass);
}
Confirmation.prototype.getBtnCancelLabel = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelLabel') || (typeof o.btnCancelLabel == 'function' ? o.btnCancelLabel.call(this, $e[0]) : o.btnCancelLabel);
}
Confirmation.prototype.getBtnCancelIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelIcon') || (typeof o.btnCancelIcon == 'function' ? o.btnCancelIcon.call(this, $e[0]) : o.btnCancelIcon);
}
Confirmation.prototype.getHref = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-href') || (typeof o.href == 'function' ? o.href.call(this, $e[0]) : o.href);
}
Confirmation.prototype.getTarget = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-target') || (typeof o.target == 'function' ? o.target.call(this, $e[0]) : o.target);
}
Confirmation.prototype.isPopout = function () {
var popout;
var $e = this.$element;
var o = this.options;
popout = $e.attr('data-popout') || (typeof o.popout == 'function' ? o.popout.call(this, $e[0]) : o.popout);
if(popout == 'false') popout = false;
return popout
}
// NOTE: CONFIRMATION EXTENDS popover.js
// ================================
Confirmation.prototype = $.extend({}, $.fn.popover.Constructor.prototype);
// CONFIRMATION PLUGIN DEFINITION
// =========================
var old = $.fn.confirmation;
Confirmation.prototype.constructor = Confirmation;
$.fn.confirmation = function (option) {
var that = this;
Confirmation.prototype.getDefaults = function () {
return Confirmation.DEFAULTS;
}
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.confirmation');
var options = typeof option == 'object' && option;
Confirmation.prototype.setContent = function () {
var that = this;
var $tip = this.tip();
var title = this.getTitle();
var $btnOk = $tip.find('[data-apply="confirmation"]');
var $btnCancel = $tip.find('[data-dismiss="confirmation"]');
var options = this.options
options = options || {};
options.all_selector = that.selector;
$btnOk.addClass(this.getBtnOkClass())
.html(this.getBtnOkLabel())
.prepend($('<i></i>').addClass(this.getBtnOkIcon()), " ")
.attr('href', this.getHref())
.attr('target', this.getTarget())
.off('click').on('click', function(event) {
that.$element.confirmation('hide');
if (!data && option == 'destroy') return;
if (!data) $this.data('bs.confirmation', (data = new Confirmation(this, options)));
if (typeof option == 'string') data[option]();
});
}
options.onConfirm(event, that.$element);
});
$btnCancel.addClass(this.getBtnCancelClass())
.html(this.getBtnCancelLabel())
.prepend($('<i></i>').addClass(this.getBtnCancelIcon()), " ")
.off('click').on('click', function(event){
options.onCancel(event, that.$element);
that.$element.confirmation('hide');
});
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title);
$tip.removeClass('fade top bottom left right in');
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide();
}
Confirmation.prototype.getBtnOkClass = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkClass') || (typeof o.btnOkClass == 'function' ? o.btnOkClass.call($e[0]) : o.btnOkClass);
}
Confirmation.prototype.getBtnOkLabel = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkLabel') || (typeof o.btnOkLabel == 'function' ? o.btnOkLabel.call($e[0]) : o.btnOkLabel);
}
Confirmation.prototype.getBtnOkIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkIcon') || (typeof o.btnOkIcon == 'function' ? o.btnOkIcon.call($e[0]) : o.btnOkIcon);
}
Confirmation.prototype.getBtnCancelClass = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelClass') || (typeof o.btnCancelClass == 'function' ? o.btnCancelClass.call($e[0]) : o.btnCancelClass);
}
Confirmation.prototype.getBtnCancelLabel = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelLabel') || (typeof o.btnCancelLabel == 'function' ? o.btnCancelLabel.call($e[0]) : o.btnCancelLabel);
}
Confirmation.prototype.getBtnCancelIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelIcon') || (typeof o.btnCancelIcon == 'function' ? o.btnCancelIcon.call($e[0]) : o.btnCancelIcon);
}
Confirmation.prototype.getHref = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-href') || (typeof o.href == 'function' ? o.href.call($e[0]) : o.href);
}
Confirmation.prototype.getTarget = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-target') || (typeof o.target == 'function' ? o.target.call($e[0]) : o.target);
}
Confirmation.prototype.isPopout = function () {
var popout;
var $e = this.$element;
var o = this.options;
popout = $e.attr('data-popout') || (typeof o.popout == 'function' ? o.popout.call($e[0]) : o.popout);
if(popout == 'false') popout = false;
return popout
}
$.fn.confirmation.Constructor = Confirmation
// CONFIRMATION PLUGIN DEFINITION
// =========================
var old = $.fn.confirmation;
// CONFIRMATION NO CONFLICT
// ===================
$.fn.confirmation.noConflict = function () {
$.fn.confirmation = old;
$.fn.confirmation = function (option) {
var that = this;
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.confirmation');
var options = typeof option == 'object' && option;
options = options || {};
options.all_selector = that.selector;
if (!data && option == 'destroy') return;
if (!data) $this.data('bs.confirmation', (data = new Confirmation(this, options)));
if (typeof option == 'string') data[option]();
});
}
$.fn.confirmation.Constructor = Confirmation
// CONFIRMATION NO CONFLICT
// ===================
$.fn.confirmation.noConflict = function () {
$.fn.confirmation = old;
return this;
}
}(jQuery);
return this;
}
}(jQuery);

File diff suppressed because one or more lines are too long

View File

@@ -48,7 +48,7 @@ requirejs.config({
blueImpGallery: 'lib/blueimp-gallery', // v2.21.3 Image Gallery - https://github.com/blueimp/Gallery
blueImpGalleryHelper: 'lib/blueimp-helper', // helper function for Blue Imp Gallery
blueImpGalleryBootstrap: 'lib/bootstrap-image-gallery', // v3.4.2 Bootstrap extension for Blue Imp Gallery - https://blueimp.github.io/Bootstrap-Image-Gallery
bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.1 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.5 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
bootstrapToggle: 'lib/bootstrap2-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
lazyload: 'lib/jquery.lazyload.min', // v1.9.5 LazyLoader images - http://www.appelsiini.net/projects/lazyload

View File

@@ -21,6 +21,7 @@ define('app/init',['jquery'], function($) {
logIn: 'api/user/logIn', // ajax URL - login
logout: 'api/user/logout', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
openIngameWindow: 'api/user/openIngameWindow', // ajax URL - open inGame Window
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
// access API
@@ -70,6 +71,7 @@ define('app/init',['jquery'], function($) {
splashOverlay: 300, // "splash" loading overlay
headerLink: 100, // links in head bar
mapOverlay: 200, // show/hide duration for map overlays
mapOverlayLocal: 180, // show/hide duration for map "local" overlay
mapMoveSystem: 180, // system position has changed animation
mapDeleteSystem: 200, // remove system from map
mapModule: 200, // show/hide of an map module
@@ -1688,255 +1690,270 @@ define("xEditable", ["bootstrap"], function(){});
(function($){$.fn.hoverIntent=function(handlerIn,handlerOut,selector){var cfg={interval:100,sensitivity:6,timeout:0};if(typeof handlerIn==="object"){cfg=$.extend(cfg,handlerIn)}else{if($.isFunction(handlerOut)){cfg=$.extend(cfg,{over:handlerIn,out:handlerOut,selector:selector})}else{cfg=$.extend(cfg,{over:handlerIn,out:handlerIn,selector:handlerOut})}}var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if(Math.sqrt((pX-cX)*(pX-cX)+(pY-cY)*(pY-cY))<cfg.sensitivity){$(ob).off("mousemove.hoverIntent",track);ob.hoverIntent_s=true;return cfg.over.apply(ob,[ev])}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=false;return cfg.out.apply(ob,[ev])};var handleHover=function(e){var ev=$.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t)}if(e.type==="mouseenter"){pX=ev.pageX;pY=ev.pageY;$(ob).on("mousemove.hoverIntent",track);if(!ob.hoverIntent_s){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}}else{$(ob).off("mousemove.hoverIntent",track);if(ob.hoverIntent_s){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob)},cfg.timeout)}}};return this.on({"mouseenter.hoverIntent":handleHover,"mouseleave.hoverIntent":handleHover},cfg.selector)}})(jQuery);
define("hoverIntent", ["jquery"], function(){});
/*!
* Bootstrap Confirmation v1.0.5
* https://github.com/tavicu/bs-confirmation
*/
+function ($) {
'use strict';
'use strict';
//var for check event at body can have only one.
var event_body = false;
//var for check event at body can have only one.
var event_body = false;
// CONFIRMATION PUBLIC CLASS DEFINITION
// ===============================
var Confirmation = function (element, options) {
var that = this;
// CONFIRMATION PUBLIC CLASS DEFINITION
// ===============================
var Confirmation = function (element, options) {
var that = this;
this.init('confirmation', element, options);
this.init('confirmation', element, options);
$(element).on('show.bs.confirmation', function(e) {
that.options.onShow(e, this);
$(this).addClass('open');
var options = that.options;
var all = options.all_selector;
if(options.singleton)
{
$(all).not(that.$element).each(function()
{
if( $(this).hasClass('open') )
{
$(this).confirmation('hide');
}
});
}
});
$(element).on('hide.bs.confirmation', function(e) {
that.options.onHide(e, this);
$(this).removeClass('open');
});
$(element).on('shown.bs.confirmation', function(e) {
var options = that.options;
var all = options.all_selector;
if(that.isPopout()) {
if(!event_body) {
event_body = $('body').on('click', function (e) {
if(that.$element.is(e.target)) return;
if(that.$element.has(e.target).length) return;
if($('.popover').has(e.target).length) return;
that.hide();
that.inState.click = false;
$('body').unbind(e);
event_body = false;
return;
});
}
}
});
if(options.selector) {
$(element).on('click.bs.confirmation', options.selector, function(e) {
e.preventDefault();
});
} else {
$(element).on('click.bs.confirmation', function(e) {
e.preventDefault();
});
}
}
if (!$.fn.popover || !$.fn.tooltip) throw new Error('Confirmation requires popover.js and tooltip.js');
Confirmation.VERSION = '1.0.5'
Confirmation.DEFAULTS = $.extend({}, $.fn.popover.Constructor.DEFAULTS, {
placement : 'right',
title : 'Are you sure?',
btnOkClass : 'btn btn-sm btn-danger',
btnOkLabel : 'Delete',
btnOkIcon : 'glyphicon glyphicon-ok',
btnCancelClass : 'btn btn-sm btn-default',
btnCancelLabel : 'Cancel',
btnCancelIcon : 'glyphicon glyphicon-remove',
href : '#',
target : '_self',
singleton : true,
popout : true,
onShow : function(event, element){},
onHide : function(event, element){},
onConfirm : function(event, element){},
onCancel : function(event, element){},
template : '<div class="popover"><div class="arrow"></div>'
+ '<h3 class="popover-title"></h3>'
+ '<div class="popover-content">'
+ '<a data-apply="confirmation">Yes</a>'
+ '<a data-dismiss="confirmation">No</a>'
+ '</div>'
+ '</div>'
});
$(element).on('show.bs.confirmation', function(e) {
that.options.onShow(e, this);
// NOTE: CONFIRMATION EXTENDS popover.js
// ================================
Confirmation.prototype = $.extend({}, $.fn.popover.Constructor.prototype);
$(this).addClass('open');
Confirmation.prototype.constructor = Confirmation;
var options = that.options;
var all = options.all_selector;
Confirmation.prototype.getDefaults = function () {
return Confirmation.DEFAULTS;
}
if(options.singleton)
{
$(all).not(that.$element).each(function()
{
if( $(this).hasClass('open') )
{
$(this).confirmation('hide');
}
});
}
});
Confirmation.prototype.setContent = function () {
var that = this;
var $tip = this.tip();
var title = this.getTitle();
var $btnOk = $tip.find('[data-apply="confirmation"]');
var $btnCancel = $tip.find('[data-dismiss="confirmation"]');
var options = this.options
$(element).on('hide.bs.confirmation', function(e) {
that.options.onHide(e, this);
$btnOk.addClass(this.getBtnOkClass())
.html(this.getBtnOkLabel())
.prepend($('<i></i>').addClass(this.getBtnOkIcon()), " ")
.attr('href', this.getHref())
.attr('target', this.getTarget())
.off('click').on('click', function(event) {
options.onConfirm(event, that.$element);
$(this).removeClass('open');
});
// If the button is a submit one
if (that.$element.attr('type') == 'submit')
that.$element.closest('form:first').submit();
$(element).on('shown.bs.confirmation', function(e) {
var options = that.options;
var all = options.all_selector;
that.hide();
that.inState.click = false;
});
that.$element.on('click.dismiss.bs.confirmation', '[data-dismiss="confirmation"]', $.proxy(that.hide, that));
$btnCancel.addClass(this.getBtnCancelClass())
.html(this.getBtnCancelLabel())
.prepend($('<i></i>').addClass(this.getBtnCancelIcon()), " ")
.off('click').on('click', function(event){
options.onCancel(event, that.$element);
if(that.isPopout()) {
if(!event_body) {
event_body = $('body').on('click', function (e) {
if(that.$element.is(e.target)) return;
if(that.$element.has(e.target).length) return;
if($('.popover').has(e.target).length) return;
that.hide();
that.inState.click = false;
});
that.$element.confirmation('hide');
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title);
$('body').unbind(e);
$tip.removeClass('fade top bottom left right in');
event_body = false;
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide();
}
return;
});
}
}
});
Confirmation.prototype.getBtnOkClass = function () {
var $e = this.$element;
var o = this.options;
$(element).on('click', function(e) {
e.preventDefault();
});
}
return $e.attr('data-btnOkClass') || (typeof o.btnOkClass == 'function' ? o.btnOkClass.call(this, $e[0]) : o.btnOkClass);
}
if (!$.fn.popover || !$.fn.tooltip) throw new Error('Confirmation requires popover.js and tooltip.js');
Confirmation.prototype.getBtnOkLabel = function () {
var $e = this.$element;
var o = this.options;
Confirmation.DEFAULTS = $.extend({}, $.fn.popover.Constructor.DEFAULTS, {
placement : 'right',
title : 'Are you sure?',
btnOkClass : 'btn btn-sm btn-danger',
btnOkLabel : 'Delete',
btnOkIcon : 'glyphicon glyphicon-ok',
btnCancelClass : 'btn btn-sm btn-default',
btnCancelLabel : 'Cancel',
btnCancelIcon : 'glyphicon glyphicon-remove',
href : '#',
target : '_self',
singleton : true,
popout : true,
onShow : function(event, element){},
onHide : function(event, element){},
onConfirm : function(event, element){},
onCancel : function(event, element){},
template : '<div class="popover"><div class="arrow"></div>'
+ '<h3 class="popover-title"></h3>'
+ '<div class="popover-content">'
+ '<div class="btn-group">'
+ '<a data-dismiss="confirmation">No</a>'
+ '<a data-apply="confirmation">Yes</a>'
+ '</div>'
+ '</div>'
+ '</div>'
});
return $e.attr('data-btnOkLabel') || (typeof o.btnOkLabel == 'function' ? o.btnOkLabel.call(this, $e[0]) : o.btnOkLabel);
}
Confirmation.prototype.getBtnOkIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkIcon') || (typeof o.btnOkIcon == 'function' ? o.btnOkIcon.call(this, $e[0]) : o.btnOkIcon);
}
Confirmation.prototype.getBtnCancelClass = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelClass') || (typeof o.btnCancelClass == 'function' ? o.btnCancelClass.call(this, $e[0]) : o.btnCancelClass);
}
Confirmation.prototype.getBtnCancelLabel = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelLabel') || (typeof o.btnCancelLabel == 'function' ? o.btnCancelLabel.call(this, $e[0]) : o.btnCancelLabel);
}
Confirmation.prototype.getBtnCancelIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelIcon') || (typeof o.btnCancelIcon == 'function' ? o.btnCancelIcon.call(this, $e[0]) : o.btnCancelIcon);
}
Confirmation.prototype.getHref = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-href') || (typeof o.href == 'function' ? o.href.call(this, $e[0]) : o.href);
}
Confirmation.prototype.getTarget = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-target') || (typeof o.target == 'function' ? o.target.call(this, $e[0]) : o.target);
}
Confirmation.prototype.isPopout = function () {
var popout;
var $e = this.$element;
var o = this.options;
popout = $e.attr('data-popout') || (typeof o.popout == 'function' ? o.popout.call(this, $e[0]) : o.popout);
if(popout == 'false') popout = false;
return popout
}
// NOTE: CONFIRMATION EXTENDS popover.js
// ================================
Confirmation.prototype = $.extend({}, $.fn.popover.Constructor.prototype);
// CONFIRMATION PLUGIN DEFINITION
// =========================
var old = $.fn.confirmation;
Confirmation.prototype.constructor = Confirmation;
$.fn.confirmation = function (option) {
var that = this;
Confirmation.prototype.getDefaults = function () {
return Confirmation.DEFAULTS;
}
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.confirmation');
var options = typeof option == 'object' && option;
Confirmation.prototype.setContent = function () {
var that = this;
var $tip = this.tip();
var title = this.getTitle();
var $btnOk = $tip.find('[data-apply="confirmation"]');
var $btnCancel = $tip.find('[data-dismiss="confirmation"]');
var options = this.options
options = options || {};
options.all_selector = that.selector;
$btnOk.addClass(this.getBtnOkClass())
.html(this.getBtnOkLabel())
.prepend($('<i></i>').addClass(this.getBtnOkIcon()), " ")
.attr('href', this.getHref())
.attr('target', this.getTarget())
.off('click').on('click', function(event) {
that.$element.confirmation('hide');
if (!data && option == 'destroy') return;
if (!data) $this.data('bs.confirmation', (data = new Confirmation(this, options)));
if (typeof option == 'string') data[option]();
});
}
options.onConfirm(event, that.$element);
});
$btnCancel.addClass(this.getBtnCancelClass())
.html(this.getBtnCancelLabel())
.prepend($('<i></i>').addClass(this.getBtnCancelIcon()), " ")
.off('click').on('click', function(event){
options.onCancel(event, that.$element);
that.$element.confirmation('hide');
});
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title);
$tip.removeClass('fade top bottom left right in');
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide();
}
Confirmation.prototype.getBtnOkClass = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkClass') || (typeof o.btnOkClass == 'function' ? o.btnOkClass.call($e[0]) : o.btnOkClass);
}
Confirmation.prototype.getBtnOkLabel = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkLabel') || (typeof o.btnOkLabel == 'function' ? o.btnOkLabel.call($e[0]) : o.btnOkLabel);
}
Confirmation.prototype.getBtnOkIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkIcon') || (typeof o.btnOkIcon == 'function' ? o.btnOkIcon.call($e[0]) : o.btnOkIcon);
}
Confirmation.prototype.getBtnCancelClass = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelClass') || (typeof o.btnCancelClass == 'function' ? o.btnCancelClass.call($e[0]) : o.btnCancelClass);
}
Confirmation.prototype.getBtnCancelLabel = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelLabel') || (typeof o.btnCancelLabel == 'function' ? o.btnCancelLabel.call($e[0]) : o.btnCancelLabel);
}
Confirmation.prototype.getBtnCancelIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelIcon') || (typeof o.btnCancelIcon == 'function' ? o.btnCancelIcon.call($e[0]) : o.btnCancelIcon);
}
Confirmation.prototype.getHref = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-href') || (typeof o.href == 'function' ? o.href.call($e[0]) : o.href);
}
Confirmation.prototype.getTarget = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-target') || (typeof o.target == 'function' ? o.target.call($e[0]) : o.target);
}
Confirmation.prototype.isPopout = function () {
var popout;
var $e = this.$element;
var o = this.options;
popout = $e.attr('data-popout') || (typeof o.popout == 'function' ? o.popout.call($e[0]) : o.popout);
if(popout == 'false') popout = false;
return popout
}
$.fn.confirmation.Constructor = Confirmation
// CONFIRMATION PLUGIN DEFINITION
// =========================
var old = $.fn.confirmation;
// CONFIRMATION NO CONFLICT
// ===================
$.fn.confirmation.noConflict = function () {
$.fn.confirmation = old;
$.fn.confirmation = function (option) {
var that = this;
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.confirmation');
var options = typeof option == 'object' && option;
options = options || {};
options.all_selector = that.selector;
if (!data && option == 'destroy') return;
if (!data) $this.data('bs.confirmation', (data = new Confirmation(this, options)));
if (typeof option == 'string') data[option]();
});
}
$.fn.confirmation.Constructor = Confirmation
// CONFIRMATION NO CONFLICT
// ===================
$.fn.confirmation.noConflict = function () {
$.fn.confirmation = old;
return this;
}
return this;
}
}(jQuery);
define("bootstrapConfirmation", ["bootstrap"], function(){});
/*! ========================================================================
@@ -2004,6 +2021,9 @@ define('app/util',[
// map module
mapModuleId: 'pf-map-module', // id for main map module
mapTabBarId: 'pf-map-tabs', // id for map tab bar
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapClass: 'pf-map' , // class for all maps
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
@@ -2030,40 +2050,41 @@ define('app/util',[
* displays a loading indicator on an element
*/
$.fn.showLoadingAnimation = function(options){
let loadingElement = $(this);
return this.each(function(){
let loadingElement = $(this);
let iconSize = 'fa-lg';
let iconSize = 'fa-lg';
// disable all events
loadingElement.css('pointer-events', 'none');
// disable all events
loadingElement.css('pointer-events', 'none');
if(options){
if(options.icon){
if(options.icon.size){
iconSize = options.icon.size;
if(options){
if(options.icon){
if(options.icon.size){
iconSize = options.icon.size;
}
}
}
}
let overlay = $('<div>', {
class: config.ajaxOverlayClass
}).append(
$('<div>', {
class: [config.ajaxOverlayWrapperClass].join(' ')
let overlay = $('<div>', {
class: config.ajaxOverlayClass
}).append(
$('<i>', {
class: ['fa', 'fa-fw', iconSize, 'fa-refresh', 'fa-spin'].join(' ')
})
)
);
$('<div>', {
class: [config.ajaxOverlayWrapperClass].join(' ')
}).append(
$('<i>', {
class: ['fa', 'fa-fw', iconSize, 'fa-refresh', 'fa-spin'].join(' ')
})
)
);
loadingElement.append(overlay);
loadingElement.append(overlay);
// fade in
$(overlay).velocity({
opacity: 0.6
},{
duration: 120
// fade in
$(overlay).velocity({
opacity: 0.6
},{
duration: 120
});
});
};
@@ -2071,16 +2092,20 @@ define('app/util',[
* removes a loading indicator
*/
$.fn.hideLoadingAnimation = function(){
let loadingElement = $(this);
let overlay = loadingElement.find('.' + config.ajaxOverlayClass );
return this.each(function(){
let loadingElement = $(this);
let overlay = loadingElement.find('.' + config.ajaxOverlayClass );
// important: "stop" is required to stop "show" animation
// -> otherwise "complete" callback is not fired!
$(overlay).velocity('stop').velocity('reverse', {
complete: function(){
$(this).remove();
// enable all events
loadingElement.css('pointer-events', 'auto');
if(overlay.length){
// important: "stop" is required to stop "show" animation
// -> otherwise "complete" callback is not fired!
overlay.velocity('stop').velocity('reverse', {
complete: function(){
$(this).remove();
// enable all events
loadingElement.css('pointer-events', 'auto');
}
});
}
});
};
@@ -3216,6 +3241,15 @@ define('app/util',[
return mapTabElements;
};
/**
* get mapElement from overlay or any child of that
* @param mapOverlay
* @returns {jQuery}
*/
let getMapElementFromOverlay = (mapOverlay) => {
return $(mapOverlay).parents('.' + config.mapWrapperClass).find('.' + config.mapClass);
};
/**
* get the map module object or create a new module
* @returns {*|HTMLElement}
@@ -3748,6 +3782,132 @@ define('app/util',[
return userInfo;
};
/**
* get "nearBy" systemData based on a jump radius around a currentSystem
* @param currentSystemData
* @param currentMapData
* @param jumps
* @param foundSystemIds
* @returns {{systemData: *, tree: {}}}
*/
let getNearBySystemData = (currentSystemData, currentMapData, jumps, foundSystemIds = {}) => {
// look for systemData by ID
let getSystemData = (systemId) => {
for(let j = 0; j < currentMapData.data.systems.length; j++){
let systemData = currentMapData.data.systems[j];
if(systemData.id === systemId){
return systemData;
}
}
return false;
};
// skip systems that are already found in recursive calls
foundSystemIds[currentSystemData.id] = {distance: jumps};
let nearBySystems = {
systemData: currentSystemData,
tree: {}
};
jumps--;
if(jumps >= 0){
for(let i = 0; i < currentMapData.data.connections.length; i++){
let connectionData = currentMapData.data.connections[i];
let type = ''; // "source" OR "target"
if(connectionData.source === currentSystemData.id){
type = 'target';
}else if(connectionData.target === currentSystemData.id){
type = 'source';
}
if(
type &&
(
foundSystemIds[connectionData[type]] === undefined ||
foundSystemIds[connectionData[type]].distance < jumps
)
){
let newSystemData = getSystemData(connectionData[type]);
if(newSystemData){
nearBySystems.tree[connectionData[type]] = getNearBySystemData(newSystemData, currentMapData, jumps, foundSystemIds);
}
}
}
}
return nearBySystems;
};
/**
* get current character data from all characters who are "nearby" the current user
* -> see getNearBySystemData()
* @param nearBySystems
* @param userData
* @param jumps
* @param data
* @returns {{}}
*/
let getNearByCharacterData = (nearBySystems, userData, jumps = 0, data = {}) => {
let getCharacterDataBySystemId = (systemId) => {
for(let i = 0; i < userData.length; i++){
if(userData[i].id === systemId){
return userData[i].user;
}
}
return [];
};
let filterFinalCharData = function(tmpFinalCharData){
return this.id !== tmpFinalCharData.id;
};
let characterData = getCharacterDataBySystemId(nearBySystems.systemData.systemId);
if(characterData.length){
// filter (remove) characterData for "already" added chars
characterData = characterData.filter(function(tmpCharacterData, index, allData){
let keepData = true;
for(let tmpJump in data) {
// just scan systems with > jumps than current system
if(tmpJump > jumps){
let filteredFinalData = data[tmpJump].filter(filterFinalCharData, tmpCharacterData);
if(filteredFinalData.length > 0){
data[tmpJump] = filteredFinalData;
}else{
delete data[tmpJump];
}
}else{
for(let k = 0; k < data[tmpJump].length; k++){
if(data[tmpJump][k].id === tmpCharacterData.id){
keepData = false;
break;
}
}
}
}
return keepData;
});
data[jumps] = data[jumps] ? data[jumps] : [];
data[jumps] = [...data[jumps], ...characterData];
}
jumps++;
for(let prop in nearBySystems.tree) {
if( nearBySystems.tree.hasOwnProperty(prop) ){
let tmpSystemData = nearBySystems.tree[prop];
data = getNearByCharacterData(tmpSystemData, userData, jumps, data);
}
}
return data;
};
/**
* set new destination for a system
* -> CREST request
@@ -3845,6 +4005,34 @@ define('app/util',[
return $('.' + config.dialogClass).filter(':visible');
};
/**
* send Ajax request that remote opens an ingame Window
* @param targetId
*/
let openIngameWindow = (targetId) => {
targetId = parseInt(targetId);
if(targetId > 0){
$.ajax({
type: 'POST',
url: Init.path.openIngameWindow,
data: {
targetId: targetId
},
dataType: 'json'
}).done(function(data){
if(data.error.length > 0){
showNotify({title: 'Open window in client', text: 'Remote window open failed', type: 'error'});
}else{
showNotify({title: 'Open window in client', text: 'Check your EVE client', type: 'success'});
}
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
showNotify({title: jqXHR.status + ': openWindow', text: reason, type: 'error'});
});
}
};
/**
* formats a price string into an ISK Price
* @param price
@@ -3982,6 +4170,7 @@ define('app/util',[
setSyncStatus: setSyncStatus,
getSyncType: getSyncType,
isXHRAborted: isXHRAborted,
getMapElementFromOverlay: getMapElementFromOverlay,
getMapModule: getMapModule,
getSystemEffectData: getSystemEffectData,
getSystemEffectTable: getSystemEffectTable,
@@ -4009,9 +4198,12 @@ define('app/util',[
getCurrentUserInfo: getCurrentUserInfo,
getCurrentCharacterLog: getCurrentCharacterLog,
flattenXEditableSelectArray: flattenXEditableSelectArray,
getNearBySystemData: getNearBySystemData,
getNearByCharacterData: getNearByCharacterData,
setDestination: setDestination,
convertDateToString: convertDateToString,
getOpenDialogs: getOpenDialogs,
openIngameWindow: openIngameWindow,
formatPrice: formatPrice,
getLocalStorage: getLocalStorage,
getDocumentPath: getDocumentPath,
@@ -7066,6 +7258,9 @@ define('login',[
// cookie hint
cookieHintId: 'pf-cookie-hint', // id for "cookie hint" element
// login
ssoButtonClass: 'pf-sso-login-button', // class for SSO login button
// character select
characterSelectionClass: 'pf-character-selection', // class for character panel wrapper
characterRowAnimateClass: 'pf-character-row-animate', // class for character panel row during animation
@@ -7086,7 +7281,9 @@ define('login',[
serverPanelId: 'pf-server-panel', // id for EVE Online server status panel
// animation
animateElementClass: 'pf-animate-on-visible' // class for elements that will be animated to show
animateElementClass: 'pf-animate-on-visible', // class for elements that will be animated to show
defaultAcceptCookieExpire: 365 // default expire for "accept coolies" cookie
};
/**
@@ -7143,10 +7340,39 @@ define('login',[
if(getCookie('cookie') !== '1'){
// hint not excepted
$('#' + config.cookieHintId).collapse('show');
// show Cookie accept hint on SSO login button
let confirmationSettings = {
container: 'body',
placement: 'bottom',
btnOkClass: 'btn btn-sm btn-default',
btnOkLabel: 'dismiss',
btnOkIcon: 'fa fa-fw fa-sign-in',
title: 'Accept cookies',
btnCancelClass: 'btn btn-sm btn-success',
btnCancelLabel: 'accept',
btnCancelIcon: 'fa fa-fw fa-check',
onCancel: function(e, target){
// "Accept cookies"
setCookie('cookie', 1, config.defaultAcceptCookieExpire);
// set "default" href
let href = $(target).data('bs.confirmation').getHref();
$(e.target).attr('href', href);
},
onConfirm : function(e, target){
// "NO cookies" => trigger "default" href link action
},
href: function(target){
return $(target).attr('href');
}
};
$('.' + config.ssoButtonClass).confirmation(confirmationSettings);
}
$('#' + config.cookieHintId + ' .btn-success').on('click', function(){
setCookie('cookie', 1, 365);
setCookie('cookie', 1, config.defaultAcceptCookieExpire);
});
// manual -------------------------------------------------------------

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -16,6 +16,7 @@ define('app/init',['jquery'], function($) {
logIn: 'api/user/logIn', // ajax URL - login
logout: 'api/user/logout', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
openIngameWindow: 'api/user/openIngameWindow', // ajax URL - open inGame Window
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
// access API
@@ -65,6 +66,7 @@ define('app/init',['jquery'], function($) {
splashOverlay: 300, // "splash" loading overlay
headerLink: 100, // links in head bar
mapOverlay: 200, // show/hide duration for map overlays
mapOverlayLocal: 180, // show/hide duration for map "local" overlay
mapMoveSystem: 180, // system position has changed animation
mapDeleteSystem: 200, // remove system from map
mapModule: 200, // show/hide of an map module

File diff suppressed because one or more lines are too long

View File

@@ -21,6 +21,7 @@ define('app/init',['jquery'], function($) {
logIn: 'api/user/logIn', // ajax URL - login
logout: 'api/user/logout', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
openIngameWindow: 'api/user/openIngameWindow', // ajax URL - open inGame Window
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data
// access API
@@ -70,6 +71,7 @@ define('app/init',['jquery'], function($) {
splashOverlay: 300, // "splash" loading overlay
headerLink: 100, // links in head bar
mapOverlay: 200, // show/hide duration for map overlays
mapOverlayLocal: 180, // show/hide duration for map "local" overlay
mapMoveSystem: 180, // system position has changed animation
mapDeleteSystem: 200, // remove system from map
mapModule: 200, // show/hide of an map module
@@ -1688,255 +1690,270 @@ define("xEditable", ["bootstrap"], function(){});
(function($){$.fn.hoverIntent=function(handlerIn,handlerOut,selector){var cfg={interval:100,sensitivity:6,timeout:0};if(typeof handlerIn==="object"){cfg=$.extend(cfg,handlerIn)}else{if($.isFunction(handlerOut)){cfg=$.extend(cfg,{over:handlerIn,out:handlerOut,selector:selector})}else{cfg=$.extend(cfg,{over:handlerIn,out:handlerIn,selector:handlerOut})}}var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if(Math.sqrt((pX-cX)*(pX-cX)+(pY-cY)*(pY-cY))<cfg.sensitivity){$(ob).off("mousemove.hoverIntent",track);ob.hoverIntent_s=true;return cfg.over.apply(ob,[ev])}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=false;return cfg.out.apply(ob,[ev])};var handleHover=function(e){var ev=$.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t)}if(e.type==="mouseenter"){pX=ev.pageX;pY=ev.pageY;$(ob).on("mousemove.hoverIntent",track);if(!ob.hoverIntent_s){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob)},cfg.interval)}}else{$(ob).off("mousemove.hoverIntent",track);if(ob.hoverIntent_s){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob)},cfg.timeout)}}};return this.on({"mouseenter.hoverIntent":handleHover,"mouseleave.hoverIntent":handleHover},cfg.selector)}})(jQuery);
define("hoverIntent", ["jquery"], function(){});
/*!
* Bootstrap Confirmation v1.0.5
* https://github.com/tavicu/bs-confirmation
*/
+function ($) {
'use strict';
'use strict';
//var for check event at body can have only one.
var event_body = false;
//var for check event at body can have only one.
var event_body = false;
// CONFIRMATION PUBLIC CLASS DEFINITION
// ===============================
var Confirmation = function (element, options) {
var that = this;
// CONFIRMATION PUBLIC CLASS DEFINITION
// ===============================
var Confirmation = function (element, options) {
var that = this;
this.init('confirmation', element, options);
this.init('confirmation', element, options);
$(element).on('show.bs.confirmation', function(e) {
that.options.onShow(e, this);
$(this).addClass('open');
var options = that.options;
var all = options.all_selector;
if(options.singleton)
{
$(all).not(that.$element).each(function()
{
if( $(this).hasClass('open') )
{
$(this).confirmation('hide');
}
});
}
});
$(element).on('hide.bs.confirmation', function(e) {
that.options.onHide(e, this);
$(this).removeClass('open');
});
$(element).on('shown.bs.confirmation', function(e) {
var options = that.options;
var all = options.all_selector;
if(that.isPopout()) {
if(!event_body) {
event_body = $('body').on('click', function (e) {
if(that.$element.is(e.target)) return;
if(that.$element.has(e.target).length) return;
if($('.popover').has(e.target).length) return;
that.hide();
that.inState.click = false;
$('body').unbind(e);
event_body = false;
return;
});
}
}
});
if(options.selector) {
$(element).on('click.bs.confirmation', options.selector, function(e) {
e.preventDefault();
});
} else {
$(element).on('click.bs.confirmation', function(e) {
e.preventDefault();
});
}
}
if (!$.fn.popover || !$.fn.tooltip) throw new Error('Confirmation requires popover.js and tooltip.js');
Confirmation.VERSION = '1.0.5'
Confirmation.DEFAULTS = $.extend({}, $.fn.popover.Constructor.DEFAULTS, {
placement : 'right',
title : 'Are you sure?',
btnOkClass : 'btn btn-sm btn-danger',
btnOkLabel : 'Delete',
btnOkIcon : 'glyphicon glyphicon-ok',
btnCancelClass : 'btn btn-sm btn-default',
btnCancelLabel : 'Cancel',
btnCancelIcon : 'glyphicon glyphicon-remove',
href : '#',
target : '_self',
singleton : true,
popout : true,
onShow : function(event, element){},
onHide : function(event, element){},
onConfirm : function(event, element){},
onCancel : function(event, element){},
template : '<div class="popover"><div class="arrow"></div>'
+ '<h3 class="popover-title"></h3>'
+ '<div class="popover-content">'
+ '<a data-apply="confirmation">Yes</a>'
+ '<a data-dismiss="confirmation">No</a>'
+ '</div>'
+ '</div>'
});
$(element).on('show.bs.confirmation', function(e) {
that.options.onShow(e, this);
// NOTE: CONFIRMATION EXTENDS popover.js
// ================================
Confirmation.prototype = $.extend({}, $.fn.popover.Constructor.prototype);
$(this).addClass('open');
Confirmation.prototype.constructor = Confirmation;
var options = that.options;
var all = options.all_selector;
Confirmation.prototype.getDefaults = function () {
return Confirmation.DEFAULTS;
}
if(options.singleton)
{
$(all).not(that.$element).each(function()
{
if( $(this).hasClass('open') )
{
$(this).confirmation('hide');
}
});
}
});
Confirmation.prototype.setContent = function () {
var that = this;
var $tip = this.tip();
var title = this.getTitle();
var $btnOk = $tip.find('[data-apply="confirmation"]');
var $btnCancel = $tip.find('[data-dismiss="confirmation"]');
var options = this.options
$(element).on('hide.bs.confirmation', function(e) {
that.options.onHide(e, this);
$btnOk.addClass(this.getBtnOkClass())
.html(this.getBtnOkLabel())
.prepend($('<i></i>').addClass(this.getBtnOkIcon()), " ")
.attr('href', this.getHref())
.attr('target', this.getTarget())
.off('click').on('click', function(event) {
options.onConfirm(event, that.$element);
$(this).removeClass('open');
});
// If the button is a submit one
if (that.$element.attr('type') == 'submit')
that.$element.closest('form:first').submit();
$(element).on('shown.bs.confirmation', function(e) {
var options = that.options;
var all = options.all_selector;
that.hide();
that.inState.click = false;
});
that.$element.on('click.dismiss.bs.confirmation', '[data-dismiss="confirmation"]', $.proxy(that.hide, that));
$btnCancel.addClass(this.getBtnCancelClass())
.html(this.getBtnCancelLabel())
.prepend($('<i></i>').addClass(this.getBtnCancelIcon()), " ")
.off('click').on('click', function(event){
options.onCancel(event, that.$element);
if(that.isPopout()) {
if(!event_body) {
event_body = $('body').on('click', function (e) {
if(that.$element.is(e.target)) return;
if(that.$element.has(e.target).length) return;
if($('.popover').has(e.target).length) return;
that.hide();
that.inState.click = false;
});
that.$element.confirmation('hide');
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title);
$('body').unbind(e);
$tip.removeClass('fade top bottom left right in');
event_body = false;
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide();
}
return;
});
}
}
});
Confirmation.prototype.getBtnOkClass = function () {
var $e = this.$element;
var o = this.options;
$(element).on('click', function(e) {
e.preventDefault();
});
}
return $e.attr('data-btnOkClass') || (typeof o.btnOkClass == 'function' ? o.btnOkClass.call(this, $e[0]) : o.btnOkClass);
}
if (!$.fn.popover || !$.fn.tooltip) throw new Error('Confirmation requires popover.js and tooltip.js');
Confirmation.prototype.getBtnOkLabel = function () {
var $e = this.$element;
var o = this.options;
Confirmation.DEFAULTS = $.extend({}, $.fn.popover.Constructor.DEFAULTS, {
placement : 'right',
title : 'Are you sure?',
btnOkClass : 'btn btn-sm btn-danger',
btnOkLabel : 'Delete',
btnOkIcon : 'glyphicon glyphicon-ok',
btnCancelClass : 'btn btn-sm btn-default',
btnCancelLabel : 'Cancel',
btnCancelIcon : 'glyphicon glyphicon-remove',
href : '#',
target : '_self',
singleton : true,
popout : true,
onShow : function(event, element){},
onHide : function(event, element){},
onConfirm : function(event, element){},
onCancel : function(event, element){},
template : '<div class="popover"><div class="arrow"></div>'
+ '<h3 class="popover-title"></h3>'
+ '<div class="popover-content">'
+ '<div class="btn-group">'
+ '<a data-dismiss="confirmation">No</a>'
+ '<a data-apply="confirmation">Yes</a>'
+ '</div>'
+ '</div>'
+ '</div>'
});
return $e.attr('data-btnOkLabel') || (typeof o.btnOkLabel == 'function' ? o.btnOkLabel.call(this, $e[0]) : o.btnOkLabel);
}
Confirmation.prototype.getBtnOkIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkIcon') || (typeof o.btnOkIcon == 'function' ? o.btnOkIcon.call(this, $e[0]) : o.btnOkIcon);
}
Confirmation.prototype.getBtnCancelClass = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelClass') || (typeof o.btnCancelClass == 'function' ? o.btnCancelClass.call(this, $e[0]) : o.btnCancelClass);
}
Confirmation.prototype.getBtnCancelLabel = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelLabel') || (typeof o.btnCancelLabel == 'function' ? o.btnCancelLabel.call(this, $e[0]) : o.btnCancelLabel);
}
Confirmation.prototype.getBtnCancelIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelIcon') || (typeof o.btnCancelIcon == 'function' ? o.btnCancelIcon.call(this, $e[0]) : o.btnCancelIcon);
}
Confirmation.prototype.getHref = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-href') || (typeof o.href == 'function' ? o.href.call(this, $e[0]) : o.href);
}
Confirmation.prototype.getTarget = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-target') || (typeof o.target == 'function' ? o.target.call(this, $e[0]) : o.target);
}
Confirmation.prototype.isPopout = function () {
var popout;
var $e = this.$element;
var o = this.options;
popout = $e.attr('data-popout') || (typeof o.popout == 'function' ? o.popout.call(this, $e[0]) : o.popout);
if(popout == 'false') popout = false;
return popout
}
// NOTE: CONFIRMATION EXTENDS popover.js
// ================================
Confirmation.prototype = $.extend({}, $.fn.popover.Constructor.prototype);
// CONFIRMATION PLUGIN DEFINITION
// =========================
var old = $.fn.confirmation;
Confirmation.prototype.constructor = Confirmation;
$.fn.confirmation = function (option) {
var that = this;
Confirmation.prototype.getDefaults = function () {
return Confirmation.DEFAULTS;
}
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.confirmation');
var options = typeof option == 'object' && option;
Confirmation.prototype.setContent = function () {
var that = this;
var $tip = this.tip();
var title = this.getTitle();
var $btnOk = $tip.find('[data-apply="confirmation"]');
var $btnCancel = $tip.find('[data-dismiss="confirmation"]');
var options = this.options
options = options || {};
options.all_selector = that.selector;
$btnOk.addClass(this.getBtnOkClass())
.html(this.getBtnOkLabel())
.prepend($('<i></i>').addClass(this.getBtnOkIcon()), " ")
.attr('href', this.getHref())
.attr('target', this.getTarget())
.off('click').on('click', function(event) {
that.$element.confirmation('hide');
if (!data && option == 'destroy') return;
if (!data) $this.data('bs.confirmation', (data = new Confirmation(this, options)));
if (typeof option == 'string') data[option]();
});
}
options.onConfirm(event, that.$element);
});
$btnCancel.addClass(this.getBtnCancelClass())
.html(this.getBtnCancelLabel())
.prepend($('<i></i>').addClass(this.getBtnCancelIcon()), " ")
.off('click').on('click', function(event){
options.onCancel(event, that.$element);
that.$element.confirmation('hide');
});
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title);
$tip.removeClass('fade top bottom left right in');
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
// this manually by checking the contents.
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide();
}
Confirmation.prototype.getBtnOkClass = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkClass') || (typeof o.btnOkClass == 'function' ? o.btnOkClass.call($e[0]) : o.btnOkClass);
}
Confirmation.prototype.getBtnOkLabel = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkLabel') || (typeof o.btnOkLabel == 'function' ? o.btnOkLabel.call($e[0]) : o.btnOkLabel);
}
Confirmation.prototype.getBtnOkIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnOkIcon') || (typeof o.btnOkIcon == 'function' ? o.btnOkIcon.call($e[0]) : o.btnOkIcon);
}
Confirmation.prototype.getBtnCancelClass = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelClass') || (typeof o.btnCancelClass == 'function' ? o.btnCancelClass.call($e[0]) : o.btnCancelClass);
}
Confirmation.prototype.getBtnCancelLabel = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelLabel') || (typeof o.btnCancelLabel == 'function' ? o.btnCancelLabel.call($e[0]) : o.btnCancelLabel);
}
Confirmation.prototype.getBtnCancelIcon = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-btnCancelIcon') || (typeof o.btnCancelIcon == 'function' ? o.btnCancelIcon.call($e[0]) : o.btnCancelIcon);
}
Confirmation.prototype.getHref = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-href') || (typeof o.href == 'function' ? o.href.call($e[0]) : o.href);
}
Confirmation.prototype.getTarget = function () {
var $e = this.$element;
var o = this.options;
return $e.attr('data-target') || (typeof o.target == 'function' ? o.target.call($e[0]) : o.target);
}
Confirmation.prototype.isPopout = function () {
var popout;
var $e = this.$element;
var o = this.options;
popout = $e.attr('data-popout') || (typeof o.popout == 'function' ? o.popout.call($e[0]) : o.popout);
if(popout == 'false') popout = false;
return popout
}
$.fn.confirmation.Constructor = Confirmation
// CONFIRMATION PLUGIN DEFINITION
// =========================
var old = $.fn.confirmation;
// CONFIRMATION NO CONFLICT
// ===================
$.fn.confirmation.noConflict = function () {
$.fn.confirmation = old;
$.fn.confirmation = function (option) {
var that = this;
return this.each(function () {
var $this = $(this);
var data = $this.data('bs.confirmation');
var options = typeof option == 'object' && option;
options = options || {};
options.all_selector = that.selector;
if (!data && option == 'destroy') return;
if (!data) $this.data('bs.confirmation', (data = new Confirmation(this, options)));
if (typeof option == 'string') data[option]();
});
}
$.fn.confirmation.Constructor = Confirmation
// CONFIRMATION NO CONFLICT
// ===================
$.fn.confirmation.noConflict = function () {
$.fn.confirmation = old;
return this;
}
return this;
}
}(jQuery);
define("bootstrapConfirmation", ["bootstrap"], function(){});
/*! ========================================================================
@@ -2004,6 +2021,9 @@ define('app/util',[
// map module
mapModuleId: 'pf-map-module', // id for main map module
mapTabBarId: 'pf-map-tabs', // id for map tab bar
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapClass: 'pf-map' , // class for all maps
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
@@ -2030,40 +2050,41 @@ define('app/util',[
* displays a loading indicator on an element
*/
$.fn.showLoadingAnimation = function(options){
let loadingElement = $(this);
return this.each(function(){
let loadingElement = $(this);
let iconSize = 'fa-lg';
let iconSize = 'fa-lg';
// disable all events
loadingElement.css('pointer-events', 'none');
// disable all events
loadingElement.css('pointer-events', 'none');
if(options){
if(options.icon){
if(options.icon.size){
iconSize = options.icon.size;
if(options){
if(options.icon){
if(options.icon.size){
iconSize = options.icon.size;
}
}
}
}
let overlay = $('<div>', {
class: config.ajaxOverlayClass
}).append(
$('<div>', {
class: [config.ajaxOverlayWrapperClass].join(' ')
let overlay = $('<div>', {
class: config.ajaxOverlayClass
}).append(
$('<i>', {
class: ['fa', 'fa-fw', iconSize, 'fa-refresh', 'fa-spin'].join(' ')
})
)
);
$('<div>', {
class: [config.ajaxOverlayWrapperClass].join(' ')
}).append(
$('<i>', {
class: ['fa', 'fa-fw', iconSize, 'fa-refresh', 'fa-spin'].join(' ')
})
)
);
loadingElement.append(overlay);
loadingElement.append(overlay);
// fade in
$(overlay).velocity({
opacity: 0.6
},{
duration: 120
// fade in
$(overlay).velocity({
opacity: 0.6
},{
duration: 120
});
});
};
@@ -2071,16 +2092,20 @@ define('app/util',[
* removes a loading indicator
*/
$.fn.hideLoadingAnimation = function(){
let loadingElement = $(this);
let overlay = loadingElement.find('.' + config.ajaxOverlayClass );
return this.each(function(){
let loadingElement = $(this);
let overlay = loadingElement.find('.' + config.ajaxOverlayClass );
// important: "stop" is required to stop "show" animation
// -> otherwise "complete" callback is not fired!
$(overlay).velocity('stop').velocity('reverse', {
complete: function(){
$(this).remove();
// enable all events
loadingElement.css('pointer-events', 'auto');
if(overlay.length){
// important: "stop" is required to stop "show" animation
// -> otherwise "complete" callback is not fired!
overlay.velocity('stop').velocity('reverse', {
complete: function(){
$(this).remove();
// enable all events
loadingElement.css('pointer-events', 'auto');
}
});
}
});
};
@@ -3216,6 +3241,15 @@ define('app/util',[
return mapTabElements;
};
/**
* get mapElement from overlay or any child of that
* @param mapOverlay
* @returns {jQuery}
*/
let getMapElementFromOverlay = (mapOverlay) => {
return $(mapOverlay).parents('.' + config.mapWrapperClass).find('.' + config.mapClass);
};
/**
* get the map module object or create a new module
* @returns {*|HTMLElement}
@@ -3748,6 +3782,132 @@ define('app/util',[
return userInfo;
};
/**
* get "nearBy" systemData based on a jump radius around a currentSystem
* @param currentSystemData
* @param currentMapData
* @param jumps
* @param foundSystemIds
* @returns {{systemData: *, tree: {}}}
*/
let getNearBySystemData = (currentSystemData, currentMapData, jumps, foundSystemIds = {}) => {
// look for systemData by ID
let getSystemData = (systemId) => {
for(let j = 0; j < currentMapData.data.systems.length; j++){
let systemData = currentMapData.data.systems[j];
if(systemData.id === systemId){
return systemData;
}
}
return false;
};
// skip systems that are already found in recursive calls
foundSystemIds[currentSystemData.id] = {distance: jumps};
let nearBySystems = {
systemData: currentSystemData,
tree: {}
};
jumps--;
if(jumps >= 0){
for(let i = 0; i < currentMapData.data.connections.length; i++){
let connectionData = currentMapData.data.connections[i];
let type = ''; // "source" OR "target"
if(connectionData.source === currentSystemData.id){
type = 'target';
}else if(connectionData.target === currentSystemData.id){
type = 'source';
}
if(
type &&
(
foundSystemIds[connectionData[type]] === undefined ||
foundSystemIds[connectionData[type]].distance < jumps
)
){
let newSystemData = getSystemData(connectionData[type]);
if(newSystemData){
nearBySystems.tree[connectionData[type]] = getNearBySystemData(newSystemData, currentMapData, jumps, foundSystemIds);
}
}
}
}
return nearBySystems;
};
/**
* get current character data from all characters who are "nearby" the current user
* -> see getNearBySystemData()
* @param nearBySystems
* @param userData
* @param jumps
* @param data
* @returns {{}}
*/
let getNearByCharacterData = (nearBySystems, userData, jumps = 0, data = {}) => {
let getCharacterDataBySystemId = (systemId) => {
for(let i = 0; i < userData.length; i++){
if(userData[i].id === systemId){
return userData[i].user;
}
}
return [];
};
let filterFinalCharData = function(tmpFinalCharData){
return this.id !== tmpFinalCharData.id;
};
let characterData = getCharacterDataBySystemId(nearBySystems.systemData.systemId);
if(characterData.length){
// filter (remove) characterData for "already" added chars
characterData = characterData.filter(function(tmpCharacterData, index, allData){
let keepData = true;
for(let tmpJump in data) {
// just scan systems with > jumps than current system
if(tmpJump > jumps){
let filteredFinalData = data[tmpJump].filter(filterFinalCharData, tmpCharacterData);
if(filteredFinalData.length > 0){
data[tmpJump] = filteredFinalData;
}else{
delete data[tmpJump];
}
}else{
for(let k = 0; k < data[tmpJump].length; k++){
if(data[tmpJump][k].id === tmpCharacterData.id){
keepData = false;
break;
}
}
}
}
return keepData;
});
data[jumps] = data[jumps] ? data[jumps] : [];
data[jumps] = [...data[jumps], ...characterData];
}
jumps++;
for(let prop in nearBySystems.tree) {
if( nearBySystems.tree.hasOwnProperty(prop) ){
let tmpSystemData = nearBySystems.tree[prop];
data = getNearByCharacterData(tmpSystemData, userData, jumps, data);
}
}
return data;
};
/**
* set new destination for a system
* -> CREST request
@@ -3845,6 +4005,34 @@ define('app/util',[
return $('.' + config.dialogClass).filter(':visible');
};
/**
* send Ajax request that remote opens an ingame Window
* @param targetId
*/
let openIngameWindow = (targetId) => {
targetId = parseInt(targetId);
if(targetId > 0){
$.ajax({
type: 'POST',
url: Init.path.openIngameWindow,
data: {
targetId: targetId
},
dataType: 'json'
}).done(function(data){
if(data.error.length > 0){
showNotify({title: 'Open window in client', text: 'Remote window open failed', type: 'error'});
}else{
showNotify({title: 'Open window in client', text: 'Check your EVE client', type: 'success'});
}
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
showNotify({title: jqXHR.status + ': openWindow', text: reason, type: 'error'});
});
}
};
/**
* formats a price string into an ISK Price
* @param price
@@ -3982,6 +4170,7 @@ define('app/util',[
setSyncStatus: setSyncStatus,
getSyncType: getSyncType,
isXHRAborted: isXHRAborted,
getMapElementFromOverlay: getMapElementFromOverlay,
getMapModule: getMapModule,
getSystemEffectData: getSystemEffectData,
getSystemEffectTable: getSystemEffectTable,
@@ -4009,9 +4198,12 @@ define('app/util',[
getCurrentUserInfo: getCurrentUserInfo,
getCurrentCharacterLog: getCurrentCharacterLog,
flattenXEditableSelectArray: flattenXEditableSelectArray,
getNearBySystemData: getNearBySystemData,
getNearByCharacterData: getNearByCharacterData,
setDestination: setDestination,
convertDateToString: convertDateToString,
getOpenDialogs: getOpenDialogs,
openIngameWindow: openIngameWindow,
formatPrice: formatPrice,
getLocalStorage: getLocalStorage,
getDocumentPath: getDocumentPath,

File diff suppressed because one or more lines are too long

View File

@@ -61,13 +61,13 @@ app/util.js
lib/mustache.min.js
app/render.js
app/logging.js
app/map/util.js
lib/requirejs/text.js
text!img/logo.svg!strip
text!templates/modules/header.html
text!templates/modules/footer.html
app/ui/dialog/notification.js
app/ui/dialog/stats.js
app/map/util.js
app/ui/dialog/map_info.js
app/ui/dialog/account_settings.js
app/ui/dialog/manual.js
@@ -90,6 +90,7 @@ lib/jquery.dragToSelect.js
lib/select2.min.js
app/map/contextmenu.js
app/map/overlay.js
app/map/local.js
app/map/map.js
app/counter.js
app/ui/system_info.js

View File

@@ -1,12 +1,12 @@
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header pull-left">
<ul class="nav navbar-nav {{dialogNavigationClass}}" role="tablist">
<li class="active">
<li class="{{#openTabInformation}}active{{/openTabInformation}}">
<a role="tab" data-toggle="tab" data-name="infoSummary" href="#{{dialogSummaryContainerId}}">
<i class="fa fa-street-view fa-fw"></i>&nbsp;Information
</a>
</li>
<li class="">
<li class="{{#openTabActivity}}active{{/openTabActivity}}">
<a role="tab" data-toggle="tab" data-name="infoUsers" href="#{{dialogUsersContainerId}}">
<i class="fa fa-fighter-jet fa-fw"></i>&nbsp;Activity
</a>
@@ -28,7 +28,7 @@
<div class="tab-content">
{{! "summary" tab ------------------------------------------------------ }}
<div role="tabpanel" class="tab-pane fade in active" id="{{dialogSummaryContainerId}}">
<div role="tabpanel" class="tab-pane fade {{#openTabInformation}}in active{{/openTabInformation}}" id="{{dialogSummaryContainerId}}">
<div class="alert alert-info fade in hidden-md hidden-lg">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><i class="fa fa-close"></i></button>
<span class="txt-color txt-color-information">Info</span>
@@ -52,7 +52,7 @@
</div>
{{! "users" tab -------------------------------------------------------- }}
<div role="tabpanel" class="tab-pane fade" id="{{dialogUsersContainerId}}">
<div role="tabpanel" class="tab-pane fade {{#openTabActivity}}in active{{/openTabActivity}}" id="{{dialogUsersContainerId}}">
<h4><i class="fa fa-male fa-lg fa-fw"></i> Active pilots</h4>
<div id="{{mapInfoUsersId}}" class="pf-dynamic-area">

View File

@@ -60,7 +60,7 @@
<option value="{{id}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/mapSelectOptions}}
</select>
<span class="help-block with-errors">Set maps from search</span>
<span class="help-block with-errors">Set maps for search</span>
</div>
</div>
</div>
@@ -105,7 +105,18 @@
</div>
</div>
<div class="col-sm-4 col-sm-offset-8">
<div class="col-sm-4 col-sm-offset-4">
<div class="form-group">
<div class="col-sm-offset-1 col-sm-11">
<div class="col-sm-12 col-xs-6 checkbox checkbox checkbox-circle" title="include frigate connections">
<input id="form_wormholes_frigate" name="wormholesFrigate" value="1" type="checkbox" checked>
<label for="form_wormholes_frigate">Frigate</label>
</div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="form-group">
<div class="col-sm-offset-1 col-sm-11">
<div class="col-sm-12 col-xs-6 checkbox checkbox-danger checkbox-circle" title="include EOL connections">

View File

@@ -34,7 +34,7 @@
<div class="pf-dynamic-area pf-system-info-description-area">
<i class="fa fa-fw fa-lg fa-pencil pull-right pf-module-icon-button {{descriptionButtonClass}}" data-toggle="tooltip" data-container="body" title="edit description"></i>
<div id="{{moduleToolbarActionId}}" class="pf-table-tools-action">
<div class="{{tableToolsActionClass}}">
<a href="#" class="{{descriptionTextareaClass}}"></a>
</div>
</div>

View File

@@ -7,10 +7,10 @@
<div class="panel-body no-padding text-align-center">
<div class="price-features" style="min-height: inherit;">
<ul class="list-unstyled text-left">
<li><i class="fa fa-fw fa-angle-right"></i>NEW signature overlays (signatures can be bind to connections) <a target="_blank" href="https://www.youtube.com/watch?v=5kp3CBx6998">Youtube</a></li>
<li><i class="fa fa-fw fa-angle-right"></i>NEW wormhole overlays ("updated"/"created" information)</li>
<li><i class="fa fa-fw fa-angle-right"></i>NEW keyboard shortcuts (beta)</li>
<li><i class="fa fa-fw fa-angle-right"></i>Added ~80 missing static WHs for "Shattered wormholes"</li>
<li><i class="fa fa-fw fa-angle-right"></i>New "<em><a target="_blank" href="https://github.com/exodus4d/pathfinder/issues/481#issue-223584078">Nearby</a></em>" map overlay shows active pilots within 3 jumps around your current location</li>
<li><i class="fa fa-fw fa-angle-right"></i>Switch to <em>CCPs</em> new <a target="_blank" href="https://community.eveonline.com/news/dev-blogs/introducing-esi/">ESI API</a>. Replaces CREST API.</li>
<li><i class="fa fa-fw fa-angle-right"></i>New UI options added. E.g. Remote open inGame information windows</li>
<li><i class="fa fa-fw fa-angle-right"></i>Added new filter option for "<em>Frigat wormholes</em>" to route finder</li>
<li><i class="fa fa-fw fa-angle-double-right"></i>Complete <a href="javascript:void(0)" class="pf-navbar-version-info">changelog</a></li>
</ul>
</div>

View File

@@ -4,8 +4,14 @@
{{#serviceStatus}}
<li><i class="fa-li fa fa-server " aria-hidden="true"></i><span class="txt-color {{ style }}">{{ eve }}</span></li>
{{/serviceStatus}}
{{#userCounts}}
<li><i class="fa-li fa fa-users" aria-hidden="true"></i>{{ userCounts }}</li>
{{/userCounts}}
{{#playerCount}}
<li><i class="fa-li fa fa-users" aria-hidden="true"></i>{{ playerCount }}</li>
{{/playerCount}}
{{#startTime}}
<li><i class="fa-li fa fa-clock-o" aria-hidden="true"></i>up {{ startTime }}</li>
{{/startTime}}
{{#serverVersion}}
<li><i class="fa-li fa fa-certificate" aria-hidden="true"></i>v. {{ serverVersion }}</li>
{{/serverVersion}}
</ul>
</div>

View File

@@ -53,7 +53,25 @@
{* Youtube verification code *}
<meta name="google-site-verification" content="sHoh0gfMw3x1wiwLTK5OsKsxt7kRgxi69hRgWEGh9DQ" />
<link rel="stylesheet" type="text/css" media="screen" href="{{ @BASE }}/public/css/pathfinder.css?{{ @PATHFINDER.VERSION }}">
{* Resources *}
<set pathCSS="{{ @BASE . '/public/css/pathfinder.css?' . @PATHFINDER.VERSION }}" />
<set pathJSApp="{{ @BASE . '/' . @pathJs . '/app' }}" />
<set pathJSRequire="{{ @BASE . '/' . @pathJs . '/lib/require.js' }}" />
<link rel="stylesheet" type="text/css" media="screen" href="{{@pathCSS}}">
{* Prefetch / Preload *}
<link rel="preload" href="{{@pathCSS}}" as="style">
<link rel="preload" href="{{@pathJSRequire}}" as="script">
<link rel="preload" href="{{@pathJSApp}}.js" as="script">
<link rel="preload" href="{{@pathJSApp}}/{{@jsView}}.js" as="script">
<link rel="dns-prefetch" href="https://login.eveonline.com">
<link rel="dns-prefetch" href="https://image.eveonline.com">
<link rel="dns-prefetch" href="https://i.ytimg.com">
<check if="{{ @jsView != 'mappage' }}">
<link rel="prefetch" href="{{@pathJSApp}}/mappage.js" as="script">
</check>
</head>
<body class="{{ @bodyClass }}" data-js-path="{{ @BASE }}/{{ @pathJs }}" data-script="{{ @jsView }}" data-version="{{ @PATHFINDER.VERSION }}">
@@ -61,7 +79,7 @@
<!-- Hey dude! Where is all the magic? -->
<script data-main="{{ @BASE }}/{{ @pathJs }}/app" src="{{ @BASE }}/{{ @pathJs }}/lib/require.js" ></script>
<script data-main="{{@pathJSApp}}" src="{{@pathJSRequire}}" ></script>
</body>
</html>

View File

@@ -905,7 +905,8 @@
Pathfinder requires cookies to maintain your login state between browser sessions. Read <a target="_blank" href="https://github.com/exodus4d/pathfinder/issues/138">more</a>.
</p>
<div class="pull-right">
<button type="button" class="btn btn-default navbar-btn btn-success" data-toggle="collapse" data-target="#pf-cookie-hint" aria-expanded="false">That´s fine</button>
<button type="button" class="btn btn-default navbar-btn btn-success" data-toggle="collapse" data-target="#pf-cookie-hint" aria-expanded="false">
<i class="fa fa-fw fa-check"></i>&nbsp;accept&nbsp;cookies</button>
</div>
</div>
</footer>

View File

@@ -987,6 +987,27 @@
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 pf-landing-pricing-panel">
{* Cookie *}
<div class="panel panel-default pricing-big">
<div class="panel-heading text-left">
<h3 class="panel-title">Cookies
<i class="fa fa-fw fa-question-circle pf-help-light" title="Force all users to re-login through SSO. "></i>
</h3>
</div>
<div class="panel-body no-padding">
<div class="btn-group btn-group-justified">
<span class="btn btn-default disabled btn-fake">Invalidate all Cookie data</span>
<a href="?invalidateCookies=1#pf-setup-administration" class="btn btn-warning" role="button">
<i class="fa fa-fw fa-times"></i> Clear authentication data
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>

View File

@@ -867,30 +867,23 @@ input[type="email"]{
overflow: hidden;
height: 18px;
background: $gray-lighter;
box-shadow: 0 1px 0 transparent, 0 0 0 1px lighten( $gray-light, 29%) inset;
-webkit-box-shadow: 0 1px 0 transparent, 0 0 0 1px lighten( $gray-light, 29%) inset;
-moz-box-shadow: 0 1px 0 transparent, 0 0 0 1px lighten( $gray-light, 29%) inset;
border-radius:$progressbar-radius;
-moz-border-radius:$progressbar-radius;
-webkit-border-radius:$progressbar-radius;
@include box-shadow( 0 1px 0 transparent, 0 0 0 1px lighten( $gray-light, 29%) inset);
@include border-radius($progressbar-radius);
}
.progress-bar {
float: left;
width: 0;
height: 100%;
font-size: 11px;
color: $white;
text-align: center;
background-color: $blue;
-webkit-box-shadow: inset 0 -1px 0 rgba(red($black), green($black), blue($black), 0.15);
box-shadow: inset 0 -1px 0 rgba(red($black), green($black), blue($black), 0.15);
font-weight:bold;
text-shadow: 0 -1px 0 rgba(red($black), green($black), blue($black), 0.25);
//background-image:url("#{$base-url}/overlay-pattern.png");
-webkit-transition: width 1.5s ease-in-out;
transition: width 1.5s ease-in-out;
float: left;
width: 0;
height: 100%;
font-size: 11px;
color: $white;
text-align: center;
background-color: $blue;
font-weight: bold;
text-shadow: 0 -1px 0 rgba(red($black), green($black), blue($black), 0.25);
//background-image:url("#{$base-url}/overlay-pattern.png");
@include box-shadow( inset 0 -1px 0 rgba(red($black), green($black), blue($black), 0.15));
@include transition(width 1s ease-in-out) ;
}
.progress-striped .progress-bar {

View File

@@ -552,7 +552,7 @@ $modal-header-border-color: #e5e5e5;
$modal-footer-border-color: $modal-header-border-color;
$modal-lg: 1100px;
$modal-md: 600px;
$modal-md: 700px;
$modal-sm: 300px;

View File

@@ -216,6 +216,19 @@ select:active, select:hover {
td{
&.pf-table-action-cell{
cursor: pointer;
// icon within <td> cell content that should be highlighted on hover
> .pf-table-action-icon-cell{
@extend .txt-color;
@extend .txt-color-gray;
@include transition( color 0.08s ease-out );
}
&:hover{
> .pf-table-action-icon-cell{
@extend .txt-color-orange;
}
}
}
&.pf-table-image-cell{

View File

@@ -69,23 +69,24 @@ $mapWidth: 2500px ;
position: absolute;
display: none; // triggered by js
z-index: 10000;
height: 36px;
right: 10px;
background: rgba($black, 0.25);
@include border-radius(5px);
&.pf-map-overlay-timer{
width: 36px;
bottom: 23px;
width: 36px;
height: 36px;
}
&.pf-map-overlay-info{
top: 8px;
height: 36px;
min-height: 36px;
min-width: 36px;
color: $gray-darker;
padding: 3px;
line-height: 26px;
min-height: 36px;
min-width: 36px;
i{
margin: 0; // overwrite default
@@ -115,6 +116,93 @@ $mapWidth: 2500px ;
}
}
}
&.pf-map-overlay-local{
top: 54px;
min-height: 80px;
width: 32px;
display: block;
will-change: width;
.pf-map-overlay-local-content {
margin-right: 36px;
padding: 5px 0 5px 5px;
overflow: hidden;
.pf-map-overlay-headline{
font-size: 12px;
font-family: $font-family-bold;
white-space: nowrap;
.badge{
margin-left: 5px;
}
.pf-system-sec{
cursor: default; // overwrite
}
}
.pf-local-table {
font-size: 10px;
td {
white-space: nowrap;
.pf-table-cell-ellipsis{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pf-table-cell-80{
width: 90px; // 100 + padding for "order" icons
}
.pf-table-cell-90{
width: 100px; // 100 + padding for "order" icons
}
}
}
.dataTables_paginate,
.dataTables_empty{
white-space: nowrap;
}
}
.pf-map-overlay-local-main{
position: absolute;
top: 0;
right: 0;
height: 100%;
padding: 3px;
width: 32px;
cursor: pointer;
text-align: center;
border-left: 1px solid $gray-darker;
.pf-map-overlay-local-trigger{
margin-bottom: 10px;
}
i{
font-size: 12px;
}
}
.pf-map-overlay-local-jumps{
position: absolute;
bottom: 5px;
width: calc(100% - 6px);
}
.badge{
font-family: Arial, sans-serif; // fix for element width on custom font family
background-color: $gray-darker;
}
}
}
// 20x20px grid background

View File

@@ -117,7 +117,7 @@ table.dataTable.order-column tbody tr > .sorting_2,
table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,
table.dataTable.display tbody tr > .sorting_2,
table.dataTable.display tbody tr > .sorting_3 {
background-color: #fafafa;
background-color: $gray-darker;
}
table.dataTable.order-column tbody tr.selected > .sorting_1,
table.dataTable.order-column tbody tr.selected > .sorting_2,
@@ -186,9 +186,13 @@ table.dataTable.no-footer {
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.compact thead th:not(.sorting_disabled),
table.dataTable.compact thead td:not(.sorting_disabled) {
padding: 4px 17px 4px 4px;
}
table.dataTable.compact thead th,
table.dataTable.compact thead td {
padding: 4px 17px 4px 4px;
padding: 4px 4px 4px 4px;
}
table.dataTable.compact tfoot th,
table.dataTable.compact tfoot td {