2583 lines
104 KiB
JavaScript
2583 lines
104 KiB
JavaScript
/**
|
||
* System signature module
|
||
*/
|
||
|
||
define([
|
||
'jquery',
|
||
'app/init',
|
||
'app/util',
|
||
'bootbox',
|
||
'app/counter',
|
||
'app/map/map',
|
||
'app/map/util',
|
||
'app/ui/form_element'
|
||
], ($, Init, Util, bootbox, Counter, Map, MapUtil, FormElement) => {
|
||
'use strict';
|
||
|
||
let config = {
|
||
// module info
|
||
modulePosition: 4,
|
||
moduleName: 'systemSignature',
|
||
moduleHeadClass: 'pf-module-head', // class for module header
|
||
moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler
|
||
|
||
// system signature module
|
||
moduleTypeClass: 'pf-system-signature-module', // class for this module
|
||
|
||
// headline toolbar
|
||
moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head
|
||
moduleHeadlineIconAddClass: 'pf-module-icon-button-add', // class for "add signature" icon
|
||
moduleHeadlineIconReaderClass: 'pf-module-icon-button-reader', // class for "signature reader" icon
|
||
moduleHeadlineIconLazyClass: 'pf-module-icon-button-lazy', // class for "lazy delete" toggle icon
|
||
moduleHeadlineProgressBarClass: 'pf-system-progress-scanned', // class for signature progress bar
|
||
|
||
// tables
|
||
tableToolsActionClass: 'pf-table-tools-action', // class for "new signature" table (hidden)
|
||
|
||
// table toolbar
|
||
sigTableClearButtonClass: 'pf-sig-table-clear-button', // class for "clear" signatures button
|
||
|
||
// signature table
|
||
sigTableId: 'pf-sig-table-', // Table id prefix
|
||
sigTableClass: 'pf-sig-table', // Table class for all Signature Tables
|
||
sigTablePrimaryClass: 'pf-sig-table-primary', // class for primary sig table
|
||
sigTableSecondaryClass: 'pf-sig-table-secondary', // class for secondary sig table
|
||
sigTableRowIdPrefix: 'pf-sig-row_', // id prefix for table rows
|
||
|
||
sigTableEditSigNameInput: 'pf-sig-table-edit-name-input', // class for editable fields (sig name)
|
||
|
||
tableCellConnectionClass: 'pf-table-connection-cell', // class for "connection" cells
|
||
tableCellFocusClass: 'pf-table-focus-cell', // class for "tab-able" cells. enable focus()
|
||
tableCellCounterClass: 'pf-table-counter-cell', // class for "counter" cells
|
||
tableCellActionClass: 'pf-table-action-cell', // class for "action" cells
|
||
|
||
// xEditable
|
||
editableNameInputClass: 'pf-editable-name', // class for "name" input
|
||
editableDescriptionInputClass: 'pf-editable-description', // class for "description" textarea
|
||
editableUnknownInputClass: 'pf-editable-unknown', // class for input fields (e.g. checkboxes) with "unknown" status
|
||
|
||
signatureGroupsLabels: Util.getSignatureGroupOptions('label'),
|
||
signatureGroupsNames: Util.getSignatureGroupOptions('name')
|
||
};
|
||
|
||
let lockedTables = {}; // locked tables (e.g. disable cops&paste, disable table update)
|
||
|
||
let sigNameCache = {}; // cache signature names
|
||
|
||
let validSignatureNames = [ // allowed signature type/names
|
||
'Cosmic Anomaly',
|
||
'Cosmic Signature',
|
||
'Kosmische Anomalie',
|
||
'Kosmische Signatur',
|
||
'Anomalie cosmique',
|
||
'Signature cosmique',
|
||
'Космическая аномалия', // == "Cosmic Anomaly"
|
||
'Источники сигналов' // == "Cosmic Signature"
|
||
];
|
||
|
||
let emptySignatureData = {
|
||
id: 0,
|
||
name: '',
|
||
groupId: 0,
|
||
typeId: 0
|
||
};
|
||
|
||
let editableDefaults = { // xEditable default options for signature fields
|
||
url: Init.path.saveSignatureData,
|
||
dataType: 'json',
|
||
container: 'body',
|
||
highlight: false, // i use a custom implementation. xEditable uses inline styles for bg color animation -> does not work properly on datatables "sort" cols
|
||
error: function(jqXHR, newValue){
|
||
let reason = '';
|
||
let status = 'Error';
|
||
if(jqXHR.statusText){
|
||
reason = jqXHR.statusText;
|
||
}else if(jqXHR.name){
|
||
// validation error new sig (new row data save function)
|
||
reason = jqXHR.name;
|
||
// re-open "name" fields (its a collection of fields but we need "id" field)
|
||
jqXHR.name.field.$element.editable('show');
|
||
}else{
|
||
reason = jqXHR.responseJSON.text;
|
||
status = jqXHR.status;
|
||
}
|
||
|
||
Util.showNotify({title: status + ': save signature', text: reason, type: 'error'});
|
||
$(document).setProgramStatus('problem');
|
||
return reason;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* get custom "metaData" from dataTables API
|
||
* @param tableApi
|
||
* @returns {*}
|
||
*/
|
||
let getTableMetaData = tableApi => {
|
||
let data = null;
|
||
if(tableApi){
|
||
data = tableApi.init().pfMeta;
|
||
}
|
||
return data;
|
||
};
|
||
|
||
/**
|
||
* lock signature tableApi and lockType
|
||
* @param tableApi
|
||
* @param lockType
|
||
*/
|
||
let lockTable = (tableApi, lockType = 'update') => {
|
||
let metaData = getTableMetaData(tableApi);
|
||
if(metaData.systemId){
|
||
if( !lockedTables.hasOwnProperty(metaData.systemId) ){
|
||
lockedTables[metaData.systemId] = {};
|
||
}
|
||
lockedTables[metaData.systemId][lockType] = true;
|
||
}else{
|
||
console.warn('metaData.systemId required in lockTable()', metaData.systemId);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* check whether a signature tableApi is locked by lockType
|
||
* @param tableApi
|
||
* @param lockType
|
||
* @returns {boolean}
|
||
*/
|
||
let isLockedTable = (tableApi, lockType = 'update') => {
|
||
let locked = false;
|
||
if(tableApi){
|
||
let metaData = getTableMetaData(tableApi);
|
||
if(metaData.systemId){
|
||
if(
|
||
lockedTables.hasOwnProperty(metaData.systemId) &&
|
||
lockedTables[metaData.systemId].hasOwnProperty(lockType)
|
||
){
|
||
locked = true;
|
||
}
|
||
}else{
|
||
console.warn('metaData.systemId required in isLockedTable()', metaData.systemId);
|
||
}
|
||
}
|
||
|
||
return locked;
|
||
};
|
||
|
||
/**
|
||
* unlock signature tableApi and lockType
|
||
* @param tableApi
|
||
* @param lockType
|
||
*/
|
||
let unlockTable = (tableApi, lockType = 'update') => {
|
||
if(tableApi){
|
||
let metaData = getTableMetaData(tableApi);
|
||
if(isLockedTable(tableApi, lockType)){
|
||
delete lockedTables[metaData.systemId][lockType];
|
||
}
|
||
if(
|
||
lockedTables.hasOwnProperty(metaData.systemId) &&
|
||
!Object.getOwnPropertyNames(lockedTables[metaData.systemId]).length
|
||
){
|
||
delete lockedTables[metaData.systemId];
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* get dataTable id
|
||
* @param mapId
|
||
* @param systemId
|
||
* @param tableType
|
||
* @returns {string}
|
||
*/
|
||
let getTableId = (mapId, systemId, tableType) => Util.getTableId(config.sigTableId, mapId, systemId, tableType);
|
||
|
||
/**
|
||
* get a dataTableApi instance from global cache
|
||
* @param mapId
|
||
* @param systemId
|
||
* @param tableType
|
||
* @returns {*}
|
||
*/
|
||
let getDataTableInstance = (mapId, systemId, tableType) => Util.getDataTableInstance(config.sigTableId, mapId, systemId, tableType);
|
||
|
||
/**
|
||
* Update/set tooltip for an element
|
||
* @param element
|
||
* @param title
|
||
*/
|
||
let updateTooltip = (element, title) => {
|
||
$(element).attr('data-container', 'body')
|
||
.attr('title', title.toUpperCase())
|
||
.tooltip('fixTitle').tooltip('setContent');
|
||
};
|
||
|
||
/**
|
||
* sum up all options in nested (or not nested) object of objects
|
||
* -> e.g.
|
||
* {
|
||
* first: {
|
||
* count = [4, 2, 1]
|
||
* test = { ... }
|
||
* },
|
||
* second: {
|
||
* count = [12, 13]
|
||
* test = { ... }
|
||
* }
|
||
* }
|
||
* -> getOptionsCount('count', obj) => 5;
|
||
* @param key
|
||
* @param obj
|
||
* @returns {number}
|
||
*/
|
||
let getOptionsCount = (key, obj) => {
|
||
let sum = 0;
|
||
for(let entry of obj){
|
||
if(entry.hasOwnProperty(key)){
|
||
sum += entry[key].length;
|
||
}else{
|
||
sum++;
|
||
}
|
||
}
|
||
return sum;
|
||
};
|
||
|
||
/**
|
||
* get possible frig holes that could spawn in a system
|
||
* filtered by "systemTypeId"
|
||
* @param systemTypeId
|
||
* @returns {{}}
|
||
*/
|
||
let getFrigateHolesBySystem = systemTypeId => {
|
||
let signatureNames = {};
|
||
if(Init.frigateWormholes[systemTypeId]){
|
||
signatureNames = Init.frigateWormholes[systemTypeId];
|
||
}
|
||
return signatureNames;
|
||
};
|
||
|
||
/**
|
||
* get all signature types that can exist for a given system
|
||
* -> result is partially cached
|
||
* @param systemData
|
||
* @param systemTypeId
|
||
* @param areaId
|
||
* @param groupId
|
||
* @returns {Array}
|
||
*/
|
||
let getAllSignatureNames = (systemData, systemTypeId, areaId, groupId) => {
|
||
systemTypeId = parseInt(systemTypeId || 0);
|
||
areaId = parseInt(areaId || 0);
|
||
groupId = parseInt(groupId || 0);
|
||
let newSelectOptions = [];
|
||
let newSelectOptionsCount = 0;
|
||
|
||
if(!systemTypeId || !areaId || !groupId){
|
||
return newSelectOptions;
|
||
}
|
||
|
||
let cacheKey = [systemTypeId, areaId, groupId].join('_');
|
||
|
||
// check for cached signature names
|
||
if(sigNameCache.hasOwnProperty( cacheKey )){
|
||
// cached signatures do not include static WHs!
|
||
// -> ".slice(0)" creates copy
|
||
newSelectOptions = sigNameCache[cacheKey].slice(0);
|
||
newSelectOptionsCount = getOptionsCount('children', newSelectOptions);
|
||
}else{
|
||
// get new Options ----------
|
||
// get all possible "static" signature names by the selected groupId
|
||
let tempSelectOptions = Util.getAllSignatureNames(systemTypeId, areaId, groupId);
|
||
|
||
// format options into array with objects advantages: keep order, add more options (whs), use optgroup
|
||
if(tempSelectOptions){
|
||
let fixSelectOptions = [];
|
||
for(let key in tempSelectOptions){
|
||
if(
|
||
key > 0 &&
|
||
tempSelectOptions.hasOwnProperty(key)
|
||
){
|
||
newSelectOptionsCount++;
|
||
fixSelectOptions.push({value: newSelectOptionsCount, text: tempSelectOptions[key]});
|
||
}
|
||
}
|
||
|
||
if(newSelectOptionsCount > 0){
|
||
if(groupId === 5){
|
||
// "wormhole" selected => multiple <optgroup> available
|
||
newSelectOptions.push({ text: 'Wandering', children: fixSelectOptions});
|
||
}else{
|
||
newSelectOptions = fixSelectOptions;
|
||
}
|
||
}
|
||
}
|
||
|
||
// wormhole (cached signatures)
|
||
if( groupId === 5 ){
|
||
|
||
// add possible frigate holes
|
||
let frigateHoles = getFrigateHolesBySystem(areaId);
|
||
let frigateWHData = [];
|
||
for(let frigKey in frigateHoles){
|
||
if(
|
||
frigKey > 0 &&
|
||
frigateHoles.hasOwnProperty(frigKey)
|
||
){
|
||
newSelectOptionsCount++;
|
||
frigateWHData.push( {value: newSelectOptionsCount, text: frigateHoles[frigKey]} );
|
||
}
|
||
}
|
||
|
||
if(frigateWHData.length > 0){
|
||
newSelectOptions.push({ text: 'Frigate', children: frigateWHData});
|
||
}
|
||
|
||
// add possible incoming holes
|
||
let incomingWHData = [];
|
||
for(let incomingKey in Init.incomingWormholes){
|
||
if(
|
||
incomingKey > 0 &&
|
||
Init.incomingWormholes.hasOwnProperty(incomingKey)
|
||
){
|
||
newSelectOptionsCount++;
|
||
incomingWHData.push( {value: newSelectOptionsCount, text: Init.incomingWormholes[incomingKey]} );
|
||
}
|
||
}
|
||
|
||
if(incomingWHData.length > 0){
|
||
newSelectOptions.push({ text: 'Incoming', children: incomingWHData});
|
||
}
|
||
}else{
|
||
// groups without "children" (optgroup) should be sorted by "value"
|
||
// this is completely optional and not necessary!
|
||
newSelectOptions = newSelectOptions.sortBy('text');
|
||
}
|
||
|
||
// update cache (clone array) -> further manipulation to this array, should not be cached
|
||
sigNameCache[cacheKey] = newSelectOptions.slice(0);
|
||
}
|
||
|
||
// static wormholes (DO NOT CACHE) (not all C2 WHs have the same statics,...
|
||
if( groupId === 5 ){
|
||
// add static WH(s) for this system
|
||
if(systemData.statics){
|
||
let staticWHData = [];
|
||
for(let wormholeName of systemData.statics){
|
||
let wormholeData = Object.assign({}, Init.wormholes[wormholeName]);
|
||
let staticWHName = wormholeData.name + ' - ' + wormholeData.security;
|
||
|
||
newSelectOptionsCount++;
|
||
staticWHData.push( {value: newSelectOptionsCount, text: staticWHName} );
|
||
}
|
||
|
||
if(staticWHData.length > 0){
|
||
newSelectOptions.unshift({ text: 'Static', children: staticWHData});
|
||
}
|
||
}
|
||
}
|
||
|
||
return newSelectOptions;
|
||
};
|
||
|
||
/**
|
||
* get all signature types that can exist for a system (jQuery obj)
|
||
* @param systemElement
|
||
* @param groupId
|
||
* @returns {Array}
|
||
*/
|
||
let getAllSignatureNamesBySystem = (systemElement, groupId) => {
|
||
let systemTypeId = systemElement.data('typeId');
|
||
let areaId = Util.getAreaIdBySecurity(systemElement.data('security'));
|
||
let systemData = {statics: systemElement.data('statics')};
|
||
return getAllSignatureNames(systemData, systemTypeId, areaId, groupId);
|
||
};
|
||
|
||
/**
|
||
* get all connection select options
|
||
* @param mapId
|
||
* @param systemData
|
||
* @returns {Array}
|
||
*/
|
||
let getSignatureConnectionOptions = (mapId, systemData) => {
|
||
let map = Map.getMapInstance( mapId );
|
||
let systemId = MapUtil.getSystemId(mapId, systemData.id);
|
||
let systemConnections = MapUtil.searchConnectionsBySystems(map, [systemId], 'wh');
|
||
let newSelectOptions = [];
|
||
let connectionOptions = [];
|
||
|
||
/**
|
||
* get option data for a single connection
|
||
* @param type
|
||
* @param connectionData
|
||
* @param systemData
|
||
* @returns {{value: *, text: string, metaData: {type: *}}}
|
||
*/
|
||
let getOption = (type, connectionData, systemData) => {
|
||
let text = 'UNKNOWN';
|
||
if(type === 'source'){
|
||
text = connectionData.sourceAlias + ' - ' + systemData.security;
|
||
}else if(type === 'target'){
|
||
text = connectionData.targetAlias + ' - ' + systemData.security;
|
||
}
|
||
|
||
let option = {
|
||
value: connectionData.id,
|
||
text: text,
|
||
metaData: {
|
||
type: connectionData.type
|
||
}
|
||
};
|
||
|
||
return option;
|
||
};
|
||
|
||
for(let systemConnection of systemConnections){
|
||
let connectionData = MapUtil.getDataByConnection(systemConnection);
|
||
|
||
// connectionId is required (must be stored)
|
||
if(connectionData.id){
|
||
// check whether "source" or "target" system is relevant for this connection
|
||
// -> hint "source" === 'target' --> loop
|
||
if(systemData.id !== connectionData.target){
|
||
let targetSystemData = MapUtil.getSystemData(mapId, connectionData.target);
|
||
if(targetSystemData){
|
||
// take target...
|
||
connectionOptions.push(getOption('target', connectionData, targetSystemData));
|
||
}
|
||
}else if(systemData.id !== connectionData.source){
|
||
let sourceSystemData = MapUtil.getSystemData(mapId, connectionData.source);
|
||
if(sourceSystemData){
|
||
// take source...
|
||
connectionOptions.push(getOption('source', connectionData, sourceSystemData));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if(connectionOptions.length > 0){
|
||
newSelectOptions.push({ text: 'System', children: connectionOptions});
|
||
}
|
||
|
||
return newSelectOptions;
|
||
};
|
||
|
||
/**
|
||
* show/hides a table <tr> rowElement
|
||
* @param rowElement
|
||
*/
|
||
let toggleTableRow = rowElement => {
|
||
|
||
let toggleTableRowExecutor = (resolve, reject) => {
|
||
let cellElements = rowElement.children('td');
|
||
let duration = 350;
|
||
// wrap each <td> into a container (for better animation performance)
|
||
// slideUp new wrapper divs
|
||
if(rowElement.is(':visible')){
|
||
// hide row
|
||
|
||
// stop sig counter by adding a stopClass to each <td>, remove padding
|
||
cellElements.addClass('stopCounter')
|
||
.velocity({
|
||
paddingTop: [0, '4px'],
|
||
paddingBottom: [0, '4px'],
|
||
opacity: [0, 1]
|
||
},{
|
||
duration: duration,
|
||
easing: 'linear'
|
||
}).wrapInner('<div>')
|
||
.children()
|
||
.css({
|
||
'willChange': 'height'
|
||
}).velocity('slideUp', {
|
||
duration: duration,
|
||
easing: 'linear',
|
||
complete: function(animationElements){
|
||
// remove wrapper
|
||
$(animationElements).children().unwrap();
|
||
|
||
resolve({
|
||
action: 'rowHidden',
|
||
row: rowElement
|
||
});
|
||
}
|
||
});
|
||
}else{
|
||
// show row
|
||
|
||
// remove padding on "hidden" cells for smother animation
|
||
cellElements.css({
|
||
'padding-top': 0,
|
||
'padding-bottom': 0,
|
||
'willChange': 'padding-top, padding-top, height'
|
||
});
|
||
|
||
// add hidden wrapper for ea
|
||
cellElements.wrapInner($('<div>').hide());
|
||
|
||
// show row for padding animation
|
||
rowElement.show();
|
||
|
||
cellElements.velocity({
|
||
paddingTop: ['4px', 0],
|
||
paddingBottom: ['4px', 0]
|
||
},{
|
||
duration: duration,
|
||
queue: false,
|
||
complete: function(){
|
||
// animate <td> wrapper
|
||
cellElements.children()
|
||
.css({
|
||
'willChange': 'height'
|
||
}).velocity('slideDown', {
|
||
duration: duration,
|
||
complete: function(animationElements){
|
||
// remove wrapper
|
||
for(let i = 0; i < animationElements.length; i++){
|
||
let currentWrapper = $(animationElements[i]);
|
||
if(currentWrapper.children().length > 0){
|
||
currentWrapper.children().unwrap();
|
||
}else{
|
||
currentWrapper.parent().html( currentWrapper.html() );
|
||
}
|
||
}
|
||
|
||
resolve({
|
||
action: 'rowShown',
|
||
row: rowElement
|
||
});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
return new Promise(toggleTableRowExecutor);
|
||
};
|
||
|
||
/**
|
||
* update scanned signatures progress bar
|
||
* @param tableApi
|
||
* @param options
|
||
*/
|
||
let updateScannedSignaturesBar = (tableApi, options) => {
|
||
let tableElement = tableApi.table().node();
|
||
let moduleElement = $(tableElement).parents('.' + config.moduleTypeClass);
|
||
let progressBar = moduleElement.find('.progress-bar');
|
||
let progressBarLabel = moduleElement.find('.progress-label-right');
|
||
|
||
let percent = 0;
|
||
let progressBarType = '';
|
||
let columnGroupData = tableApi.column('group:name').data();
|
||
let sigCount = columnGroupData.length;
|
||
let sigIncompleteCount = columnGroupData.filter((value, index) => !value).length;
|
||
|
||
if(sigCount){
|
||
percent = 100 - Math.round( 100 / sigCount * sigIncompleteCount );
|
||
}
|
||
|
||
if(percent < 30){
|
||
progressBarType = 'progress-bar-danger';
|
||
}else if(percent < 100){
|
||
progressBarType = 'progress-bar-warning';
|
||
}else{
|
||
progressBarType = 'progress-bar-success';
|
||
}
|
||
|
||
progressBarLabel.text(percent + '%');
|
||
progressBar.removeClass().addClass('progress-bar').addClass(progressBarType);
|
||
progressBar.attr('aria-valuenow', percent);
|
||
progressBar.css({width: percent + '%'});
|
||
|
||
// show notifications
|
||
if(options.showNotice !== false){
|
||
let notification = (sigCount - sigIncompleteCount) + ' / ' + sigCount + ' (' + percent + '%) signatures scanned';
|
||
|
||
if(percent < 100){
|
||
Util.showNotify({title: 'Unscanned signatures', text: notification, type: 'info'});
|
||
}else{
|
||
Util.showNotify({title: 'System is scanned', text: notification, type: 'success'});
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* open "signature reader" dialog for signature table
|
||
* @param systemData
|
||
*/
|
||
$.fn.showSignatureReaderDialog = function(systemData){
|
||
let moduleElement = $(this);
|
||
|
||
requirejs(['text!templates/dialog/signature_reader.html', 'mustache'], (template, Mustache) => {
|
||
let signatureReaderDialog = bootbox.dialog({
|
||
title: 'Signature reader',
|
||
message: Mustache.render(template, {}),
|
||
buttons: {
|
||
close: {
|
||
label: 'cancel',
|
||
className: 'btn-default'
|
||
},
|
||
success: {
|
||
label: '<i class="fas fa-paste fa-fw"></i> update signatures',
|
||
className: 'btn-success',
|
||
callback: function(){
|
||
let form = this.find('form');
|
||
let formData = form.getFormValues();
|
||
let signatureOptions = {
|
||
deleteOld: (formData.deleteOld) ? 1 : 0
|
||
};
|
||
|
||
let mapId = moduleElement.data('mapId');
|
||
let systemId = moduleElement.data('systemId');
|
||
let tableApi = getDataTableInstance(mapId, systemId, 'primary');
|
||
|
||
updateSignatureTableByClipboard(tableApi, systemData, formData.clipboard, signatureOptions);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// dialog shown event
|
||
signatureReaderDialog.on('shown.bs.modal', function(e){
|
||
signatureReaderDialog.initTooltips();
|
||
|
||
// set focus on sig-input textarea
|
||
signatureReaderDialog.find('textarea').focus();
|
||
});
|
||
});
|
||
};
|
||
|
||
/**
|
||
* parses a copy&paste string from ingame scanning window
|
||
* @param systemData
|
||
* @param clipboard
|
||
* @returns {Array}
|
||
*/
|
||
let parseSignatureString = (systemData, clipboard) => {
|
||
let signatureData = [];
|
||
|
||
if(clipboard.length){
|
||
let signatureRows = clipboard.split(/\r\n|\r|\n/g);
|
||
let signatureGroupOptions = config.signatureGroupsNames;
|
||
let invalidSignatures = 0;
|
||
|
||
for(let i = 0; i < signatureRows.length; i++){
|
||
let rowData = signatureRows[i].split(/\t/g);
|
||
if(rowData.length === 6){
|
||
// check if sig Type = anomaly or combat site
|
||
if(validSignatureNames.indexOf( rowData[1] ) !== -1){
|
||
|
||
let sigGroup = $.trim(rowData[2]).toLowerCase();
|
||
let sigDescription = $.trim(rowData[3]);
|
||
let sigGroupId = 0;
|
||
let typeId = 0;
|
||
|
||
// get groupId by groupName
|
||
for(let groupOption of signatureGroupOptions){
|
||
let reg = new RegExp(groupOption.text, 'i');
|
||
if(reg.test(sigGroup)){
|
||
sigGroupId = groupOption.value;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// wormhole type cant be extracted from signature string -> skip function call
|
||
if(sigGroupId !== 5){
|
||
// try to get "typeId" by description string
|
||
typeId = Util.getSignatureTypeIdByName(systemData, sigGroupId, sigDescription);
|
||
|
||
// set signature name as "description" if signature matching failed
|
||
sigDescription = (typeId === 0) ? sigDescription : '';
|
||
}else{
|
||
sigDescription = '';
|
||
}
|
||
|
||
// map array values to signature Object
|
||
let signatureObj = {
|
||
systemId: systemData.id,
|
||
name: $.trim( rowData[0] ).toLowerCase(),
|
||
groupId: sigGroupId,
|
||
typeId: typeId,
|
||
description: sigDescription
|
||
};
|
||
|
||
signatureData.push(signatureObj);
|
||
}else{
|
||
invalidSignatures++;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(invalidSignatures > 0){
|
||
let notification = invalidSignatures + ' / ' + signatureRows.length + ' signatures invalid';
|
||
Util.showNotify({title: 'Invalid signature(s)', text: notification, type: 'warning'});
|
||
}
|
||
}
|
||
|
||
return signatureData;
|
||
};
|
||
|
||
/**
|
||
* updates the signature table with all signatures pasted into the "signature reader" dialog
|
||
* -> Hint: copy&paste signature data (without any open dialog) will add signatures as well
|
||
* @param tableApi
|
||
* @param systemData
|
||
* @param clipboard data stream
|
||
* @param options
|
||
*/
|
||
let updateSignatureTableByClipboard = (tableApi, systemData, clipboard, options) => {
|
||
if(isLockedTable(tableApi, 'clipboard')) return;
|
||
|
||
let saveSignatureData = signatureData => {
|
||
// lock update function until request is finished
|
||
lockTable(tableApi);
|
||
|
||
// lock copy during request (prevent spamming (ctrl + c )
|
||
lockTable(tableApi, 'clipboard');
|
||
|
||
let requestData = {
|
||
signatures: signatureData,
|
||
deleteOld: (options.deleteOld) ? 1 : 0,
|
||
systemId: parseInt(systemData.id)
|
||
};
|
||
|
||
$.ajax({
|
||
type: 'POST',
|
||
url: Init.path.saveSignatureData,
|
||
data: requestData,
|
||
dataType: 'json',
|
||
context: {
|
||
tableApi: tableApi
|
||
}
|
||
}).done(function(responseData){
|
||
// unlock table for update
|
||
unlockTable(this.tableApi);
|
||
// updates table with new/updated signature information
|
||
updateSignatureTable(this.tableApi, responseData.signatures, false);
|
||
}).fail(function(jqXHR, status, error){
|
||
let reason = status + ' ' + error;
|
||
Util.showNotify({title: jqXHR.status + ': Update signatures', text: reason, type: 'warning'});
|
||
$(document).setProgramStatus('problem');
|
||
}).always(function(){
|
||
unlockTable(this.tableApi);
|
||
unlockTable(this.tableApi, 'clipboard');
|
||
});
|
||
};
|
||
|
||
// parse input stream
|
||
let signatureData = parseSignatureString(systemData, clipboard);
|
||
if(signatureData.length > 0){
|
||
// valid signature data parsed
|
||
|
||
// check if signatures will be added to a system where character is currently in
|
||
// if user is not in any system -> id === undefined -> no "confirmation required
|
||
let currentLocationData = Util.getCurrentLocationData();
|
||
if(
|
||
currentLocationData.id &&
|
||
currentLocationData.id !== systemData.id
|
||
){
|
||
|
||
let systemNameStr = (systemData.name === systemData.alias) ? '"' + systemData.name + '"' : '"' + systemData.alias + '" (' + systemData.name + ')';
|
||
systemNameStr = '<span class="txt-color txt-color-warning">' + systemNameStr + '</span>';
|
||
|
||
let msg = 'Update signatures in ' + systemNameStr + ' ? This not your current location, "' + currentLocationData.name + '" !';
|
||
bootbox.confirm(msg, function(result){
|
||
if(result){
|
||
saveSignatureData(signatureData);
|
||
}
|
||
});
|
||
}else{
|
||
// current system selected -> no "confirmation" required
|
||
saveSignatureData(signatureData);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* deletes signature rows from signature table
|
||
* @param tableApi
|
||
* @param rows
|
||
*/
|
||
let deleteSignatures = (tableApi, rows) => {
|
||
// get unique id array from rows -> in case there are 2 rows with same id -> you never know
|
||
let signatureIds = [...new Set(rows.data().toArray().map(rowData => rowData.id))];
|
||
|
||
let requestData = {
|
||
signatureIds: signatureIds
|
||
};
|
||
|
||
$.ajax({
|
||
type: 'POST',
|
||
url: Init.path.deleteSignatureData,
|
||
data: requestData,
|
||
dataType: 'json',
|
||
context: {
|
||
tableApi: tableApi
|
||
}
|
||
}).done(function(responseData){
|
||
// promises for all delete rows
|
||
let promisesToggleRow = [];
|
||
// get deleted rows -> match with response data
|
||
let rows = this.tableApi.rows((idx, rowData, node) => responseData.deletedSignatureIds.includes(rowData.id));
|
||
// toggle hide animation for rows one by one...
|
||
rows.every(function(rowIdx, tableLoop, rowLoop){
|
||
let row = this;
|
||
let rowElement = row.nodes().to$();
|
||
|
||
rowElement.pulseBackgroundColor('deleted');
|
||
|
||
promisesToggleRow.push(toggleTableRow(rowElement));
|
||
});
|
||
|
||
// ... all hide animations done ...
|
||
Promise.all(promisesToggleRow).then(payloads => {
|
||
// ... get deleted (hide animation done) and delete them
|
||
this.tableApi.rows(payloads.map(payload => payload.row)).remove().draw();
|
||
|
||
// update signature bar
|
||
updateScannedSignaturesBar(this.tableApi, {showNotice: false});
|
||
|
||
// update connection conflicts
|
||
checkConnectionConflicts();
|
||
|
||
let notificationOptions = {
|
||
type: 'success'
|
||
};
|
||
if(payloads.length === 1){
|
||
notificationOptions.title = 'Signature deleted';
|
||
}else{
|
||
notificationOptions.title = payloads.length + ' Signatures deleted ';
|
||
}
|
||
Util.showNotify(notificationOptions);
|
||
});
|
||
}).fail(function(jqXHR, status, error){
|
||
let reason = status + ' ' + error;
|
||
Util.showNotify({title: jqXHR.status + ': Delete signature', text: reason, type: 'warning'});
|
||
$(document).setProgramStatus('problem');
|
||
});
|
||
};
|
||
|
||
/**
|
||
* updates a single cell with new data (e.g. "updated" cell)
|
||
* @param tableApi
|
||
* @param rowIndex
|
||
* @param columnSelector
|
||
* @param data
|
||
*/
|
||
let updateSignatureCell = (tableApi, rowIndex, columnSelector, data) => {
|
||
tableApi.cell(rowIndex, columnSelector).data(data);
|
||
};
|
||
|
||
/**
|
||
* check connectionIds for conflicts (multiple signatures -> same connection)
|
||
* -> show "conflict" icon next to select
|
||
*/
|
||
let checkConnectionConflicts = () => {
|
||
setTimeout(() => {
|
||
let connectionSelects = $('.' + config.tableCellConnectionClass + '.editable');
|
||
let connectionIds = [];
|
||
let duplicateConnectionIds = [];
|
||
let groupedSelects = [];
|
||
|
||
connectionSelects.each(function(){
|
||
let select = $(this);
|
||
let value = parseInt(select.editable('getValue', true) )|| 0;
|
||
|
||
if(
|
||
connectionIds.indexOf(value) > -1 &&
|
||
duplicateConnectionIds.indexOf(value) === -1
|
||
){
|
||
// duplicate found
|
||
duplicateConnectionIds.push(value);
|
||
}
|
||
|
||
if(groupedSelects[value] !== undefined){
|
||
groupedSelects[value].push(select[0]);
|
||
}else{
|
||
groupedSelects[value] = [select[0]];
|
||
}
|
||
|
||
connectionIds.push(value);
|
||
});
|
||
|
||
// update "conflict" icon next to select label for connectionIds
|
||
connectionSelects.each(function(){
|
||
let select = $(this);
|
||
let value = parseInt(select.editable('getValue', true) )|| 0;
|
||
let conflictIcon = select.find('.fa-exclamation-triangle');
|
||
if(
|
||
duplicateConnectionIds.indexOf(value) > -1 &&
|
||
groupedSelects[value].indexOf(select[0]) > -1
|
||
){
|
||
conflictIcon.removeClass('hide');
|
||
}else{
|
||
conflictIcon.addClass('hide');
|
||
}
|
||
});
|
||
}, 200);
|
||
};
|
||
|
||
/**
|
||
* get group label by groupId
|
||
* @param groupId
|
||
* @returns {string}
|
||
*/
|
||
let getGroupLabelById = (groupId) => {
|
||
let options = config.signatureGroupsLabels.filter(option => option.value === groupId);
|
||
return options.length ? options[0].text : '';
|
||
};
|
||
|
||
/**
|
||
* helper function - get cell by columnSelector from same row as cell
|
||
* @param tableApi
|
||
* @param cell
|
||
* @param columnSelector
|
||
* @returns {*}
|
||
*/
|
||
let getNeighboringCell = (tableApi, cell, columnSelector) => {
|
||
return tableApi.cell(tableApi.row(cell).index(), columnSelector);
|
||
};
|
||
|
||
/**
|
||
* get next cell by columnSelector
|
||
* @param tableApi
|
||
* @param cell
|
||
* @param columnSelectors
|
||
* @returns {*}
|
||
*/
|
||
let searchNextCell = (tableApi, cell, columnSelectors) => {
|
||
if(columnSelectors.length){
|
||
// copy selectors -> .shift() modifies the orig array, important!
|
||
columnSelectors = columnSelectors.slice(0);
|
||
let nextCell = getNeighboringCell(tableApi, cell, columnSelectors.shift());
|
||
let nextCellElement = nextCell.nodes().to$();
|
||
if( nextCellElement.data('editable') ){
|
||
// cell is xEditable field -> skip "disabled" OR check value
|
||
let nextCellValue = nextCellElement.editable('getValue', true);
|
||
if(
|
||
[0, null].includes(nextCellValue) &&
|
||
!nextCellElement.data('editable').options.disabled
|
||
){
|
||
// xEditable value is empty
|
||
return nextCell;
|
||
}else{
|
||
// search next cell
|
||
return searchNextCell(tableApi, cell, columnSelectors);
|
||
}
|
||
}else if( nextCell.index().column === tableApi.column(-1).index() ){
|
||
// NO xEditable cell BUT last column (=> action cell) -> OK
|
||
return nextCell;
|
||
}else{
|
||
console.error('No cell found for activation!');
|
||
}
|
||
}else{
|
||
// return origin cell
|
||
return tableApi.cell(cell);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* make cell active -> focus() + show xEditable
|
||
* @param cell
|
||
*/
|
||
let activateCell = (cell) => {
|
||
let cellElement = cell.nodes().to$();
|
||
// NO xEditable
|
||
cellElement.focus();
|
||
|
||
if( cellElement.data('editable') ){
|
||
// cell is xEditable field -> show xEditable form
|
||
cellElement.editable('show');
|
||
}
|
||
};
|
||
|
||
/**
|
||
* search neighboring cell (same row) and set "active" -> show editable
|
||
* @param tableApi
|
||
* @param cell
|
||
* @param columnSelectors
|
||
*/
|
||
let activateNextCell = (tableApi, cell, columnSelectors) => {
|
||
let nextCell = searchNextCell(tableApi, cell, columnSelectors);
|
||
activateCell(nextCell);
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'save' observer for xEditable cell
|
||
* -> show "neighboring" xEditable field
|
||
* @param tableApi
|
||
* @param cell
|
||
* @param columnSelectorsAjax - used for Ajax save (edit signature)
|
||
* @param columnSelectorsDry - used for dry save (new signature)
|
||
*/
|
||
let editableOnSave = (tableApi, cell, columnSelectorsAjax = [], columnSelectorsDry = []) => {
|
||
$(cell).on('save', function(e, params){
|
||
if(params.response){
|
||
// send by Ajax
|
||
activateNextCell(tableApi, cell, columnSelectorsAjax);
|
||
}else{
|
||
// dry save - no request
|
||
activateNextCell(tableApi, cell, columnSelectorsDry);
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'hidden' observer for xEditable cell
|
||
* -> set focus() on xEditable field
|
||
* @param tableApi
|
||
* @param cell
|
||
*/
|
||
let editableOnHidden = (tableApi, cell) => {
|
||
$(cell).on('hidden', function(e, reason){
|
||
// re-focus element on close (keyboard navigation)
|
||
// 'save' event handles default focus (e.g. open new xEditable)
|
||
// 'hide' handles all the rest (experimental)
|
||
if(reason !== 'save'){
|
||
this.focus();
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'shown' observer for xEditable type cell
|
||
* -> enable Select2 for xEditable form
|
||
* @param cell
|
||
*/
|
||
let editableGroupOnShown = cell => {
|
||
$(cell).on('shown', function(e, editable){
|
||
let inputField = editable.input.$input;
|
||
inputField.addClass('pf-select2').initSignatureGroupSelect();
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'save' observer for xEditable group cell
|
||
* -> update scanned signature bar
|
||
* @param tableApi
|
||
* @param cell
|
||
*/
|
||
let editableGroupOnSave = (tableApi, cell) => {
|
||
$(cell).on('save', function(e, params){
|
||
if(params.response){
|
||
// send by Ajax
|
||
updateScannedSignaturesBar(tableApi, {showNotice: true});
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'init' observer for xEditable type cell
|
||
* -> disable xEditable field if no options available
|
||
* @param cell
|
||
*/
|
||
let editableTypeOnInit = cell => {
|
||
$(cell).on('init', function(e, editable){
|
||
if(!editable.options.source().length){
|
||
editableDisable($(this));
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'shown' observer for xEditable type cell
|
||
* -> enable Select2 for xEditable form
|
||
* @param cell
|
||
*/
|
||
let editableTypeOnShown = cell => {
|
||
$(cell).on('shown', function(e, editable){
|
||
// destroy possible open popovers (e.g. wormhole types)
|
||
$(this).destroyPopover(true);
|
||
|
||
let inputField = editable.input.$input;
|
||
let hasOptGroups = inputField.has('optgroup').length > 0;
|
||
inputField.addClass('pf-select2').initSignatureTypeSelect({}, hasOptGroups);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'shown' observer for xEditable description cell
|
||
* -> change height for "new signature" table wrapper
|
||
* @param cell
|
||
*/
|
||
let editableDescriptionOnShown = cell => {
|
||
$(cell).on('shown', function(e, editable){
|
||
$(this).parents('.' + config.tableToolsActionClass).css('height', '+=35px');
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'hidden' observer for xEditable description cell
|
||
* -> change height for "new signature" table wrapper
|
||
* @param cell
|
||
*/
|
||
let editableDescriptionOnHidden = cell => {
|
||
$(cell).on('hidden', function(e, editable){
|
||
$(this).parents('.' + config.tableToolsActionClass).css('height', '-=35px');
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'init' observer for xEditable connection cell
|
||
* -> set focus() on xEditable field
|
||
* @param cell
|
||
*/
|
||
let editableConnectionOnInit = cell => {
|
||
$(cell).on('init', function(e, editable){
|
||
if(editable.value > 0){
|
||
// empty connection selects ON INIT don´t make a difference for conflicts
|
||
checkConnectionConflicts();
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'shown' observer for xEditable connection cell
|
||
* -> enable Select2 for xEditable form
|
||
* @param cell
|
||
*/
|
||
let editableConnectionOnShown = cell => {
|
||
$(cell).on('shown', function(e, editable){
|
||
let inputField = editable.input.$input;
|
||
|
||
// Select2 init would work without passing select options as "data", Select2 would grap data from DOM
|
||
// -> We want to pass "meta" data for each option into Select2 for formatting
|
||
let options = {
|
||
data: Util.convertXEditableOptionsToSelect2(editable)
|
||
};
|
||
|
||
inputField.addClass('pf-select2').initSignatureConnectionSelect(options);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* helper function - set 'save' observer for xEditable connection cell
|
||
* -> check connection conflicts
|
||
* @param cell
|
||
*/
|
||
let editableConnectionOnSave = cell => {
|
||
$(cell).on('save', function(e, params){
|
||
checkConnectionConflicts();
|
||
});
|
||
};
|
||
|
||
/**
|
||
* enable xEditable element
|
||
* @param element
|
||
*/
|
||
let editableEnable = element => {
|
||
element.editable('enable');
|
||
// (re)-enable focus on element by tabbing, xEditable removes "tabindex" on 'disable'
|
||
element.attr('tabindex', 0);
|
||
};
|
||
|
||
/**
|
||
* disable xEditable element
|
||
* @param element
|
||
*/
|
||
let editableDisable = element => {
|
||
element.editable('disable');
|
||
// xEditable sets 'tabindex = -1'
|
||
};
|
||
|
||
/**
|
||
* en/disables xEditable element (select)
|
||
* -> disables if there are no source options found
|
||
* @param element
|
||
*/
|
||
let editableSelectCheck = element => {
|
||
if(element.data('editable')){
|
||
let options = element.data('editable').options.source();
|
||
if(options.length > 0){
|
||
editableEnable(element);
|
||
}else{
|
||
editableDisable(element);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* get dataTables default options for signature tables
|
||
* @param mapId
|
||
* @param systemData
|
||
* @returns {{}}
|
||
*/
|
||
let getSignatureDataTableDefaults = (mapId, systemData) => {
|
||
|
||
/**
|
||
* add map/system specific data for each editable field in the sig-table
|
||
* @param params
|
||
* @returns {*}
|
||
*/
|
||
let modifyFieldParamsOnSend = params => {
|
||
params.systemId = systemData.id;
|
||
return params;
|
||
};
|
||
|
||
let dataTableDefaults = {
|
||
pfMeta: {
|
||
'mapId': mapId,
|
||
'systemId': systemData.id
|
||
},
|
||
order: [1, 'asc'],
|
||
rowId: rowData => config.sigTableRowIdPrefix + rowData.id,
|
||
language: {
|
||
emptyTable: 'No signatures added',
|
||
info: 'Showing _START_ to _END_ of _TOTAL_ signatures',
|
||
infoEmpty: 'Showing 0 to 0 of 0 signatures',
|
||
infoFiltered: '(<i class="fas fa-fw fa-filter"></i> from _MAX_ total)',
|
||
lengthMenu: 'Show _MENU_',
|
||
zeroRecords: 'No signatures recorded'
|
||
},
|
||
columnDefs: [
|
||
{
|
||
targets: 0,
|
||
name: 'status',
|
||
orderable: true,
|
||
searchable: false,
|
||
title: '',
|
||
width: 2,
|
||
class: ['text-center'].join(' '),
|
||
data: 'updated',
|
||
type: 'html',
|
||
render: {
|
||
_: (cellData, type, rowData, meta) => {
|
||
let value = '';
|
||
if(cellData && cellData.character){
|
||
value = Util.getStatusInfoForCharacter(cellData.character, 'class');
|
||
}
|
||
|
||
if(type === 'display'){
|
||
value = '<i class="fas fa-fw fa-circle pf-user-status ' + value + '"></i>';
|
||
}
|
||
return value;
|
||
}
|
||
}
|
||
},{
|
||
targets: 1,
|
||
name: 'id',
|
||
orderable: true,
|
||
searchable: true,
|
||
title: 'id',
|
||
type: 'string',
|
||
width: 12,
|
||
class: [config.tableCellFocusClass, config.sigTableEditSigNameInput].join(' '),
|
||
data: 'name',
|
||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||
let tableApi = this.api();
|
||
|
||
updateTooltip(cell, cellData);
|
||
|
||
editableOnSave(tableApi, cell, [], ['group:name', 'type:name', 'action:name']);
|
||
editableOnHidden(tableApi, cell);
|
||
|
||
$(cell).editable($.extend({
|
||
mode: 'popup',
|
||
type: 'text',
|
||
title: 'signature id',
|
||
name: 'name',
|
||
pk: rowData.id || null,
|
||
emptytext: '? ? ?',
|
||
value: cellData,
|
||
inputclass: config.editableNameInputClass,
|
||
display: function(value){
|
||
// change display value to first 3 letters
|
||
$(this).text($.trim( value.substr(0, 3) ).toLowerCase());
|
||
},
|
||
validate: function(value){
|
||
let msg = false;
|
||
if($.trim(value).length < 3){
|
||
msg = 'Id is less than min of "3"';
|
||
}else if($.trim(value).length > 10){
|
||
msg = 'Id is more than max of "10"';
|
||
}
|
||
|
||
if(msg){
|
||
return {newValue: value, msg: msg, field: this};
|
||
}
|
||
},
|
||
params: modifyFieldParamsOnSend,
|
||
success: function(response, newValue){
|
||
tableApi.cell(cell).data(newValue);
|
||
|
||
$(this).pulseBackgroundColor('changed');
|
||
updateTooltip(cell, newValue);
|
||
|
||
if(response){
|
||
let newRowData = response.signatures[0];
|
||
updateSignatureCell(tableApi, rowIndex, 'status:name', newRowData.updated);
|
||
updateSignatureCell(tableApi, rowIndex, 'updated:name', newRowData.updated.updated);
|
||
}
|
||
tableApi.draw();
|
||
}
|
||
}, editableDefaults));
|
||
}
|
||
},{
|
||
targets: 2,
|
||
name: 'group',
|
||
orderable: true,
|
||
searchable: true,
|
||
title: 'group',
|
||
type: 'string', // required for sort/filter because initial data type is numeric
|
||
width: 40,
|
||
class: [config.tableCellFocusClass].join(' '),
|
||
data: 'groupId',
|
||
render: {
|
||
sort: getGroupLabelById,
|
||
filter: getGroupLabelById
|
||
},
|
||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||
let tableApi = this.api();
|
||
|
||
editableOnSave(tableApi, cell, ['type:name'], ['type:name', 'action:name']);
|
||
editableOnHidden(tableApi, cell);
|
||
editableGroupOnShown(cell);
|
||
editableGroupOnSave(tableApi, cell);
|
||
|
||
$(cell).editable($.extend({
|
||
mode: 'popup',
|
||
type: 'select',
|
||
title: 'group',
|
||
name: 'groupId',
|
||
pk: rowData.id || null,
|
||
emptytext: 'unknown',
|
||
onblur: 'submit',
|
||
showbuttons: false,
|
||
value: cellData,
|
||
prepend: [{value: 0, text: ''}],
|
||
params: modifyFieldParamsOnSend,
|
||
source: config.signatureGroupsLabels,
|
||
display: function(value, sourceData){
|
||
let selected = $.fn.editableutils.itemsByValue(value, sourceData);
|
||
if(selected.length && selected[0].value > 0){
|
||
$(this).html(selected[0].text);
|
||
}else{
|
||
$(this).empty();
|
||
}
|
||
},
|
||
validate: function(value){
|
||
// convert string to int -> important for further processing
|
||
// -> on submit record (new signature) validate() is called and no error should be returned
|
||
// value should already be integer
|
||
if( !Number.isInteger(value) ){
|
||
return {newValue: parseInt(value) || 0, msg: null};
|
||
}
|
||
},
|
||
success: function(response, newValue){
|
||
tableApi.cell(cell).data(newValue);
|
||
|
||
$(this).pulseBackgroundColor('changed');
|
||
|
||
if(response){
|
||
let newRowData = response.signatures[0];
|
||
updateSignatureCell(tableApi, rowIndex, 'status:name', newRowData.updated);
|
||
updateSignatureCell(tableApi, rowIndex, 'updated:name', newRowData.updated.updated);
|
||
}
|
||
tableApi.draw();
|
||
|
||
// find related "type" select (same row) and change options ---------------------------
|
||
let signatureTypeCell = getNeighboringCell(tableApi, cell, 'type:name');
|
||
let signatureTypeField = signatureTypeCell.nodes().to$();
|
||
editableSelectCheck(signatureTypeField);
|
||
|
||
signatureTypeCell.data(0);
|
||
signatureTypeField.editable('setValue', 0);
|
||
|
||
|
||
// find "connection" select (same row) and change "enabled" flag ----------------------
|
||
let signatureConnectionCell = getNeighboringCell(tableApi, cell, 'connection:name');
|
||
let signatureConnectionField = signatureConnectionCell.nodes().to$();
|
||
|
||
if(newValue === 5){
|
||
// wormhole
|
||
editableEnable(signatureConnectionField);
|
||
}else{
|
||
checkConnectionConflicts();
|
||
editableDisable(signatureConnectionField);
|
||
}
|
||
signatureConnectionCell.data(0);
|
||
signatureConnectionField.editable('setValue', 0);
|
||
}
|
||
}, editableDefaults));
|
||
}
|
||
},{
|
||
targets: 3,
|
||
name: 'type',
|
||
orderable: false,
|
||
searchable: false,
|
||
title: 'type',
|
||
type: 'string', // required for sort/filter because initial data type is numeric
|
||
width: 180,
|
||
class: [config.tableCellFocusClass].join(' '),
|
||
data: 'typeId',
|
||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||
let tableApi = this.api();
|
||
|
||
editableOnSave(tableApi, cell, ['connection:name'], ['action:name']);
|
||
editableOnHidden(tableApi, cell);
|
||
editableTypeOnInit(cell);
|
||
editableTypeOnShown(cell);
|
||
|
||
$(cell).editable($.extend({
|
||
mode: 'popup',
|
||
type: 'select',
|
||
title: 'type',
|
||
name: 'typeId',
|
||
pk: rowData.id || null,
|
||
emptytext: 'unknown',
|
||
onblur: 'submit',
|
||
showbuttons: false,
|
||
disabled: rowData.groupId <= 0, // initial disabled if groupId not set
|
||
value: cellData,
|
||
prepend: [{value: 0, text: ''}],
|
||
params: modifyFieldParamsOnSend,
|
||
source: function(){
|
||
// get current row data (important!)
|
||
// -> "rowData" param is not current state, values are "on createCell()" state
|
||
let rowData = tableApi.row($(cell).parents('tr')).data();
|
||
|
||
let typeOptions = getAllSignatureNames(
|
||
systemData,
|
||
systemData.type.id,
|
||
Util.getAreaIdBySecurity(systemData.security),
|
||
rowData.groupId
|
||
);
|
||
return typeOptions;
|
||
},
|
||
display: function(value, sourceData){
|
||
let selected = $.fn.editableutils.itemsByValue(value, sourceData);
|
||
if(selected.length && selected[0].value > 0){
|
||
$(this).html(FormElement.formatSignatureTypeSelectionData({text: selected[0].text}));
|
||
}else{
|
||
$(this).empty();
|
||
}
|
||
},
|
||
validate: function(value){
|
||
// convert string to int -> important for further processing
|
||
// -> on submit record (new signature) validate() is called and no error should be returned
|
||
// value should already be integer
|
||
if( !Number.isInteger(value) ){
|
||
return {newValue: parseInt(value) || 0, msg: null};
|
||
}
|
||
},
|
||
success: function(response, newValue){
|
||
tableApi.cell(cell).data(newValue);
|
||
|
||
$(this).pulseBackgroundColor('changed');
|
||
|
||
if(response){
|
||
let newRowData = response.signatures[0];
|
||
updateSignatureCell(tableApi, rowIndex, 'status:name', newRowData.updated);
|
||
updateSignatureCell(tableApi, rowIndex, 'updated:name', newRowData.updated.updated);
|
||
}
|
||
tableApi.draw();
|
||
}
|
||
}, editableDefaults));
|
||
}
|
||
},{
|
||
targets: 4,
|
||
name: 'description',
|
||
orderable: false,
|
||
searchable: false,
|
||
title: 'description',
|
||
class: [config.tableCellFocusClass, config.tableCellActionClass].join(' '),
|
||
type: 'html',
|
||
data: 'description',
|
||
defaultContent: '',
|
||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||
let tableApi = this.api();
|
||
|
||
editableOnSave(tableApi, cell, [], ['action:name']);
|
||
editableOnHidden(tableApi, cell);
|
||
editableDescriptionOnShown(cell);
|
||
editableDescriptionOnHidden(cell);
|
||
|
||
$(cell).editable($.extend({
|
||
mode: 'inline',
|
||
type: 'textarea',
|
||
title: 'description',
|
||
name: 'description',
|
||
pk: rowData.id || null,
|
||
emptytext: '<i class="fas fa-fw fa-lg fa-pen"></i>',
|
||
onblur: 'submit',
|
||
showbuttons: false,
|
||
inputclass: config.editableDescriptionInputClass,
|
||
emptyclass: config.moduleHeadlineIconClass,
|
||
params: modifyFieldParamsOnSend,
|
||
success: function(response, newValue){
|
||
tableApi.cell(cell).data(newValue);
|
||
|
||
$(this).pulseBackgroundColor('changed');
|
||
|
||
if(response){
|
||
let newRowData = response.signatures[0];
|
||
updateSignatureCell(tableApi, rowIndex, 'status:name', newRowData.updated);
|
||
updateSignatureCell(tableApi, rowIndex, 'updated:name', newRowData.updated.updated);
|
||
}
|
||
tableApi.draw();
|
||
}
|
||
}, editableDefaults));
|
||
}
|
||
},{
|
||
targets: 5,
|
||
name: 'connection',
|
||
orderable: false,
|
||
searchable: false,
|
||
title: 'leads to',
|
||
type: 'string', // required for sort/filter because initial data type is numeric
|
||
className: [config.tableCellFocusClass, config.tableCellConnectionClass].join(' '),
|
||
width: 80,
|
||
data: 'connection.id',
|
||
defaultContent: 0,
|
||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||
let tableApi = this.api();
|
||
|
||
editableOnSave(tableApi, cell, [], ['action:name']);
|
||
editableOnHidden(tableApi, cell);
|
||
editableConnectionOnInit(cell);
|
||
editableConnectionOnShown(cell);
|
||
editableConnectionOnSave(cell);
|
||
|
||
$(cell).editable($.extend({
|
||
mode: 'popup',
|
||
type: 'select',
|
||
title: 'system',
|
||
name: 'connectionId',
|
||
pk: rowData.id || null,
|
||
emptytext: 'unknown',
|
||
onblur: 'submit',
|
||
showbuttons: false,
|
||
disabled: rowData.groupId !== 5, // initial disabled if NON wh
|
||
value: cellData,
|
||
prepend: [{value: 0, text: ''}],
|
||
params: modifyFieldParamsOnSend,
|
||
source: function(){
|
||
let activeMap = Util.getMapModule().getActiveMap();
|
||
let mapId = activeMap.data('id');
|
||
let connectionOptions = getSignatureConnectionOptions(mapId, systemData);
|
||
return connectionOptions;
|
||
},
|
||
display: function(value, sourceData){
|
||
let selected = $.fn.editableutils.itemsByValue(value, sourceData);
|
||
if(selected.length && selected[0].value > 0){
|
||
let errorIcon = '<i class="fas fa-exclamation-triangle txt-color txt-color-danger hide"></i> ';
|
||
$(this).html(FormElement.formatSignatureConnectionSelectionData({
|
||
text: selected[0].text,
|
||
metaData: selected[0].metaData
|
||
})).prepend(errorIcon);
|
||
}else{
|
||
$(this).empty();
|
||
}
|
||
},
|
||
validate: function(value, b, c){
|
||
// convert string to int -> important for further processing
|
||
// -> on submit record (new signature) validate() is called and no error should be returned
|
||
// value should already be integer
|
||
if( !Number.isInteger(value) ){
|
||
return {newValue: parseInt(value) || 0, msg: null};
|
||
}
|
||
},
|
||
success: function(response, newValue){
|
||
tableApi.cell(cell).data(newValue);
|
||
|
||
$(this).pulseBackgroundColor('changed');
|
||
|
||
if(response){
|
||
let newRowData = response.signatures[0];
|
||
updateSignatureCell(tableApi, rowIndex, 'status:name', newRowData.updated);
|
||
updateSignatureCell(tableApi, rowIndex, 'updated:name', newRowData.updated.updated);
|
||
}
|
||
tableApi.draw();
|
||
}
|
||
}, editableDefaults));
|
||
}
|
||
},{
|
||
targets: 6,
|
||
name: 'created',
|
||
title: 'created',
|
||
searchable: false,
|
||
width: 80,
|
||
className: ['text-right', config.tableCellCounterClass, 'min-screen-d'].join(' '),
|
||
data: 'created.created',
|
||
defaultContent: ''
|
||
},{
|
||
targets: 7,
|
||
name: 'updated',
|
||
title: 'updated',
|
||
searchable: false,
|
||
width: 80,
|
||
className: ['text-right', config.tableCellCounterClass, 'min-screen-d'].join(' '),
|
||
data: 'updated.updated',
|
||
defaultContent: '',
|
||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||
// highlight cell
|
||
let diff = Math.floor((new Date()).getTime()) - cellData * 1000;
|
||
|
||
// age > 1 day
|
||
if( diff > 86400000){
|
||
$(cell).addClass('txt-color txt-color-warning');
|
||
}
|
||
}
|
||
},{
|
||
targets: 8,
|
||
name: 'info',
|
||
title: '',
|
||
orderable: false,
|
||
searchable: false,
|
||
width: 10,
|
||
class: ['text-center', Util.config.helpClass , Util.config.popoverTriggerClass].join(' '),
|
||
data: 'created.created',
|
||
defaultContent: '',
|
||
render: {
|
||
display: (cellData, type, rowData, meta) => {
|
||
if(cellData){
|
||
return '<i class="fas fa-question-circle"></i>';
|
||
}
|
||
}
|
||
}
|
||
},{
|
||
targets: 9,
|
||
name: 'action',
|
||
title: '',
|
||
orderable: false,
|
||
searchable: false,
|
||
width: 10,
|
||
class: ['text-center', config.tableCellFocusClass, config.tableCellActionClass].join(' '),
|
||
data: null,
|
||
render: {
|
||
display: (cellData, type, rowData, meta) => {
|
||
let val = '<i class="fas fa-plus"></i>';
|
||
if(rowData.id){
|
||
val = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
|
||
}
|
||
return val;
|
||
}
|
||
},
|
||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||
let tableApi = this.api();
|
||
|
||
if(rowData.id){
|
||
// delete signature -----------------------------------------------------------------------
|
||
let confirmationSettings = {
|
||
container: 'body',
|
||
placement: 'left',
|
||
btnCancelClass: 'btn btn-sm btn-default',
|
||
btnCancelLabel: 'cancel',
|
||
btnCancelIcon: 'fas fa-fw fa-ban',
|
||
title: 'delete signature',
|
||
btnOkClass: 'btn btn-sm btn-danger',
|
||
btnOkLabel: 'delete',
|
||
btnOkIcon: 'fas fa-fw fa-times',
|
||
onConfirm: function(e, target){
|
||
// top scroll to top
|
||
e.preventDefault();
|
||
|
||
let deleteRowElement = $(target).parents('tr');
|
||
let row = tableApi.rows(deleteRowElement);
|
||
deleteSignatures(tableApi, row);
|
||
}
|
||
};
|
||
|
||
$(cell).confirmation(confirmationSettings);
|
||
}else{
|
||
// add new signature ----------------------------------------------------------------------
|
||
$(cell).on('click', {tableApi: tableApi, rowIndex: rowIndex}, function(e){
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
|
||
let secondaryTableApi = e.data.tableApi;
|
||
let metaData = getTableMetaData(secondaryTableApi);
|
||
let primaryTableApi = getDataTableInstance(metaData.mapId, metaData.systemId, 'primary');
|
||
|
||
let formFields = secondaryTableApi.row(e.data.rowIndex).nodes().to$().find('.editable');
|
||
|
||
// the "hide" makes sure to take care about open editable fields (e.g. description)
|
||
// otherwise, changes would not be submitted in this field (not necessary)
|
||
formFields.editable('hide');
|
||
|
||
// submit all xEditable fields
|
||
formFields.editable('submit', {
|
||
url: Init.path.saveSignatureData,
|
||
ajaxOptions: {
|
||
dataType: 'json', //assuming json response
|
||
beforeSend: function(xhr, settings){
|
||
lockTable(primaryTableApi);
|
||
},
|
||
context: {
|
||
primaryTableApi: primaryTableApi,
|
||
secondaryTableApi: secondaryTableApi,
|
||
}
|
||
},
|
||
data: {
|
||
systemId: metaData.systemId, // additional data to submit
|
||
pk: 0 // new data no primary key
|
||
},
|
||
error: editableDefaults.error, // user default xEditable error function
|
||
success: function(data, editableConfig){
|
||
let context = editableConfig.ajaxOptions.context;
|
||
let primaryTableApi = context.primaryTableApi;
|
||
let secondaryTableApi = context.secondaryTableApi;
|
||
|
||
unlockTable(primaryTableApi);
|
||
|
||
let signatureData = data.signatures[0];
|
||
let row = addSignatureRow(primaryTableApi, signatureData);
|
||
if(row){
|
||
primaryTableApi.draw();
|
||
// highlight
|
||
row.nodes().to$().pulseBackgroundColor('added');
|
||
|
||
// prepare "add signature" table for new entry -> reset -------------------
|
||
secondaryTableApi.clear().row.add($.extend(true, {}, emptySignatureData)).draw();
|
||
|
||
Util.showNotify({
|
||
title: 'Signature added',
|
||
text: 'Name: ' + signatureData.name,
|
||
type: 'success'
|
||
});
|
||
|
||
// update signature bar
|
||
updateScannedSignaturesBar(primaryTableApi, {showNotice: true});
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
}
|
||
],
|
||
createdRow: function(row, data, dataIndex){
|
||
// enable tabbing for interactive cells
|
||
let focusCells = $(row).find('.' + config.tableCellFocusClass + ':not(.editable-disabled)').attr('tabindex', 0);
|
||
// enable "return" key -> click()
|
||
focusCells.on('keydown', function(e){
|
||
e.stopPropagation();
|
||
if(e.which === 13){
|
||
$(this).trigger('click');
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
return dataTableDefaults;
|
||
};
|
||
|
||
/**
|
||
* key (arrow) navigation inside a table -> set cell focus()
|
||
* @param tableApi
|
||
* @param e
|
||
*/
|
||
let keyNavigation = (tableApi, e) => {
|
||
let offset = [0, 0];
|
||
if(e.keyCode === 37){
|
||
offset = [-1, 0];
|
||
}else if(e.keyCode === 38){
|
||
offset = [0, -1];
|
||
}else if(e.keyCode === 39){
|
||
offset = [1, 0];
|
||
}else if(e.keyCode === 40){
|
||
offset = [0, 1];
|
||
}
|
||
|
||
if(offset !== [0, 0]){
|
||
/**
|
||
* check if cellIndex is out of table range
|
||
* @param tableApi
|
||
* @param cellIndex
|
||
* @returns {*}
|
||
*/
|
||
let checkIndex = (tableApi, cellIndex) => {
|
||
if(cellIndex[0] < 0){
|
||
cellIndex[0] = tableApi.column(':last').nodes().to$().index(); // last column
|
||
}
|
||
if(cellIndex[0] > tableApi.column(':last').nodes().to$().index()){
|
||
cellIndex[0] = 0; // first column
|
||
}
|
||
if(cellIndex[1] < 0){
|
||
cellIndex[1] = tableApi.row(':last', {search: 'applied'}).nodes().to$().index(); // last row
|
||
}
|
||
if(cellIndex[1] > tableApi.row(':last', {search: 'applied'}).nodes().to$().index()){
|
||
cellIndex[1] = 0; // first row
|
||
}
|
||
return cellIndex;
|
||
};
|
||
|
||
/**
|
||
* recursive search next cell
|
||
* @param tableApi
|
||
* @param cellOrigin
|
||
* @param offset
|
||
* @returns {*}
|
||
*/
|
||
let searchCell = (tableApi, cellOrigin, offset) => {
|
||
// we need to get the current cell indexes from DOM (not internal DataTables indexes)
|
||
let nodeOrig = cellOrigin.nodes();
|
||
let colIndex = nodeOrig.to$().index();
|
||
let rowIndex = nodeOrig.to$().closest('tr').index();
|
||
let currentCellIndex = [colIndex, rowIndex];
|
||
let newCellIndex = currentCellIndex.map((index, i) => index + offset[i]);
|
||
// check if cell index is inside table dimensions
|
||
newCellIndex = checkIndex(tableApi, newCellIndex);
|
||
|
||
let cell = tableApi.cell(':eq(' + newCellIndex[1] + ')', ':eq(' + newCellIndex[0] + ')', {search: 'applied'});
|
||
let node = cell.node();
|
||
|
||
if(
|
||
!node.hasAttribute('tabindex') ||
|
||
parseInt(node.getAttribute('tabindex')) < 0
|
||
){
|
||
// cell can not be focused -> search next
|
||
cell = searchCell(tableApi, cell, offset);
|
||
}
|
||
return cell;
|
||
};
|
||
|
||
let cell = searchCell(tableApi, tableApi.cell(e.target), offset);
|
||
cell.node().focus();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* draw signature table toolbar (add signature button, scan progress bar
|
||
* @param moduleElement
|
||
* @param mapId
|
||
* @param systemData
|
||
*/
|
||
let drawSignatureTableNew = (moduleElement, mapId, systemData) => {
|
||
let secondaryTableContainer = $('<div>', {
|
||
class: config.tableToolsActionClass
|
||
});
|
||
|
||
// create "empty table for new signature
|
||
let table = $('<table>', {
|
||
id: getTableId(mapId, systemData.id, 'secondary'),
|
||
class: ['stripe', 'row-border', 'compact', 'nowrap', config.sigTableClass, config.sigTableSecondaryClass].join(' ')
|
||
});
|
||
|
||
secondaryTableContainer.append(table);
|
||
|
||
moduleElement.find('.' + config.moduleHeadClass).after(secondaryTableContainer);
|
||
|
||
let dataTableOptions = {
|
||
paging: false,
|
||
info: false,
|
||
searching: false,
|
||
tabIndex: -1,
|
||
data: [$.extend(true, {}, emptySignatureData)],
|
||
initComplete: function(settings, json){
|
||
let tableApi = this.api();
|
||
|
||
$(this).on('keyup', 'td', {tableApi: tableApi}, function(e){
|
||
keyNavigation(tableApi, e);
|
||
});
|
||
}
|
||
};
|
||
|
||
$.extend(true, dataTableOptions, getSignatureDataTableDefaults(mapId, systemData));
|
||
|
||
let tableApi = table.DataTable(dataTableOptions);
|
||
|
||
// "Responsive" dataTables plugin did not load automatic (because table is invisible onInit)
|
||
// -> manually start "Responsive" extension -> see default dataTable setting for config e.g. breakpoints
|
||
new $.fn.dataTable.Responsive(tableApi);
|
||
};
|
||
|
||
/**
|
||
* filter table "group" column
|
||
* @param tableApi
|
||
* @param newValue
|
||
* @param sourceOptions
|
||
*/
|
||
let searchGroupColumn = (tableApi, newValue, sourceOptions) => {
|
||
let column = tableApi.column('group:name');
|
||
let pattern = '';
|
||
|
||
if(newValue.length <= sourceOptions.length){
|
||
// all options selected + "prepend" option
|
||
let selected = $.fn.editableutils.itemsByValue(newValue, sourceOptions);
|
||
|
||
pattern = selected.map(option => option.value !== 0 ? $.fn.dataTable.util.escapeRegex(option.text) : '^$').join('|');
|
||
}
|
||
column.search(pattern, true, false).draw();
|
||
};
|
||
|
||
/**
|
||
* init table filter button "group" column
|
||
* @param tableApi
|
||
*/
|
||
let initGroupFilterButton = tableApi => {
|
||
let characterId = Util.getCurrentCharacterId();
|
||
|
||
let promiseStore = MapUtil.getLocaleData('character', Util.getCurrentCharacterId());
|
||
promiseStore.then(data => {
|
||
let filterButton = tableApi.button('tableTools', 'filterGroup:name').node();
|
||
let prependOptions = [{value: 0, text: 'unknown'}];
|
||
let sourceOptions = config.signatureGroupsLabels;
|
||
let selectedValues = [];
|
||
|
||
if(data && data.filterSignatureGroups && data.filterSignatureGroups.length){
|
||
// select local stored values
|
||
selectedValues = data.filterSignatureGroups;
|
||
}else{
|
||
// no default group filter options -> show all
|
||
selectedValues = sourceOptions.map(option => option.value);
|
||
selectedValues.unshift(0);
|
||
}
|
||
|
||
filterButton.editable({
|
||
mode: 'popup',
|
||
container: 'body',
|
||
type: 'checklist',
|
||
showbuttons: false,
|
||
onblur: 'submit',
|
||
highlight: false,
|
||
title: 'filter groups',
|
||
value: selectedValues,
|
||
prepend: prependOptions,
|
||
source: sourceOptions,
|
||
inputclass: config.editableUnknownInputClass,
|
||
display: function(value, sourceData){
|
||
// update filter button label
|
||
let html = '<i class="fa fa-filter"></i>group';
|
||
let allSelected = value.length >= sourceData.length;
|
||
if( !allSelected ){
|
||
html += ' (' + value.length + ')';
|
||
}
|
||
$(this).toggleClass('active', !allSelected).html(html);
|
||
},
|
||
validate: function(value){
|
||
// convert string to int -> important for further processing
|
||
return {newValue: value.map(num => parseInt(num)), msg: null};
|
||
}
|
||
});
|
||
|
||
let allOptions = prependOptions.concat(sourceOptions);
|
||
|
||
filterButton.on('save', {tableApi: tableApi, sourceOptions: allOptions}, function(e, params){
|
||
// store values local -> IndexDB
|
||
MapUtil.storeLocaleCharacterData('filterSignatureGroups', params.newValue);
|
||
|
||
searchGroupColumn(e.data.tableApi, params.newValue, e.data.sourceOptions);
|
||
});
|
||
|
||
// set initial search string -> even if table ist currently empty
|
||
searchGroupColumn(tableApi, selectedValues, allOptions);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* draw empty signature table
|
||
* @param moduleElement
|
||
* @param mapId
|
||
* @param systemData
|
||
*/
|
||
let drawSignatureTable = (moduleElement, mapId, systemData) => {
|
||
let table = $('<table>', {
|
||
id: getTableId(mapId, systemData.id, 'primary'),
|
||
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTablePrimaryClass].join(' ')
|
||
});
|
||
|
||
moduleElement.append(table);
|
||
|
||
let dataTableOptions = {
|
||
tabIndex: -1,
|
||
dom: '<"row"<"col-xs-3"l><"col-xs-5"B><"col-xs-4"f>>' +
|
||
'<"row"<"col-xs-12"tr>>' +
|
||
'<"row"<"col-xs-5"i><"col-xs-7"p>>',
|
||
buttons: {
|
||
name: 'tableTools',
|
||
buttons: [
|
||
{
|
||
name: 'filterGroup',
|
||
className: config.moduleHeadlineIconClass,
|
||
text: '' // set by js (xEditable)
|
||
},
|
||
{
|
||
name: 'selectAll',
|
||
className: config.moduleHeadlineIconClass,
|
||
text: '<i class="fa fa-check-double"></i>select all',
|
||
action: function(e, tableApi, node, conf){
|
||
let allRows = tableApi.rows();
|
||
let selectedRows = getSelectedRows(tableApi);
|
||
let allRowElements = allRows.nodes().to$();
|
||
|
||
if(allRows.data().length === selectedRows.data().length){
|
||
allRowElements.removeClass('selected');
|
||
}else{
|
||
allRowElements.addClass('selected');
|
||
}
|
||
|
||
// check delete button
|
||
checkDeleteSignaturesButton(tableApi);
|
||
}
|
||
},
|
||
{
|
||
name: 'delete',
|
||
className: [config.moduleHeadlineIconClass, config.sigTableClearButtonClass].join(' '),
|
||
text: '<i class="fa fa-trash"></i>delete (<span>0</span>)',
|
||
action: function(e, tableApi, node, conf){
|
||
let selectedRows = getSelectedRows(tableApi);
|
||
bootbox.confirm('Delete ' + selectedRows.data().length + ' signature?', function(result){
|
||
if(result){
|
||
deleteSignatures(tableApi, selectedRows);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
]
|
||
},
|
||
initComplete: function(settings, json){
|
||
let tableApi = this.api();
|
||
|
||
initGroupFilterButton(tableApi);
|
||
|
||
$(this).on('keyup', 'td', {tableApi: tableApi}, function(e){
|
||
keyNavigation(tableApi, e);
|
||
});
|
||
|
||
Counter.initTableCounter(this, ['created:name', 'updated:name']);
|
||
}
|
||
};
|
||
|
||
$.extend(true, dataTableOptions, getSignatureDataTableDefaults(mapId, systemData));
|
||
|
||
let tableApi = table.DataTable(dataTableOptions);
|
||
|
||
// "Responsive" dataTables plugin did not load automatic (because table is invisible onInit)
|
||
// -> manually start "Responsive" extension -> see default dataTable setting for config e.g. breakpoints
|
||
new $.fn.dataTable.Responsive(tableApi);
|
||
|
||
// lock table until module is fully rendered
|
||
lockTable(tableApi);
|
||
};
|
||
|
||
/**
|
||
* open xEditable input field in "new Signature" table
|
||
* @param moduleElement
|
||
*/
|
||
let focusNewSignatureEditableField = moduleElement => {
|
||
moduleElement.find('.' + config.sigTableSecondaryClass)
|
||
.find('td.' + config.sigTableEditSigNameInput).editable('show');
|
||
};
|
||
|
||
/**
|
||
* get all selected rows of a table
|
||
* @param tableApi
|
||
* @returns {*}
|
||
*/
|
||
let getSelectedRows = tableApi => {
|
||
return tableApi.rows('.selected');
|
||
};
|
||
|
||
/**
|
||
* check the "delete signature" button. show/hide the button if a signature is selected
|
||
* @param tableApi
|
||
*/
|
||
let checkDeleteSignaturesButton = tableApi => {
|
||
let selectedRows = getSelectedRows(tableApi);
|
||
let selectedRowCount = selectedRows.data().length;
|
||
let clearButton = tableApi.button('tableTools', 'delete:name').node();
|
||
|
||
if(selectedRowCount > 0){
|
||
let allRows = tableApi.rows();
|
||
let rowCount = allRows.data().length;
|
||
|
||
let countText = selectedRowCount;
|
||
if(selectedRowCount >= rowCount){
|
||
countText = 'all';
|
||
}
|
||
clearButton.find('i+span').text(countText);
|
||
|
||
// update clear signatures button text
|
||
clearButton.velocity('stop');
|
||
|
||
if( clearButton.is(':hidden') ){
|
||
// show button
|
||
clearButton.velocity('transition.expandIn', {
|
||
duration: 100
|
||
});
|
||
}else{
|
||
// highlight button
|
||
clearButton.velocity('callout.pulse', {
|
||
duration: 200
|
||
});
|
||
}
|
||
}else{
|
||
// hide button
|
||
clearButton.velocity('transition.expandOut', {
|
||
duration: 100
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* set module observer and look for relevant signature data to update
|
||
* @param moduleElement
|
||
* @param mapId
|
||
* @param systemData
|
||
*/
|
||
let setModuleObserver = (moduleElement, mapId, systemData) => {
|
||
let primaryTable = moduleElement.find('.' + config.sigTablePrimaryClass);
|
||
let primaryTableApi = getDataTableInstance(mapId, systemData.id, 'primary');
|
||
|
||
// add signature toggle ---------------------------------------------------------------------------------------
|
||
let toggleAddSignature = (show = 'auto') => {
|
||
let button = moduleElement.find('.' + config.moduleHeadlineIconAddClass);
|
||
let toolsElement = moduleElement.find('.' + config.tableToolsActionClass);
|
||
button.toggleClass('active', show === 'auto' ? undefined : show);
|
||
|
||
if(toolsElement.is(':visible') && (!show || show === 'auto')){
|
||
// hide container
|
||
toolsElement.velocity('stop').velocity({
|
||
opacity: [0, 1],
|
||
height: [0, '70px']
|
||
},{
|
||
duration: 150,
|
||
display: 'none'
|
||
});
|
||
}else if(!toolsElement.is(':visible') && (show || show === 'auto')){
|
||
// show container
|
||
toolsElement.velocity('stop').velocity({
|
||
opacity: [1, 0],
|
||
height: ['70px', 0]
|
||
},{
|
||
duration: 150,
|
||
display: 'block',
|
||
complete: function(){
|
||
focusNewSignatureEditableField(moduleElement);
|
||
}
|
||
});
|
||
}else if(toolsElement.is(':visible') && show){
|
||
// still visible -> no animation
|
||
focusNewSignatureEditableField(moduleElement);
|
||
}
|
||
};
|
||
|
||
moduleElement.find('.' + config.moduleHeadlineIconAddClass).on('click', function(e){
|
||
toggleAddSignature('auto');
|
||
});
|
||
|
||
moduleElement.on('pf:showSystemSignatureModuleAddNew', function(e){
|
||
toggleAddSignature(true);
|
||
});
|
||
|
||
// signature reader dialog ------------------------------------------------------------------------------------
|
||
moduleElement.find('.' + config.moduleHeadlineIconReaderClass).on('click', function(e){
|
||
moduleElement.showSignatureReaderDialog(systemData);
|
||
});
|
||
|
||
// "lazy update" toggle ---------------------------------------------------------------------------------------
|
||
moduleElement.find('.' + config.moduleHeadlineIconLazyClass).on('click', function(e){
|
||
let button = $(this);
|
||
button.toggleClass('active');
|
||
});
|
||
|
||
// set multi row select ---------------------------------------------------------------------------------------
|
||
primaryTable.on('mousedown', 'td', {tableApi: primaryTableApi}, function(e){
|
||
if(e.ctrlKey){
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
// xEditable field should not open -> on 'click'
|
||
// -> therefore disable "pointer-events" on "td" for some ms -> 'click' event is not triggered
|
||
$(this).css('pointer-events', 'none');
|
||
$(e.target).closest('tr').toggleClass('selected');
|
||
|
||
// check delete button
|
||
checkDeleteSignaturesButton(e.data.tableApi);
|
||
|
||
setTimeout(() => {
|
||
$(this).css('pointer-events', 'auto');
|
||
}, 250);
|
||
}
|
||
});
|
||
|
||
// draw event for signature table -----------------------------------------------------------------------------
|
||
primaryTableApi.on('draw.dt', function(e, settings){
|
||
// check delete button
|
||
let tableApi = $(this).dataTable().api();
|
||
checkDeleteSignaturesButton(tableApi);
|
||
});
|
||
|
||
// event listener for global "paste" signatures into the page -------------------------------------------------
|
||
moduleElement.on('pf:updateSystemSignatureModuleByClipboard', {tableApi: primaryTableApi}, function(e, clipboard){
|
||
let signatureOptions = {
|
||
deleteOld: moduleElement.find('.' + config.moduleHeadlineIconLazyClass).hasClass('active') ? 1 : 0
|
||
};
|
||
updateSignatureTableByClipboard(e.data.tableApi, systemData, clipboard, signatureOptions);
|
||
});
|
||
|
||
// signature column - "type" popover --------------------------------------------------------------------------
|
||
moduleElement.find('.' + config.sigTableClass).hoverIntent({
|
||
over: function(e){
|
||
let staticWormholeElement = $(this);
|
||
let wormholeName = staticWormholeElement.attr('data-name');
|
||
let wormholeData = Util.getObjVal(Init, 'wormholes.' + wormholeName);
|
||
if(wormholeData){
|
||
staticWormholeElement.addWormholeInfoTooltip(wormholeData, {
|
||
trigger: 'manual',
|
||
placement: 'top',
|
||
show: true
|
||
});
|
||
}
|
||
},
|
||
out: function(e){
|
||
$(this).destroyPopover();
|
||
},
|
||
selector: '.editable-click:not(.editable-open) span[class^="pf-system-sec-"]'
|
||
});
|
||
|
||
// signature column - "info" popover --------------------------------------------------------------------------
|
||
moduleElement.find('.' + config.sigTablePrimaryClass).hoverIntent({
|
||
over: function(e){
|
||
let cellElement = $(this);
|
||
let rowData = primaryTableApi.row(cellElement.parents('tr')).data();
|
||
cellElement.addCharacterInfoTooltip(rowData, {
|
||
trigger: 'manual',
|
||
placement: 'top',
|
||
show: true
|
||
});
|
||
},
|
||
out: function(e){
|
||
$(this).destroyPopover();
|
||
},
|
||
selector: 'td.' + Util.config.helpClass
|
||
});
|
||
};
|
||
|
||
/**
|
||
* add new row to signature table
|
||
* @param tableApi
|
||
* @param signatureData
|
||
* @returns {*}
|
||
*/
|
||
let addSignatureRow = (tableApi, signatureData) => {
|
||
let row = null;
|
||
if(tableApi){
|
||
row = tableApi.row.add(signatureData);
|
||
}
|
||
return row;
|
||
};
|
||
|
||
/**
|
||
* update signature table with new signatures
|
||
* -> add/update/delete rows
|
||
* @param tableApi
|
||
* @param signaturesDataOrig
|
||
* @param deleteOutdatedSignatures
|
||
*/
|
||
let updateSignatureTable = (tableApi, signaturesDataOrig, deleteOutdatedSignatures = false) => {
|
||
if(isLockedTable(tableApi)) return;
|
||
|
||
// disable tableApi until update finished;
|
||
lockTable(tableApi);
|
||
|
||
// clone signature array because of further manipulation
|
||
let signaturesData = $.extend([], signaturesDataOrig);
|
||
|
||
let rowIdsExist = [];
|
||
|
||
let promisesAdded = [];
|
||
let promisesChanged = [];
|
||
let promisesDeleted = [];
|
||
|
||
let allRows = tableApi.rows();
|
||
let updateEmptyTable = !allRows.any();
|
||
|
||
let rowUpdate = function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
|
||
let cell = this;
|
||
let node = cell.nodes().to$();
|
||
if(node.data('editable')){
|
||
// xEditable is active -> should always be active!
|
||
// set new value even if no change -> e.g. render selected Ids as text labels
|
||
let oldValue = node.editable('getValue', true);
|
||
|
||
// ... some editable cells depend on each other (e.g. group->type, group->connection)
|
||
switch(node.data('editable').options.name){
|
||
case 'typeId':
|
||
// ... disable if no type options found
|
||
editableSelectCheck(node);
|
||
break;
|
||
case 'connectionId':
|
||
// disables if no wormhole group set
|
||
let groupId = cell.cell(rowIndex, 'group:name').data();
|
||
if(groupId === 5){
|
||
// wormhole
|
||
editableEnable(node);
|
||
}else{
|
||
editableDisable(node);
|
||
}
|
||
break;
|
||
}
|
||
|
||
// values should be set AFTER en/disabling of a field
|
||
node.editable('setValue', cell.data());
|
||
|
||
if(oldValue !== cell.data()){
|
||
// highlight cell on data change
|
||
node.pulseBackgroundColor('changed');
|
||
}
|
||
}else if(node.hasClass(config.tableCellCounterClass)){
|
||
// "updated" timestamp always changed
|
||
node.pulseBackgroundColor('changed');
|
||
}
|
||
};
|
||
|
||
// update signatures ------------------------------------------------------------------------------------------
|
||
allRows.every(function(rowIdx, tableLoop, rowLoop){
|
||
let row = this;
|
||
let rowData = row.data();
|
||
let rowElement = row.nodes().to$();
|
||
|
||
for(let i = 0; i < signaturesData.length; i++){
|
||
if(signaturesData[i].id === rowData.id){
|
||
let rowId = row.id(true);
|
||
|
||
// check if row was updated
|
||
if(signaturesData[i].updated.updated > rowData.updated.updated){
|
||
// set new row data -> draw() is executed after all changes made
|
||
row.data(signaturesData[i]);
|
||
|
||
// bind new signature dataTable data() -> to xEditable inputs
|
||
row.cells(row.id(true), ['id:name', 'group:name', 'type:name', 'description:name', 'connection:name', 'updated:name'])
|
||
.every(rowUpdate);
|
||
|
||
promisesChanged.push(new Promise((resolve, reject) => {
|
||
resolve({action: 'changed', rowId: rowId});
|
||
}));
|
||
}
|
||
|
||
rowIdsExist.push(rowId);
|
||
|
||
// remove signature data -> all left signatures will be added
|
||
signaturesData.splice(i, 1);
|
||
i--;
|
||
}
|
||
}
|
||
});
|
||
|
||
// delete signatures ------------------------------------------------------------------------------------------
|
||
if(deleteOutdatedSignatures){
|
||
let rows = tableApi.rows((rowIdx, rowData, node) => !rowIdsExist.includes('#' + config.sigTableRowIdPrefix + rowData.id));
|
||
rows.every(function(rowIdx, tableLoop, rowLoop){
|
||
let row = this;
|
||
let rowId = row.id(true);
|
||
let rowElement = row.nodes().to$();
|
||
|
||
// hide open editable fields on the row before removing them
|
||
rowElement.find('.editable').editable('destroy');
|
||
|
||
// destroy possible open popovers (e.g. wormhole types, update popover)
|
||
rowElement.destroyPopover(true);
|
||
|
||
rowElement.pulseBackgroundColor('deleted');
|
||
|
||
promisesDeleted.push(new Promise((resolve, reject) => {
|
||
toggleTableRow(rowElement).then(payload => resolve({action: 'deleted', rowIdx: rowId}));
|
||
}));
|
||
}).remove();
|
||
}
|
||
|
||
// add new signatures -----------------------------------------------------------------------------------------
|
||
for(let signatureData of signaturesData){
|
||
let row = addSignatureRow(tableApi, signatureData);
|
||
let rowId = row.id(true);
|
||
let rowElement = row.nodes().to$();
|
||
rowElement.pulseBackgroundColor('added');
|
||
|
||
promisesAdded.push(new Promise((resolve, reject) => {
|
||
resolve({action: 'added', rowId: rowId});
|
||
}));
|
||
}
|
||
|
||
// done -------------------------------------------------------------------------------------------------------
|
||
Promise.all(promisesAdded.concat(promisesChanged, promisesDeleted)).then(payloads => {
|
||
if(payloads.length){
|
||
// table data changed -> draw() table changes
|
||
tableApi.draw();
|
||
|
||
// check for "leads to" conflicts -> important if there are just "update" (no add/delete) changes
|
||
checkConnectionConflicts();
|
||
|
||
if(!updateEmptyTable){
|
||
// no notifications if table was empty just progressbar notification is needed
|
||
// sum payloads by "action"
|
||
let notificationCounter = payloads.reduce((acc, payload) => {
|
||
if(!acc[payload.action]){
|
||
acc[payload.action] = 0;
|
||
}
|
||
acc[payload.action]++;
|
||
return acc;
|
||
}, {});
|
||
|
||
let notification = '';
|
||
if(notificationCounter.added > 0){
|
||
notification += notificationCounter.added + ' added<br>';
|
||
}
|
||
if(notificationCounter.changed > 0){
|
||
notification += notificationCounter.changed + ' updated<br>';
|
||
}
|
||
if(notificationCounter.deleted > 0){
|
||
notification += notificationCounter.deleted + ' deleted<br>';
|
||
}
|
||
if(notification.length){
|
||
Util.showNotify({title: 'Signatures updated', text: notification, type: 'success'});
|
||
}
|
||
}
|
||
|
||
updateScannedSignaturesBar(tableApi, {showNotice: true});
|
||
}
|
||
|
||
// unlock table
|
||
unlockTable(tableApi);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* update trigger function for this module
|
||
* compare data and update module
|
||
* @param moduleElement
|
||
* @param systemData
|
||
*/
|
||
let updateModule = (moduleElement, systemData) => {
|
||
|
||
if(systemData.signatures){
|
||
let mapId = moduleElement.data('mapId');
|
||
let systemId = moduleElement.data('systemId');
|
||
let tableApi = getDataTableInstance(mapId, systemId, 'primary');
|
||
updateSignatureTable(tableApi, systemData.signatures, true);
|
||
}
|
||
|
||
moduleElement.hideLoadingAnimation();
|
||
};
|
||
|
||
/**
|
||
* init callback
|
||
* @param moduleElement
|
||
* @param mapId
|
||
* @param systemData
|
||
*/
|
||
let initModule = (moduleElement, mapId, systemData) => {
|
||
let tableApi = getDataTableInstance(mapId, systemData.id, 'primary');
|
||
unlockTable(tableApi);
|
||
};
|
||
|
||
/**
|
||
* get module toolbar element
|
||
* @returns {jQuery}
|
||
*/
|
||
let getHeadlineToolbar = () => {
|
||
let headlineToolbar = $('<h5>', {
|
||
class: 'pull-right'
|
||
}).append(
|
||
$('<span>', {
|
||
class: 'progress-label-right',
|
||
text: '0%'
|
||
}),
|
||
$('<i>', {
|
||
class: ['fas', 'fa-fw', 'fa-plus', config.moduleHeadlineIconClass, config.moduleHeadlineIconAddClass].join(' '),
|
||
title: 'add'
|
||
}).attr('data-toggle', 'tooltip'),
|
||
$('<i>', {
|
||
class: ['fas', 'fa-fw', 'fa-paste', config.moduleHeadlineIconClass, config.moduleHeadlineIconReaderClass].join(' '),
|
||
title: 'signature reader'
|
||
}).attr('data-toggle', 'tooltip'),
|
||
$('<i>', {
|
||
class: ['fas', 'fa-fw', 'fa-exchange-alt', config.moduleHeadlineIconClass, config.moduleHeadlineIconLazyClass].join(' '),
|
||
title: 'lazy \'delete\' signatures'
|
||
}).attr('data-toggle', 'tooltip')
|
||
);
|
||
|
||
headlineToolbar.find('[data-toggle="tooltip"]').tooltip({
|
||
container: 'body'
|
||
});
|
||
|
||
return headlineToolbar;
|
||
};
|
||
|
||
/**
|
||
* get module element
|
||
* @param parentElement
|
||
* @param mapId
|
||
* @param systemData
|
||
* @returns {jQuery}
|
||
*/
|
||
let getModule = (parentElement, mapId, systemData) => {
|
||
let moduleElement = $('<div>').append(
|
||
$('<div>', {
|
||
class: config.moduleHeadClass
|
||
}).append(
|
||
$('<h5>', {
|
||
class: config.moduleHandlerClass
|
||
}),
|
||
$('<h5>', {
|
||
text: 'Signatures'
|
||
}),
|
||
getHeadlineToolbar()
|
||
)
|
||
);
|
||
|
||
// scanned signatures progress bar ----------------------------------------------------------------------------
|
||
requirejs(['text!templates/form/progress.html', 'mustache'], (template, Mustache) => {
|
||
let data = {
|
||
label: true,
|
||
wrapperClass: config.moduleHeadlineProgressBarClass,
|
||
class: ['progress-bar-success'].join(' '),
|
||
percent: 0
|
||
};
|
||
moduleElement.find('.' + config.moduleHeadClass).append(Mustache.render(template, data));
|
||
});
|
||
|
||
moduleElement.data('mapId', mapId);
|
||
moduleElement.data('systemId', systemData.id);
|
||
|
||
moduleElement.showLoadingAnimation();
|
||
|
||
// draw "new signature" add table
|
||
drawSignatureTableNew(moduleElement, mapId, systemData);
|
||
|
||
// draw signature table
|
||
drawSignatureTable(moduleElement, mapId, systemData);
|
||
|
||
// set module observer
|
||
setModuleObserver(moduleElement, mapId, systemData);
|
||
|
||
return moduleElement;
|
||
};
|
||
|
||
/**
|
||
* before module hide callback
|
||
* @param moduleElement
|
||
*/
|
||
let beforeHide = moduleElement => {
|
||
// disable update
|
||
let mapId = moduleElement.data('mapId');
|
||
let systemId = moduleElement.data('systemId');
|
||
let tableApi = getDataTableInstance(mapId, systemId, 'primary');
|
||
lockTable(tableApi);
|
||
};
|
||
|
||
/**
|
||
* before module destroy callback
|
||
* @param moduleElement
|
||
*/
|
||
let beforeDestroy = moduleElement => {
|
||
// Destroying the data tables throws
|
||
// -> safety remove all dataTables
|
||
let mapId = moduleElement.data('mapId');
|
||
let systemId = moduleElement.data('systemId');
|
||
let primaryTableApi = getDataTableInstance(mapId, systemId, 'primary');
|
||
let secondaryTableApi = getDataTableInstance(mapId, systemId, 'secondary');
|
||
primaryTableApi.destroy();
|
||
secondaryTableApi.destroy();
|
||
};
|
||
|
||
return {
|
||
config: config,
|
||
getModule: getModule,
|
||
initModule: initModule,
|
||
updateModule: updateModule,
|
||
beforeHide: beforeHide,
|
||
beforeDestroy: beforeDestroy,
|
||
getAllSignatureNamesBySystem: getAllSignatureNamesBySystem
|
||
};
|
||
}); |