/** * Util */ define([ 'jquery', 'app/init', 'conf/system_effect', 'conf/signature_type', 'bootbox', 'localForage', 'velocity', 'velocityUI', 'customScrollbar', 'validator', 'easyPieChart', 'hoverIntent', 'bootstrapConfirmation', 'bootstrapToggle', 'select2' ], ($, Init, SystemEffect, SignatureType, bootbox, localforage) => { 'use strict'; let config = { ajaxOverlayClass: 'pf-loading-overlay', ajaxOverlayWrapperClass: 'pf-loading-overlay-wrapper', // form formEditableFieldClass: 'pf-editable', // class for all xEditable fields formErrorContainerClass: 'pf-dialog-error-container', // class for "error" containers in dialogs formWarningContainerClass: 'pf-dialog-warning-container', // class for "warning" containers in dialogs formInfoContainerClass: 'pf-dialog-info-container', // class for "info" containers in dialogs // head headMapTrackingId: 'pf-head-map-tracking', // id for "map tracking" toggle (checkbox) headCurrentLocationId: 'pf-head-current-location', // id for "show current location" element // menu menuButtonFullScreenId: 'pf-menu-button-fullscreen', // id for menu button "fullScreen" menuButtonMagnetizerId: 'pf-menu-button-magnetizer', // id for menu button "magnetizer" menuButtonGridId: 'pf-menu-button-grid', // id for menu button "grid snap" menuButtonEndpointId: 'pf-menu-button-endpoint', // id for menu button "endpoint" overlays menuButtonCompactId: 'pf-menu-button-compact', // id for menu button "compact" UI map view menuButtonMapDeleteId: 'pf-menu-button-map-delete', // id for menu button "delete map" settingsMessageVelocityOptions: { duration: 180 }, // dialogs dialogClass: 'modal-dialog', // class for all dialogs (bootstrap) // 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 // util userStatusClass: 'pf-user-status', // class for player status // select2 select2Class: 'pf-select2', // class for all "Select2" field. This is bad! // we want to keep the focus where it is (e.g. signature table cell) // the only way to prevent this is to remove the element // https://stackoverflow.com/questions/17995057/prevent-select2-from-autmatically-focussing-its-search-input-when-dropdown-is-op $(this).parents('.editableform').find(this).next().find('.select2-selection').remove(); }); }; /** * set default configuration for "xEditable" */ let initDefaultEditableConfig = () => { // use fontAwesome buttons template $.fn.editableform.buttons = ''+ ''; // loading spinner template $.fn.editableform.loading = '
'; }; /** * get the current main trigger delay for the main trigger functions * optional in/decrease the delay * @param updateKey * @param value * @returns {*} */ let getCurrentTriggerDelay = (updateKey, value ) => { // make sure the delay timer is valid! // if this is called for the first time -> set CURRENT_DELAY if( Init.timer[updateKey].CURRENT_DELAY === undefined || Init.timer[updateKey].CURRENT_DELAY <= 0 ){ Init.timer[updateKey].CURRENT_DELAY = Init.timer[updateKey].DELAY; } // in/decrease the trigger delay if( value === parseInt(value, 10) && ( Init.timer[updateKey].CURRENT_DELAY ) + value > 0 ){ Init.timer[updateKey].CURRENT_DELAY += value; } return Init.timer[updateKey].CURRENT_DELAY; }; /** * get date obj with current EVE Server Time. * @returns {Date} */ let getServerTime = () => { // Server is running with GMT/UTC (EVE Time) let localDate = new Date(); let serverDate= new Date( localDate.getUTCFullYear(), localDate.getUTCMonth(), localDate.getUTCDate(), localDate.getUTCHours(), localDate.getUTCMinutes(), localDate.getUTCSeconds() ); return serverDate; }; /** * convert timestamp to server time * @param timestamp * @returns {Date} */ let convertTimestampToServerTime = timestamp => { let currentTimeZoneOffsetInMinutes = new Date().getTimezoneOffset(); return new Date( (timestamp + (currentTimeZoneOffsetInMinutes * 60)) * 1000); }; /** * get date difference as time parts (days, hours, minutes, seconds) * @param date1 * @param date2 * @returns {{}} */ let getTimeDiffParts = (date1, date2) => { let parts = {}; let time1 = date1.getTime(); let time2 = date2.getTime(); let diff = 0; if( time1 >= 0 && time2 >= 0 ){ diff = (date2.getTime() - date1.getTime()) / 1000; } diff = Math.abs(Math.floor(diff)); parts.days = Math.floor(diff/(24*60*60)); let leftSec = diff - parts.days * 24*60*60; parts.hours = Math.floor(leftSec/(60*60)); leftSec = leftSec - parts.hours * 60*60; parts.min = Math.floor(leftSec/(60)); parts.sec = leftSec - parts.min * 60; return parts; }; /** * start time measurement by a unique string identifier * @param timerName */ let timeStart = timerName => { if(typeof performance === 'object'){ stopTimerCache[timerName] = performance.now(); }else{ stopTimerCache[timerName] = new Date().getTime(); } }; /** * get time delta between timeStart() and timeStop() by a unique string identifier * @param timerName * @returns {number} */ let timeStop = timerName => { let duration = 0; if( stopTimerCache.hasOwnProperty(timerName) ){ // check browser support for performance API let timeNow = 0; if(typeof performance === 'object'){ timeNow = performance.now(); }else{ timeNow = new Date(); } // format ms time duration duration = Number( (timeNow - stopTimerCache[timerName] ).toFixed(2) ); // delete key delete( stopTimerCache[timerName]); } return duration; }; /** * update a character counter field with current value length - maxCharLength * @param field * @param charCounterElement * @param maxCharLength */ let updateCounter = (field, charCounterElement, maxCharLength) => { let value = field.val(); let inputLength = value.length; // line breaks are 2 characters! let newLines = value.match(/(\r\n|\n|\r)/g); let addition = 0; if(newLines != null){ addition = newLines.length; } inputLength += addition; charCounterElement.text(maxCharLength - inputLength); if(maxCharLength <= inputLength){ charCounterElement.toggleClass('txt-color-red', true); }else{ charCounterElement.toggleClass('txt-color-red', false); } }; /** * trigger main logging event with log information * @param logKey * @param options */ let log = (logKey, options) => { $(window).trigger('pf:log', [logKey, options]); }; /** * trigger a notification (on screen or desktop) * @param customConfig * @param desktop */ let showNotify = (customConfig, desktop) => { requirejs(['notification'], Notification => { Notification.showNotify(customConfig, desktop); }); }; /** * stop browser tab title "blinking" */ let stopTabBlink = function(){ requirejs(['notification'], function(Notification){ Notification.stopTabBlink(); }); }; /** * get log entry info * @param logType * @param option * @returns {string} */ let getLogInfo = (logType, option) => { let logInfo = ''; if(Init.classes.logTypes.hasOwnProperty(logType)){ logInfo = Init.classes.logTypes[logType][option]; } return logInfo; }; /** * set currentUserData as "global" variable * this function should be called continuously after data change * to keep the data always up2data * @param userData */ let setCurrentUserData = (userData) => { Init.currentUserData = userData; // check if function is available // this is not the case in "login" page if( $.fn.updateHeaderUserData ){ $.fn.updateHeaderUserData(); } return getCurrentUserData(); }; /** * get currentUserData from "global" variable * @returns {*} */ let getCurrentUserData = () => { return Init.currentUserData; }; /** * get either active characterID or characterId from initial page load * @returns {number} */ let getCurrentCharacterId = () => { let userData = getCurrentUserData(); let currentCharacterId = 0; if( userData && userData.character ){ currentCharacterId = parseInt( userData.character.id ); } if(!currentCharacterId){ // no active character... -> get default characterId from initial page load currentCharacterId = parseInt(document.body.getAttribute('data-character-id')); } return currentCharacterId; }; /** * get a unique ID for each tab * -> store ID in session storage */ let getBrowserTabId = () => { let key = 'tabId'; let tabId = sessionStorage.getItem(key); if(tabId === null){ tabId = Math.random().toString(36).substr(2, 5); sessionStorage.setItem(key, tabId); } return tabId; }; /** * set default jQuery AJAX configuration */ let ajaxSetup = () => { $.ajaxSetup({ beforeSend: function(jqXHR, settings){ // store request URL for later use (e.g. in error messages) jqXHR.url = location.protocol + '//' + location.host + settings.url; // Add custom application headers on "same origin" requests only! // -> Otherwise a "preflight" request is made, which will "probably" fail if(settings.crossDomain === false){ // add current character data to ANY XHR request (HTTP HEADER) // -> This helps to identify multiple characters on multiple browser tabs jqXHR.setRequestHeader('Pf-Character', getCurrentCharacterId()); } } }); }; /** * get WebSocket readyState description from ID * https://developer.mozilla.org/de/docs/Web/API/WebSocket * @param readyState * @returns {string} */ let getWebSocketDescriptionByReadyState = (readyState) => { let description = ''; switch(readyState){ case 0: description = 'connecting'; break; case 1: description = 'open'; break; case 2: description = 'closing'; break; case 3: description = 'closed'; break; } return description; }; /** * set sync status for map updates * -> if SharedWorker AND WebSocket connected -> status = "WebSocket" * -> else -> status = "ajax" (long polling) * @param type * @param options */ let setSyncStatus = (type, options) => { // current syncStatus let syncStatus = Init.syncStatus; switch(type){ case 'ws:open': // WebSocket open syncStatus.webSocket.status = getWebSocketDescriptionByReadyState(options.readyState); syncStatus.webSocket.class = 'txt-color-success'; syncStatus.webSocket.timestamp = new Date().getTime() / 1000; syncStatus.type = 'webSocket'; setSyncStatus('ajax:disable'); $(window).trigger('pf:syncStatus'); break; case 'ws:get': // WebSocket data pushed from server syncStatus.webSocket.timestamp = new Date().getTime() / 1000; $(window).trigger('pf:syncStatus'); break; case 'ws:closed': // WebSocket closed syncStatus.webSocket.status = getWebSocketDescriptionByReadyState(options.readyState); syncStatus.webSocket.class = 'txt-color-danger'; syncStatus.webSocket.timestamp = undefined; setSyncStatus('ajax:enable'); break; case 'ws:error': // WebSocket error syncStatus.webSocket.status = getWebSocketDescriptionByReadyState(options.readyState); syncStatus.webSocket.class = 'txt-color-danger'; setSyncStatus('ajax:enable'); break; case 'sw:init': // SharedWorker initialized syncStatus.sharedWorker.status = 'online'; syncStatus.sharedWorker.class = 'txt-color-success'; break; case 'sw:error': // SharedWorker error syncStatus.sharedWorker.status = 'offline'; syncStatus.sharedWorker.class = 'txt-color-danger'; setSyncStatus('ajax:enable'); break; case 'ajax:enable': // Ajax enabled (WebSocket error/not connected) syncStatus.ajax.status = 'enabled'; syncStatus.ajax.class = 'txt-color-success'; syncStatus.ajax.timestamp = new Date().getTime() / 1000; syncStatus.type = 'ajax'; $(window).trigger('pf:syncStatus'); break; case 'ajax:get': // Ajax data pulled from client syncStatus.ajax.timestamp = new Date().getTime() / 1000; $(window).trigger('pf:syncStatus'); break; case 'ajax:disable': // Ajax disabled (WebSocket open/ready) syncStatus.ajax.status = 'disabled'; syncStatus.ajax.class = 'txt-color-warning'; break; } }; /** * get current sync type for map updates * -> "ajax" or "webSocket" * @returns {string} */ let getSyncType = () => { return Init.syncStatus.type; }; /** * Returns true if the user hit Esc or navigated away from the * current page before an AJAX call was done. (The response * headers will be null or empty, depending on the browser.) * * NOTE: this function is only meaningful when called from * inside an AJAX "error" callback! * @param jqXHR XMLHttpRequest instance * @returns {boolean} */ let isXHRAborted = (jqXHR) => { return !jqXHR.getAllResponseHeaders(); }; /** * get label element for role data * @param role * @returns {*|jQuery|HTMLElement} */ let getLabelByRole = (role) => { return $('', { class: ['label', 'label-' + role.style].join(' '), text: role.label }); }; /** * get all mapTabElements ( tags) * or search for a specific tabElement within mapModule * @param mapId * @returns {JQuery|*|{}|T} */ $.fn.getMapTabElements = function(mapId){ let mapModule = $(this); let mapTabElements = mapModule.find('#' + config.mapTabBarId).find('a'); if(mapId){ // search for a specific tab element mapTabElements = mapTabElements.filter(function(i, el){ return ( $(el).data('mapId') === mapId ); }); } 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} */ let getMapModule = () => { let mapModule = $('#' + config.mapModuleId); if(mapModule.length === 0){ mapModule = $('
', { id: config.mapModuleId }); } return mapModule; }; /** * get areaId by security string * areaId is required as a key for signature names * if areaId is 0, no signature data is available for this system * @param security * @returns {number} */ let getAreaIdBySecurity = security => { let areaId = 0; switch(security){ case 'H': areaId = 10; break; case 'L': areaId = 11; break; case '0.0': areaId = 12; break; case 'SH': case 'C13': areaId = 13; break; default: // w-space for(let i = 1; i <= 6; i++){ if(security === 'C' + i){ areaId = i; break; } } break; } return areaId; }; /** * get system effect data by system security and system class * if no search parameters given -> get all effect data * @param security * @param effect * @returns {boolean} */ let getSystemEffectData = (security, effect) => { let data = SystemEffect; if(security){ // look for specific data data = false; let areaId = getAreaIdBySecurity(security); if( areaId > 0 && SystemEffect.wh[effect] && SystemEffect.wh[effect][areaId] ){ data = SystemEffect.wh[effect][areaId]; } } return data; }; /** * get status info for a character for a given status * @param characterData * @param option * @returns {string} */ let getStatusInfoForCharacter = (characterData, option) => { let statusInfo = ''; // character status can not be checked if there are no reference data // e.g. during registration process (login page) if(Init.characterStatus){ // get info for current "main" character let corporationId = getCurrentUserInfo('corporationId'); let allianceId = getCurrentUserInfo('allianceId'); // get all user characters let userData = getCurrentUserData(); if(userData){ // check if character is one of his own characters let userCharactersData = userData.characters; for(let i = 0; i < userCharactersData.length; i++){ if(userCharactersData[i].id === characterData.id){ statusInfo = Init.characterStatus.own[option]; break; } } } if(statusInfo === ''){ // compare current user data with given user data if( characterData.corporation && characterData.corporation.id === corporationId ){ statusInfo = Init.characterStatus.corporation[option]; }else if( characterData.alliance && characterData.alliance.id === allianceId ){ statusInfo = Init.characterStatus.alliance[option]; } } } return statusInfo; }; /** * get a HTML table with system effect information * e.g. for popover * @param data * @returns {string} */ let getSystemEffectTable = effects => { let table = ''; if(effects.length > 0){ table += ''; for(let effect of effects){ table += ''; table += ''; table += ''; table += ''; } table += '
'; table += effect.effect; table += ''; table += effect.value; table += '
'; } return table; }; /** * get a HTML table with planet names * e.g. for popover * @param planets * @returns {string} */ let getSystemPlanetsTable = planets => { let table = ''; if(planets.length > 0){ table += ''; for(let planet of planets){ table += ''; table += ''; table += ''; table += ''; } table += '
'; table += planet.name; table += ''; table += planet.type.name; table += '
'; } return table; }; /** * get a HTML table with pilots/ship names * @param users * @returns {string} */ let getSystemPilotsTable = users => { let table = ''; if(users.length > 0){ let getRow = (statusClass, userName, shipName, shipTypeName, mass) => { let row = ''; row += ''; row += ''; row += statusClass !== null ? '' : ''; row += ''; row += ''; row += ''; row += userName; row += ''; row += ''; row += shipName; row += ''; row += ''; row += shipTypeName; row += ''; row += ''; row += mass; row += ''; row += ''; return row; }; let massAll = 0; table += ''; for(let user of users){ massAll += parseInt(user.log.ship.mass); let statusClass = getStatusInfoForCharacter(user, 'class'); let mass = formatMassValue(user.log.ship.mass); table += getRow(statusClass, user.name, user.log.ship.name, user.log.ship.typeName, mass); } table += getRow(null, '', '', '', formatMassValue(massAll)); table += '
'; } return table; }; /** * get a HTML table with information for multiple systems * e.g. for popover * @param data * @returns {string} */ let getSystemsInfoTable = data => { let table = ''; if(data.length > 0){ table += ''; for(let i = 0; i < data.length; i++){ let trueSecClass = getTrueSecClassForSystem( data[i].trueSec ); let securityClass = getSecurityClassForSystem( data[i].security ); table += ''; table += ''; table += ''; table += ''; table += ''; } table += '
'; table += data[i].name; table += ''; table += data[i].security; table += ''; table += parseFloat( data[i].trueSec ).toFixed(1); table += '
'; } return table; }; /** * get a css class for the security level of a system * @param sec * @returns {string} */ let getSecurityClassForSystem = sec => { let secClass = ''; if(sec === 'C13'){ sec = 'SH'; } if( Init.classes.systemSecurity.hasOwnProperty(sec) ){ secClass = Init.classes.systemSecurity[sec]['class']; } return secClass; }; /** * get a css class for the trueSec level of a system * @param trueSec * @returns {string} */ let getTrueSecClassForSystem = function(trueSec){ let trueSecClass = ''; trueSec = parseFloat(trueSec); // check for valid decimal number if( !isNaN( trueSec ) && isFinite( trueSec ) ){ if(trueSec < 0){ trueSec = 0; } trueSec = trueSec.toFixed(1).toString(); if( Init.classes.trueSec.hasOwnProperty(trueSec) ){ trueSecClass = Init.classes.trueSec[trueSec]['class']; } } return trueSecClass; }; /** * get status info * @param status * @param option * @returns {string} */ let getStatusInfoForSystem = function(status, option){ let statusInfo = ''; if( Init.systemStatus.hasOwnProperty(status) ){ // search by status string statusInfo = Init.systemStatus[status][option]; }else{ // saarch by statusID $.each(Init.systemStatus, function(prop, data){ if(status === data.id){ statusInfo = data[option]; return; } }); } return statusInfo; }; /** * get signature group information * @param option * @returns {Array} */ let getSignatureGroupOptions = option => { let options = []; for(let [key, data] of Object.entries(Init.signatureGroups)){ options.push({ value: parseInt(key), text: data[option] }); } return options; }; /** * get Signature names out of global * @param systemTypeId * @param areaId * @param sigGroupId * @returns {{}} */ let getAllSignatureNames = function(systemTypeId, areaId, sigGroupId){ let signatureNames = {}; if( SignatureType[systemTypeId] && SignatureType[systemTypeId][areaId] && SignatureType[systemTypeId][areaId][sigGroupId] ){ signatureNames = SignatureType[systemTypeId][areaId][sigGroupId]; } return signatureNames; }; /** * get the typeID of a signature name * @param systemData * @param sigGroupId * @param name * @returns {number} */ let getSignatureTypeIdByName = (systemData, sigGroupId, name) => { let signatureTypeId = 0; let areaId = getAreaIdBySecurity(systemData.security); if(areaId > 0){ let signatureNames = getAllSignatureNames(systemData.type.id, areaId, sigGroupId); name = name.toLowerCase(); for(let prop in signatureNames){ if( signatureNames.hasOwnProperty(prop) && signatureNames[prop].toLowerCase() === name ){ signatureTypeId = parseInt(prop); break; } } } return signatureTypeId; }; /** * get array key that points to map data catching mapId * @param data * @param mapId * @returns {boolean} */ let getDataIndexByMapId = (data, mapId) => { let index = false; if( Array.isArray(data) && mapId === parseInt(mapId, 10) ){ for(let i = 0; i < data.length; i++){ if(data[i].config.id === mapId){ index = i; break; } } } return index; }; // CurrentMapUserData ============================================================================================= /** * set currentMapUserData as "global" variable (count of active pilots) * this function should be called continuously after data change * to keep the data always up2data * @param mapUserData */ let setCurrentMapUserData = mapUserData => { Init.currentMapUserData = mapUserData; return getCurrentMapUserData(); }; /** * get currentMapUserData from "global" variable for specific map or all maps * @param mapId * @returns {boolean} */ let getCurrentMapUserData = mapId => { let currentMapUserData = false; if(Init.currentMapUserData){ if(mapId === parseInt(mapId, 10)){ // search for a specific map for(let i = 0; i < Init.currentMapUserData.length; i++){ if( Init.currentMapUserData[i].config && Init.currentMapUserData[i].config.id === mapId ){ currentMapUserData = Init.currentMapUserData[i]; break; } } }else{ // get data for all maps currentMapUserData = Init.currentMapUserData; } } if(currentMapUserData !== false){ // return a fresh (deep) copy of that, in case of further modifications currentMapUserData = $.extend(true, {}, currentMapUserData); } return currentMapUserData; }; /** * get mapDataUser array index by mapId * @param mapId * @returns {boolean|int} */ let getCurrentMapUserDataIndex = mapId => { return getDataIndexByMapId(Init.currentMapUserData, mapId); }; /** * update cached mapUserData for a single map * @param mapUserData */ let updateCurrentMapUserData = mapUserData => { let mapUserDataIndex = getCurrentMapUserDataIndex( mapUserData.config.id ); if( !Array.isArray(Init.currentMapUserData) ){ Init.currentMapUserData = []; } if(mapUserDataIndex !== false){ Init.currentMapUserData[mapUserDataIndex] = mapUserData; }else{ // new map data Init.currentMapUserData.push(mapUserData); } }; // CurrentMapData ================================================================================================= /** * set currentMapData as "global" variable * this function should be called continuously after data change * to keep the data always up2data * @param mapData */ let setCurrentMapData = mapData => { Init.currentMapData = mapData; return getCurrentMapData(); }; /** * get currentMapData from "global" variable for a specific map or all maps * @param mapId * @returns {boolean} */ let getCurrentMapData = mapId => { let currentMapData = false; if( mapId === parseInt(mapId, 10) ){ // search for a specific map for(let i = 0; i < Init.currentMapData.length; i++){ if(Init.currentMapData[i].config.id === mapId){ currentMapData = Init.currentMapData[i]; break; } } }else{ // get data for all maps currentMapData = Init.currentMapData; } return currentMapData; }; /** * get mapData array index by mapId * @param mapId * @returns {boolean|int} */ let getCurrentMapDataIndex = mapId => { return getDataIndexByMapId(Init.currentMapData, mapId); }; /** * update cached mapData for a single map * @param mapData */ let updateCurrentMapData = mapData => { let mapDataIndex = getCurrentMapDataIndex( mapData.config.id ); if(mapDataIndex !== false){ Init.currentMapData[mapDataIndex].config = mapData.config; Init.currentMapData[mapDataIndex].data = mapData.data; }else{ // new map data Init.currentMapData.push(mapData); } }; /** * @param path * @param value * @returns {boolean} */ let filterCurrentMapData = (path, value) => { let currentMapData = getCurrentMapData(); if(currentMapData){ currentMapData = currentMapData.filter((mapData) => { return (getObjVal(mapData, path) === value); }); } return currentMapData; }; /** * delete map data by mapId from currentMapData * @param mapId */ let deleteCurrentMapData = mapId => { Init.currentMapData = Init.currentMapData.filter((mapData) => { return (mapData.config.id !== mapId); }); }; /** * get the current log data for the current user character * @returns {boolean} */ let getCurrentCharacterLog = function(){ let characterLog = false; let currentUserData = getCurrentUserData(); if( currentUserData && currentUserData.character && currentUserData.character.log ){ characterLog = currentUserData.character.log; } return characterLog; }; /** * get information for the current mail user * @param option * @returns {boolean} */ let getCurrentUserInfo = option => { let currentUserData = getCurrentUserData(); let userInfo = false; if(currentUserData){ // user data is set -> user data will be set AFTER the main init request! let characterData = currentUserData.character; if(characterData){ if(option === 'privateId'){ userInfo = characterData.id; } if(option === 'allianceId' && characterData.alliance){ userInfo = characterData.alliance.id; } if(option === 'corporationId' && characterData.corporation){ userInfo = characterData.corporation.id; } } } 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 userData (pilots) from systemId * @param userData * @param systemId * @returns {*} */ let getCharacterDataBySystemId = (userData, systemId) => { if(userData && userData.length){ for(let i = 0; i < userData.length; i++){ if(userData[i].id === systemId){ return userData[i].user; } } } return []; }; /** * 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 filterFinalCharData = function(tmpFinalCharData){ return this.id !== tmpFinalCharData.id; }; let characterData = getCharacterDataBySystemId(userData, 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 * @param systemData * @param type */ let setDestination = function(systemData, type){ let description = ''; switch(type){ case 'set_destination': description = 'Set destination'; break; case 'add_first_waypoint': description = 'Set first waypoint'; break; case 'add_last_waypoint': description = 'Set new waypoint'; break; } $.ajax({ type: 'POST', url: Init.path.setDestination, data: { clearOtherWaypoints: (type === 'set_destination') ? 1 : 0, first: (type === 'add_last_waypoint') ? 0 : 1, systemData: [{ systemId: systemData.systemId, name: systemData.name }] }, context: { description: description }, dataType: 'json' }).done(function(responseData){ if( responseData.systemData && responseData.systemData.length > 0 ){ for (let j = 0; j < responseData.systemData.length; j++){ showNotify({title: this.description, text: 'System: ' + responseData.systemData[j].name, type: 'success'}); } } if( responseData.error && responseData.error.length > 0 ){ for(let i = 0; i < responseData.error.length; i++){ showNotify({title: this.description + ' error', text: 'System: ' + responseData.error[i].message, type: 'error'}); } } }).fail(function( jqXHR, status, error){ let reason = status + ' ' + error; showNotify({title: jqXHR.status + ': ' + this.description, text: reason, type: 'warning'}); }); }; /** * write clipboard text * @param text * @returns {Promise} */ let copyToClipboard = (text) => { let copyToClipboardExecutor = (resolve, reject) => { let payload = { action: 'copyToClipboard', data: false }; if (navigator.clipboard) { // get current permission status navigator.permissions.query({ name: 'clipboard-write' }).then(permissionStatus => { // will be 'granted', 'denied' or 'prompt' if( permissionStatus.state === 'granted' || permissionStatus.state === 'prompt' ){ navigator.clipboard.writeText(text) .then(() => { payload.data = true; resolve(payload); }) .catch(err => { let errorMsg = 'Failed to write clipboard content'; console.error(errorMsg, err); showNotify({title: 'Clipboard API', text: errorMsg, type: 'error'}); resolve(payload); }); }else{ showNotify({title: 'Clipboard API', text: 'You denied write access', type: 'warning'}); resolve(payload); } }); } else { console.warn('Clipboard API not supported by your browser'); resolve(payload); } }; return new Promise(copyToClipboardExecutor); }; /** * read clipboard text * @returns {Promise} */ let readFromClipboard = () => { let readFromClipboardExecutor = (resolve, reject) => { let payload = { action: 'readFromClipboard', data: false }; if (navigator.clipboard) { // get current permission status navigator.permissions.query({ name: 'clipboard-read' }).then(permissionStatus => { // will be 'granted', 'denied' or 'prompt' if( permissionStatus.state === 'granted' || permissionStatus.state === 'prompt' ){ navigator.clipboard.readText() .then(text => { payload.data = text; resolve(payload); }) .catch(err => { let errorMsg = 'Failed to read clipboard content'; console.error(errorMsg, err); showNotify({title: 'Clipboard API', text: errorMsg, type: 'error'}); resolve(payload); }); }else{ showNotify({title: 'Clipboard API', text: 'You denied read access', type: 'warning'}); resolve(payload); } }); } else { console.warn('Clipboard API not supported by your browser'); resolve(payload); } }; return new Promise(readFromClipboardExecutor); }; /** * set currentSystemData as "global" variable * @param systemData */ let setCurrentSystemData = (systemData) => { Init.currentSystemData = systemData; }; /** * get currentSystemData from "global" variables * @returns {*} */ let getCurrentSystemData = () => { return Init.currentSystemData; }; /** * get current location data * -> system data where current user is located * @returns {{id: *, name: *}} */ let getCurrentLocationData = function(){ let currentLocationLink = $('#' + config.headCurrentLocationId).find('a'); return { id: currentLocationLink.data('systemId'), name: currentLocationLink.data('systemName') }; }; /** * get all "open" dialog elements * @returns {*|jQuery} */ let getOpenDialogs = function(){ 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 * @returns {string} */ let formatPrice = (price) => { price = Number( price ).toFixed(2); let parts = price.toString().split('.'); price = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',') + (parts[1] ? '.' + parts[1] : ''); return price + ' ISK'; }; /** * format mass value * @param value * @returns {string} */ let formatMassValue = (value) => { return (parseInt(value) / 1000).toLocaleString() + ' t'; }; /** * get localForage instance (singleton) for offline client site storage * @returns {localforage} */ let getLocalStorage = function(){ if(localStorage === undefined){ localStorage = localforage.createInstance({ driver: [localforage.INDEXEDDB, localforage.WEBSQL, localforage.LOCALSTORAGE], name: 'Pathfinder local storage' }); } return localStorage; }; /** * clear session Storage * -> otherwise a tab refresh does not clear sessionStorage! */ let clearSessionStorage = () => { if(sessionStorage){ sessionStorage.clear(); } }; /** * Create Date() as UTC * @param date * @returns {Date} */ let createDateAsUTC = (date) => { return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds())); }; /** * Convert Date() to UTC (!important function!) * @param date * @returns {Date} */ let convertDateToUTC = (date) => { return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()); }; /** * Convert Date() to Time String * @param date * @param showSeconds * @returns {string} */ let convertDateToString = (date, showSeconds) => { let dateString = ('0'+ (date.getMonth() + 1 )).slice(-2) + '/' + ('0'+date.getDate()).slice(-2) + '/' + date.getFullYear(); let timeString = ('0' + date.getHours()).slice(-2) + ':' + ('0'+date.getMinutes()).slice(-2); timeString += (showSeconds) ? ':' + ('0'+date.getSeconds()).slice(-2) : ''; return dateString + ' ' + timeString; }; /** * check an element for attached event by name * -> e.g. eventName = 'click.myNamespace' * @param element * @param eventName * @returns {boolean} */ let hasEvent = (element, eventName) => { let exists = false; let parts = eventName.split('.'); let name = parts[0]; let namespace = parts.length === 2 ? parts[1] : false; let events = $._data( element[0], 'events')[name]; if(events){ if(namespace){ // seach events by namespace for(let event of events){ if(event.namespace === namespace){ exists = true; break; } } }else{ // at least ONE event of the given name found exists = true; } } return exists; }; /** * wrapper function for onClick() || onDblClick() events in order to distinguish between this two types of events * @param element * @param singleClickCallback * @param doubleClickCallback * @param timeout */ let singleDoubleClick = (element, singleClickCallback, doubleClickCallback, timeout) => { let eventName = 'mouseup.singleDouble'; if(!hasEvent(element, eventName)){ let clicks = 0; // prevent default behaviour (e.g. open
-tag link) element.off('click').on('click', function(e){ e.preventDefault(); }); element.off(eventName).on(eventName, function(e){ clicks++; if (clicks === 1){ setTimeout(element => { if(clicks === 1){ singleClickCallback.call(element, e); } else { doubleClickCallback.call(element, e); } clicks = 0; }, timeout || Init.timer.DBL_CLICK, this); } }); } }; /** * get deep json object value if exists * -> e.g. key = 'first.last.third' string * @param obj * @param key * @returns {*} */ let getObjVal = (obj, key) => { return key.split('.').reduce((o, x) => { return (typeof o === 'undefined' || o === null) ? o : o[x]; }, obj); }; /** * get document path * -> www.pathfinder.com/pathfinder/ -> /pathfinder * @returns {string|string} */ let getDocumentPath = () => { let pathname = window.location.pathname; // replace file endings let r = /[^\/]*$/; let path = pathname.replace(r, ''); return path || '/'; }; /** * redirect * @param url * @param params */ let redirect = (url, params) => { let currentUrl = document.URL; if(url !== currentUrl){ if( params && params.length > 0 ){ url += '?' + params.join('&'); } window.location = url; } }; /** * send logout request * @param params */ let logout = (params) => { let data = {}; if( params && params.ajaxData ){ data = params.ajaxData; } $.ajax({ type: 'POST', url: Init.path.logout, data: data, dataType: 'json' }).done(function(data){ if(data.reroute){ redirect(data.reroute, ['logout']); } }).fail(function( jqXHR, status, error){ let reason = status + ' ' + error; showNotify({title: jqXHR.status + ': logout', text: reason, type: 'error'}); }); }; /** * set a cookie * @param name * @param value * @param expire * @param format */ let setCookie = (name, value, expire, format) => { let d = new Date(); let time = d.getTime(); let timeExpire = time * -1; if(expire > 0){ switch(format){ case 'd': // days timeExpire = expire * 24 * 60 * 60 * 1000; break; case 's': // seconds timeExpire = expire * 1000; break; } } d.setTime(time + timeExpire); let expires = 'expires=' + d.toUTCString(); let path = 'path=' + getDocumentPath(); document.cookie = name + '=' + value + '; ' + expires + '; ' + path; }; /** * get cookie value by name * @param cname * @returns {string} */ let getCookie = (cname) => { let name = cname + '='; let ca = document.cookie.split(';'); for(let i = 0; i