define([ 'jquery', 'app/init', 'app/util', 'app/render', 'bootbox', 'app/ccp', 'jsPlumb', 'dragToSelect', 'select2', 'app/map/contextmenu', 'app/map/overlay' ], function($, Init, Util, Render, bootbox, CCP) { 'use strict'; var config = { zIndexCounter: 110, newSystemOffset: { x: 130, y: 0 }, mapSnapToGridDimension: 20, // px for grid snapping (grid YxY) mapSnapToGrid: false, // Snap systems to grid while dragging 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 }, target: { filter: '.' + config.systemHeadNameClass, isSource:true, //isTarget:true, allowLoopback: false, // loopback connections are not allowed cssClass: config.endpointTargetClass, dropOptions: { tolerance: 'touch', hoverClass: config.systemActiveClass, activeClass: 'dragActive', } }, 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 = $('
', { class: config.systemBodyItemClass }).append( $('', { text: userData.log.ship.typeName, class: config.systemBodyRightClass }) ).append( $('', { class: ['fa', 'fa-circle', config.systemBodyItemStatusClass, statusClass].join(' ') }) ).append( $('', { 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 = ""; tooltipOptions.title += ' ' + 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 systems * @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 ? true : false); 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 = ''; 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 = $('
', { // system id: systemId, class: config.systemClass }).append( // system head $('
', { class: config.systemHeadClass }).append( // System name is editable $('', { href: '#', class: config.systemHeadNameClass }).attr('data-value', systemName) ).append( // System locked status $('', { class: ['fa', 'fa-lock', 'fa-fw'].join(' ') }).attr('title', 'locked') ).append( // System effect color $('', { class: ['fa', 'fa-square ', 'fa-fw', effectBasicClass, effectClass].join(' ') }).attr('title', effectName) ).append( // expand option $('', { class: ['fa', 'fa-angle-down ', config.systemHeadExpandClass].join(' ') }) ).prepend( $('', { class: [config.systemSec, secClass].join(' '), text: data.security }) ) ).append( // system body $('
', { 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') ) !== Boolean( parseInt( data.locked ) )){ system.toggleLockSystem(false, {hideNotification: true, hideCounter: true, map: map}); } // rally system if( Boolean( system.data( 'rally') ) !== Boolean( parseInt( 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(); if(mapContainer === undefined){ // add new map // create map wrapper var mapWrapper = $('
', { class: config.mapWrapperClass }); // create new map container mapContainer = $('
', { 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); } } // 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}); } } } } }); } 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 || 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 }); // show overlay elements (if some exist) if(overlayElements.length > 0){ overlayElements.delay(500).velocity('transition.fadeIn', { stagger: 50, duration: 180, display: 'auto', complete: function(){ callback(); } }); } } }); }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 object * @param systemData * @param {String[]} optional Systems for connection */ 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) * @param system */ $.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, mapId: mapId } }).done(function(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'}); }).fail(function( jqXHR, status, error) { // remove this connection from map this._jsPlumb.instance.detach(this); 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 */ $.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 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(){ // start map update timer mapOverlayTimer.startMapUpdateCounter(); }, 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}); // 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]); // set all selected systems as "changes" for update tempSystem.markAsChanged(); // set new position for popover edit field (system name) var tempPosition = tempSystem.position(); var placement = 'top'; if(tempPosition.top < 100){ placement = 'bottom'; } if(tempPosition.left < 100){ placement = 'right'; } tempSystem.find('.' + config.systemHeadNameClass).editable('option', 'placement', placement); } } }); 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: ' Set rally and poke', className: 'btn-primary', callback: function() { currentSystem.toggleRallyPoint(true, {}); currentSystem.markAsChanged(); } }, success: { label: ' 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 var systemDeleteDialog = 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); var systems = mapElement.find('.' + config.systemSelectedClass); return systems; }; /** * 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 "snap to grid" option $(mapContainer).on('pf:menuGrid', function(e, data){ var currentMapElement = $(this); config.mapSnapToGrid = !config.mapSnapToGrid; // toggle grid class currentMapElement.toggleClass(config.mapGridClass); // toggle button class $(data.button).toggleClass('active'); var notificationText = 'disabled'; if(config.mapSnapToGrid){ notificationText = 'enabled'; // show map overlay grid info currentMapElement.getMapOverlay('info').updateOverlayIcon('grid', 'show'); }else{ // hide map overlay grid info currentMapElement.getMapOverlay('info').updateOverlayIcon('grid', 'hide'); } Util.showNotify({title: 'Grid snapping', 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: ' 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'}); 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( CCP.isInGameBrowser() === true && 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; }; /** * 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) Endpoints: [ [ 'Dot', { radius: 5 } ], [ 'Dot', { radius: 5 } ] ], // Endpoint: 'Blank', // does not work... :( 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 ){ Util.showNotify({title: 'Map initialized', text: mapName + ' - loaded', type: 'success'}); return false; } if(options.showAnimation){ // show nice visualization effect mapContainer.visualizeMap('show', function(){ switchTabCallback( mapConfig.config.name ); }); } }); }; /** * 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); }); }; });