/**
* 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
};
});