- Improved performance of JS window.setInterval() update counters

This commit is contained in:
Mark Friedrich
2019-11-22 19:35:04 +01:00
parent 4136bc48cb
commit e59a6c8b59
30 changed files with 959 additions and 247 deletions

View File

@@ -13,8 +13,8 @@
// Define globals exposed by Node.js.
"node": true,
// Allow ES6.
"esversion": 7,
// Allow ES8.
"esversion": 8,
/*
* ENFORCING OPTIONS

View File

@@ -37,6 +37,7 @@ requirejs.config({
slidebars: 'lib/slidebars', // v2.0.2 Slidebars - side menu plugin https://www.adchsm.com/slidebars/
jsPlumb: 'lib/jsplumb', // v2.9.3 jsPlumb main map draw plugin http://jsplumb.github.io/jsplumb/home.html
farahey: 'lib/farahey', // v1.1.2 jsPlumb "magnetizing" plugin extension - https://github.com/ThomasChan/farahey
easyTimer: 'lib/easytimer.min', // v4.0.2 EasyTimer - Timer/Chronometer/Countdown library - http://albert-gonzalez.github.io/easytimer.js
customScrollbar: 'lib/jquery.mCustomScrollbar.min', // v3.1.5 Custom scroll bars - http://manos.malihu.gr
mousewheel: 'lib/jquery.mousewheel.min', // v3.1.13 Mousewheel - https://github.com/jquery/jquery-mousewheel
xEditable: 'lib/bootstrap-editable.min', // v1.5.1 X-editable - in placed editing

View File

@@ -1,13 +1,16 @@
define([
'jquery',
'app/init',
'app/util'
], ($, Init, Util) => {
'app/util',
'app/lib/cron'
], ($, Init, Util, Cron) => {
'use strict';
let config = {
counterDigitSmallClass: 'pf-digit-counter-small',
counterDigitLargeClass: 'pf-digit-counter-large'
counterTaskAttr: 'data-counter-task', // element attr name with initialized counter name
counterStopClass: 'stopCounter', // class for counter elements where counter should be destroyed
counterDigitSmallClass: 'pf-digit-counter-small', // class for 'small' counter DOM elements (e.g. 'hour' number)
counterDigitLargeClass: 'pf-digit-counter-large' // class for 'large' counter DOM elements (e.g. 'days' number)
};
/**
@@ -57,73 +60,58 @@ define([
}
}
element.html(parts.join(' '));
};
/**
* destroy all active counter recursive
*/
$.fn.destroyTimestampCounter = function(recursive){
return this.each(function(){
let element = $(this);
let counterSelector = '[data-counter="init"]';
let counterElements = element.filter(counterSelector);
if(recursive){
counterElements = counterElements.add(element.find(counterSelector));
}
let destroyTimestampCounter = (element, recursive) => {
let counterTaskSelector = '[' + config.counterTaskAttr + ']';
let counterElements = element.filter(counterTaskSelector);
if(recursive){
counterElements = counterElements.add(element.find(counterTaskSelector));
}
counterElements.each(function(){
let element = $(this);
let interval = element.data('interval');
if(interval){
clearInterval(interval);
element.removeAttr('data-counter').removeData('interval').removeClass('stopCounter');
}
});
counterElements.each(function(){
let element = $(this);
let taskName = element.attr(config.counterTaskAttr);
if(Cron.delete(taskName)){
element.removeAttr(config.counterTaskAttr).removeClass(config.counterStopClass);
}
});
};
/**
* init a live counter based on a unix timestamp
* @param round string e.g. 'd' => round days
* @param element
* @param round e.g. 'd' => round days
* @returns {void|*|undefined}
*/
$.fn.initTimestampCounter = function(round){
return this.each(function(){
let element = $(this);
let timestamp = parseInt( element.text() );
let initTimestampCounter = (element, round) => {
let timestamp = parseInt(element.text());
// do not init twice
if(timestamp > 0){
let taskName = element.attr('id') || Util.getRandomString();
let date = new Date( timestamp * 1000);
updateDateDiff(element, date, round);
// do not init twice
if(timestamp > 0){
// mark as init
element.attr('data-counter', 'init');
// show element (if invisible) after first update
element.css({'visibility': 'initial'});
let date = new Date( timestamp * 1000);
let counterTask = Cron.new(taskName, {precision: 'seconds', interval: 1, timeout: 100});
counterTask.task = () => {
if(element.hasClass(config.counterStopClass)){
destroyTimestampCounter(element);
}else{
updateDateDiff(element, date, round);
}
};
Cron.set(counterTask);
updateDateDiff(element, date, round);
// show element (if invisible) after first update
element.css({'visibility': 'initial'});
// calc ms until next second
// -> makes sure all counter update in sync no matter when init
let msUntilSecond = 1500 - new Date().getMilliseconds();
setTimeout(function(){
let refreshIntervalId = window.setInterval(function(){
// update element with current time
if( !element.hasClass('stopCounter')){
updateDateDiff(element, date, round);
}else{
clearInterval( element.data('interval') );
}
}, 500);
element.data('interval', refreshIntervalId);
}, msUntilSecond);
}
});
element.attr(config.counterTaskAttr, taskName);
}
};
/**
@@ -134,30 +122,33 @@ define([
*/
let initTableCounter = (tableElement, columnSelector, round) => {
let tableApi = tableElement.api();
let taskName = tableElement.attr('id');
// mark as init
tableElement.attr('data-counter', 'init');
let updateTableCount = () => {
tableApi.cells(null, columnSelector).every(function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
let cell = this;
let node = cell.node();
let data = cell.data();
if(data && Number.isInteger(data) && !node.classList.contains('stopCounter')){
// timestamp expected int > 0
let date = new Date(data * 1000);
updateDateDiff( cell.nodes().to$(), date, round);
}
});
let cellUpdate = function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
let cell = this;
let node = cell.node();
let data = cell.data();
if(data && Number.isInteger(data) && !node.classList.contains(config.counterStopClass)){
// timestamp expected int > 0
let date = new Date(data * 1000);
updateDateDiff(cell.nodes().to$(), date, round);
}
};
let refreshIntervalId = window.setInterval(updateTableCount, 500);
let counterTask = Cron.new(taskName, {precision: 'seconds', interval: 1, timeout: 100});
counterTask.task = timer => {
tableApi.cells(null, columnSelector).every(cellUpdate);
};
Cron.set(counterTask);
tableElement.data('interval', refreshIntervalId);
tableElement.attr(config.counterTaskAttr, taskName);
};
return {
config: config,
updateDateDiff: updateDateDiff,
initTableCounter: initTableCounter
initTimestampCounter: initTimestampCounter,
initTableCounter: initTableCounter,
destroyTimestampCounter: destroyTimestampCounter
};
});

View File

@@ -1,6 +1,7 @@
define([
'jquery',
'app/init',
'app/counter',
'app/promises/promise.deferred',
'app/promises/promise.timeout',
'datatables.net',
@@ -8,7 +9,7 @@ define([
'datatables.net-buttons-html',
'datatables.net-responsive',
'datatables.net-select'
], ($, Init, DeferredPromise, TimeoutPromise) => {
], ($, Init, Counter, DeferredPromise, TimeoutPromise) => {
'use strict';
// all Datatables stuff is available...
@@ -42,7 +43,7 @@ define([
}
// remove all active counters in table
table.destroyTimestampCounter(true);
Counter.destroyTimestampCounter(table, true);
});
// Status Plugin ==============================================================================================

View File

@@ -126,13 +126,13 @@ define([], () => {
let Cache = class Cache {
constructor(config){
this.config = Object.assign({
this.config = Object.assign({},{
name: 'Default', // custom name for identification
ttl: 3600,
maxSize: 600,
bufferSize: 10, // in percent
strategy: 'FIFO',
debug: false
ttl: 3600, // default ttl for cache entries
maxSize: 600, // max cache entries
bufferSize: 10, // cache entry count in percent to be removed if maxSize reached
strategy: 'FIFO', // cache strategy policy
debug: false // debug output in console
}, config);
this.store = new Map();

318
js/app/lib/cron.js Normal file
View File

@@ -0,0 +1,318 @@
define([
'easyTimer',
'app/promises/promise.timeout',
], (easytimer, TimeoutPromise) => {
'use strict';
/*
Example1 run task every second ------------------------------------------------------------------------------------
let task1 = Cron.new('task1', {precision: 'seconds', interval: 1, timeout: 100});
task1.task = (timer) => {
console.log('task1 function():', timer.getTotalTimeValues());
return 'OK';
};
Cron.set(task1);
Example2 run task every 3 seconds ---------------------------------------------------------------------------------
let task1 = Cron.new('task1', {precision: 'seconds', interval: 3, timeout: 100});
task1.task = (timer) => {
console.log('task1 function():', timer.getTotalTimeValues());
return 'OK';
};
Cron.set(task1);
Example3 resolve Promise on run ----------------------------------------------------------------------------------
let task1 = Cron.new('task1', {precision: 'seconds', interval: 1, timeout: 100});
task1.task = (timer, task) => {
return new Promise((resolve, reject) => {
console.log('task1 Promise1():', timer.getTotalTimeValues(), task.get('interval'));
//task.set('interval', task.get('interval') + 1) // increase run interval every time by 1s
resolve('OK1');
}).then(payload => {
return new Promise((resolve, reject) => {
console.log('task2 Promise2():', timer.getTotalTimeValues(), payload);
resolve('OK2');
});
});
};
Cron.set(task1);
Example4 run task once at given Date() --------------------------------------------------------------------------
let dueDate = new Date();
dueDate.setSeconds(dueDate.getSeconds() + 5);
let task2 = Cron.new('task2', {precision: 'seconds', timeout: 100, dueDate: dueDate});
task2.task = () => 'OK task2';
Cron.set(task2);
*/
/**
* Task instances represent a task that should be executed at a given interval or dueDate
* -> Task´s are managed by CronManager()
* @type {Task}
*/
let Task = class Task {
constructor(name, config){
if(typeof name !== 'string'){
throw new TypeError('Task "name" must be instance of String, Type of "' + typeof name + '" given');
}
this._config = Object.assign({}, this.constructor.defaultConfig, config);
this._name = name; // unique name for identification
this._task = undefined; // task to run, instanceof Function, can also return a Promise
this._manager = undefined; // reference to CronManager() that handles this task
this._runQueue = new Map(); // current run() processes. > 1 requires config.isParallel: true
this._runCount = 0; // total run counter for this task
this._lastTotalTimeValues = undefined; // time values of last run()
}
get name(){
return this._name;
}
get precision(){
return this._config.precision;
}
get task(){
return this._task;
}
set task(task){
if(task instanceof Function){
this._task = task;
}else{
throw new TypeError('Task "task" must be instance of "function", Type of "' + typeof task + '" given');
}
}
get(option){
return this._config[option];
}
set(option, value){
this._config[option] = value;
}
setManager(manager){
this._manager = manager;
}
isRunning(){
return !!this._runQueue.size;
}
delete(){
let isDeleted = false;
if(this._manager){
isDeleted = this._manager.delete(this.name);
}
return isDeleted;
}
isDue(timer){
if(this._config.dueDate instanceof Date){
// run once at dueDate
if(new Date().getTime() >= this._config.dueDate.getTime()){
return true;
}
}else{
// periodic execution
let totalTimeValues = timer.getTotalTimeValues();
let totalTimeValuePrecision = totalTimeValues[this.precision];
totalTimeValuePrecision -= this._lastTotalTimeValues ? this._lastTotalTimeValues[this.precision] : 0;
if(
this._config.interval === 1 ||
totalTimeValuePrecision % this._config.interval === 0
){
return true;
}
}
return false;
}
invoke(timer){
if(
this.isDue(timer) &&
(!this.isRunning() || this._config.isParallel)
){
this.run(timer);
}
}
run(timer){
this._lastTotalTimeValues = Object.assign({}, timer.getTotalTimeValues());
let runId = 'run_' + (++this._runCount);
let runExec = resolve => {
resolve(this.task(timer, this));
};
let myProm = this._config.timeout > 0 ? new TimeoutPromise(runExec, this._config.timeout) : new Promise(runExec);
myProm.then(payload => {
// resolved within timeout -> wait for finally() block
}).catch(error => {
if(error instanceof Error){
// either timeout error or error from rejected deferredPromise
console.warn(error);
}
}).finally(() => {
// no matter if TimeoutPromise is resolved or rejected
// -> remove from _runQueue
this._runQueue.delete(runId);
// remove this task from store after run
if(this._config.dueDate instanceof Date){
this.delete();
}
});
this._runQueue.set(runId, myProm);
}
};
Task.defaultConfig = {
precision: 'seconds', // updateEvent this tasked will be subscribed to
isParallel: false, // if true this task can run parallel, e.g. if prev execution has not finished
interval: 1, // relates to 'precision'. 'interval' = 3 and 'precision' = "seconds" -> run every 3 seconds
dueDate: undefined, // if Date() instance is set, task only runs once at dueDate
timeout: 50 // if > 0, execution time that exceeds timeout (ms) throw error
};
/**
* An instance of CronManager() handles multiple Task()´s
* -> Task()´s can be set()/delete() from CronManager() instance
* @type {CronManager}
*/
let CronManager = class CronManager {
constructor(config){
this._config = Object.assign({}, this.constructor.defaultConfig, config);
this._timerConfig = Object.assign({}, this.constructor.defaultTimerConfig);
this._tasks = new Map();
this._timer = new easytimer.Timer();
// init Easytimer update events
this._config.precisions.map(precision => precision + 'Updated').forEach(eventName => {
this._timer.on(eventName, e => {
let precision = e.type.substring(0, e.type.indexOf('Updated'));
this.tasksByPrecision(precision).forEach(task => task.invoke(e.detail.timer));
});
});
this.debug = (msg,...data) => {
if(this._config.debug){
data = (data || []);
console.info(msg, ...data);
}
};
}
new(name, config){
return new Task(name, config);
}
set(task){
if(task instanceof Task){
// check for unique task name, or update existing task
if(!this.has(task.name) || (this.get(task.name) === task)){
// set new or update existing task
task.setManager(this);
this._tasks.set(task.name, task);
this.debug('SET/UPDATE task: %o config: %o', task.name, task);
// start timer (if it is not already running)
this.auto();
}else{
console.warn('FAILED to set task. Task name %o already exists', task.name);
}
}else{
throw new TypeError('Parameter must be instance of Task');
}
}
setNew(name, config){
this.set(this.new(name, config));
}
get(name){
return this._tasks.get(name);
}
has(name){
return this._tasks.has(name);
}
delete(name){
let isDeleted = this._tasks.delete(name);
if(isDeleted){
this.debug('DELETE task: %o', name);
this.auto();
}
return isDeleted;
}
clear(){
this.debug('CLEAR all %o task(s)', this._tasks.size);
this._tasks.clear();
this.auto();
}
tasksByPrecision(precision){
let tasks = [];
this._tasks.forEach(task => {
if(precision === task.precision){
tasks.push(task);
}
});
return tasks;
}
// EasyTimer controls -----------------------------------------------------------------------------------------
start(){
this._timer.start(this._timerConfig);
}
stop(){
this._timer.stop();
}
pause(){
this._timer.pause();
}
reset(){
this._timer.reset();
}
auto(){
if(this._tasks.size){
if(!this._timer.isRunning()){
this.start();
this.debug('START [auto] timer. %o task(s) found.', this._tasks.size);
}
}else{
this.stop();
this.debug('STOP [auto] timer. No tasks set.');
}
}
};
CronManager.defaultConfig = {
precisions: [
'secondTenths',
'seconds',
'minutes',
'hours',
'days'
],
debug: false // debug output in console
};
CronManager.defaultTimerConfig = {
precision: 'secondTenths', // Timer update frequency. Values: 'secondTenths', 'seconds', 'minutes', 'hours'
countdown: false // If true, the timer is a countdown
};
return new CronManager({
debug: false
});
});

View File

@@ -6,8 +6,9 @@ define([
'jquery',
'app/init',
'app/util',
'app/counter',
'bootbox'
], ($, Init, Util, bootbox) => {
], ($, Init, Util, Counter, bootbox) => {
'use strict';
@@ -31,11 +32,11 @@ define([
* updated "sync status" dynamic dialog area
*/
let updateSyncStatus = () => {
// check if task manager dialog is open
let logDialog = $('#' + config.taskDialogId);
if(logDialog.length){
// dialog is open
let statusArea = logDialog.find('.' + config.taskDialogStatusAreaClass);
requirejs(['text!templates/modules/sync_status.html', 'mustache'], (templateSyncStatus, Mustache) => {
let data = {
timestampCounterClass: config.timestampCounterClass,
@@ -49,10 +50,12 @@ define([
};
let syncStatusElement = $(Mustache.render(templateSyncStatus, data ));
Counter.destroyTimestampCounter(statusArea, true);
logDialog.find('.' + config.taskDialogStatusAreaClass).html( syncStatusElement );
statusArea.html(syncStatusElement);
logDialog.find('.' + config.timestampCounterClass).initTimestampCounter();
let counterElements = syncStatusElement.find('.' + config.timestampCounterClass);
Counter.initTimestampCounter(counterElements);
syncStatusElement.initTooltips({
placement: 'right'

View File

@@ -6,6 +6,7 @@ define([
'jquery',
'app/init',
'app/util',
'app/counter',
'app/logging',
'mustache',
'app/map/util',
@@ -26,7 +27,7 @@ define([
'dialog/credit',
'xEditable',
'app/module_map'
], ($, Init, Util, Logging, Mustache, MapUtil, MapContextMenu, SlideBars, TplHead, TplFooter) => {
], ($, Init, Util, Counter, Logging, Mustache, MapUtil, MapContextMenu, SlideBars, TplHead, TplFooter) => {
'use strict';
@@ -879,7 +880,6 @@ define([
// global "modal" callback --------------------------------------------------------------------------------
bodyElement.on('hide.bs.modal', '> .modal', e => {
let modalElement = $(e.target);
modalElement.destroyTimestampCounter(true);
// destroy all form validators
// -> does not work properly. validation functions still used (js error) after 'destroy'
@@ -892,6 +892,14 @@ define([
modalElement.find('.' + Util.config.select2Class)
.filter((i, element) => $(element).data('select2'))
.select2('destroy');
// destroy DataTable instances
for(let table of modalElement.find('table.dataTable')){
$(table).DataTable().destroy(true);
}
// destroy counter
Counter.destroyTimestampCounter(modalElement, true);
});
// global "close" trigger for context menus ---------------------------------------------------------------

View File

@@ -7,10 +7,10 @@ define([
'app/init',
'app/util',
'app/render',
'bootbox',
'app/counter',
'bootbox',
'app/map/util'
], ($, Init, Util, Render, bootbox, Counter, MapUtil) => {
], ($, Init, Util, Render, Counter, bootbox, MapUtil) => {
'use strict';
@@ -182,7 +182,7 @@ define([
mapElement.append(dlElementRight);
// init map lifetime counter
$('.' + config.mapInfoLifetimeCounterClass).initTimestampCounter();
Counter.initTimestampCounter($('.' + config.mapInfoLifetimeCounterClass));
mapElement.find('.' + config.textActionIconCopyClass).on('click', function(){
let mapUrl = $(this).find('span').text().trim();

View File

@@ -8,9 +8,10 @@ define([
'app/init',
'app/util',
'app/render',
'app/counter',
'bootbox',
'peityInlineChart'
], ($, Init, Util, Render, bootbox) => {
], ($, Init, Util, Render, Counter, bootbox) => {
'use strict';
let config = {
@@ -37,7 +38,7 @@ define([
* init blank statistics dataTable
* @param dialogElement
*/
let initStatsTable = function(dialogElement){
let initStatsTable = dialogElement => {
let columnNumberWidth = 28;
let cellPadding = 4;
let lineChartWidth = columnNumberWidth + (2 * cellPadding);
@@ -114,6 +115,7 @@ define([
columnDefs: [
{
targets: 0,
name: 'rowIndex',
title: '<i class="fas fa-hashtag"></i>',
orderable: false,
searchable: false,
@@ -122,6 +124,7 @@ define([
data: 'character.id'
},{
targets: 1,
name: 'image',
title: '',
orderable: false,
searchable: false,
@@ -135,6 +138,7 @@ define([
}
},{
targets: 2,
name: 'name',
title: 'name',
width: 200,
data: 'character',
@@ -144,20 +148,16 @@ define([
}
},{
targets: 3,
name: 'lastLogin',
title: 'last login',
searchable: false,
width: 70,
className: ['text-right', 'separator-right'].join(' '),
data: 'character',
render: {
_: 'lastLogin',
sort: 'lastLogin'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter();
}
data: 'character.lastLogin',
defaultContent: ''
},{
targets: 4,
name: 'mapCreate',
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -169,6 +169,7 @@ define([
}
},{
targets: 5,
name: 'mapUpdate',
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -180,6 +181,7 @@ define([
}
},{
targets: 6,
name: 'mapDelete',
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -191,6 +193,7 @@ define([
}
},{
targets: 7,
name: 'mapSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -201,6 +204,7 @@ define([
}
},{
targets: 8,
name: 'systemCreate',
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -212,6 +216,7 @@ define([
}
},{
targets: 9,
name: 'systemUpdate',
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -223,6 +228,7 @@ define([
}
},{
targets: 10,
name: 'systemDelete',
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -234,6 +240,7 @@ define([
}
},{
targets: 11,
name: 'systemSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -244,6 +251,7 @@ define([
}
},{
targets: 12,
name: 'connectionCreate',
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -255,6 +263,7 @@ define([
}
},{
targets: 13,
name: 'connectionUpdate',
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -266,6 +275,7 @@ define([
}
},{
targets: 14,
name: 'connectionDelete',
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -277,6 +287,7 @@ define([
}
},{
targets: 15,
name: 'connectionSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -287,6 +298,7 @@ define([
}
},{
targets: 16,
name: 'signatureCreate',
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -298,6 +310,7 @@ define([
}
},{
targets: 17,
name: 'signatureUpdate',
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -309,6 +322,7 @@ define([
}
},{
targets: 18,
name: 'signatureDelete',
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -320,6 +334,7 @@ define([
}
},{
targets: 19,
name: 'signatureSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -330,6 +345,7 @@ define([
}
},{
targets: 20,
name: 'totalSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -346,6 +362,8 @@ define([
// initial statistics data request
let requestData = getRequestDataFromTabPanels(dialogElement);
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
Counter.initTableCounter(this, ['lastLogin:name']);
},
drawCallback: function(settings){
this.api().rows().nodes().to$().each(function(i, row){
@@ -374,7 +392,7 @@ define([
});
$(sumColumnIndexes).each(function(index, value){
$( api.column( value ).footer() ).text( renderNumericColumn(pageTotalColumns[index], 'display') );
$(api.column(value).footer()).text( renderNumericColumn(pageTotalColumns[index], 'display') );
});
},
data: [] // will be added dynamic
@@ -404,8 +422,7 @@ define([
* @param requestData
* @param context
*/
let getStatsData = function(requestData, context){
let getStatsData = (requestData, context) => {
context.dynamicArea = $('#' + config.statsContainerId + ' .' + Util.config.dynamicAreaClass);
context.dynamicArea.showLoadingAnimation();
@@ -418,7 +435,7 @@ define([
}).done(function(data){
this.dynamicArea.hideLoadingAnimation();
this.callback(data);
this.callback(data, this);
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': loadStatistics', text: reason, type: 'warning'});
@@ -430,7 +447,7 @@ define([
* update "header"/"filter" elements in dialog
* @param responseData
*/
let drawStatsTable = function(responseData){
let drawStatsTable = (responseData, context) => {
let dialogElement = $('#' + config.statsDialogId);
// update filter/header -----------------------------------------------------------------------------
@@ -467,8 +484,8 @@ define([
// clear and (re)-fill table ------------------------------------------------------------------------
let formattedData = formatStatisticsData(responseData);
this.tableApi.clear();
this.tableApi.rows.add(formattedData).draw();
context.tableApi.clear();
context.tableApi.rows.add(formattedData).draw();
};
/**
@@ -477,7 +494,7 @@ define([
* @param statsData
* @returns {Array}
*/
let formatStatisticsData = function(statsData){
let formatStatisticsData = statsData => {
let formattedData = [];
let yearStart = statsData.start.year;
let weekStart = statsData.start.week;
@@ -687,7 +704,7 @@ define([
* @param dialogElement
* @returns {{}}
*/
let getRequestDataFromTabPanels = function(dialogElement){
let getRequestDataFromTabPanels = dialogElement => {
let requestData = {};
// get data from "tab" panel links ------------------------------------------------------------------

View File

@@ -7,8 +7,9 @@ define([
'app/init',
'app/util',
'bootbox',
'app/counter',
'app/map/util'
], ($, Init, Util, bootbox, MapUtil) => {
], ($, Init, Util, bootbox, Counter, MapUtil) => {
'use strict';
let config = {
@@ -827,10 +828,7 @@ define([
title: 'log',
width: 55,
className: ['text-right', config.tableCellCounterClass].join(' '),
data: 'created.created',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter('d');
}
data: 'created.created'
},{
targets: 5,
name: 'edit',
@@ -931,6 +929,9 @@ define([
}
}
],
initComplete: function(settings, json){
Counter.initTableCounter(this, ['created:name']);
},
drawCallback: function(settings){
let animationRows = this.api().rows().nodes().to$().filter(function(a,b ){
return (

View File

@@ -6,9 +6,8 @@ define([
'jquery',
'app/init',
'app/util',
'app/cache',
'morris'
], ($, Init, Util, Cache, Morris) => {
'app/lib/cache'
], ($, Init, Util, Cache) => {
'use strict';
let config = {

View File

@@ -6,13 +6,13 @@ define([
'jquery',
'app/init',
'app/util',
'app/cache',
'bootbox',
'app/counter',
'app/map/map',
'app/map/util',
'app/lib/cache',
'app/ui/form_element'
], ($, Init, Util, Cache, bootbox, Counter, Map, MapUtil, FormElement) => {
], ($, Init, Util, bootbox, Counter, Map, MapUtil, Cache, FormElement) => {
'use strict';
let config = {
@@ -482,7 +482,7 @@ define([
// hide row
// stop sig counter by adding a stopClass to each <td>, remove padding
cellElements.addClass('stopCounter')
cellElements.addClass(Counter.config.counterStopClass)
.velocity({
paddingTop: [0, '4px'],
paddingBottom: [0, '4px'],

View File

@@ -742,7 +742,7 @@ define([
let defaultOptions = {
dismissible: true,
messageId: 'pf-alert-' + Math.random().toString(36).substring(7),
messageId: getRandomString('pf-alert-'),
messageTypeClass: messageTypeClass,
messageTextClass: messageTextClass,
insertElement: 'replace'
@@ -873,7 +873,8 @@ define([
if(!resourceVariant){
switch(resourceType){
case 'factions': resourceType = 'corporations'; // faction icons are on 'corporations' endpoint.. CCP fail?!
// faction icons are on 'corporations' endpoint.. CCP fail?!
case 'factions': resourceType = 'corporations'; // jshint ignore:line
case 'alliances':
case 'corporations': resourceVariant = 'logo'; break;
case 'characters': resourceVariant = 'portrait'; break;
@@ -1580,6 +1581,14 @@ define([
return Init.timer[updateKey].CURRENT_DELAY;
};
/**
* get a random string
* -> e.g. as for Ids
* @param prefix
* @returns {string}
*/
let getRandomString = (prefix = 'id_') => prefix + Math.random().toString(36).substring(2,10);
/**
* get date obj with current EVE Server Time.
* @returns {Date}
@@ -1857,7 +1866,7 @@ define([
let key = 'tabId';
let tabId = sessionStorage.getItem(key);
if(tabId === null){
tabId = Math.random().toString(36).substr(2, 5);
tabId = getRandomString();
sessionStorage.setItem(key, tabId);
}
return tabId;
@@ -3521,6 +3530,7 @@ define([
initDefaultSelect2Config: initDefaultSelect2Config,
initDefaultEditableConfig: initDefaultEditableConfig,
getCurrentTriggerDelay: getCurrentTriggerDelay,
getRandomString: getRandomString,
getServerTime: getServerTime,
convertTimestampToServerTime: convertTimestampToServerTime,
getTimeDiffParts: getTimeDiffParts,

7
js/lib/easytimer.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -26,7 +26,7 @@
"gulp-requirejs-optimize": "1.3.x",
"gulp-sourcemaps": "^2.6.5",
"gulp-uglify": "^3.0.2",
"jshint": "^2.10.2",
"jshint": "^2.10.3",
"jshint-stylish": "^2.x.x",
"lodash.padend": "4.6.x",
"node-notifier": "^5.4.0",

View File

@@ -37,6 +37,7 @@ requirejs.config({
slidebars: 'lib/slidebars', // v2.0.2 Slidebars - side menu plugin https://www.adchsm.com/slidebars/
jsPlumb: 'lib/jsplumb', // v2.9.3 jsPlumb main map draw plugin http://jsplumb.github.io/jsplumb/home.html
farahey: 'lib/farahey', // v1.1.2 jsPlumb "magnetizing" plugin extension - https://github.com/ThomasChan/farahey
easyTimer: 'lib/easytimer.min', // v4.0.2 EasyTimer - Timer/Chronometer/Countdown library - http://albert-gonzalez.github.io/easytimer.js
customScrollbar: 'lib/jquery.mCustomScrollbar.min', // v3.1.5 Custom scroll bars - http://manos.malihu.gr
mousewheel: 'lib/jquery.mousewheel.min', // v3.1.13 Mousewheel - https://github.com/jquery/jquery-mousewheel
xEditable: 'lib/bootstrap-editable.min', // v1.5.1 X-editable - in placed editing

View File

@@ -1,13 +1,16 @@
define([
'jquery',
'app/init',
'app/util'
], ($, Init, Util) => {
'app/util',
'app/lib/cron'
], ($, Init, Util, Cron) => {
'use strict';
let config = {
counterDigitSmallClass: 'pf-digit-counter-small',
counterDigitLargeClass: 'pf-digit-counter-large'
counterTaskAttr: 'data-counter-task', // element attr name with initialized counter name
counterStopClass: 'stopCounter', // class for counter elements where counter should be destroyed
counterDigitSmallClass: 'pf-digit-counter-small', // class for 'small' counter DOM elements (e.g. 'hour' number)
counterDigitLargeClass: 'pf-digit-counter-large' // class for 'large' counter DOM elements (e.g. 'days' number)
};
/**
@@ -57,73 +60,58 @@ define([
}
}
element.html(parts.join(' '));
};
/**
* destroy all active counter recursive
*/
$.fn.destroyTimestampCounter = function(recursive){
return this.each(function(){
let element = $(this);
let counterSelector = '[data-counter="init"]';
let counterElements = element.filter(counterSelector);
if(recursive){
counterElements = counterElements.add(element.find(counterSelector));
}
let destroyTimestampCounter = (element, recursive) => {
let counterTaskSelector = '[' + config.counterTaskAttr + ']';
let counterElements = element.filter(counterTaskSelector);
if(recursive){
counterElements = counterElements.add(element.find(counterTaskSelector));
}
counterElements.each(function(){
let element = $(this);
let interval = element.data('interval');
if(interval){
clearInterval(interval);
element.removeAttr('data-counter').removeData('interval').removeClass('stopCounter');
}
});
counterElements.each(function(){
let element = $(this);
let taskName = element.attr(config.counterTaskAttr);
if(Cron.delete(taskName)){
element.removeAttr(config.counterTaskAttr).removeClass(config.counterStopClass);
}
});
};
/**
* init a live counter based on a unix timestamp
* @param round string e.g. 'd' => round days
* @param element
* @param round e.g. 'd' => round days
* @returns {void|*|undefined}
*/
$.fn.initTimestampCounter = function(round){
return this.each(function(){
let element = $(this);
let timestamp = parseInt( element.text() );
let initTimestampCounter = (element, round) => {
let timestamp = parseInt(element.text());
// do not init twice
if(timestamp > 0){
let taskName = element.attr('id') || Util.getRandomString();
let date = new Date( timestamp * 1000);
updateDateDiff(element, date, round);
// do not init twice
if(timestamp > 0){
// mark as init
element.attr('data-counter', 'init');
// show element (if invisible) after first update
element.css({'visibility': 'initial'});
let date = new Date( timestamp * 1000);
let counterTask = Cron.new(taskName, {precision: 'seconds', interval: 1, timeout: 100});
counterTask.task = () => {
if(element.hasClass(config.counterStopClass)){
destroyTimestampCounter(element);
}else{
updateDateDiff(element, date, round);
}
};
Cron.set(counterTask);
updateDateDiff(element, date, round);
// show element (if invisible) after first update
element.css({'visibility': 'initial'});
// calc ms until next second
// -> makes sure all counter update in sync no matter when init
let msUntilSecond = 1500 - new Date().getMilliseconds();
setTimeout(function(){
let refreshIntervalId = window.setInterval(function(){
// update element with current time
if( !element.hasClass('stopCounter')){
updateDateDiff(element, date, round);
}else{
clearInterval( element.data('interval') );
}
}, 500);
element.data('interval', refreshIntervalId);
}, msUntilSecond);
}
});
element.attr(config.counterTaskAttr, taskName);
}
};
/**
@@ -134,30 +122,33 @@ define([
*/
let initTableCounter = (tableElement, columnSelector, round) => {
let tableApi = tableElement.api();
let taskName = tableElement.attr('id');
// mark as init
tableElement.attr('data-counter', 'init');
let updateTableCount = () => {
tableApi.cells(null, columnSelector).every(function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
let cell = this;
let node = cell.node();
let data = cell.data();
if(data && Number.isInteger(data) && !node.classList.contains('stopCounter')){
// timestamp expected int > 0
let date = new Date(data * 1000);
updateDateDiff( cell.nodes().to$(), date, round);
}
});
let cellUpdate = function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
let cell = this;
let node = cell.node();
let data = cell.data();
if(data && Number.isInteger(data) && !node.classList.contains(config.counterStopClass)){
// timestamp expected int > 0
let date = new Date(data * 1000);
updateDateDiff(cell.nodes().to$(), date, round);
}
};
let refreshIntervalId = window.setInterval(updateTableCount, 500);
let counterTask = Cron.new(taskName, {precision: 'seconds', interval: 1, timeout: 100});
counterTask.task = timer => {
tableApi.cells(null, columnSelector).every(cellUpdate);
};
Cron.set(counterTask);
tableElement.data('interval', refreshIntervalId);
tableElement.attr(config.counterTaskAttr, taskName);
};
return {
config: config,
updateDateDiff: updateDateDiff,
initTableCounter: initTableCounter
initTimestampCounter: initTimestampCounter,
initTableCounter: initTableCounter,
destroyTimestampCounter: destroyTimestampCounter
};
});

View File

@@ -1,6 +1,7 @@
define([
'jquery',
'app/init',
'app/counter',
'app/promises/promise.deferred',
'app/promises/promise.timeout',
'datatables.net',
@@ -8,7 +9,7 @@ define([
'datatables.net-buttons-html',
'datatables.net-responsive',
'datatables.net-select'
], ($, Init, DeferredPromise, TimeoutPromise) => {
], ($, Init, Counter, DeferredPromise, TimeoutPromise) => {
'use strict';
// all Datatables stuff is available...
@@ -42,7 +43,7 @@ define([
}
// remove all active counters in table
table.destroyTimestampCounter(true);
Counter.destroyTimestampCounter(table, true);
});
// Status Plugin ==============================================================================================

View File

@@ -126,13 +126,13 @@ define([], () => {
let Cache = class Cache {
constructor(config){
this.config = Object.assign({
this.config = Object.assign({},{
name: 'Default', // custom name for identification
ttl: 3600,
maxSize: 600,
bufferSize: 10, // in percent
strategy: 'FIFO',
debug: false
ttl: 3600, // default ttl for cache entries
maxSize: 600, // max cache entries
bufferSize: 10, // cache entry count in percent to be removed if maxSize reached
strategy: 'FIFO', // cache strategy policy
debug: false // debug output in console
}, config);
this.store = new Map();

View File

@@ -0,0 +1,318 @@
define([
'easyTimer',
'app/promises/promise.timeout',
], (easytimer, TimeoutPromise) => {
'use strict';
/*
Example1 run task every second ------------------------------------------------------------------------------------
let task1 = Cron.new('task1', {precision: 'seconds', interval: 1, timeout: 100});
task1.task = (timer) => {
console.log('task1 function():', timer.getTotalTimeValues());
return 'OK';
};
Cron.set(task1);
Example2 run task every 3 seconds ---------------------------------------------------------------------------------
let task1 = Cron.new('task1', {precision: 'seconds', interval: 3, timeout: 100});
task1.task = (timer) => {
console.log('task1 function():', timer.getTotalTimeValues());
return 'OK';
};
Cron.set(task1);
Example3 resolve Promise on run ----------------------------------------------------------------------------------
let task1 = Cron.new('task1', {precision: 'seconds', interval: 1, timeout: 100});
task1.task = (timer, task) => {
return new Promise((resolve, reject) => {
console.log('task1 Promise1():', timer.getTotalTimeValues(), task.get('interval'));
//task.set('interval', task.get('interval') + 1) // increase run interval every time by 1s
resolve('OK1');
}).then(payload => {
return new Promise((resolve, reject) => {
console.log('task2 Promise2():', timer.getTotalTimeValues(), payload);
resolve('OK2');
});
});
};
Cron.set(task1);
Example4 run task once at given Date() --------------------------------------------------------------------------
let dueDate = new Date();
dueDate.setSeconds(dueDate.getSeconds() + 5);
let task2 = Cron.new('task2', {precision: 'seconds', timeout: 100, dueDate: dueDate});
task2.task = () => 'OK task2';
Cron.set(task2);
*/
/**
* Task instances represent a task that should be executed at a given interval or dueDate
* -> Task´s are managed by CronManager()
* @type {Task}
*/
let Task = class Task {
constructor(name, config){
if(typeof name !== 'string'){
throw new TypeError('Task "name" must be instance of String, Type of "' + typeof name + '" given');
}
this._config = Object.assign({}, this.constructor.defaultConfig, config);
this._name = name; // unique name for identification
this._task = undefined; // task to run, instanceof Function, can also return a Promise
this._manager = undefined; // reference to CronManager() that handles this task
this._runQueue = new Map(); // current run() processes. > 1 requires config.isParallel: true
this._runCount = 0; // total run counter for this task
this._lastTotalTimeValues = undefined; // time values of last run()
}
get name(){
return this._name;
}
get precision(){
return this._config.precision;
}
get task(){
return this._task;
}
set task(task){
if(task instanceof Function){
this._task = task;
}else{
throw new TypeError('Task "task" must be instance of "function", Type of "' + typeof task + '" given');
}
}
get(option){
return this._config[option];
}
set(option, value){
this._config[option] = value;
}
setManager(manager){
this._manager = manager;
}
isRunning(){
return !!this._runQueue.size;
}
delete(){
let isDeleted = false;
if(this._manager){
isDeleted = this._manager.delete(this.name);
}
return isDeleted;
}
isDue(timer){
if(this._config.dueDate instanceof Date){
// run once at dueDate
if(new Date().getTime() >= this._config.dueDate.getTime()){
return true;
}
}else{
// periodic execution
let totalTimeValues = timer.getTotalTimeValues();
let totalTimeValuePrecision = totalTimeValues[this.precision];
totalTimeValuePrecision -= this._lastTotalTimeValues ? this._lastTotalTimeValues[this.precision] : 0;
if(
this._config.interval === 1 ||
totalTimeValuePrecision % this._config.interval === 0
){
return true;
}
}
return false;
}
invoke(timer){
if(
this.isDue(timer) &&
(!this.isRunning() || this._config.isParallel)
){
this.run(timer);
}
}
run(timer){
this._lastTotalTimeValues = Object.assign({}, timer.getTotalTimeValues());
let runId = 'run_' + (++this._runCount);
let runExec = resolve => {
resolve(this.task(timer, this));
};
let myProm = this._config.timeout > 0 ? new TimeoutPromise(runExec, this._config.timeout) : new Promise(runExec);
myProm.then(payload => {
// resolved within timeout -> wait for finally() block
}).catch(error => {
if(error instanceof Error){
// either timeout error or error from rejected deferredPromise
console.warn(error);
}
}).finally(() => {
// no matter if TimeoutPromise is resolved or rejected
// -> remove from _runQueue
this._runQueue.delete(runId);
// remove this task from store after run
if(this._config.dueDate instanceof Date){
this.delete();
}
});
this._runQueue.set(runId, myProm);
}
};
Task.defaultConfig = {
precision: 'seconds', // updateEvent this tasked will be subscribed to
isParallel: false, // if true this task can run parallel, e.g. if prev execution has not finished
interval: 1, // relates to 'precision'. 'interval' = 3 and 'precision' = "seconds" -> run every 3 seconds
dueDate: undefined, // if Date() instance is set, task only runs once at dueDate
timeout: 50 // if > 0, execution time that exceeds timeout (ms) throw error
};
/**
* An instance of CronManager() handles multiple Task()´s
* -> Task()´s can be set()/delete() from CronManager() instance
* @type {CronManager}
*/
let CronManager = class CronManager {
constructor(config){
this._config = Object.assign({}, this.constructor.defaultConfig, config);
this._timerConfig = Object.assign({}, this.constructor.defaultTimerConfig);
this._tasks = new Map();
this._timer = new easytimer.Timer();
// init Easytimer update events
this._config.precisions.map(precision => precision + 'Updated').forEach(eventName => {
this._timer.on(eventName, e => {
let precision = e.type.substring(0, e.type.indexOf('Updated'));
this.tasksByPrecision(precision).forEach(task => task.invoke(e.detail.timer));
});
});
this.debug = (msg,...data) => {
if(this._config.debug){
data = (data || []);
console.info(msg, ...data);
}
};
}
new(name, config){
return new Task(name, config);
}
set(task){
if(task instanceof Task){
// check for unique task name, or update existing task
if(!this.has(task.name) || (this.get(task.name) === task)){
// set new or update existing task
task.setManager(this);
this._tasks.set(task.name, task);
this.debug('SET/UPDATE task: %o config: %o', task.name, task);
// start timer (if it is not already running)
this.auto();
}else{
console.warn('FAILED to set task. Task name %o already exists', task.name);
}
}else{
throw new TypeError('Parameter must be instance of Task');
}
}
setNew(name, config){
this.set(this.new(name, config));
}
get(name){
return this._tasks.get(name);
}
has(name){
return this._tasks.has(name);
}
delete(name){
let isDeleted = this._tasks.delete(name);
if(isDeleted){
this.debug('DELETE task: %o', name);
this.auto();
}
return isDeleted;
}
clear(){
this.debug('CLEAR all %o task(s)', this._tasks.size);
this._tasks.clear();
this.auto();
}
tasksByPrecision(precision){
let tasks = [];
this._tasks.forEach(task => {
if(precision === task.precision){
tasks.push(task);
}
});
return tasks;
}
// EasyTimer controls -----------------------------------------------------------------------------------------
start(){
this._timer.start(this._timerConfig);
}
stop(){
this._timer.stop();
}
pause(){
this._timer.pause();
}
reset(){
this._timer.reset();
}
auto(){
if(this._tasks.size){
if(!this._timer.isRunning()){
this.start();
this.debug('START [auto] timer. %o task(s) found.', this._tasks.size);
}
}else{
this.stop();
this.debug('STOP [auto] timer. No tasks set.');
}
}
};
CronManager.defaultConfig = {
precisions: [
'secondTenths',
'seconds',
'minutes',
'hours',
'days'
],
debug: false // debug output in console
};
CronManager.defaultTimerConfig = {
precision: 'secondTenths', // Timer update frequency. Values: 'secondTenths', 'seconds', 'minutes', 'hours'
countdown: false // If true, the timer is a countdown
};
return new CronManager({
debug: false
});
});

View File

@@ -6,8 +6,9 @@ define([
'jquery',
'app/init',
'app/util',
'app/counter',
'bootbox'
], ($, Init, Util, bootbox) => {
], ($, Init, Util, Counter, bootbox) => {
'use strict';
@@ -31,11 +32,11 @@ define([
* updated "sync status" dynamic dialog area
*/
let updateSyncStatus = () => {
// check if task manager dialog is open
let logDialog = $('#' + config.taskDialogId);
if(logDialog.length){
// dialog is open
let statusArea = logDialog.find('.' + config.taskDialogStatusAreaClass);
requirejs(['text!templates/modules/sync_status.html', 'mustache'], (templateSyncStatus, Mustache) => {
let data = {
timestampCounterClass: config.timestampCounterClass,
@@ -49,10 +50,12 @@ define([
};
let syncStatusElement = $(Mustache.render(templateSyncStatus, data ));
Counter.destroyTimestampCounter(statusArea, true);
logDialog.find('.' + config.taskDialogStatusAreaClass).html( syncStatusElement );
statusArea.html(syncStatusElement);
logDialog.find('.' + config.timestampCounterClass).initTimestampCounter();
let counterElements = syncStatusElement.find('.' + config.timestampCounterClass);
Counter.initTimestampCounter(counterElements);
syncStatusElement.initTooltips({
placement: 'right'

View File

@@ -6,6 +6,7 @@ define([
'jquery',
'app/init',
'app/util',
'app/counter',
'app/logging',
'mustache',
'app/map/util',
@@ -26,7 +27,7 @@ define([
'dialog/credit',
'xEditable',
'app/module_map'
], ($, Init, Util, Logging, Mustache, MapUtil, MapContextMenu, SlideBars, TplHead, TplFooter) => {
], ($, Init, Util, Counter, Logging, Mustache, MapUtil, MapContextMenu, SlideBars, TplHead, TplFooter) => {
'use strict';
@@ -879,7 +880,6 @@ define([
// global "modal" callback --------------------------------------------------------------------------------
bodyElement.on('hide.bs.modal', '> .modal', e => {
let modalElement = $(e.target);
modalElement.destroyTimestampCounter(true);
// destroy all form validators
// -> does not work properly. validation functions still used (js error) after 'destroy'
@@ -892,6 +892,14 @@ define([
modalElement.find('.' + Util.config.select2Class)
.filter((i, element) => $(element).data('select2'))
.select2('destroy');
// destroy DataTable instances
for(let table of modalElement.find('table.dataTable')){
$(table).DataTable().destroy(true);
}
// destroy counter
Counter.destroyTimestampCounter(modalElement, true);
});
// global "close" trigger for context menus ---------------------------------------------------------------

View File

@@ -7,10 +7,10 @@ define([
'app/init',
'app/util',
'app/render',
'bootbox',
'app/counter',
'bootbox',
'app/map/util'
], ($, Init, Util, Render, bootbox, Counter, MapUtil) => {
], ($, Init, Util, Render, Counter, bootbox, MapUtil) => {
'use strict';
@@ -182,7 +182,7 @@ define([
mapElement.append(dlElementRight);
// init map lifetime counter
$('.' + config.mapInfoLifetimeCounterClass).initTimestampCounter();
Counter.initTimestampCounter($('.' + config.mapInfoLifetimeCounterClass));
mapElement.find('.' + config.textActionIconCopyClass).on('click', function(){
let mapUrl = $(this).find('span').text().trim();

View File

@@ -8,9 +8,10 @@ define([
'app/init',
'app/util',
'app/render',
'app/counter',
'bootbox',
'peityInlineChart'
], ($, Init, Util, Render, bootbox) => {
], ($, Init, Util, Render, Counter, bootbox) => {
'use strict';
let config = {
@@ -37,7 +38,7 @@ define([
* init blank statistics dataTable
* @param dialogElement
*/
let initStatsTable = function(dialogElement){
let initStatsTable = dialogElement => {
let columnNumberWidth = 28;
let cellPadding = 4;
let lineChartWidth = columnNumberWidth + (2 * cellPadding);
@@ -114,6 +115,7 @@ define([
columnDefs: [
{
targets: 0,
name: 'rowIndex',
title: '<i class="fas fa-hashtag"></i>',
orderable: false,
searchable: false,
@@ -122,6 +124,7 @@ define([
data: 'character.id'
},{
targets: 1,
name: 'image',
title: '',
orderable: false,
searchable: false,
@@ -135,6 +138,7 @@ define([
}
},{
targets: 2,
name: 'name',
title: 'name',
width: 200,
data: 'character',
@@ -144,20 +148,16 @@ define([
}
},{
targets: 3,
name: 'lastLogin',
title: 'last login',
searchable: false,
width: 70,
className: ['text-right', 'separator-right'].join(' '),
data: 'character',
render: {
_: 'lastLogin',
sort: 'lastLogin'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter();
}
data: 'character.lastLogin',
defaultContent: ''
},{
targets: 4,
name: 'mapCreate',
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -169,6 +169,7 @@ define([
}
},{
targets: 5,
name: 'mapUpdate',
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -180,6 +181,7 @@ define([
}
},{
targets: 6,
name: 'mapDelete',
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -191,6 +193,7 @@ define([
}
},{
targets: 7,
name: 'mapSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -201,6 +204,7 @@ define([
}
},{
targets: 8,
name: 'systemCreate',
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -212,6 +216,7 @@ define([
}
},{
targets: 9,
name: 'systemUpdate',
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -223,6 +228,7 @@ define([
}
},{
targets: 10,
name: 'systemDelete',
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -234,6 +240,7 @@ define([
}
},{
targets: 11,
name: 'systemSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -244,6 +251,7 @@ define([
}
},{
targets: 12,
name: 'connectionCreate',
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -255,6 +263,7 @@ define([
}
},{
targets: 13,
name: 'connectionUpdate',
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -266,6 +275,7 @@ define([
}
},{
targets: 14,
name: 'connectionDelete',
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -277,6 +287,7 @@ define([
}
},{
targets: 15,
name: 'connectionSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -287,6 +298,7 @@ define([
}
},{
targets: 16,
name: 'signatureCreate',
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -298,6 +310,7 @@ define([
}
},{
targets: 17,
name: 'signatureUpdate',
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -309,6 +322,7 @@ define([
}
},{
targets: 18,
name: 'signatureDelete',
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
@@ -320,6 +334,7 @@ define([
}
},{
targets: 19,
name: 'signatureSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -330,6 +345,7 @@ define([
}
},{
targets: 20,
name: 'totalSum',
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
@@ -346,6 +362,8 @@ define([
// initial statistics data request
let requestData = getRequestDataFromTabPanels(dialogElement);
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
Counter.initTableCounter(this, ['lastLogin:name']);
},
drawCallback: function(settings){
this.api().rows().nodes().to$().each(function(i, row){
@@ -374,7 +392,7 @@ define([
});
$(sumColumnIndexes).each(function(index, value){
$( api.column( value ).footer() ).text( renderNumericColumn(pageTotalColumns[index], 'display') );
$(api.column(value).footer()).text( renderNumericColumn(pageTotalColumns[index], 'display') );
});
},
data: [] // will be added dynamic
@@ -404,8 +422,7 @@ define([
* @param requestData
* @param context
*/
let getStatsData = function(requestData, context){
let getStatsData = (requestData, context) => {
context.dynamicArea = $('#' + config.statsContainerId + ' .' + Util.config.dynamicAreaClass);
context.dynamicArea.showLoadingAnimation();
@@ -418,7 +435,7 @@ define([
}).done(function(data){
this.dynamicArea.hideLoadingAnimation();
this.callback(data);
this.callback(data, this);
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': loadStatistics', text: reason, type: 'warning'});
@@ -430,7 +447,7 @@ define([
* update "header"/"filter" elements in dialog
* @param responseData
*/
let drawStatsTable = function(responseData){
let drawStatsTable = (responseData, context) => {
let dialogElement = $('#' + config.statsDialogId);
// update filter/header -----------------------------------------------------------------------------
@@ -467,8 +484,8 @@ define([
// clear and (re)-fill table ------------------------------------------------------------------------
let formattedData = formatStatisticsData(responseData);
this.tableApi.clear();
this.tableApi.rows.add(formattedData).draw();
context.tableApi.clear();
context.tableApi.rows.add(formattedData).draw();
};
/**
@@ -477,7 +494,7 @@ define([
* @param statsData
* @returns {Array}
*/
let formatStatisticsData = function(statsData){
let formatStatisticsData = statsData => {
let formattedData = [];
let yearStart = statsData.start.year;
let weekStart = statsData.start.week;
@@ -687,7 +704,7 @@ define([
* @param dialogElement
* @returns {{}}
*/
let getRequestDataFromTabPanels = function(dialogElement){
let getRequestDataFromTabPanels = dialogElement => {
let requestData = {};
// get data from "tab" panel links ------------------------------------------------------------------

View File

@@ -7,8 +7,9 @@ define([
'app/init',
'app/util',
'bootbox',
'app/counter',
'app/map/util'
], ($, Init, Util, bootbox, MapUtil) => {
], ($, Init, Util, bootbox, Counter, MapUtil) => {
'use strict';
let config = {
@@ -827,10 +828,7 @@ define([
title: 'log',
width: 55,
className: ['text-right', config.tableCellCounterClass].join(' '),
data: 'created.created',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter('d');
}
data: 'created.created'
},{
targets: 5,
name: 'edit',
@@ -931,6 +929,9 @@ define([
}
}
],
initComplete: function(settings, json){
Counter.initTableCounter(this, ['created:name']);
},
drawCallback: function(settings){
let animationRows = this.api().rows().nodes().to$().filter(function(a,b ){
return (

View File

@@ -6,9 +6,8 @@ define([
'jquery',
'app/init',
'app/util',
'app/cache',
'morris'
], ($, Init, Util, Cache, Morris) => {
'app/lib/cache'
], ($, Init, Util, Cache) => {
'use strict';
let config = {

View File

@@ -6,13 +6,13 @@ define([
'jquery',
'app/init',
'app/util',
'app/cache',
'bootbox',
'app/counter',
'app/map/map',
'app/map/util',
'app/lib/cache',
'app/ui/form_element'
], ($, Init, Util, Cache, bootbox, Counter, Map, MapUtil, FormElement) => {
], ($, Init, Util, bootbox, Counter, Map, MapUtil, Cache, FormElement) => {
'use strict';
let config = {
@@ -482,7 +482,7 @@ define([
// hide row
// stop sig counter by adding a stopClass to each <td>, remove padding
cellElements.addClass('stopCounter')
cellElements.addClass(Counter.config.counterStopClass)
.velocity({
paddingTop: [0, '4px'],
paddingBottom: [0, '4px'],

View File

@@ -742,7 +742,7 @@ define([
let defaultOptions = {
dismissible: true,
messageId: 'pf-alert-' + Math.random().toString(36).substring(7),
messageId: getRandomString('pf-alert-'),
messageTypeClass: messageTypeClass,
messageTextClass: messageTextClass,
insertElement: 'replace'
@@ -873,7 +873,8 @@ define([
if(!resourceVariant){
switch(resourceType){
case 'factions': resourceType = 'corporations'; // faction icons are on 'corporations' endpoint.. CCP fail?!
// faction icons are on 'corporations' endpoint.. CCP fail?!
case 'factions': resourceType = 'corporations'; // jshint ignore:line
case 'alliances':
case 'corporations': resourceVariant = 'logo'; break;
case 'characters': resourceVariant = 'portrait'; break;
@@ -1580,6 +1581,14 @@ define([
return Init.timer[updateKey].CURRENT_DELAY;
};
/**
* get a random string
* -> e.g. as for Ids
* @param prefix
* @returns {string}
*/
let getRandomString = (prefix = 'id_') => prefix + Math.random().toString(36).substring(2,10);
/**
* get date obj with current EVE Server Time.
* @returns {Date}
@@ -1857,7 +1866,7 @@ define([
let key = 'tabId';
let tabId = sessionStorage.getItem(key);
if(tabId === null){
tabId = Math.random().toString(36).substr(2, 5);
tabId = getRandomString();
sessionStorage.setItem(key, tabId);
}
return tabId;
@@ -3521,6 +3530,7 @@ define([
initDefaultSelect2Config: initDefaultSelect2Config,
initDefaultEditableConfig: initDefaultEditableConfig,
getCurrentTriggerDelay: getCurrentTriggerDelay,
getRandomString: getRandomString,
getServerTime: getServerTime,
convertTimestampToServerTime: convertTimestampToServerTime,
getTimeDiffParts: getTimeDiffParts,

7
public/js/v1.5.5/lib/easytimer.min.js vendored Normal file

File diff suppressed because one or more lines are too long