/** * map system functions */ define([ 'jquery', 'app/init', 'app/util', 'bootbox', 'app/map/util', 'app/map/layout' ], ($, Init, Util, bootbox, MapUtil, Layout) => { 'use strict'; let config = { newSystemOffset: { x: 130, y: 0 }, mapClass: 'pf-map', // class for all maps systemHeadInfoClass: 'pf-system-head-info', // class for system info systemHeadInfoLeftClass: 'pf-system-head-info-left', // class for left system info systemHeadInfoRightClass: 'pf-system-head-info-right', // class for right system info systemActiveClass: 'pf-system-active', // class for an active system on a map systemTooltipInnerIdPrefix: 'pf-system-tooltip-inner-', // id prefix for system tooltip content systemTooltipInnerClass: 'pf-system-tooltip-inner', // class for system tooltip content // dialogs dialogSystemId: 'pf-system-dialog', // id for system dialog dialogSystemSelectClass: 'pf-system-dialog-select', // class for system select element dialogSystemStatusSelectId: 'pf-system-dialog-status-select', // id for "status" select dialogSystemLockId: 'pf-system-dialog-lock', // id for "locked" checkbox dialogSystemRallyId: 'pf-system-dialog-rally', // id for "rally" checkbox dialogSystemSectionInfoId: 'pf-system-dialog-section-info', // id for "info" section element dialogSystemSectionInfoStatusId: 'pf-system-dialog-section-info-status', // id for "status" message in "info" element dialogSystemAliasId: 'pf-system-dialog-alias', // id for "alias" static element dialogSystemDescriptionId: 'pf-system-dialog-description', // id for "description" static element dialogSystemCreatedId: 'pf-system-dialog-created', // id for "created" static element dialogSystemUpdatedId: 'pf-system-dialog-updated', // id for "updated" static element dialogRallyId: 'pf-rally-dialog', // id for "Rally point" dialog dialogRallyPokeDesktopId: 'pf-rally-dialog-poke-desktop', // id for "desktop" poke checkbox dialogRallyPokeSlackId: 'pf-rally-dialog-poke-slack', // id for "Slack" poke checkbox dialogRallyPokeDiscordId: 'pf-rally-dialog-poke-discord', // id for "Discord" poke checkbox dialogRallyPokeMailId: 'pf-rally-dialog-poke-mail', // id for "mail" poke checkbox dialogRallyMessageId: 'pf-rally-dialog-message', // id for "message" textarea dialogRallyMessageDefault: '' + 'I need some help!\n\n' + '- Potential PvP options around\n' + '- DPS and Logistic ships needed' }; /** * 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 * @param callback */ let showNewSystemDialog = (map, options, callback) => { let mapContainer = $(map.getContainer()); let mapId = mapContainer.data('id'); /** * update new system dialog with some "additional" data * -> if system was mapped before * @param dialogElement * @param systemData */ let updateDialog = (dialogElement, systemData = null) => { let labelEmpty = 'empty'; let labelUnknown = 'unknown'; let labelExist = 'loaded'; let showInfoHeadline = 'fadeOut'; let showInfoSection = 'hide'; let info = labelEmpty; let statusId = false; // -> no value change let alias = labelEmpty; let description = labelEmpty; let createdTime = labelUnknown; let updatedTime = labelUnknown; if(systemData){ // system data found for selected system showInfoHeadline = 'fadeIn'; showInfoSection = 'show'; info = labelExist; statusId = parseInt(Util.getObjVal(systemData, 'status.id')) || statusId; alias = systemData.alias.length ? Util.htmlEncode(systemData.alias) : alias; description = systemData.description.length ? systemData.description : description; let dateCreated = new Date(systemData.created.created * 1000); let dateUpdated = new Date(systemData.updated.updated * 1000); let dateCreatedUTC = Util.convertDateToUTC(dateCreated); let dateUpdatedUTC = Util.convertDateToUTC(dateUpdated); createdTime = Util.convertDateToString(dateCreatedUTC); updatedTime = Util.convertDateToString(dateUpdatedUTC); }else if(systemData === null){ // no system found for selected system showInfoHeadline = 'fadeIn'; } // update new system dialog with new default data dialogElement.find('#' + config.dialogSystemSectionInfoStatusId).html(info); if(statusId !== false){ dialogElement.find('#' + config.dialogSystemStatusSelectId).val(statusId).trigger('change'); } dialogElement.find('#' + config.dialogSystemAliasId).html(alias); dialogElement.find('#' + config.dialogSystemDescriptionId).html(description); dialogElement.find('#' + config.dialogSystemCreatedId).html(' ' + createdTime); dialogElement.find('#' + config.dialogSystemUpdatedId).html(' ' + updatedTime); dialogElement.find('[data-target="#' + config.dialogSystemSectionInfoId + '"]').velocity('stop').velocity(showInfoHeadline, {duration: 120}); dialogElement.find('[data-type="spinner"]').removeClass('in'); dialogElement.find('#' + config.dialogSystemSectionInfoId).collapse(showInfoSection); }; /** * request system data from server for persistent data -> update dialog * @param dialogElement * @param mapId * @param systemId */ let requestSystemData = (dialogElement, mapId, systemId) => { // show loading animation dialogElement.find('[data-type="spinner"]').addClass('in'); MapUtil.requestSystemData({ mapId: mapId, systemId: systemId, isCcpId: 1 }, { dialogElement: dialogElement }).then(payload => updateDialog(payload.context.dialogElement, payload.data)) .catch(payload => updateDialog(payload.context.dialogElement)); }; // format system status for form select ----------------------------------------------------------------------- // "default" selection (id = 0) prevents status from being overwritten // -> e.g. keep status information if system was just inactive (active = 0) let statusData = [{id: 0, text: 'auto'}]; // get current map data --------------------------------------------------------------------------------------- let mapData = mapContainer.getMapDataFromClient({forceData: true}); let mapSystems = mapData.data.systems; let mapSystemCount = mapSystems.length; let mapTypeName = mapContainer.data('typeName'); let maxAllowedSystems = Init.mapTypes[mapTypeName].defaultConfig.max_systems; // show error if system max count reached --------------------------------------------------------------------- if(mapSystemCount >= maxAllowedSystems){ Util.showNotify({title: 'Max system count exceeded', text: 'Limit of ' + maxAllowedSystems + ' systems reached', type: 'warning'}); return; } // disable systems that are already on it --------------------------------------------------------------------- let mapSystemIds = mapSystems.map(systemData => systemData.systemId); // dialog data ------------------------------------------------------------------------------------------------ let data = { id: config.dialogSystemId, select2Class: Util.config.select2Class, systemSelectClass: config.dialogSystemSelectClass, statusSelectId: config.dialogSystemStatusSelectId, lockId: config.dialogSystemLockId, rallyId: config.dialogSystemRallyId, sectionInfoId: config.dialogSystemSectionInfoId, sectionInfoStatusId: config.dialogSystemSectionInfoStatusId, aliasId: config.dialogSystemAliasId, descriptionId: config.dialogSystemDescriptionId, createdId: config.dialogSystemCreatedId, updatedId: config.dialogSystemUpdatedId, statusData: statusData }; // set current position as "default" system to add ------------------------------------------------------------ let 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'], (template, Mustache) => { let content = Mustache.render(template, data); let systemDialog = bootbox.dialog({ title: 'Add new system', message: content, show: false, buttons: { close: { label: 'cancel', className: 'btn-default' }, success: { label: ' save', className: 'btn-success', callback: function(e){ // get form Values let form = this.find('form'); let formData = $(form).getFormValues(); // validate form form.validator('validate'); // check whether the form is valid let formValid = form.isValidForm(); // don't close dialog on invalid data if(formValid === false) return false; // calculate new system position ---------------------------------------------------------- let newPosition = { x: 0, y: 0 }; // add new position let sourceSystem = null; 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 }; } formData.position = newPosition; formData.mapId = mapId; // ---------------------------------------------------------------------------------------- this.find('.modal-content').showLoadingAnimation(); Util.request('PUT', 'system', [], formData, { systemDialog: systemDialog, formElement: form, map: map, sourceSystem: sourceSystem }, context => { // always do context.systemDialog.find('.modal-content').hideLoadingAnimation(); }).then( payload => { Util.showNotify({title: 'New system', text: payload.data.name, type: 'success'}); callback(payload.context.map, payload.data, payload.context.sourceSystem); bootbox.hideAll(); }, Util.handleAjaxErrorResponse ); return false; } } } }); systemDialog.on('show.bs.modal', function(e){ let dialogElement = $(this); // init "status" select2 ------------------------------------------------------------------------------ for(let [statusName, data] of Object.entries(Init.systemStatus)){ statusData.push({id: data.id, text: data.label, class: data.class}); } dialogElement.find('#' + config.dialogSystemStatusSelectId).initStatusSelect({ data: statusData, iconClass: 'fa-tag' }); // initial dialog update with persistent system data -------------------------------------------------- // -> only if system is preselected (e.g. current active system) let systemId = parseInt(dialogElement.find('.' + config.dialogSystemSelectClass).val()) || 0; if(systemId){ requestSystemData(dialogElement, mapId, systemId); } }); systemDialog.on('shown.bs.modal', function(e){ let dialogElement = $(this); // no system selected updateDialog(dialogElement, false); dialogElement.initTooltips(); // init system select live search - some delay until modal transition has finished let selectElement = dialogElement.find('.' + config.dialogSystemSelectClass); selectElement.delay(240).initSystemSelect({ key: 'id', disabledOptions: mapSystemIds, onChange: systemId => { // on system select -> update dialog with persistent system data if(systemId){ requestSystemData(dialogElement, mapId, systemId); }else{ // no system selected updateDialog(dialogElement, false); } } }); }); // show dialog systemDialog.modal('show'); }); }; /** * show "set rally point" dialog for system * @param system */ $.fn.showRallyPointDialog = (system) => { let mapId = system.data('mapid'); let systemId = system.data('id'); let mapData = Util.getCurrentMapData(mapId); requirejs(['text!templates/dialog/system_rally.html', 'mustache'], function(template, Mustache){ let setCheckboxObserver = (checkboxes) => { checkboxes.each(function(){ $(this).on('change', function(){ // check all others let allUnchecked = true; checkboxes.each(function(){ if(this.checked){ allUnchecked = false; } }); let textareaElement = $('#' + config.dialogRallyMessageId); if(allUnchecked){ textareaElement.prop('disabled', true); }else{ textareaElement.prop('disabled', false); } }); }); }; let sendPoke = (requestData, context) => { // lock dialog let dialogContent = context.rallyDialog.find('.modal-content'); dialogContent.showLoadingAnimation(); $.ajax({ type: 'POST', url: Init.path.pokeRally, data: requestData, dataType: 'json', context: context }).done(function(data){ }).fail(function(jqXHR, status, error){ let reason = status + ' ' + error; Util.showNotify({title: jqXHR.status + ': sendPoke', text: reason, type: 'warning'}); }).always(function(){ this.rallyDialog.find('.modal-content').hideLoadingAnimation(); }); }; let data = { id: config.dialogRallyId, dialogRallyPokeDesktopId: config.dialogRallyPokeDesktopId, dialogRallyPokeSlackId: config.dialogRallyPokeSlackId, dialogRallyPokeDiscordId: config.dialogRallyPokeDiscordId, dialogRallyPokeMailId: config.dialogRallyPokeMailId, dialogRallyMessageId: config.dialogRallyMessageId , desktopRallyEnabled: true, slackRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.slackRally')), discordRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.discordRally')), mailRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.mailRally')), dialogRallyMessageDefault: config.dialogRallyMessageDefault, systemUrl: MapUtil.getMapDeeplinkUrl(mapId, systemId), systemId: systemId }; let content = Mustache.render(template, data); let rallyDialog = bootbox.dialog({ message: content, title: 'Set rally point in "' + system.getSystemInfo( ['alias'] ) + '"', buttons: { close: { label: 'cancel', className: 'btn-default' }, success: { label: ' set rally point', className: 'btn-success', callback: function(){ let form = $('#' + config.dialogRallyId).find('form'); // get form data let formData = form.getFormValues(); // update map system.setSystemRally(1, { poke: Boolean(formData.pokeDesktop) }); system.markAsChanged(); // send poke data to server sendPoke(formData, { rallyDialog: this }); } } } }); rallyDialog.initTooltips(); // after modal is shown ================================================================================== rallyDialog.on('shown.bs.modal', function(e){ // set event for checkboxes setCheckboxObserver(rallyDialog.find(':checkbox')); }); }); }; /** * shows delete dialog for systems that should be deleted * @param map * @param systems * @returns {*} */ $.fn.showDeleteSystemDialog = (map, systems = []) => { let mapContainer = $( map.getContainer() ); let validDeleteSystems = []; let activeCharacters = 0; // check if systems belong to map -> security check for(let system of systems){ let systemElement = $(system); if( systemElement.data('mapid') === mapContainer.data('id') && !systemElement.data('locked') ){ // system belongs to map -> valid system validDeleteSystems.push(system); activeCharacters += (systemElement.data('userCount') ? parseInt( systemElement.data('userCount') ) : 0); } } if(validDeleteSystems.length){ let msg = ''; if(validDeleteSystems.length === 1){ let deleteSystem = $(validDeleteSystems[0]); let systemName = deleteSystem.data('name'); let systemAlias = deleteSystem.getSystemInfo( ['alias'] ); let systemNameStr = (systemName === systemAlias) ? '"' + systemName + '"' : '"' + systemAlias + '" (' + systemName + ')'; systemNameStr = '' + systemNameStr + ''; msg = 'Delete system ' + systemNameStr + ' and all its connections?'; }else{ msg = 'Delete ' + validDeleteSystems.length + ' selected systems and their connections?'; } // add warning for active characters if(activeCharacters > 0){ msg += ' Warning: ' + activeCharacters + ' active characters'; } let systemDeleteDialog = bootbox.confirm(msg, result => { if(result){ deleteSystems(map, validDeleteSystems, (deletedSystems) => { // callback function after deleted -> close dialog systemDeleteDialog.modal('hide'); // check whether all systems were deleted properly if(deletedSystems.length !== validDeleteSystems.length){ let notDeletedCount = validDeleteSystems.length - deletedSystems.length; Util.showNotify({ title: 'Failed to delete systems', text: '(' + notDeletedCount + '/' + validDeleteSystems.length + ') systems could not be deleted', type: 'warning'} ); }else if(deletedSystems.length === 1){ Util.showNotify({title: 'System deleted', text: $(deletedSystems[0]).data('name'), type: 'success'}); }else{ Util.showNotify({title: systems.length + ' systems deleted', type: 'success'}); } }); } }); }else{ Util.showNotify({title: 'No systems selected', type: 'warning'}); } return this; }; /** * toggle system tooltip (current pilot count) * @param show * @param tooltipOptions * @returns {*} */ $.fn.toggleSystemTooltip = function(show, tooltipOptions){ let highlightData = { good: { colorClass: 'txt-color-green', iconClass: 'fa-caret-up' }, bad: { colorClass: 'txt-color-red', iconClass: 'fa-caret-down' } }; let getHighlightClass = (highlight) => { return Util.getObjVal(highlightData, highlight + '.colorClass') || ''; }; let getHighlightIcon = (highlight) => { return Util.getObjVal(highlightData, highlight + '.iconClass') || ''; }; let getTitle = (userCounter, highlight) => { return ' ' + userCounter + ''; }; return this.each(function(){ let system = $(this); switch(show){ case 'destroy': //destroy tooltip and remove some attributes which are not deleted by 'destroy' system.tooltip('destroy').removeAttr('title data-original-title'); break; case 'hide': system.tooltip('hide'); break; case 'show': // initial "show" OR "update" open tooltip // -> do not update tooltips while a system is dragged let showTooltip = !system.hasClass('jsPlumb_dragged'); if(system.data('bs.tooltip')){ // tooltip initialized but could be hidden // check for title update if( tooltipOptions.hasOwnProperty('userCount') && tooltipOptions.hasOwnProperty('highlight') ){ let currentTitle = system.attr('data-original-title'); let newTitle = getTitle(tooltipOptions.userCount, tooltipOptions.highlight); if(currentTitle !== newTitle){ // update tooltip let tooltipInner = system.attr('title', newTitle).tooltip('fixTitle').data('bs.tooltip').$tip.find('.tooltip-inner'); tooltipInner.html(newTitle); // change highlight class let highlightClass = getHighlightClass(tooltipOptions.highlight); if( !tooltipInner.hasClass(highlightClass) ){ tooltipInner.removeClass( getHighlightClass('good') + ' ' + getHighlightClass('bad')).addClass(highlightClass); } } } let tip = system.data('bs.tooltip').tip(); if( !tip.hasClass('in') && showTooltip){ // update tooltip placement based on system position system.data('bs.tooltip').options.placement = getSystemTooltipPlacement(system); system.tooltip('show'); } }else{ // no tooltip initialized // "some" config data is required if( tooltipOptions.hasOwnProperty('systemId') && tooltipOptions.hasOwnProperty('userCount') && tooltipOptions.hasOwnProperty('highlight') ){ let innerTooltipId = config.systemTooltipInnerIdPrefix + tooltipOptions.systemId; let template = '