Files
pathfinder/js/app/map/map.js
Exodus4D ac1a746f12 closed #131 new "route search" algorithm, added current map systems to live search, added refresh/update functionality for each found route, added bulk route refresh function, added "meta map" route search (search on multiple maps), added route "filters" (restrict search on "stargates", "wormholes", "jumpbridges"), added route "filter" for wormholes (reduced/critical wormholes)
closed #89 fixed "loop connections" on same system
#84 added error messages for "invalid" CREST "Client ID"
added "bootboxjs" (customized styled checkboxes/radio buttons) CSS only
"Font Awesome" version upgrade 4.4.0 -> 4.61
"Bootbox.js" version upgrade 4.3.0 -> 4.4.0
fixed "system dialog" (added responsive layout)
2016-04-24 19:59:17 +02:00

3604 lines
128 KiB
JavaScript

/**
* Main map functionality
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox',
'app/ccp',
'app/map/magnetizing',
'dragToSelect',
'select2',
'app/map/contextmenu',
'app/map/overlay'
], function($, Init, Util, Render, bootbox, CCP, MagnetizerWrapper) {
'use strict';
var config = {
zIndexCounter: 110,
newSystemOffset: {
x: 130,
y: 0
},
mapSnapToGridDimension: 20, // px for grid snapping (grid YxY)
mapSnapToGrid: false, // "Snap to Grid" feature for drag&drop systems on map (optional)
mapMagnetizer: false, // "Magnetizer" feature for drag&drop systems on map (optional)
mapTabContentClass: 'pf-map-tab-content', // Tab-Content element (parent element)
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
headMapTrackingId: 'pf-head-map-tracking', // id for "map tracking" toggle (checkbox)
mapClass: 'pf-map', // class for all maps
mapGridClass: 'pf-grid-small', // class for map grid snapping
mapIdPrefix: 'pf-map-', // id prefix for all maps
systemIdPrefix: 'pf-system-', // id prefix for a system
systemClass: 'pf-system', // class for all systems
systemActiveClass: 'pf-system-active', // class for an active system in a map
systemSelectedClass: 'pf-system-selected', // class for selected systems in a map
systemLockedClass: 'pf-system-locked', // class for locked systems in a map
systemHeadClass: 'pf-system-head', // class for system head
systemHeadNameClass: 'pf-system-head-name', // class for system name
systemHeadExpandClass: 'pf-system-head-expand', // class for system head expand arrow
systemBodyClass: 'pf-system-body', // class for system body
systemBodyItemHeight: 16, // px of a system body entry
systemBodyItemClass: 'pf-system-body-item', // class for a system body entry
systemBodyItemStatusClass: 'pf-user-status', // class for player status in system body
systemBodyItemNameClass: 'pf-system-body-item-name', // class for player name in system body
systemBodyRightClass: 'pf-system-body-right', // class for player ship name in system body
systemTooltipInnerClass: 'pf-system-tooltip-inner', // class for system tooltip content
systemTooltipInnerIdPrefix: 'pf-system-tooltip-inner-', // id prefix for system tooltip content
dynamicElementWrapperId: 'pf-dialog-wrapper', // wrapper div for dynamic content (dialogs, context-menus,...)
// endpoint classes
endpointSourceClass: 'pf-map-endpoint-source',
endpointTargetClass: 'pf-map-endpoint-target',
// context menus
mapContextMenuId: 'pf-map-contextmenu',
connectionContextMenuId: 'pf-map-connection-contextmenu',
systemContextMenuId: 'pf-map-system-contextmenu',
// dialogs
systemDialogId: 'pf-system-dialog', // id for system dialog
systemDialogSelectClass: 'pf-system-dialog-select', // class for system select Element
// system security classes
systemSec: 'pf-system-sec',
systemSecHigh: 'pf-system-sec-highSec',
systemSecLow: 'pf-system-sec-lowSec',
systemSecNull: 'pf-system-sec-nullSec',
systemSecWHHeigh: 'pf-system-sec-high',
systemSecWHMid: 'pf-system-sec-mid',
systemSecWHLow: 'pf-system-sec-low'
};
// active jsPlumb instances currently running
var activeInstances = {};
// active connections per map (cache object)
var connectionCache = {};
// characterID => systemIds are cached temporary where the active user character is in
// if a character switches/add system, establish connection with "previous" system
var activeSystemCache = '';
// jsPlumb config
var globalMapConfig = {
source: {
filter: '.' + config.systemHeadNameClass,
//isSource:true,
isTarget:true, // add target Endpoint to each system (e.g. for drag&drop)
allowLoopback: false, // loopBack connections are not allowed
cssClass: config.endpointSourceClass,
uniqueEndpoint: false, // each connection has its own endpoint visible
dragOptions:{
},
connectionsDetachable: true, // dragOptions are set -> allow detaching them
maxConnections: 10, // due to isTarget is true, this is the max count of !out!-going connections
// isSource:true,
anchor: 'Continuous'
},
target: {
filter: '.' + config.systemHeadNameClass,
isSource:true,
//isTarget:true,
//allowLoopback: false, // loopback connections are not allowed
cssClass: config.endpointTargetClass,
dropOptions: {
hoverClass: config.systemActiveClass,
activeClass: 'dragActive'
},
// isTarget:true,
// uniqueEndpoint: false,
anchor: 'Continuous'
},
connectionTypes: Init.connectionTypes
};
/**
* updates a system with current information
* @param map
* @param data
* @param currentUserIsHere boolean - if the current user is in this system
*/
$.fn.updateSystemUserData = function(map, data, currentUserIsHere){
var system = $(this);
var systemId = system.attr('id');
// find system body
var systemBody = $( system.find('.' + config.systemBodyClass) );
// find expand arrow
var systemHeadExpand = $( system.find('.' + config.systemHeadExpandClass) );
var oldCacheKey = system.data('userCache');
var oldUserCount = system.data('userCount');
oldUserCount = (oldUserCount !== undefined ? oldUserCount : 0);
var userCounter = 0;
system.data('currentUser', false);
// if current user is in THIS system trigger event
if(currentUserIsHere){
system.data('currentUser', true);
}
// add user information
if(
data &&
data.user
){
var cacheArray = [];
// loop all active pilots and build cache-key
for(var i = 0; i < data.user.length; i++){
userCounter++;
var tempUserData = data.user[i];
cacheArray.push(tempUserData.id + '_' + tempUserData.log.ship.id);
}
var cacheKey = cacheArray.join('_');
// check for if cacheKey has changed
if(cacheKey !== oldCacheKey){
// set new CacheKey
system.data('userCache', cacheKey);
system.data('userCount', userCounter);
// remove all content
systemBody.empty();
// loop "again" and build DOM object with user information
for(var j = 0; j < data.user.length; j++){
var userData = data.user[j];
var statusClass = Util.getStatusInfoForCharacter(userData, 'class');
var userName = userData.name;
var item = $('<div>', {
class: config.systemBodyItemClass
}).append(
$('<span>', {
text: userData.log.ship.typeName,
class: config.systemBodyRightClass
})
).append(
$('<i>', {
class: ['fa', 'fa-circle', config.systemBodyItemStatusClass, statusClass].join(' ')
})
).append(
$('<span>', {
class: config.systemBodyItemNameClass,
text: userName
})
);
systemBody.append(item);
}
// =================================================================
// user count changed -> change tooltip content
var tooltipOptions = {placement: 'top', trigger: 'manual'};
// set tooltip color
var highlight = false;
var tooltipIconClass = '';
if(userCounter > oldUserCount){
highlight = 'good';
tooltipIconClass = 'fa-caret-up';
}else if(userCounter < oldUserCount){
highlight = 'bad';
tooltipIconClass = 'fa-caret-down';
}
tooltipOptions.id = systemId;
tooltipOptions.highlight = highlight;
tooltipOptions.title = "<i class='fa " + tooltipIconClass + "'></i>";
tooltipOptions.title += '&nbsp;' + userCounter;
// show system head
systemHeadExpand.velocity('stop', true).velocity({
width: '10px'
},{
duration: 50,
display: 'inline-block',
progress: function(){
//revalidate element size and repaint
map.revalidate( systemId );
},
complete: function(){
// show system body
system.toggleBody(true, map, {complete: function(system){
// complete callback function
// show active user tooltip
system.toggleSystemTooltip('show', tooltipOptions);
}});
}
});
}
}else{
// no user data found for this system
system.data('userCache', false);
system.data('userCount', 0);
systemBody.empty();
if(
oldCacheKey &&
oldCacheKey.length > 0
){
// remove tooltip
system.toggleSystemTooltip('destroy', {});
// no user -> clear SystemBody
systemHeadExpand.velocity('stop').velocity('reverse',{
display: 'none',
complete: function(){
system.toggleBody(false, map, {});
}
});
}
}
};
/**
* show/hide system body element
* @param type
* @param map
* @param callback
*/
$.fn.toggleBody = function(type, map, callback){
var system = $(this);
var systemBody = system.find('.' + config.systemBodyClass);
var systemDomId = system.attr('id');
if(type === true){
// show minimal body
systemBody.velocity({
height: config.systemBodyItemHeight + 'px'
},{
duration: 50,
display: 'auto',
progress: function(){
//revalidate element size and repaint
map.revalidate( systemDomId );
},
complete: function(){
map.revalidate( systemDomId );
if(callback.complete){
callback.complete(system);
}
}
});
}else if(type === false){
// hide body
// remove all inline styles -> possible relict from previous hover-extend
systemBody.velocity({
height: 0 + 'px',
width: '100%',
'min-width': 'none'
},{
duration: 50,
display: 'none',
begin: function(){
},
progress: function(){
// revalidate element size and repaint
map.revalidate( systemDomId );
},
complete: function(){
map.revalidate( systemDomId );
}
});
}
};
/**
* show/hide systems tooltip
* @param show
* @param options
*/
$.fn.toggleSystemTooltip = function(show, options){
// tooltip colors
var colorClasses = {
good: 'txt-color-green',
bad: 'txt-color-red'
};
return this.each(function(){
var system = $(this);
var tooltipId = 0;
var tooltipClassHighlight = false;
// do not update tooltips while a system is dragged
if(system.hasClass('jsPlumb_dragged')){
// skip system
return true;
}
if(show === 'destroy'){
system.tooltip( show );
system.removeAttr('data-original-title');
}else if(show === 'hide'){
system.tooltip( show );
} else if(show === 'toggle'){
system.tooltip( show );
}else if(show === 'show'){
// check if tooltip is currently visible
var tooltipActive = (system.attr('aria-describedby') !== undefined);
if(options === undefined){
options = {};
}
// optional color highlight
if(colorClasses.hasOwnProperty( options.highlight )){
tooltipClassHighlight = colorClasses[ options.highlight ];
}
if(
tooltipActive === false &&
options.id
){
// init new tooltip
tooltipId = config.systemTooltipInnerIdPrefix + options.id;
var template = '<div class="tooltip" role="tooltip">' +
'<div class="tooltip-arrow"></div>' +
'<div id="' + tooltipId + '" class="tooltip-inner txt-color ' + config.systemTooltipInnerClass + '"></div>' +
'</div>';
options.html = true;
options.animation = true;
options.template = template;
options.viewport = system.parent('.' + config.mapClass);
system.attr('title', options.title);
system.tooltip(options);
system.tooltip(show);
if(tooltipClassHighlight !== false){
// set tooltip observer and set new class after open -> due to transition effect
system.on('shown.bs.tooltip', function() {
$('#' + tooltipId).addClass( tooltipClassHighlight );
// remove observer -> color should not be changed every time a tooltip toggles e.g. dragging system
$(this).off('shown.bs.tooltip');
});
}
}else{
// update/change/toggle tooltip text or color without tooltip reload
var tooltipInner = false;
if(
options.title ||
tooltipClassHighlight !== false
){
tooltipInner = system.tooltip('fixTitle')
.data('bs.tooltip')
.$tip.find('.tooltip-inner');
if(options.title){
tooltipInner.html( options.title );
}
if(tooltipClassHighlight !== false){
tooltipInner.removeClass( colorClasses.good + ' ' + colorClasses.bad).addClass(tooltipClassHighlight);
}
}
// show() can be forced
if(options.show === true){
system.tooltip('show');
}
}
}
});
};
/**
* set or change the status of a system
* @param status
*/
$.fn.setSystemStatus = function(status){
var system = $(this);
var statusId = Util.getStatusInfoForSystem(status, 'id');
var statusClass = Util.getStatusInfoForSystem(status, 'class');
for(var property in Init.systemStatus) {
if (Init.systemStatus.hasOwnProperty(property)) {
system.removeClass( Init.systemStatus[property].class );
}
}
// add new class
system.data('statusId', statusId);
system.addClass( statusClass );
};
/**
* returns a new system or updates an existing system
* @param map
* @param data
* @returns {HTMLElement}
*/
$.fn.getSystem = function(map, data){
// get map container for mapId information
var mapContainer = $(this);
var systemId = config.systemIdPrefix + mapContainer.data('id') + '-' + data.id;
// check if system already exists
var system = document.getElementById( systemId );
var newPosX = data.position.x + 'px';
var newPosY = data.position.y + 'px';
if(!system){
// set system name or alias
var systemName = data.name;
if(
data.alias &&
data.alias !== ''
){
systemName = data.alias;
}
// get system info classes
var effectBasicClass = Util.getEffectInfoForSystem('effect', 'class');
var effectName = Util.getEffectInfoForSystem(data.effect, 'name');
var effectClass = Util.getEffectInfoForSystem(data.effect, 'class');
var secClass = Util.getSecurityClassForSystem(data.security);
system = $('<div>', {
// system
id: systemId,
class: config.systemClass
}).append(
// system head
$('<div>', {
class: config.systemHeadClass
}).append(
// System name is editable
$('<a>', {
href: '#',
class: config.systemHeadNameClass
}).attr('data-value', systemName)
).append(
// System locked status
$('<i>', {
class: ['fa', 'fa-lock', 'fa-fw'].join(' ')
}).attr('title', 'locked')
).append(
// System effect color
$('<i>', {
class: ['fa', 'fa-square ', 'fa-fw', effectBasicClass, effectClass].join(' ')
}).attr('title', effectName)
).append(
// expand option
$('<i>', {
class: ['fa', 'fa-angle-down ', config.systemHeadExpandClass].join(' ')
})
).prepend(
$('<span>', {
class: [config.systemSec, secClass].join(' '),
text: data.security
})
)
).append(
// system body
$('<div>', {
class: config.systemBodyClass
})
);
// set initial system position
system.css({
'left': newPosX,
'top': newPosY
});
}else{
system = $(system);
// set system position
var currentPosX = system.css('left');
var currentPosY = system.css('top');
if(
newPosX !== currentPosX ||
newPosY !== currentPosY
){
// change position with animation
system.velocity(
{
left: newPosX,
top: newPosY
},{
easing: 'linear',
duration: Init.animationSpeed.mapMoveSystem,
begin: function(system){
// hide system tooltip
$(system).toggleSystemTooltip('hide', {});
// move them to the "top"
$(system).updateSystemZIndex();
},
progress: function(){
map.revalidate( systemId );
},
complete: function(system){
// show tooltip
$(system).toggleSystemTooltip('show', {show: true});
map.revalidate( systemId );
}
}
);
}
// set system alias
var alias = system.getSystemInfo(['alias']);
if(alias !== data.alias){
// alias changed
system.find('.' + config.systemHeadNameClass).editable('setValue', data.alias);
}
}
// set system status
system.setSystemStatus(data.status.name);
system.data('id', parseInt(data.id));
system.data('systemId', parseInt(data.systemId));
system.data('name', data.name);
system.data('typeId', parseInt(data.type.id));
system.data('effect', data.effect);
system.data('security', data.security);
system.data('trueSec', parseFloat(data.trueSec));
system.data('regionId', parseInt(data.region.id));
system.data('region', data.region.name);
system.data('constellationId', parseInt(data.constellation.id));
system.data('constellation', data.constellation.name);
system.data('statics', data.statics);
system.data('updated', parseInt(data.updated.updated));
system.attr('data-mapid', parseInt(mapContainer.data('id')));
// locked system
if( Boolean( system.data( 'locked') ) !== data.locked ){
system.toggleLockSystem(false, {hideNotification: true, hideCounter: true, map: map});
}
// rally system
if( Boolean( system.data( 'rally') ) !== data.rally ){
system.toggleRallyPoint(false, {hideNotification: true, hideCounter: true});
}
return system;
};
/**
* draw a new map or update an existing map with all its systems and connections
* @param parentElement
* @param mapConfig
* @returns {*}
*/
var updateMap = function(parentElement, mapConfig){
var mapContainer = mapConfig.map.getContainer();
var newSystems = 0;
if(mapContainer === undefined){
// add new map
// create map wrapper
var mapWrapper = $('<div>', {
class: config.mapWrapperClass
});
// create new map container
mapContainer = $('<div>', {
id: config.mapIdPrefix + mapConfig.config.id,
class: [config.mapClass].join(' ')
});
// add additional information
mapContainer.data('id', mapConfig.config.id);
mapWrapper.append(mapContainer);
// append mapWrapper to parent element (at the top)
$(parentElement).prepend(mapWrapper);
// set main Container for current map -> the container exists now in DOM !! very important
mapConfig.map.setContainer( config.mapIdPrefix + mapConfig.config.id );
// set map observer
setMapObserver(mapConfig.map);
}
mapContainer = $(mapContainer);
// add additional information for this map
if(mapContainer.data('updated') !== mapConfig.config.updated){
mapContainer.data('name', mapConfig.config.name);
mapContainer.data('scopeId', mapConfig.config.scope.id);
mapContainer.data('typeId', mapConfig.config.type.id);
mapContainer.data('icon', mapConfig.config.icon);
mapContainer.data('created', mapConfig.config.created);
mapContainer.data('updated', mapConfig.config.updated);
}
// get map data
var mapData = mapContainer.getMapDataFromClient({forceData: false});
if(mapData !== false){
// map data available -> map not locked by update counter :)
var currentSystemData = mapData.data.systems;
var currentConnectionData = mapData.data.connections;
// update systems ===========================================================
for(var i = 0; i < mapConfig.data.systems.length; i++){
var systemData = mapConfig.data.systems[i];
// add system
var addNewSystem = true;
for(var k = 0; k < currentSystemData.length; k++){
if(currentSystemData[k].id === systemData.id){
if( currentSystemData[k].updated.updated < systemData.updated.updated ){
// system changed -> update
mapContainer.getSystem(mapConfig.map, systemData);
}
addNewSystem = false;
break;
}
}
if( addNewSystem === true){
drawSystem(mapConfig.map, systemData);
newSystems++;
}
}
// check for systems that are gone -> delete system
for(var a = 0; a < currentSystemData.length; a++){
var deleteThisSystem = true;
for(var b = 0; b < mapConfig.data.systems.length; b++){
var deleteSystemData = mapConfig.data.systems[b];
if(deleteSystemData.id === currentSystemData[a].id){
deleteThisSystem = false;
break;
}
}
if(deleteThisSystem === true){
var deleteSystem = $('#' + config.systemIdPrefix + mapContainer.data('id') + '-' + currentSystemData[a].id);
// system not found -> delete system
removeSystem(mapConfig.map, deleteSystem);
}
}
// update connections =========================================================
// jsPlumb batch() is used, otherwise there are some "strange" visual bugs
// when switching maps (Endpoints are not displayed correctly)
mapConfig.map.batch(function() {
for(var j = 0; j < mapConfig.data.connections.length; j++){
var connectionData = mapConfig.data.connections[j];
// add connection
var addNewConnection= true;
for(var c = 0; c < currentConnectionData.length; c++){
if(
currentConnectionData[c].id === connectionData.id
){
// connection already exists -> check for updates
if(
currentConnectionData[c].updated < connectionData.updated
){
// connection changed -> update
var tempConnection = $().getConnectionById(mapData.config.id, connectionData.id);
updateConnection(tempConnection, currentConnectionData[c], connectionData);
}
addNewConnection = false;
break;
}
}
if(addNewConnection === true){
drawConnection(mapConfig.map, connectionData);
}
}
// check for connections that are gone -> delete connection
for(var d = 0; d < currentConnectionData.length; d++){
var deleteThisConnection = true;
for(var e = 0; e < mapConfig.data.connections.length;e++){
var deleteConnectionData = mapConfig.data.connections[e];
if(deleteConnectionData.id === currentConnectionData[d].id){
deleteThisConnection = false;
break;
}
}
if(deleteThisConnection === true){
// get connection from cache -> delete connection
var deleteConnection = $().getConnectionById(mapData.config.id, currentConnectionData[d].id);
if(deleteConnection){
// check if "source" and "target" still exist before remove
// this is NOT the case if the system was removed previous
if(
deleteConnection.source &&
deleteConnection.target
){
mapConfig.map.detach(deleteConnection, {fireEvent: false});
}
}
}
}
});
// init/update map "magnetization" feature if new systems where added
if(
config.mapMagnetizer === true &&
newSystems > 0
){
mapContainer.initMagnetizer();
}
}
return mapContainer;
};
/**
* update local connection cache
* @param mapId
* @param connection
*/
var updateConnectionCache = function(mapId, connection){
if(
mapId > 0 &&
connection
){
var connectionId = parseInt( connection.getParameter('connectionId') );
if(connectionId > 0){
connectionCache[mapId][connectionId] = connection;
}else{
console.error('updateConnectionCache', 'connectionId missing');
}
}else{
console.error('updateConnectionCache', 'missing data');
}
};
/**
* get a connection object from "cache" (this requires the "connectionCache" cache to be actual!
* @param mapId
* @param connectionId
* @returns {*}
*/
$.fn.getConnectionById = function(mapId, connectionId){
var connection = null;
if(
connectionCache[mapId] &&
connectionCache[mapId][connectionId]
){
connection = connectionCache[mapId][connectionId];
}
return connection;
};
/**
* make all systems appear visual on the map with its connections
* @param show
* @param callback
*/
$.fn.visualizeMap = function(show, callback){
var mapElement = $(this);
// start map update counter -> prevent map updates during animations
mapElement.getMapOverlay('timer').startMapUpdateCounter();
var systemElements = mapElement.find('.' + config.systemClass);
var endpointElements = mapElement.find('.jsplumb-endpoint');
var connectorElements = mapElement.find('.jsplumb-connector');
var overlayElements = mapElement.find('.jsplumb-overlay, .tooltip');
// if map empty (no systems), execute callback and return
// no visual effects in IGB (glitches)
if(
systemElements.length === 0 ||
systemElements.length > 20 ||
endpointElements.length === 0 ||
CCP.isInGameBrowser() === true
){
callback();
return;
}
// show nice animation
if(show === 'show'){
// hide elements
systemElements.css('opacity', 0);
endpointElements.css('opacity', 0);
connectorElements.css('opacity', 0);
overlayElements.css('opacity', 0);
systemElements.velocity('transition.whirlIn', {
stagger: 30,
drag: true,
duration: 100,
//display: 'auto',
complete: function(){
// show connections
endpointElements.velocity('transition.fadeIn', {
duration: 0
});
connectorElements.velocity('transition.fadeIn', {
stagger: 30,
duration: 120,
complete: function(){
callback();
}
});
// show overlay elements (if some exist)
if(overlayElements.length > 0){
overlayElements.delay(500).velocity('transition.fadeIn', {
stagger: 50,
duration: 180,
display: 'auto'
});
}
}
});
}else if(show === 'hide'){
$('.mCSB_container').velocity('callout.shake', {
stagger: 0,
drag: false,
duration: 180,
display: 'auto'
});
overlayElements.velocity('transition.fadeOut', {
stagger: 50,
drag: true,
duration: 180,
display: 'auto'
});
endpointElements.velocity('transition.fadeOut', {
duration: 0,
display: 'block',
complete: function(){
// show connections
connectorElements.velocity('transition.fadeOut', {
stagger: 0,
drag: true,
duration: 20,
display: 'block'
});
systemElements.delay(100).velocity('transition.slideUpOut', {
stagger: 30,
drag: true,
duration: 180,
display: 'block',
complete: function(){
callback();
}
});
}
});
}
};
/**
* mark a system as source
* @param map
* @param system
*/
var makeSource = function(map, system){
// get scope from map defaults
var sourceConfig = globalMapConfig.source;
sourceConfig.scope = map.Defaults.Scope; // set all allowed connections for this scopes
// default connector for initial dragging a new connection
sourceConfig.connector = Util.getScopeInfoForConnection('wh', 'connectorDefinition');
map.makeSource(system, sourceConfig);
};
/**
* mark a system as target
* @param map
* @param system
*/
var makeTarget = function(map, system){
// get scope from map defaults
var targetConfig = globalMapConfig.target;
targetConfig.scope = map.Defaults.Scope; // set all allowed connections for this scopes
map.makeTarget(system, targetConfig);
};
/**
* checks if json system data is valid
* @param systemData
* @returns {boolean}
*/
var isValidSystem = function(systemData){
var isValid = true;
if(
! systemData.hasOwnProperty('name') ||
systemData.name.length === 0
){
return false;
}
return isValid;
};
/**
* draw a system with its data to a map
* @param map
* @param systemData
* @param connectedSystem
*/
var drawSystem = function(map, systemData, connectedSystem){
// check if systemData is valid
if(isValidSystem(systemData)){
var mapContainer = $(map.getContainer());
// get System Element by data
var newSystem = mapContainer.getSystem(map, systemData);
// add new system to map
mapContainer.append(newSystem);
// make new system editable
makeEditable(newSystem);
// make target
makeTarget(map, newSystem);
// make source
makeSource(map, newSystem);
// set system observer
setSystemObserver(map, newSystem);
// connect new system (if connection data is given)
if(connectedSystem){
// hint: "type" will be auto detected by jump distance
var connectionData = {
source: $(connectedSystem).data('id'),
target: newSystem.data('id'),
type: ['wh_fresh'] // default type.
};
var connection = drawConnection(map, connectionData);
// store connection
saveConnection(connection);
}
}
};
/**
* delete a system with all its connections
* (ajax call) remove system from DB
* @param map
* @param systems
* @param callback function
*/
var deleteSystems = function(map, systems, callback){
var mapContainer = $( map.getContainer() );
mapContainer.getMapOverlay('timer').startMapUpdateCounter();
var systemIds = [];
// systemIds for delete request
for(var i = 0; i < systems.length; i++){
systemIds.push( $(systems[i]).data('id') );
}
var requestData = {
systemIds: systemIds
};
$.ajax({
type: 'POST',
url: Init.path.deleteSystem,
data: requestData,
dataType: 'json',
context: {
map: map,
systems: systems
}
}).done(function(){
// deleted SystemIds
var triggerData = {
systemIds: []
};
// remove systems from map
for(var i = 0; i < this.systems.length; i++){
var system = $(this.systems[i]);
triggerData.systemIds.push( system.data('id') );
removeSystem(this.map, system );
}
callback();
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': deleteSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
};
/**
* remove a system from map (no backend requests)
* @param map
* @param system
*/
var removeSystem = function(map, system){
system = $(system);
// check if system is "active"
if( system.hasClass(config.systemActiveClass) ){
// get parent Tab Content and fire clear modules event
var tabContentElement = getTabContentElementByMapElement( system );
$(tabContentElement).trigger('pf:removeSystemModules');
}
// remove endpoints and their connections
// do not fire a "connectionDetached" event
map.detachAllConnections(system, {fireEvent: false});
// hide tooltip
system.toggleSystemTooltip('destroy', {});
// remove system
system.velocity('transition.whirlOut', {
duration: Init.animationSpeed.mapDeleteSystem,
complete: function(){
map.remove(this);
}
});
};
/**
* make a system name/alias editable by x-editable
* @param system
*/
var makeEditable = function(system){
system = $(system);
var headElement = $(system).find('.' + config.systemHeadNameClass);
headElement.editable({
mode: 'popup',
type: 'text',
name: 'alias',
emptytext: system.data('name'),
title: 'System alias',
placement: 'top',
onblur: 'submit',
container: 'body',
toggle: 'manual', // is triggered manually on dblclick
showbuttons: false
});
headElement.on('save', function(e, params) {
// system alias changed -> mark system as updated
system.markAsChanged();
});
};
/**
* update z-index for a system (dragged systems should be always on top)
*/
$.fn.updateSystemZIndex = function(){
return this.each(function(){
// increase global counter
var newZIndexSystem = config.zIndexCounter++;
$(this).css('z-index', newZIndexSystem);
});
};
/**
* get all connections of multiple systems
* @param map
* @param systems
* @returns {Array}
*/
var getConnections = function(map, systems){
var connections = [];
var withBackConnection = false;
$.each(systems, function(i, system){
// get connections where system is source
connections = connections.concat( map.getConnections({source: system}) );
if(withBackConnection === true){
// get connections where system is target
connections = connections.concat( map.getConnections({target: system}) );
}
});
return connections;
};
/**
* get all direct connections between two given systems
* @param map
* @param systemA
* @param systemB
* @returns {Array}
*/
var checkForConnection = function(map, systemA, systemB){
var connections = [];
connections = connections.concat( map.getConnections({scope: '*', source: systemA, target: systemB}) );
// get connections where system is target
connections = connections.concat( map.getConnections({scope: '*', source: systemB, target: systemA}) );
return connections;
};
/**
* connect two systems
* @param mapConfig
* @param connectionData
* @returns new connection
*/
var drawConnection = function(map, connectionData){
var mapContainer = $( map.getContainer() );
var mapId = mapContainer.data('id');
// connection id
var connectionId = 0;
if(connectionData.id){
connectionId = connectionData.id;
}
var connection = map.connect({
source: config.systemIdPrefix + mapId + '-' + connectionData.source,
target: config.systemIdPrefix + mapId + '-' + connectionData.target,
parameters: {
connectionId: connectionId,
updated: connectionData.updated
},
type: null
/* experimental (straight connections)
anchors: [
[ "Perimeter", { shape: 'Rectangle' }],
[ "Perimeter", { shape: 'Rectangle' }]
]
*/
});
// add connection types -----------------------------------------------------
if(connectionData.type){
for(var i = 0; i < connectionData.type.length; i++){
connection.addType(connectionData.type[i]);
}
}
// add connection scope -----------------------------------------------------
// connection have the default map Scope scope
var scope = map.Defaults.Scope;
if(connectionData.scope){
scope = connectionData.scope;
}
setConnectionScope(connection, scope);
// set Observer for new Connection -> is automatically set
return connection;
};
/**
* stores a connection in database
* @param connection
*/
var saveConnection = function(connection){
var map = connection._jsPlumb.instance;
var mapContainer = $( map.getContainer() );
mapContainer.getMapOverlay('timer').startMapUpdateCounter();
var mapId = mapContainer.data('id');
var connectionData = getDataByConnection(connection);
var requestData = {
mapData: {
id: mapId
},
connectionData: connectionData
};
$.ajax({
type: 'POST',
url: Init.path.saveConnection,
data: requestData,
dataType: 'json',
//context: connection
context: {
connection: connection,
map: map,
mapId: mapId
}
}).done(function(newConnectionData){
if( !$.isEmptyObject(newConnectionData) ){
// update connection data e.g. "scope" has auto detected
connection = updateConnection(this.connection, connectionData, newConnectionData);
// new connection should be cached immediately!
updateConnectionCache(this.mapId, connection);
// connection scope
var scope = Util.getScopeInfoForConnection(newConnectionData.scope, 'label');
var title = 'New connection established';
if(connectionData.id > 0){
title = 'Connection switched';
}
Util.showNotify({title: title, text: 'Scope: ' + scope, type: 'success'});
}else{
// some save errors
this.map.detach(this.connection, {fireEvent: false});
}
}).fail(function( jqXHR, status, error) {
// remove this connection from map
this.map.detach(this.connection, {fireEvent: false});
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveConnection', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
};
/**
* delete a connection and all related data
* @param connections
* @param callback
*/
$.fn.deleteConnections = function(connections, callback){
if(connections.length > 0){
// remove connections from map
var removeConnections = function(tempConnections){
for(var i = 0; i < tempConnections.length; i++){
// if a connection is manually (drag&drop) detached, the jsPlumb instance does not exist any more
// connection is already deleted!
if(tempConnections[i]._jsPlumb){
tempConnections[i]._jsPlumb.instance.detach(tempConnections[i], {fireEvent: false});
}
}
};
// prepare delete request
var map = connections[0]._jsPlumb.instance;
var mapContainer = $( map.getContainer() );
mapContainer.getMapOverlay('timer').startMapUpdateCounter();
var connectionIds = [];
// connectionIds for delete request
for(var i = 0; i < connections.length; i++){
var connectionId = connections[i].getParameter('connectionId');
// drag&drop a new connection does not have an id yet, if connection is not established correct
if(connectionId !== undefined){
connectionIds[i] = connections[i].getParameter('connectionId');
}
}
if(connectionIds.length > 0){
var requestData = {
connectionIds: connectionIds
};
$.ajax({
type: 'POST',
url: Init.path.deleteConnection,
data: requestData,
dataType: 'json',
context: connections
}).done(function(data){
// remove connections from map
removeConnections(this);
// optional callback
if(callback){
callback();
}
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': deleteSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
}
}
};
/**
* compares the current data and new data of a connection and updates status
* @param connection
* @param connectionData
* @param newConnectionData
* @returns {*}
*/
var updateConnection = function(connection, connectionData, newConnectionData){
var map = connection._jsPlumb.instance;
var mapContainer = $( map.getContainer() );
var mapId = mapContainer.data('id');
// check id, IDs should never change but must be set after initial save
if(connection.getParameter('connectionId') !== newConnectionData.id){
connection.setParameter('connectionId', newConnectionData.id);
}
// check scope
if(connectionData.scope !== newConnectionData.scope){
setConnectionScope(connection, newConnectionData.scope);
// for some reason the observers are gone after scope change...
setConnectionObserver(map, connection);
}
var addType = $(newConnectionData.type).not(connectionData.type).get();
var removeType = $(connectionData.type).not(newConnectionData.type).get();
// check if source or target has changed
if(connectionData.source !== newConnectionData.source ){
map.setSource(connection, config.systemIdPrefix + mapId + '-' + newConnectionData.source);
}
if(connectionData.target !== newConnectionData.target ){
map.setTarget(connection, config.systemIdPrefix + mapId + '-' + newConnectionData.target);
}
// connection.targetId
// add types
for(var i = 0; i < addType.length; i++){
if(
addType[i].indexOf('fresh') !== -1 ||
addType[i].indexOf('reduced') !== -1 ||
addType[i].indexOf('critical') !== -1
){
setConnectionWHStatus(connection, addType[i]);
}else if( connection.hasType(addType[i]) !== true ){
// additional types e.g. eol, frig, preserve mass
connection.addType(addType[i]);
setConnectionObserver(map, connection);
}
}
// remove types
for(var j = 0; j < removeType.length; j++){
if(
removeType[j] === 'wh_eol' ||
removeType[j] === 'frigate' ||
removeType[j] === 'preserve_mass'
){
connection.removeType(removeType[j]);
setConnectionObserver(map, connection);
}
}
// set update date
connection.setParameter('updated', newConnectionData.updated);
return connection;
};
/**
* set/change connection scope
* @param connection
* @param scope
*/
var setConnectionScope = function(connection, scope){
var map = connection._jsPlumb.instance;
var currentConnector = connection.getConnector();
var newConnector = Util.getScopeInfoForConnection(scope, 'connectorDefinition');
if(currentConnector.type !== newConnector[0]){
// connector has changed
connection.setConnector( newConnector );
// remove all connection types
connection.clearTypes();
// set new new connection type
// if scope changed -> connection type == scope
connection.setType( getDefaultConnectionTypeByScope(scope) );
// change scope
connection.scope = scope;
// new observer is required after scope change
setConnectionObserver(map, connection);
}
};
/**
* get the default connection type for a scope
* e.g. for new type after scope change
* @param scope
* @returns {string}
*/
var getDefaultConnectionTypeByScope = function(scope){
var type = '';
switch(scope){
case 'wh':
type = 'wh_fresh';
break;
case 'jumpbridge':
type = 'jumpbridge';
break;
case'stargate':
type = 'stargate';
break;
}
return type;
};
/**
* set/change connection status of a wormhole
* @param connection
* @param status
*/
var setConnectionWHStatus = function(connection, status){
if(
status === 'wh_fresh' &&
connection.hasType('wh_fresh') !== true
){
connection.removeType('wh_reduced');
connection.removeType('wh_critical');
connection.addType('wh_fresh');
}else if(
status === 'wh_reduced' &&
connection.hasType('wh_reduced') !== true
){
connection.removeType('wh_fresh');
connection.removeType('wh_critical');
connection.addType('wh_reduced');
}else if(
status === 'wh_critical' &&
connection.hasType('wh_critical') !== true
){
connection.removeType('wh_fresh');
connection.removeType('wh_reduced');
connection.addType('wh_critical');
}else if(
status === 'wh_eol' &&
connection.hasType('wh_eol') !== true
){
connection.addType('wh_eol');
}else if(
status === 'wh_eol' &&
connection.hasType('wh_eol') !== true
){
connection.addType('wh_eol');
}
};
/**
* load context menu template for map
*/
var initMapContextMenu = function(){
var moduleConfig = {
name: 'modules/contextmenu',
position: $('#' + config.dynamicElementWrapperId)
};
var moduleData = {
id: config.mapContextMenuId,
items: [
{icon: 'fa-info', action: 'info', text: 'info'},
{icon: 'fa-plus', action: 'add_system', text: 'add system'},
{icon: 'fa-object-ungroup', action: 'select_all', text: 'select all'},
{icon: 'fa-filter', action: 'filter_scope', text: 'filter scope', subitems: [
{subIcon: '', subAction: 'filter_wh', subText: 'wormhole'},
{subIcon: '', subAction: 'filter_stargate', subText: 'stargate'},
{subIcon: '', subAction: 'filter_jumpbridge', subText: 'jumpbridge'}
]},
{divider: true, action: 'delete_systems'},
{icon: 'fa-eraser', action: 'delete_systems', text: 'delete systems'}
]
};
Render.showModule(moduleConfig, moduleData);
};
/**
* load contextmenu template for connections
*/
var initConnectionContextMenu = function(){
var moduleConfig = {
name: 'modules/contextmenu',
position: $('#' + config.dynamicElementWrapperId)
};
var moduleData = {
id: config.connectionContextMenuId,
items: [
{icon: 'fa-plane', action: 'frigate', text: 'frigate hole'},
{icon: 'fa-warning', action: 'preserve_mass', text: 'preserve mass'},
{icon: 'fa-crosshairs', action: 'change_scope', text: 'change scope', subitems: [
{subIcon: 'fa-minus-circle', subIconClass: '', subAction: 'scope_wh', subText: 'wormhole'},
{subIcon: 'fa-minus-circle', subIconClass: 'txt-color txt-color-indigoDarkest', subAction: 'scope_stargate', subText: 'stargate'},
{subIcon: 'fa-minus-circle', subIconClass: 'txt-color txt-color-tealLighter', subAction: 'scope_jumpbridge', subText: 'jumpbridge'}
]},
{icon: 'fa-reply fa-rotate-180', action: 'change_status', text: 'change status', subitems: [
{subIcon: 'fa-clock-o', subAction: 'wh_eol', subText: 'toggle EOL'},
{subDivider: true},
{subIcon: 'fa-circle', subAction: 'status_fresh', subText: 'stage 0 (fresh)'},
{subIcon: 'fa-adjust', subAction: 'status_reduced', subText: 'stage 1 (reduced)'},
{subIcon: 'fa-circle-o', subAction: 'status_critical', subText: 'stage 2 (critical)'}
]},
{divider: true, action: 'delete_connection'},
{icon: 'fa-eraser', action: 'delete_connection', text: 'delete'}
]
};
Render.showModule(moduleConfig, moduleData);
};
/**
* load contextmenu template for systems
*/
var initSystemContextMenu = function(){
var systemStatus = [];
$.each(Init.systemStatus, function(status, statusData){
var tempStatus = {
subIcon: 'fa-tag',
subIconClass: statusData.class,
subAction: 'change_status_' + status,
subText: statusData.label
};
systemStatus.push(tempStatus);
});
var moduleConfig = {
name: 'modules/contextmenu',
position: $('#' + config.dynamicElementWrapperId)
};
var moduleData = {
id: config.systemContextMenuId,
items: [
{icon: 'fa-plus', action: 'add_system', text: 'add system'},
{icon: 'fa-lock', action: 'lock_system', text: 'lock system'},
{icon: 'fa-users', action: 'set_rally', text: 'set rally point'},
{icon: 'fa-tags', text: 'set status', subitems: systemStatus},
{divider: true, action: 'ingame'},
{icon: 'fa-reply fa-rotate-180', action: 'ingame', text: 'ingame actions', subitems: [
{subIcon: 'fa-info', subAction: 'ingame_show_info', subText: 'show info'},
{subDivider: true, action: 'ingame'},
{subIcon: 'fa-flag', subAction: 'ingame_add_waypoint', subText: 'add waypoint'},
{subIcon: 'fa-flag-checkered', subAction: 'ingame_set_destination', subText: 'set destination'}
]},
{divider: true, action: 'delete_system'},
{icon: 'fa-eraser', action: 'delete_system', text: 'delete system'}
]
};
Render.showModule(moduleConfig, moduleData);
};
/**
* set up all actions that can be preformed on a system
* @param map
* @param system
*/
var setSystemObserver = function(map, system){
system = $(system);
// get map container
var mapContainer = $( map.getContainer() );
var systemHeadExpand = $( system.find('.' + config.systemHeadExpandClass) );
var systemBody = $( system.find('.' + config.systemBodyClass) );
// map overlay will be set on "drag" start
var mapOverlayTimer = null;
// make system draggable
map.draggable(system, {
containment: 'parent',
constrain: true,
//scroll: true, // not working because of customized scrollbar
filter: '.' + config.systemHeadNameClass, // disable drag on "system name"
snapThreshold: config.mapSnapToGridDimension, // distance for grid snapping "magnet" effect (optional)
start: function(params, a, b){
var dragSystem = $(params.el);
mapOverlayTimer = dragSystem.getMapOverlay('timer');
// start map update timer
mapOverlayTimer.startMapUpdateCounter();
// check if grid-snap is enable
if(config.mapSnapToGrid){
params.drag.params.grid = [config.mapSnapToGridDimension, config.mapSnapToGridDimension];
}else{
delete( params.drag.params.grid );
}
// stop "system click event" right after drop event is finished
dragSystem.addClass('no-click');
// drag system is not always selected
var selectedSystems = mapContainer.getSelectedSystems().get();
selectedSystems = selectedSystems.concat( dragSystem.get() );
selectedSystems = $.unique( selectedSystems );
// hide tooltip
$(selectedSystems).toggleSystemTooltip('hide', {});
// move them to the "top"
$(selectedSystems).updateSystemZIndex();
},
drag: function(p){
// start map update timer
mapOverlayTimer.startMapUpdateCounter();
// update system positions for "all" systems that are effected by drag&drop
// this requires "magnet" feature to be active! (optional)
MagnetizerWrapper.executeAtEvent(map, p.e);
},
stop: function(params){
var dragSystem = $(params.el);
// start map update timer
mapOverlayTimer.startMapUpdateCounter();
setTimeout(function(){
dragSystem.removeClass('no-click');
}, Init.timer.DBL_CLICK + 50);
// show tooltip
dragSystem.toggleSystemTooltip('show', {show: true});
// mark as "changed"
dragSystem.markAsChanged();
// set new position for popover edit field (system name)
var newPosition = dragSystem.position();
var placement = 'top';
if(newPosition.top < 100){
placement = 'bottom';
}
if(newPosition.left < 100){
placement = 'right';
}
dragSystem.find('.' + config.systemHeadNameClass).editable('option', 'placement', placement);
// drag system is not always selected
var selectedSystems = mapContainer.getSelectedSystems().get();
selectedSystems = selectedSystems.concat( dragSystem.get() );
selectedSystems = $.unique( selectedSystems );
for(var i = 0; i < selectedSystems.length; i++){
var tempSystem = $(selectedSystems[i]);
// repaint connections -> just in case something fails...
map.revalidate( tempSystem.attr('id') );
}
}
});
if(system.data('locked') === true){
map.setDraggable(system, false);
}
// init system tooltips =============================================================================
var systemTooltipOptions = {
toggle: 'tooltip',
placement: 'right',
container: 'body',
viewport: system.id
};
system.find('.fa').tooltip(systemTooltipOptions);
// init system body expand ==========================================================================
systemHeadExpand.hoverIntent(function(e){
// hover in
var hoverSystem = $(this).parents('.' + config.systemClass);
var hoverSystemId = hoverSystem.attr('id');
// get ship counter and calculate expand height
var userCount = parseInt( hoverSystem.data('userCount') );
var expandHeight = userCount * config.systemBodyItemHeight;
systemBody.velocity('stop').velocity(
{
height: expandHeight + 'px',
width: 150,
'min-width': '150px'
},{
easing: 'easeInOutQuart',
duration: 150,
progress: function(){
// repaint connections of current system
map.revalidate( hoverSystemId );
},
complete: function(){
map.revalidate( hoverSystemId );
// extend player name element
$(this).find('.' + config.systemBodyItemNameClass).css({width: '80px'});
$(this).find('.' + config.systemBodyRightClass).velocity('stop').velocity({
opacity: 1
},{
duration: 150,
display: 'auto'
});
}
}
);
}, function(e){
// hover out
var hoverSystem = $(this).parents('.' + config.systemClass);
var hoverSystemId = hoverSystem.attr('id');
// stop animation (prevent visual bug if user spams hover-icon [in - out])
systemBody.velocity('stop');
// reduce player name element back to "normal" size (css class width is used)
systemBody.find('.' + config.systemBodyItemNameClass).css({width: ''});
systemBody.find('.' + config.systemBodyRightClass).velocity('stop').velocity( {
opacity: 0,
'min-width': '0px'
},{
easing: 'easeInOutQuart',
duration: 150,
display: 'none',
complete: function(){
systemBody.velocity('stop').velocity('reverse', {
complete: function(){
// overwrite "complete" function from first "hover"-open
map.revalidate( hoverSystemId );
}
});
}
});
});
// context menu =====================================================================================
// trigger context menu
system.off('contextmenu').on('contextmenu', function(e){
e.preventDefault();
e.stopPropagation();
var systemElement = $(this);
// hide all map contextmenu options
var hideOptions = getHiddenContextMenuOptions(systemElement);
var activeOptions = getActiveContextMenuOptions(systemElement);
$(e.target).trigger('pf:openContextMenu', [e, this, hideOptions, activeOptions]);
return false;
});
// init context menu
system.contextMenu({
menuSelector: "#" + config.systemContextMenuId,
menuSelected: function (params) {
// click action
var action = params.selectedMenu.attr('data-action');
// current system
var currentSystem = $(params.component);
// system name
var currentSystemName = currentSystem.getSystemInfo( ['alias'] );
var systemData = {};
switch(action){
case 'add_system':
// add a new system
showNewSystemDialog(map, {sourceSystem: currentSystem} );
break;
case 'lock_system':
// lock system
currentSystem.toggleLockSystem(true, {map: map});
// repaint connections, -> system changed its size!
map.repaint( currentSystem );
currentSystem.markAsChanged();
break;
case 'set_rally':
// set rally point
if( ! currentSystem.data( 'rally' ) ){
// show confirm dialog
var rallyDialog = bootbox.dialog({
message: 'Do you want to poke active pilots?',
title: 'Set rally point for system "' + currentSystemName + '"',
buttons: {
close: {
label: 'cancel',
className: 'btn-default',
callback: function(){
$(rallyDialog).modal('hide');
}
},
setRallyPoke: {
label: '<i class="fa fa-fw fa-bullhorn"></i> Set rally and poke',
className: 'btn-primary',
callback: function() {
currentSystem.toggleRallyPoint(true, {});
currentSystem.markAsChanged();
}
},
success: {
label: '<i class="fa fa-fw fa-check"></i> save',
className: 'btn-success',
callback: function() {
currentSystem.toggleRallyPoint(false, {});
currentSystem.markAsChanged();
}
}
}
});
}else{
// remove rally point
currentSystem.toggleRallyPoint(false, {});
currentSystem.markAsChanged();
}
break;
case 'change_status_unknown':
case 'change_status_friendly':
case 'change_status_occupied':
case 'change_status_hostile':
case 'change_status_empty':
case 'change_status_unscanned':
// change system status
currentSystem.getMapOverlay('timer').startMapUpdateCounter();
var statusString = action.split('_');
currentSystem.setSystemStatus(statusString[2]);
currentSystem.markAsChanged();
break;
case 'delete_system':
// confirm dialog
bootbox.confirm('Delete system and all its connections?', function(result) {
if(result){
var systemName = currentSystem.getSystemInfo(['alias']);
deleteSystems(map, [currentSystem], function(){
// callback function after delete -> close dialog
bootbox.hideAll();
Util.showNotify({title: 'System deleted', text: systemName, type: 'success'});
});
return false;
}
});
break;
case 'ingame_show_info':
systemData = system.getSystemData();
CCPEVE.showInfo(5, systemData.systemId );
break;
case 'ingame_set_destination':
systemData = system.getSystemData();
CCPEVE.setDestination( systemData.systemId );
break;
case 'ingame_add_waypoint':
systemData = system.getSystemData();
CCPEVE.addWaypoint( systemData.systemId );
break;
}
}
});
// system click events ==============================================================================
var double = function(e){
var system = $(this);
var headElement = $(system).find('.' + config.systemHeadNameClass);
// update z-index for system, editable field should be on top
// move them to the "top"
$(system).updateSystemZIndex();
// show "set alias" input (x-editable)
headElement.editable('show');
};
var single = function(e){
// check if click was performed on "popover" (x-editable)
var popoverClick = false;
if( $(e.target).parents('.popover').length ){
popoverClick = true;
}
// continue if click was *not* on a popover dialog of a system
if( !popoverClick ){
var system = $(this);
// check if system is locked for "click" events
if( !system.hasClass('no-click') ){
// left mouse button
if(e.which === 1){
if(! system.hasClass('no-click')){
if(e.ctrlKey === true){
// select system
system.toggleSelectSystem(map);
}else{
system.showSystemInfo(map);
}
}
}
}
}
};
system.singleDoubleClick(single, double);
};
/**
* mark a dom element (map, system, connection) as changed
*/
$.fn.markAsChanged = function(){
return this.each(function(){
var element = $(this);
if( element.hasClass(config.systemClass) ){
// system element
element.data('updated', 0);
}else{
// connection element
this.setParameter('updated', 0);
}
});
};
/**
* check if an dom element (system, connection) has changed
* @returns {boolean}
*/
$.fn.hasChanged = function(){
var element = $(this);
var changed = false;
if( element.hasClass(config.systemClass) ){
// system element
changed = (element.data('updated') === 0);
}else{
// connection element
changed = (this[0].getParameter('updated') === 0);
}
return changed;
};
/**
* triggers the "showSystemInfo" event, that is responsible for initializing the "map info" panel
* @param map
*/
$.fn.showSystemInfo = function(map){
var system = $(this);
// activate system
markSystemActive(map, system);
// get parent Tab Content and fire update event
var tabContentElement = getTabContentElementByMapElement( system );
// collect all required data from map module to update the info element
// store them global and assessable for each module
var currentSystemData = {
systemData: system.getSystemData(),
mapId: parseInt( system.attr('data-mapid') )
};
Util.setCurrentSystemData(currentSystemData);
$(tabContentElement).trigger('pf:drawSystemModules');
};
/**
* toggle selectable status of a system
*/
$.fn.toggleSelectSystem = function(map){
return this.each(function(){
var system = $(this);
if( system.data('locked') !== true ){
if( system.hasClass( config.systemSelectedClass ) ){
system.removeClass( config.systemSelectedClass );
map.removeFromDragSelection(system);
}else{
system.addClass( config.systemSelectedClass );
map.addToDragSelection(system);
}
}
});
};
/**
* get all selected (NOT active) systems in a map
* @returns {*}
*/
$.fn.getSelectedSystems = function(){
var mapElement = $(this);
return mapElement.find('.' + config.systemSelectedClass);
};
/**
* toggle log status of a system
* @param poke
* @param options
*/
$.fn.toggleLockSystem = function(poke, options){
var system = $(this);
var map = options.map;
var hideNotification = false;
if(options.hideNotification === true){
hideNotification = true;
}
var hideCounter = false;
if(options.hideCounter === true){
hideCounter = true;
}
var systemName = system.getSystemInfo( ['alias'] );
if( system.data( 'locked' ) === true ){
system.data('locked', false);
system.removeClass( config.systemLockedClass );
// enable draggable
map.setDraggable(system, true);
if(! hideNotification){
Util.showNotify({title: 'System unlocked', text: systemName, type: 'unlock'});
}
}else{
system.data('locked', true);
system.addClass( config.systemLockedClass );
// enable draggable
map.setDraggable(system, false);
if(! hideNotification){
Util.showNotify({title: 'System locked', text: systemName, type: 'lock'});
}
}
// repaint connections
map.revalidate( system.attr('id') );
if(! hideCounter){
$(system).getMapOverlay('timer').startMapUpdateCounter();
}
};
/**
* toggle a system as rally point and display notifications
* @param poke
* @param options
*/
$.fn.toggleRallyPoint = function(poke, options){
var system = $(this);
var rallyClass = Util.getInfoForSystem('rally', 'class');
var hideNotification = false;
if(options.hideNotification === true){
hideNotification = true;
}
var hideCounter = false;
if(options.hideCounter === true){
hideCounter = true;
}
// check of system is already marked as rally
if( system.data( 'rally' ) === true ){
system.removeClass( rallyClass );
system.data( 'rally', false );
if(! hideNotification){
Util.showNotify({title: 'Rally point removed', type: 'success'});
}
}else{
system.addClass( rallyClass );
system.data( 'rally', true );
if(! hideNotification){
var systemName = system.getSystemInfo( ['alias'] );
var notificationOptions = {
title: 'Rally Point',
text: 'System: ' + systemName,
type: 'success'
};
if(poke === true){
// desktop poke
Util.showNotify(notificationOptions, {desktop: true, stack: 'barBottom'});
}else{
Util.showNotify(notificationOptions, {stack: 'barBottom'});
}
}
}
if(! hideCounter){
$(system).getMapOverlay('timer').startMapUpdateCounter();
}
};
/**
* get TabContentElement by any element on a map e.g. system
* @param element
* @returns {*}
*/
var getTabContentElementByMapElement = function(element){
var tabContentElement = $(element).parents('.' + config.mapTabContentClass);
return tabContentElement;
};
/**
* set observer for a map container
* @param map
*/
var setMapObserver = function(map){
// get map container
var mapContainer = map.getContainer();
$(mapContainer).bind('contextmenu', function(e){
e.preventDefault();
e.stopPropagation();
// make sure map is clicked and NOT a connection
if($(e.target).hasClass( config.mapClass )){
var mapElement = $(this);
var hideOptions = getHiddenContextMenuOptions(mapElement);
var activeOptions = getActiveContextMenuOptions(mapElement);
$(e.target).trigger('pf:openContextMenu', [e, mapElement, hideOptions, activeOptions]);
}
return false;
});
$(mapContainer).contextMenu({
menuSelector: "#" + config.mapContextMenuId,
menuSelected: function (params) {
// click action
var action = params.selectedMenu.attr('data-action');
// current map
var currentMapElement = $(params.component);
var currentMapId = parseInt( currentMapElement.data('id') );
// get map instance
var currentMap = getMapInstance(currentMapId);
// click position
var position = params.position;
switch(action){
case 'add_system':
// add new system dialog
showNewSystemDialog(currentMap, {position: position});
break;
case 'select_all':
var allSystems = currentMapElement.find('.' + config.systemClass + ':not(.' + config.systemSelectedClass + ')');
// filter non-locked systems
allSystems = allSystems.filter(function(i, el){
return ( $(el).data('locked') !== true );
});
allSystems.toggleSelectSystem(currentMap);
Util.showNotify({title: allSystems.length + ' systems selected', type: 'success'});
break;
case 'filter_wh':
case 'filter_stargate':
case 'filter_jumpbridge':
// filter (show/hide)
var filterScope = action.split('_')[1];
// scope label
var filterScopeLabel = Util.getScopeInfoForMap(filterScope, 'label');
var showScope = true;
if(
currentMapElement.data('filter_scope') &&
currentMapElement.data('filter_scope') === filterScope
){
// remove scope filter
currentMapElement.data('filter_scope', false);
showScope = false;
// hide map overlay filter info
currentMapElement.getMapOverlay('info').updateOverlayIcon('filter', 'hide');
}else{
// set scope filter
currentMapElement.data('filter_scope', filterScope);
// show map overlay filter info
currentMapElement.getMapOverlay('info').updateOverlayIcon('filter', 'show');
}
var filteredConnections = currentMap.getAllConnections(filterScope);
for(var i = 0; i < filteredConnections.length; i++){
var tempConnection = filteredConnections[i];
var tempEndpoints = tempConnection.endpoints;
var setVisible = true;
if(
showScope &&
tempConnection.scope !== filterScope
){
setVisible = false;
}
for(var j = 0; j < tempEndpoints.length; j++){
tempEndpoints[j].setVisible( setVisible );
}
}
Util.showNotify({title: 'Scope filter changed', text: filterScopeLabel, type: 'success'});
break;
case 'delete_systems':
// delete all selected systems with its connections
var selectedSystems = $(currentMapElement).getSelectedSystems();
if(selectedSystems.length > 0){
var systemDeleteDialog = bootbox.confirm('Delete ' + selectedSystems.length + ' selected systems and its connections?', function(result) {
if(result){
currentMapElement.getMapOverlay('timer').startMapUpdateCounter();
deleteSystems(currentMap, selectedSystems, function(){
// callback function after delete -> close dialog
$(systemDeleteDialog).modal('hide');
Util.showNotify({title: selectedSystems.length + ' systems deleted', type: 'success'});
});
}
});
}else{
Util.showNotify({title: 'No systems selected', type: 'error'});
}
break;
case 'info':
// open map info dialog
$(document).triggerMenuEvent('ShowMapInfo');
break;
}
}
});
// init drag-frame selection
$(mapContainer).dragToSelect({
selectables: '.' + config.systemClass,
onHide: function (selectBox, deselectedSystems) {
var selectedSystems = $(mapContainer).getSelectedSystems();
if(selectedSystems.length > 0){
// make all selected systems draggable
Util.showNotify({title: selectedSystems.length + ' systems selected', type: 'success'});
// convert former group draggable systems so single draggable
for(var i = 0; i < selectedSystems.length; i++){
map.addToDragSelection( selectedSystems[i] );
}
}
// convert former group draggable systems so single draggable
for(var j = 0; j < deselectedSystems.length; j++){
map.removeFromDragSelection( deselectedSystems[j] );
}
},
onShow: function(){
$(document).trigger('pf:closeMenu', [{}]);
},
onRefresh: function(){
}
});
// catch events =========================================================
// toggle global map option (e.g. "grid snap", "magnetization"
$(mapContainer).on('pf:menuMapOption', function(e, data){
var currentMapElement = $(this);
// toggle map option
config[data.option] = !config[data.option];
// toggle map class (e.g. for grid)
if(data.class){
currentMapElement.toggleClass( config[data.class] );
}
// toggle button class
$(data.button).toggleClass('active');
var notificationText = 'disabled';
if( config[data.option] ){
// call optional jQuery extension on mapElement
if(data.onEnable){
$.fn[ data.onEnable ].apply( currentMapElement );
}
// show map overlay info icon
notificationText = 'enabled';
currentMapElement.getMapOverlay('info').updateOverlayIcon(data.option, 'show');
}else{
// call optional jQuery extension on mapElement
if(data.onDisable){
$.fn[ data.onDisable ].apply( currentMapElement );
}
// hide map overlay info icon
currentMapElement.getMapOverlay('info').updateOverlayIcon(data.option, 'hide');
}
Util.showNotify({title: data.description, text: notificationText, type: 'info'});
});
// delete system event
// triggered from "map info" dialog scope
$(mapContainer).on('pf:deleteSystems', function(e, data){
deleteSystems(map, data.systems, data.callback);
});
$(mapContainer).on('pf:menuSelectSystem', function(e, data){
var tempMapContainer = $(this);
var systemId = config.systemIdPrefix + tempMapContainer.data('id') + '-' + data.systemId;
var system = $(this).find('#' + systemId);
if(system.length === 1){
// scroll to system
var tempMapWrapper = tempMapContainer.parents('.' + config.mapWrapperClass);
tempMapWrapper.scrollTo(system);
// select system
system.showSystemInfo(map);
}
});
};
/**
* get hidden menu entry options for a context menu
* @param component
* @returns {Array}
*/
var getHiddenContextMenuOptions = function(component){
var hiddenOptions = [];
if(component instanceof jsPlumb.Connection){
// disable connection menu entries
var scope = component.scope;
if(scope === 'stargate'){
hiddenOptions.push('frigate');
hiddenOptions.push('preserve_mass');
hiddenOptions.push('change_status');
hiddenOptions.push('scope_stargate');
}else if(scope === 'jumpbridge'){
hiddenOptions.push('frigate');
hiddenOptions.push('preserve_mass');
hiddenOptions.push('change_status');
hiddenOptions.push('scope_jumpbridge');
}else if(scope === 'wh'){
hiddenOptions.push('scope_wh');
}
}else if( component.hasClass(config.systemClass) ){
// disable system menu entries
if(component.data('locked') === true){
hiddenOptions.push('delete_system');
}
// disable ingame options if not IGB browser
if(! CCP.isInGameBrowser() ){
hiddenOptions.push('ingame');
}
}
return hiddenOptions;
};
/**
* get active menu entry options for a context menu
* @param component
* @returns {Array}
*/
var getActiveContextMenuOptions = function(component){
var activeOptions = [];
if(component instanceof jsPlumb.Connection){
var scope = component.scope;
if(component.hasType('wh_eol') === true){
activeOptions.push('wh_eol');
}
if(component.hasType('frigate') === true){
activeOptions.push('frigate');
}
if(component.hasType('preserve_mass') === true){
activeOptions.push('preserve_mass');
}
if(component.hasType('wh_reduced') === true){
activeOptions.push('status_reduced');
}else if(component.hasType('wh_critical') === true){
activeOptions.push('status_critical');
}else{
// not reduced is default
activeOptions.push('status_fresh');
}
}else if( component.hasClass(config.mapClass) ){
// active map menu entries
if(component.data('filter_scope') === 'wh'){
activeOptions.push('filter_wh');
}
if(component.data('filter_scope') === 'stargate'){
activeOptions.push('filter_stargate');
}
if(component.data('filter_scope') === 'jumpbridge'){
activeOptions.push('filter_jumpbridge');
}
}else if( component.hasClass(config.systemClass) ){
// active system menu entries
if(component.data('locked') === true){
activeOptions.push('lock_system');
}
if(component.data('rally') === true){
activeOptions.push('set_rally');
}
}
return activeOptions;
};
/**
* set observer for a given connection
* @param map
* @param connection
*/
var setConnectionObserver = function(map, connection){
// get map container
var mapElement = $( map.getContainer() );
// if the connection already exists -> do not set it twice
connection.unbind('contextmenu').bind('contextmenu', function(component, e) {
e.preventDefault();
e.stopPropagation();
// trigger menu "open
// get invisible menu entries
var hideOptions = getHiddenContextMenuOptions(component);
var activeOptions = getActiveContextMenuOptions(component);
$(e.target).trigger('pf:openContextMenu', [e, component, hideOptions, activeOptions]);
return false;
});
/**
* init context menu for all connections
* must be triggered manually on demand
*/
$(connection.canvas).contextMenu({
menuSelector: "#" + config.connectionContextMenuId,
menuSelected: function (params){
var action = params.selectedMenu.attr('data-action');
var activeConnection = params.component;
var activeScope = activeConnection.scope;
var activeScopeName = Util.getScopeInfoForConnection( activeScope, 'label');
switch(action){
case 'delete_connection':
// delete a single connection
// confirm dialog
bootbox.confirm('Is this connection really gone?', function(result) {
if(result){
$().deleteConnections([activeConnection]);
}
});
break;
case 'frigate': // set as frigate hole
case 'preserve_mass': // set "preserve mass
case 'wh_eol': // set "end of life"
mapElement.getMapOverlay('timer').startMapUpdateCounter();
activeConnection.toggleType( action );
$(activeConnection).markAsChanged();
break;
case 'status_fresh':
case 'status_reduced':
case 'status_critical':
var newStatus = action.split('_')[1];
mapElement.getMapOverlay('timer').startMapUpdateCounter();
setConnectionWHStatus(activeConnection, 'wh_' + newStatus);
$(activeConnection).markAsChanged();
break;
case 'scope_wh':
case 'scope_stargate':
case 'scope_jumpbridge':
var newScope = action.split('_')[1];
var newScopeName = Util.getScopeInfoForConnection( newScope, 'label');
bootbox.confirm('Change scope from ' + activeScopeName + ' to ' + newScopeName + '?', function(result) {
if(result){
mapElement.getMapOverlay('timer').startMapUpdateCounter();
setConnectionScope(activeConnection, newScope);
Util.showNotify({title: 'Connection scope changed', text: 'New scope: ' + newScopeName, type: 'success'});
$(activeConnection).markAsChanged();
}
});
break;
}
}
});
};
/**
* mark a system as active
* @param map
* @param system
*/
var markSystemActive = function(map, system){
// deactivate all systems in map
var mapContainer = $( map.getContainer() );
mapContainer.find('.' + config.systemClass).removeClass(config.systemActiveClass);
// set current system active
system.addClass(config.systemActiveClass);
};
/**
* get system data out of its object
* @param info
* @returns {*}
*/
$.fn.getSystemInfo = function(info){
var systemInfo = [];
for(var i = 0; i < info.length; i++){
switch(info[i]){
case 'alias':
// get current system alias
var systemHeadNameElement = $(this).find('.' + config.systemHeadNameClass);
var alias = '';
if( systemHeadNameElement.hasClass('editable') ){
// xEditable is initiated
alias = systemHeadNameElement.editable('getValue', true);
}
systemInfo.push(alias );
break;
default:
systemInfo.push('bad system query');
}
}
if(systemInfo.length === 1){
return systemInfo[0];
}else{
return systemInfo;
}
};
/**
* open "new system" dialog and add the system to map
* optional the new system is connected to a "sourceSystem" (if available)
*
* @param map
* @param options
*/
var showNewSystemDialog = function(map, options){
var mapContainer = $(map.getContainer());
// format system status for form select ------------------------------------------------------------------------
var systemStatus = {};
$.each(Init.systemStatus, function(status, statusData){
systemStatus[statusData.id] = statusData.label;
});
// default system status -> first status entry
var tempKeys = [];
for(var k in Init.systemStatus){
if (Init.systemStatus.hasOwnProperty(k)){
tempKeys.push(k);
}
}
var defaultSystemStatus = Init.systemStatus[ tempKeys[0] ].id;
// get current map data -> disable systems that are already on it ----------------------------------------------
var mapData = mapContainer.getMapDataFromClient({forceData: true});
var mapSystems = mapData.data.systems;
var mapSystemIds = [];
for(var i = 0; i < mapSystems.length; i++ ){
mapSystemIds.push( mapSystems[i].systemId );
}
// dialog data -------------------------------------------------------------------------------------------------
var data = {
id: config.systemDialogId,
selectClass: config.systemDialogSelectClass
};
// set current position as "default" system to add -------------------------------------------------------------
var currentCharacterLog = Util.getCurrentCharacterLog();
if(
currentCharacterLog !== false &&
mapSystemIds.indexOf( currentCharacterLog.system.id ) === -1
){
// current system is NOT already on this map
// set current position as "default" system to add
data.currentSystem = currentCharacterLog.system;
}
requirejs(['text!templates/dialog/system.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
// disable modal focus event -> otherwise select2 is not working! -> quick fix
$.fn.modal.Constructor.prototype.enforceFocus = function() {};
var systemDialog = bootbox.dialog({
title: 'Add new system',
message: content,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fa fa-fw fa-check"></i> save',
className: 'btn-success',
callback: function (e) {
// get form Values
var form = $('#' + config.systemDialogId).find('form');
var systemDialogData = $(form).getFormValues();
// validate form
form.validator('validate');
// check weather the form is valid
var formValid = form.isValidForm();
if(formValid === false){
// don't close dialog
return false;
}
mapContainer.getMapOverlay('timer').startMapUpdateCounter();
// calculate new system position -----------------------------------------------
var newPosition = {
x: 0,
y: 0
};
var sourceSystem = null;
// add new position
if(options.sourceSystem !== undefined){
sourceSystem = options.sourceSystem;
// get new position
newPosition = calculateNewSystemPosition(sourceSystem);
}else{
// check mouse cursor position (add system to map)
newPosition = {
x: options.position.x,
y: options.position.y
};
}
systemDialogData.position = newPosition;
// -----------------------------------------------------------------------------
var requestData = {
systemData: systemDialogData,
mapData: {
id: mapContainer.data('id')
}
};
saveSystem(map, requestData, sourceSystem, function(){
bootbox.hideAll();
});
return false;
}
}
}
});
// init dialog
systemDialog.on('shown.bs.modal', function(e) {
var modalContent = $('#' + config.systemDialogId);
// init system select live search - some delay until modal transition has finished
var selectElement = modalContent.find('.' + config.systemDialogSelectClass);
selectElement.delay(240).initSystemSelect({
key: 'systemId',
disabledOptions: mapSystemIds
});
});
// init system status select
var modalFields = $('.bootbox .modal-dialog').find('.pf-editable-system-status');
modalFields.editable({
mode: 'inline',
emptytext: 'unknown',
onblur: 'submit',
showbuttons: false,
source: systemStatus,
value: defaultSystemStatus,
inputclass: config.systemDialogSelectClass
});
});
};
/**
* save a new system and add it to the map
* @param map
* @param requestData
* @param sourceSystem
* @param callback
*/
var saveSystem = function(map, requestData, sourceSystem, callback){
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: {
map: map,
sourceSystem: sourceSystem
}
}).done(function(newSystemData){
// draw new system to map
drawSystem(this.map, newSystemData, this.sourceSystem);
Util.showNotify({title: 'New system', text: newSystemData.name, type: 'success'});
// re-init "magnetizer" with new added system
if(config.mapMagnetizer === true){
var mapContainer = this.map.getContainer();
$(mapContainer).initMagnetizer();
// re/arrange systems (prevent overlapping)
MagnetizerWrapper.executeAtCenter(this.map);
}
if(callback){
callback();
}
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
};
/**
* calculate the x/y coordinates for a new system - relativ to a source system
* @param sourceSystem
* @returns {{x: *, y: *}}
*/
var calculateNewSystemPosition = function(sourceSystem){
// related system is available
var currentX = sourceSystem.css('left');
var currentY = sourceSystem.css('top');
// remove "px"
currentX = parseInt( currentX.substring(0, currentX.length - 2) );
currentY = parseInt( currentY.substring(0, currentY.length - 2) );
var newPosition = {
x: currentX + config.newSystemOffset.x,
y: currentY + config.newSystemOffset.y
};
return newPosition;
};
/**
* updates all systems on map with current user Data (all users on this map)
* update the Data of the user that is currently viewing the map (if available)
* @param userData
* @returns {boolean}
*/
$.fn.updateUserData = function(userData){
var returnStatus = true;
// get new map instance or load existing
var map = getMapInstance(userData.config.id);
var mapElement = map.getContainer();
// get map tracking toggle value
// if "false" -> new systems/connections will not automatically added
var mapTracking = $('#' + config.headMapTrackingId).is(':checked');
// container must exist! otherwise systems can not be updated
if(mapElement !== undefined){
mapElement = $(mapElement);
// get current character log data
var currentCharacterLog = Util.getCurrentCharacterLog();
// check if map is frozen
if(mapElement.data('frozen') === true){
return returnStatus;
}
// data for header update
var headerUpdateData = {
mapId: userData.config.id,
userCount: 0 // active user in a map
};
// check if current user was found on the map
var currentUserOnMap = false;
// get all systems
var systems = mapElement.find('.' + config.systemClass);
for(var i = 0; i < systems.length; i++){
// get user Data for System
var system = $( systems[i] );
var systemId = $(system).data('systemId');
var tempUserData = null;
// check if user is currently in "this" system
var currentUserIsHere = false;
var j = userData.data.systems.length;
// search backwards to avoid decrement the counter after splice()
while (j--) {
var systemData = userData.data.systems[j];
// check if any user is in this system
if (systemId === systemData.id) {
tempUserData = systemData;
// add "user count" to "total map user count"
headerUpdateData.userCount += tempUserData.user.length;
// remove system from "search" array -> speed up loop
userData.data.systems.splice(j, 1);
}
}
// the current user can only be in a single system -------------------------------------------------------
if( !currentUserOnMap){
if(
currentCharacterLog &&
currentCharacterLog.system &&
currentCharacterLog.system.id === systemId
){
currentUserIsHere = true;
currentUserOnMap = true;
// set current location data for header update
headerUpdateData.currentSystemId = $(system).data('id');
headerUpdateData.currentSystemName = currentCharacterLog.system.name;
// check connection exists between new and previous systems --------o------------------------------
// e.g. a loop
if(
activeSystemCache &&
mapTracking &&
activeSystemCache.data('systemId') !== currentCharacterLog.system.id
){
// maybe a loop detected (both systems already on map -> connection missing
var connections = checkForConnection(map, activeSystemCache, system );
if(connections.length === 0){
var connectionData = {
source: activeSystemCache.data('id') ,
target: system.data('id'),
type: ['wh_fresh'] // default type.
};
var connection = drawConnection(map, connectionData);
saveConnection(connection);
}
}
// cache current location
activeSystemCache = system;
}
}
system.updateSystemUserData(map, tempUserData, currentUserIsHere);
}
// current user was not found on any map system -> add new system to map where the user is in ----------------
// this is restricted to IGB-usage! CharacterLog data is always set through the IGB
// ->this prevent adding the same system multiple times, if a user is online with IGB AND OOG
if(
currentUserOnMap === false &&
currentCharacterLog &&
mapTracking
){
// add new system to the map
var requestData = {
systemData: {
systemId: currentCharacterLog.system.id
},
mapData: {
id: userData.config.id
}
};
// check if a system jump is detected previous system !== current system
// and add a connection to the previous system as well
// hint: if a user just logged on -> there is no active system cached
var sourceSystem = false;
if(
activeSystemCache &&
activeSystemCache.data('systemId') !== currentCharacterLog.system.id
){
// draw new connection
sourceSystem = activeSystemCache;
// calculate new system coordinates
requestData.systemData.position = calculateNewSystemPosition(sourceSystem);
}
mapElement.getMapOverlay('timer').startMapUpdateCounter();
saveSystem(map, requestData, sourceSystem, false);
}
// trigger document event -> update header
$(document).trigger('pf:updateHeaderMapData', headerUpdateData);
}
return returnStatus;
};
/**
* collect all map data for export/save for a map
* this function returns the "client" data NOT the "server" data for a map
* @param options
* @returns {*}
*/
$.fn.getMapDataFromClient = function(options){
var mapElement = $(this);
var map = getMapInstance( mapElement.data('id') );
var mapData = {};
// check if there is an active map counter that prevents collecting map data
var overlay = mapElement.getMapOverlay('timer');
var counterChart = overlay.getMapCounter();
var interval = counterChart.data('interval');
if(
! interval ||
options.forceData === true
){
// map config -----------------------------------------------------------
var mapConfig = {};
mapConfig.id = parseInt( mapElement.data('id') );
mapConfig.name = mapElement.data('name');
mapConfig.scope = {
id: parseInt( mapElement.data('scopeId') )
};
mapConfig.icon = mapElement.data('icon');
mapConfig.type = {
id: parseInt( mapElement.data('typeId') )
};
mapConfig.created = parseInt( mapElement.data('created') );
mapConfig.updated = parseInt( mapElement.data('updated') );
mapData.config = mapConfig;
// map data -------------------------------------------------------------
var data = {};
// systems data ---------------------------------------------------------
var systemsData = [];
var systems = mapElement.find('.' + config.systemClass);
for(var i = 0; i < systems.length; i++){
var tempSystem = $(systems[i]);
// check if system data should be added
var addSystemData = true;
if(
options.checkForChange === true &&
!tempSystem.hasChanged()
){
addSystemData = false;
}
if(addSystemData){
systemsData.push( tempSystem.getSystemData() );
}
}
data.systems = systemsData;
// connections ----------------------------------------------------------
var connections = map.getAllConnections();
var connectionsFormatted = [];
// new connections cache
var updatedConnectionCache = {};
// format connections
for(var j = 0; j < connections.length; j++){
var tempConnection = connections[j];
// check if connection data should be added
var addConnectionData = true;
if(
options.checkForChange === true &&
!$(tempConnection).hasChanged()
){
addConnectionData = false;
}
var connectionData = getDataByConnection(tempConnection);
if(addConnectionData){
connectionsFormatted.push( connectionData );
}
// add to cache
updatedConnectionCache[connectionData.id] = tempConnection;
}
// overwrite connection cache
connectionCache[mapConfig.id] = updatedConnectionCache;
data.connections = connectionsFormatted;
mapData.data = data;
}else{
return false;
}
return mapData;
};
/**
* get all relevant data for a system object
* @returns {{}}
*/
$.fn.getSystemData = function(){
var system = $(this);
var systemData = {};
systemData.id = parseInt( system.data('id') );
systemData.systemId = parseInt( system.data('systemId') );
systemData.name = system.data('name');
systemData.alias = system.getSystemInfo(['alias']);
systemData.effect = system.data('effect');
systemData.type = {
id: system.data('typeId')
};
systemData.security = system.data('security');
systemData.trueSec = system.data('trueSec');
systemData.region = {
id: system.data('regionId'),
name: system.data('region')
};
systemData.constellation = {
id: system.data('constellationId'),
name: system.data('constellation')
};
systemData.status = {
id: system.data('statusId')
};
systemData.locked = system.data('locked') ? 1 : 0;
systemData.rally = system.data('rally') ? 1 : 0;
systemData.currentUser = system.data('currentUser'); // if user is currently in this system
systemData.statics = system.data('statics');
systemData.updated = {
updated: parseInt( system.data('updated') )
};
systemData.userCount = (system.data('userCount') ? parseInt( system.data('userCount') ) : 0);
// position -----------------------------------------------------------------
var positionData = {};
var currentX = system.css('left');
var currentY = system.css('top');
// remove 'px'
positionData.x = parseInt( currentX.substring(0, currentX.length - 2) );
positionData.y = parseInt( currentY.substring(0, currentY.length - 2) );
systemData.position = positionData;
return systemData;
};
/**
* get all relevant data for a connection object
* @param connection
* @returns {{id: Number, source: Number, sourceName: (*|T|JQuery|{}), target: Number, targetName: (*|T|JQuery), scope: *, type: *, updated: Number}}
*/
var getDataByConnection = function(connection){
var source = $(connection.source);
var target = $(connection.target);
var id = connection.getParameter('connectionId');
var updated = connection.getParameter('updated');
var connectionTypes = connection.getType();
// normalize connection array
connectionTypes = $.grep(connectionTypes, function(n){
if(
n.length > 0 &&
n !== 'default' // this is added by jsplumb by default -_-
){
return true;
}else{
return false;
}
});
var data = {
id: id ? id : 0,
source: parseInt( source.data('id') ),
sourceName: source.data('name'),
target: parseInt( target.data('id') ),
targetName: target.data('name'),
scope: connection.scope,
type: connectionTypes,
updated: updated ? updated : 0
};
return data;
};
/**
* removes a map instance from local cache
* @param mapId
*/
var clearMapInstance = function(mapId){
if(typeof activeInstances[mapId] === 'object'){
delete activeInstances[mapId];
}
};
/**
* get a new jsPlumb map instance or or get a cached one for update
* @param mapId
* @returns {*}
*/
var getMapInstance = function(mapId){
if(typeof activeInstances[mapId] !== 'object'){
// create new instance
jsPlumb.Defaults.LogEnabled = true;
var newJsPlumbInstance = jsPlumb.getInstance({
Anchor: 'Continuous', // anchors on each site
Container: null, // will be set as soon as container is connected to DOM
PaintStyle: {
lineWidth: 4, // width of a Connector's line. An integer.
strokeStyle: 'red', // color for a Connector
outlineColor: 'red', // color of the outline for an Endpoint or Connector. see fillStyle examples.
outlineWidth: 2 // width of the outline for an Endpoint or Connector. An integer.
},
Connector: [ 'Bezier', { curviness: 40 } ], // default connector style (this is not used!) all connections have their own style (by scope)
Endpoint: [ 'Dot', { radius: 5 } ],
ReattachConnections: false, // re-attach connection if dragged with mouse to "nowhere"
Scope: Init.defaultMapScope, // default map scope for connections
LogEnabled: true
});
// register all available connection types ------------------------------
newJsPlumbInstance.registerConnectionTypes(globalMapConfig.connectionTypes);
// event after a new connection is established --------------------------
newJsPlumbInstance.bind('connection', function(info, e) {
// set connection observer
setConnectionObserver(newJsPlumbInstance, info.connection);
});
// event after connection moved -----------------------------------------
newJsPlumbInstance.bind('connectionMoved', function(info, e) {
});
// event after DragStop a connection or new connection ------------------
newJsPlumbInstance.bind('beforeDrop', function(info) {
var connection = info.connection;
// lock the target system for "click" events
// to prevent loading system information
var sourceSystem = $('#' + info.sourceId);
var targetSystem = $('#' + info.targetId);
sourceSystem.addClass('no-click');
targetSystem.addClass('no-click');
setTimeout(function(){
sourceSystem.removeClass('no-click');
targetSystem.removeClass('no-click');
}, Init.timer.DBL_CLICK + 50);
// set "default" connection status only for NEW connections
if(!connection.suspendedElement){
setConnectionWHStatus(connection, getDefaultConnectionTypeByScope(connection.scope) );
}
// prevent multiple connections between same systems
var connections = checkForConnection(newJsPlumbInstance, info.sourceId, info.targetId );
if(connections.length > 1){
bootbox.confirm('Connection already exists. Do you really want to add an additional one?', function(result) {
if(!result){
connection._jsPlumb.instance.detach(connection);
}
});
}
// always save the new connection
saveConnection(connection);
return true;
});
// event before Detach connection ---------------------------------------
newJsPlumbInstance.bind('beforeDetach', function(info) {
return true;
});
newJsPlumbInstance.bind('connectionDetached', function(info, e){
// a connection is manually (drag&drop) detached! otherwise this event should not be send!
var connection = info.connection;
$().deleteConnections([connection]);
});
newJsPlumbInstance.bind('checkDropAllowed', function(params){
// connections can not be attached to foreign endpoints
// the only endpoint available is endpoint from where the connection was dragged away (re-attach)
return true;
});
activeInstances[mapId] = newJsPlumbInstance;
}
return activeInstances[mapId];
};
/**
* load OR updates system map
* @param mapConfig
*/
$.fn.loadMap = function(mapConfig, options){
// parent element where the map will be loaded
var parentElement = $(this);
// add context menus to dom (if not already
initMapContextMenu();
initConnectionContextMenu();
initSystemContextMenu();
// new map init
var newMap = false;
// init jsPlumb
jsPlumb.ready(function() {
// get new map instance or load existing
mapConfig.map = getMapInstance(mapConfig.config.id);
// check for map Container -> first time initialization
if(mapConfig.map.getContainer() === undefined){
// new map instance
newMap = true;
}
// draw/update map initial map and set container
var mapContainer = updateMap(parentElement, mapConfig);
if(newMap){
// init custom scrollbars and add overlay
parentElement.initMapScrollbar();
}
// callback function after tab switch
function switchTabCallback( mapName, mapContainer ){
Util.showNotify({title: 'Map initialized', text: mapName + ' - loaded', type: 'success'});
if( config.mapMagnetizer === true ){
mapContainer.initMagnetizer();
}
return false;
}
if(options.showAnimation){
// show nice visualization effect
mapContainer.visualizeMap('show', function(){
switchTabCallback( mapConfig.config.name, mapContainer );
});
}
});
};
/**
* init scrollbar for Map element
*/
$.fn.initMapScrollbar = function(){
// get Map Scrollbar
var scrollableElement = $(this).find('.' + config.mapWrapperClass);
initCustomScrollbar( scrollableElement );
// ---------------------------------------------------------------------------
// add map overlays after scrollbar is initialized
// because of its absolute position
scrollableElement.initMapOverlays();
};
/**
* init a custom scrollbar
* @param scrollableElement
*/
var initCustomScrollbar = function( scrollableElement ){
// prevent multiple initialization
$(scrollableElement).mCustomScrollbar('destroy');
// init custom scrollbars
$(scrollableElement).mCustomScrollbar({
axis: 'x',
theme: 'light-thick',
scrollInertia: 300,
autoExpandScrollbar: false,
scrollButtons:{
scrollAmount: 30,
enable: true
},
callbacks:{
onTotalScrollOffset: 0,
onTotalScrollBackOffset: 0,
alwaysTriggerOffsets:true,
onScrollStart: function(){
// hide all open xEditable fields
$(this).find('.editable').editable('hide');
// hide all system head tooltips
$(this).find('.' + config.systemHeadClass + ' .fa').tooltip('hide');
},
whileScrolling:function(){
// update scroll position for drag-frame-selection
var mapElement = $(scrollableElement).find('.' + config.mapClass);
$(mapElement).data('scrollLeft', this.mcs.left);
$(mapElement).data('scrollTop', this.mcs.top);
}
},
advanced: {
updateOnBrowserResize: true,
updateOnContentResize: true,
autoExpandHorizontalScroll: true,
autoScrollOnFocus: "div"
},
mouseWheel:{
enable: false, // scroll weel currently disabled
scrollAmount: 'auto',
axis: 'x',
preventDefault: true
},
scrollbarPosition: 'inside',
autoDraggerLength: true
//autoHideScrollbar: false
});
};
/**
* scroll to a specific position in the map
* @returns {*} // string or id
*/
$.fn.scrollTo = function(position){
return this.each(function(){
$(this).mCustomScrollbar('scrollTo', position);
});
};
return {
clearMapInstance: clearMapInstance
};
});