Files
pathfinder/js/app/util.js
2018-07-14 21:32:31 +02:00

2781 lines
88 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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)
headCharacterSwitchId: 'pf-head-character-switch', // id for "character switch" popover
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" <select> elements
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
animationPulseWarningClass: 'pf-animation-pulse-warning', // animation class
// popover
popoverSmallClass: 'pf-popover-small', // class for "small" popover
popoverTriggerClass: 'pf-popover-trigger', // class for "popover" trigger elements
// fonts
fontTriglivianClass: 'pf-triglivian' // class for "Triglivian" names (e.g. Abyssal systems)
};
let stopTimerCache = {}; // cache for stopwatch timer
let animationTimerCache = {}; // cache for table row animation timeout
let localStorage; // cache for "localForage" singleton
/*
* ===============================================================================================================
* Global jQuery plugins for some common and frequently used functions
* ==============================================================================================================
*/
/**
* displays a loading indicator on an element
*/
$.fn.showLoadingAnimation = function(options){
return this.each(function(){
let loadingElement = $(this);
let iconSize = 'fa-lg';
// disable all events
loadingElement.css('pointer-events', 'none');
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(' ')
}).append(
$('<i>', {
class: ['fas', iconSize, 'fa-sync', 'fa-spin'].join(' ')
})
)
);
loadingElement.append(overlay);
// fade in
$(overlay).velocity({
opacity: 0.6
},{
duration: 120
});
});
};
/**
* removes a loading indicator
*/
$.fn.hideLoadingAnimation = function(){
return this.each(function(){
let loadingElement = $(this);
let overlay = loadingElement.find('.' + config.ajaxOverlayClass );
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');
}
});
}
});
};
/**
* show "splash" loading overlay
* @param callback
*/
$.fn.showSplashOverlay = function(callback){
let splashOverlay = $(this);
splashOverlay.velocity('fadeIn', {
duration: Init.animationSpeed.splashOverlay,
complete: function(){
// execute callback function if given
if(callback !== undefined){
callback();
}
}
});
};
/**
* hide "splash" loading overlay
*/
$.fn.hideSplashOverlay = function(){
let splashOverlay = $(this);
splashOverlay.velocity('fadeOut', {
duration: Init.animationSpeed.splashOverlay
});
};
/**
* show a unique generated captcha image
* @param reason
* @param callback
* @returns {*}
*/
$.fn.showCaptchaImage = function(reason, callback){
return this.each(function(){
let captchaWrapper = $(this);
let captchaImage = captchaWrapper.find('img');
captchaWrapper.showLoadingAnimation(config.loadingOptions);
getCaptchaImage(reason, function(base64Image){
captchaImage.attr('src', base64Image).show();
captchaWrapper.hideLoadingAnimation({
icon: {
size: 'fa-2x'
}
});
if(callback){
callback();
}
});
});
};
/**
* request a captcha image
* @param reason
* @param callback
*/
let getCaptchaImage = function(reason, callback){
$.ajax({
type: 'POST',
url: Init.path.getCaptcha,
data: {
reason: reason
},
dataType: 'json'
}).done(function(responseData){
if(responseData.error.length > 0){
showNotify({title: 'getCaptchaImage', text: 'Captcha image gneration failed', type: 'error'});
}else{
callback(responseData.img);
}
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
showNotify({title: jqXHR.status + ': getCaptchaImage', text: reason, type: 'error'});
});
};
/**
* reset/clear form fields
* @returns {*}
*/
$.fn.resetFormFields = function(){
return this.each(function(){
let field = $(this);
if( !field.is('select') ){
// "input"
field.val('');
}
field.parents('.form-group').removeClass('has-error has-success');
});
};
/**
* show form messages
* check: showMessage() for en other way of showing messages
* @param errors
*/
$.fn.showFormMessage = function(errors){
let formElement = $(this);
let errorMessage = [];
let warningMessage = [];
let infoMessage = [];
for(let i = 0; i < errors.length; i++){
if(errors[i].type === 'error'){
errorMessage.push( errors[i].message );
// mark form field as invalid in case of a validation error
if(
errors[i].field &&
errors[i].field.length > 0
){
let formField = formElement.find('[name="' + errors[i].field + '"]');
let formGroup = formField.parents('.form-group').removeClass('has-success').addClass('has-error');
let formHelp = formGroup.find('.help-block').text(errors[i].message);
}
}else if(errors[i].type === 'warning'){
warningMessage.push( errors[i].message );
}else if(errors[i].type === 'info'){
infoMessage.push( errors[i].message );
}
}
if(errorMessage.length > 0){
formElement.hideFormMessage('error', function(element){
$(element).find('small').html( errorMessage.join('<br>') );
$(element).velocity('transition.slideUpIn', config.settingsMessageVelocityOptions);
});
}
if(warningMessage.length > 0){
formElement.hideFormMessage('warning', function(element){
$(element).find('small').html( warningMessage.join('<br>') );
$(element).velocity('transition.slideUpIn', config.settingsMessageVelocityOptions);
});
}
if(infoMessage.length > 0){
formElement.hideFormMessage('info', function(element){
$(element).find('small').html( infoMessage.join('<br>') );
$(element).velocity('transition.slideUpIn', config.settingsMessageVelocityOptions);
});
}
};
/**
* hide all form messages
* @param type
* @param callback
*/
$.fn.hideFormMessage = function(type, callback){
let formElement = $(this);
let settingsMessageVelocityOptions = $.extend({}, config.settingsMessageVelocityOptions);
// check if callback exists
if(callback !== undefined){
settingsMessageVelocityOptions.complete = callback;
// new error will be shown afterwards -> keep display
settingsMessageVelocityOptions.display = 'block';
}
let messageElement = null;
switch(type){
case 'error':
// find error container
messageElement = formElement.find('.' + config.formErrorContainerClass);
break;
case 'warning':
messageElement = formElement.find('.' + config.formWarningContainerClass);
break;
case 'info':
messageElement = formElement.find('.' + config.formInfoContainerClass);
break;
case 'all':
messageElement = formElement.find(
'.' + config.formErrorContainerClass + ', ' +
'.' + config.formWarningContainerClass + ', ' +
'.' + config.formInfoContainerClass
);
}
if(messageElement){
if(messageElement.is(':visible')){
messageElement.velocity('transition.slideDownOut', settingsMessageVelocityOptions);
}else if(callback){
// skip hide animation
callback(messageElement);
}
}
};
/**
* init form elements for validation (bootstrap3 validation)
* @param options
* @returns {*}
*/
$.fn.initFormValidation = function(options){
options = (typeof options === 'undefined')? {} : options;
return this.each(function(){
let form = $(this);
// init form validation
form.validator(options);
// validation event listener
form.on('valid.bs.validator', function(validatorObj){
let inputGroup = $(validatorObj.relatedTarget).parents('.form-group');
if(inputGroup){
inputGroup.removeClass('has-error').addClass('has-success');
}
});
form.on('invalid.bs.validator', function(validatorObj){
let field = $(validatorObj.relatedTarget);
let inputGroup = field.parents('.form-group');
if(inputGroup){
inputGroup.removeClass('has-success').addClass('has-error');
}
});
});
};
/**
* checks whether a bootstrap form is valid or not
* validation plugin does not provide a proper function for this
* @returns {boolean}
*/
$.fn.isValidForm = function(){
let form = $(this);
let valid = false;
let errorElements = form.find('.has-error');
if(errorElements.length === 0){
valid = true;
}
return valid;
};
/**
* check multiple element if they arecurrently visible in viewport
* @returns {Array}
*/
$.fn.isInViewport = function(){
let visibleElement = [];
this.each(function(){
let element = $(this)[0];
let top = element.offsetTop;
let left = element.offsetLeft;
let width = element.offsetWidth;
let height = element.offsetHeight;
while(element.offsetParent) {
element = element.offsetParent;
top += element.offsetTop;
left += element.offsetLeft;
}
if(
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
){
visibleElement.push( this );
}
});
return visibleElement;
};
/**
* init the map-update-counter as "easy-pie-chart"
*/
$.fn.initMapUpdateCounter = function(){
let counterChart = $(this);
counterChart.easyPieChart({
barColor: function(percent){
let color = '#568a89';
if(percent <= 30){
color = '#d9534f';
}else if(percent <= 50){
color = '#f0ad4e';
}
return color;
},
trackColor: '#2b2b2b',
size: 30,
scaleColor: false,
lineWidth: 2,
animate: 1000
});
};
/**
* init tooltips on an element
* @param {object} options
* @returns {*}
*/
$.fn.initTooltips = function(options){
options = (typeof options === 'object') ? options : {};
let defaultOptions = {
container: this,
delay: 100
};
options = $.extend(defaultOptions, options);
return this.each(function(){
let tooltipElements = $(this).find('[title]');
tooltipElements.tooltip('destroy').tooltip(options);
});
};
/**
* destroy popover elements
* @param recursive
* @returns {*}
*/
$.fn.destroyTooltip = function(recursive){
return this.each(function() {
let element = $(this);
let tooltipSelector = '[title]';
let tooltipElements = element.filter(tooltipSelector);
if(recursive){
tooltipElements = tooltipElements.add(element.find(tooltipSelector));
}
tooltipElements.each(function() {
$(this).tooltip('destroy');
});
});
};
/**
* adds a popup tooltip with character information (created/updated)
* @param tooltipData
*/
$.fn.addCharacterInfoTooltip = function(tooltipData){
let element = $(this);
if(
tooltipData.created.character &&
tooltipData.updated.character
){
let createdData = tooltipData.created;
let updatedData = tooltipData.updated;
// check if data has changed
if(
element.data('created') !== createdData.created ||
element.data('updated') !== updatedData.updated
){
// data changed
// set new data for next check
element.data('created', createdData.created);
element.data('updated', updatedData.updated);
let statusCreatedClass = getStatusInfoForCharacter(createdData.character, 'class');
let statusUpdatedClass = getStatusInfoForCharacter(updatedData.character, 'class');
// convert timestamps
let dateCreated = new Date(createdData.created * 1000);
let dateUpdated = new Date(updatedData.updated * 1000);
let dateCreatedUTC = convertDateToUTC(dateCreated);
let dateUpdatedUTC = convertDateToUTC(dateUpdated);
let data = {
created: createdData,
updated: updatedData,
createdTime: convertDateToString(dateCreatedUTC),
updatedTime: convertDateToString(dateUpdatedUTC),
createdStatusClass: statusCreatedClass,
updatedStatusClass: statusUpdatedClass
};
requirejs(['text!templates/tooltip/character_info.html', 'mustache'], function(template, Mustache) {
let content = Mustache.render(template, data);
element.popover({
placement: 'top',
html: true,
trigger: 'hover',
content: '',
container: 'body',
title: 'Created / Updated',
delay: {
show: 250,
hide: 0
}
});
// set new popover content
let popover = element.data('bs.popover');
popover.options.content = content;
});
}
}
return element;
};
/**
* add character switch popover
* @param userData
*/
$.fn.initCharacterSwitchPopover = function(userData){
let elements = $(this);
let eventNamespace = 'hideCharacterPopup';
requirejs(['text!templates/tooltip/character_switch.html', 'mustache'], function (template, Mustache) {
let data = {
id: config.headCharacterSwitchId,
browserTabId: getBrowserTabId(),
routes: Init.routes,
userData: userData,
otherCharacters: () => {
return userData.characters.filter((character, i) => {
let characterImage = Init.url.ccpImageServer + '/Character/' + character.id + '_32.jpg';
// preload image (prevent UI flicker
let img= new Image();
img.src = characterImage;
userData.characters[i].image = characterImage;
return character.id !== userData.character.id;
});
}
};
let content = Mustache.render(template, data);
return elements.each(function() {
let element = $(this);
// check if popover already exists -> remove it
if(element.data('bs.popover') !== undefined){
element.off('click').popover('destroy');
}
element.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
let button = $(this);
let easeEffect = button.attr('data-easein');
let popoverData = button.data('bs.popover');
let popoverElement = null;
let velocityOptions = {
duration: Init.animationSpeed.dialogEvents
};
if(popoverData === undefined){
button.on('shown.bs.popover', function (e) {
let tmpPopupElement = $(this).data('bs.popover').tip();
tmpPopupElement.find('.btn').on('click', function(e){
// close popover
$('body').click();
});
});
// init popover and add specific class to it (for styling)
button.popover({
html: true,
title: 'select character',
trigger: 'manual',
placement: 'bottom',
container: 'body',
content: content,
animation: false
}).data('bs.popover').tip().addClass('pf-popover');
button.popover('show');
popoverElement = button.data('bs.popover').tip();
popoverElement.velocity('transition.' + easeEffect, velocityOptions);
popoverElement.initTooltips();
// set click events. This is required to pass data to "beforeunload" events
// -> there is no way to identify the target within that event
popoverElement.on('click', '.btn', function(){
// character switch detected
$('body').data('characterSwitch', true);
// ... and remove "characterSwitch" data again! after "unload"
setTimeout(function() {
$('body').removeData('characterSwitch');
}, 500);
});
}else{
popoverElement = button.data('bs.popover').tip();
if(popoverElement.is(':visible')){
popoverElement.velocity('reverse');
}else{
button.popover('show');
popoverElement.initTooltips();
popoverElement.velocity('transition.' + easeEffect, velocityOptions);
}
}
});
// set popup "close" observer
elements.initPopoverClose(eventNamespace);
});
});
};
/**
* destroy popover elements
* @param recursive
* @returns {*}
*/
$.fn.destroyPopover = function(recursive){
return this.each(function() {
let element = $(this);
let popoverSelector = '.' + config.popoverTriggerClass;
let popoverElements = element.filter(popoverSelector);
if(recursive){
popoverElements = popoverElements.add(element.find(popoverSelector));
}
popoverElements.each(function() {
let popoverElement = $(this);
if(popoverElement.data('bs.popover')){
popoverElement.popover('destroy');
}
});
});
};
/**
* set "popover" close action on clicking "somewhere" on the <body>
* @param eventNamespace
* @returns {*}
*/
$.fn.initPopoverClose = function(eventNamespace){
return this.each(function() {
$('body').off('click.' + eventNamespace).on('click.' + eventNamespace + ' contextmenu', function (e) {
$('.' + config.popoverTriggerClass).each(function () {
let popoverElement = $(this);
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if(
!popoverElement.is(e.target) &&
popoverElement.has(e.target).length === 0 &&
$('.popover').has(e.target).length === 0
){
let popover = popoverElement.data('bs.popover');
if(
popover !== undefined &&
popover.tip().is(':visible')
){
popoverElement.popover('hide');
}
}
});
});
});
};
/**
* adds the small-class to a tooltip
* @returns {*}
*/
$.fn.setPopoverSmall = function(){
return this.each(function() {
let element = $(this);
let popover = element.data('bs.popover');
if(popover){
popover.tip().addClass(config.popoverSmallClass);
}
});
};
/**
* display a custom message (info/warning/error) to a container element
* check: $.fn.showFormMessage() for an other way of showing messages
* @param config
*/
$.fn.showMessage = function(config){
let containerElement = $(this);
requirejs(['text!templates/form/message.html', 'mustache'], function(template, Mustache) {
let messageTypeClass = 'alert-danger';
let messageTextClass = 'txt-color-danger';
switch(config.type){
case 'info':
messageTypeClass = 'alert-info';
messageTextClass = 'txt-color-information';
break;
case 'success':
messageTypeClass = 'alert-success';
messageTextClass = 'txt-color-success';
break;
case 'warning':
messageTypeClass = 'alert-warning';
messageTextClass = 'txt-color-warning';
break;
}
let defaultOptions = {
dismissible: true,
messageId: 'pf-alert-' + Math.random().toString(36).substring(7),
messageTypeClass: messageTypeClass,
messageTextClass: messageTextClass,
insertElement: 'replace'
};
defaultOptions = $.extend(defaultOptions, config);
let content = Mustache.render(template, defaultOptions);
switch(defaultOptions.insertElement){
case 'replace': containerElement.html(content); break;
case 'prepend': containerElement.prepend(content); break;
case 'append': containerElement.append(content); break;
default: console.error('insertElement: %s is not specified!', defaultOptions.insertElement);
}
//containerElement.children().first().velocity('stop').velocity('fadeIn');
$('#' + defaultOptions.messageId).velocity('stop').velocity('fadeIn');
});
};
/**
* highlight jquery elements
* add/remove css class for keyframe animation
* @returns {any|JQuery|*}
*/
$.fn.pulseTableRow = function(status, clear){
let animationClass = '';
switch(status){
case 'added':
animationClass = config.animationPulseSuccessClass;
break;
case 'changed':
animationClass = config.animationPulseWarningClass;
break;
}
let clearTimer = function(element) {
element.removeClass( animationClass );
let currentTimer = element.data('animationTimer');
if( animationTimerCache.hasOwnProperty(currentTimer) ){
clearTimeout( currentTimer );
delete animationTimerCache[currentTimer];
element.removeData('animationTimer');
}
};
return this.each(function(){
let element = $(this);
if( element.hasClass(animationClass) ){
// clear timer -> set new timer
clearTimer(element);
}
if(clear !== true){
element.addClass( animationClass );
let timer = setTimeout(clearTimer, 1500, element);
element.data('animationTimer', timer);
animationTimerCache[timer] = true;
}
});
};
/*
* ===============================================================================================================
* Util functions that are global available for all modules
* ==============================================================================================================
*/
/**
* get current Pathfinder version number
* @returns {*|jQuery}
*/
let getVersion = function(){
return $('body').data('version');
};
/**
* show current program version information in browser console
*/
let showVersionInfo = function(){
console.info('PATHFINDER ' + getVersion());
};
/**
* polyfill for "passive" events
* -> see https://github.com/zzarcon/default-passive-events
*/
let initPassiveEvents = () => {
const defaultOptions = {
passive: true,
capture: false
};
const supportedPassiveTypes = [
'scroll', 'wheel',
'touchstart', 'touchmove', 'touchenter', 'touchend', 'touchleave',
'mouseout', 'mouseleave', 'mouseup', 'mousedown', 'mousemove', 'mouseenter', 'mousewheel', 'mouseover'
];
const getDefaultPassiveOption = (passive, eventName) => {
if (passive !== undefined) return passive;
return supportedPassiveTypes.indexOf(eventName) === -1 ? false : defaultOptions.passive;
};
const getWritableOptions = (options) => {
const passiveDescriptor = Object.getOwnPropertyDescriptor(options, 'passive');
return passiveDescriptor &&
passiveDescriptor.writable !== true &&
passiveDescriptor.set === undefined ? Object.assign({}, options) : options;
};
const prepareSafeListener = (listener, passive) => {
if (!passive) return listener;
return function (e) {
e.preventDefault = () => {};
return listener.call(this, e);
};
};
const overwriteAddEvent = (superMethod) => {
EventTarget.prototype.addEventListener = function (type, listener, options) { // jshint ignore:line
const usesListenerOptions = typeof options === 'object';
const useCapture = usesListenerOptions ? options.capture : options;
options = usesListenerOptions ? getWritableOptions(options) : {};
options.passive = getDefaultPassiveOption(options.passive, type);
options.capture = useCapture === undefined ? defaultOptions.capture : useCapture;
listener = prepareSafeListener(listener, options.passive);
superMethod.call(this, type, listener, options);
};
};
let eventListenerOptionsSupported = () => {
let supported = false;
try {
const opts = Object.defineProperty({}, 'passive', {
get() {
supported = true;
}
});
window.addEventListener('test', null, opts);
window.removeEventListener('test', null, opts);
} catch (e) {}
return supported;
};
let supportsPassive = eventListenerOptionsSupported ();
if (supportsPassive) {
const addEvent = EventTarget.prototype.addEventListener; // jshint ignore:line
overwriteAddEvent(addEvent);
}
};
/**
* init utility prototype functions
*/
let initPrototypes = () => {
// Array diff
// [1,2,3,4,5,6].diff( [3,4,5] );
// => [1, 2, 6]
Array.prototype.diff = function(a) {
return this.filter(function(i) {return a.indexOf(i) < 0;});
};
/**
* sort array of objects by property name
* @param p
* @returns {Array.<T>}
*/
Array.prototype.sortBy = function(p) {
return this.slice(0).sort((a,b) => {
return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
});
};
/**
* get hash from string
* @returns {number}
*/
String.prototype.hashCode = function() {
let hash = 0, i, chr;
if (this.length === 0) return hash;
for (i = 0; i < this.length; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
initPassiveEvents();
};
/**
* flatten XEditable array for select fields
* @param dataArray
* @returns {{}}
*/
let flattenXEditableSelectArray = (dataArray) => {
let flatten = {};
for(let data of dataArray){
if(data.children && data.children.length > 0){
for(let child of data.children){
flatten[child.value] = child.text;
}
}else{
flatten[data.value] = data.text;
}
}
return flatten;
};
/**
* set default configuration for "Bootbox"
*/
let initDefaultBootboxConfig = () => {
bootbox.setDefaults({
onEscape: true // enables close dialogs on ESC key
});
};
/**
* set default configuration for "Select2"
*/
let initDefaultSelect2Config = () => {
$.fn.select2.defaults.set('theme', 'pathfinder');
let initScrollbar = (resultsWrapper) => {
// default 'mousewheel' event set by select2 needs to be disabled
// in order to make mCustomScrollbar mouseWheel enable works correctly
$(resultsWrapper).find('ul.select2-results__options').off('mousewheel');
resultsWrapper.mCustomScrollbar({
mouseWheel: {
enable: true,
scrollAmount: 'auto',
axis: 'y',
preventDefault: true
},
keyboard: {
enable: false,
scrollType: 'stepless',
scrollAmount: 'auto'
},
scrollbarPosition: 'inside',
autoDraggerLength: true,
autoHideScrollbar: false,
advanced: {
updateOnContentResize: true
},
callbacks: {
alwaysTriggerOffsets: false, // only trigger callback.onTotalScroll() once
onTotalScrollOffset: 300, // trigger callback.onTotalScroll() 100px before end
onTotalScroll: function(a){
// we want to "trigger" Select2´s 'scroll' event
// in order to make its "infinite scrolling" function working
this.mcs.content.find(':first-child').trigger('scroll');
}
}
});
};
let getResultsWrapper = (selectElement) => {
let wrapper = null;
let selectElementId = selectElement.getAttribute('data-select2-id');
if(selectElementId){
let resultsOptions = $('#select2-' + selectElementId + '-results');
if(resultsOptions.length) {
let resultsWrapper = resultsOptions.parents('.select2-results');
if(resultsWrapper.length){
wrapper = resultsWrapper;
}
}
}
return wrapper;
};
// global open event
$(document).on('select2:open', '.' + config.select2Class, function(e){
let resultsWrapper = getResultsWrapper(this);
if(resultsWrapper){
initScrollbar(resultsWrapper);
}
});
// global select2:closing event
$(document).on('select2:closing', '.' + config.select2Class, function(e){
let resultsWrapper = getResultsWrapper(this);
if(resultsWrapper){
resultsWrapper.mCustomScrollbar('destroy');
}
});
};
/**
* set default configuration for "xEditable"
*/
let initDefaultEditableConfig = () => {
// use fontAwesome buttons
$.fn.editableform.buttons =
'<button type="submit" class="btn btn-primary btn-sm editable-submit">'+
'<i class="fa fa-fw fa-check"></i>'+
'</button>'+
'<button type="button" class="btn btn-default btn-sm editable-cancel">'+
'<i class="fa fa-fw fa-times"></i>'+
'</button>';
};
/**
* 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 $('<span>', {
class: ['label', 'label-' + role.style].join(' '),
text: role.label
});
};
/**
* get all mapTabElements (<a> 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('map-id') === 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 = $('<div>', {
id: config.mapModuleId
});
}
return mapModule;
};
/**
* get Area ID by security string
* @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 += '<table>';
for(let effect of effects){
table += '<tr>';
table += '<td>';
table += effect.effect;
table += '</td>';
table += '<td class="text-right">';
table += effect.value;
table += '</td>';
table += '</tr>';
}
table += '</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 += '<table>';
for(let planet of planets){
table += '<tr>';
table += '<td>';
table += planet.name;
table += '</td>';
table += '<td class="text-right">';
table += planet.type.name;
table += '</td>';
table += '</tr>';
}
table += '</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 = '<tr>';
row += '<td>';
row += statusClass !== null ? '<i class="fas fa-circle ' + config.userStatusClass + ' ' + statusClass + '">' : '';
row += '</td>';
row += '<td>';
row += userName;
row += '</td>';
row += '<td>';
row += shipName;
row += '</td>';
row += '<td class="text-right txt-color txt-color-orangeLight">';
row += shipTypeName;
row += '</td>';
row += '<td class="text-right txt-color">';
row += mass;
row += '</td>';
row += '</tr>';
return row;
};
let massAll = 0;
table += '<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 += '</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 += '<table>';
for(let i = 0; i < data.length; i++){
let trueSecClass = getTrueSecClassForSystem( data[i].trueSec );
let securityClass = getSecurityClassForSystem( data[i].security );
table += '<tr>';
table += '<td>';
table += data[i].name;
table += '</td>';
table += '<td class="text-right ' + securityClass + '">';
table += data[i].security;
table += '</td>';
table += '<td class="text-right ' + trueSecClass + '">';
table += parseFloat( data[i].trueSec ).toFixed(1);
table += '</td>';
table += '</tr>';
}
table += '</table>';
}
return table;
};
/**
* get a css class for the security level of a system
* @param sec
* @returns {string}
*/
let getSecurityClassForSystem = sec => {
let secClass = '';
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 {{}}
*/
let getSignatureGroupInfo = function(option){
let groupInfo = {};
for (let prop in Init.signatureGroups) {
if(Init.signatureGroups.hasOwnProperty(prop)){
prop = parseInt(prop);
groupInfo[prop] = Init.signatureGroups[prop][option];
}
}
return groupInfo;
};
/**
* 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 = function(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'});
});
};
/**
* 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 <a>-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 <ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length,c.length);
}
}
return '';
};
return {
config: config,
getVersion: getVersion,
showVersionInfo: showVersionInfo,
initPrototypes: initPrototypes,
initDefaultBootboxConfig: initDefaultBootboxConfig,
initDefaultSelect2Config: initDefaultSelect2Config,
initDefaultEditableConfig: initDefaultEditableConfig,
getCurrentTriggerDelay: getCurrentTriggerDelay,
getServerTime: getServerTime,
convertTimestampToServerTime: convertTimestampToServerTime,
getTimeDiffParts: getTimeDiffParts,
timeStart: timeStart,
timeStop: timeStop,
updateCounter: updateCounter,
log: log,
showNotify: showNotify,
stopTabBlink: stopTabBlink,
getLogInfo: getLogInfo,
ajaxSetup: ajaxSetup,
setSyncStatus: setSyncStatus,
getSyncType: getSyncType,
isXHRAborted: isXHRAborted,
getLabelByRole: getLabelByRole,
getMapElementFromOverlay: getMapElementFromOverlay,
getMapModule: getMapModule,
getSystemEffectData: getSystemEffectData,
getSystemEffectTable: getSystemEffectTable,
getSystemPlanetsTable: getSystemPlanetsTable,
getSystemPilotsTable: getSystemPilotsTable,
getSystemsInfoTable: getSystemsInfoTable,
getStatusInfoForCharacter: getStatusInfoForCharacter,
getSecurityClassForSystem: getSecurityClassForSystem,
getTrueSecClassForSystem: getTrueSecClassForSystem,
getStatusInfoForSystem: getStatusInfoForSystem,
getSignatureGroupInfo: getSignatureGroupInfo,
getAllSignatureNames: getAllSignatureNames,
getSignatureTypeIdByName: getSignatureTypeIdByName,
getAreaIdBySecurity: getAreaIdBySecurity,
setCurrentMapUserData: setCurrentMapUserData,
getCurrentMapUserData: getCurrentMapUserData,
updateCurrentMapUserData: updateCurrentMapUserData,
setCurrentMapData: setCurrentMapData,
getCurrentMapData: getCurrentMapData,
filterCurrentMapData: filterCurrentMapData,
updateCurrentMapData: updateCurrentMapData,
deleteCurrentMapData: deleteCurrentMapData,
setCurrentUserData: setCurrentUserData,
getCurrentUserData: getCurrentUserData,
getCurrentCharacterId: getCurrentCharacterId,
setCurrentSystemData: setCurrentSystemData,
getCurrentSystemData: getCurrentSystemData,
getCurrentLocationData: getCurrentLocationData,
getCurrentUserInfo: getCurrentUserInfo,
getCurrentCharacterLog: getCurrentCharacterLog,
flattenXEditableSelectArray: flattenXEditableSelectArray,
getCharacterDataBySystemId: getCharacterDataBySystemId,
getNearBySystemData: getNearBySystemData,
getNearByCharacterData: getNearByCharacterData,
setDestination: setDestination,
convertDateToUTC: convertDateToUTC,
convertDateToString: convertDateToString,
getOpenDialogs: getOpenDialogs,
openIngameWindow: openIngameWindow,
formatPrice: formatPrice,
formatMassValue: formatMassValue,
getLocalStorage: getLocalStorage,
clearSessionStorage: clearSessionStorage,
getBrowserTabId: getBrowserTabId,
singleDoubleClick: singleDoubleClick,
getObjVal: getObjVal,
redirect: redirect,
logout: logout,
setCookie: setCookie,
getCookie: getCookie
};
});