/** * System info module */ define([ 'jquery', 'app/init', 'app/util', 'app/map/util' ], ($, Init, Util, MapUtil) => { 'use strict'; let config = { // module info modulePosition: 2, moduleName: 'systemInfo', // system info module moduleTypeClass: 'pf-system-info-module', // class for this module // headline toolbar moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head // breadcrumb constellationLinkClass: 'pf-system-info-constellation', // class for "constellation" name regionLinkClass: 'pf-system-info-region', // class for "region" name typeLinkClass: 'pf-system-info-type', // class for "type" name urlLinkClass: 'pf-system-info-url', // class for "url" copy link // info table systemInfoTableClass: 'pf-module-table', // class for system info table systemInfoNameClass: 'pf-system-info-name', // class for "name" information element systemInfoEffectClass: 'pf-system-info-effect', // class for "effect" information element systemInfoPlanetsClass: 'pf-system-info-planets', // class for "planets" information element systemInfoStatusLabelClass: 'pf-system-info-status-label', // class for "status" information element systemInfoStatusAttributeName: 'data-status', // attribute name for status label systemInfoWormholeClass: 'pf-system-info-wormhole-', // class prefix for static wormhole element // description field descriptionAreaClass: 'pf-system-info-description-area', // class for "description" area addDescriptionButtonClass: 'pf-system-info-description-button', // class for "add description" button descriptionTextareaElementClass: 'pf-system-info-description', // class for "description" textarea element (Summernote) // fonts fontTriglivianClass: 'pf-triglivian', // class for "Triglivian" names (e.g. Abyssal systems) // Summernote defaultBgColor: '#e2ce48' }; // max character length for system description let maxDescriptionLength = 9000; /** * update trigger function for this module * compare data and update module * @param moduleElement * @param systemData */ let updateModule = (moduleElement, systemData) => { let systemId = moduleElement.data('id'); let updated = moduleElement.data('updated'); if( systemId === systemData.id && updated !== systemData.updated.updated ){ let setUpdated = true; // created/updated tooltip -------------------------------------------------------------------------------- let nameRowElement = moduleElement.find('.' + config.systemInfoNameClass); let tooltipData = { created: systemData.created, updated: systemData.updated }; nameRowElement.addCharacterInfoTooltip( tooltipData ); // update system status ----------------------------------------------------------------------------------- let systemStatusLabelElement = moduleElement.find('.' + config.systemInfoStatusLabelClass); let systemStatusId = parseInt( systemStatusLabelElement.attr( config.systemInfoStatusAttributeName ) ); if(systemStatusId !== systemData.status.id){ // status changed let currentStatusClass = Util.getStatusInfoForSystem(systemStatusId, 'class'); let newStatusClass = Util.getStatusInfoForSystem(systemData.status.id, 'class'); let newStatusLabel = Util.getStatusInfoForSystem(systemData.status.id, 'label'); systemStatusLabelElement.removeClass(currentStatusClass).addClass(newStatusClass).text(newStatusLabel); // set new status attribute systemStatusLabelElement.attr( config.systemInfoStatusAttributeName, systemData.status.id); } // update description textarea ---------------------------------------------------------------------------- let descriptionTextareaElement = moduleElement.find('.' + config.descriptionTextareaElementClass); if(descriptionTextareaElement.length){ let description = descriptionTextareaElement.html(); if(description !== systemData.description){ // description has changed if(typeof descriptionTextareaElement.data().summernote === 'object'){ // "Summernote" editor is currently open setUpdated = false; }else{ // not open let newDescription = systemData.description; if( !Util.isValidHtml(newDescription) ){ // try to convert raw text into valid html newDescription = newDescription.replace(/(\r\n|\n|\r)/g, '
'); newDescription = '

' + newDescription + '

'; } descriptionTextareaElement.html(newDescription); } } } if(setUpdated){ moduleElement.data('updated', systemData.updated.updated); } } moduleElement.find('.' + config.descriptionAreaClass).hideLoadingAnimation(); }; /** * get module element * @param parentElement * @param mapId * @param systemData */ let getModule = (parentElement, mapId, systemData) => { let moduleElement = $('
'); // store systemId -> module can be updated with the correct data moduleElement.data('id', systemData.id); // system "static" wh data let staticsData = []; if( systemData.statics && systemData.statics.length > 0 ){ for(let wormholeName of systemData.statics){ let wormholeData = Object.assign({}, Init.wormholes[wormholeName]); wormholeData.class = Util.getSecurityClassForSystem(wormholeData.security); staticsData.push(wormholeData); } } let effectName = MapUtil.getEffectInfoForSystem(systemData.effect, 'name'); let effectClass = MapUtil.getEffectInfoForSystem(systemData.effect, 'class'); let data = { system: systemData, static: staticsData, moduleHeadlineIconClass: config.moduleHeadlineIconClass, tableClass: config.systemInfoTableClass, nameInfoClass: config.systemInfoNameClass, effectInfoClass: config.systemInfoEffectClass, planetsInfoClass: config.systemInfoPlanetsClass, wormholePrefixClass: config.systemInfoWormholeClass, statusInfoClass: config.systemInfoStatusLabelClass, popoverTriggerClass: Util.config.popoverTriggerClass, systemUrl: MapUtil.getMapDeeplinkUrl(mapId, systemData.id), systemTypeName: MapUtil.getSystemTypeInfo(systemData.type.id, 'name'), systemIsWormhole: MapUtil.getSystemTypeInfo(systemData.type.id, 'name') === 'w-space', systemStatusId: systemData.status.id, systemStatusClass: Util.getStatusInfoForSystem(systemData.status.id, 'class'), systemStatusLabel: Util.getStatusInfoForSystem(systemData.status.id, 'label'), securityClass: Util.getSecurityClassForSystem( systemData.security ), trueSec: systemData.trueSec.toFixed(1), trueSecClass: Util.getTrueSecClassForSystem( systemData.trueSec ), effectName: effectName, effectClass: effectClass, descriptionAreaClass: config.descriptionAreaClass, descriptionButtonClass: config.addDescriptionButtonClass, descriptionTextareaClass: config.descriptionTextareaElementClass, summernoteClass: Util.config.summernoteClass, systemNameClass: () => { return (val, render) => { return render(val) === 'A' ? config.fontTriglivianClass : ''; }; }, formatUrl: () => { return (val, render) => render(val).replace(/ /g, '_'); }, planetCount: systemData.planets ? systemData.planets.length : 0, shatteredClass: Util.getSecurityClassForSystem('SH'), ajaxConstellationInfoUrl: Init.path.getConstellationData, systemConstellationLinkClass: config.constellationLinkClass, systemRegionLinkClass: config.regionLinkClass, systemTypeLinkClass: config.typeLinkClass, systemUrlLinkClass: config.urlLinkClass }; requirejs(['text!templates/modules/system_info.html', 'mustache', 'summernote.loader'], (template, Mustache, Summernote) => { let content = Mustache.render(template, data); moduleElement.append(content); let descriptionArea = moduleElement.find('.' + config.descriptionAreaClass); let descriptionButton = moduleElement.find('.' + config.addDescriptionButtonClass); let descriptionTextareaElement = moduleElement.find('.' + config.descriptionTextareaElementClass); // lock "description" field until first update descriptionArea.showLoadingAnimation(); // WYSIWYG init on button click --------------------------------------------------------------------------- descriptionButton.on('click', function(e){ e.stopPropagation(); let descriptionButton = $(this); // hide edit button descriptionButton.hide(); // content has changed let descriptionChanged = false; Summernote.initSummernote(descriptionTextareaElement, { height: 75, // set editor height minHeight: 75, // set minimum height of editor maxHeight: 500, // set maximum height of editor focus: true, placeholder: false, maxTextLength: maxDescriptionLength, disableDragAndDrop: true, shortcuts: false, toolbar: [ ['style', ['style']], ['font', ['underline', 'strikethrough', 'clear']], ['color', ['color']], ['para', ['ul', 'ol', 'paragraph']], ['table', ['table']], ['insert', ['link', 'hr']], //['view', ['codeview', 'help']], ['misc', ['undo', 'redo']], ['lengthField'], ['customBtn', ['discardBtn', 'saveBtn']] ], buttons: { saveBtn: context => { let ui = $.summernote.ui; let button = ui.button({ contents: '', container: 'body', className: ['btn-success', 'btn-save'], click: e => { context.layoutInfo.editable.removeClass('has-error'); // save changes if(descriptionChanged){ let validDescription = true; let description = ''; if( context.$note.summernote('isEmpty') ){ // ... isEmpty -> clear empty default tags as well context.$note.summernote('code', ''); }else{ description = context.$note.summernote('code'); if( !Util.isValidHtml(description) ){ // ... not valid HTML validDescription = false; context.layoutInfo.editable.addClass('has-error'); Util.showNotify({title: 'Validation failed', text: 'HTML not valid', type: 'error'}); } } if(validDescription){ // ... valid -> save() descriptionArea.showLoadingAnimation(); Util.request('PATCH', 'system', systemData.id, { description: description }, { descriptionArea: descriptionArea }, context => { // always do context.descriptionArea.hideLoadingAnimation(); }).then( payload => { context.$note.summernote('destroy'); updateModule(moduleElement, payload.data); }, Util.handleAjaxErrorResponse ); } }else{ // ... no changes -> no save() context.$note.summernote('destroy'); } } }); return button.render(); } }, callbacks: { onInit: function(context){ // make editable field a bit larger context.editable.css('height', '150px'); // set default background color // -> could not figure out how to set by API as default color context.toolbar.find('.note-current-color-button').attr('data-backcolor', config.defaultBgColor) .find('.note-recent-color').css('background-color', config.defaultBgColor); }, onChange: function(contents){ descriptionChanged = true; }, onPaste: function (e) { let bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text'); e.preventDefault(); // Firefox fix setTimeout(() => { document.execCommand('insertText', false, bufferText); }, 10); }, onDestroy: function(context){ descriptionButton.show(); } } }); }); // init system effect popover ----------------------------------------------------------------------------- moduleElement.find('.' + config.systemInfoEffectClass).addSystemEffectTooltip(systemData.security, systemData.effect); // init planets popover ----------------------------------------------------------------------------------- moduleElement.find('.' + config.systemInfoPlanetsClass).addSystemPlanetsTooltip(systemData.planets); // init static wormhole information ----------------------------------------------------------------------- for(let staticData of staticsData){ let staticRowElement = moduleElement.find('.' + config.systemInfoWormholeClass + staticData.name); staticRowElement.addWormholeInfoTooltip(staticData); } // copy system deeplink URL ------------------------------------------------------------------------------- moduleElement.find('.' + config.urlLinkClass).on('click', function(){ let mapUrl = $(this).attr('data-url'); Util.copyToClipboard(mapUrl).then(payload => { if(payload.data){ Util.showNotify({title: 'Copied to clipbaord', text: mapUrl, type: 'success'}); } }); }); // constellation popover ---------------------------------------------------------------------------------- moduleElement.find('a.popup-ajax').popover({ html: true, trigger: 'hover', placement: 'top', delay: 200, container: 'body', content: function(){ return details_in_popup(this); } }); let details_in_popup = popoverElement => { popoverElement = $(popoverElement); let popover = popoverElement.data('bs.popover'); $.ajax({ url: popoverElement.data('url'), success: function(data){ popover.options.content = Util.getSystemsInfoTable(data.systemsData); // reopen popover (new content size) popover.show(); } }); return 'Loading...'; }; // init tooltips ------------------------------------------------------------------------------------------ let tooltipElements = moduleElement.find('[data-toggle="tooltip"]'); tooltipElements.tooltip({ container: 'body', placement: 'top' }); }); return moduleElement; }; /** * efore module destroy callback * @param moduleElement */ let beforeDestroy = moduleElement => { moduleElement.find('.' + config.descriptionTextareaElementClass).summernote('destroy'); moduleElement.destroyPopover(true); }; return { config: config, getModule: getModule, updateModule: updateModule, beforeDestroy: beforeDestroy }; });