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