- New delete "linked connection" options added to the "signature table", closed #803

- Fixed a bug where "signature reader" updates don´t respect changed sig. `groupId` changes, closed #838
- Fixed some minor CSS glitches in "modals/popups"
- Upgraded "[_BootboxJS_](http://bootboxjs.com)" js lib `v4.4.0` → `v5.2.0`
- Upgraded "[_hoverIntent_](http://briancherne.github.io/jquery-hoverIntent/)" js lib `v1.9.0` → `v1.10.0`
This commit is contained in:
Mark Friedrich
2019-08-12 19:26:08 +02:00
parent a69c9f78f1
commit 70f7501a43
47 changed files with 1907 additions and 590 deletions

View File

@@ -18,7 +18,8 @@ Mapping tool for [*EVE ONLINE*](https://www.eveonline.com)
- [wiki](https://github.com/exodus4d/pathfinder/wiki)
- Developer [Slack](https://slack.com) chat:
- https://pathfinder-eve-online.slack.com
- Please send me a mail for invite: pathfinder@exodus4d.de
- Join channel [pathfinder-eve-online.slack.com](https://join.slack.com/t/pathfinder-eve-online/shared_invite/enQtMzMyOTkyMjczMTA3LWI2NGE1OTY5ODBmNDZlMDY3MDIzYjk5ZTljM2JjZjIwNDRkNzMyMTEwMDUzOGQwM2E3ZjE1NGEwNThlMzYzY2Y)
- Can´t join? pathfinder@exodus4d.de
**Feel free to check the code for bugs and security issues.
Issues should be reported in the [Issue](https://github.com/exodus4d/pathfinder/issues) section.**
@@ -49,7 +50,7 @@ Issues should be reported in the [Issue](https://github.com/exodus4d/pathfinder/
|-- app.js --> require.js config (!required for production!)
|-- [0777] logs/ --> log files
|-- ...
| -- node_modules/ --> node.js modules (not used for production)
|-- node_modules/ --> node.js modules (not used for production)
|-- ...
|-- [0755] public/ --> frontend source
|-- css/ --> CSS dist/build folder (minified)

View File

@@ -56,7 +56,7 @@ class Signature extends AbstractRestController {
$data['groupId'] == 5 ||
$data['typeId'] == 0
){
unset( $data['typeId'] );
unset($data['typeId']);
}
// "sig reader" should not overwrite signature group information
@@ -65,6 +65,7 @@ class Signature extends AbstractRestController {
$signature->groupId > 0
){
unset($data['groupId']);
unset($data['typeId']);
}
}
@@ -78,13 +79,20 @@ class Signature extends AbstractRestController {
// delete "old" signatures ----------------------------------------------------------------------------
if((bool)$requestData['deleteOld']){
// if linked ConnectionModels should be deleted as well
$deleteConnectionId = (bool)$requestData['deleteConnection'];
$updatedSignatureIds = array_column($signaturesData, 'id');
$signatures = $system->getSignatures();
foreach($signatures as $signature){
if(!in_array($signature->_id, $updatedSignatureIds)){
// set if potential linked ConnectionModel should be deleted as well
$signature->virtual('connectionIdDeleteCascade', $deleteConnectionId);
if($signature->delete()){
$updateSignaturesHistory = true;
}
// clear temp virtual field as well
$signature->clearVirtual('connectionIdDeleteCascade');
}
}
}
@@ -190,6 +198,9 @@ class Signature extends AbstractRestController {
$system->getById($systemId);
if($system->hasAccess($activeCharacter)){
// if linked ConnectionModels should be deleted as well
$deleteConnectionId = (bool)$requestData['deleteConnection'];
// if there is any changed/deleted/updated signature
// -> we need to update signature history data for the system
$updateSignaturesHistory = false;
@@ -202,11 +213,15 @@ class Signature extends AbstractRestController {
$signature->getById($signatureId);
// make sure signature belongs to main system (user has access)
if($signature->get('systemId', true) == $systemId){
// set if potential linked ConnectionModel should be deleted as well
$signature->virtual('connectionIdDeleteCascade', $deleteConnectionId);
if($signature->delete()){
$deletedSignatureIds[] = $signatureId;
$updateSignaturesHistory = true;
}
$signature->reset();
// clear temp virtual field as well
$signature->clearVirtual('connectionIdDeleteCascade');
}
}

View File

@@ -278,6 +278,13 @@ class SystemSignatureModel extends AbstractMapTrackingModel {
*/
public function afterEraseEvent($self, $pkeys){
$self->logActivity('signatureDelete');
if(
$self->connectionIdDeleteCascade === true &&
($connection = $self->getConnection())
){
$connection->erase();
}
}
/**

View File

@@ -42,11 +42,11 @@ requirejs.config({
xEditable: 'lib/bootstrap-editable.min', // v1.5.1 X-editable - in placed editing
morris: 'lib/morris.min', // v0.5.1 Morris.js - graphs and charts
raphael: 'lib/raphael.min', // v2.2.8 Raphaël - required for morris - https://dmitrybaranovskiy.github.io/raphael
bootbox: 'lib/bootbox.min', // v4.4.0 Bootbox.js - custom dialogs - http://bootboxjs.com
bootbox: 'lib/bootbox.min', // v5.2.0 Bootbox.js - custom dialogs - http://bootboxjs.com
easyPieChart: 'lib/jquery.easypiechart.min', // v2.1.6 Easy Pie Chart - HTML 5 pie charts - http://rendro.github.io/easy-pie-chart
peityInlineChart: 'lib/jquery.peity.min', // v3.2.1 Inline Chart - http://benpickles.github.io/peity/
dragToSelect: 'lib/jquery.dragToSelect', // v1.1 Drag to Select - http://andreaslagerkvist.com/jquery/drag-to-select
hoverIntent: 'lib/jquery.hoverIntent.min', // v1.9.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
hoverIntent: 'lib/jquery.hoverIntent.min', // v1.10.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
select2: 'lib/select2.min', // v4.0.3 Drop Down customization - https://select2.github.io
validator: 'lib/validator.min', // v0.10.1 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
lazylinepainter: 'lib/jquery.lazylinepainter-1.5.1.min', // v1.5.1 SVG line animation plugin - http://lazylinepainter.info

View File

@@ -45,6 +45,7 @@ define([
table.destroyTimestampCounter(true);
});
// Status Plugin ==============================================================================================
let StatusTable = function(settings){
let me = this;
me.statusContainer = $('<div>', {

View File

@@ -141,15 +141,14 @@ define([
// show Cookie accept hint on SSO login button
let confirmationSettings = {
container: 'body',
placement: 'bottom',
btnOkClass: 'btn btn-sm btn-default',
btnOkLabel: 'dismiss',
btnOkIcon: 'fas fa-fw fa-sign-in-alt',
title: 'Accept cookies',
btnCancelClass: 'btn btn-sm btn-success',
btnCancelLabel: 'accept',
btnCancelIcon: 'fas fa-fw fa-check',
btnOkClass: 'btn btn-sm btn-default',
btnOkLabel: 'dismiss',
btnOkIcon: 'fas fa-fw fa-sign-in-alt',
onCancel: function(e, target){
// "Accept cookies"
setAcceptCookie();

View File

@@ -31,6 +31,9 @@ define([
// set default dialog config
Util.initDefaultBootboxConfig();
// set default confirmation popover config
Util.initDefaultConfirmationConfig();
// set default select2 config
Util.initDefaultSelect2Config();

View File

@@ -853,6 +853,10 @@ define([
let modalElement = $(e.target);
modalElement.destroyTimestampCounter(true);
// destroy all form validators
// -> does not work properly. validation functions still used (js error) after 'destroy'
//modalElement.find('form').filter((i, form) => $(form).data('bs.validator')).validator('destroy');
// destroy all popovers
modalElement.find('.' + Util.config.popoverTriggerClass).popover('destroy');

View File

@@ -77,11 +77,9 @@ define([
// show "discard" changes confirmation
let confirmationSettings = {
container: 'body',
placement: 'top',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
title: 'discard changes',
btnCancelIcon: '',
btnOkClass: 'btn btn-sm btn-warning',
btnOkLabel: 'discard',
btnOkIcon: 'fas fa-fw fa-ban',

View File

@@ -56,18 +56,6 @@ define([
}
};
// confirmation dialog settings (e.g. delete row)
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times'
};
/**
* get icon that marks a table cell as clickable
* @returns {string}
@@ -456,44 +444,49 @@ define([
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tempTableElement = this;
let tempConfirmationSettings = confirmationSettings;
tempConfirmationSettings.title = 'Delete system';
tempConfirmationSettings.onConfirm = function(e, target){
let deleteRowElement = $(target).parents('tr');
let confirmationSettings = {
placement: 'left',
title: 'Delete system',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm: function(e, target){
let deleteRowElement = $(target).parents('tr');
let activeMap = Util.getMapModule().getActiveMap();
let systemElement = $('#' + MapUtil.getSystemId(mapData.config.id, rowData.id) );
let activeMap = Util.getMapModule().getActiveMap();
let systemElement = $('#' + MapUtil.getSystemId(mapData.config.id, rowData.id) );
if(systemElement.length){
// trigger system delete event
activeMap.trigger('pf:deleteSystems', [{
systems: [systemElement[0]],
callback: function(deletedSystems){
// callback function after ajax "delete" success
// check if system was deleted
if(deletedSystems.length === 1){
// remove table row
tempTableElement.DataTable().rows(deleteRowElement).remove().draw();
if(systemElement.length){
// trigger system delete event
activeMap.trigger('pf:deleteSystems', [{
systems: [systemElement[0]],
callback: function(deletedSystems){
// callback function after ajax "delete" success
// check if system was deleted
if(deletedSystems.length === 1){
// remove table row
tempTableElement.DataTable().rows(deleteRowElement).remove().draw();
Util.showNotify({title: 'System deleted', text: rowData.name, type: 'success'});
Util.showNotify({title: 'System deleted', text: rowData.name, type: 'success'});
// refresh connection table (connections might have changed) --------------
let connectionsElement = $('#' + config.mapInfoConnectionsId);
let mapDataNew = activeMap.getMapDataFromClient(['hasId']);
// refresh connection table (connections might have changed) --------------
let connectionsElement = $('#' + config.mapInfoConnectionsId);
let mapDataNew = activeMap.getMapDataFromClient(['hasId']);
connectionsElement.initConnectionInfoTable(mapDataNew);
}else{
// error
Util.showNotify({title: 'Failed to delete system', text: rowData.name, type: 'error'});
connectionsElement.initConnectionInfoTable(mapDataNew);
}else{
// error
Util.showNotify({title: 'Failed to delete system', text: rowData.name, type: 'error'});
}
}
}
}]);
}]);
}
}
};
// init confirmation dialog
$(cell).confirmation(tempConfirmationSettings);
$(cell).confirmation(confirmationSettings);
}
}
],
@@ -652,23 +645,29 @@ define([
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tempTableElement = this;
let tempConfirmationSettings = confirmationSettings;
tempConfirmationSettings.title = 'Delete connection';
tempConfirmationSettings.onConfirm = function(e, target){
let deleteRowElement = $(target).parents('tr');
let confirmationSettings = {
placement: 'left',
title: 'Delete connection',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm: function(e, target){
let deleteRowElement = $(target).parents('tr');
// deleteSignatures(row);
let connection = $().getConnectionById(mapData.config.id, rowData.id);
// deleteSignatures(row);
let connection = $().getConnectionById(mapData.config.id, rowData.id);
MapUtil.deleteConnections([connection], () => {
// callback function after ajax "delete" success
// remove table row
tempTableElement.DataTable().rows(deleteRowElement).remove().draw();
});
MapUtil.deleteConnections([connection], () => {
// callback function after ajax "delete" success
// remove table row
tempTableElement.DataTable().rows(deleteRowElement).remove().draw();
});
}
};
// init confirmation dialog
$(cell).confirmation(tempConfirmationSettings);
$(cell).confirmation(confirmationSettings);
}
}
],

View File

@@ -189,7 +189,7 @@ define([
html: '<i class="fas fa-fw fa-question-circle"></i>'
}),
$('<td>', {
text: scopeLabel.charAt(0).toUpperCase() + scopeLabel.slice(1)
text: scopeLabel.capitalize()
}),
$('<td>', {
class: ['text-right', config.connectionInfoTableCellConnectionClass].join(' ')
@@ -883,15 +883,11 @@ define([
if(rowData.active){
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete jump log',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm : function(e, target){
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state

View File

@@ -569,15 +569,11 @@ define([
$(cell).find('i').tooltip();
}else{
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete structure',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm : function(e, target){
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state

View File

@@ -88,6 +88,12 @@ define([
return cachedData;
};
/**
* @param mapIds
* @param sourceName
* @param targetName
* @returns {string}
*/
let getRouteDataCacheKey = (mapIds, sourceName, targetName) => {
return [mapIds.join('_'), sourceName.toLowerCase(), targetName.toLowerCase()].join('###');
};
@@ -245,8 +251,7 @@ define([
* show route dialog. User can search for systems and jump-info for each system is added to a data table
* @param dialogData
*/
let showFindRouteDialog = (dialogData) => {
let showFindRouteDialog = dialogData => {
let mapSelectOptions = [];
let currentMapData = Util.getCurrentMapData();
if(currentMapData !== false){
@@ -548,12 +553,12 @@ define([
* set event observer for route finder dialog
* @param routeDialog
*/
let setDialogObserver = (routeDialog) => {
let wormholeCheckbox = routeDialog.find('input[type="checkbox"][name="wormholes"]');
let wormholeReducedCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesReduced"]');
let wormholeCriticalCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesCritical"]');
let wormholeEolCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesEOL"]');
let wormholeSizeSelect = routeDialog.find('#' + config.routeDialogSizeSelectId);
let setDialogObserver = routeDialog => {
let wormholeCheckbox = routeDialog.find('input[type="checkbox"][name="wormholes"]');
let wormholeReducedCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesReduced"]');
let wormholeCriticalCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesCritical"]');
let wormholeEolCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesEOL"]');
let wormholeSizeSelect = routeDialog.find('#' + config.routeDialogSizeSelectId);
// store current "checked" state for each box ---------------------------------------------
let storeCheckboxStatus = function(){
@@ -1127,15 +1132,11 @@ define([
let tempTableElement = this;
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete route',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm : function(e, target){
let deleteRowElement = $(cell).parents('tr');
tempTableElement.api().rows(deleteRowElement).remove().draw();

View File

@@ -31,10 +31,14 @@ define([
moduleHeadlineIconLazyClass: 'pf-module-icon-button-lazy', // class for "lazy delete" toggle icon
moduleHeadlineProgressBarClass: 'pf-system-progress-scanned', // class for signature progress bar
// fonts
fontUppercaseClass: 'pf-font-uppercase', // class for "uppercase" font
// tables
tableToolsActionClass: 'pf-table-tools-action', // class for "new signature" table (hidden)
// table toolbar
tableToolbarStatusClass: 'pf-table-toolbar-status', // class for "status" DataTable Toolbar
sigTableClearButtonClass: 'pf-sig-table-clear-button', // class for "clear" signatures button
// signature table
@@ -42,6 +46,7 @@ define([
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
sigTableInfoClass: 'pf-sig-table-info', // class for info sig table
sigTableRowIdPrefix: 'pf-sig-row_', // id prefix for table rows
sigTableEditSigNameInput: 'pf-sig-table-edit-name-input', // class for editable fields (sig name)
@@ -52,12 +57,23 @@ define([
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')
signatureGroupsNames: Util.getSignatureGroupOptions('name'),
// signature reader dialog
sigReaderDialogClass: 'pf-sig-reader-dialog', // class for "signature reader" dialog
sigInfoId: 'pf-sig-info', // id for "signature info" table area
sigInfoTextareaId: 'pf-sig-info-textarea', // id for signature reader "textarea"
sigReaderLazyUpdateId: 'pf-sig-reader-lazy-update', // id for "lazy update" checkbox
sigReaderConnectionDeleteId: 'pf-sig-reader-delete-connection', // id for "delete connection" checkbox
sigInfoCountSigNewId: 'pf-sig-info-count-sig-new', // id for "signature new" counter
sigInfoCountSigChangeId: 'pf-sig-info-count-sig-change', // id for "signature change" counter
sigInfoCountSigDeleteId: 'pf-sig-info-count-sig-delete', // id for "signature delete" counter
sigInfoCountConDeleteId: 'pf-sig-info-count-con-delete' // id for "connection delete" counter
};
let sigNameCache = {}; // cache signature names
@@ -84,6 +100,13 @@ define([
typeId: 0
};
let emptySignatureReaderCounterData = {
added: 0,
changed: 0,
deleted: 0,
deleteCon: 0
};
let editableDefaults = { // xEditable default options for signature fields
url: function(params){
let saveExecutor = (resolve, reject) => {
@@ -518,9 +541,9 @@ define([
*/
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 parentElement = $(tableElement).parents('.' + config.moduleTypeClass + ', .' + config.sigReaderDialogClass);
let progressBar = parentElement.find('.progress-bar');
let progressBarLabel = parentElement.find('.progress-label-right');
let percent = 0;
let progressBarType = '';
@@ -557,17 +580,168 @@ define([
}
};
/**
* load existing (current) signature data into info table (preview)
* @param infoTableApi
* @param mapId
* @param systemId
* @param draw
*/
let initTableDataWithCurrentSignatureData = (infoTableApi, mapId, systemId, draw = false) => {
// reset/clear infoTable
infoTableApi.clear();
let primaryTableApi = getDataTableInstance(mapId, systemId, 'primary');
if(primaryTableApi){
infoTableApi.rows.add(primaryTableApi.data().toArray());
if(draw){
infoTableApi.draw();
}
}else{
console.warn('Signature table not found. mapId: %d; systemId: %d', mapId, systemId);
}
};
/**
* set "signature reader" dialog observer
* @param dialogElement
* @param mapId
* @param systemData
*/
let setSignatureReaderDialogObserver = (dialogElement, mapId, systemData) => {
let systemId = systemData.id;
let form = dialogElement.find('form').first();
let textarea = form.find('#' + config.sigInfoTextareaId);
let deleteOutdatedCheckbox = form.find('#' + config.sigReaderLazyUpdateId);
let deleteConnectionsCheckbox = form.find('#' + config.sigReaderConnectionDeleteId);
let errorClipboardValidation = 'No signatures found in scan result';
let tableStatusElement = dialogElement.find('.' + config.tableToolbarStatusClass);
form.initFormValidation({
delay: 0,
feedback: {
success: 'fa-check',
error: 'fa-times'
},
custom: {
clipboard: function(textarea){
let signatureData = parseSignatureString(systemData, textarea.val());
tableStatusElement.text(signatureData.length + ' signatures parsed');
if(signatureData.length === 0){
return errorClipboardValidation;
}
}
}
});
let updatePreviewSection = (mapId, systemId, formData) => {
let infoTableApi = getDataTableInstance(mapId, systemId, 'info');
if(infoTableApi){
// init 'infoTable' with existing signature rows
// infoTableApi.draw() not necessary at this point!
initTableDataWithCurrentSignatureData(infoTableApi, mapId, systemId);
let signatureData = parseSignatureString(systemData, formData.clipboard);
if(signatureData.length > 0){
// valid signature data parsed
// -> add some default data (e.g. currentCharacter data) to parsed signatureData
// -> not required, just for filling up some more columns
signatureData = enrichParsedSignatureData(signatureData);
updateSignatureInfoTable(infoTableApi, signatureData, Boolean(formData.deleteOld), Boolean(formData.deleteConnection));
}else{
// no signatures pasted -> draw current signature rows
infoTableApi.draw();
// reset counter elements
updateSignatureReaderCounters(emptySignatureReaderCounterData);
updateScannedSignaturesBar(infoTableApi, {showNotice: false});
console.info(errorClipboardValidation);
}
}else{
console.warn('Signature "preview" table not found. mapId: %d; systemId: %d', mapId, systemId);
}
};
// changes in 'scan result' textarea -> update preview table --------------------------------------------------
let oldValue = '';
textarea.on('change keyup paste', function(){
let formData = form.getFormValues();
let currentValue = formData.clipboard;
if(currentValue === oldValue){
return; //check to prevent multiple simultaneous triggers
}
oldValue = currentValue;
updatePreviewSection(mapId, systemId, formData);
});
textarea.on('focus', function(e){
this.select();
});
// en/disable 'lazy update' toggles dependent checkbox --------------------------------------------------------
let onDeleteOutdatedCheckboxChange = function(){
deleteConnectionsCheckbox.prop('disabled', !this.checked);
deleteConnectionsCheckbox.prop('checked', false);
}.bind(deleteOutdatedCheckbox[0]);
deleteOutdatedCheckbox.on('change', onDeleteOutdatedCheckboxChange);
onDeleteOutdatedCheckboxChange();
// en/disable checkboxes -> update preview table --------------------------------------------------------------
deleteOutdatedCheckbox.add(deleteConnectionsCheckbox).on('change', function(){
let formData = form.getFormValues();
if(formData.clipboard.length){
updatePreviewSection(mapId, systemId, form.getFormValues());
}
});
// listen 'primary' sig table updates -> update 'preview' sig table in the dialog -----------------------------
dialogElement.on('pf:updateSignatureReaderDialog', function(e){
updatePreviewSection(mapId, systemId, form.getFormValues());
});
};
/**
* open "signature reader" dialog for signature table
* @param systemData
*/
$.fn.showSignatureReaderDialog = function(systemData){
let moduleElement = $(this);
let mapId = moduleElement.data('mapId');
let systemId = moduleElement.data('systemId');
requirejs([
'text!templates/dialog/signature_reader.html',
'text!templates/form/progress.html',
'mustache'
], (TplDialog, TplProgress, Mustache) => {
let data = {
sigInfoId: config.sigInfoId,
sigReaderLazyUpdateId: config.sigReaderLazyUpdateId,
sigReaderConnectionDeleteId: config.sigReaderConnectionDeleteId,
sigInfoTextareaId: config.sigInfoTextareaId,
sigInfoLazyUpdateStatus: getLazyUpdateToggleStatus(moduleElement),
sigInfoCountSigNewId: config.sigInfoCountSigNewId,
sigInfoCountSigChangeId: config.sigInfoCountSigChangeId,
sigInfoCountSigDeleteId: config.sigInfoCountSigDeleteId,
sigInfoCountConDeleteId: config.sigInfoCountConDeleteId,
sigInfoProgressElement : Mustache.render(TplProgress, {
label: true,
wrapperClass: config.moduleHeadlineProgressBarClass,
class: ['progress-bar-success'].join(' '),
percent: 0
})
};
requirejs(['text!templates/dialog/signature_reader.html', 'mustache'], (template, Mustache) => {
let signatureReaderDialog = bootbox.dialog({
className: config.sigReaderDialogClass,
title: 'Signature reader',
message: Mustache.render(template, {}),
size: 'large',
message: Mustache.render(TplDialog, data),
show: false,
buttons: {
close: {
label: 'cancel',
@@ -578,21 +752,42 @@ define([
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');
// validate form
form.validator('validate');
updateSignatureTableByClipboard(tableApi, systemData, formData.clipboard, signatureOptions);
// check whether the form is valid
if(form.isValidForm()){
// get form data
let formData = form.getFormValues();
let signatureOptions = {
deleteOld: (formData.deleteOld) ? 1 : 0,
deleteConnection: (formData.deleteConnection) ? 1 : 0
};
let tableApi = getDataTableInstance(mapId, systemId, 'primary');
updateSignatureTableByClipboard(tableApi, systemData, formData.clipboard, signatureOptions);
}else{
return false;
}
}
}
}
});
signatureReaderDialog.on('show.bs.modal', function(e) {
let dialogElement = $(this);
let infoTableApi = drawSignatureTableInfo(this, mapId, systemData);
// init 'infoTable' with existing signature rows
initTableDataWithCurrentSignatureData(infoTableApi, mapId, systemId, true);
updateScannedSignaturesBar(infoTableApi, {showNotice: false});
setSignatureReaderDialogObserver(dialogElement, mapId, systemData);
});
// dialog shown event
signatureReaderDialog.on('shown.bs.modal', function(e){
signatureReaderDialog.initTooltips();
@@ -600,9 +795,36 @@ define([
// set focus on sig-input textarea
signatureReaderDialog.find('textarea').focus();
});
// show dialog
signatureReaderDialog.modal('show');
});
};
/**
* Parsed scan result data (from EVE client) should be enriched with some data
* -> fill up more columns in the 'preview' signature tab.e
* @param signatureData
* @returns {*}
*/
let enrichParsedSignatureData = signatureData => {
let characterData = Util.getObjVal(Util.getCurrentUserData(), 'character');
let timestamp = Math.floor((new Date()).getTime() / 1000);
for(let i = 0; i < signatureData.length; i++){
signatureData[i].created = {
created: timestamp,
character: characterData
};
signatureData[i].updated = {
updated: timestamp,
character: characterData
};
}
return signatureData;
};
/**
* parses a copy&paste string from ingame scanning window
* @param systemData
@@ -712,7 +934,8 @@ define([
[],
{
signatures: signatureData,
deleteOld: options.deleteOld,
deleteOld: options.deleteOld || 0,
deleteConnection: options.deleteConnection || 0,
systemId: parseInt(systemData.id)
},
{
@@ -764,17 +987,19 @@ define([
* deletes signature rows from signature table
* @param tableApi
* @param rows
* @param deleteOptions
*/
let deleteSignatures = (tableApi, rows) => {
let deleteSignatures = (tableApi, rows, deleteOptions = {}) => {
// 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 metaData = getTableMetaData(tableApi);
let data = Object.assign(deleteOptions, {
systemId: metaData.systemId
});
let processRequestPromise = tableApi.newProcess('request');
Util.request('DELETE', 'signature', signatureIds, {
systemId: metaData.systemId
}, {
Util.request('DELETE', 'signature', signatureIds, data, {
tableApi: tableApi,
processRequestPromise: processRequestPromise
},
@@ -841,7 +1066,11 @@ define([
*/
let checkConnectionConflicts = () => {
setTimeout(() => {
let connectionSelects = $('.' + config.tableCellConnectionClass + '.editable');
let connectionSelectsSelector = [config.sigTablePrimaryClass, config.sigTableSecondaryClass].map(
tableClass => '.' + tableClass + ' .' + config.tableCellConnectionClass + '.editable'
).join(', ');
let connectionSelects = $(connectionSelectsSelector);
let connectionIds = [];
let duplicateConnectionIds = [];
let groupedSelects = [];
@@ -1216,6 +1445,47 @@ define([
}
};
/**
* get HTML for "delete connection" confirmation popover
* @returns {string}
*/
let getConfirmationContent = () => {
let checkOptions = [{
name: 'deleteConnection',
value: '1',
label: 'delete connection',
class: 'pf-editable-warn',
checked: true
}];
let getChecklist = checkOptions => {
let html = '<form class="form-inline editableform popover-content-inner">';
html += '<div class="control-group form-group">';
html += '<div class="editable-input">';
html += '<div class="editable-checklist">';
for(let option of checkOptions){
html += '<div><label>';
html += '<input type="checkbox" name="' + option.name + '" value="' + option.value + '" ';
html += 'class="' + option.class + '" ' + (option.checked ? 'checked' : '') + '>';
html += '<span>' + option.label + '</span>';
html += '</label></div>';
}
html += '</div>';
html += '</div>';
html += '</div>';
html += '</form>';
return html;
};
let html = '';
html += getChecklist(checkOptions);
return html;
};
/**
* get dataTables default options for signature tables
* @param mapId
@@ -1281,7 +1551,7 @@ define([
title: 'id',
type: 'string',
width: 12,
class: [config.tableCellFocusClass, config.sigTableEditSigNameInput].join(' '),
class: [config.tableCellFocusClass, config.sigTableEditSigNameInput, config.fontUppercaseClass].join(' '),
data: 'name',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
@@ -1299,7 +1569,7 @@ define([
pk: rowData.id || null,
emptytext: '? ? ?',
value: cellData,
inputclass: config.editableNameInputClass,
inputclass: config.fontUppercaseClass,
display: function(value){
// change display value to first 3 letters
$(this).text($.trim( value.substr(0, 3) ).toLowerCase());
@@ -1634,7 +1904,7 @@ define([
let diff = Math.floor((new Date()).getTime()) - cellData * 1000;
// age > 1 day
if( diff > 86400000){
if(diff > 86400000){
$(cell).addClass('txt-color txt-color-warning');
}
}
@@ -1679,22 +1949,43 @@ define([
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',
title: 'Delete signature',
template: Util.getConfirmationTemplate(getConfirmationContent(), {
size: 'small',
noTitle: true
}),
onConfirm: function(e, target){
// top scroll to top
e.preventDefault();
// get form data (check if form tag is not hidden!) from confirmation popover
let tip = target.data('bs.confirmation').tip();
let form = tip.find('form:not(.hidden)').first();
let formData = form.getFormValues();
let deleteOptions = Util.getObjVal(formData, 'deleteConnection') ? formData : {};
// add "processing" state or connection that will be deleted as well
if(deleteOptions.deleteConnection){
let connectionId = tableApi.cell(rowIndex, 'connection:name').data();
if(connectionId){
let metaData = getTableMetaData(tableApi);
let connection = $().getConnectionById(metaData.mapId, connectionId);
if(connection){
connection.addType('state_process');
}
}
}
let deleteRowElement = $(target).parents('tr');
let row = tableApi.rows(deleteRowElement);
deleteSignatures(tableApi, row);
deleteSignatures(tableApi, row, deleteOptions);
},
onShow: function(e, target){
// hide "deleteConnection" checkbox if no connectionId linked
let tip = target.data('bs.confirmation').tip();
let form = tip.find('form').first();
let connectionId = tableApi.cell(rowIndex, 'connection:name').data();
form.toggleClass('hidden', !connectionId);
}
};
@@ -1867,6 +2158,51 @@ define([
}
};
/**
* draw signature 'info' (preview) table in 'signatureReader' dialog
* @param dialogElement
* @param mapId
* @param systemData
* @returns {jQuery}
*/
let drawSignatureTableInfo = (dialogElement, mapId, systemData) => {
let infoElement = $(dialogElement).find('#' + config.sigInfoId);
let table = $('<table>', {
id: getTableId(mapId, systemData.id, 'info'),
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTableInfoClass].join(' ')
});
infoElement.append(table);
let dataTableOptions = {
tabIndex: -1,
dom: '<"row"<"col-xs-3"l><"col-xs-5 ' + config.tableToolbarStatusClass + '"><"col-xs-4"f>>' +
'<"row"<"col-xs-12"tr>>' +
'<"row"<"col-xs-5"i><"col-xs-7"p>>',
initComplete: function(settings, json){
let tableApi = this.api();
initCharacterInfoTooltip(this, tableApi);
tableApi.columns(['action:name']).visible(false);
Counter.initTableCounter(this, ['created:name', 'updated:name']);
}
};
$.extend(true, dataTableOptions, getSignatureDataTableDefaults(mapId, systemData));
let tableApi = table.DataTable(dataTableOptions);
tableApi.on('draw.dt', function(e, settings){
// xEditable cells should not be editable in this table
$(dialogElement).find('.' + config.sigTableInfoClass).find('td.editable').editable('disable');
});
return tableApi;
};
/**
* draw signature table toolbar (add signature button, scan progress bar
* @param moduleElement
@@ -2020,8 +2356,6 @@ define([
editable.input.$input.first().prop('disabled', true);
// preselect second option
//editable.input.$input.eq(1).prop('checked', true);
//editable.setValue('ad78172b72d0327b237c4a7dc1daa5d7');
// "fake" radio button behaviour
editable.input.$input.attr('name', 'test').attr('type', 'radio');
@@ -2162,6 +2496,30 @@ define([
});
};
/**
* init character info Tooltips
* -> e.g. table cell 'question mark' icon
* @param element
* @param tableApi
*/
let initCharacterInfoTooltip = (element, tableApi) => {
element.hoverIntent({
over: function(e){
let cellElement = $(this);
let rowData = tableApi.row(cellElement.parents('tr')).data();
cellElement.addCharacterInfoTooltip(rowData, {
trigger: 'manual',
placement: 'top',
show: true
});
},
out: function(e){
$(this).destroyPopover();
},
selector: 'td.' + Util.config.helpClass
});
};
/**
* draw empty signature table
* @param moduleElement
@@ -2213,6 +2571,7 @@ define([
initComplete: function(settings, json){
let tableApi = this.api();
initCharacterInfoTooltip(this, tableApi);
initGroupFilterButton(tableApi);
initUndoButton(tableApi);
initSelectAllButton(tableApi);
@@ -2386,13 +2745,13 @@ define([
// event listener for global "paste" signatures into the page -------------------------------------------------
moduleElement.on('pf:updateSystemSignatureModuleByClipboard', {tableApi: primaryTableApi}, function(e, clipboard){
let lazyUpdateToggle = moduleElement.find('.' + config.moduleHeadlineIconLazyClass);
let signatureOptions = {
deleteOld: lazyUpdateToggle.hasClass('active') ? 1 : 0
deleteOld: getLazyUpdateToggleStatus(moduleElement),
deleteConnection: 0
};
// "disable" lazy update icon -> prevents accidental removal for next paste #724
lazyUpdateToggle.toggleClass('active', false);
getLazyUpdateToggleElement(moduleElement).toggleClass('active', false);
updateSignatureTableByClipboard(e.data.tableApi, systemData, clipboard, signatureOptions);
});
@@ -2402,23 +2761,38 @@ define([
moduleElement.find('.' + config.sigTableClass),
'.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
});
/**
* get "lazy delete" toggle element
* @param moduleElement
* @returns {*}
*/
let getLazyUpdateToggleElement = moduleElement => moduleElement.find('.' + config.moduleHeadlineIconLazyClass);
/**
* get status for "lazy delete" toggle
* @param moduleElement
* @returns {number}
*/
let getLazyUpdateToggleStatus = moduleElement => getLazyUpdateToggleElement(moduleElement).hasClass('active') ? 1 : 0;
/**
* update 'counter' UI elements in 'signature reader' dialog
* @param data
*/
let updateSignatureReaderCounters = data => {
let counterElement = $('#' + config.sigInfoCountSigNewId).text(data.added || 0);
counterElement.toggleClass(counterElement.attr('data-class'), Boolean(data.added));
counterElement = $('#' + config.sigInfoCountSigChangeId).text(data.changed || 0);
counterElement.toggleClass(counterElement.attr('data-class'), Boolean(data.changed));
counterElement = $('#' + config.sigInfoCountSigDeleteId).text(data.deleted || 0);
counterElement.toggleClass(counterElement.attr('data-class'), Boolean(data.deleted));
counterElement = $('#' + config.sigInfoCountConDeleteId).text(data.deleteCon || 0);
counterElement.toggleClass(counterElement.attr('data-class'), Boolean(data.deleteCon));
};
/**
@@ -2435,6 +2809,181 @@ define([
return row;
};
/**
* @param action
* @param rowId
* @returns {Promise<unknown>}
*/
let getPromiseForRow = (action, rowId) => {
return new Promise((resolve, reject) => {
resolve({action: action, rowId: rowId});
});
};
/**
* callback for a changed row
* @param rowIndex
* @param colIndex
* @param tableLoopCount
* @param cellLoopCount
* @param options CUSTOM parameter (not DataTables specific)!
*/
let rowUpdate = function(rowIndex, colIndex, tableLoopCount, cellLoopCount, options){
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', Util.getObjVal(options, 'keepVisible') || false);
}
}else if(node.hasClass(config.tableCellCounterClass)){
// "updated" timestamp always changed
node.pulseBackgroundColor('changed', Util.getObjVal(options, 'keepVisible') || false);
}
};
/**
* update 'info' (preview) signature table (inside 'signature reader' dialog)
* @param tableApi
* @param signaturesDataOrig
* @param deleteOutdatedSignatures
* @param deleteConnections
*/
let updateSignatureInfoTable = (tableApi, signaturesDataOrig, deleteOutdatedSignatures = false, deleteConnections = false) => {
// clone signature array because of further manipulation
let signaturesData = $.extend([], signaturesDataOrig);
let rowIdsExist = [];
let promisesAdded = [];
let promisesChanged = [];
let promisesDeleted = [];
let allRows = tableApi.rows();
let rowUpdateCallback = function(){
rowUpdate.apply(this, [...arguments, {keepVisible: true}]);
};
// update rows ------------------------------------------------------------------------------------------------
allRows.every(function(rowIdx, tableLoop, rowLoop){
let row = this;
let rowData = row.data();
for(let i = 0; i < signaturesData.length; i++){
if(signaturesData[i].name === rowData.name){
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
let newRowData = signaturesData[i];
// keep "description" must not be replaced
newRowData.description = rowData.description;
// existing "groupId" must not be removed
if(!newRowData.groupId){
newRowData.groupId = rowData.groupId;
newRowData.typeId = rowData.typeId;
}else if(newRowData.groupId === rowData.groupId){
if(!newRowData.typeId){
newRowData.typeId = rowData.typeId;
}
}
// "created" timestamp will not change -> use existing
newRowData.created = rowData.created;
row.data(newRowData);
// 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(rowUpdateCallback);
promisesChanged.push(getPromiseForRow('changed', rowId));
}
rowIdsExist.push(rowIdx);
// remove signature data -> all left signatures will be added
signaturesData.splice(i, 1);
i--;
}
}
});
// delete rows ------------------------------------------------------------------------------------------------
if(deleteOutdatedSignatures){
let rows = tableApi.rows((rowIdx, rowData, node) => !rowIdsExist.includes(rowIdx));
rows.every(function(rowIdx, tableLoop, rowLoop){
let row = this;
let rowId = row.id(true);
let rowElement = row.nodes().to$();
let rowData = row.data();
rowElement.pulseBackgroundColor('deleted', true);
promisesChanged.push(getPromiseForRow('deleted', rowId));
// check if there is a connectionId.
if(deleteConnections && Util.getObjVal(rowData, 'connection.id')){
promisesChanged.push(getPromiseForRow('deleteCon', rowId));
}
});
}
// add rows ---------------------------------------------------------------------------------------------------
for(let signatureData of signaturesData){
let row = addSignatureRow(tableApi, signatureData);
let rowElement = row.nodes().to$();
rowElement.pulseBackgroundColor('added', true);
promisesAdded.push(getPromiseForRow('added', row.index()));
}
// done -------------------------------------------------------------------------------------------------------
Promise.all(promisesAdded.concat(promisesChanged, promisesDeleted)).then(payloads => {
if(payloads.length){
// table data changed -> draw() table changes
tableApi.draw();
// no notifications if table was empty just progressbar notification is needed
// sum payloads by "action"
let notificationCounter = payloads.reduce((acc, payload) => {
acc[payload.action]++;
return acc;
}, Object.assign({}, emptySignatureReaderCounterData));
updateSignatureReaderCounters(notificationCounter);
updateScannedSignaturesBar(tableApi, {showNotice: false});
}
});
};
/**
* update signature table with new signatures
* -> add/update/delete rows
@@ -2463,51 +3012,6 @@ define([
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');
}
};
let getPromiseForRow = (action, rowId) => {
return new Promise((resolve, reject) => {
resolve({action: action, rowId: rowId});
});
};
// update signatures ------------------------------------------------------------------------------------------
allRows.every(function(rowIdx, tableLoop, rowLoop){
let row = this;
@@ -2588,7 +3092,7 @@ define([
}
acc[payload.action]++;
return acc;
}, {});
}, Object.assign({}, emptySignatureReaderCounterData));
let notification = '';
if(notificationCounter.added > 0){
@@ -2606,6 +3110,13 @@ define([
}
updateScannedSignaturesBar(tableApi, {showNotice: true});
// at this point the 'primary' signature table update is done
// we need to check if there is an open 'signature reader' dialog,
// that needs to update its 'preview' signature table
// -> to use DataTables "drawCallback" option or "draw.dt" event is not the *best* option:
// Both are called to frequently (e.g. after filter/sort actions)
$('.' + config.sigReaderDialogClass + '.in').trigger('pf:updateSignatureReaderDialog');
}
// unlock table

View File

@@ -75,13 +75,11 @@ define([
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad',
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
animationPulseWarningClass: 'pf-animation-pulse-warning', // animation class
animationPulseDangerClass: 'pf-animation-pulse-danger', // animation class
animationPulseClassPrefix: 'pf-animation-pulse-', // class prefix for "pulse" background animation
// popover
popoverTriggerClass: 'pf-popover-trigger', // class for "popover" trigger elements
popoverSmallClass: 'pf-popover-small', // class for small "popover"
popoverSmallClass: 'popover-small', // class for small "popover"
popoverCharacterClass: 'pf-popover-character', // class for character "popover"
// Summernote
@@ -765,28 +763,30 @@ define([
/**
* highlight jquery elements
* add/remove css class for keyframe animation
* @returns {any|JQuery|*}
* @param status
* @param keepVisible
* @param clear
* @returns {void|*|undefined}
*/
$.fn.pulseBackgroundColor = function(status, clear){
let animationClass = '';
$.fn.pulseBackgroundColor = function(status, keepVisible = false, clear = false){
let animationClass = config.animationPulseClassPrefix;
switch(status){
case 'added':
animationClass = config.animationPulseSuccessClass;
break;
case 'changed':
animationClass = config.animationPulseWarningClass;
break;
case 'deleted':
animationClass = config.animationPulseDangerClass;
break;
case 'added': animationClass += 'success'; break;
case 'changed': animationClass += 'warning'; break;
case 'deleted': animationClass += 'danger'; break;
default: console.warn('Invalid status: %s', status);
}
// if keepVisible -> background color animation class will not be deleted
if(keepVisible){
animationClass += '-keep';
}
let clearTimer = element => {
element.removeClass( animationClass );
element.removeClass(animationClass);
let currentTimer = element.data('animationTimer');
if( animationTimerCache.hasOwnProperty(currentTimer) ){
if(animationTimerCache.hasOwnProperty(currentTimer)){
clearTimeout( currentTimer );
delete animationTimerCache[currentTimer];
element.removeData('animationTimer');
@@ -796,18 +796,20 @@ define([
return this.each(function(){
let element = $(this);
if( element.hasClass(animationClass) ){
if(element.hasClass(animationClass)){
// clear timer -> set new timer
clearTimer(element);
}
if(clear !== true){
if(!clear){
element.addClass(animationClass);
let timer = setTimeout(clearTimer, 1500, element);
element.data('animationTimer', timer);
animationTimerCache[timer] = true;
// remove class after animation finish, if not 'keepVisible'
if(!keepVisible){
let timer = setTimeout(clearTimer, 1500, element);
element.data('animationTimer', timer);
animationTimerCache[timer] = true;
}
}
});
};
@@ -971,6 +973,14 @@ define([
});
};
/**
* capitalize first letter
* @returns {string}
*/
String.prototype.capitalize = function(){
return this.charAt(0).toUpperCase() + this.slice(1);
};
/**
* get hash from string
* @returns {number}
@@ -1158,6 +1168,52 @@ define([
}
};
/**
* get template for Bootstrap "Confirmation" popover plugin
* -> if HTML 'content' not set, we expect the default template
* https://www.npmjs.com/package/bs-confirmation
* -> options.size for "small" popover layout
* -> options.noTitle for hide title element
* @param content
* @param options
* @returns {string}
*/
let getConfirmationTemplate = (content, options) => {
let getButtons = () => {
let buttonHtml = '<div class="btn-group">';
buttonHtml += '<a data-apply="confirmation">Yes</a>';
buttonHtml += '<a data-dismiss="confirmation">No</a>';
buttonHtml += '</div>';
return buttonHtml;
};
let getContent = content => {
let contentHtml = content ? content : '';
contentHtml += '<div class="popover-footer">';
contentHtml += getButtons();
contentHtml += '</div>';
return contentHtml;
};
let popoverClass = ['popover'];
if('small' === getObjVal(options, 'size')){
popoverClass.push('popover-small');
}
let contentClass = ['popover-content', 'no-padding'];
let html = '<div class="' + popoverClass.join(' ') + '">';
html += '<div class="arrow"></div>';
if(true !== getObjVal(options, 'noTitle')){
html += '<h3 class="popover-title"></h3>';
}
html += '<div class="' + contentClass.join(' ') + '">';
html += getContent(content);
html += '</div>';
html += '</div>';
return html;
};
/**
* convert XEditable Select <option> data into Select2 data format
* -> "prepend" (empty) options get added, too
@@ -1239,7 +1295,7 @@ define([
};
/**
* set default configuration for "Bootbox"
* set default configuration for "Bootbox" plugin
*/
let initDefaultBootboxConfig = () => {
bootbox.setDefaults({
@@ -1248,7 +1304,22 @@ define([
};
/**
* set default configuration for "Select2"
* set default configuration for "Confirmation" popover plugin
*/
let initDefaultConfirmationConfig = () => {
$.fn.confirmation.Constructor.DEFAULTS.placement = 'left';
$.fn.confirmation.Constructor.DEFAULTS.container = 'body';
$.fn.confirmation.Constructor.DEFAULTS.btnCancelClass = 'btn btn-sm btn-default';
$.fn.confirmation.Constructor.DEFAULTS.btnCancelLabel = 'cancel';
$.fn.confirmation.Constructor.DEFAULTS.btnCancelIcon = 'fas fa-fw fa-ban';
$.fn.confirmation.Constructor.DEFAULTS.btnOkClass = 'btn btn-sm btn-danger';
$.fn.confirmation.Constructor.DEFAULTS.btnOkLabel = 'delete';
$.fn.confirmation.Constructor.DEFAULTS.btnOkIcon = 'fas fa-fw fa-times';
$.fn.confirmation.Constructor.DEFAULTS.template = getConfirmationTemplate();
};
/**
* set default configuration for "Select2" plugin
*/
let initDefaultSelect2Config = () => {
$.fn.select2.defaults.set('theme', 'pathfinder');
@@ -1372,7 +1443,7 @@ define([
};
/**
* set default configuration for "xEditable"
* set default configuration for "xEditable" plugin
*/
let initDefaultEditableConfig = () => {
// use fontAwesome buttons template
@@ -1462,7 +1533,7 @@ define([
// Server is running with GMT/UTC (EVE Time)
let localDate = new Date();
let serverDate= new Date(
let serverDate = new Date(
localDate.getUTCFullYear(),
localDate.getUTCMonth(),
localDate.getUTCDate(),
@@ -1766,8 +1837,8 @@ define([
* Request data from Server
* -> This function should be used (in future) for all Ajax and REST API calls
* -> works as a "wrapper" for jQueries ajax() method
* @param action
* @param entity
* @param {String} action
* @param {String} entity
* @param ids
* @param data
* @param context
@@ -1779,7 +1850,7 @@ define([
let requestExecutor = (resolve, reject) => {
let payload = {
action: 'request',
name: action.toLowerCase() + entity.charAt(0).toUpperCase() + entity.slice(1)
name: action.toLowerCase() + entity.capitalize()
};
// build request url --------------------------------------------------------------------------------------
@@ -2206,7 +2277,7 @@ define([
let typeClass = '';
let matches = regex.exec(typeName.toLowerCase());
if(matches && matches[1]){
typeName = matches[1].charAt(0).toUpperCase() + matches[1].slice(1);
typeName = matches[1].capitalize();
typeClass = getPlanetInfo(matches[1]);
}
@@ -3356,6 +3427,7 @@ define([
showVersionInfo: showVersionInfo,
initPrototypes: initPrototypes,
initDefaultBootboxConfig: initDefaultBootboxConfig,
initDefaultConfirmationConfig: initDefaultConfirmationConfig,
initDefaultSelect2Config: initDefaultSelect2Config,
initDefaultEditableConfig: initDefaultEditableConfig,
getCurrentTriggerDelay: getCurrentTriggerDelay,
@@ -3410,6 +3482,7 @@ define([
getCurrentCharacterLog: getCurrentCharacterLog,
findInViewport: findInViewport,
initScrollSpy: initScrollSpy,
getConfirmationTemplate: getConfirmationTemplate,
convertXEditableOptionsToSelect2: convertXEditableOptionsToSelect2,
flattenXEditableSelectArray: flattenXEditableSelectArray,
getCharacterDataBySystemId: getCharacterDataBySystemId,

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,9 @@
/*!
* hoverIntent v1.9.0 // 2017.09.01 // jQuery v1.7.0+
* hoverIntent v1.10.0 // 2019.02.25 // jQuery v1.7.0+
* http://briancherne.github.io/jquery-hoverIntent/
*
* You may use hoverIntent under the terms of the MIT license. Basically that
* means you are free to use hoverIntent as long as this header is left intact.
* Copyright 2007-2017 Brian Cherne
* Copyright 2007-2019 Brian Cherne
*/
!function(factory){"use strict";"function"==typeof define&&define.amd?define(["jquery"],factory):jQuery&&!jQuery.fn.hoverIntent&&factory(jQuery)}(function($){"use strict";var cX,cY,_cfg={interval:100,sensitivity:6,timeout:0},INSTANCE_COUNT=0,track=function(ev){cX=ev.pageX,cY=ev.pageY},compare=function(ev,$el,s,cfg){if(Math.sqrt((s.pX-cX)*(s.pX-cX)+(s.pY-cY)*(s.pY-cY))<cfg.sensitivity)return $el.off(s.event,track),delete s.timeoutId,s.isActive=!0,ev.pageX=cX,ev.pageY=cY,delete s.pX,delete s.pY,cfg.over.apply($el[0],[ev]);s.pX=cX,s.pY=cY,s.timeoutId=setTimeout(function(){compare(ev,$el,s,cfg)},cfg.interval)},delay=function(ev,$el,s,out){return delete $el.data("hoverIntent")[s.id],out.apply($el[0],[ev])};$.fn.hoverIntent=function(handlerIn,handlerOut,selector){var instanceId=INSTANCE_COUNT++,cfg=$.extend({},_cfg);$.isPlainObject(handlerIn)?(cfg=$.extend(cfg,handlerIn),$.isFunction(cfg.out)||(cfg.out=cfg.over)):cfg=$.isFunction(handlerOut)?$.extend(cfg,{over:handlerIn,out:handlerOut,selector:selector}):$.extend(cfg,{over:handlerIn,out:handlerIn,selector:handlerOut});var handleHover=function(e){var ev=$.extend({},e),$el=$(this),hoverIntentData=$el.data("hoverIntent");hoverIntentData||$el.data("hoverIntent",hoverIntentData={});var state=hoverIntentData[instanceId];state||(hoverIntentData[instanceId]=state={id:instanceId}),state.timeoutId&&(state.timeoutId=clearTimeout(state.timeoutId));var mousemove=state.event="mousemove.hoverIntent.hoverIntent"+instanceId;if("mouseenter"===e.type){if(state.isActive)return;state.pX=ev.pageX,state.pY=ev.pageY,$el.off(mousemove,track).on(mousemove,track),state.timeoutId=setTimeout(function(){compare(ev,$el,state,cfg)},cfg.interval)}else{if(!state.isActive)return;$el.off(mousemove,track),state.timeoutId=setTimeout(function(){delay(ev,$el,state,cfg.out)},cfg.timeout)}};return this.on({"mouseenter.hoverIntent":handleHover,"mouseleave.hoverIntent":handleHover},cfg.selector)}});
!function(factory){"use strict";"function"==typeof define&&define.amd?define(["jquery"],factory):"object"==typeof module&&module.exports?module.exports=factory(require("jquery")):jQuery&&!jQuery.fn.hoverIntent&&factory(jQuery)}(function($){"use strict";var cX,cY,_cfg={interval:100,sensitivity:6,timeout:0},INSTANCE_COUNT=0,track=function(ev){cX=ev.pageX,cY=ev.pageY},compare=function(ev,$el,s,cfg){if(Math.sqrt((s.pX-cX)*(s.pX-cX)+(s.pY-cY)*(s.pY-cY))<cfg.sensitivity)return $el.off(s.event,track),delete s.timeoutId,s.isActive=!0,ev.pageX=cX,ev.pageY=cY,delete s.pX,delete s.pY,cfg.over.apply($el[0],[ev]);s.pX=cX,s.pY=cY,s.timeoutId=setTimeout(function(){compare(ev,$el,s,cfg)},cfg.interval)};$.fn.hoverIntent=function(handlerIn,handlerOut,selector){var instanceId=INSTANCE_COUNT++,cfg=$.extend({},_cfg);$.isPlainObject(handlerIn)?(cfg=$.extend(cfg,handlerIn),$.isFunction(cfg.out)||(cfg.out=cfg.over)):cfg=$.isFunction(handlerOut)?$.extend(cfg,{over:handlerIn,out:handlerOut,selector:selector}):$.extend(cfg,{over:handlerIn,out:handlerIn,selector:handlerOut});var handleHover=function(e){var ev=$.extend({},e),$el=$(this),hoverIntentData=$el.data("hoverIntent");hoverIntentData||$el.data("hoverIntent",hoverIntentData={});var state=hoverIntentData[instanceId];state||(hoverIntentData[instanceId]=state={id:instanceId}),state.timeoutId&&(state.timeoutId=clearTimeout(state.timeoutId));var mousemove=state.event="mousemove.hoverIntent.hoverIntent"+instanceId;if("mouseenter"===e.type){if(state.isActive)return;state.pX=ev.pageX,state.pY=ev.pageY,$el.off(mousemove,track).on(mousemove,track),state.timeoutId=setTimeout(function(){compare(ev,$el,state,cfg)},cfg.interval)}else{if(!state.isActive)return;$el.off(mousemove,track),state.timeoutId=setTimeout(function(){!function(ev,$el,s,out){delete $el.data("hoverIntent")[s.id],out.apply($el[0],[ev])}(ev,$el,state,cfg.out)},cfg.timeout)}};return this.on({"mouseenter.hoverIntent":handleHover,"mouseleave.hoverIntent":handleHover},cfg.selector)}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -42,11 +42,11 @@ requirejs.config({
xEditable: 'lib/bootstrap-editable.min', // v1.5.1 X-editable - in placed editing
morris: 'lib/morris.min', // v0.5.1 Morris.js - graphs and charts
raphael: 'lib/raphael.min', // v2.2.8 Raphaël - required for morris - https://dmitrybaranovskiy.github.io/raphael
bootbox: 'lib/bootbox.min', // v4.4.0 Bootbox.js - custom dialogs - http://bootboxjs.com
bootbox: 'lib/bootbox.min', // v5.2.0 Bootbox.js - custom dialogs - http://bootboxjs.com
easyPieChart: 'lib/jquery.easypiechart.min', // v2.1.6 Easy Pie Chart - HTML 5 pie charts - http://rendro.github.io/easy-pie-chart
peityInlineChart: 'lib/jquery.peity.min', // v3.2.1 Inline Chart - http://benpickles.github.io/peity/
dragToSelect: 'lib/jquery.dragToSelect', // v1.1 Drag to Select - http://andreaslagerkvist.com/jquery/drag-to-select
hoverIntent: 'lib/jquery.hoverIntent.min', // v1.9.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
hoverIntent: 'lib/jquery.hoverIntent.min', // v1.10.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
select2: 'lib/select2.min', // v4.0.3 Drop Down customization - https://select2.github.io
validator: 'lib/validator.min', // v0.10.1 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
lazylinepainter: 'lib/jquery.lazylinepainter-1.5.1.min', // v1.5.1 SVG line animation plugin - http://lazylinepainter.info

View File

@@ -45,6 +45,7 @@ define([
table.destroyTimestampCounter(true);
});
// Status Plugin ==============================================================================================
let StatusTable = function(settings){
let me = this;
me.statusContainer = $('<div>', {

View File

@@ -141,15 +141,14 @@ define([
// show Cookie accept hint on SSO login button
let confirmationSettings = {
container: 'body',
placement: 'bottom',
btnOkClass: 'btn btn-sm btn-default',
btnOkLabel: 'dismiss',
btnOkIcon: 'fas fa-fw fa-sign-in-alt',
title: 'Accept cookies',
btnCancelClass: 'btn btn-sm btn-success',
btnCancelLabel: 'accept',
btnCancelIcon: 'fas fa-fw fa-check',
btnOkClass: 'btn btn-sm btn-default',
btnOkLabel: 'dismiss',
btnOkIcon: 'fas fa-fw fa-sign-in-alt',
onCancel: function(e, target){
// "Accept cookies"
setAcceptCookie();

View File

@@ -31,6 +31,9 @@ define([
// set default dialog config
Util.initDefaultBootboxConfig();
// set default confirmation popover config
Util.initDefaultConfirmationConfig();
// set default select2 config
Util.initDefaultSelect2Config();

View File

@@ -853,6 +853,10 @@ define([
let modalElement = $(e.target);
modalElement.destroyTimestampCounter(true);
// destroy all form validators
// -> does not work properly. validation functions still used (js error) after 'destroy'
//modalElement.find('form').filter((i, form) => $(form).data('bs.validator')).validator('destroy');
// destroy all popovers
modalElement.find('.' + Util.config.popoverTriggerClass).popover('destroy');

View File

@@ -77,11 +77,9 @@ define([
// show "discard" changes confirmation
let confirmationSettings = {
container: 'body',
placement: 'top',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
title: 'discard changes',
btnCancelIcon: '',
btnOkClass: 'btn btn-sm btn-warning',
btnOkLabel: 'discard',
btnOkIcon: 'fas fa-fw fa-ban',

View File

@@ -56,18 +56,6 @@ define([
}
};
// confirmation dialog settings (e.g. delete row)
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times'
};
/**
* get icon that marks a table cell as clickable
* @returns {string}
@@ -456,44 +444,49 @@ define([
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tempTableElement = this;
let tempConfirmationSettings = confirmationSettings;
tempConfirmationSettings.title = 'Delete system';
tempConfirmationSettings.onConfirm = function(e, target){
let deleteRowElement = $(target).parents('tr');
let confirmationSettings = {
placement: 'left',
title: 'Delete system',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm: function(e, target){
let deleteRowElement = $(target).parents('tr');
let activeMap = Util.getMapModule().getActiveMap();
let systemElement = $('#' + MapUtil.getSystemId(mapData.config.id, rowData.id) );
let activeMap = Util.getMapModule().getActiveMap();
let systemElement = $('#' + MapUtil.getSystemId(mapData.config.id, rowData.id) );
if(systemElement.length){
// trigger system delete event
activeMap.trigger('pf:deleteSystems', [{
systems: [systemElement[0]],
callback: function(deletedSystems){
// callback function after ajax "delete" success
// check if system was deleted
if(deletedSystems.length === 1){
// remove table row
tempTableElement.DataTable().rows(deleteRowElement).remove().draw();
if(systemElement.length){
// trigger system delete event
activeMap.trigger('pf:deleteSystems', [{
systems: [systemElement[0]],
callback: function(deletedSystems){
// callback function after ajax "delete" success
// check if system was deleted
if(deletedSystems.length === 1){
// remove table row
tempTableElement.DataTable().rows(deleteRowElement).remove().draw();
Util.showNotify({title: 'System deleted', text: rowData.name, type: 'success'});
Util.showNotify({title: 'System deleted', text: rowData.name, type: 'success'});
// refresh connection table (connections might have changed) --------------
let connectionsElement = $('#' + config.mapInfoConnectionsId);
let mapDataNew = activeMap.getMapDataFromClient(['hasId']);
// refresh connection table (connections might have changed) --------------
let connectionsElement = $('#' + config.mapInfoConnectionsId);
let mapDataNew = activeMap.getMapDataFromClient(['hasId']);
connectionsElement.initConnectionInfoTable(mapDataNew);
}else{
// error
Util.showNotify({title: 'Failed to delete system', text: rowData.name, type: 'error'});
connectionsElement.initConnectionInfoTable(mapDataNew);
}else{
// error
Util.showNotify({title: 'Failed to delete system', text: rowData.name, type: 'error'});
}
}
}
}]);
}]);
}
}
};
// init confirmation dialog
$(cell).confirmation(tempConfirmationSettings);
$(cell).confirmation(confirmationSettings);
}
}
],
@@ -652,23 +645,29 @@ define([
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tempTableElement = this;
let tempConfirmationSettings = confirmationSettings;
tempConfirmationSettings.title = 'Delete connection';
tempConfirmationSettings.onConfirm = function(e, target){
let deleteRowElement = $(target).parents('tr');
let confirmationSettings = {
placement: 'left',
title: 'Delete connection',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm: function(e, target){
let deleteRowElement = $(target).parents('tr');
// deleteSignatures(row);
let connection = $().getConnectionById(mapData.config.id, rowData.id);
// deleteSignatures(row);
let connection = $().getConnectionById(mapData.config.id, rowData.id);
MapUtil.deleteConnections([connection], () => {
// callback function after ajax "delete" success
// remove table row
tempTableElement.DataTable().rows(deleteRowElement).remove().draw();
});
MapUtil.deleteConnections([connection], () => {
// callback function after ajax "delete" success
// remove table row
tempTableElement.DataTable().rows(deleteRowElement).remove().draw();
});
}
};
// init confirmation dialog
$(cell).confirmation(tempConfirmationSettings);
$(cell).confirmation(confirmationSettings);
}
}
],

View File

@@ -189,7 +189,7 @@ define([
html: '<i class="fas fa-fw fa-question-circle"></i>'
}),
$('<td>', {
text: scopeLabel.charAt(0).toUpperCase() + scopeLabel.slice(1)
text: scopeLabel.capitalize()
}),
$('<td>', {
class: ['text-right', config.connectionInfoTableCellConnectionClass].join(' ')
@@ -883,15 +883,11 @@ define([
if(rowData.active){
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete jump log',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm : function(e, target){
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state

View File

@@ -569,15 +569,11 @@ define([
$(cell).find('i').tooltip();
}else{
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete structure',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm : function(e, target){
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state

View File

@@ -88,6 +88,12 @@ define([
return cachedData;
};
/**
* @param mapIds
* @param sourceName
* @param targetName
* @returns {string}
*/
let getRouteDataCacheKey = (mapIds, sourceName, targetName) => {
return [mapIds.join('_'), sourceName.toLowerCase(), targetName.toLowerCase()].join('###');
};
@@ -245,8 +251,7 @@ define([
* show route dialog. User can search for systems and jump-info for each system is added to a data table
* @param dialogData
*/
let showFindRouteDialog = (dialogData) => {
let showFindRouteDialog = dialogData => {
let mapSelectOptions = [];
let currentMapData = Util.getCurrentMapData();
if(currentMapData !== false){
@@ -548,12 +553,12 @@ define([
* set event observer for route finder dialog
* @param routeDialog
*/
let setDialogObserver = (routeDialog) => {
let wormholeCheckbox = routeDialog.find('input[type="checkbox"][name="wormholes"]');
let wormholeReducedCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesReduced"]');
let wormholeCriticalCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesCritical"]');
let wormholeEolCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesEOL"]');
let wormholeSizeSelect = routeDialog.find('#' + config.routeDialogSizeSelectId);
let setDialogObserver = routeDialog => {
let wormholeCheckbox = routeDialog.find('input[type="checkbox"][name="wormholes"]');
let wormholeReducedCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesReduced"]');
let wormholeCriticalCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesCritical"]');
let wormholeEolCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesEOL"]');
let wormholeSizeSelect = routeDialog.find('#' + config.routeDialogSizeSelectId);
// store current "checked" state for each box ---------------------------------------------
let storeCheckboxStatus = function(){
@@ -1127,15 +1132,11 @@ define([
let tempTableElement = this;
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete route',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
template: Util.getConfirmationTemplate(null, {
size: 'small',
noTitle: true
}),
onConfirm : function(e, target){
let deleteRowElement = $(cell).parents('tr');
tempTableElement.api().rows(deleteRowElement).remove().draw();

View File

@@ -31,10 +31,14 @@ define([
moduleHeadlineIconLazyClass: 'pf-module-icon-button-lazy', // class for "lazy delete" toggle icon
moduleHeadlineProgressBarClass: 'pf-system-progress-scanned', // class for signature progress bar
// fonts
fontUppercaseClass: 'pf-font-uppercase', // class for "uppercase" font
// tables
tableToolsActionClass: 'pf-table-tools-action', // class for "new signature" table (hidden)
// table toolbar
tableToolbarStatusClass: 'pf-table-toolbar-status', // class for "status" DataTable Toolbar
sigTableClearButtonClass: 'pf-sig-table-clear-button', // class for "clear" signatures button
// signature table
@@ -42,6 +46,7 @@ define([
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
sigTableInfoClass: 'pf-sig-table-info', // class for info sig table
sigTableRowIdPrefix: 'pf-sig-row_', // id prefix for table rows
sigTableEditSigNameInput: 'pf-sig-table-edit-name-input', // class for editable fields (sig name)
@@ -52,12 +57,23 @@ define([
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')
signatureGroupsNames: Util.getSignatureGroupOptions('name'),
// signature reader dialog
sigReaderDialogClass: 'pf-sig-reader-dialog', // class for "signature reader" dialog
sigInfoId: 'pf-sig-info', // id for "signature info" table area
sigInfoTextareaId: 'pf-sig-info-textarea', // id for signature reader "textarea"
sigReaderLazyUpdateId: 'pf-sig-reader-lazy-update', // id for "lazy update" checkbox
sigReaderConnectionDeleteId: 'pf-sig-reader-delete-connection', // id for "delete connection" checkbox
sigInfoCountSigNewId: 'pf-sig-info-count-sig-new', // id for "signature new" counter
sigInfoCountSigChangeId: 'pf-sig-info-count-sig-change', // id for "signature change" counter
sigInfoCountSigDeleteId: 'pf-sig-info-count-sig-delete', // id for "signature delete" counter
sigInfoCountConDeleteId: 'pf-sig-info-count-con-delete' // id for "connection delete" counter
};
let sigNameCache = {}; // cache signature names
@@ -84,6 +100,13 @@ define([
typeId: 0
};
let emptySignatureReaderCounterData = {
added: 0,
changed: 0,
deleted: 0,
deleteCon: 0
};
let editableDefaults = { // xEditable default options for signature fields
url: function(params){
let saveExecutor = (resolve, reject) => {
@@ -518,9 +541,9 @@ define([
*/
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 parentElement = $(tableElement).parents('.' + config.moduleTypeClass + ', .' + config.sigReaderDialogClass);
let progressBar = parentElement.find('.progress-bar');
let progressBarLabel = parentElement.find('.progress-label-right');
let percent = 0;
let progressBarType = '';
@@ -557,17 +580,168 @@ define([
}
};
/**
* load existing (current) signature data into info table (preview)
* @param infoTableApi
* @param mapId
* @param systemId
* @param draw
*/
let initTableDataWithCurrentSignatureData = (infoTableApi, mapId, systemId, draw = false) => {
// reset/clear infoTable
infoTableApi.clear();
let primaryTableApi = getDataTableInstance(mapId, systemId, 'primary');
if(primaryTableApi){
infoTableApi.rows.add(primaryTableApi.data().toArray());
if(draw){
infoTableApi.draw();
}
}else{
console.warn('Signature table not found. mapId: %d; systemId: %d', mapId, systemId);
}
};
/**
* set "signature reader" dialog observer
* @param dialogElement
* @param mapId
* @param systemData
*/
let setSignatureReaderDialogObserver = (dialogElement, mapId, systemData) => {
let systemId = systemData.id;
let form = dialogElement.find('form').first();
let textarea = form.find('#' + config.sigInfoTextareaId);
let deleteOutdatedCheckbox = form.find('#' + config.sigReaderLazyUpdateId);
let deleteConnectionsCheckbox = form.find('#' + config.sigReaderConnectionDeleteId);
let errorClipboardValidation = 'No signatures found in scan result';
let tableStatusElement = dialogElement.find('.' + config.tableToolbarStatusClass);
form.initFormValidation({
delay: 0,
feedback: {
success: 'fa-check',
error: 'fa-times'
},
custom: {
clipboard: function(textarea){
let signatureData = parseSignatureString(systemData, textarea.val());
tableStatusElement.text(signatureData.length + ' signatures parsed');
if(signatureData.length === 0){
return errorClipboardValidation;
}
}
}
});
let updatePreviewSection = (mapId, systemId, formData) => {
let infoTableApi = getDataTableInstance(mapId, systemId, 'info');
if(infoTableApi){
// init 'infoTable' with existing signature rows
// infoTableApi.draw() not necessary at this point!
initTableDataWithCurrentSignatureData(infoTableApi, mapId, systemId);
let signatureData = parseSignatureString(systemData, formData.clipboard);
if(signatureData.length > 0){
// valid signature data parsed
// -> add some default data (e.g. currentCharacter data) to parsed signatureData
// -> not required, just for filling up some more columns
signatureData = enrichParsedSignatureData(signatureData);
updateSignatureInfoTable(infoTableApi, signatureData, Boolean(formData.deleteOld), Boolean(formData.deleteConnection));
}else{
// no signatures pasted -> draw current signature rows
infoTableApi.draw();
// reset counter elements
updateSignatureReaderCounters(emptySignatureReaderCounterData);
updateScannedSignaturesBar(infoTableApi, {showNotice: false});
console.info(errorClipboardValidation);
}
}else{
console.warn('Signature "preview" table not found. mapId: %d; systemId: %d', mapId, systemId);
}
};
// changes in 'scan result' textarea -> update preview table --------------------------------------------------
let oldValue = '';
textarea.on('change keyup paste', function(){
let formData = form.getFormValues();
let currentValue = formData.clipboard;
if(currentValue === oldValue){
return; //check to prevent multiple simultaneous triggers
}
oldValue = currentValue;
updatePreviewSection(mapId, systemId, formData);
});
textarea.on('focus', function(e){
this.select();
});
// en/disable 'lazy update' toggles dependent checkbox --------------------------------------------------------
let onDeleteOutdatedCheckboxChange = function(){
deleteConnectionsCheckbox.prop('disabled', !this.checked);
deleteConnectionsCheckbox.prop('checked', false);
}.bind(deleteOutdatedCheckbox[0]);
deleteOutdatedCheckbox.on('change', onDeleteOutdatedCheckboxChange);
onDeleteOutdatedCheckboxChange();
// en/disable checkboxes -> update preview table --------------------------------------------------------------
deleteOutdatedCheckbox.add(deleteConnectionsCheckbox).on('change', function(){
let formData = form.getFormValues();
if(formData.clipboard.length){
updatePreviewSection(mapId, systemId, form.getFormValues());
}
});
// listen 'primary' sig table updates -> update 'preview' sig table in the dialog -----------------------------
dialogElement.on('pf:updateSignatureReaderDialog', function(e){
updatePreviewSection(mapId, systemId, form.getFormValues());
});
};
/**
* open "signature reader" dialog for signature table
* @param systemData
*/
$.fn.showSignatureReaderDialog = function(systemData){
let moduleElement = $(this);
let mapId = moduleElement.data('mapId');
let systemId = moduleElement.data('systemId');
requirejs([
'text!templates/dialog/signature_reader.html',
'text!templates/form/progress.html',
'mustache'
], (TplDialog, TplProgress, Mustache) => {
let data = {
sigInfoId: config.sigInfoId,
sigReaderLazyUpdateId: config.sigReaderLazyUpdateId,
sigReaderConnectionDeleteId: config.sigReaderConnectionDeleteId,
sigInfoTextareaId: config.sigInfoTextareaId,
sigInfoLazyUpdateStatus: getLazyUpdateToggleStatus(moduleElement),
sigInfoCountSigNewId: config.sigInfoCountSigNewId,
sigInfoCountSigChangeId: config.sigInfoCountSigChangeId,
sigInfoCountSigDeleteId: config.sigInfoCountSigDeleteId,
sigInfoCountConDeleteId: config.sigInfoCountConDeleteId,
sigInfoProgressElement : Mustache.render(TplProgress, {
label: true,
wrapperClass: config.moduleHeadlineProgressBarClass,
class: ['progress-bar-success'].join(' '),
percent: 0
})
};
requirejs(['text!templates/dialog/signature_reader.html', 'mustache'], (template, Mustache) => {
let signatureReaderDialog = bootbox.dialog({
className: config.sigReaderDialogClass,
title: 'Signature reader',
message: Mustache.render(template, {}),
size: 'large',
message: Mustache.render(TplDialog, data),
show: false,
buttons: {
close: {
label: 'cancel',
@@ -578,21 +752,42 @@ define([
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');
// validate form
form.validator('validate');
updateSignatureTableByClipboard(tableApi, systemData, formData.clipboard, signatureOptions);
// check whether the form is valid
if(form.isValidForm()){
// get form data
let formData = form.getFormValues();
let signatureOptions = {
deleteOld: (formData.deleteOld) ? 1 : 0,
deleteConnection: (formData.deleteConnection) ? 1 : 0
};
let tableApi = getDataTableInstance(mapId, systemId, 'primary');
updateSignatureTableByClipboard(tableApi, systemData, formData.clipboard, signatureOptions);
}else{
return false;
}
}
}
}
});
signatureReaderDialog.on('show.bs.modal', function(e) {
let dialogElement = $(this);
let infoTableApi = drawSignatureTableInfo(this, mapId, systemData);
// init 'infoTable' with existing signature rows
initTableDataWithCurrentSignatureData(infoTableApi, mapId, systemId, true);
updateScannedSignaturesBar(infoTableApi, {showNotice: false});
setSignatureReaderDialogObserver(dialogElement, mapId, systemData);
});
// dialog shown event
signatureReaderDialog.on('shown.bs.modal', function(e){
signatureReaderDialog.initTooltips();
@@ -600,9 +795,36 @@ define([
// set focus on sig-input textarea
signatureReaderDialog.find('textarea').focus();
});
// show dialog
signatureReaderDialog.modal('show');
});
};
/**
* Parsed scan result data (from EVE client) should be enriched with some data
* -> fill up more columns in the 'preview' signature tab.e
* @param signatureData
* @returns {*}
*/
let enrichParsedSignatureData = signatureData => {
let characterData = Util.getObjVal(Util.getCurrentUserData(), 'character');
let timestamp = Math.floor((new Date()).getTime() / 1000);
for(let i = 0; i < signatureData.length; i++){
signatureData[i].created = {
created: timestamp,
character: characterData
};
signatureData[i].updated = {
updated: timestamp,
character: characterData
};
}
return signatureData;
};
/**
* parses a copy&paste string from ingame scanning window
* @param systemData
@@ -712,7 +934,8 @@ define([
[],
{
signatures: signatureData,
deleteOld: options.deleteOld,
deleteOld: options.deleteOld || 0,
deleteConnection: options.deleteConnection || 0,
systemId: parseInt(systemData.id)
},
{
@@ -764,17 +987,19 @@ define([
* deletes signature rows from signature table
* @param tableApi
* @param rows
* @param deleteOptions
*/
let deleteSignatures = (tableApi, rows) => {
let deleteSignatures = (tableApi, rows, deleteOptions = {}) => {
// 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 metaData = getTableMetaData(tableApi);
let data = Object.assign(deleteOptions, {
systemId: metaData.systemId
});
let processRequestPromise = tableApi.newProcess('request');
Util.request('DELETE', 'signature', signatureIds, {
systemId: metaData.systemId
}, {
Util.request('DELETE', 'signature', signatureIds, data, {
tableApi: tableApi,
processRequestPromise: processRequestPromise
},
@@ -841,7 +1066,11 @@ define([
*/
let checkConnectionConflicts = () => {
setTimeout(() => {
let connectionSelects = $('.' + config.tableCellConnectionClass + '.editable');
let connectionSelectsSelector = [config.sigTablePrimaryClass, config.sigTableSecondaryClass].map(
tableClass => '.' + tableClass + ' .' + config.tableCellConnectionClass + '.editable'
).join(', ');
let connectionSelects = $(connectionSelectsSelector);
let connectionIds = [];
let duplicateConnectionIds = [];
let groupedSelects = [];
@@ -1216,6 +1445,47 @@ define([
}
};
/**
* get HTML for "delete connection" confirmation popover
* @returns {string}
*/
let getConfirmationContent = () => {
let checkOptions = [{
name: 'deleteConnection',
value: '1',
label: 'delete connection',
class: 'pf-editable-warn',
checked: true
}];
let getChecklist = checkOptions => {
let html = '<form class="form-inline editableform popover-content-inner">';
html += '<div class="control-group form-group">';
html += '<div class="editable-input">';
html += '<div class="editable-checklist">';
for(let option of checkOptions){
html += '<div><label>';
html += '<input type="checkbox" name="' + option.name + '" value="' + option.value + '" ';
html += 'class="' + option.class + '" ' + (option.checked ? 'checked' : '') + '>';
html += '<span>' + option.label + '</span>';
html += '</label></div>';
}
html += '</div>';
html += '</div>';
html += '</div>';
html += '</form>';
return html;
};
let html = '';
html += getChecklist(checkOptions);
return html;
};
/**
* get dataTables default options for signature tables
* @param mapId
@@ -1281,7 +1551,7 @@ define([
title: 'id',
type: 'string',
width: 12,
class: [config.tableCellFocusClass, config.sigTableEditSigNameInput].join(' '),
class: [config.tableCellFocusClass, config.sigTableEditSigNameInput, config.fontUppercaseClass].join(' '),
data: 'name',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
@@ -1299,7 +1569,7 @@ define([
pk: rowData.id || null,
emptytext: '? ? ?',
value: cellData,
inputclass: config.editableNameInputClass,
inputclass: config.fontUppercaseClass,
display: function(value){
// change display value to first 3 letters
$(this).text($.trim( value.substr(0, 3) ).toLowerCase());
@@ -1634,7 +1904,7 @@ define([
let diff = Math.floor((new Date()).getTime()) - cellData * 1000;
// age > 1 day
if( diff > 86400000){
if(diff > 86400000){
$(cell).addClass('txt-color txt-color-warning');
}
}
@@ -1679,22 +1949,43 @@ define([
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',
title: 'Delete signature',
template: Util.getConfirmationTemplate(getConfirmationContent(), {
size: 'small',
noTitle: true
}),
onConfirm: function(e, target){
// top scroll to top
e.preventDefault();
// get form data (check if form tag is not hidden!) from confirmation popover
let tip = target.data('bs.confirmation').tip();
let form = tip.find('form:not(.hidden)').first();
let formData = form.getFormValues();
let deleteOptions = Util.getObjVal(formData, 'deleteConnection') ? formData : {};
// add "processing" state or connection that will be deleted as well
if(deleteOptions.deleteConnection){
let connectionId = tableApi.cell(rowIndex, 'connection:name').data();
if(connectionId){
let metaData = getTableMetaData(tableApi);
let connection = $().getConnectionById(metaData.mapId, connectionId);
if(connection){
connection.addType('state_process');
}
}
}
let deleteRowElement = $(target).parents('tr');
let row = tableApi.rows(deleteRowElement);
deleteSignatures(tableApi, row);
deleteSignatures(tableApi, row, deleteOptions);
},
onShow: function(e, target){
// hide "deleteConnection" checkbox if no connectionId linked
let tip = target.data('bs.confirmation').tip();
let form = tip.find('form').first();
let connectionId = tableApi.cell(rowIndex, 'connection:name').data();
form.toggleClass('hidden', !connectionId);
}
};
@@ -1867,6 +2158,51 @@ define([
}
};
/**
* draw signature 'info' (preview) table in 'signatureReader' dialog
* @param dialogElement
* @param mapId
* @param systemData
* @returns {jQuery}
*/
let drawSignatureTableInfo = (dialogElement, mapId, systemData) => {
let infoElement = $(dialogElement).find('#' + config.sigInfoId);
let table = $('<table>', {
id: getTableId(mapId, systemData.id, 'info'),
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTableInfoClass].join(' ')
});
infoElement.append(table);
let dataTableOptions = {
tabIndex: -1,
dom: '<"row"<"col-xs-3"l><"col-xs-5 ' + config.tableToolbarStatusClass + '"><"col-xs-4"f>>' +
'<"row"<"col-xs-12"tr>>' +
'<"row"<"col-xs-5"i><"col-xs-7"p>>',
initComplete: function(settings, json){
let tableApi = this.api();
initCharacterInfoTooltip(this, tableApi);
tableApi.columns(['action:name']).visible(false);
Counter.initTableCounter(this, ['created:name', 'updated:name']);
}
};
$.extend(true, dataTableOptions, getSignatureDataTableDefaults(mapId, systemData));
let tableApi = table.DataTable(dataTableOptions);
tableApi.on('draw.dt', function(e, settings){
// xEditable cells should not be editable in this table
$(dialogElement).find('.' + config.sigTableInfoClass).find('td.editable').editable('disable');
});
return tableApi;
};
/**
* draw signature table toolbar (add signature button, scan progress bar
* @param moduleElement
@@ -2020,8 +2356,6 @@ define([
editable.input.$input.first().prop('disabled', true);
// preselect second option
//editable.input.$input.eq(1).prop('checked', true);
//editable.setValue('ad78172b72d0327b237c4a7dc1daa5d7');
// "fake" radio button behaviour
editable.input.$input.attr('name', 'test').attr('type', 'radio');
@@ -2162,6 +2496,30 @@ define([
});
};
/**
* init character info Tooltips
* -> e.g. table cell 'question mark' icon
* @param element
* @param tableApi
*/
let initCharacterInfoTooltip = (element, tableApi) => {
element.hoverIntent({
over: function(e){
let cellElement = $(this);
let rowData = tableApi.row(cellElement.parents('tr')).data();
cellElement.addCharacterInfoTooltip(rowData, {
trigger: 'manual',
placement: 'top',
show: true
});
},
out: function(e){
$(this).destroyPopover();
},
selector: 'td.' + Util.config.helpClass
});
};
/**
* draw empty signature table
* @param moduleElement
@@ -2213,6 +2571,7 @@ define([
initComplete: function(settings, json){
let tableApi = this.api();
initCharacterInfoTooltip(this, tableApi);
initGroupFilterButton(tableApi);
initUndoButton(tableApi);
initSelectAllButton(tableApi);
@@ -2386,13 +2745,13 @@ define([
// event listener for global "paste" signatures into the page -------------------------------------------------
moduleElement.on('pf:updateSystemSignatureModuleByClipboard', {tableApi: primaryTableApi}, function(e, clipboard){
let lazyUpdateToggle = moduleElement.find('.' + config.moduleHeadlineIconLazyClass);
let signatureOptions = {
deleteOld: lazyUpdateToggle.hasClass('active') ? 1 : 0
deleteOld: getLazyUpdateToggleStatus(moduleElement),
deleteConnection: 0
};
// "disable" lazy update icon -> prevents accidental removal for next paste #724
lazyUpdateToggle.toggleClass('active', false);
getLazyUpdateToggleElement(moduleElement).toggleClass('active', false);
updateSignatureTableByClipboard(e.data.tableApi, systemData, clipboard, signatureOptions);
});
@@ -2402,23 +2761,38 @@ define([
moduleElement.find('.' + config.sigTableClass),
'.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
});
/**
* get "lazy delete" toggle element
* @param moduleElement
* @returns {*}
*/
let getLazyUpdateToggleElement = moduleElement => moduleElement.find('.' + config.moduleHeadlineIconLazyClass);
/**
* get status for "lazy delete" toggle
* @param moduleElement
* @returns {number}
*/
let getLazyUpdateToggleStatus = moduleElement => getLazyUpdateToggleElement(moduleElement).hasClass('active') ? 1 : 0;
/**
* update 'counter' UI elements in 'signature reader' dialog
* @param data
*/
let updateSignatureReaderCounters = data => {
let counterElement = $('#' + config.sigInfoCountSigNewId).text(data.added || 0);
counterElement.toggleClass(counterElement.attr('data-class'), Boolean(data.added));
counterElement = $('#' + config.sigInfoCountSigChangeId).text(data.changed || 0);
counterElement.toggleClass(counterElement.attr('data-class'), Boolean(data.changed));
counterElement = $('#' + config.sigInfoCountSigDeleteId).text(data.deleted || 0);
counterElement.toggleClass(counterElement.attr('data-class'), Boolean(data.deleted));
counterElement = $('#' + config.sigInfoCountConDeleteId).text(data.deleteCon || 0);
counterElement.toggleClass(counterElement.attr('data-class'), Boolean(data.deleteCon));
};
/**
@@ -2435,6 +2809,181 @@ define([
return row;
};
/**
* @param action
* @param rowId
* @returns {Promise<unknown>}
*/
let getPromiseForRow = (action, rowId) => {
return new Promise((resolve, reject) => {
resolve({action: action, rowId: rowId});
});
};
/**
* callback for a changed row
* @param rowIndex
* @param colIndex
* @param tableLoopCount
* @param cellLoopCount
* @param options CUSTOM parameter (not DataTables specific)!
*/
let rowUpdate = function(rowIndex, colIndex, tableLoopCount, cellLoopCount, options){
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', Util.getObjVal(options, 'keepVisible') || false);
}
}else if(node.hasClass(config.tableCellCounterClass)){
// "updated" timestamp always changed
node.pulseBackgroundColor('changed', Util.getObjVal(options, 'keepVisible') || false);
}
};
/**
* update 'info' (preview) signature table (inside 'signature reader' dialog)
* @param tableApi
* @param signaturesDataOrig
* @param deleteOutdatedSignatures
* @param deleteConnections
*/
let updateSignatureInfoTable = (tableApi, signaturesDataOrig, deleteOutdatedSignatures = false, deleteConnections = false) => {
// clone signature array because of further manipulation
let signaturesData = $.extend([], signaturesDataOrig);
let rowIdsExist = [];
let promisesAdded = [];
let promisesChanged = [];
let promisesDeleted = [];
let allRows = tableApi.rows();
let rowUpdateCallback = function(){
rowUpdate.apply(this, [...arguments, {keepVisible: true}]);
};
// update rows ------------------------------------------------------------------------------------------------
allRows.every(function(rowIdx, tableLoop, rowLoop){
let row = this;
let rowData = row.data();
for(let i = 0; i < signaturesData.length; i++){
if(signaturesData[i].name === rowData.name){
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
let newRowData = signaturesData[i];
// keep "description" must not be replaced
newRowData.description = rowData.description;
// existing "groupId" must not be removed
if(!newRowData.groupId){
newRowData.groupId = rowData.groupId;
newRowData.typeId = rowData.typeId;
}else if(newRowData.groupId === rowData.groupId){
if(!newRowData.typeId){
newRowData.typeId = rowData.typeId;
}
}
// "created" timestamp will not change -> use existing
newRowData.created = rowData.created;
row.data(newRowData);
// 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(rowUpdateCallback);
promisesChanged.push(getPromiseForRow('changed', rowId));
}
rowIdsExist.push(rowIdx);
// remove signature data -> all left signatures will be added
signaturesData.splice(i, 1);
i--;
}
}
});
// delete rows ------------------------------------------------------------------------------------------------
if(deleteOutdatedSignatures){
let rows = tableApi.rows((rowIdx, rowData, node) => !rowIdsExist.includes(rowIdx));
rows.every(function(rowIdx, tableLoop, rowLoop){
let row = this;
let rowId = row.id(true);
let rowElement = row.nodes().to$();
let rowData = row.data();
rowElement.pulseBackgroundColor('deleted', true);
promisesChanged.push(getPromiseForRow('deleted', rowId));
// check if there is a connectionId.
if(deleteConnections && Util.getObjVal(rowData, 'connection.id')){
promisesChanged.push(getPromiseForRow('deleteCon', rowId));
}
});
}
// add rows ---------------------------------------------------------------------------------------------------
for(let signatureData of signaturesData){
let row = addSignatureRow(tableApi, signatureData);
let rowElement = row.nodes().to$();
rowElement.pulseBackgroundColor('added', true);
promisesAdded.push(getPromiseForRow('added', row.index()));
}
// done -------------------------------------------------------------------------------------------------------
Promise.all(promisesAdded.concat(promisesChanged, promisesDeleted)).then(payloads => {
if(payloads.length){
// table data changed -> draw() table changes
tableApi.draw();
// no notifications if table was empty just progressbar notification is needed
// sum payloads by "action"
let notificationCounter = payloads.reduce((acc, payload) => {
acc[payload.action]++;
return acc;
}, Object.assign({}, emptySignatureReaderCounterData));
updateSignatureReaderCounters(notificationCounter);
updateScannedSignaturesBar(tableApi, {showNotice: false});
}
});
};
/**
* update signature table with new signatures
* -> add/update/delete rows
@@ -2463,51 +3012,6 @@ define([
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');
}
};
let getPromiseForRow = (action, rowId) => {
return new Promise((resolve, reject) => {
resolve({action: action, rowId: rowId});
});
};
// update signatures ------------------------------------------------------------------------------------------
allRows.every(function(rowIdx, tableLoop, rowLoop){
let row = this;
@@ -2588,7 +3092,7 @@ define([
}
acc[payload.action]++;
return acc;
}, {});
}, Object.assign({}, emptySignatureReaderCounterData));
let notification = '';
if(notificationCounter.added > 0){
@@ -2606,6 +3110,13 @@ define([
}
updateScannedSignaturesBar(tableApi, {showNotice: true});
// at this point the 'primary' signature table update is done
// we need to check if there is an open 'signature reader' dialog,
// that needs to update its 'preview' signature table
// -> to use DataTables "drawCallback" option or "draw.dt" event is not the *best* option:
// Both are called to frequently (e.g. after filter/sort actions)
$('.' + config.sigReaderDialogClass + '.in').trigger('pf:updateSignatureReaderDialog');
}
// unlock table

View File

@@ -75,13 +75,11 @@ define([
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad',
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
animationPulseWarningClass: 'pf-animation-pulse-warning', // animation class
animationPulseDangerClass: 'pf-animation-pulse-danger', // animation class
animationPulseClassPrefix: 'pf-animation-pulse-', // class prefix for "pulse" background animation
// popover
popoverTriggerClass: 'pf-popover-trigger', // class for "popover" trigger elements
popoverSmallClass: 'pf-popover-small', // class for small "popover"
popoverSmallClass: 'popover-small', // class for small "popover"
popoverCharacterClass: 'pf-popover-character', // class for character "popover"
// Summernote
@@ -765,28 +763,30 @@ define([
/**
* highlight jquery elements
* add/remove css class for keyframe animation
* @returns {any|JQuery|*}
* @param status
* @param keepVisible
* @param clear
* @returns {void|*|undefined}
*/
$.fn.pulseBackgroundColor = function(status, clear){
let animationClass = '';
$.fn.pulseBackgroundColor = function(status, keepVisible = false, clear = false){
let animationClass = config.animationPulseClassPrefix;
switch(status){
case 'added':
animationClass = config.animationPulseSuccessClass;
break;
case 'changed':
animationClass = config.animationPulseWarningClass;
break;
case 'deleted':
animationClass = config.animationPulseDangerClass;
break;
case 'added': animationClass += 'success'; break;
case 'changed': animationClass += 'warning'; break;
case 'deleted': animationClass += 'danger'; break;
default: console.warn('Invalid status: %s', status);
}
// if keepVisible -> background color animation class will not be deleted
if(keepVisible){
animationClass += '-keep';
}
let clearTimer = element => {
element.removeClass( animationClass );
element.removeClass(animationClass);
let currentTimer = element.data('animationTimer');
if( animationTimerCache.hasOwnProperty(currentTimer) ){
if(animationTimerCache.hasOwnProperty(currentTimer)){
clearTimeout( currentTimer );
delete animationTimerCache[currentTimer];
element.removeData('animationTimer');
@@ -796,18 +796,20 @@ define([
return this.each(function(){
let element = $(this);
if( element.hasClass(animationClass) ){
if(element.hasClass(animationClass)){
// clear timer -> set new timer
clearTimer(element);
}
if(clear !== true){
if(!clear){
element.addClass(animationClass);
let timer = setTimeout(clearTimer, 1500, element);
element.data('animationTimer', timer);
animationTimerCache[timer] = true;
// remove class after animation finish, if not 'keepVisible'
if(!keepVisible){
let timer = setTimeout(clearTimer, 1500, element);
element.data('animationTimer', timer);
animationTimerCache[timer] = true;
}
}
});
};
@@ -971,6 +973,14 @@ define([
});
};
/**
* capitalize first letter
* @returns {string}
*/
String.prototype.capitalize = function(){
return this.charAt(0).toUpperCase() + this.slice(1);
};
/**
* get hash from string
* @returns {number}
@@ -1158,6 +1168,52 @@ define([
}
};
/**
* get template for Bootstrap "Confirmation" popover plugin
* -> if HTML 'content' not set, we expect the default template
* https://www.npmjs.com/package/bs-confirmation
* -> options.size for "small" popover layout
* -> options.noTitle for hide title element
* @param content
* @param options
* @returns {string}
*/
let getConfirmationTemplate = (content, options) => {
let getButtons = () => {
let buttonHtml = '<div class="btn-group">';
buttonHtml += '<a data-apply="confirmation">Yes</a>';
buttonHtml += '<a data-dismiss="confirmation">No</a>';
buttonHtml += '</div>';
return buttonHtml;
};
let getContent = content => {
let contentHtml = content ? content : '';
contentHtml += '<div class="popover-footer">';
contentHtml += getButtons();
contentHtml += '</div>';
return contentHtml;
};
let popoverClass = ['popover'];
if('small' === getObjVal(options, 'size')){
popoverClass.push('popover-small');
}
let contentClass = ['popover-content', 'no-padding'];
let html = '<div class="' + popoverClass.join(' ') + '">';
html += '<div class="arrow"></div>';
if(true !== getObjVal(options, 'noTitle')){
html += '<h3 class="popover-title"></h3>';
}
html += '<div class="' + contentClass.join(' ') + '">';
html += getContent(content);
html += '</div>';
html += '</div>';
return html;
};
/**
* convert XEditable Select <option> data into Select2 data format
* -> "prepend" (empty) options get added, too
@@ -1239,7 +1295,7 @@ define([
};
/**
* set default configuration for "Bootbox"
* set default configuration for "Bootbox" plugin
*/
let initDefaultBootboxConfig = () => {
bootbox.setDefaults({
@@ -1248,7 +1304,22 @@ define([
};
/**
* set default configuration for "Select2"
* set default configuration for "Confirmation" popover plugin
*/
let initDefaultConfirmationConfig = () => {
$.fn.confirmation.Constructor.DEFAULTS.placement = 'left';
$.fn.confirmation.Constructor.DEFAULTS.container = 'body';
$.fn.confirmation.Constructor.DEFAULTS.btnCancelClass = 'btn btn-sm btn-default';
$.fn.confirmation.Constructor.DEFAULTS.btnCancelLabel = 'cancel';
$.fn.confirmation.Constructor.DEFAULTS.btnCancelIcon = 'fas fa-fw fa-ban';
$.fn.confirmation.Constructor.DEFAULTS.btnOkClass = 'btn btn-sm btn-danger';
$.fn.confirmation.Constructor.DEFAULTS.btnOkLabel = 'delete';
$.fn.confirmation.Constructor.DEFAULTS.btnOkIcon = 'fas fa-fw fa-times';
$.fn.confirmation.Constructor.DEFAULTS.template = getConfirmationTemplate();
};
/**
* set default configuration for "Select2" plugin
*/
let initDefaultSelect2Config = () => {
$.fn.select2.defaults.set('theme', 'pathfinder');
@@ -1372,7 +1443,7 @@ define([
};
/**
* set default configuration for "xEditable"
* set default configuration for "xEditable" plugin
*/
let initDefaultEditableConfig = () => {
// use fontAwesome buttons template
@@ -1462,7 +1533,7 @@ define([
// Server is running with GMT/UTC (EVE Time)
let localDate = new Date();
let serverDate= new Date(
let serverDate = new Date(
localDate.getUTCFullYear(),
localDate.getUTCMonth(),
localDate.getUTCDate(),
@@ -1766,8 +1837,8 @@ define([
* Request data from Server
* -> This function should be used (in future) for all Ajax and REST API calls
* -> works as a "wrapper" for jQueries ajax() method
* @param action
* @param entity
* @param {String} action
* @param {String} entity
* @param ids
* @param data
* @param context
@@ -1779,7 +1850,7 @@ define([
let requestExecutor = (resolve, reject) => {
let payload = {
action: 'request',
name: action.toLowerCase() + entity.charAt(0).toUpperCase() + entity.slice(1)
name: action.toLowerCase() + entity.capitalize()
};
// build request url --------------------------------------------------------------------------------------
@@ -2206,7 +2277,7 @@ define([
let typeClass = '';
let matches = regex.exec(typeName.toLowerCase());
if(matches && matches[1]){
typeName = matches[1].charAt(0).toUpperCase() + matches[1].slice(1);
typeName = matches[1].capitalize();
typeClass = getPlanetInfo(matches[1]);
}
@@ -3356,6 +3427,7 @@ define([
showVersionInfo: showVersionInfo,
initPrototypes: initPrototypes,
initDefaultBootboxConfig: initDefaultBootboxConfig,
initDefaultConfirmationConfig: initDefaultConfirmationConfig,
initDefaultSelect2Config: initDefaultSelect2Config,
initDefaultEditableConfig: initDefaultEditableConfig,
getCurrentTriggerDelay: getCurrentTriggerDelay,
@@ -3410,6 +3482,7 @@ define([
getCurrentCharacterLog: getCurrentCharacterLog,
findInViewport: findInViewport,
initScrollSpy: initScrollSpy,
getConfirmationTemplate: getConfirmationTemplate,
convertXEditableOptionsToSelect2: convertXEditableOptionsToSelect2,
flattenXEditableSelectArray: flattenXEditableSelectArray,
getCharacterDataBySystemId: getCharacterDataBySystemId,

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,9 @@
/*!
* hoverIntent v1.9.0 // 2017.09.01 // jQuery v1.7.0+
* hoverIntent v1.10.0 // 2019.02.25 // jQuery v1.7.0+
* http://briancherne.github.io/jquery-hoverIntent/
*
* You may use hoverIntent under the terms of the MIT license. Basically that
* means you are free to use hoverIntent as long as this header is left intact.
* Copyright 2007-2017 Brian Cherne
* Copyright 2007-2019 Brian Cherne
*/
!function(factory){"use strict";"function"==typeof define&&define.amd?define(["jquery"],factory):jQuery&&!jQuery.fn.hoverIntent&&factory(jQuery)}(function($){"use strict";var cX,cY,_cfg={interval:100,sensitivity:6,timeout:0},INSTANCE_COUNT=0,track=function(ev){cX=ev.pageX,cY=ev.pageY},compare=function(ev,$el,s,cfg){if(Math.sqrt((s.pX-cX)*(s.pX-cX)+(s.pY-cY)*(s.pY-cY))<cfg.sensitivity)return $el.off(s.event,track),delete s.timeoutId,s.isActive=!0,ev.pageX=cX,ev.pageY=cY,delete s.pX,delete s.pY,cfg.over.apply($el[0],[ev]);s.pX=cX,s.pY=cY,s.timeoutId=setTimeout(function(){compare(ev,$el,s,cfg)},cfg.interval)},delay=function(ev,$el,s,out){return delete $el.data("hoverIntent")[s.id],out.apply($el[0],[ev])};$.fn.hoverIntent=function(handlerIn,handlerOut,selector){var instanceId=INSTANCE_COUNT++,cfg=$.extend({},_cfg);$.isPlainObject(handlerIn)?(cfg=$.extend(cfg,handlerIn),$.isFunction(cfg.out)||(cfg.out=cfg.over)):cfg=$.isFunction(handlerOut)?$.extend(cfg,{over:handlerIn,out:handlerOut,selector:selector}):$.extend(cfg,{over:handlerIn,out:handlerIn,selector:handlerOut});var handleHover=function(e){var ev=$.extend({},e),$el=$(this),hoverIntentData=$el.data("hoverIntent");hoverIntentData||$el.data("hoverIntent",hoverIntentData={});var state=hoverIntentData[instanceId];state||(hoverIntentData[instanceId]=state={id:instanceId}),state.timeoutId&&(state.timeoutId=clearTimeout(state.timeoutId));var mousemove=state.event="mousemove.hoverIntent.hoverIntent"+instanceId;if("mouseenter"===e.type){if(state.isActive)return;state.pX=ev.pageX,state.pY=ev.pageY,$el.off(mousemove,track).on(mousemove,track),state.timeoutId=setTimeout(function(){compare(ev,$el,state,cfg)},cfg.interval)}else{if(!state.isActive)return;$el.off(mousemove,track),state.timeoutId=setTimeout(function(){delay(ev,$el,state,cfg.out)},cfg.timeout)}};return this.on({"mouseenter.hoverIntent":handleHover,"mouseleave.hoverIntent":handleHover},cfg.selector)}});
!function(factory){"use strict";"function"==typeof define&&define.amd?define(["jquery"],factory):"object"==typeof module&&module.exports?module.exports=factory(require("jquery")):jQuery&&!jQuery.fn.hoverIntent&&factory(jQuery)}(function($){"use strict";var cX,cY,_cfg={interval:100,sensitivity:6,timeout:0},INSTANCE_COUNT=0,track=function(ev){cX=ev.pageX,cY=ev.pageY},compare=function(ev,$el,s,cfg){if(Math.sqrt((s.pX-cX)*(s.pX-cX)+(s.pY-cY)*(s.pY-cY))<cfg.sensitivity)return $el.off(s.event,track),delete s.timeoutId,s.isActive=!0,ev.pageX=cX,ev.pageY=cY,delete s.pX,delete s.pY,cfg.over.apply($el[0],[ev]);s.pX=cX,s.pY=cY,s.timeoutId=setTimeout(function(){compare(ev,$el,s,cfg)},cfg.interval)};$.fn.hoverIntent=function(handlerIn,handlerOut,selector){var instanceId=INSTANCE_COUNT++,cfg=$.extend({},_cfg);$.isPlainObject(handlerIn)?(cfg=$.extend(cfg,handlerIn),$.isFunction(cfg.out)||(cfg.out=cfg.over)):cfg=$.isFunction(handlerOut)?$.extend(cfg,{over:handlerIn,out:handlerOut,selector:selector}):$.extend(cfg,{over:handlerIn,out:handlerIn,selector:handlerOut});var handleHover=function(e){var ev=$.extend({},e),$el=$(this),hoverIntentData=$el.data("hoverIntent");hoverIntentData||$el.data("hoverIntent",hoverIntentData={});var state=hoverIntentData[instanceId];state||(hoverIntentData[instanceId]=state={id:instanceId}),state.timeoutId&&(state.timeoutId=clearTimeout(state.timeoutId));var mousemove=state.event="mousemove.hoverIntent.hoverIntent"+instanceId;if("mouseenter"===e.type){if(state.isActive)return;state.pX=ev.pageX,state.pY=ev.pageY,$el.off(mousemove,track).on(mousemove,track),state.timeoutId=setTimeout(function(){compare(ev,$el,state,cfg)},cfg.interval)}else{if(!state.isActive)return;$el.off(mousemove,track),state.timeoutId=setTimeout(function(){!function(ev,$el,s,out){delete $el.data("hoverIntent")[s.id],out.apply($el[0],[ev])}(ev,$el,state,cfg.out)},cfg.timeout)}};return this.on({"mouseenter.hoverIntent":handleHover,"mouseleave.hoverIntent":handleHover},cfg.selector)}});

View File

@@ -1,32 +1,79 @@
<form role="form" class="form-horizontal">
<div class="row">
<div class="col-sm-12">
<div class="form-group" style="margin-bottom: 0;">
<label class="col-sm-2 control-label" for="form_result">Scan result</label>
<div class="col-xs-4 col-sm-3 col-md-2">
<h4><i class="fas fa-fw fa-cogs"></i>&nbsp;&nbsp;Update options</h4>
<div class="checkbox checkbox-warning">
<input id="{{sigReaderLazyUpdateId}}" name="deleteOld" value="1" type="checkbox" {{#sigInfoLazyUpdateStatus}}checked=""{{/sigInfoLazyUpdateStatus}}>
<label for="{{sigReaderLazyUpdateId}}">'lazy' delete&nbsp;
(<i class="fas fa-fw fa-exchange-alt"></i>)
<i class="fas fa-fw fa-question-circle pf-help-light" title="Delete saved signatures, not found in scan result"></i>
</label>
</div>
<i class="fas fa-tree-child"></i>
<div class="checkbox checkbox-danger">
<input id="{{sigReaderConnectionDeleteId}}" name="deleteConnection" value="1" type="checkbox">
<label for="{{sigReaderConnectionDeleteId}}">Delete 'leads to'
<i class="fas fa-fw fa-question-circle pf-help-light" title="Delete 'leads to' connections from map, on signature delete"></i>
</label>
</div>
</div>
<div class="col-xs-8 col-sm-9 col-md-10">
<div class="form-group has-feedback" style="margin-bottom: 0;">
<label for="{{sigInfoTextareaId}}" class="col-sm-2 control-label">Scan result</label>
<div class="col-sm-10">
<textarea style="resize: vertical" rows="3" id="form_result" name="clipboard" class="form-control custom-scroll" autofocus></textarea>
<span class="help-block">
Copy and paste signatures from your probe scanning window.<br>
Hit <kbd>ctrl</kbd> + <kbd>c</kbd> (copy) then <kbd>ctrl</kbd> + <kbd>v</kbd> (paste).
This tool can add and update signatures.
</span>
<textarea id="{{sigInfoTextareaId}}" name="clipboard" class="form-control" rows="4" data-clipboard="rdd" data-required-error="Scan result can not be empty" required></textarea>
<div class="help-block with-errors">
Copy ( <kbd>ctrl</kbd> + <kbd>c</kbd> ) and paste ( <kbd>ctrl</kbd> + <kbd>v</kbd> ) scan result.
</div>
<span class="fas form-control-feedback" aria-hidden="true"></span>
</div>
</div>
</div>
</div>
</form>
<h4>
<i class="fas fa-eye fa-fw"></i>&nbsp;&nbsp;Preview&nbsp;&nbsp;
<span class="label label-default txt-color txt-color-warning bg-color bg-color-grayDarker">beta</span>
{{{sigInfoProgressElement}}}
<span class="progress-label-right">0%</span>
</h4>
<div id="{{sigInfoId}}" class="pf-dynamic-area"></div>
<div class="row">
<div class="col-xs-8">
<div class="well well-sm">
<div class="row">
<div class="col-xs-3">Signatures:</div>
<div class="col-xs-3 text-right" title="New signatures found">
new&nbsp;
<kbd id="{{sigInfoCountSigNewId}}" class="txt-color" data-class="txt-color-success">0</kbd>
</div>
<div class="col-xs-3 text-right" title="Update signatures">
change&nbsp;
<kbd id="{{sigInfoCountSigChangeId}}" class="txt-color" data-class="txt-color-warning">0</kbd>
</div>
<div class="col-xs-3 text-right" title="Delete signatures">
delete&nbsp;
<kbd id="{{sigInfoCountSigDeleteId}}" class="txt-color" data-class="txt-color-red">0</kbd>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group" style="margin-bottom: 0;">
<div class="col-sm-offset-2 col-sm-10 col-xs-9">
<div class="checkbox checkbox-warning">
<input id="form_delete" name="deleteOld" value="1" type="checkbox">
<label for="form_delete" title="delete saved signatures not found in scan result">'lazy' delete&nbsp;
(<i class="fas fa-fw fa-exchange-alt"></i>)
</label>
</div>
<div class="col-xs-4">
<div class="well well-sm">
<div class="row">
<div class="col-xs-6">Connections:</div>
<div class="col-xs-6 text-right" title="Delete linked connections">
delete&nbsp;
<kbd id="{{sigInfoCountConDeleteId}}" class="txt-color" data-class="txt-color-red">0</kbd>
</div>
</div>
</div>
</div>
</form>
</div>

View File

@@ -67,7 +67,7 @@
&.bg-color-lighten { background-color: $lighten !important; }
&.bg-color-white { background-color: $white !important; }
&.bg-color-gray { background-color: $gray !important; }
&.bg-color-grayDark { background-color: $greyDark !important; }
&.bg-color-grayDark { background-color: $gray-dark !important; }
&.bg-color-grayDarker { background-color: $gray-darker !important; }
&.bg-color-magenta { background-color: $magenta !important; }
&.bg-color-tealLighter { background-color: $teal-lighter !important; }

View File

@@ -546,7 +546,7 @@ $modal-title-padding: 14px;
$modal-title-line-height: $line-height-base;
$modal-content-bg: $gray;
$modal-content-border-color: rgba(0,0,0,.2);
$modal-content-border-color: transparent;
$modal-content-fallback-border-color: #999;
$modal-backdrop-bg: #000;

View File

@@ -53,8 +53,9 @@
.modal-content {
position: relative;
background-color: $modal-content-bg;
border: 1px solid $modal-content-fallback-border-color; //old browsers fallback (ie8 etc)
border: 1px solid $modal-content-border-color;
//border: 1px solid $modal-content-fallback-border-color; //old browsers fallback (ie8 etc)
//border: 1px solid $modal-content-border-color;
border: none;
//border-radius: $border-radius-large;
@include box-shadow(0 3px 9px rgba(0,0,0,.5));
background-clip: padding-box;
@@ -106,6 +107,8 @@
position: relative;
padding: $modal-inner-padding;
color: $gray-lighter;
border-left: 1px solid $modal-content-bg;
border-right: 1px solid $modal-content-bg;
}
// Footer (for actions)

View File

@@ -10,7 +10,8 @@
z-index: $zindex-popover;
display: none;
max-width: $popover-max-width;
padding: 1px;
//padding: 1px;
padding: 0;
text-align: left; // Reset given new insertion method
background-color: $popover-bg;
background-clip: padding-box;
@@ -36,8 +37,8 @@
font-weight: normal;
line-height: 18px;
background-color: $popover-title-bg;
border-bottom: 1px solid darken($popover-title-bg, 5%);
border-radius: 5px 5px 0 0;
border-bottom: 1px solid darken($popover-bg, 5%);
@include border-top-radius(($border-radius-large - 1));
color: $gray-light
}

View File

@@ -1,3 +1,4 @@
@import "_function";
@import "_animation";
@import "_fonts";
@import "_main";

View File

@@ -94,86 +94,77 @@
}
// pulse-out ======================================================================================
.pf-animation-pulse-success{
@include animation( pulseBackgroundSuccess 1s 1 );
@include animation-timing-function( cubic-bezier(0.53, -0.03, 0.68, 0.38) );
@mixin pf-pulse-keyframes($name, $color, $backgroundColor, $keepVisible: false){
@include keyframes($name){
0% {
@if $keepVisible {
color: inherit;
}
}
10% {
background-color: $backgroundColor;
color: $color;
}
100% {
@if $keepVisible {
background-color: rgba($backgroundColor, 0.3);
}
}
};
}
.sorting_1{
@include animation( pulseBackgroundSuccessActive 1s 1 );
@include animation-timing-function( cubic-bezier(0.53, -0.03, 0.68, 0.38) );
@mixin pf-pulse-Background($status, $keepVisible: false){
$statusMap: (
success: (
color: $gray-dark,
backgroundColor: $green-dark
),
warning: (
color: $gray-darker,
backgroundColor: $orange
),
danger: (
color: $gray-darker,
backgroundColor: $red
)
);
$map: map-get($statusMap, $status);
$name: pulseBackground + capitalize($status);
$nameActive: $name + 'Active';
$className: 'pf-animation-pulse-' + $status;
@if $keepVisible {
$className: str-insert($className, '-keep', -1);
$name: str-insert($name, 'Keep', -1);
$nameActive: str-insert($nameActive, 'Keep', -1);
}
@include pf-pulse-keyframes($name, map-get($map, 'color'), map-get($map, 'backgroundColor'), $keepVisible);
@include pf-pulse-keyframes($nameActive, map-get($map, 'color'), darken(map-get($map, 'backgroundColor'), 5%), $keepVisible);
.#{$className}{
@include animation($name 1s 1);
@include animation-timing-function(cubic-bezier(0.53, -0.03, 0.68, 0.38));
will-change: color, background-color;
animation-fill-mode: forwards;
.sorting_1{
@include animation($nameActive 1s 1);
@include animation-timing-function(cubic-bezier(0.53, -0.03, 0.68, 0.38));
will-change: color, background-color;
animation-fill-mode: forwards;
}
}
}
.pf-animation-pulse-warning{
@include animation( pulseBackgroundWarning 1s 1 );
@include animation-timing-function( cubic-bezier(0.53, -0.03, 0.68, 0.38) );
.sorting_1{
@include animation( pulseBackgroundWarningActive 1s 1 );
@include animation-timing-function( cubic-bezier(0.53, -0.03, 0.68, 0.38) );
}
}
.pf-animation-pulse-danger{
@include animation( pulseBackgroundDanger 1s 1 );
@include animation-timing-function( cubic-bezier(0.53, -0.03, 0.68, 0.38) );
.sorting_1{
@include animation( pulseBackgroundDangerActive 1s 1 );
@include animation-timing-function( cubic-bezier(0.53, -0.03, 0.68, 0.38) );
}
}
@include keyframes(pulseBackgroundSuccess){
0% {}
10% {
background-color: $green-dark;
color: $gray-dark;
}
100% {}
};
@include keyframes(pulseBackgroundSuccessActive){
0% {}
10% {
background-color: darken($green-dark, 5%);
color: $gray-dark;
}
100% {}
};
@include keyframes(pulseBackgroundWarning){
0% {}
10% {
background-color: $orange;
color: $gray-darker;
}
100% {}
};
@include keyframes(pulseBackgroundWarningActive){
0% {}
10% {
background-color: darken($orange, 5%);
color: $gray-darker;
}
100% {}
};
@include keyframes(pulseBackgroundDanger){
0% {}
10% {
background-color: $red;
color: $gray-darker;
}
100% {}
};
@include keyframes(pulseBackgroundDangerActive){
0% {}
10% {
background-color: darken($red, 5%);
color: $gray-darker;
}
100% {}
};
@include pf-pulse-Background('success');
@include pf-pulse-Background('success', true);
@include pf-pulse-Background('warning');
@include pf-pulse-Background('warning', true);
@include pf-pulse-Background('danger');
@include pf-pulse-Background('danger', true);
// rotate =========================================================================================
.pf-animate-rotate{

View File

@@ -246,6 +246,49 @@
}
}
// signature reader dialog ====================================================
.pf-sig-reader-dialog{
h4 {
margin-top: 8px;
.pf-system-progress-scanned{
display: inline-block;
margin-left: 20px;
width: calc(100% - 255px);
.progress{
margin-bottom: 2px;
}
}
.progress-label-right{
margin-left: 8px;
vertical-align: middle;
font-size: 11px;
opacity: 0.65;
}
}
.fa-tree-child + .checkbox{
margin-left: 5px; // "delete connections" checkbox
display: inline-block;
}
#pf-sig-info{
margin-bottom: 10px;
}
.pf-sig-table-info{
tbody{
pointer-events: none; // disables "hover" effect for rows/cells
}
// disabled xEditable table cells should have the "enabled" color
.editable-click.editable-disabled{
color: inherit;
}
}
}
// jump info dialog ===========================================================
.pf-jump-info-dialog{
blockquote{

View File

@@ -0,0 +1,3 @@
@function capitalize($string) {
@return to-upper-case(str-slice($string, 1, 1)) + str-slice($string, 2);
}

View File

@@ -627,6 +627,11 @@
}
}
.badge{
color: $gray-lighter;
background-color: $gray-darker;
}
// table
table tr td {
line-height: 1;

View File

@@ -40,6 +40,10 @@ em{
}
}
.pf-font-uppercase{
text-transform: uppercase;
}
.pf-font-capitalize{
text-transform: capitalize;
}
@@ -56,6 +60,15 @@ em{
}
}
.fa-tree-child{
@extend .fa-fw;
@extend .fa-lg;
@extend .fa-level-up-alt;
@extend .fa-rotate-90;
color: $gray-light;
padding-right: 5px;
}
.no-scroll{
overflow: hidden;
}
@@ -141,6 +154,7 @@ em{
.pf-help-default{
cursor: help;
pointer-events: auto; // enable mouse events even if a parent was set to 'none' (e.g. disabled xEditable cells in sig-reader dialog table)
@include transition( color 0.08s ease-out );
&:hover{
@@ -196,10 +210,6 @@ a{
// form fields custom styles ======================================================================
.editable-input{
.pf-editable-name{
text-transform: uppercase;
}
optgroup[label]{
background-color: $gray;
color: $gray-light;
@@ -230,6 +240,10 @@ a{
.pf-editable-unknown[value='0'] + span {
color: $red;
}
.pf-editable-warn + span {
color: $orange;
}
}
}
select:active, select:hover {
@@ -285,6 +299,12 @@ select:active, select:hover {
padding: 1px;
}
.pf-table-toolbar-status{
text-align: center;
line-height: 22px;
color: $gray-light;
}
.dt-buttons,
.dt-stats{
display: inline-block;

View File

@@ -22,6 +22,19 @@
.popover-content {
font-family: $font-family-readable;
> .hidden + .popover-footer, // first content child is .hidden -> border radius to footer
> .popover-footer:first-child { // no content -> border radius to footer
@include border-top-radius(($border-radius-large - 1));
}
}
.popover-footer{
margin: 0; // reset heading margin
padding: 8px 14px; // same as popover-title
background-color: $popover-title-bg;
border-top: 1px solid darken($popover-bg, 5%);
@include border-bottom-radius(($border-radius-large - 1));
}
// image in popover
@@ -53,13 +66,22 @@
}
// smaller variant ----------------------------------------------------------------------------------------------------
.pf-popover-small{
.popover-small{
.popover-title {
padding: 3px 6px;
padding: 4px 6px;
}
.popover-content {
padding: 6px 1px 3px;
padding: 4px 2px 4px;
}
// "special" content wrapper for popups with "footer" (.popover-footer) element
.popover-content-inner{
padding: 7px 6px 0;
}
.popover-footer{
padding: 6px 6px;
}
}

View File

@@ -145,10 +145,6 @@
.pf-sig-table{
font-size: 10px;
.pf-sig-table-edit-name-input{
text-transform: uppercase;
}
// textarea field
// overwrite some styles in order to make <textarea> 100% width
.editable-container.editable-inline {

View File

@@ -438,10 +438,10 @@ table.dataTable td {
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate {
float: none;
text-align: center;
//text-align: center;
}
.dataTables_wrapper .dataTables_paginate {
margin-top: 0.5em;
//margin-top: 0.5em;
}
}
@media screen and (max-width: 640px) {