From 74faec37c9e28788275677834ac5b848774dffe0 Mon Sep 17 00:00:00 2001 From: Exodus4D Date: Wed, 14 Sep 2016 19:51:14 +0200 Subject: [PATCH] - added multi character support per user, #314 - improved client site storage (IndexedDB) setup --- app/main/controller/api/map.php | 2 +- app/main/controller/api/user.php | 84 ++++++++++++----- app/main/controller/ccp/sso.php | 32 +++++-- app/main/controller/controller.php | 133 +++++++++++++++++++++++---- app/main/model/characterlogmodel.php | 6 +- app/main/model/usermodel.php | 10 ++ js/app/map/util.js | 10 +- js/app/mappage.js | 10 +- js/app/page.js | 37 +++++--- js/app/util.js | 110 ++++++++++++++++------ public/js/v1.1.5/app/map/util.js | 10 +- public/js/v1.1.5/app/mappage.js | 10 +- public/js/v1.1.5/app/page.js | 37 +++++--- public/js/v1.1.5/app/util.js | 110 ++++++++++++++++------ 14 files changed, 439 insertions(+), 162 deletions(-) diff --git a/app/main/controller/api/map.php b/app/main/controller/api/map.php index c015a6a2..69d9b9d9 100644 --- a/app/main/controller/api/map.php +++ b/app/main/controller/api/map.php @@ -779,7 +779,7 @@ class Map extends Controller\AccessController { $systemPosX = 0; $systemPosY = 30; - $sourceSystemId = (int)$this->getF3()->get(User::SESSION_KEY_CHARACTER_PREV_SYSTEM_ID); + $sourceSystemId = (int)$this->getF3()->get(User::SESSION_KEY_CHARACTERS_PREV_SYSTEM_ID); $targetSystemId = (int)$log->systemId; if($sourceSystemId){ diff --git a/app/main/controller/api/user.php b/app/main/controller/api/user.php index 9d660320..2b4a3c97 100644 --- a/app/main/controller/api/user.php +++ b/app/main/controller/api/user.php @@ -25,14 +25,12 @@ class User extends Controller\Controller{ const SESSION_KEY_USER_NAME = 'SESSION.USER.NAME'; // character specific session keys - const SESSION_KEY_CHARACTER = 'SESSION.CHARACTER'; - const SESSION_KEY_CHARACTER_ID = 'SESSION.CHARACTER.ID'; - const SESSION_KEY_CHARACTER_NAME = 'SESSION.CHARACTER.NAME'; - const SESSION_KEY_CHARACTER_TIME = 'SESSION.CHARACTER.TIME'; - const SESSION_KEY_CHARACTER_PREV_SYSTEM_ID = 'SESSION.CHARACTER.PREV_SYSTEM_ID'; + const SESSION_KEY_CHARACTERS = 'SESSION.CHARACTERS'; + //const SESSION_KEY_CHARACTERS_PREV_SYSTEM_ID = 'SESSION.CHARACTERS.PREV_SYSTEM_ID'; + const SESSION_KEY_CHARACTERS_PREV_SYSTEM_ID = 'SESSION.TEST.PREV_SYSTEM_ID'; - const SESSION_KEY_CHARACTER_ACCESS_TOKEN = 'SESSION.CHARACTER.ACCESS_TOKEN'; - const SESSION_KEY_CHARACTER_REFRESH_TOKEN = 'SESSION.CHARACTER.REFRESH_TOKEN'; + // temp login character ID (during HTTP redirects on login) + const SESSION_KEY_TEMP_CHARACTER_ID = 'SESSION.TEMP_CHARACTER_ID'; // log text const LOG_LOGGED_IN = 'userId: [%10s], userName: [%30s], charId: [%20s], charName: %s'; @@ -45,6 +43,29 @@ class User extends Controller\Controller{ */ private static $captchaReason = [self::SESSION_CAPTCHA_ACCOUNT_UPDATE, self::SESSION_CAPTCHA_ACCOUNT_DELETE]; + /** + * merges two multidimensional characterSession arrays by checking characterID + * @param array $characterDataBase + * @param array $characterData + * @return array + */ + private function mergeSessionCharacterData($characterDataBase = [], $characterData = []){ + $addData = []; + foreach($characterDataBase as $i => $baseData){ + foreach($characterData as $data){ + if((int)$baseData['ID'] === (int)$data['ID']){ + // overwrite changes + $characterDataBase[$i]['NAME'] = $data['NAME']; + $characterDataBase[$i]['TIME'] = $data['TIME']; + }else{ + $addData[] = $data; + } + } + } + + return array_merge($characterDataBase, $addData); + } + /** * login a valid character * @param Model\CharacterModel $characterModel @@ -54,24 +75,43 @@ class User extends Controller\Controller{ $login = false; if($user = $characterModel->getUser()){ - // set user/character data to session ------------------- - $this->f3->set(self::SESSION_KEY_USER, [ - 'ID' => $user->_id, - 'NAME' => $user->name - ]); + // check if character belongs to current user + // -> If there is already a logged in user! (e.g. multi character use) + $currentUser = $this->getUser(); - $dateTime = new \DateTime(); - $this->f3->set(self::SESSION_KEY_CHARACTER, [ - 'ID' => $characterModel->_id, - 'NAME' => $characterModel->name, - 'TIME' => $dateTime->getTimestamp() - ]); + $sessionCharacters = [ + [ + 'ID' => $characterModel->_id, + 'NAME' => $characterModel->name, + 'TIME' => (new \DateTime())->getTimestamp() + ] + ]; - // save user login information --------------------------- + if( + is_null($currentUser) || + $currentUser->_id !== $user->_id + ){ + // user has changed OR new user --------------------------------------------------- + //-> set user/character data to session + $this->f3->set(self::SESSION_KEY_USER, [ + 'ID' => $user->_id, + 'NAME' => $user->name + ]); + }else{ + // user has NOT changed ----------------------------------------------------------- + // -> get current session characters + $currentSessionCharacters = (array)$this->f3->get(self::SESSION_KEY_CHARACTERS); + + $sessionCharacters = $this->mergeSessionCharacterData($sessionCharacters, $currentSessionCharacters); + } + + $this->f3->set(self::SESSION_KEY_CHARACTERS, $sessionCharacters); + + // save user login information -------------------------------------------------------- $characterModel->touch('lastLogin'); $characterModel->save(); - // write login log -------------------------------------- + // write login log -------------------------------------------------------------------- self::getLogger('LOGIN')->write( sprintf(self::LOG_LOGGED_IN, $user->_id, @@ -210,7 +250,7 @@ class User extends Controller\Controller{ if($activeCharacter = $this->getCharacter(0)){ $user = $activeCharacter->getUser(); - // captcha is send -> check captcha --------------------------------- + // captcha is send -> check captcha ------------------------------------------- if( isset($formData['captcha']) && !empty($formData['captcha']) @@ -250,7 +290,7 @@ class User extends Controller\Controller{ } } - // sharing config --------------------------------------------------- + // sharing config ------------------------------------------------------------- if(isset($formData['share'])){ $privateSharing = 0; $corporationSharing = 0; diff --git a/app/main/controller/ccp/sso.php b/app/main/controller/ccp/sso.php index 857729fa..fe23d3b3 100644 --- a/app/main/controller/ccp/sso.php +++ b/app/main/controller/ccp/sso.php @@ -115,6 +115,10 @@ class Sso extends Api\User{ if($loginCheck){ // set "login" cookie $this->setLoginCookie($character); + + // -> pass current character data to target page + $f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $character->_id); + // route to "map" $f3->reroute('@map'); } @@ -218,15 +222,18 @@ class Sso extends Api\User{ // -> update character log (current location,...) $characterModel = $characterModel->updateLog(); - // check if there is already an active user logged in - if($activeCharacter = $this->getCharacter()){ - // connect character with current user - $user = $activeCharacter->getUser(); - }elseif( is_null( $user = $characterModel->getUser()) ){ - // no user found (new character) -> create new user and connect to character - $user = Model\BasicModel::getNew('UserModel'); - $user->name = $characterModel->name; - $user->save(); + // connect character with current user + if( is_null($user = $this->getUser()) ){ + // connect character with existing user (no changes) + if( is_null( $user = $characterModel->getUser()) ){ + // no user found (new character) -> create new user and connect to character + /** + * @var $user Model\UserModel + */ + $user = Model\BasicModel::getNew('UserModel'); + $user->name = $characterModel->name; + $user->save(); + } } /** @@ -251,6 +258,9 @@ class Sso extends Api\User{ // set "login" cookie $this->setLoginCookie($characterModel); + // -> pass current character data to target page + $f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $characterModel->_id); + // route to "map" $f3->reroute('@map'); }else{ @@ -308,6 +318,10 @@ class Sso extends Api\User{ // login by character $loginCheck = $this->loginByCharacter($character); if($loginCheck){ + // set character id + // -> pass current character data to target page + $f3->set(Api\User::SESSION_KEY_TEMP_CHARACTER_ID, $character->_id); + // route to "map" $f3->reroute('@map'); } diff --git a/app/main/controller/controller.php b/app/main/controller/controller.php index 06b5cb9b..940ae319 100644 --- a/app/main/controller/controller.php +++ b/app/main/controller/controller.php @@ -318,17 +318,81 @@ class Controller { } /** - * checks whether a user is currently logged in + * get current character data from session + * @return array + */ + protected function getSessionCharacterData(){ + $data = []; + $currentSessionCharacters = (array)$this->getF3()->get(Api\User::SESSION_KEY_CHARACTERS); + $requestedCharacterId = 0; + + // get all characterData from currently active characters + if($this->getF3()->get('AJAX')){ + // Ajax request -> get characterId from Header (if already available!) + $header = $this->getRequestHeaders(); + $requestedCharacterId = (int)$header['Pf-Character']; + + if( + $requestedCharacterId > 0 && + (int)$this->getF3()->get(Api\User::SESSION_KEY_TEMP_CHARACTER_ID) === $requestedCharacterId + ){ + // characterId is available in Javascript + // -> clear temp characterId for next character login/switch + $this->getF3()->clear(Api\User::SESSION_KEY_TEMP_CHARACTER_ID); + } + } + + if($requestedCharacterId <= 0){ + // Ajax BUT characterID not yet set as HTTP header + // OR non Ajax -> get characterId from temp session (e.g. from HTTP redirect) + $requestedCharacterId = (int)$this->getF3()->get(Api\User::SESSION_KEY_TEMP_CHARACTER_ID); + } + + if($requestedCharacterId > 0){ + // search for session character data + foreach($currentSessionCharacters as $characterData){ + if($requestedCharacterId === (int)$characterData['ID']){ + $data = $characterData; + break; + } + } + }elseif( !empty($currentSessionCharacters) ){ + // no character was requested ($requestedCharacterId = 0) AND session characters were found + // -> get first matched character (e.g. user open browser tab) + $data = $currentSessionCharacters[0]; + } + + if( !empty($data) ){ + // check if character still exists on DB (e.g. was manually removed in the meantime) + // -> This should NEVER happen just for security and "local development" + $character = Model\BasicModel::getNew('CharacterModel'); + $character->getById( (int)$data['ID']); + + if( + $character->dry() || + !$character->hasUserCharacter() + ){ + // character data is invalid! + $data = []; + } + } + + return $data; + } + + /** + * checks whether a user/character is currently logged in * @param \Base $f3 * @return bool */ protected function checkLogTimer($f3){ $loginCheck = false; + $characterData = $this->getSessionCharacterData(); - if($f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) > 0){ + if( !empty($characterData) ){ // check logIn time $logInTime = new \DateTime(); - $logInTime->setTimestamp( $f3->get(Api\User::SESSION_KEY_CHARACTER_TIME) ); + $logInTime->setTimestamp( (int)$characterData['TIME'] ); $now = new \DateTime(); $timeDiff = $now->diff($logInTime); @@ -346,35 +410,66 @@ class Controller { } /** - * get current character model + * get current character * @param int $ttl * @return Model\CharacterModel|null * @throws \Exception */ public function getCharacter($ttl = 0){ $character = null; + $characterData = $this->getSessionCharacterData(); - if( $this->getF3()->exists(Api\User::SESSION_KEY_CHARACTER_ID) ){ - $characterId = (int)$this->getF3()->get(Api\User::SESSION_KEY_CHARACTER_ID); - if($characterId){ - /** - * @var $characterModel Model\CharacterModel - */ - $characterModel = Model\BasicModel::getNew('CharacterModel'); - $characterModel->getById($characterId, $ttl); + if( !empty($characterData) ){ + /** + * @var $characterModel Model\CharacterModel + */ + $characterModel = Model\BasicModel::getNew('CharacterModel'); + $characterModel->getById( (int)$characterData['ID'], $ttl); - if( - !$characterModel->dry() && - $characterModel->hasUserCharacter() - ){ - $character = &$characterModel; - } + if( + !$characterModel->dry() && + $characterModel->hasUserCharacter() + ){ + $character = &$characterModel; } } return $character; } + /** + * get current user + * @param int $ttl + * @return Model\UserModel|null + */ + public function getUser($ttl = 0){ + $user = null; + + if( $this->getF3()->exists(Api\User::SESSION_KEY_USER_ID) ){ + $userId = (int)$this->getF3()->get(Api\User::SESSION_KEY_USER_ID); + if($userId){ + /** + * @var $userModel Model\UserModel + */ + $userModel = Model\BasicModel::getNew('UserModel'); + $userModel->getById($userId, $ttl); + + if( + !$userModel->dry() && + $userModel->hasUserCharacters() + ){ + $user = &$userModel; + } + } + } + + return $user; + } + + public function getCharacterSessionData(){ + + } + /** * log out current character * @param \Base $f3 @@ -384,7 +479,7 @@ class Controller { // ---------------------------------------------------------- // delete server side cookie validation data - // for the current character as well + // for the active character if( $params['clearCookies'] === '1' && ( $activeCharacter = $this->getCharacter()) diff --git a/app/main/model/characterlogmodel.php b/app/main/model/characterlogmodel.php index 8cffef5f..781f8373 100644 --- a/app/main/model/characterlogmodel.php +++ b/app/main/model/characterlogmodel.php @@ -212,12 +212,12 @@ class CharacterLogModel extends BasicModel { ( $activeCharacter = $controller->getCharacter() ) && ( $activeCharacter->_id === $this->characterId->_id ) ){ - $prevSystemId = (int)$f3->get( User::SESSION_KEY_CHARACTER_PREV_SYSTEM_ID); + $prevSystemId = (int)$f3->get( User::SESSION_KEY_CHARACTERS_PREV_SYSTEM_ID); if($prevSystemId === 0){ - $f3->set( User::SESSION_KEY_CHARACTER_PREV_SYSTEM_ID, $systemId); + $f3->set( User::SESSION_KEY_CHARACTERS_PREV_SYSTEM_ID, $systemId); }else{ - $f3->set( User::SESSION_KEY_CHARACTER_PREV_SYSTEM_ID, (int)$this->systemId); + $f3->set( User::SESSION_KEY_CHARACTERS_PREV_SYSTEM_ID, (int)$this->systemId); } } } diff --git a/app/main/model/usermodel.php b/app/main/model/usermodel.php index 776c6b96..fe4a65e9 100644 --- a/app/main/model/usermodel.php +++ b/app/main/model/usermodel.php @@ -133,6 +133,16 @@ class UserModel extends BasicModel { } } + /** + * check whether this character has already a user assigned to it + * @return bool + */ + public function hasUserCharacters(){ + $this->filter('userCharacters', ['active = ?', 1]); + + return is_object($this->userCharacters); + } + /** * search for user by unique username * @param $name diff --git a/js/app/map/util.js b/js/app/map/util.js index 75648e6c..16512276 100644 --- a/js/app/map/util.js +++ b/js/app/map/util.js @@ -508,7 +508,7 @@ define([ var getLocaleData = function(type, objectId){ if(objectId > 0){ var storageKey = getLocalStoragePrefixByType(type) + objectId; - return Util.localforage.getItem(storageKey); + return Util.getLocalStorage().getItem(storageKey); }else{ console.warn('Local storage requires object id > 0'); } @@ -525,13 +525,13 @@ define([ if(objectId > 0){ // get current map config var storageKey = getLocalStoragePrefixByType(type) + objectId; - Util.localforage.getItem(storageKey).then(function(data) { + Util.getLocalStorage().getItem(storageKey).then(function(data) { // This code runs once the value has been loaded // from the offline store. data = (data === null) ? {} : data; // set/update value data[this.key] = this.value; - Util.localforage.setItem(this.storageKey, data); + Util.getLocalStorage().setItem(this.storageKey, data); }.bind({ key: key, value: value, @@ -555,13 +555,13 @@ define([ if(objectId > 0){ // get current map config var storageKey = getLocalStoragePrefixByType(type) + objectId; - Util.localforage.getItem(storageKey).then(function(data) { + Util.getLocalStorage().getItem(storageKey).then(function(data) { if( data && data.hasOwnProperty(key) ){ delete data[key]; - Util.localforage.setItem(this.storageKey, data); + Util.getLocalStorage().setItem(this.storageKey, data); } }.bind({ storageKey: storageKey diff --git a/js/app/mappage.js b/js/app/mappage.js index fcd959ab..a94e810e 100644 --- a/js/app/mappage.js +++ b/js/app/mappage.js @@ -19,7 +19,10 @@ define([ * main init "map" page */ $(function(){ - // set Dialog default config + // set default AJAX config + Util.ajaxSetup(); + + // set default dialog config Util.initDefaultBootboxConfig(); // load page @@ -28,11 +31,6 @@ define([ // show app information in browser console Util.showVersionInfo(); - // init local storage - Util.localforage.config({ - name: 'Pathfinder local storage' - }); - // init logging Logging.init(); diff --git a/js/app/page.js b/js/app/page.js index d9337789..6bf75516 100644 --- a/js/app/page.js +++ b/js/app/page.js @@ -573,10 +573,11 @@ define([ $(document).on('pf:menuLogout', function(e, data){ var clearCookies = false; - if( typeof data === 'object' ){ - if( data.hasOwnProperty('clearCookies') ){ - clearCookies = data.clearCookies; - } + if( + typeof data === 'object' && + data.hasOwnProperty('clearCookies') + ){ + clearCookies = data.clearCookies; } // logout @@ -674,6 +675,7 @@ define([ var userInfoElement = $('.' + config.headUserCharacterClass); var currentCharacterId = userInfoElement.data('characterId'); + var currentCharactersOptionIds = userInfoElement.data('characterOptionIds') ? userInfoElement.data('characterOptionIds') : []; var newCharacterId = 0; var newCharacterName = ''; @@ -691,7 +693,6 @@ define([ visibility : 'hidden', duration: 500, complete: function(){ - // callback callback(); @@ -727,25 +728,31 @@ define([ } } - // update user character data --------------------------------------------------- - if(currentCharacterId !== newCharacterId){ + var newCharactersOptionIds = userData.characters.map(function(data){ + return data.id; + }); - var showCharacterElement = true; - if(newCharacterId === 0){ - showCharacterElement = false; + // update user character data --------------------------------------------------- + if(currentCharactersOptionIds.toString() !== newCharactersOptionIds.toString()){ + + var currentCharacterChanged = false; + if(currentCharacterId !== newCharacterId){ + currentCharacterChanged = true; } // toggle element animateHeaderElement(userInfoElement, function(){ - userInfoElement.find('span').text( newCharacterName ); - userInfoElement.find('img').attr('src', Init.url.ccpImageServer + 'Character/' + newCharacterId + '_32.jpg' ); - + if(currentCharacterChanged){ + userInfoElement.find('span').text( newCharacterName ); + userInfoElement.find('img').attr('src', Init.url.ccpImageServer + 'Character/' + newCharacterId + '_32.jpg' ); + } // init "character switch" popover userInfoElement.initCharacterSwitchPopover(userData); - }, showCharacterElement); + }, true); - // set new id for next check + // store new id(s) for next check userInfoElement.data('characterId', newCharacterId); + userInfoElement.data('characterOptionIds', newCharactersOptionIds); } // update user ship data -------------------------------------------------------- diff --git a/js/app/util.js b/js/app/util.js index 115ceac6..a003fa09 100644 --- a/js/app/util.js +++ b/js/app/util.js @@ -64,6 +64,8 @@ define([ var animationTimerCache = {}; // cache for table row animation timeout + var localStorage; // cache for "localForage" singleton + /* * =========================================================================================================== * Global jQuery plugins for some common and frequently used functions @@ -589,40 +591,48 @@ define([ return elements.each(function() { var element = $(this); - // destroy "popover" and remove "click" event for animation - element.popover('destroy').off(); - - // init popover and add specific class to it (for styling) - element.popover({ - html: true, - title: 'select character', - trigger: 'click', - placement: 'bottom', - container: 'body', - content: content, - animation: false - }).data('bs.popover').tip().addClass('pf-popover'); + // check if tooltip already exists -> remove it + if(element.data('bs.popover') !== undefined){ + element.off('click').popover('destroy'); + } element.on('click', function(e) { e.preventDefault(); + e.stopPropagation(); + var easeEffect = $(this).attr('data-easein'); - var popover = $(this).data('bs.popover').tip(); + var popoverData = $(this).data('bs.popover'); + var popoverElement = null; + var velocityOptions = { duration: Init.animationSpeed.dialogEvents }; - switch(easeEffect){ - case 'shake': - case 'pulse': - case 'tada': - case 'flash': - case 'bounce': - case 'swing': - popover.velocity('callout.' + easeEffect, velocityOptions); - break; - default: - popover.velocity('transition.' + easeEffect, velocityOptions); - break; + if(popoverData === undefined){ + + // init popover and add specific class to it (for styling) + $(this).popover({ + html: true, + title: 'select character', + trigger: 'manual', + placement: 'bottom', + container: 'body', + content: content, + animation: false + }).data('bs.popover').tip().addClass('pf-popover'); + + $(this).popover('show'); + + popoverElement = $(this).data('bs.popover').tip(); + popoverElement.velocity('transition.' + easeEffect, velocityOptions); + }else{ + popoverElement = $(this).data('bs.popover').tip(); + if(popoverElement.is(':visible')){ + popoverElement.velocity('reverse'); + }else{ + $(this).popover('show'); + popoverElement.velocity('transition.' + easeEffect, velocityOptions); + } } }); @@ -650,7 +660,14 @@ define([ popoverElement.has(e.target).length === 0 && $('.popover').has(e.target).length === 0 ){ - popoverElement.popover('hide'); + var popover = popoverElement.data('bs.popover'); + + if( + popover !== undefined && + popover.tip().is(':visible') + ){ + popoverElement.popover('hide'); + } } }); }); @@ -1014,6 +1031,28 @@ define([ return logInfo; }; + /** + * set default jQuery AJAX configuration + */ + var ajaxSetup = function(){ + $.ajaxSetup({ + beforeSend: function(xhr) { + // add current character data to ANY XHR request (HTTP HEADER) + // -> This helps to identify multiple characters on multiple browser tabs + var userData = getCurrentUserData(); + var currentCharacterId = 0; + if( + userData && + userData.character + ){ + currentCharacterId = parseInt( userData.character.id ); + } + + xhr.setRequestHeader('Pf-Character', currentCharacterId); + } + }); + }; + /** * Returns true if the user hit Esc or navigated away from the * current page before an AJAX call was done. (The response @@ -1665,6 +1704,20 @@ define([ return price + ' ISK'; }; + /** + * get localForage instance (singleton) for offline client site storage + * @returns {localforage} + */ + var getLocalStorage = function(){ + if(localStorage === undefined){ + localStorage = localforage.createInstance({ + driver: localforage.INDEXEDDB, + name: 'Pathfinder local storage' + }); + } + return localStorage; + }; + /** * Create Date as UTC * @param date @@ -1756,7 +1809,6 @@ define([ return { config: config, - localforage: localforage, showVersionInfo: showVersionInfo, initDefaultBootboxConfig: initDefaultBootboxConfig, getCurrentTriggerDelay: getCurrentTriggerDelay, @@ -1769,6 +1821,7 @@ define([ showNotify: showNotify, stopTabBlink: stopTabBlink, getLogInfo: getLogInfo, + ajaxSetup: ajaxSetup, isXHRAborted: isXHRAborted, getMapModule: getMapModule, getSystemEffectData: getSystemEffectData, @@ -1797,6 +1850,7 @@ define([ convertDateToString: convertDateToString, getOpenDialogs: getOpenDialogs, formatPrice: formatPrice, + getLocalStorage: getLocalStorage, getDocumentPath: getDocumentPath, redirect: redirect, logout: logout diff --git a/public/js/v1.1.5/app/map/util.js b/public/js/v1.1.5/app/map/util.js index 75648e6c..16512276 100644 --- a/public/js/v1.1.5/app/map/util.js +++ b/public/js/v1.1.5/app/map/util.js @@ -508,7 +508,7 @@ define([ var getLocaleData = function(type, objectId){ if(objectId > 0){ var storageKey = getLocalStoragePrefixByType(type) + objectId; - return Util.localforage.getItem(storageKey); + return Util.getLocalStorage().getItem(storageKey); }else{ console.warn('Local storage requires object id > 0'); } @@ -525,13 +525,13 @@ define([ if(objectId > 0){ // get current map config var storageKey = getLocalStoragePrefixByType(type) + objectId; - Util.localforage.getItem(storageKey).then(function(data) { + Util.getLocalStorage().getItem(storageKey).then(function(data) { // This code runs once the value has been loaded // from the offline store. data = (data === null) ? {} : data; // set/update value data[this.key] = this.value; - Util.localforage.setItem(this.storageKey, data); + Util.getLocalStorage().setItem(this.storageKey, data); }.bind({ key: key, value: value, @@ -555,13 +555,13 @@ define([ if(objectId > 0){ // get current map config var storageKey = getLocalStoragePrefixByType(type) + objectId; - Util.localforage.getItem(storageKey).then(function(data) { + Util.getLocalStorage().getItem(storageKey).then(function(data) { if( data && data.hasOwnProperty(key) ){ delete data[key]; - Util.localforage.setItem(this.storageKey, data); + Util.getLocalStorage().setItem(this.storageKey, data); } }.bind({ storageKey: storageKey diff --git a/public/js/v1.1.5/app/mappage.js b/public/js/v1.1.5/app/mappage.js index fcd959ab..a94e810e 100644 --- a/public/js/v1.1.5/app/mappage.js +++ b/public/js/v1.1.5/app/mappage.js @@ -19,7 +19,10 @@ define([ * main init "map" page */ $(function(){ - // set Dialog default config + // set default AJAX config + Util.ajaxSetup(); + + // set default dialog config Util.initDefaultBootboxConfig(); // load page @@ -28,11 +31,6 @@ define([ // show app information in browser console Util.showVersionInfo(); - // init local storage - Util.localforage.config({ - name: 'Pathfinder local storage' - }); - // init logging Logging.init(); diff --git a/public/js/v1.1.5/app/page.js b/public/js/v1.1.5/app/page.js index d9337789..6bf75516 100644 --- a/public/js/v1.1.5/app/page.js +++ b/public/js/v1.1.5/app/page.js @@ -573,10 +573,11 @@ define([ $(document).on('pf:menuLogout', function(e, data){ var clearCookies = false; - if( typeof data === 'object' ){ - if( data.hasOwnProperty('clearCookies') ){ - clearCookies = data.clearCookies; - } + if( + typeof data === 'object' && + data.hasOwnProperty('clearCookies') + ){ + clearCookies = data.clearCookies; } // logout @@ -674,6 +675,7 @@ define([ var userInfoElement = $('.' + config.headUserCharacterClass); var currentCharacterId = userInfoElement.data('characterId'); + var currentCharactersOptionIds = userInfoElement.data('characterOptionIds') ? userInfoElement.data('characterOptionIds') : []; var newCharacterId = 0; var newCharacterName = ''; @@ -691,7 +693,6 @@ define([ visibility : 'hidden', duration: 500, complete: function(){ - // callback callback(); @@ -727,25 +728,31 @@ define([ } } - // update user character data --------------------------------------------------- - if(currentCharacterId !== newCharacterId){ + var newCharactersOptionIds = userData.characters.map(function(data){ + return data.id; + }); - var showCharacterElement = true; - if(newCharacterId === 0){ - showCharacterElement = false; + // update user character data --------------------------------------------------- + if(currentCharactersOptionIds.toString() !== newCharactersOptionIds.toString()){ + + var currentCharacterChanged = false; + if(currentCharacterId !== newCharacterId){ + currentCharacterChanged = true; } // toggle element animateHeaderElement(userInfoElement, function(){ - userInfoElement.find('span').text( newCharacterName ); - userInfoElement.find('img').attr('src', Init.url.ccpImageServer + 'Character/' + newCharacterId + '_32.jpg' ); - + if(currentCharacterChanged){ + userInfoElement.find('span').text( newCharacterName ); + userInfoElement.find('img').attr('src', Init.url.ccpImageServer + 'Character/' + newCharacterId + '_32.jpg' ); + } // init "character switch" popover userInfoElement.initCharacterSwitchPopover(userData); - }, showCharacterElement); + }, true); - // set new id for next check + // store new id(s) for next check userInfoElement.data('characterId', newCharacterId); + userInfoElement.data('characterOptionIds', newCharactersOptionIds); } // update user ship data -------------------------------------------------------- diff --git a/public/js/v1.1.5/app/util.js b/public/js/v1.1.5/app/util.js index 115ceac6..a003fa09 100644 --- a/public/js/v1.1.5/app/util.js +++ b/public/js/v1.1.5/app/util.js @@ -64,6 +64,8 @@ define([ var animationTimerCache = {}; // cache for table row animation timeout + var localStorage; // cache for "localForage" singleton + /* * =========================================================================================================== * Global jQuery plugins for some common and frequently used functions @@ -589,40 +591,48 @@ define([ return elements.each(function() { var element = $(this); - // destroy "popover" and remove "click" event for animation - element.popover('destroy').off(); - - // init popover and add specific class to it (for styling) - element.popover({ - html: true, - title: 'select character', - trigger: 'click', - placement: 'bottom', - container: 'body', - content: content, - animation: false - }).data('bs.popover').tip().addClass('pf-popover'); + // check if tooltip already exists -> remove it + if(element.data('bs.popover') !== undefined){ + element.off('click').popover('destroy'); + } element.on('click', function(e) { e.preventDefault(); + e.stopPropagation(); + var easeEffect = $(this).attr('data-easein'); - var popover = $(this).data('bs.popover').tip(); + var popoverData = $(this).data('bs.popover'); + var popoverElement = null; + var velocityOptions = { duration: Init.animationSpeed.dialogEvents }; - switch(easeEffect){ - case 'shake': - case 'pulse': - case 'tada': - case 'flash': - case 'bounce': - case 'swing': - popover.velocity('callout.' + easeEffect, velocityOptions); - break; - default: - popover.velocity('transition.' + easeEffect, velocityOptions); - break; + if(popoverData === undefined){ + + // init popover and add specific class to it (for styling) + $(this).popover({ + html: true, + title: 'select character', + trigger: 'manual', + placement: 'bottom', + container: 'body', + content: content, + animation: false + }).data('bs.popover').tip().addClass('pf-popover'); + + $(this).popover('show'); + + popoverElement = $(this).data('bs.popover').tip(); + popoverElement.velocity('transition.' + easeEffect, velocityOptions); + }else{ + popoverElement = $(this).data('bs.popover').tip(); + if(popoverElement.is(':visible')){ + popoverElement.velocity('reverse'); + }else{ + $(this).popover('show'); + popoverElement.velocity('transition.' + easeEffect, velocityOptions); + } } }); @@ -650,7 +660,14 @@ define([ popoverElement.has(e.target).length === 0 && $('.popover').has(e.target).length === 0 ){ - popoverElement.popover('hide'); + var popover = popoverElement.data('bs.popover'); + + if( + popover !== undefined && + popover.tip().is(':visible') + ){ + popoverElement.popover('hide'); + } } }); }); @@ -1014,6 +1031,28 @@ define([ return logInfo; }; + /** + * set default jQuery AJAX configuration + */ + var ajaxSetup = function(){ + $.ajaxSetup({ + beforeSend: function(xhr) { + // add current character data to ANY XHR request (HTTP HEADER) + // -> This helps to identify multiple characters on multiple browser tabs + var userData = getCurrentUserData(); + var currentCharacterId = 0; + if( + userData && + userData.character + ){ + currentCharacterId = parseInt( userData.character.id ); + } + + xhr.setRequestHeader('Pf-Character', currentCharacterId); + } + }); + }; + /** * Returns true if the user hit Esc or navigated away from the * current page before an AJAX call was done. (The response @@ -1665,6 +1704,20 @@ define([ return price + ' ISK'; }; + /** + * get localForage instance (singleton) for offline client site storage + * @returns {localforage} + */ + var getLocalStorage = function(){ + if(localStorage === undefined){ + localStorage = localforage.createInstance({ + driver: localforage.INDEXEDDB, + name: 'Pathfinder local storage' + }); + } + return localStorage; + }; + /** * Create Date as UTC * @param date @@ -1756,7 +1809,6 @@ define([ return { config: config, - localforage: localforage, showVersionInfo: showVersionInfo, initDefaultBootboxConfig: initDefaultBootboxConfig, getCurrentTriggerDelay: getCurrentTriggerDelay, @@ -1769,6 +1821,7 @@ define([ showNotify: showNotify, stopTabBlink: stopTabBlink, getLogInfo: getLogInfo, + ajaxSetup: ajaxSetup, isXHRAborted: isXHRAborted, getMapModule: getMapModule, getSystemEffectData: getSystemEffectData, @@ -1797,6 +1850,7 @@ define([ convertDateToString: convertDateToString, getOpenDialogs: getOpenDialogs, formatPrice: formatPrice, + getLocalStorage: getLocalStorage, getDocumentPath: getDocumentPath, redirect: redirect, logout: logout