diff --git a/.jshintrc b/.jshintrc
index 757b37a9..49787aca 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -13,8 +13,8 @@
// Define globals exposed by Node.js.
"node": true,
- // Allow ES6.
- "esversion": 7,
+ // Allow ES8.
+ "esversion": 8,
/*
* ENFORCING OPTIONS
diff --git a/js/app.js b/js/app.js
index ec4b4109..5bc67f69 100644
--- a/js/app.js
+++ b/js/app.js
@@ -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
diff --git a/js/app/counter.js b/js/app/counter.js
index 672893ae..43d670b7 100644
--- a/js/app/counter.js
+++ b/js/app/counter.js
@@ -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
};
});
diff --git a/js/app/datatables.loader.js b/js/app/datatables.loader.js
index b702148f..6f0aa089 100644
--- a/js/app/datatables.loader.js
+++ b/js/app/datatables.loader.js
@@ -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 ==============================================================================================
diff --git a/js/app/cache.js b/js/app/lib/cache.js
similarity index 94%
rename from js/app/cache.js
rename to js/app/lib/cache.js
index e23f9d1f..5432d0d1 100644
--- a/js/app/cache.js
+++ b/js/app/lib/cache.js
@@ -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();
diff --git a/js/app/lib/cron.js b/js/app/lib/cron.js
new file mode 100644
index 00000000..d60f6951
--- /dev/null
+++ b/js/app/lib/cron.js
@@ -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
+ });
+});
\ No newline at end of file
diff --git a/js/app/logging.js b/js/app/logging.js
index 5b36851d..dd85f9f1 100644
--- a/js/app/logging.js
+++ b/js/app/logging.js
@@ -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'
diff --git a/js/app/page.js b/js/app/page.js
index b6306418..0ffddd64 100644
--- a/js/app/page.js
+++ b/js/app/page.js
@@ -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 ---------------------------------------------------------------
diff --git a/js/app/ui/dialog/map_info.js b/js/app/ui/dialog/map_info.js
index 65e9d27b..04359396 100644
--- a/js/app/ui/dialog/map_info.js
+++ b/js/app/ui/dialog/map_info.js
@@ -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();
diff --git a/js/app/ui/dialog/stats.js b/js/app/ui/dialog/stats.js
index 9e5d2160..6d30fd64 100644
--- a/js/app/ui/dialog/stats.js
+++ b/js/app/ui/dialog/stats.js
@@ -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: '',
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: 'C ',
orderable: false,
searchable: false,
@@ -169,6 +169,7 @@ define([
}
},{
targets: 5,
+ name: 'mapUpdate',
title: 'U ',
orderable: false,
searchable: false,
@@ -180,6 +181,7 @@ define([
}
},{
targets: 6,
+ name: 'mapDelete',
title: 'D ',
orderable: false,
searchable: false,
@@ -191,6 +193,7 @@ define([
}
},{
targets: 7,
+ name: 'mapSum',
title: 'Σ ',
searchable: false,
width: 20,
@@ -201,6 +204,7 @@ define([
}
},{
targets: 8,
+ name: 'systemCreate',
title: 'C ',
orderable: false,
searchable: false,
@@ -212,6 +216,7 @@ define([
}
},{
targets: 9,
+ name: 'systemUpdate',
title: 'U ',
orderable: false,
searchable: false,
@@ -223,6 +228,7 @@ define([
}
},{
targets: 10,
+ name: 'systemDelete',
title: 'D ',
orderable: false,
searchable: false,
@@ -234,6 +240,7 @@ define([
}
},{
targets: 11,
+ name: 'systemSum',
title: 'Σ ',
searchable: false,
width: 20,
@@ -244,6 +251,7 @@ define([
}
},{
targets: 12,
+ name: 'connectionCreate',
title: 'C ',
orderable: false,
searchable: false,
@@ -255,6 +263,7 @@ define([
}
},{
targets: 13,
+ name: 'connectionUpdate',
title: 'U ',
orderable: false,
searchable: false,
@@ -266,6 +275,7 @@ define([
}
},{
targets: 14,
+ name: 'connectionDelete',
title: 'D ',
orderable: false,
searchable: false,
@@ -277,6 +287,7 @@ define([
}
},{
targets: 15,
+ name: 'connectionSum',
title: 'Σ ',
searchable: false,
width: 20,
@@ -287,6 +298,7 @@ define([
}
},{
targets: 16,
+ name: 'signatureCreate',
title: 'C ',
orderable: false,
searchable: false,
@@ -298,6 +310,7 @@ define([
}
},{
targets: 17,
+ name: 'signatureUpdate',
title: 'U ',
orderable: false,
searchable: false,
@@ -309,6 +322,7 @@ define([
}
},{
targets: 18,
+ name: 'signatureDelete',
title: 'D ',
orderable: false,
searchable: false,
@@ -320,6 +334,7 @@ define([
}
},{
targets: 19,
+ name: 'signatureSum',
title: 'Σ ',
searchable: false,
width: 20,
@@ -330,6 +345,7 @@ define([
}
},{
targets: 20,
+ name: 'totalSum',
title: 'Σ ',
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 ------------------------------------------------------------------
diff --git a/js/app/ui/module/connection_info.js b/js/app/ui/module/connection_info.js
index 59121011..10429887 100644
--- a/js/app/ui/module/connection_info.js
+++ b/js/app/ui/module/connection_info.js
@@ -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 (
diff --git a/js/app/ui/module/system_killboard.js b/js/app/ui/module/system_killboard.js
index 8b56718f..c707f00f 100644
--- a/js/app/ui/module/system_killboard.js
+++ b/js/app/ui/module/system_killboard.js
@@ -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 = {
diff --git a/js/app/ui/module/system_signature.js b/js/app/ui/module/system_signature.js
index 4a215076..6f4dad27 100644
--- a/js/app/ui/module/system_signature.js
+++ b/js/app/ui/module/system_signature.js
@@ -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
, remove padding
- cellElements.addClass('stopCounter')
+ cellElements.addClass(Counter.config.counterStopClass)
.velocity({
paddingTop: [0, '4px'],
paddingBottom: [0, '4px'],
diff --git a/js/app/util.js b/js/app/util.js
index 3007ae82..9df3fef2 100644
--- a/js/app/util.js
+++ b/js/app/util.js
@@ -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,
diff --git a/js/lib/easytimer.min.js b/js/lib/easytimer.min.js
new file mode 100644
index 00000000..8e8edc89
--- /dev/null
+++ b/js/lib/easytimer.min.js
@@ -0,0 +1,7 @@
+/**
+ * easytimer.js
+ * Generated: 2019-10-31
+ * Version: 4.0.2
+ */
+
+!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((n=n||self).easytimer={})}(this,function(n){"use strict";function q(n){return(q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&"function"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?"symbol":typeof n})(n)}function i(n,t,e){var o,r="";if((n="number"==typeof n?String(n):n).length>t)return n;for(o=0;o {
+ '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
};
});
diff --git a/public/js/v1.5.5/app/datatables.loader.js b/public/js/v1.5.5/app/datatables.loader.js
index b702148f..6f0aa089 100644
--- a/public/js/v1.5.5/app/datatables.loader.js
+++ b/public/js/v1.5.5/app/datatables.loader.js
@@ -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 ==============================================================================================
diff --git a/public/js/v1.5.5/app/cache.js b/public/js/v1.5.5/app/lib/cache.js
similarity index 94%
rename from public/js/v1.5.5/app/cache.js
rename to public/js/v1.5.5/app/lib/cache.js
index e23f9d1f..5432d0d1 100644
--- a/public/js/v1.5.5/app/cache.js
+++ b/public/js/v1.5.5/app/lib/cache.js
@@ -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();
diff --git a/public/js/v1.5.5/app/lib/cron.js b/public/js/v1.5.5/app/lib/cron.js
new file mode 100644
index 00000000..d60f6951
--- /dev/null
+++ b/public/js/v1.5.5/app/lib/cron.js
@@ -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
+ });
+});
\ No newline at end of file
diff --git a/public/js/v1.5.5/app/logging.js b/public/js/v1.5.5/app/logging.js
index 5b36851d..dd85f9f1 100644
--- a/public/js/v1.5.5/app/logging.js
+++ b/public/js/v1.5.5/app/logging.js
@@ -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'
diff --git a/public/js/v1.5.5/app/page.js b/public/js/v1.5.5/app/page.js
index b6306418..0ffddd64 100644
--- a/public/js/v1.5.5/app/page.js
+++ b/public/js/v1.5.5/app/page.js
@@ -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 ---------------------------------------------------------------
diff --git a/public/js/v1.5.5/app/ui/dialog/map_info.js b/public/js/v1.5.5/app/ui/dialog/map_info.js
index 65e9d27b..04359396 100644
--- a/public/js/v1.5.5/app/ui/dialog/map_info.js
+++ b/public/js/v1.5.5/app/ui/dialog/map_info.js
@@ -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();
diff --git a/public/js/v1.5.5/app/ui/dialog/stats.js b/public/js/v1.5.5/app/ui/dialog/stats.js
index 9e5d2160..6d30fd64 100644
--- a/public/js/v1.5.5/app/ui/dialog/stats.js
+++ b/public/js/v1.5.5/app/ui/dialog/stats.js
@@ -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: '',
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: 'C ',
orderable: false,
searchable: false,
@@ -169,6 +169,7 @@ define([
}
},{
targets: 5,
+ name: 'mapUpdate',
title: 'U ',
orderable: false,
searchable: false,
@@ -180,6 +181,7 @@ define([
}
},{
targets: 6,
+ name: 'mapDelete',
title: 'D ',
orderable: false,
searchable: false,
@@ -191,6 +193,7 @@ define([
}
},{
targets: 7,
+ name: 'mapSum',
title: 'Σ ',
searchable: false,
width: 20,
@@ -201,6 +204,7 @@ define([
}
},{
targets: 8,
+ name: 'systemCreate',
title: 'C ',
orderable: false,
searchable: false,
@@ -212,6 +216,7 @@ define([
}
},{
targets: 9,
+ name: 'systemUpdate',
title: 'U ',
orderable: false,
searchable: false,
@@ -223,6 +228,7 @@ define([
}
},{
targets: 10,
+ name: 'systemDelete',
title: 'D ',
orderable: false,
searchable: false,
@@ -234,6 +240,7 @@ define([
}
},{
targets: 11,
+ name: 'systemSum',
title: 'Σ ',
searchable: false,
width: 20,
@@ -244,6 +251,7 @@ define([
}
},{
targets: 12,
+ name: 'connectionCreate',
title: 'C ',
orderable: false,
searchable: false,
@@ -255,6 +263,7 @@ define([
}
},{
targets: 13,
+ name: 'connectionUpdate',
title: 'U ',
orderable: false,
searchable: false,
@@ -266,6 +275,7 @@ define([
}
},{
targets: 14,
+ name: 'connectionDelete',
title: 'D ',
orderable: false,
searchable: false,
@@ -277,6 +287,7 @@ define([
}
},{
targets: 15,
+ name: 'connectionSum',
title: 'Σ ',
searchable: false,
width: 20,
@@ -287,6 +298,7 @@ define([
}
},{
targets: 16,
+ name: 'signatureCreate',
title: 'C ',
orderable: false,
searchable: false,
@@ -298,6 +310,7 @@ define([
}
},{
targets: 17,
+ name: 'signatureUpdate',
title: 'U ',
orderable: false,
searchable: false,
@@ -309,6 +322,7 @@ define([
}
},{
targets: 18,
+ name: 'signatureDelete',
title: 'D ',
orderable: false,
searchable: false,
@@ -320,6 +334,7 @@ define([
}
},{
targets: 19,
+ name: 'signatureSum',
title: 'Σ ',
searchable: false,
width: 20,
@@ -330,6 +345,7 @@ define([
}
},{
targets: 20,
+ name: 'totalSum',
title: 'Σ ',
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 ------------------------------------------------------------------
diff --git a/public/js/v1.5.5/app/ui/module/connection_info.js b/public/js/v1.5.5/app/ui/module/connection_info.js
index 59121011..10429887 100644
--- a/public/js/v1.5.5/app/ui/module/connection_info.js
+++ b/public/js/v1.5.5/app/ui/module/connection_info.js
@@ -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 (
diff --git a/public/js/v1.5.5/app/ui/module/system_killboard.js b/public/js/v1.5.5/app/ui/module/system_killboard.js
index 8b56718f..c707f00f 100644
--- a/public/js/v1.5.5/app/ui/module/system_killboard.js
+++ b/public/js/v1.5.5/app/ui/module/system_killboard.js
@@ -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 = {
diff --git a/public/js/v1.5.5/app/ui/module/system_signature.js b/public/js/v1.5.5/app/ui/module/system_signature.js
index 4a215076..6f4dad27 100644
--- a/public/js/v1.5.5/app/ui/module/system_signature.js
+++ b/public/js/v1.5.5/app/ui/module/system_signature.js
@@ -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 | , remove padding
- cellElements.addClass('stopCounter')
+ cellElements.addClass(Counter.config.counterStopClass)
.velocity({
paddingTop: [0, '4px'],
paddingBottom: [0, '4px'],
diff --git a/public/js/v1.5.5/app/util.js b/public/js/v1.5.5/app/util.js
index 3007ae82..9df3fef2 100644
--- a/public/js/v1.5.5/app/util.js
+++ b/public/js/v1.5.5/app/util.js
@@ -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,
diff --git a/public/js/v1.5.5/lib/easytimer.min.js b/public/js/v1.5.5/lib/easytimer.min.js
new file mode 100644
index 00000000..8e8edc89
--- /dev/null
+++ b/public/js/v1.5.5/lib/easytimer.min.js
@@ -0,0 +1,7 @@
+/**
+ * easytimer.js
+ * Generated: 2019-10-31
+ * Version: 4.0.2
+ */
+
+!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((n=n||self).easytimer={})}(this,function(n){"use strict";function q(n){return(q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&"function"==typeof Symbol&&n.constructor===Symbol&&n!==Symbol.prototype?"symbol":typeof n})(n)}function i(n,t,e){var o,r="";if((n="number"==typeof n?String(n):n).length>t)return n;for(o=0;o | |