define([ 'jquery', 'app/init', 'app/util', 'app/map/map', 'app/map/util', 'sortable', 'app/ui/module/system_info', 'app/ui/module/system_graph', 'app/ui/module/system_signature', 'app/ui/module/system_route', 'app/ui/module/system_intel', 'app/ui/module/system_killboard', 'app/ui/module/connection_info', 'app/counter' ], ( $, Init, Util, Map, MapUtil, Sortable, SystemInfoModule, SystemGraphModule, SystemSignatureModule, SystemRouteModule, SystemIntelModule, SystemKillboardModule, ConnectionInfoModule ) => { 'use strict'; let config = { mapTabElementId: 'pf-map-tab-element', // id for map tab element (tabs + content) mapTabBarId: 'pf-map-tabs', // id for map tab bar mapTabIdPrefix: 'pf-map-tab-', // id prefix for a map tab mapTabClass: 'pf-map-tab', // class for a map tab mapTabDragHandlerClass: 'pf-map-tab-handler', // class for drag handler mapTabIconClass: 'pf-map-tab-icon', // class for map icon mapTabLinkTextClass: 'nav-tabs-link', // class for span elements in a tab mapTabSharedIconClass: 'pf-map-tab-shared-icon', // class for map shared icon mapTabContentClass: 'pf-map-tab-content', // class for tab content container mapTabContentSystemInfoClass: 'pf-map-tab-content-system', mapWrapperClass: 'pf-map-wrapper', // scrollable mapClass: 'pf-map', // class for each map // TabContentStructure mapTabContentRow: 'pf-map-content-row', // main row for Tab content (grid) mapTabContentCell: 'pf-map-content-col', // column mapTabContentCellFirst: 'pf-map-content-col-first', // first column mapTabContentCellSecond: 'pf-map-content-col-second', // second column // module moduleClass: 'pf-module', // class for a module moduleSpacerClass: 'pf-module-spacer', // class for "spacer" module (preserves height during hide/show animation) moduleClosedClass: 'pf-module-closed' // class for a closed module }; let mapTabChangeBlocked = false; // flag for preventing map tab switch /** * get all maps for a maps module * @returns {*} */ $.fn.getMaps = function(){ return $(this).find('.' + config.mapClass); }; /** * get the current active mapElement * @returns {JQuery|*|T|{}|jQuery} */ $.fn.getActiveMap = function(){ let map = $(this).find('.active.' + config.mapTabContentClass + ' .' + config.mapClass); if(!map.length){ map = false; } return map; }; /** * set mapContent Observer, events are triggered within map.js * @param tabElement */ let setMapContentObserver = (tabElement) => { tabElement.on('pf:drawSystemModules', '.' + config.mapTabContentClass, function(e){ drawSystemModules($(e.target)); }); tabElement.on('pf:removeSystemModules', '.' + config.mapTabContentClass, function(e){ removeSystemModules($(e.target)); }); tabElement.on('pf:drawConnectionModules', '.' + config.mapTabContentClass, function(e, data){ drawConnectionModules($(e.target), data); }); tabElement.on('pf:removeConnectionModules', '.' + config.mapTabContentClass, function(e){ removeConnectionModules($(e.target)); }); tabElement.on('pf:updateSystemModules', '.' + config.mapTabContentClass, function(e, data){ updateSystemModules($(e.target), data); }); tabElement.on('pf:updateRouteModules', '.' + config.mapTabContentClass, function(e, data){ updateRouteModules($(e.target), data); }); }; /** * update (multiple) modules * @param tabContentElement * @param modules * @param data */ let updateModules = (tabContentElement, modules, data) => { for(let Module of modules){ let moduleElement = tabContentElement.find('.' + Module.config.moduleTypeClass); if(moduleElement.length > 0){ Module.updateModule(moduleElement, data); } } }; /** * update system modules with new data * @param tabContentElement * @param data */ let updateSystemModules = (tabContentElement, data) => { let systemModules = [SystemInfoModule, SystemSignatureModule, SystemIntelModule]; updateModules(tabContentElement, systemModules, data); }; /** * update route modules with new data * @param tabContentElement * @param data */ let updateRouteModules = (tabContentElement, data) => { let routeModules = [SystemRouteModule]; updateModules(tabContentElement, routeModules, data); }; /** * remove multiple modules * @param tabContentElement * @param modules */ let removeModules = (tabContentElement, modules) => { for(let Module of modules){ let moduleElement = tabContentElement.find('.' + Module.config.moduleTypeClass); removeModule(moduleElement, Module); } }; /** * clear all system modules and remove them * @param tabContentElement */ let removeSystemModules = (tabContentElement) => { let systemModules = [SystemInfoModule, SystemGraphModule, SystemSignatureModule, SystemRouteModule, SystemIntelModule, SystemKillboardModule]; removeModules(tabContentElement, systemModules); }; /** * clear all connection modules and remove them * @param tabContentElement */ let removeConnectionModules = (tabContentElement) => { let connectionModules = [ConnectionInfoModule]; removeModules(tabContentElement, connectionModules); }; /** * remove a single module * @param moduleElement * @param Module * @param callback * @param addSpacer */ let removeModule = (moduleElement, Module, callback, addSpacer) => { if(moduleElement.length > 0){ if(typeof Module.beforeHide === 'function'){ Module.beforeHide(moduleElement); } moduleElement.velocity('reverse',{ complete: function(moduleElement){ moduleElement = $(moduleElement); let oldModuleHeight = moduleElement.outerHeight(); if(typeof Module.beforeDestroy === 'function'){ Module.beforeDestroy(moduleElement); } // [optional] add a "spacer"
that fakes Module height during hide->show animation if(addSpacer){ moduleElement.after($('
', { class: [config.moduleSpacerClass, Module.config.moduleTypeClass + '-spacer'].join(' '), css: { height: oldModuleHeight + 'px' } })); } moduleElement.remove(); if(typeof callback === 'function'){ callback(); } } }); } }; /** * generic function that draws a modulePanel for a given Module object * @param parentElement * @param Module * @param mapId * @param data */ let drawModule = (parentElement, Module, mapId, data) => { let drawModuleExecutor = (resolve, reject) => { /** * remove "Spacer" Module * @param parentElement * @param Module */ let removeSpacerModule = (parentElement, Module) => { parentElement.find('.' + Module.config.moduleTypeClass + '-spacer').remove(); }; /** * show/render a Module * @param parentElement * @param Module * @param mapId * @param data */ let showPanel = (parentElement, Module, mapId, data) => { let moduleElement = Module.getModule(parentElement, mapId, data); if (moduleElement) { // store Module object to DOM element for further access moduleElement.data('module', Module); moduleElement.data('data', data); moduleElement.addClass([config.moduleClass, Module.config.moduleTypeClass].join(' ')); moduleElement.css({opacity: 0}); // will be animated // check module position from local storage let promiseStore = MapUtil.getLocaleData('map', mapId); promiseStore.then(function (dataStore) { let Module = this.moduleElement.data('module'); let defaultPosition = Module.config.modulePosition || 0; // hide "spacer" Module (in case it exist) // -> Must be done BEFORE position calculation! Spacer Modules should not be counted! removeSpacerModule(this.parentElement, Module); // check for stored module order in indexDB (client) ---------------------------------------------- let key = 'modules_cell_' + this.parentElement.attr('data-position'); if ( dataStore && dataStore[key] ) { let positionIndex = dataStore[key].indexOf(Module.config.moduleName); if (positionIndex !== -1) { // first index (0) => is position 1 defaultPosition = positionIndex + 1; } } // find correct position for new moduleElement ---------------------------------------------------- let position = getModulePosition(this.parentElement, defaultPosition); this.moduleElement.attr('data-position', defaultPosition); this.moduleElement.attr('data-module', Module.config.moduleName); // insert at correct position --------------------------------------------------------------------- let prevModuleElement = this.parentElement.find('.' + config.moduleClass + ':nth-child(' + position + ')'); if (prevModuleElement.length) { this.moduleElement.insertAfter(prevModuleElement); } else { this.parentElement.prepend(this.moduleElement); } if (typeof Module.beforeShow === 'function') { Module.beforeShow(this.moduleElement, moduleElement.data('data')); } // show animation --------------------------------------------------------------------------------- this.moduleElement.velocity({ opacity: [1, 0], translateY: [0, +20] }, { duration: Init.animationSpeed.mapModule, easing: 'easeOutSine', complete: function (moduleElement) { moduleElement = $(moduleElement); let Module = $(moduleElement).data('module'); if (typeof Module.initModule === 'function') { Module.initModule(moduleElement, mapId, moduleElement.data('data')); } resolve({ action: 'drawModule', data: { module: Module } }); } }); }.bind({ parentElement: parentElement, moduleElement: moduleElement })); }else{ // Module should not be shown (e.g. "Graph" module on WH systems) removeSpacerModule(parentElement, Module); } }; // check if module already exists let moduleElement = parentElement.find('.' + Module.config.moduleTypeClass); if (moduleElement.length > 0) { removeModule(moduleElement, Module, () => { showPanel(parentElement, Module, mapId, data); }, true); } else { showPanel(parentElement, Module, mapId, data); } }; return new Promise(drawModuleExecutor); }; /** * clears and updates the system info element (signature table, system info,...) * @param tabContentElement */ let drawSystemModules = (tabContentElement) => { require(['datatables.loader'], function(){ let currentSystemData = Util.getCurrentSystemData(); let promiseDrawAll = []; // request "additional" system data (e.g. Structures, Description) // -> this is used to update some modules after initial draw let promiseRequestData = MapUtil.requestSystemData({ mapId: currentSystemData.mapId, systemId: currentSystemData.systemData.id }); // draw modules ------------------------------------------------------------------------------------------- let firstCell = tabContentElement.find('.' + config.mapTabContentCellFirst); let secondCell = tabContentElement.find('.' + config.mapTabContentCellSecond); // draw system info module let promiseInfo = drawModule(firstCell, SystemInfoModule, currentSystemData.mapId, currentSystemData.systemData); // draw system graph module drawModule(firstCell, SystemGraphModule, currentSystemData.mapId, currentSystemData.systemData); // draw signature table module let promiseSignature = drawModule(firstCell, SystemSignatureModule, currentSystemData.mapId, currentSystemData.systemData); // draw system routes module drawModule(secondCell, SystemRouteModule, currentSystemData.mapId, currentSystemData.systemData); // draw system intel module let promiseIntel = drawModule(secondCell, SystemIntelModule, currentSystemData.mapId, currentSystemData.systemData); // draw system killboard module drawModule(secondCell, SystemKillboardModule, currentSystemData.mapId, currentSystemData.systemData); // update some modules ------------------------------------------------------------------------------------ promiseDrawAll.push(promiseRequestData, promiseInfo, promiseSignature, promiseIntel); // update "some" modules after draw AND additional system data was requested Promise.all(promiseDrawAll).then(payload => { // get systemData from first Promise (ajax call) let responseData = payload.shift(); if(responseData.data){ let systemData = responseData.data; // update all Modules let modules = []; for(let responseData of payload){ modules.push(responseData.data.module); } updateModules(tabContentElement, modules, systemData); } }); }); }; /** * clears and updates the connection info element (mass log) * @param tabContentElement * @param data */ let drawConnectionModules = (tabContentElement, data) => { require(['datatables.loader'], function(){ // get grid cells let firstCell = $(tabContentElement).find('.' + config.mapTabContentCellFirst); // draw connection info module drawModule(firstCell, ConnectionInfoModule, this.mapId, this.connections); }.bind(data)); }; /** * updates current visible/active mapElement in mapModule with user data * @param mapModule * @returns {Promise} */ let updateActiveMapUserData = (mapModule) => { let updateActiveMapModuleExecutor = (resolve, reject) => { // get all active map elements for module let mapElement = mapModule.getActiveMap(); updateMapUserData(mapElement).then(payload => resolve()); }; return new Promise(updateActiveMapModuleExecutor); }; /** * updates mapElement with user data * update * @param mapElement * @returns {Promise} */ let updateMapUserData = (mapElement) => { // performance logging (time measurement) let logKeyClientUserData = Init.performanceLogging.keyClientUserData; Util.timeStart(logKeyClientUserData); let updateMapUserDataExecutor = (resolve, reject) => { if(mapElement !== false){ let mapId = mapElement.data('id'); let currentMapUserData = Util.getCurrentMapUserData(mapId); if(currentMapUserData){ // trigger "update local" for this map => async mapElement.trigger('pf:updateLocal', currentMapUserData); // update map with current user data mapElement.updateUserData(currentMapUserData); } } resolve(); }; return new Promise(updateMapUserDataExecutor).then(payload => { // log client map update time let duration = Util.timeStop(logKeyClientUserData); Util.log(logKeyClientUserData, {duration: duration, type: 'client', description: 'update users'}); }); }; /** * update active system modules (below map) * @param mapModule * @param systemData */ let updateSystemModulesData = (mapModule, systemData) => { if(systemData){ // check if current open system is still the requested info system let currentSystemData = Util.getCurrentSystemData(); if( currentSystemData && systemData.id === currentSystemData.systemData.id ){ // trigger system update events let tabContentElement = $( '#' + config.mapTabIdPrefix + systemData.mapId + '.' + config.mapTabContentClass); tabContentElement.trigger('pf:updateSystemModules', [systemData]); } } }; /** * set observer for tab content (area where modules will be shown) * @param contentStructure * @param mapId */ let setContentStructureObserver = (contentStructure, mapId) => { contentStructure.find('.' + config.mapTabContentCell).each((index, cellElement) => { let sortable = Sortable.create(cellElement, { group: { name: 'cell_' + cellElement.getAttribute('data-position') }, animation: Init.animationSpeed.mapModule, handle: '.pf-module-handler-drag', draggable: '.' + config.moduleClass, ghostClass: 'pf-sortable-ghost', scroll: true, scrollSensitivity: 50, scrollSpeed: 20, dataIdAttr: 'data-module', sort: true, store: { get: function (sortable) { return []; }, set: function (sortable) { let key = 'modules_' + sortable.options.group.name; MapUtil.storeLocalData('map', mapId, key, sortable.toArray()); } }, onStart: function (e) { // Element dragging started // -> save initial sort state -> see store.set() this.save(); } }); }); // toggle height for a module contentStructure.on('click.toggleModuleHeight', '.' + config.moduleClass, function(e){ let moduleElement = $(this); // get click position let posX = moduleElement.offset().left; let posY = moduleElement.offset().top; let clickX = e.pageX - posX; let clickY = e.pageY - posY; // check for top-left click if(clickX <= 9 && clickY <= 9 && clickX >= 0 && clickY >= 0){ // remember height if( !moduleElement.data('origHeight') ){ moduleElement.data('origHeight', moduleElement.outerHeight()); } if(moduleElement.hasClass(config.moduleClosedClass)){ let moduleHeight = moduleElement.data('origHeight'); moduleElement.velocity('finish').velocity({ height: [ moduleHeight + 'px', [ 400, 15 ] ] },{ duration: 400, easing: 'easeOutSine', complete: function(moduleElement){ moduleElement = $(moduleElement); moduleElement.removeClass(config.moduleClosedClass); moduleElement.removeData('origHeight'); moduleElement.css({height: ''}); } }); }else{ moduleElement.velocity('finish').velocity({ height: [ '35px', [ 400, 15 ] ] },{ duration: 400, easing: 'easeOutSine', complete: function(moduleElement){ moduleElement = $(moduleElement); moduleElement.addClass(config.moduleClosedClass); } }); } } }); }; /** * load all structure elements into a TabsContent div (tab body) * @param tabContentElements */ let initContentStructure = (tabContentElements) => { tabContentElements.each(function(){ let tabContentElement = $(this); let mapId = parseInt( tabContentElement.attr('data-mapid') ); // "add" tab does not need a structure and observer... if(mapId > 0){ let contentStructure = $('
', { class: ['row', config.mapTabContentRow].join(' ') }).append( $('
', { class: ['col-xs-12', 'col-md-8', config.mapTabContentCellFirst, config.mapTabContentCell].join(' ') }).attr('data-position', 1) ).append( $('
', { class: ['col-xs-12', 'col-md-4', config.mapTabContentCellSecond, config.mapTabContentCell].join(' ') }).attr('data-position', 2) ); // append grid structure tabContentElement.append(contentStructure); // set content structure observer setContentStructureObserver(contentStructure, mapId); } }); }; /** * get a fresh tab element * @param options * @param currentUserData * @returns {*|jQuery|HTMLElement} */ let getMapTabElement = (options, currentUserData) => { let tabElement = $('
', { id: config.mapTabElementId }); let tabBar = $('