/**
* System intel module
*/
define([
'jquery',
'app/init',
'app/util',
'bootbox',
'app/counter',
'app/map/util',
], ($, Init, Util, bootbox, Counter, MapUtil) => {
'use strict';
let config = {
// module info
modulePosition: 1,
moduleName: 'systemIntel',
moduleHeadClass: 'pf-module-head', // class for module header
moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler
// system intel module
moduleTypeClass: 'pf-system-intel-module', // class for this module
// headline toolbar
moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head
// system intel module
intelTableId: 'pf-intel-table-', // id prefix for all tables in module
intelTableRowIdPrefix: 'pf-intel-row-', // id prefix for table rows
systemStationsTableClass: 'pf-system-station-table', // class for NPC owned stations table
systemStructuresTableClass: 'pf-system-structure-table', // class for player owned structures table
// structure dialog
structureDialogId: 'pf-structure-dialog', // id for "structure" dialog
nameInputId: 'pf-structure-dialog-name-input', // id for "name" input
statusSelectId: 'pf-structure-dialog-status-select', // id for "status" select
typeSelectId: 'pf-structure-dialog-type-select', // id for "type" select
corporationSelectId: 'pf-structure-dialog-corporation-select', // id for "corporation" select
descriptionTextareaId: 'pf-structure-dialog-description-textarea', // id for "description" textarea
descriptionTextareaCharCounter: 'pf-form-field-char-count', // class for "character counter" element for form field
// dataTable
tableCellImageClass: 'pf-table-image-smaller-cell', // class for table "image" cells
tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells
tableCellEllipsisClass: 'pf-table-cell-ellipses-auto', // class for table "ellipsis" cells
tableCellActionClass: 'pf-table-action-cell', // class for "action" cells
tableCellActionIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
tableCellServicesClass: 'pf-table-services-cell', // class for table station "services" cells
tableCellPopoverClass: 'pf-table-popover-cell' // class for table cells with a "popover"
};
let maxDescriptionLength = 512;
/**
* get status icon for structure
* @param statusData
* @returns {string}
*/
let getIconForStatusData = statusData => {
return '';
};
/**
* get icon that marks a table cell as clickable
* @returns {string}
*/
let getIconForInformationWindow = () => {
return '';
};
/**
* get a dataTableApi instance from global cache
* @param mapId
* @param systemId
* @param tableType
* @returns {*}
*/
let getDataTableInstance = (mapId, systemId, tableType) => Util.getDataTableInstance(config.intelTableId, mapId, systemId, tableType);
/**
* get dataTable id
* @param mapId
* @param systemId
* @param tableType
* @returns {string}
*/
let getTableId = (tableType, mapId, systemId) => Util.getTableId(config.intelTableId, tableType, mapId, systemId);
/**
* get dataTable row id
* @param tableType
* @param id
* @returns {string}
*/
let getRowId = (tableType, id) => Util.getTableRowId(config.intelTableRowIdPrefix, tableType, id);
/**
* get
DOM id by id
* @param tableApi
* @param id
* @returns {*}
*/
let getRowById = (tableApi, id) => {
return tableApi.rows().ids().toArray().find(rowId => rowId === getRowId(Util.getObjVal(getTableMetaData(tableApi), 'type'), id));
};
/**
* get custom "metaData" from dataTables API
* @param tableApi
* @returns {*}
*/
let getTableMetaData = tableApi => {
let data = null;
if(tableApi){
data = tableApi.init().pfMeta;
}
return data;
};
/**
* vormat roman numeric string to int
* -> e.g. 'VII' => 7
* @param str
* @returns {number}
*/
let romanToInt = str => {
let charToTnt = char => {
switch (char) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return -1;
}
};
if(str == null) return -1;
let num = charToTnt(str.charAt(0));
let pre, curr;
for(let i = 1; i < str.length; i++){
curr = charToTnt(str.charAt(i));
pre = charToTnt(str.charAt(i - 1));
if(curr <= pre){
num += curr;
}else{
num = num - pre * 2 + curr;
}
}
return num;
};
/**
* callback -> add table rows from grouped tableData
* @param context
* @param tableData
* @param groupedDataKey
*/
let callbackUpdateTableRows = (context, tableData, groupedDataKey = 'structures') => {
let touchedRows = [];
let hadData = context.tableApi.rows().any();
let notificationCounter = {
added: 0,
changed: 0,
deleted: 0
};
if(tableData){
for(let [rowGroupId, rowGroupData] of Object.entries(tableData)){
if(rowGroupData[groupedDataKey] && rowGroupData[groupedDataKey].length){
for(let rowData of rowGroupData[groupedDataKey]){
let rowId = getRowById(context.tableApi, rowData.id);
// add rowGroupData as well to each rowData
rowData.rowGroupData = {
id: rowGroupData.id,
name: rowGroupData.name,
groupedDataKey: groupedDataKey
};
if(rowId){
// update row
let api = context.tableApi.row('#' + rowId);
let rowDataCurrent = api.data();
// check for update
if(rowDataCurrent.updated.updated !== rowData.updated.updated){
// row data changed -> update
api.data(rowData);
notificationCounter.changed++;
}
touchedRows.push(api.id());
}else{
// insert new row
let api = context.tableApi.row.add(rowData);
api.nodes().to$().data('animationStatus', 'added');
notificationCounter.added++;
touchedRows.push(api.id());
}
}
}
}
}
if(context.removeMissing){
let api = context.tableApi.rows((idx, data, node) => !touchedRows.includes(node.id));
notificationCounter.deleted += api.ids().count();
api.remove();
}
if(
notificationCounter.added > 0 ||
notificationCounter.changed > 0 ||
notificationCounter.deleted > 0
){
context.tableApi.draw();
}
// show notification ------------------------------------------------------------------------------------------
let notification = '';
notification += notificationCounter.added > 0 ? notificationCounter.added + ' added ' : '';
notification += notificationCounter.changed > 0 ? notificationCounter.changed + ' changed ' : '';
notification += notificationCounter.deleted > 0 ? notificationCounter.deleted + ' deleted ' : '';
if(hadData && notification.length){
Util.showNotify({title: 'Structures updated', text: notification, type: 'success'});
}
};
/**
* callback -> delete structure rows
* @param context
* @param structureIds
*/
let callbackDeleteStructures = (context, structureIds) => {
let deletedCounter = 0;
if(structureIds && structureIds.length){
for(let structureId of structureIds){
let rowId = getRowById(context.tableApi, structureId);
if(rowId){
context.tableApi.row('#' + rowId).remove();
deletedCounter++;
}
}
}
if(deletedCounter){
context.tableApi.draw();
Util.showNotify({title: 'Structure deleted', text: deletedCounter + ' deleted', type: 'success'});
}
};
/**
* send ajax request
* @param url
* @param requestData
* @param context
* @param callback
*/
let sendRequest = (url, requestData, context, callback) => {
context.moduleElement.showLoadingAnimation();
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data: requestData,
context: context
}).done(function(data){
callback(this, data);
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': System intel data', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
// hide loading animation
this.moduleElement.hideLoadingAnimation();
});
};
/**
* show structure dialog
* @param moduleElement
* @param tableApi
* @param systemId
* @param structureData
* @param bulkData
*/
let showStructureDialog = (moduleElement, tableApi, systemId, structureData = null, bulkData = null) => {
let structureStatusData = Util.getObjVal(Init, 'structureStatus');
let statusData = Object.keys(structureStatusData).map((k) => {
let data = structureStatusData[k];
data.selected = data.id === Util.getObjVal(structureData, 'status.id');
return data;
});
// if current user is currently docked at a structure (not station)
// -> add a modal button for pre-fill modal with it
// -> systemId must match systemId from current character log
let currentUserData = Util.getCurrentUserData();
let isCurrentLocation = false;
let characterStructureId = Util.getObjVal(currentUserData, 'character.log.structure.id') || 0;
let characterStructureName = Util.getObjVal(currentUserData, 'character.log.structure.name') || '';
let characterStructureTypeId = Util.getObjVal(currentUserData, 'character.log.structure.type.id') || 0;
let characterStructureTypeName = Util.getObjVal(currentUserData, 'character.log.structure.type.name') || '';
if(systemId === Util.getObjVal(currentUserData, 'character.log.system.id')){
isCurrentLocation = true;
}
let disableButtonAutoFill = true;
let buttonLabelAutoFill = ' ';
if(characterStructureId){
buttonLabelAutoFill += characterStructureTypeName + ' "' + characterStructureName + '"';
if(isCurrentLocation){
disableButtonAutoFill = false;
}
}else{
buttonLabelAutoFill += 'unknown structure';
}
let data = {
id: config.structureDialogId,
structureData: structureData,
bulkData: bulkData,
structureStatus: statusData,
nameInputId: config.nameInputId,
statusSelectId: config.statusSelectId,
typeSelectId: config.typeSelectId,
corporationSelectId: config.corporationSelectId,
descriptionTextareaId: config.descriptionTextareaId,
descriptionTextareaCharCounter: config.descriptionTextareaCharCounter,
maxDescriptionLength: maxDescriptionLength
};
requirejs(['text!templates/dialog/structure.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
let title = 'Structure';
if(bulkData){
title += ' (' + bulkData.length + ' rows) ';
}
let structureDialog = bootbox.dialog({
title: title,
message: content,
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default pull-left'
},
autoFill: {
label: buttonLabelAutoFill,
className: 'btn-primary' +
(disableButtonAutoFill ? ' pf-font-italic disabled' : '') +
(bulkData ? ' hidden' : ''),
callback: function(){
let form = this.find('form');
form.find('#' + config.nameInputId).val(characterStructureName);
form.find('#' + config.statusSelectId).val(2).trigger('change');
form.find('#' + config.typeSelectId).val(characterStructureTypeId).trigger('change');
return false;
}
},
success: {
label: ' save',
className: 'btn-success',
callback: function(){
let form = this.find('form');
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
if(formValid){
// get form data
let formData = form.getFormValues();
formData.id = Util.getObjVal(structureData, 'id') | 0;
formData.structureId = Util.getObjVal(formData, 'structureId') | 0;
formData.corporationId = Util.getObjVal(formData, 'corporationId') | 0;
formData.systemId = systemId | 0;
moduleElement.showLoadingAnimation();
let method = formData.id ? 'PATCH' : 'PUT';
let ids = formData.id;
let data = formData;
if(bulkData){
// bulk update multiple rows
method = 'POST';
ids = [];
data = bulkData.map(structureData => {
structureData.corporationId = formData.corporationId;
return structureData;
});
}
Util.request(method, 'structure', ids, data,
{
moduleElement: moduleElement,
tableApi: tableApi
},
context => context.moduleElement.hideLoadingAnimation()
).then(
payload => callbackUpdateTableRows(payload.context, payload.data),
Util.handleAjaxErrorResponse
);
}else{
return false;
}
}
}
}
});
structureDialog.on('show.bs.modal', function(e){
let modalContent = $('#' + config.structureDialogId);
// init type select live search
let selectElementType = modalContent.find('#' + config.typeSelectId);
selectElementType.initUniverseTypeSelect({
categoryIds: [65],
maxSelectionLength: 1,
selected: [Util.getObjVal(structureData, 'structure.id')]
});
// init corporation select live search
let selectElementCorporation = modalContent.find('#' + config.corporationSelectId);
selectElementCorporation.initUniverseSearch({
categoryNames: ['corporation'],
maxSelectionLength: 1
});
// init status select2
modalContent.find('#' + config.statusSelectId).initStatusSelect({
data: statusData
});
// init char counter
let textarea = modalContent.find('#' + config.descriptionTextareaId);
let charCounter = modalContent.find('.' + config.descriptionTextareaCharCounter);
Util.updateCounter(textarea, charCounter, maxDescriptionLength);
textarea.on('keyup', function(){
Util.updateCounter($(this), charCounter, maxDescriptionLength);
});
// set form validator (after select2 init finish)
modalContent.find('form').initFormValidation();
});
// show dialog
structureDialog.modal('show');
});
};
/**
* show D-Scan reader dialog
* @param moduleElement
* @param tableApi
* @param systemData
*/
let showDscanReaderDialog = (moduleElement, tableApi, systemData) => {
requirejs(['text!templates/dialog/dscan_reader.html', 'mustache'], (template, Mustache) => {
let structureDialog = bootbox.dialog({
title: 'D-Scan reader',
message: Mustache.render(template, {}),
show: true,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: ' update intel',
className: 'btn-success',
callback: function(){
let form = this.find('form');
let formData = form.getFormValues();
updateStructureTableByClipboard(systemData, formData.clipboard, {
moduleElement: moduleElement,
tableApi: tableApi
});
}
}
}
});
// dialog shown event
structureDialog.on('shown.bs.modal', function(e){
// set focus on textarea
structureDialog.find('textarea').focus();
});
});
};
/**
* init station services tooltips
* @param element
* @param tableApi
*/
let initStationServiceTooltip = (element, tableApi) => {
element.hoverIntent({
over: function(e){
let cellElement = $(this);
let rowData = tableApi.row(cellElement.parents('tr')).data();
cellElement.addStationServiceTooltip(Util.getObjVal(rowData, 'services'), {
placement: 'left',
trigger: 'manual',
show: true
});
},
out: function(e){
$(this).destroyPopover();
},
selector: 'td.' + config.tableCellServicesClass
});
};
/**
* get dataTables default options for intel tables
* @returns {*}
*/
let getDataTableDefaults = () => {
return {
paging: false,
lengthChange: false,
ordering: true,
info: false,
searching: false,
hover: false,
autoWidth: false,
drawCallback: function (settings) {
let tableApi = this.api();
let columnCount = tableApi.columns(':visible').count();
let rows = tableApi.rows({page: 'current'}).nodes();
let last = null;
tableApi.column('rowGroupData:name', {page: 'current'}).data().each(function (group, i) {
if (!last || last.id !== group.id) {
// "stations" are grouped by "raceId" with its "factionId"
// "structures" are grouped by "corporationId" that ADDED it (not the ingame "owner" of it)
let imgType = 'stations' === group.groupedDataKey ? 'factions' : 'corporations';
$(rows).eq(i).before(
'
' +
'
' +
'
' +
'' +
'
' +
'
' + group.name + '
' +
'
'
);
last = group;
}
});
let animationRows = rows.to$().filter(function () {
return (
$(this).data('animationStatus') ||
$(this).data('animationTimer')
);
});
for (let i = 0; i < animationRows.length; i++) {
let animationRow = $(animationRows[i]);
animationRow.pulseBackgroundColor(animationRow.data('animationStatus'));
animationRow.removeData('animationStatus');
}
}
};
};
/**
* get module element
* @param parentElement
* @param mapId
* @param systemData
* @returns {jQuery}
*/
let getModule = (parentElement, mapId, systemData) => {
let showStationTable = ['H', 'L', '0.0', 'C12'].includes(Util.getObjVal(systemData, 'security'));
let corporationId = Util.getCurrentUserInfo('corporationId');
let moduleElement = $('