- v2.0.0 JS/CSS/IMG "production" build files
This commit is contained in:
BIN
public/css/v2.0.0/pathfinder.css.br
Normal file
BIN
public/css/v2.0.0/pathfinder.css.br
Normal file
Binary file not shown.
@@ -1,176 +1,2 @@
|
||||
'use strict';
|
||||
|
||||
// main script path
|
||||
var mainScriptPath = document.body.getAttribute('data-script');
|
||||
|
||||
// js baseURL. Depends on the environment.
|
||||
// e.g. use raw files (develop) or build files (production)
|
||||
var jsBaseUrl = document.body.getAttribute('data-js-path');
|
||||
|
||||
// requireJs configuration
|
||||
requirejs.config({
|
||||
baseUrl: 'js', // src root path - dynamically set !below! ("build_js" | "js")
|
||||
|
||||
paths: {
|
||||
conf: 'app/conf', // path config files
|
||||
dialog: 'app/ui/dialog', // path dialog files
|
||||
layout: 'app/ui/layout', // path layout files
|
||||
module: 'app/ui/module', // path module files
|
||||
|
||||
templates: '../../templates', // path template base dir
|
||||
img: '../../img', // path image base dir
|
||||
|
||||
// main views
|
||||
login: './app/login', // initial start "login page" view
|
||||
mappage: './app/mappage', // initial start "map page" view
|
||||
setup: './app/setup', // initial start "setup page" view
|
||||
admin: './app/admin', // initial start "admin page" view
|
||||
|
||||
jquery: 'lib/jquery-3.4.1.min', // v3.4.1 jQuery
|
||||
bootstrap: 'lib/bootstrap.min', // v3.3.0 Bootstrap js code - http://getbootstrap.com/javascript
|
||||
text: 'lib/requirejs/text', // v2.0.12 A RequireJS/AMD loader plugin for loading text resources.
|
||||
mustache: 'lib/mustache.min', // v3.0.1 Javascript template engine - http://mustache.github.io
|
||||
localForage: 'lib/localforage.min', // v1.7.3 localStorage library - https://localforage.github.io/localForage/
|
||||
velocity: 'lib/velocity.min', // v1.5.1 animation engine - http://julian.com/research/velocity
|
||||
velocityUI: 'lib/velocity.ui.min', // v5.2.0 plugin for velocity - http://julian.com/research/velocity/#uiPack
|
||||
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
|
||||
morris: 'lib/morris.min', // v0.6.4 Morris.js - graphs and charts - https://github.com/pierresh/morris.js
|
||||
raphael: 'lib/raphael.min', // v2.3.0 Raphaël - required for morris - https://dmitrybaranovskiy.github.io/raphael
|
||||
bootbox: 'lib/bootbox.min', // v5.2.0 Bootbox.js - custom dialogs - http://bootboxjs.com
|
||||
easyPieChart: 'lib/jquery.easypiechart.min', // v2.1.6 Easy Pie Chart - HTML 5 pie charts - http://rendro.github.io/easy-pie-chart
|
||||
peityInlineChart: 'lib/jquery.peity.min', // v3.3.0 Inline Chart - http://benpickles.github.io/peity/
|
||||
hoverIntent: 'lib/jquery.hoverIntent.min', // v1.10.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
|
||||
select2: 'lib/select2.min', // v4.0.13 Drop Down customization - https://select2.github.io
|
||||
validator: 'lib/validator.min', // v0.10.1 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
|
||||
blueImpGallery: 'lib/blueimp-gallery', // v2.21.3 Image Gallery - https://github.com/blueimp/Gallery
|
||||
blueImpGalleryHelper: 'lib/blueimp-helper', // helper function for Blue Imp Gallery
|
||||
blueImpGalleryBootstrap: 'lib/bootstrap-image-gallery', // v3.4.2 Bootstrap extension for Blue Imp Gallery - https://blueimp.github.io/Bootstrap-Image-Gallery
|
||||
bootstrapConfirmation: 'lib/bootstrap-confirmation.min', // v1.0.7 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
|
||||
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
|
||||
lazyload: 'lib/lazyload.min', // v14.0.0 LazyLoader images - https://github.com/verlok/lazyload
|
||||
sortable: 'lib/sortable.min', // v1.10.1 Sortable - drag&drop reorder - https://github.com/SortableJS/Sortable
|
||||
|
||||
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org
|
||||
'summernote': 'lib/summernote/summernote.min',
|
||||
|
||||
// DataTables // v1.10.18 DataTables - https://datatables.net
|
||||
'datatables.loader': './app/datatables.loader',
|
||||
'datatables.net': 'lib/datatables/DataTables-1.10.18/js/jquery.dataTables.min',
|
||||
'datatables.net-buttons': 'lib/datatables/Buttons-1.5.6/js/dataTables.buttons.min',
|
||||
'datatables.net-buttons-html': 'lib/datatables/Buttons-1.5.6/js/buttons.html5.min',
|
||||
'datatables.net-responsive': 'lib/datatables/Responsive-2.2.2/js/dataTables.responsive.min',
|
||||
'datatables.net-rowgroup': 'lib/datatables/RowGroup-1.1.1/js/dataTables.rowGroup.min',
|
||||
'datatables.net-select': 'lib/datatables/Select-1.3.0/js/dataTables.select.min',
|
||||
'datatables.plugins.render.ellipsis': 'lib/datatables/plugins/render/ellipsis',
|
||||
|
||||
// PNotify // v4.0.0 PNotify - notification core file - https://sciactive.com/pnotify
|
||||
'pnotify.loader': './app/pnotify.loader',
|
||||
'PNotify': 'lib/pnotify/PNotify',
|
||||
'PNotifyButtons': 'lib/pnotify/PNotifyButtons',
|
||||
'PNotifyCallbacks': 'lib/pnotify/PNotifyCallbacks',
|
||||
'PNotifyDesktop': 'lib/pnotify/PNotifyDesktop',
|
||||
'NonBlock': 'lib/pnotify/NonBlock' // v1.0.8 NonBlock.js - for PNotify "nonblock" feature
|
||||
},
|
||||
shim: {
|
||||
bootstrap: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
farahey: {
|
||||
deps: ['jsPlumb']
|
||||
},
|
||||
velocity: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
velocityUI: {
|
||||
deps: ['velocity']
|
||||
},
|
||||
slidebars: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
customScrollbar: {
|
||||
deps: ['jquery', 'mousewheel']
|
||||
},
|
||||
'datatables.loader': {
|
||||
deps: ['jquery']
|
||||
},
|
||||
'datatables.net': {
|
||||
deps: ['jquery']
|
||||
},
|
||||
'datatables.net-buttons': {
|
||||
deps: ['datatables.net']
|
||||
},
|
||||
'datatables.net-buttons-html': {
|
||||
deps: ['datatables.net-buttons']
|
||||
},
|
||||
'datatables.net-responsive': {
|
||||
deps: ['datatables.net']
|
||||
},
|
||||
'datatables.net-rowgroup': {
|
||||
deps: ['datatables.net']
|
||||
},
|
||||
'datatables.net-select': {
|
||||
deps: ['datatables.net']
|
||||
},
|
||||
'datatables.plugins.render.ellipsis': {
|
||||
deps: ['datatables.net']
|
||||
},
|
||||
xEditable: {
|
||||
deps: ['bootstrap']
|
||||
},
|
||||
bootbox: {
|
||||
deps: ['jquery', 'bootstrap'],
|
||||
exports: 'bootbox'
|
||||
},
|
||||
morris: {
|
||||
deps: ['jquery', 'raphael'],
|
||||
exports: 'Morris',
|
||||
init: function ($, Raphael) {
|
||||
window.Raphael = Raphael;
|
||||
}
|
||||
},
|
||||
easyPieChart: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
peityInlineChart: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
hoverIntent: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
select2: {
|
||||
deps: ['jquery', 'mousewheel'],
|
||||
exports: 'Select2'
|
||||
},
|
||||
validator: {
|
||||
deps: ['jquery', 'bootstrap']
|
||||
},
|
||||
blueImpGallery: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
bootstrapConfirmation: {
|
||||
deps: ['bootstrap']
|
||||
},
|
||||
bootstrapToggle: {
|
||||
deps: ['jquery', 'bootstrap']
|
||||
},
|
||||
summernote: {
|
||||
deps: ['jquery']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// switch baseUrl to js "build_js" in production environment
|
||||
// this has no effect for js build process!
|
||||
// check build.js for build configuration
|
||||
require.config({
|
||||
baseUrl: jsBaseUrl
|
||||
});
|
||||
|
||||
// load the main app module -> initial app start
|
||||
requirejs( [mainScriptPath] );
|
||||
"use strict";var mainScriptPath=document.body.getAttribute("data-script"),jsBaseUrl=document.body.getAttribute("data-js-path");requirejs.config({baseUrl:"js",paths:{conf:"app/conf",dialog:"app/ui/dialog",layout:"app/ui/layout",module:"app/ui/module",templates:"../../templates",img:"../../img",login:"./app/login",mappage:"./app/mappage",setup:"./app/setup",admin:"./app/admin",jquery:"lib/jquery-3.4.1.min",bootstrap:"lib/bootstrap.min",text:"lib/requirejs/text",mustache:"lib/mustache.min",localForage:"lib/localforage.min",velocity:"lib/velocity.min",velocityUI:"lib/velocity.ui.min",slidebars:"lib/slidebars",jsPlumb:"lib/jsplumb",farahey:"lib/farahey",easyTimer:"lib/easytimer.min",customScrollbar:"lib/jquery.mCustomScrollbar.min",mousewheel:"lib/jquery.mousewheel.min",xEditable:"lib/bootstrap-editable.min",morris:"lib/morris.min",raphael:"lib/raphael.min",bootbox:"lib/bootbox.min",easyPieChart:"lib/jquery.easypiechart.min",peityInlineChart:"lib/jquery.peity.min",hoverIntent:"lib/jquery.hoverIntent.min",select2:"lib/select2.min",validator:"lib/validator.min",blueImpGallery:"lib/blueimp-gallery",blueImpGalleryHelper:"lib/blueimp-helper",blueImpGalleryBootstrap:"lib/bootstrap-image-gallery",bootstrapConfirmation:"lib/bootstrap-confirmation.min",bootstrapToggle:"lib/bootstrap-toggle.min",lazyload:"lib/lazyload.min",sortable:"lib/sortable.min","summernote.loader":"./app/summernote.loader",summernote:"lib/summernote/summernote.min","datatables.loader":"./app/datatables.loader","datatables.net":"lib/datatables/DataTables-1.10.18/js/jquery.dataTables.min","datatables.net-buttons":"lib/datatables/Buttons-1.5.6/js/dataTables.buttons.min","datatables.net-buttons-html":"lib/datatables/Buttons-1.5.6/js/buttons.html5.min","datatables.net-responsive":"lib/datatables/Responsive-2.2.2/js/dataTables.responsive.min","datatables.net-rowgroup":"lib/datatables/RowGroup-1.1.1/js/dataTables.rowGroup.min","datatables.net-select":"lib/datatables/Select-1.3.0/js/dataTables.select.min","datatables.plugins.render.ellipsis":"lib/datatables/plugins/render/ellipsis","pnotify.loader":"./app/pnotify.loader",PNotify:"lib/pnotify/PNotify",PNotifyButtons:"lib/pnotify/PNotifyButtons",PNotifyCallbacks:"lib/pnotify/PNotifyCallbacks",PNotifyDesktop:"lib/pnotify/PNotifyDesktop",NonBlock:"lib/pnotify/NonBlock"},shim:{bootstrap:{deps:["jquery"]},farahey:{deps:["jsPlumb"]},velocity:{deps:["jquery"]},velocityUI:{deps:["velocity"]},slidebars:{deps:["jquery"]},customScrollbar:{deps:["jquery","mousewheel"]},"datatables.loader":{deps:["jquery"]},"datatables.net":{deps:["jquery"]},"datatables.net-buttons":{deps:["datatables.net"]},"datatables.net-buttons-html":{deps:["datatables.net-buttons"]},"datatables.net-responsive":{deps:["datatables.net"]},"datatables.net-rowgroup":{deps:["datatables.net"]},"datatables.net-select":{deps:["datatables.net"]},"datatables.plugins.render.ellipsis":{deps:["datatables.net"]},xEditable:{deps:["bootstrap"]},bootbox:{deps:["jquery","bootstrap"],exports:"bootbox"},morris:{deps:["jquery","raphael"],exports:"Morris",init:function(e,t){window.Raphael=t}},easyPieChart:{deps:["jquery"]},peityInlineChart:{deps:["jquery"]},hoverIntent:{deps:["jquery"]},select2:{deps:["jquery","mousewheel"],exports:"Select2"},validator:{deps:["jquery","bootstrap"]},blueImpGallery:{deps:["jquery"]},bootstrapConfirmation:{deps:["bootstrap"]},bootstrapToggle:{deps:["jquery","bootstrap"]},summernote:{deps:["jquery"]}}}),require.config({baseUrl:jsBaseUrl}),requirejs([mainScriptPath]);
|
||||
//# sourceMappingURL=app.js.map
|
||||
|
||||
BIN
public/js/v2.0.0/app.js.br
Normal file
BIN
public/js/v2.0.0/app.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app.js.map
Normal file
1
public/js/v2.0.0/app.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["app.js"],"names":["mainScriptPath","document","body","getAttribute","jsBaseUrl","requirejs","config","baseUrl","paths","conf","dialog","layout","module","templates","img","login","mappage","setup","admin","jquery","bootstrap","text","mustache","localForage","velocity","velocityUI","slidebars","jsPlumb","farahey","easyTimer","customScrollbar","mousewheel","xEditable","morris","raphael","bootbox","easyPieChart","peityInlineChart","hoverIntent","select2","validator","blueImpGallery","blueImpGalleryHelper","blueImpGalleryBootstrap","bootstrapConfirmation","bootstrapToggle","lazyload","sortable","summernote.loader","summernote","datatables.loader","datatables.net","datatables.net-buttons","datatables.net-buttons-html","datatables.net-responsive","datatables.net-rowgroup","datatables.net-select","datatables.plugins.render.ellipsis","pnotify.loader","PNotify","PNotifyButtons","PNotifyCallbacks","PNotifyDesktop","NonBlock","shim","deps","exports","init","$","Raphael","window","require"],"mappings":"AAAA,aAGA,IAAIA,eAAiBC,SAASC,KAAKC,aAAa,eAI5CC,UAAYH,SAASC,KAAKC,aAAa,gBAG3CE,UAAUC,QACNC,QAAS,KAETC,OACIC,KAAM,WACNC,OAAQ,gBACRC,OAAQ,gBACRC,OAAQ,gBAERC,UAAW,kBACXC,IAAK,YAGLC,MAAO,cACPC,QAAS,gBACTC,MAAO,cACPC,MAAO,cAEPC,OAAQ,uBACRC,UAAW,oBACXC,KAAM,qBACNC,SAAU,mBACVC,YAAa,sBACbC,SAAU,mBACVC,WAAY,sBACZC,UAAW,gBACXC,QAAS,cACTC,QAAS,cACTC,UAAW,oBACXC,gBAAiB,kCACjBC,WAAY,4BACZC,UAAW,6BACXC,OAAQ,iBACRC,QAAS,kBACTC,QAAS,kBACTC,aAAc,8BACdC,iBAAkB,uBAClBC,YAAa,6BACbC,QAAS,kBACTC,UAAW,oBACXC,eAAgB,sBAChBC,qBAAsB,qBACtBC,wBAAyB,8BACzBC,sBAAuB,iCACvBC,gBAAiB,2BACjBC,SAAU,mBACVC,SAAU,mBAEVC,oBAAqB,0BACrBC,WAAc,gCAGdC,oBAAqB,0BACrBC,iBAAkB,6DAClBC,yBAA0B,yDAC1BC,8BAA+B,oDAC/BC,4BAA6B,+DAC7BC,0BAA2B,2DAC3BC,wBAAyB,uDACzBC,qCAAsC,yCAGtCC,iBAAkB,uBAClBC,QAAW,sBACXC,eAAkB,6BAClBC,iBAAoB,+BACpBC,eAAkB,6BAClBC,SAAY,wBAEhBC,MACI5C,WACI6C,MAAO,WAEXrC,SACIqC,MAAO,YAEXzC,UACIyC,MAAO,WAEXxC,YACIwC,MAAO,aAEXvC,WACIuC,MAAO,WAEXnC,iBACImC,MAAO,SAAU,eAErBf,qBACIe,MAAO,WAEXd,kBACIc,MAAO,WAEXb,0BACIa,MAAO,mBAEXZ,+BACIY,MAAO,2BAEXX,6BACIW,MAAO,mBAEXV,2BACIU,MAAO,mBAEXT,yBACIS,MAAO,mBAEXR,sCACIQ,MAAO,mBAEXjC,WACIiC,MAAO,cAEX9B,SACI8B,MAAO,SAAU,aACjBC,QAAS,WAEbjC,QACIgC,MAAO,SAAU,WACjBC,QAAS,SACTC,KAAM,SAAUC,EAAGC,GACfC,OAAOD,QAAUA,IAGzBjC,cACI6B,MAAO,WAEX5B,kBACI4B,MAAO,WAEX3B,aACI2B,MAAO,WAEX1B,SACI0B,MAAO,SAAU,cACjBC,QAAS,WAEb1B,WACIyB,MAAO,SAAU,cAErBxB,gBACIwB,MAAO,WAEXrB,uBACIqB,MAAO,cAEXpB,iBACIoB,MAAO,SAAU,cAErBhB,YACIgB,MAAO,cAQnBM,QAAQjE,QACJC,QAASH,YAIbC,WAAYL","file":"app.js","sourceRoot":"/js"}
|
||||
File diff suppressed because one or more lines are too long
BIN
public/js/v2.0.0/app/admin.js.br
Normal file
BIN
public/js/v2.0.0/app/admin.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/admin.js.map
Normal file
1
public/js/v2.0.0/app/admin.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,613 +0,0 @@
|
||||
/**
|
||||
* Created by exodus4d
|
||||
* static signature types
|
||||
*
|
||||
* (*) marked fields are in-game verified and
|
||||
* proofed, signature names (copy & paste from scanning window)
|
||||
*/
|
||||
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
// signature sources
|
||||
// http://de.sistersprobe.wikia.com/wiki/EVE_Sister_Core_Scanner_Probe_Wiki
|
||||
|
||||
// Combat sites ===================================================================================================
|
||||
|
||||
let c1Combat = {
|
||||
1: 'Perimeter Ambush Point',
|
||||
2: 'Perimeter Camp',
|
||||
3: 'Phase Catalyst Node',
|
||||
4: 'The Line'
|
||||
};
|
||||
|
||||
let c2Combat = {
|
||||
1: 'Perimeter Checkpoint',
|
||||
2: 'Perimeter Hangar',
|
||||
3: 'The Ruins of Enclave Cohort 27',
|
||||
4: 'Sleeper Data Sanctuary'
|
||||
};
|
||||
|
||||
let c3Combat = {
|
||||
1: 'Fortification Frontier Stronghold',
|
||||
2: 'Outpost Frontier Stronghold',
|
||||
3: 'Solar Cell',
|
||||
4: 'The Oruze Construct'
|
||||
};
|
||||
|
||||
let c4Combat = {
|
||||
1: 'Frontier Barracks',
|
||||
2: 'Frontier Command Post',
|
||||
3: 'Integrated Terminus',
|
||||
4: 'Sleeper Information Sanctum'
|
||||
};
|
||||
|
||||
let c5Combat = {
|
||||
1: 'Core Garrison', //*
|
||||
2: 'Core Stronghold', //*
|
||||
3: 'Oruze Osobnyk', //*
|
||||
4: 'Quarantine Area'
|
||||
};
|
||||
|
||||
let c6Combat = {
|
||||
1: 'Core Citadel', //*
|
||||
2: 'Core Bastion', //*
|
||||
3: 'Strange Energy Readings', //*
|
||||
4: 'The Mirror' //*
|
||||
};
|
||||
|
||||
// Thera WH
|
||||
let c12Combat = {
|
||||
1: 'Epicenter',
|
||||
2: 'Expedition Command Outpost Wreck',
|
||||
3: 'Planetary Colonization Office Wreck',
|
||||
4: 'Testing Facilities'
|
||||
};
|
||||
|
||||
// Drifter Sentinel WH
|
||||
let c14Combat = {
|
||||
1: 'Monolith',
|
||||
2: 'Wormhole in Rock Circle',
|
||||
3: 'Opposing Spatial Rifts',
|
||||
4: 'Sleeper Enclave Debris',
|
||||
5: 'Crystal Resource'
|
||||
};
|
||||
|
||||
// Drifter Barbican WH
|
||||
let c15Combat = {
|
||||
1: 'Wrecked Ships',
|
||||
2: 'Unstable Wormhole',
|
||||
3: 'Spatial Rift',
|
||||
4: 'Heavily Guarded Spatial Rift',
|
||||
5: 'Crystals'
|
||||
};
|
||||
|
||||
// Drifter Vidette WH
|
||||
let c16Combat = {
|
||||
1: 'Ship Graveyard',
|
||||
2: 'Sleeper Engineering Station',
|
||||
3: 'Spatial Rift',
|
||||
4: 'Sleeper Enclave in Coral Rock',
|
||||
5: 'Crystals and Stone Circle'
|
||||
};
|
||||
|
||||
// Drifter Conflux WH
|
||||
let c17Combat = {
|
||||
1: 'Monolith',
|
||||
2: 'Caged Wormhole',
|
||||
3: 'Rock Formation and Wormhole',
|
||||
4: 'Particle Acceleration Array',
|
||||
5: 'Guarded Asteroid Station'
|
||||
};
|
||||
|
||||
// Drifter Redoubt WH
|
||||
let c18Combat = {
|
||||
1: 'Ship Graveyard',
|
||||
2: 'Caged Wormhole',
|
||||
3: 'Spatial Rift Generator',
|
||||
4: 'Sleeper Enclave',
|
||||
5: 'Hollow Asteroid'
|
||||
};
|
||||
|
||||
// Relic sites ====================================================================================================
|
||||
|
||||
// NullSec Relic sites, which can also spawn in C1, C2, C3 wormholes
|
||||
let nullRelic = {
|
||||
10: 'Ruined Angel Crystal Quarry',
|
||||
11: 'Ruined Angel Monument Site',
|
||||
12: 'Ruined Angel Science Outpost',
|
||||
13: 'Ruined Angel Temple Site',
|
||||
14: 'Ruined Blood Raider Crystal Quarry',
|
||||
15: 'Ruined Blood Raider Monument Site',
|
||||
16: 'Ruined Blood Raider Science Outpost',
|
||||
17: 'Ruined Blood Raider Temple Site',
|
||||
18: 'Ruined Guristas Crystal Quarry',
|
||||
19: 'Ruined Guristas Monument Site',
|
||||
20: 'Ruined Guristas Science Outpost',
|
||||
21: 'Ruined Guristas Temple Site',
|
||||
22: 'Ruined Sansha Crystal Quarry',
|
||||
23: 'Ruined Sansha Monument Site',
|
||||
24: 'Ruined Sansha Science Outpost',
|
||||
25: 'Ruined Sansha Temple Site',
|
||||
26: 'Ruined Serpentis Crystal Quarry',
|
||||
27: 'Ruined Serpentis Monument Site',
|
||||
28: 'Ruined Serpentis Science Outpost',
|
||||
29: 'Ruined Serpentis Temple Site'
|
||||
};
|
||||
|
||||
let c1Relic = Object.assign({}, nullRelic, {
|
||||
1: 'Forgotten Perimeter Coronation Platform', //*
|
||||
2: 'Forgotten Perimeter Power Array' //*
|
||||
});
|
||||
|
||||
let c2Relic = Object.assign({}, nullRelic, {
|
||||
1: 'Forgotten Perimeter Gateway', //*
|
||||
2: 'Forgotten Perimeter Habitation Coils' //*
|
||||
});
|
||||
|
||||
let c3Relic = Object.assign({}, nullRelic, {
|
||||
1: 'Forgotten Frontier Quarantine Outpost', //*
|
||||
2: 'Forgotten Frontier Recursive Depot' //*
|
||||
});
|
||||
|
||||
let c4Relic = {
|
||||
1: 'Forgotten Frontier Conversion Module',
|
||||
2: 'Forgotten Frontier Evacuation Center'
|
||||
};
|
||||
|
||||
let c5Relic = {
|
||||
1: 'Forgotten Core Data Field',
|
||||
2: 'Forgotten Core Information Pen'
|
||||
};
|
||||
|
||||
let c6Relic = {
|
||||
1: 'Forgotten Core Assembly Hall', //*
|
||||
2: 'Forgotten Core Circuitry Disassembler' //*
|
||||
};
|
||||
|
||||
// Data sites =====================================================================================================
|
||||
|
||||
// NulSec Data sites, which can also spawn in C1, C2, C3 wormholes
|
||||
let nullData = {
|
||||
10: 'Abandoned Research Complex DA005',
|
||||
11: 'Abandoned Research Complex DA015',
|
||||
12: 'Abandoned Research Complex DC007',
|
||||
13: 'Abandoned Research Complex DC021',
|
||||
14: 'Abandoned Research Complex DC035',
|
||||
15: 'Abandoned Research Complex DG003',
|
||||
16: 'Central Angel Command Center',
|
||||
17: 'Central Angel Data Mining Site',
|
||||
18: 'Central Angel Sparking Transmitter',
|
||||
19: 'Central Angel Survey Site',
|
||||
20: 'Central Blood Raider Command Center',
|
||||
21: 'Central Blood Raider Data Mining Site',
|
||||
22: 'Central Blood Raider Sparking Transmitter',
|
||||
23: 'Central Blood Raider Survey Site',
|
||||
24: 'Central Guristas Command Center',
|
||||
25: 'Central Guristas Data Mining Site',
|
||||
26: 'Central Guristas Sparking Transmitter',
|
||||
27: 'Central Guristas Survey Site',
|
||||
28: 'Central Sansha Command Center',
|
||||
29: 'Central Sansha Data Mining Site',
|
||||
30: 'Central Sansha Sparking Transmitter',
|
||||
31: 'Central Sansha Survey Site',
|
||||
32: 'Central Serpentis Command Center',
|
||||
33: 'Central Serpentis Data Mining Site',
|
||||
34: 'Central Serpentis Sparking Transmitter',
|
||||
35: 'Central Serpentis Survey Site'
|
||||
};
|
||||
|
||||
let c1Data = Object.assign({}, nullData, {
|
||||
1: 'Unsecured Perimeter Amplifier', //*
|
||||
2: 'Unsecured Perimeter Information Center' //*
|
||||
});
|
||||
|
||||
let c2Data = Object.assign({}, nullData, {
|
||||
1: 'Unsecured Perimeter Comms Relay', //*
|
||||
2: 'Unsecured Perimeter Transponder Farm' //*
|
||||
});
|
||||
|
||||
let c3Data = Object.assign({}, nullData, {
|
||||
1: 'Unsecured Frontier Database', //*
|
||||
2: 'Unsecured Frontier Receiver' //*
|
||||
});
|
||||
|
||||
let c4Data = {
|
||||
1: 'Unsecured Frontier Digital Nexus',
|
||||
2: 'Unsecured Frontier Trinary Hub'
|
||||
};
|
||||
|
||||
let c5Data = {
|
||||
1: 'Unsecured Frontier Enclave Relay',
|
||||
2: 'Unsecured Frontier Server Bank'
|
||||
};
|
||||
|
||||
let c6Data = {
|
||||
1: 'Unsecured Core Backup Array', //*
|
||||
2: 'Unsecured Core Emergence' //*
|
||||
};
|
||||
|
||||
// Gas sites ======================================================================================================
|
||||
|
||||
let c1Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir' //*
|
||||
};
|
||||
|
||||
let c2Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir' //*
|
||||
};
|
||||
|
||||
let c3Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir', //*
|
||||
6: 'Bountiful Frontier Reservoir', //*
|
||||
7: 'Vast Frontier Reservoir' //*
|
||||
};
|
||||
|
||||
let c4Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Token Perimeter Reservoir', //*
|
||||
3: 'Minor Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Ordinary Perimeter Reservoir', //*
|
||||
6: 'Vast Frontier Reservoir', //*
|
||||
7: 'Bountiful Frontier Reservoir' //*
|
||||
};
|
||||
|
||||
let c5Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Minor Perimeter Reservoir', //*
|
||||
3: 'Ordinary Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Token Perimeter Reservoir', //*
|
||||
6: 'Bountiful Frontier Reservoir', //*
|
||||
7: 'Vast Frontier Reservoir', //*
|
||||
8: 'Instrumental Core Reservoir', //*
|
||||
9: 'Vital Core Reservoir' //*
|
||||
};
|
||||
|
||||
let c6Gas = {
|
||||
1: 'Barren Perimeter Reservoir', //*
|
||||
2: 'Minor Perimeter Reservoir', //*
|
||||
3: 'Ordinary Perimeter Reservoir', //*
|
||||
4: 'Sizeable Perimeter Reservoir', //*
|
||||
5: 'Token Perimeter Reservoir', //*
|
||||
6: 'Bountiful Frontier Reservoir', //*
|
||||
7: 'Vast Frontier Reservoir', //*
|
||||
8: 'Instrumental Core Reservoir', //*
|
||||
9: 'Vital Core Reservoir' //*
|
||||
};
|
||||
|
||||
// Ore sites ======================================================================================================
|
||||
|
||||
let c1Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Isolated Core Deposit', //*
|
||||
6: 'Uncommon Core Deposit' //*
|
||||
};
|
||||
|
||||
let c2Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Isolated Core Deposit', //*
|
||||
6: 'Uncommon Core Deposit' //*
|
||||
};
|
||||
|
||||
let c3Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Infrequent Core Deposit', //*
|
||||
6: 'Unusual Core Deposit' //*
|
||||
};
|
||||
|
||||
let c4Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Unusual Core Deposit', //*
|
||||
6: 'Infrequent Core Deposit' //*
|
||||
};
|
||||
|
||||
let c5Ore = {
|
||||
1: 'Average Frontier Deposit', //*
|
||||
2: 'Unexceptional Frontier Deposit', //*
|
||||
3: 'Uncommon Core Deposit', //*
|
||||
4: 'Ordinary Perimeter Deposit', //*
|
||||
5: 'Common Perimeter Deposit', //*
|
||||
6: 'Exceptional Core Deposit', //*
|
||||
7: 'Infrequent Core Deposit', //*
|
||||
8: 'Unusual Core Deposit', //*
|
||||
9: 'Rarified Core Deposit', //*
|
||||
10: 'Isolated Core Deposit' //*
|
||||
};
|
||||
|
||||
let c6Ore = {
|
||||
1: 'Ordinary Perimeter Deposit', //*
|
||||
2: 'Common Perimeter Deposit', //*
|
||||
3: 'Unexceptional Frontier Deposit', //*
|
||||
4: 'Average Frontier Deposit', //*
|
||||
5: 'Rarified Core Deposit' //*
|
||||
};
|
||||
|
||||
let c13Ore = {
|
||||
1: 'Shattered Debris Field',
|
||||
2: 'Shattered Ice Field'
|
||||
};
|
||||
|
||||
// Wormholes ======================================================================================================
|
||||
|
||||
// all k-space exits are static or K162
|
||||
let c1WH = {
|
||||
1: 'H121 - C1',
|
||||
2: 'C125 - C2',
|
||||
3: 'O883 - C3',
|
||||
4: 'M609 - C4',
|
||||
5: 'L614 - C5',
|
||||
6: 'S804 - C6',
|
||||
7: 'N110 - H',
|
||||
8: 'J244 - L',
|
||||
9: 'Z060 - 0.0',
|
||||
10: 'F353 - C12 Thera'
|
||||
};
|
||||
|
||||
// all w-space -> w-space are statics or K162
|
||||
let c2WH = {
|
||||
1: 'Z647 - C1',
|
||||
2: 'D382 - C2',
|
||||
3: 'O477 - C3',
|
||||
4: 'Y683 - C4',
|
||||
5: 'N062 - C5',
|
||||
6: 'R474 - C6',
|
||||
7: 'B274 - H',
|
||||
8: 'A239 - L',
|
||||
9: 'E545 - 0.0',
|
||||
10: 'F135 - C12 Thera'
|
||||
};
|
||||
|
||||
// all k-space exits are static or K162
|
||||
let c3WH = {
|
||||
1: 'V301 - C1',
|
||||
2: 'I182 - C2',
|
||||
3: 'N968 - C3',
|
||||
4: 'T405 - C4',
|
||||
5: 'N770 - C5',
|
||||
6: 'A982 - C6',
|
||||
7: 'D845 - H',
|
||||
8: 'U210 - L',
|
||||
9: 'K346 - 0.0',
|
||||
10: 'F135 - C12 Thera'
|
||||
};
|
||||
|
||||
// no *wandering* w-space -> w-space
|
||||
// all holes are statics or K162
|
||||
let c4WH = {
|
||||
1: 'P060 - C1',
|
||||
2: 'N766 - C2',
|
||||
3: 'C247 - C3',
|
||||
4: 'X877 - C4',
|
||||
5: 'H900 - C5',
|
||||
6: 'U574 - C6',
|
||||
7: 'S047 - H',
|
||||
8: 'N290 - L',
|
||||
9: 'K329 - 0.0'
|
||||
};
|
||||
|
||||
let c5WH = {
|
||||
1: 'Y790 - C1',
|
||||
2: 'D364 - C2',
|
||||
3: 'M267 - C3',
|
||||
4: 'E175 - C4',
|
||||
5: 'H296 - C5',
|
||||
6: 'V753 - C6',
|
||||
7: 'D792 - H',
|
||||
8: 'C140 - L',
|
||||
9: 'Z142 - 0.0'
|
||||
};
|
||||
|
||||
let c6WH = {
|
||||
1: 'Q317 - C1',
|
||||
2: 'G024 - C2',
|
||||
3: 'L477 - C3',
|
||||
4: 'Z457 - C4',
|
||||
5: 'V911 - C5',
|
||||
6: 'W237 - C6',
|
||||
7: 'B520 - H',
|
||||
8: 'D792 - H',
|
||||
9: 'C140 - L',
|
||||
10: 'C391 - L',
|
||||
11: 'C248 - 0.0',
|
||||
12: 'Z142 - 0.0'
|
||||
};
|
||||
|
||||
// Shattered WH (some of them are static)
|
||||
let c13WH = {
|
||||
1: 'P060 - C1',
|
||||
2: 'Z647 - C1',
|
||||
3: 'D382 - C2',
|
||||
4: 'L005 - C2',
|
||||
5: 'N766 - C2',
|
||||
6: 'C247 - C3',
|
||||
7: 'M267 - C3',
|
||||
8: 'O477 - C3',
|
||||
9: 'X877 - C4',
|
||||
10: 'Y683 - C4',
|
||||
11: 'H296 - C5',
|
||||
12: 'H900 - C5',
|
||||
13: 'H296 - C5',
|
||||
14: 'N062 - C5', // ??
|
||||
15: 'V911 - C5',
|
||||
16: 'U574 - C6',
|
||||
17: 'V753 - C6',
|
||||
18: 'W237 - C6',
|
||||
19: 'B274 - H',
|
||||
20: 'D792 - H',
|
||||
21: 'D845 - H',
|
||||
22: 'N110 - H',
|
||||
23: 'A239 - L',
|
||||
24: 'C391 - L',
|
||||
25: 'J244 - L',
|
||||
26: 'U201 - L', // ??
|
||||
27: 'U210 - L',
|
||||
28: 'C248 - 0.0',
|
||||
29: 'E545 - 0.0',
|
||||
30: 'K346 - 0.0',
|
||||
31: 'Z060 - 0.0'
|
||||
};
|
||||
|
||||
let hsWH = {
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
3: 'X702 - C3',
|
||||
4: 'O128 - C4',
|
||||
5: 'M555 - C5',
|
||||
6: 'B041 - C6',
|
||||
7: 'A641 - H',
|
||||
8: 'R051 - L',
|
||||
9: 'V283 - 0.0',
|
||||
10: 'T458 - C12 Thera'
|
||||
};
|
||||
|
||||
let lsWH = {
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
3: 'X702 - C3',
|
||||
4: 'O128 - C4',
|
||||
5: 'N432 - C5',
|
||||
6: 'U319 - C6',
|
||||
7: 'B449 - H',
|
||||
8: 'N944 - L',
|
||||
9: 'S199 - 0.0',
|
||||
10: 'M164 - C12 Thera'
|
||||
};
|
||||
|
||||
let nullWH = {
|
||||
1: 'Z971 - C1',
|
||||
2: 'R943 - C2',
|
||||
3: 'X702 - C3',
|
||||
4: 'O128 - C4',
|
||||
5: 'N432 - C5',
|
||||
6: 'U319 - C6',
|
||||
7: 'B449 - H',
|
||||
8: 'N944 - L',
|
||||
9: 'S199 - 0.0',
|
||||
10: 'L031 - C12 Thera'
|
||||
};
|
||||
|
||||
// ================================================================================================================
|
||||
// Signature types
|
||||
// ================================================================================================================
|
||||
|
||||
// signature types
|
||||
return {
|
||||
1: { // system type (wh)
|
||||
1: { // C1 (area id)
|
||||
1: c1Combat,
|
||||
2: c1Relic,
|
||||
3: c1Data,
|
||||
4: c1Gas,
|
||||
5: c1WH,
|
||||
6: c1Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
2: { // C2
|
||||
1: c2Combat,
|
||||
2: c2Relic,
|
||||
3: c2Data,
|
||||
4: c2Gas,
|
||||
5: c2WH,
|
||||
6: c2Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
3: { // C3
|
||||
1: c3Combat,
|
||||
2: c3Relic,
|
||||
3: c3Data,
|
||||
4: c3Gas,
|
||||
5: c3WH,
|
||||
6: c3Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
4: { // C4
|
||||
1: c4Combat,
|
||||
2: c4Relic,
|
||||
3: c4Data,
|
||||
4: c4Gas,
|
||||
5: c4WH,
|
||||
6: c4Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
5: { // C5
|
||||
1: c5Combat,
|
||||
2: c5Relic,
|
||||
3: c5Data,
|
||||
4: c5Gas,
|
||||
5: c5WH,
|
||||
6: c5Ore,
|
||||
7: {} // Ghost
|
||||
},
|
||||
6: { // C6
|
||||
1: c6Combat,
|
||||
2: c6Relic,
|
||||
3: c6Data,
|
||||
4: c6Gas,
|
||||
5: c6WH,
|
||||
6: c6Ore,
|
||||
7: { // Ghost
|
||||
1: 'Superior Blood Raider Covert Research Facility' //*
|
||||
}
|
||||
},
|
||||
12: { // Thera WH
|
||||
1: c12Combat
|
||||
},
|
||||
13: { // Shattered WH
|
||||
5: c13WH,
|
||||
6: c13Ore
|
||||
},
|
||||
14: { // Drifter Sentinel WH
|
||||
1: c14Combat
|
||||
},
|
||||
15: { // Drifter Barbican WH
|
||||
1: c15Combat
|
||||
},
|
||||
16: { // Drifter Vidette WH
|
||||
1: c16Combat
|
||||
},
|
||||
17: { // Drifter Conflux WH
|
||||
1: c17Combat
|
||||
},
|
||||
18: { // Drifter Redoubt WH
|
||||
1: c18Combat
|
||||
}
|
||||
}, // system type (k-space)
|
||||
2: {
|
||||
30: { // High Sec
|
||||
5: hsWH
|
||||
},
|
||||
31: { // Low Sec
|
||||
5: lsWH
|
||||
},
|
||||
32: { // 0.0
|
||||
5: nullWH
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,823 +0,0 @@
|
||||
/**
|
||||
* Created by exodus4d on 06.07.2015.
|
||||
* static system effects
|
||||
*/
|
||||
|
||||
|
||||
define([], () => {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* get system effect multiplier
|
||||
* @param areaId
|
||||
* @returns {number}
|
||||
*/
|
||||
let getMultiplierByAreaId = areaId => {
|
||||
let multiply = 0;
|
||||
switch(areaId){
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
multiply = areaId; // C1-C6 holes
|
||||
break;
|
||||
case 13:
|
||||
multiply = 6; // Shattered frigate holes
|
||||
break;
|
||||
case 14:
|
||||
case 15:
|
||||
case 16:
|
||||
case 17:
|
||||
case 18:
|
||||
multiply = 2; // Drifter space
|
||||
break;
|
||||
}
|
||||
|
||||
return multiply;
|
||||
};
|
||||
|
||||
|
||||
let magnetar = {
|
||||
1: [
|
||||
{
|
||||
effect: 'Damage',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Missile exp. radius',
|
||||
value: '+15%'
|
||||
}, {
|
||||
effect: 'Drone tracking',
|
||||
value: '-15%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '-15%'
|
||||
}, {
|
||||
effect: 'Tracking speed',
|
||||
value: '-15%'
|
||||
}, {
|
||||
effect: 'Target Painter strength',
|
||||
value: '-15%'
|
||||
}
|
||||
],
|
||||
2: [
|
||||
{
|
||||
effect: 'Damage',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Missile exp. radius',
|
||||
value: '+22%'
|
||||
}, {
|
||||
effect: 'Drone tracking',
|
||||
value: '-22%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '-22%'
|
||||
}, {
|
||||
effect: 'Tracking speed',
|
||||
value: '-22%'
|
||||
}, {
|
||||
effect: 'Target Painter strength',
|
||||
value: '-22%'
|
||||
}
|
||||
],
|
||||
3: [
|
||||
{
|
||||
effect: 'Damage',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Missile exp. radius',
|
||||
value: '+29%'
|
||||
}, {
|
||||
effect: 'Drone tracking',
|
||||
value: '-29%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '-29%'
|
||||
}, {
|
||||
effect: 'Tracking speed',
|
||||
value: '-29%'
|
||||
}, {
|
||||
effect: 'Target Painter strength',
|
||||
value: '-29%'
|
||||
}
|
||||
],
|
||||
4: [
|
||||
{
|
||||
effect: 'Damage',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Missile exp. radius',
|
||||
value: '+36%'
|
||||
}, {
|
||||
effect: 'Drone tracking',
|
||||
value: '-36%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '-36%'
|
||||
}, {
|
||||
effect: 'Tracking speed',
|
||||
value: '-36%'
|
||||
}, {
|
||||
effect: 'Target Painter strength',
|
||||
value: '-36%'
|
||||
}
|
||||
],
|
||||
5: [
|
||||
{
|
||||
effect: 'Damage',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Missile exp. radius',
|
||||
value: '+43%'
|
||||
}, {
|
||||
effect: 'Drone tracking',
|
||||
value: '-43%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '-43%'
|
||||
}, {
|
||||
effect: 'Tracking speed',
|
||||
value: '-43%'
|
||||
}, {
|
||||
effect: 'Target Painter strength',
|
||||
value: '-43%'
|
||||
}
|
||||
],
|
||||
6: [
|
||||
{
|
||||
effect: 'Damage',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Missile exp. radius',
|
||||
value: '+50%'
|
||||
}, {
|
||||
effect: 'Drone tracking',
|
||||
value: '-50%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '-50%'
|
||||
}, {
|
||||
effect: 'Tracking speed',
|
||||
value: '-50%'
|
||||
}, {
|
||||
effect: 'Target Painter strength',
|
||||
value: '-50%'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let redGiant = {
|
||||
1: [
|
||||
{
|
||||
effect: 'Heat damage',
|
||||
value: '+15%'
|
||||
}, {
|
||||
effect: 'Overload bonus',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Smart Bomb range',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Smart Bomb damage',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Bomb damage',
|
||||
value: '+30%'
|
||||
}
|
||||
],
|
||||
2: [
|
||||
{
|
||||
effect: 'Heat damage',
|
||||
value: '+22%'
|
||||
}, {
|
||||
effect: 'Overload bonus',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Smart Bomb range',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Smart Bomb damage',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Bomb damage',
|
||||
value: '+44%'
|
||||
}
|
||||
],
|
||||
3: [
|
||||
{
|
||||
effect: 'Heat damage',
|
||||
value: '+29%'
|
||||
}, {
|
||||
effect: 'Overload bonus',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Smart Bomb range',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Smart Bomb damage',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Bomb damage',
|
||||
value: '+58%'
|
||||
}
|
||||
],
|
||||
4: [
|
||||
{
|
||||
effect: 'Heat damage',
|
||||
value: '+36%'
|
||||
}, {
|
||||
effect: 'Overload bonus',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Smart Bomb range',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Smart Bomb damage',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Bomb damage',
|
||||
value: '+72%'
|
||||
}
|
||||
],
|
||||
5: [
|
||||
{
|
||||
effect: 'Heat damage',
|
||||
value: '+43%'
|
||||
}, {
|
||||
effect: 'Overload bonus',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Smart Bomb range',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Smart Bomb damage',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Bomb damage',
|
||||
value: '+86%'
|
||||
}
|
||||
],
|
||||
6: [
|
||||
{
|
||||
effect: 'Heat damage',
|
||||
value: '+50%'
|
||||
}, {
|
||||
effect: 'Overload bonus',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Smart Bomb range',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Smart Bomb damage',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Bomb damage',
|
||||
value: '+100%'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let pulsar = {
|
||||
1: [
|
||||
{
|
||||
effect: 'Shield HP',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Armor resist',
|
||||
value: '-15%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge',
|
||||
value: '-15%'
|
||||
}, {
|
||||
effect: 'Signature',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'NOS/Neut drain',
|
||||
value: '+30%'
|
||||
}
|
||||
],
|
||||
2: [
|
||||
{
|
||||
effect: 'Shield HP',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Armor resist',
|
||||
value: '-22%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge',
|
||||
value: '-22%'
|
||||
}, {
|
||||
effect: 'Signature',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'NOS/Neut drain',
|
||||
value: '+44%'
|
||||
}
|
||||
],
|
||||
3: [
|
||||
{
|
||||
effect: 'Shield HP',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Armor resist',
|
||||
value: '-29%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge',
|
||||
value: '-29%'
|
||||
}, {
|
||||
effect: 'Signature',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'NOS/Neut drain',
|
||||
value: '+58%'
|
||||
}
|
||||
],
|
||||
4: [
|
||||
{
|
||||
effect: 'Shield HP',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Armor resist',
|
||||
value: '-36%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge',
|
||||
value: '-36%'
|
||||
}, {
|
||||
effect: 'Signature',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'NOS/Neut drain',
|
||||
value: '+72%'
|
||||
}
|
||||
],
|
||||
5: [
|
||||
{
|
||||
effect: 'Shield HP',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Armor resist',
|
||||
value: '-43%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge',
|
||||
value: '-43%'
|
||||
}, {
|
||||
effect: 'Signature',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'NOS/Neut drain',
|
||||
value: '+86%'
|
||||
}
|
||||
],
|
||||
6: [
|
||||
{
|
||||
effect: 'Shield HP',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Armor resist',
|
||||
value: '-50%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge',
|
||||
value: '-50%'
|
||||
}, {
|
||||
effect: 'Signature',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'NOS/Neut drain',
|
||||
value: '+100%'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let wolfRayet = {
|
||||
1: [
|
||||
{
|
||||
effect: 'Armor HP',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Shield resist',
|
||||
value: '-15%'
|
||||
}, {
|
||||
effect: 'Small Weapon damage',
|
||||
value: '+60%'
|
||||
}, {
|
||||
effect: 'Signature size',
|
||||
value: '-15%'
|
||||
}
|
||||
],
|
||||
2: [
|
||||
{
|
||||
effect: 'Armor HP',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Shield resist',
|
||||
value: '-22%'
|
||||
}, {
|
||||
effect: 'Small Weapon damage',
|
||||
value: '+88%'
|
||||
}, {
|
||||
effect: 'Signature size',
|
||||
value: '-22%'
|
||||
}
|
||||
],
|
||||
3: [
|
||||
{
|
||||
effect: 'Armor HP',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Shield resist',
|
||||
value: '-29%'
|
||||
}, {
|
||||
effect: 'Small Weapon damage',
|
||||
value: '+116%'
|
||||
}, {
|
||||
effect: 'Signature size',
|
||||
value: '-29%'
|
||||
}
|
||||
],
|
||||
4: [
|
||||
{
|
||||
effect: 'Armor HP',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Shield resist',
|
||||
value: '-36%'
|
||||
}, {
|
||||
effect: 'Small Weapon damage',
|
||||
value: '+144%'
|
||||
}, {
|
||||
effect: 'Signature size',
|
||||
value: '-36%'
|
||||
}
|
||||
],
|
||||
5: [
|
||||
{
|
||||
effect: 'Armor HP',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Shield resist',
|
||||
value: '-43%'
|
||||
}, {
|
||||
effect: 'Small Weapon damage',
|
||||
value: '+172%'
|
||||
}, {
|
||||
effect: 'Signature size',
|
||||
value: '-43%'
|
||||
}
|
||||
],
|
||||
6: [
|
||||
{
|
||||
effect: 'Armor HP',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Shield resist',
|
||||
value: '-50%'
|
||||
}, {
|
||||
effect: 'Small Weapon damage',
|
||||
value: '+200%'
|
||||
}, {
|
||||
effect: 'Signature size',
|
||||
value: '-50%'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let cataclysmic = {
|
||||
1: [
|
||||
{
|
||||
effect: 'Local armor repair amount',
|
||||
value: '-15%'
|
||||
}, {
|
||||
effect: 'Local shield boost amount',
|
||||
value: '-15%'
|
||||
}, {
|
||||
effect: 'Shield transfer amount',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Remote repair amount',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Capacitor capacity',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge time',
|
||||
value: '+15%'
|
||||
}, {
|
||||
effect: 'Remote Capacitor Transmitter amount',
|
||||
value: '-15%'
|
||||
}
|
||||
],
|
||||
2: [
|
||||
{
|
||||
effect: 'Local armor repair amount',
|
||||
value: '-22%'
|
||||
}, {
|
||||
effect: 'Local shield boost amount',
|
||||
value: '-22%'
|
||||
}, {
|
||||
effect: 'Shield transfer amount',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Remote repair amount',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Capacitor capacity',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge time',
|
||||
value: '+22%'
|
||||
}, {
|
||||
effect: 'Remote Capacitor Transmitter amount',
|
||||
value: '-22%'
|
||||
}
|
||||
],
|
||||
3: [
|
||||
{
|
||||
effect: 'Local armor repair amount',
|
||||
value: '-29%'
|
||||
}, {
|
||||
effect: 'Local shield boost amount',
|
||||
value: '-29%'
|
||||
}, {
|
||||
effect: 'Shield transfer amount',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Remote repair amount',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Capacitor capacity',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge time',
|
||||
value: '+29%'
|
||||
}, {
|
||||
effect: 'Remote Capacitor Transmitter amount',
|
||||
value: '-29%'
|
||||
}
|
||||
],
|
||||
4: [
|
||||
{
|
||||
effect: 'Local armor repair amount',
|
||||
value: '-36%'
|
||||
}, {
|
||||
effect: 'Local shield boost amount',
|
||||
value: '-36%'
|
||||
}, {
|
||||
effect: 'Shield transfer amount',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Remote repair amount',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Capacitor capacity',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge time',
|
||||
value: '+36%'
|
||||
}, {
|
||||
effect: 'Remote Capacitor Transmitter amount',
|
||||
value: '-36%'
|
||||
}
|
||||
],
|
||||
5: [
|
||||
{
|
||||
effect: 'Local armor repair amount',
|
||||
value: '-43%'
|
||||
}, {
|
||||
effect: 'Local shield boost amount',
|
||||
value: '-43%'
|
||||
}, {
|
||||
effect: 'Shield transfer amount',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Remote repair amount',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Capacitor capacity',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge time',
|
||||
value: '+43%'
|
||||
}, {
|
||||
effect: 'Remote Capacitor Transmitter amount',
|
||||
value: '-43%'
|
||||
}
|
||||
],
|
||||
6: [
|
||||
{
|
||||
effect: 'Local armor repair amount',
|
||||
value: '-50%'
|
||||
}, {
|
||||
effect: 'Local shield boost amount',
|
||||
value: '-50%'
|
||||
}, {
|
||||
effect: 'Shield transfer amount',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Remote repair amount',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Capacitor capacity',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Capacitor recharge time',
|
||||
value: '+50%'
|
||||
}, {
|
||||
effect: 'Remote Capacitor Transmitter amount',
|
||||
value: '-50%'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let blackHole = {
|
||||
1: [
|
||||
{
|
||||
effect: 'Missile velocity',
|
||||
value: '+15%'
|
||||
}, {
|
||||
effect: 'Missile exp. velocity',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Ship velocity',
|
||||
value: '+30%'
|
||||
}, {
|
||||
effect: 'Stasis Webifier strength',
|
||||
value: '-15%'
|
||||
}, {
|
||||
effect: 'Inertia',
|
||||
value: '+15%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '+30%'
|
||||
}
|
||||
],
|
||||
2: [
|
||||
{
|
||||
effect: 'Missile velocity',
|
||||
value: '+22%'
|
||||
}, {
|
||||
effect: 'Missile exp. velocity',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Ship velocity',
|
||||
value: '+44%'
|
||||
}, {
|
||||
effect: 'Stasis Webifier strength',
|
||||
value: '-22%'
|
||||
}, {
|
||||
effect: 'Inertia',
|
||||
value: '+22%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '+44%'
|
||||
}
|
||||
],
|
||||
3: [
|
||||
{
|
||||
effect: 'Missile velocity',
|
||||
value: '+29%'
|
||||
}, {
|
||||
effect: 'Missile exp. velocity',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Ship velocity',
|
||||
value: '+58%'
|
||||
}, {
|
||||
effect: 'Stasis Webifier strength',
|
||||
value: '-29%'
|
||||
}, {
|
||||
effect: 'Inertia',
|
||||
value: '+29%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '+58%'
|
||||
}
|
||||
],
|
||||
4: [
|
||||
{
|
||||
effect: 'Missile velocity',
|
||||
value: '+36%'
|
||||
}, {
|
||||
effect: 'Missile exp. velocity',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Ship velocity',
|
||||
value: '+72%'
|
||||
}, {
|
||||
effect: 'Stasis Webifier strength',
|
||||
value: '-36%'
|
||||
}, {
|
||||
effect: 'Inertia',
|
||||
value: '+36%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '+72%'
|
||||
}
|
||||
],
|
||||
5: [
|
||||
{
|
||||
effect: 'Missile velocity',
|
||||
value: '+43%'
|
||||
}, {
|
||||
effect: 'Missile exp. velocity',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Ship velocity',
|
||||
value: '+86%'
|
||||
}, {
|
||||
effect: 'Stasis Webifier strength',
|
||||
value: '-43%'
|
||||
}, {
|
||||
effect: 'Inertia',
|
||||
value: '+43%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '+86%'
|
||||
}
|
||||
],
|
||||
6: [
|
||||
{
|
||||
effect: 'Missile velocity',
|
||||
value: '+50%'
|
||||
}, {
|
||||
effect: 'Missile exp. velocity',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Ship velocity',
|
||||
value: '+100%'
|
||||
}, {
|
||||
effect: 'Stasis Webifier strength',
|
||||
value: '-50%'
|
||||
}, {
|
||||
effect: 'Inertia',
|
||||
value: '+50%'
|
||||
}, {
|
||||
effect: 'Targeting range',
|
||||
value: '+100%'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// system effects
|
||||
return {
|
||||
getMultiplierByAreaId: getMultiplierByAreaId,
|
||||
wh: {
|
||||
magnetar: {
|
||||
1: magnetar[getMultiplierByAreaId(1)],
|
||||
2: magnetar[getMultiplierByAreaId(2)],
|
||||
3: magnetar[getMultiplierByAreaId(3)],
|
||||
4: magnetar[getMultiplierByAreaId(4)],
|
||||
5: magnetar[getMultiplierByAreaId(5)],
|
||||
6: magnetar[getMultiplierByAreaId(6)],
|
||||
16: magnetar[getMultiplierByAreaId(16)]
|
||||
},
|
||||
redGiant: {
|
||||
1: redGiant[getMultiplierByAreaId(1)],
|
||||
2: redGiant[getMultiplierByAreaId(2)],
|
||||
3: redGiant[getMultiplierByAreaId(3)],
|
||||
4: redGiant[getMultiplierByAreaId(4)],
|
||||
5: redGiant[getMultiplierByAreaId(5)],
|
||||
6: redGiant[getMultiplierByAreaId(6)],
|
||||
14: redGiant[getMultiplierByAreaId(14)]
|
||||
},
|
||||
pulsar: {
|
||||
1: pulsar[getMultiplierByAreaId(1)],
|
||||
2: pulsar[getMultiplierByAreaId(2)],
|
||||
3: pulsar[getMultiplierByAreaId(3)],
|
||||
4: pulsar[getMultiplierByAreaId(4)],
|
||||
5: pulsar[getMultiplierByAreaId(5)],
|
||||
6: pulsar[getMultiplierByAreaId(6)],
|
||||
17: pulsar[getMultiplierByAreaId(17)]
|
||||
},
|
||||
wolfRayet: {
|
||||
1: wolfRayet[getMultiplierByAreaId(1)],
|
||||
2: wolfRayet[getMultiplierByAreaId(2)],
|
||||
3: wolfRayet[getMultiplierByAreaId(3)],
|
||||
4: wolfRayet[getMultiplierByAreaId(4)],
|
||||
5: wolfRayet[getMultiplierByAreaId(5)],
|
||||
6: wolfRayet[getMultiplierByAreaId(6)],
|
||||
13: wolfRayet[getMultiplierByAreaId(13)],
|
||||
18: wolfRayet[getMultiplierByAreaId(18)]
|
||||
},
|
||||
cataclysmic: {
|
||||
1: cataclysmic[getMultiplierByAreaId(1)],
|
||||
2: cataclysmic[getMultiplierByAreaId(2)],
|
||||
3: cataclysmic[getMultiplierByAreaId(3)],
|
||||
4: cataclysmic[getMultiplierByAreaId(4)],
|
||||
5: cataclysmic[getMultiplierByAreaId(5)],
|
||||
6: cataclysmic[getMultiplierByAreaId(6)],
|
||||
15: cataclysmic[getMultiplierByAreaId(15)]
|
||||
},
|
||||
blackHole: {
|
||||
1: blackHole[getMultiplierByAreaId(1)],
|
||||
2: blackHole[getMultiplierByAreaId(2)],
|
||||
3: blackHole[getMultiplierByAreaId(3)],
|
||||
4: blackHole[getMultiplierByAreaId(4)],
|
||||
5: blackHole[getMultiplierByAreaId(5)],
|
||||
6: blackHole[getMultiplierByAreaId(6)]
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,153 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'app/util',
|
||||
'app/lib/cron'
|
||||
], ($, Util, Cron) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
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)
|
||||
};
|
||||
|
||||
/**
|
||||
* update element with time information
|
||||
* @param element
|
||||
* @param tempDate
|
||||
* @param round
|
||||
*/
|
||||
let updateDateDiff = (element, tempDate, round) => {
|
||||
let diff = Util.getTimeDiffParts(tempDate, new Date());
|
||||
let days = diff.days;
|
||||
let hrs = diff.hours;
|
||||
let min = diff.min;
|
||||
let leftSec = diff.sec;
|
||||
let parts = [];
|
||||
|
||||
if(
|
||||
round === 'd' &&
|
||||
days >= 1
|
||||
){
|
||||
parts.push('<span class="' + config.counterDigitLargeClass + '">' + '> 1d' + '</span>');
|
||||
}else{
|
||||
if(
|
||||
days > 0 ||
|
||||
parts.length > 0
|
||||
){
|
||||
parts.push('<span class="' + config.counterDigitLargeClass + '">' + days + 'd' + '</span>');
|
||||
}
|
||||
if(
|
||||
hrs > 0 ||
|
||||
parts.length > 0
|
||||
){
|
||||
parts.push('<span class="' + config.counterDigitSmallClass + '">' + hrs + 'h' + '</span>');
|
||||
}
|
||||
if(
|
||||
min > 0 ||
|
||||
parts.length > 0
|
||||
){
|
||||
parts.push('<span class="' + config.counterDigitSmallClass + '">' + min + 'm' + '</span>');
|
||||
}
|
||||
|
||||
if(
|
||||
leftSec >= 0 ||
|
||||
parts.length > 0
|
||||
){
|
||||
parts.push('<span class="' + config.counterDigitSmallClass + '">' + leftSec + 's' + '</span>');
|
||||
}
|
||||
}
|
||||
|
||||
element.html(parts.join(' '));
|
||||
};
|
||||
|
||||
/**
|
||||
* destroy all active counter recursive
|
||||
*/
|
||||
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 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 element
|
||||
* @param round e.g. 'd' => round days
|
||||
* @returns {void|*|undefined}
|
||||
*/
|
||||
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);
|
||||
|
||||
// show element (if invisible) after first update
|
||||
element.css({'visibility': 'initial'});
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
counterTask.start();
|
||||
|
||||
element.attr(config.counterTaskAttr, taskName);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* init global timestamp counter or DataTable for specific columns
|
||||
* @param tableElement
|
||||
* @param columnSelector
|
||||
* @param round
|
||||
*/
|
||||
let initTableCounter = (tableElement, columnSelector, round) => {
|
||||
let tableApi = tableElement.api();
|
||||
let taskName = tableElement.attr('id');
|
||||
|
||||
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 counterTask = Cron.new(taskName, {precision: 'seconds', interval: 1, timeout: 100});
|
||||
counterTask.task = timer => {
|
||||
tableApi.cells(null, columnSelector).every(cellUpdate);
|
||||
};
|
||||
counterTask.start();
|
||||
|
||||
tableElement.attr(config.counterTaskAttr, taskName);
|
||||
};
|
||||
|
||||
return {
|
||||
config: config,
|
||||
updateDateDiff: updateDateDiff,
|
||||
initTimestampCounter: initTimestampCounter,
|
||||
initTableCounter: initTableCounter,
|
||||
destroyTimestampCounter: destroyTimestampCounter
|
||||
};
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
BIN
public/js/v2.0.0/app/datatables.loader.js.br
Normal file
BIN
public/js/v2.0.0/app/datatables.loader.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/datatables.loader.js.map
Normal file
1
public/js/v2.0.0/app/datatables.loader.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,590 +0,0 @@
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
let frigWH = {
|
||||
1: 'E004 - C1',
|
||||
2: 'L005 - C2',
|
||||
3: 'Z006 - C3',
|
||||
4: 'M001 - C4',
|
||||
5: 'C008 - C5',
|
||||
6: 'G008 - C6',
|
||||
7: 'Q003 - 0.0',
|
||||
8: 'A009 - C13'
|
||||
};
|
||||
|
||||
return {
|
||||
path: {
|
||||
api: '/api/rest', //ajax URL - REST API
|
||||
// user API
|
||||
getCaptcha: '/api/User/getCaptcha', // ajax URL - get captcha image
|
||||
getServerStatus: '/api/User/getEveServerStatus', // ajax URL - get EVE-Online server status
|
||||
getCookieCharacterData: '/api/User/getCookieCharacter', // ajax URL - get character data from cookie
|
||||
logIn: '/api/User/logIn', // ajax URL - login
|
||||
logout: '/api/User/logout', // ajax URL - logout
|
||||
openIngameWindow: '/api/User/openIngameWindow', // ajax URL - open inGame Window
|
||||
saveUserConfig: '/api/User/saveAccount', // ajax URL - saves/update user account
|
||||
deleteAccount: '/api/User/deleteAccount', // ajax URL - delete Account data
|
||||
// access API
|
||||
searchAccess: '/api/Access/search', // ajax URL - search user/corporation/ally by name
|
||||
// main config/map ping API
|
||||
initData: '/api/Map/initData', // ajax URL - get static configuration data
|
||||
getAccessData: '/api/Map/getAccessData', // ajax URL - get map access tokens (WebSocket)
|
||||
updateMapData: '/api/Map/updateData', // ajax URL - main map update trigger
|
||||
updateUserData: '/api/Map/updateUserData', // ajax URL - main map user data trigger
|
||||
updateUnloadData: '/api/Map/updateUnloadData', // post URL - for my sync onUnload
|
||||
// map API
|
||||
importMap: '/api/Map/import', // ajax URL - import map
|
||||
getMapConnectionData: '/api/Map/getConnectionData', // ajax URL - get connection data
|
||||
getMapLogData: '/api/Map/getLogData', // ajax URL - get logs data
|
||||
// system API
|
||||
setDestination: '/api/System/setDestination', // ajax URL - set destination
|
||||
pokeRally: '/api/System/pokeRally', // ajax URL - send rally point pokes
|
||||
// stats API
|
||||
getStatisticsData: '/api/Statistic/getData', // ajax URL - get statistics data (activity log)
|
||||
// universe API
|
||||
searchUniverseData: '/api/Universe/search', // ajax URL - search universe data by category Ids
|
||||
searchUniverseSystemData: '/api/Universe/systems', // ajax URL - search universe system data by name
|
||||
getConstellationData: '/api/Universe/constellationData', // ajax URL - get system constellation data
|
||||
// GitHub API
|
||||
gitHubReleases: '/api/GitHub/releases' // ajax URL - get release info from GitHub
|
||||
},
|
||||
breakpoints: [
|
||||
{name: 'screen-xl', width: Infinity},
|
||||
{name: 'screen-l', width: 1600},
|
||||
{name: 'screen-m', width: 1200},
|
||||
{name: 'screen-d', width: 1000},
|
||||
{name: 'screen-s', width: 780},
|
||||
{name: 'screen-xs', width: 480}
|
||||
],
|
||||
animationSpeed: {
|
||||
splashOverlay: 300, // "splash" loading overlay
|
||||
headerLink: 100, // links in head bar
|
||||
mapOverlay: 200, // show/hide duration for map overlays
|
||||
mapOverlayLocal: 180, // show/hide duration for map "local" overlay
|
||||
mapMoveSystem: 180, // system position has changed animation
|
||||
mapDeleteSystem: 200, // remove system from map
|
||||
mapModule: 200, // show/hide of an map module
|
||||
dialogEvents: 180 // dialog events /slide/show/...
|
||||
},
|
||||
syncStatus: {
|
||||
type: 'ajax',
|
||||
webSocket: {
|
||||
status: 'closed',
|
||||
class: 'txt-color-danger',
|
||||
timestamp: undefined
|
||||
},
|
||||
sharedWorker: {
|
||||
status: 'offline', // SharedWorker status
|
||||
class: 'txt-color-danger',
|
||||
timestamp: undefined
|
||||
},
|
||||
ajax: {
|
||||
status: 'enabled',
|
||||
class: 'txt-color-success',
|
||||
timestamp: undefined
|
||||
}
|
||||
},
|
||||
performanceLogging: {
|
||||
keyServerMapData: 'UPDATE_SERVER_MAP', // ajax request update map data
|
||||
keyClientMapData: 'UPDATE_CLIENT_MAP', // update client map data
|
||||
keyServerUserData: 'UPDATE_SERVER_USER_DATA', // ajax request update map user data
|
||||
keyClientUserData: 'UPDATE_CLIENT_USER_DATA', // update client map user data
|
||||
},
|
||||
mapIcons: [ // map tab-icons
|
||||
{
|
||||
class: 'fa-desktop',
|
||||
label: 'desktop',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-space-shuttle',
|
||||
label: 'space shuttle',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-anchor',
|
||||
label: 'anchor',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-satellite',
|
||||
label: 'satellite',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-skull-crossbones',
|
||||
label: 'skull crossbones',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-fire',
|
||||
label: 'fire',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-bookmark',
|
||||
label: 'bookmark',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-cube',
|
||||
label: 'cube',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-star',
|
||||
label: 'star',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-hat-wizard',
|
||||
label: 'hat wizard',
|
||||
unicode: ''
|
||||
},{
|
||||
class: 'fa-cross',
|
||||
label: 'cross',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-cannabis',
|
||||
label: 'cannabis',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-spider',
|
||||
label: 'spider',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-plane',
|
||||
label: 'plane',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-globe',
|
||||
label: 'globe',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-rocket',
|
||||
label: 'rocket',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-life-ring',
|
||||
label: 'life ring',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-heart',
|
||||
label: 'heart',
|
||||
unicode: ''
|
||||
}, {
|
||||
class: 'fa-poop',
|
||||
label: 'poop',
|
||||
unicode: ''
|
||||
}
|
||||
],
|
||||
classes: {
|
||||
// log types
|
||||
logTypes: {
|
||||
info: {
|
||||
class: 'pf-log-info',
|
||||
label: 'info'
|
||||
},
|
||||
warning: {
|
||||
class: 'pf-log-warning',
|
||||
label: 'warning'
|
||||
},
|
||||
error: {
|
||||
class: 'pf-log-error',
|
||||
label: 'error'
|
||||
}
|
||||
},
|
||||
// system effects
|
||||
systemEffects: {
|
||||
effect: {
|
||||
class: 'pf-system-effect',
|
||||
name: 'no effect'
|
||||
},
|
||||
magnetar: {
|
||||
class: 'pf-system-effect-magnetar',
|
||||
name: 'magnetar'
|
||||
},
|
||||
redGiant: {
|
||||
class: 'pf-system-effect-redgiant',
|
||||
name: 'red giant'
|
||||
},
|
||||
pulsar: {
|
||||
class: 'pf-system-effect-pulsar',
|
||||
name: 'pulsar'
|
||||
},
|
||||
wolfRayet: {
|
||||
class: 'pf-system-effect-wolfrayet',
|
||||
name: 'wolf rayet'
|
||||
},
|
||||
cataclysmic: {
|
||||
class: 'pf-system-effect-cataclysmic',
|
||||
name: 'cataclysmic'
|
||||
},
|
||||
blackHole: {
|
||||
class: 'pf-system-effect-blackhole',
|
||||
name: 'black hole'
|
||||
}
|
||||
},
|
||||
// system security
|
||||
systemSecurity: {
|
||||
'security': {
|
||||
class: 'pf-system-sec'
|
||||
},
|
||||
'A': {
|
||||
class: 'pf-system-sec-abyssal'
|
||||
},
|
||||
'SH': {
|
||||
class: 'pf-system-sec-unknown'
|
||||
},
|
||||
'H': {
|
||||
class: 'pf-system-sec-highSec'
|
||||
},
|
||||
'L': {
|
||||
class: 'pf-system-sec-lowSec'
|
||||
},
|
||||
'0.0': {
|
||||
class: 'pf-system-sec-nullSec'
|
||||
},
|
||||
'C1': {
|
||||
class: 'pf-system-sec-low'
|
||||
},
|
||||
'C2': {
|
||||
class: 'pf-system-sec-low'
|
||||
},
|
||||
'C3': {
|
||||
class: 'pf-system-sec-mid'
|
||||
},
|
||||
'C4': {
|
||||
class: 'pf-system-sec-mid'
|
||||
},
|
||||
'C5': {
|
||||
class: 'pf-system-sec-high'
|
||||
},
|
||||
'C6': {
|
||||
class: 'pf-system-sec-high'
|
||||
},
|
||||
'C12': {
|
||||
class: 'pf-system-sec-special'
|
||||
},
|
||||
'C14': {
|
||||
class: 'pf-system-sec-drifter'
|
||||
},
|
||||
'C15': {
|
||||
class: 'pf-system-sec-drifter'
|
||||
},
|
||||
'C16': {
|
||||
class: 'pf-system-sec-drifter'
|
||||
},
|
||||
'C17': {
|
||||
class: 'pf-system-sec-drifter'
|
||||
},
|
||||
'C18': {
|
||||
class: 'pf-system-sec-drifter'
|
||||
}
|
||||
},
|
||||
// true sec
|
||||
trueSec: {
|
||||
'0.0': {
|
||||
class: 'pf-system-security-0-0'
|
||||
},
|
||||
'0.1': {
|
||||
class: 'pf-system-security-0-1'
|
||||
},
|
||||
'0.2': {
|
||||
class: 'pf-system-security-0-2'
|
||||
},
|
||||
'0.3': {
|
||||
class: 'pf-system-security-0-3'
|
||||
},
|
||||
'0.4': {
|
||||
class: 'pf-system-security-0-4'
|
||||
},
|
||||
'0.5': {
|
||||
class: 'pf-system-security-0-5'
|
||||
},
|
||||
'0.6': {
|
||||
class: 'pf-system-security-0-6'
|
||||
},
|
||||
'0.7': {
|
||||
class: 'pf-system-security-0-7'
|
||||
},
|
||||
'0.8': {
|
||||
class: 'pf-system-security-0-8'
|
||||
},
|
||||
'0.9': {
|
||||
class: 'pf-system-security-0-9'
|
||||
},
|
||||
'1.0': {
|
||||
class: 'pf-system-security-1-0'
|
||||
}
|
||||
},
|
||||
// system info
|
||||
systemInfo: {
|
||||
rally: {
|
||||
class: 'pf-system-info-rally',
|
||||
label: 'rally point'
|
||||
}
|
||||
},
|
||||
// planets
|
||||
planets: {
|
||||
barren: {
|
||||
class: 'pf-planet-barren'
|
||||
},
|
||||
gas: {
|
||||
class: 'pf-planet-gas'
|
||||
},
|
||||
ice: {
|
||||
class: 'pf-planet-ice'
|
||||
},
|
||||
lava: {
|
||||
class: 'pf-planet-lava'
|
||||
},
|
||||
oceanic: {
|
||||
class: 'pf-planet-oceanic'
|
||||
},
|
||||
plasma: {
|
||||
class: 'pf-planet-plasma'
|
||||
},
|
||||
shattered: {
|
||||
class: 'pf-planet-shattered'
|
||||
},
|
||||
storm: {
|
||||
class: 'pf-planet-storm'
|
||||
},
|
||||
temperate: {
|
||||
class: 'pf-planet-temperate'
|
||||
}
|
||||
},
|
||||
// easy-pie-charts
|
||||
pieChart: {
|
||||
class: 'pf-pie-chart', // class for all pie charts
|
||||
pieChartMapCounterClass: 'pf-pie-chart-map-timer' // class for timer chart
|
||||
}
|
||||
},
|
||||
// map scopes
|
||||
defaultMapScope: 'wh', // default scope for connection
|
||||
// map endpoint types
|
||||
endpointTypes: {
|
||||
bubble: {
|
||||
cssClass: 'pf-map-endpoint-bubble',
|
||||
}
|
||||
},
|
||||
// map connection types
|
||||
connectionTypes: {
|
||||
abyssal: {
|
||||
cssClass: 'pf-map-connection-abyssal',
|
||||
paintStyle: {
|
||||
dashstyle: '0.5 2' // dotted line
|
||||
}
|
||||
},
|
||||
jumpbridge: {
|
||||
cssClass: 'pf-map-connection-jumpbridge',
|
||||
paintStyle: {
|
||||
dashstyle: '4 2 1 2'
|
||||
}
|
||||
},
|
||||
stargate: {
|
||||
cssClass: 'pf-map-connection-stargate',
|
||||
paintStyle: {
|
||||
dashstyle: '0' // solid line
|
||||
}
|
||||
},
|
||||
wh_eol: {
|
||||
cssClass: 'pf-map-connection-wh-eol'
|
||||
},
|
||||
wh_fresh: {
|
||||
cssClass: 'pf-map-connection-wh-fresh'
|
||||
},
|
||||
wh_reduced: {
|
||||
cssClass: 'pf-map-connection-wh-reduced'
|
||||
},
|
||||
wh_critical: {
|
||||
cssClass: 'pf-map-connection-wh-critical'
|
||||
},
|
||||
wh_jump_mass_s: {
|
||||
cssClass: 'pf-map-connection-wh-size-s',
|
||||
paintStyle: {
|
||||
dashstyle: '0.5 1',
|
||||
strokeWidth: 3
|
||||
},
|
||||
overlays: [
|
||||
['Label',
|
||||
{
|
||||
label: '<i class="fas fa-char pf-jump-mass-s" data-char-content="S"></i>',
|
||||
cssClass: ['pf-map-component-overlay', 'small', 'text-center'].join(' '),
|
||||
location: 0.65,
|
||||
id: 'pf-map-connection-jump-mass-overlay'
|
||||
}]
|
||||
]
|
||||
},
|
||||
wh_jump_mass_m: {
|
||||
cssClass: 'pf-map-connection-wh-size-m',
|
||||
paintStyle: {
|
||||
dashstyle: '3 1'
|
||||
},
|
||||
overlays: [
|
||||
['Label',
|
||||
{
|
||||
label: '<i class="fas fa-char pf-jump-mass-m" data-char-content="M"></i>',
|
||||
cssClass: ['pf-map-component-overlay', 'small', 'text-center'].join(' '),
|
||||
location: 0.65,
|
||||
id: 'pf-map-connection-jump-mass-overlay'
|
||||
}]
|
||||
]
|
||||
},
|
||||
wh_jump_mass_l: {
|
||||
cssClass: 'pf-map-connection-wh-size-l',
|
||||
overlays: [
|
||||
['Label',
|
||||
{
|
||||
label: '<i class="fas fa-char pf-jump-mass-l" data-char-content="L"></i>',
|
||||
cssClass: ['pf-map-component-overlay', 'small', 'text-center'].join(' '),
|
||||
location: 0.65,
|
||||
id: 'pf-map-connection-jump-mass-overlay'
|
||||
}]
|
||||
]
|
||||
},
|
||||
wh_jump_mass_xl: {
|
||||
cssClass: 'pf-map-connection-wh-size-xl',
|
||||
paintStyle: {
|
||||
strokeWidth: 6
|
||||
},
|
||||
overlays: [
|
||||
['Label',
|
||||
{
|
||||
label: '<i class="fas fa-char pf-jump-mass-xl" data-char-content="XL"></i>',
|
||||
cssClass: ['pf-map-component-overlay', 'small', 'text-center'].join(' '),
|
||||
location: 0.65,
|
||||
id: 'pf-map-connection-jump-mass-overlay'
|
||||
}]
|
||||
]
|
||||
},
|
||||
preserve_mass: {
|
||||
cssClass: 'pf-map-connection-preserve-mass',
|
||||
overlays: [
|
||||
['Label',
|
||||
{
|
||||
label: '<i class="fas fa-fw fa-exclamation-triangle"></i> save mass',
|
||||
cssClass: ['pf-map-component-overlay', 'mass'].join(' '),
|
||||
location: 0.35
|
||||
}]
|
||||
]
|
||||
},
|
||||
info_signature: {
|
||||
overlays: [
|
||||
['Arrow',
|
||||
{
|
||||
id: 'pf-map-connection-arrow-overlay',
|
||||
cssClass: 'pf-map-connection-arrow-overlay',
|
||||
width: 12,
|
||||
length: 15,
|
||||
direction: 1,
|
||||
foldback: 0.8,
|
||||
location: 0.5
|
||||
}]
|
||||
]
|
||||
},
|
||||
state_active: {
|
||||
cssClass: 'pf-map-connection-active'
|
||||
},
|
||||
state_process: {
|
||||
cssClass: 'pf-map-connection-process',
|
||||
overlays: [
|
||||
['Label',
|
||||
{
|
||||
label: '<i class="fas fa-fw fa-sync fa-spin"></i>',
|
||||
cssClass: ['pf-map-connection-state-overlay'].join(' '),
|
||||
location: 0.5
|
||||
}]
|
||||
]
|
||||
}
|
||||
},
|
||||
wormholeSizes: {
|
||||
wh_jump_mass_xl: {
|
||||
jumpMassMin: 1000000000,
|
||||
type: 'wh_jump_mass_xl',
|
||||
class: 'pf-jump-mass-xl',
|
||||
label: 'XL',
|
||||
text: 'capital ships'
|
||||
},
|
||||
wh_jump_mass_l: {
|
||||
jumpMassMin: 300000000,
|
||||
type: 'wh_jump_mass_l',
|
||||
class: 'pf-jump-mass-l',
|
||||
label: 'L',
|
||||
text: 'larger ships'
|
||||
},
|
||||
wh_jump_mass_m: {
|
||||
jumpMassMin: 20000000,
|
||||
type: 'wh_jump_mass_m',
|
||||
class: 'pf-jump-mass-m',
|
||||
label: 'M',
|
||||
text: 'medium ships'
|
||||
},
|
||||
wh_jump_mass_s: {
|
||||
jumpMassMin: 1000,
|
||||
type: 'wh_jump_mass_s',
|
||||
class: 'pf-jump-mass-s',
|
||||
label: 'S',
|
||||
text: 'smallest ships'
|
||||
}
|
||||
},
|
||||
// signature groups
|
||||
signatureGroups: {
|
||||
1: {
|
||||
name: '(combat site|kampfgebiet|site de combat|Боевой район|战斗地点)',
|
||||
label: 'Combat'
|
||||
},
|
||||
2: {
|
||||
name: '(relic site|reliktgebiet|site de reliques|Археологический район|遗迹地点)',
|
||||
label: 'Relic'
|
||||
},
|
||||
3: {
|
||||
name: '(data site|datengebiet|site de données|Информационный район|数据地点)',
|
||||
label: 'Data'
|
||||
},
|
||||
4: {
|
||||
name: '(gas site|gasgebiet|site de collecte de gaz|Газовый район|气云地点)',
|
||||
label: 'Gas'
|
||||
},
|
||||
5: {
|
||||
name: '(wormhole|wurmloch|trou de ver|Червоточина|虫洞)',
|
||||
label: 'Wormhole'
|
||||
},
|
||||
6: {
|
||||
name: '(ore site|mineraliengebiet|site de minerai|Астероидный район|矿石地点)',
|
||||
label: 'Ore'
|
||||
},
|
||||
7: {
|
||||
name: '(ghost|Призрачный)',
|
||||
label: 'Ghost'
|
||||
}
|
||||
},
|
||||
// frigate wormholes
|
||||
frigateWormholes: {
|
||||
1: frigWH, // C1
|
||||
2: frigWH, // C2
|
||||
3: frigWH, // C3
|
||||
4: frigWH, // C4
|
||||
5: frigWH, // C5
|
||||
6: frigWH, // C6
|
||||
13: frigWH, // Shattered Wormholes (some of them are static)
|
||||
30: frigWH, // High Sec
|
||||
31: frigWH, // Low Sec
|
||||
32: frigWH // 0.0
|
||||
},
|
||||
// Drifter wormholes (can only appear in k-space)
|
||||
drifterWormholes: {
|
||||
1: 'S877 - C14 Sentinel',
|
||||
2: 'B735 - C15 Barbican',
|
||||
3: 'V928 - C16 Vidette',
|
||||
4: 'C414 - C17 Conflux',
|
||||
5: 'R259 - C18 Redoubt'
|
||||
},
|
||||
// incoming wormholes
|
||||
incomingWormholes: {
|
||||
1: 'K162 - C1/2/3 (unknown)',
|
||||
2: 'K162 - C4/5 (dangerous)',
|
||||
3: 'K162 - C6 (deadly)',
|
||||
4: 'K162 - H',
|
||||
5: 'K162 - L',
|
||||
6: 'K162 - 0.0',
|
||||
7: 'K162 - C12 Thera'
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,478 +0,0 @@
|
||||
define([
|
||||
'jquery'
|
||||
], ($) => {
|
||||
'use strict';
|
||||
|
||||
let allCombo = {
|
||||
// global -------------------------------------------------------------------------------------------
|
||||
tabReload: {
|
||||
group: 'global',
|
||||
label: 'Close open dialog',
|
||||
keyNames: ['ESC']
|
||||
},
|
||||
// map ----------------------------------------------------------------------------------------------
|
||||
mapMove: {
|
||||
group: 'map',
|
||||
label: 'Move map section',
|
||||
keyNames: ['space', 'drag']
|
||||
},
|
||||
// signature ----------------------------------------------------------------------------------------
|
||||
signatureSelect: {
|
||||
group: 'signatures',
|
||||
label: 'Select multiple rows',
|
||||
keyNames: ['CONTROL', 'CLICK']
|
||||
},
|
||||
signatureNavigate: {
|
||||
group: 'signatures',
|
||||
label: 'Table navigation',
|
||||
keyNames: ['UP', 'RIGHT', 'DOWN', 'LEFT'],
|
||||
list: true
|
||||
}
|
||||
};
|
||||
|
||||
let allEvents = {
|
||||
// global -------------------------------------------------------------------------------------------
|
||||
tabReload: {
|
||||
group: 'global',
|
||||
label: 'Reload tab',
|
||||
keyNames: ['CONTROL', 'R']
|
||||
},
|
||||
clipboardPaste: {
|
||||
group: 'global',
|
||||
label: 'Update signatures/D-Scan from clipboard',
|
||||
keyNames: ['CONTROL', 'V'],
|
||||
alias: 'paste'
|
||||
},
|
||||
renameSystem: {
|
||||
group: 'map',
|
||||
label: 'Rename system',
|
||||
keyNames: ['ALT', 'N']
|
||||
},
|
||||
newSignature: {
|
||||
group: 'signatures',
|
||||
label: 'New Signature',
|
||||
keyNames: ['ALT', '3']
|
||||
},
|
||||
|
||||
// map ----------------------------------------------------------------------------------------------
|
||||
mapSystemAdd: {
|
||||
group: 'map',
|
||||
label: 'New system',
|
||||
keyNames: ['ALT', '2']
|
||||
},
|
||||
mapSystemsSelect: {
|
||||
group: 'map',
|
||||
label: 'Select all systems',
|
||||
keyNames: ['CONTROL', 'A']
|
||||
},
|
||||
mapSystemsDelete: {
|
||||
group: 'map',
|
||||
label: 'Delete selected systems',
|
||||
keyNames: ['CONTROL', 'D']
|
||||
}
|
||||
};
|
||||
|
||||
let groups = {
|
||||
global: {
|
||||
label: 'Global'
|
||||
},
|
||||
map: {
|
||||
label: 'Map'
|
||||
},
|
||||
signatures: {
|
||||
label: 'Signature'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* enables some debug output in console
|
||||
* @type {boolean}
|
||||
*/
|
||||
let debug = false;
|
||||
|
||||
/**
|
||||
* DOM data key for an element that lists all active events (comma separated)
|
||||
* @type {string}
|
||||
*/
|
||||
let dataKeyEvents = 'key-events';
|
||||
|
||||
/**
|
||||
* DOM data key prefix whether domElement that holds the trigger needs to be "focused"
|
||||
* @type {string}
|
||||
*/
|
||||
let dataKeyFocusPrefix = 'key-focus-';
|
||||
|
||||
/**
|
||||
* DOM data key that holds the callback function for that element
|
||||
* @type {string}
|
||||
*/
|
||||
let dataKeyCallbackPrefix = 'key-callback-';
|
||||
|
||||
/**
|
||||
* check if module is initiated
|
||||
*/
|
||||
let isInit = false;
|
||||
|
||||
/**
|
||||
* global key map holds all active (hold down) keys
|
||||
* @type {{}}
|
||||
*/
|
||||
let map = {};
|
||||
|
||||
/**
|
||||
* show debug information in console
|
||||
* @param msg
|
||||
* @param element
|
||||
*/
|
||||
let debugWatchKey = (msg, element) => {
|
||||
if(debug){
|
||||
console.info(msg, element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* get all active (hold down) keys at this moment
|
||||
* @returns {Array}
|
||||
*/
|
||||
let getActiveKeys = () => {
|
||||
return Object.keys(map);
|
||||
};
|
||||
|
||||
/**
|
||||
* checks whether a key is currently active (keydown)
|
||||
* @param key
|
||||
* @returns {boolean}
|
||||
*/
|
||||
let isActive = key => map.hasOwnProperty(key) && map[key] === true;
|
||||
|
||||
/**
|
||||
* callback function that compares two arrays
|
||||
* @param element
|
||||
* @param index
|
||||
* @param array
|
||||
*/
|
||||
let compareKeyLists = function(element, index, array){
|
||||
return this.find(x => x === element);
|
||||
};
|
||||
|
||||
/**
|
||||
* get event names that COULD lead to a "full" event (not all keys pressed yet)
|
||||
* @param keyList
|
||||
* @returns {Array}
|
||||
*/
|
||||
let checkEventNames = (keyList) => {
|
||||
let incompleteEvents = [];
|
||||
for(let event in allEvents){
|
||||
// check if "some" or "all" keys are pressed for en event
|
||||
if( keyList.every(compareKeyLists, allEvents[event].keyNames) ){
|
||||
incompleteEvents.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
return incompleteEvents;
|
||||
};
|
||||
|
||||
/**
|
||||
* get all event names
|
||||
* @returns {Array}
|
||||
*/
|
||||
let getAllEventNames = () => {
|
||||
let eventNames = [];
|
||||
for(let event in allEvents){
|
||||
eventNames.push(event);
|
||||
}
|
||||
return eventNames;
|
||||
};
|
||||
|
||||
/**
|
||||
* get all event names that matches a given keyList
|
||||
* @param keyList
|
||||
* @param checkEvents
|
||||
* @returns {Array}
|
||||
*/
|
||||
let getMatchingEventNames = (keyList, checkEvents) => {
|
||||
checkEvents = checkEvents || getAllEventNames();
|
||||
let events = [];
|
||||
|
||||
for(let event of checkEvents){
|
||||
// check if both key arrays are equal
|
||||
if(
|
||||
allEvents[event].keyNames.every(compareKeyLists, keyList) &&
|
||||
keyList.every(compareKeyLists, allEvents[event].keyNames)
|
||||
){
|
||||
events.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
};
|
||||
|
||||
/**
|
||||
* init global keyWatch interval and check for event trigger (hotKey combinations)
|
||||
*/
|
||||
let init = () => {
|
||||
if( !isInit ){
|
||||
// key watch loop -------------------------------------------------------------------------------
|
||||
let prevActiveKeys = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
* @returns {number} 0: no keys hold, 1: invalid match, 2: partial match, 3: match, 4: alias match, 5: event(s) fired
|
||||
*/
|
||||
let checkForEvents = (e) => {
|
||||
let status = 0;
|
||||
|
||||
// get all pressed keys
|
||||
let activeKeys = getActiveKeys();
|
||||
debugWatchKey('activeKeys', activeKeys);
|
||||
|
||||
// check if "active" keys has changes since last loop
|
||||
if(activeKeys.length){
|
||||
// check for "incomplete" events (not all keys pressed yet)
|
||||
let incompleteEvents = checkEventNames(activeKeys);
|
||||
if(incompleteEvents.length){
|
||||
// "some" event keys pressed OR "all" keys pressed
|
||||
status = 2;
|
||||
|
||||
// check if key combo matches a registered (valid) event
|
||||
let events = getMatchingEventNames(activeKeys, incompleteEvents);
|
||||
if(events.length){
|
||||
status = 3;
|
||||
// check events if there are attached elements to it
|
||||
events.forEach((event) => {
|
||||
// skip events that has an alias and should not be triggered by key combo
|
||||
if( !allEvents[event].alias ){
|
||||
if(allEvents[event].elements){
|
||||
// search for callback functions attached to each element
|
||||
allEvents[event].elements.forEach((domElement) => {
|
||||
let domElementObj = $(domElement);
|
||||
// check if event on this element requires active "focus"
|
||||
let optFocus = domElementObj.data(dataKeyFocusPrefix + event);
|
||||
|
||||
if( !(
|
||||
optFocus &&
|
||||
document.activeElement !== domElement
|
||||
)
|
||||
){
|
||||
// execute callback if valid
|
||||
let callback = domElementObj.data(dataKeyCallbackPrefix + event);
|
||||
if(typeof callback === 'function'){
|
||||
status = 5;
|
||||
callback.call(domElement, domElement, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}else{
|
||||
status = 4;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}else{
|
||||
// invalid combo
|
||||
status = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// store current keys for next loop check
|
||||
prevActiveKeys = activeKeys;
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
// set key-events -------------------------------------------------------------------------------
|
||||
let evKeyDown = (e) => {
|
||||
// exclude some HTML Tags from watcher
|
||||
if(
|
||||
e.target.tagName !== 'INPUT' &&
|
||||
e.target.tagName !== 'TEXTAREA' &&
|
||||
!e.target.classList.contains('note-editable') // Summerstyle editor
|
||||
){
|
||||
let key = e.key.toUpperCase();
|
||||
map[key] = true;
|
||||
|
||||
// check for any shortcut combo that triggers an event
|
||||
let status = checkForEvents(e);
|
||||
|
||||
if(
|
||||
status === 2 ||
|
||||
status === 3 ||
|
||||
status === 5
|
||||
){
|
||||
// prevent SOME browser default actions -> we want 'Pathfinder' shortcuts :)
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let evKeyUp = (e) => {
|
||||
if(e.key){
|
||||
let key = e.key.toUpperCase();
|
||||
|
||||
if(map.hasOwnProperty(key)){
|
||||
delete map[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let container = $('body');
|
||||
container.on('keydown', evKeyDown);
|
||||
container.on('keyup', evKeyUp);
|
||||
|
||||
// global dom remove listener -------------------------------------------------------------------
|
||||
// -> check whether the removed element had an event listener active and removes them.
|
||||
new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if(mutation.type === 'childList'){
|
||||
for(let i = 0; i < mutation.removedNodes.length; i++){
|
||||
let removedNode = mutation.removedNodes[i];
|
||||
if(typeof removedNode.getAttribute === 'function'){
|
||||
let eventNames = removedNode.getAttribute(dataKeyEvents);
|
||||
if(eventNames){
|
||||
let events = eventNames.split(',');
|
||||
for(let j = 0; i < events.length; j++){
|
||||
let event = events[j];
|
||||
let index = allEvents[event].elements.indexOf(removedNode);
|
||||
if(index > -1){
|
||||
// remove element from event list
|
||||
allEvents[event].elements.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}).observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
isInit = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* add a new "shortCut" combination (event) to a DOM element
|
||||
* @param event
|
||||
* @param callback
|
||||
* @param options
|
||||
*/
|
||||
$.fn.watchKey = function(event, callback, options){
|
||||
|
||||
// default options for keyWatcher on elements
|
||||
let defaultOptions = {
|
||||
focus: false, // element must be focused (active)
|
||||
bubbling: true // elements deeper (children) in the DOM can bubble the event up
|
||||
};
|
||||
|
||||
let customOptions = $.extend(true, {}, defaultOptions, options );
|
||||
|
||||
return this.each((i, domElement) => {
|
||||
let element = $(domElement);
|
||||
|
||||
// init global key events
|
||||
init();
|
||||
|
||||
// check if event is "valid" (exists) and is not already set for this element
|
||||
let validEvent = false;
|
||||
if(allEvents[event].elements){
|
||||
if(allEvents[event].elements.indexOf(domElement) === -1){
|
||||
validEvent = true;
|
||||
}else{
|
||||
console.warn('Event "' + event + '" already set');
|
||||
}
|
||||
}else{
|
||||
validEvent = true;
|
||||
allEvents[event].elements = [];
|
||||
}
|
||||
|
||||
if(validEvent){
|
||||
// store callback options to dom element
|
||||
if(customOptions.focus){
|
||||
let dataAttr = dataKeyFocusPrefix + event;
|
||||
element.data(dataAttr, true);
|
||||
|
||||
// check if DOM element has "tabindex" attr -> required to manually set focus() to it
|
||||
if(!domElement.hasAttribute('tabindex')){
|
||||
domElement.setAttribute('tabindex', 0);
|
||||
}
|
||||
|
||||
// element requires a "focus" listener
|
||||
element.off('click.focusKeyWatcher').on('click.focusKeyWatcher', function(e){
|
||||
if(
|
||||
e.target === this ||
|
||||
customOptions.bubbling
|
||||
){
|
||||
this.focus();
|
||||
debugWatchKey('focus set:', this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// check if is key combo has a native JS event that should be used instead
|
||||
if(allEvents[event].alias){
|
||||
element.on(allEvents[event].alias, callback);
|
||||
}else{
|
||||
// store callback function to dom element
|
||||
let dataAttr = dataKeyCallbackPrefix + event;
|
||||
element.data(dataAttr, callback);
|
||||
}
|
||||
|
||||
// add eventName to dom element as attribute ----------------------------------------------------
|
||||
let currentEventNames = element.attr(dataKeyEvents) ? element.attr(dataKeyEvents).split(',') : [];
|
||||
currentEventNames.push(event);
|
||||
element.attr(dataKeyEvents, currentEventNames.join(','));
|
||||
|
||||
// store domElement to event (global)
|
||||
allEvents[event].elements.push(domElement);
|
||||
|
||||
debugWatchKey('new event "' + event + '" registered', domElement);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* get a array with all available shortcut groups and their events
|
||||
* @returns {any[]}
|
||||
*/
|
||||
let getGroupedShortcuts = () => {
|
||||
let result = $.extend(true, {}, groups);
|
||||
|
||||
// add combos and events to groups
|
||||
let allEntries = [allCombo, allEvents];
|
||||
|
||||
for(let entries of allEntries){
|
||||
for(let [event, data] of Object.entries(entries)){
|
||||
//format keyNames for UI
|
||||
let keyNames = data.keyNames.map(key => {
|
||||
if(key === 'CONTROL'){
|
||||
key = 'ctrl';
|
||||
}
|
||||
return key;
|
||||
});
|
||||
|
||||
let newEventData = {
|
||||
label: data.label,
|
||||
keyNames: keyNames,
|
||||
list: data.list
|
||||
};
|
||||
|
||||
if( result[data.group].events ){
|
||||
result[data.group].events.push(newEventData);
|
||||
}else{
|
||||
result[data.group].events = [newEventData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert obj into array
|
||||
result = Object.values(result);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return {
|
||||
isActive: isActive,
|
||||
getGroupedShortcuts: getGroupedShortcuts
|
||||
};
|
||||
});
|
||||
@@ -1,276 +0,0 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Abstract Cache Strategy class
|
||||
* @type {AbstractStrategy}
|
||||
*/
|
||||
class AbstractStrategy {
|
||||
constructor(){
|
||||
if(new.target === AbstractStrategy){
|
||||
throw new TypeError('Cannot construct AbstractStrategy instances directly');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* factory for Strategy* instances
|
||||
* @returns {AbstractStrategy}
|
||||
*/
|
||||
static create(){
|
||||
return new this();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LIFO Cache Strategy - First In First Out
|
||||
* -> The cache evicts the entries in the order they were added,
|
||||
* without any regard to how often or how many times they were accessed before.
|
||||
* @type {StrategyFIFO}
|
||||
*/
|
||||
class StrategyFIFO extends AbstractStrategy {
|
||||
valueToCompare(metaData){
|
||||
return metaData.age();
|
||||
}
|
||||
|
||||
compare(a, b){
|
||||
return b - a;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LFU Cache Strategy - Least Frequently Used
|
||||
* -> The cache evicts the entries in order how often have been accessed.
|
||||
* Those that are used least often are discarded first
|
||||
* @type {StrategyLFU}
|
||||
*/
|
||||
class StrategyLFU extends AbstractStrategy {
|
||||
valueToCompare(metaData){
|
||||
return metaData.hitCount;
|
||||
}
|
||||
|
||||
compare(a, b){
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LRU Cache Strategy - Least Recently Used
|
||||
* -> The cache evicts entries that have not been used for the longest amount of time.
|
||||
* No matter how often they have been accessed.
|
||||
* @type {StrategyLRU}
|
||||
*/
|
||||
class StrategyLRU extends AbstractStrategy {
|
||||
valueToCompare(metaData){
|
||||
return metaData.hits[metaData.hits.length - 1] || metaData.set;
|
||||
}
|
||||
|
||||
compare(a, b){
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Each entry in cache also has its own instance of CacheEntryMeta
|
||||
* -> The configured Cache Strategy use this meta data for eviction policy
|
||||
* @type {CacheEntryMeta}
|
||||
*/
|
||||
class CacheEntryMeta {
|
||||
constructor(ttl, tSet){
|
||||
this._ttl = ttl; // ttl < 0 => no expire
|
||||
this._tSet = tSet || this.constructor.now();
|
||||
this._tHits = [];
|
||||
}
|
||||
|
||||
get set(){
|
||||
return this._tSet;
|
||||
}
|
||||
|
||||
get hits(){
|
||||
return this._tHits;
|
||||
}
|
||||
|
||||
get hitCount(){
|
||||
return this.hits.length;
|
||||
}
|
||||
|
||||
newHit(current){
|
||||
this._tHits.push(current || this.constructor.now());
|
||||
}
|
||||
|
||||
age(current){
|
||||
return (current || this.constructor.now()) - this._tSet;
|
||||
}
|
||||
|
||||
expired(current){
|
||||
return this._ttl < 0 ? false : this._ttl < this.age(current);
|
||||
}
|
||||
|
||||
static now(){
|
||||
return new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
static create(ttl, tSet){
|
||||
return new this(ttl, tSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Each instance of Cache represents a key value in memory data store
|
||||
* -> Name should be set to identify current Cache instance
|
||||
* -> Default ttl can be overwritten by individual entries
|
||||
* -> Cache Strategy handles eviction policy
|
||||
* -> Buffer Size (in percent) can be used to remove e.g. 10% of all entries
|
||||
* if cache reaches maxSize limit, to increase performance.
|
||||
* @type {Cache}
|
||||
*/
|
||||
class Cache {
|
||||
|
||||
constructor(config = {}){
|
||||
this._config = Object.assign({}, Cache.defaultConfig, config);
|
||||
this._store = new Map();
|
||||
this._metaStore = new WeakMap();
|
||||
this._strategy = this.constructor.setStrategy(this._config.strategy);
|
||||
|
||||
this.debug = (msg,...data) => {
|
||||
if(this._config.debug){
|
||||
data = (data || []);
|
||||
data.unshift(this._config.name);
|
||||
console.debug('debug: CACHE %o | ' + msg, ...data);
|
||||
}
|
||||
};
|
||||
|
||||
this.debug('New Cache instance');
|
||||
}
|
||||
|
||||
get size(){
|
||||
return this._store.size;
|
||||
}
|
||||
|
||||
isFull(){
|
||||
return this.size>= this._config.maxSize;
|
||||
}
|
||||
|
||||
set(key, value, ttl){
|
||||
if(this._store.has(key)){
|
||||
this.debug('SET key %o, UPDATE value %o', key, value);
|
||||
this._store.set(key, value);
|
||||
}else{
|
||||
this.debug('SET key %o, NEW value %o', key, value);
|
||||
if(this.isFull()){
|
||||
this.debug(' ↪ FULL trim cache…');
|
||||
this.trim(this.trimCount(1));
|
||||
}
|
||||
this._store.set(key, value);
|
||||
}
|
||||
|
||||
this._metaStore.set(value, CacheEntryMeta.create(ttl || this._config.ttl));
|
||||
}
|
||||
|
||||
get(key){
|
||||
if(this._store.has(key)){
|
||||
let value = this._store.get(key);
|
||||
if(value){
|
||||
let metaData = this._metaStore.get(value);
|
||||
if(metaData.expired()){
|
||||
this.debug('EXPIRED key %o delete', key);
|
||||
this.delete(key);
|
||||
}else{
|
||||
this.debug('HIT key %o', key);
|
||||
metaData.newHit();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.debug('MISS key %o', key);
|
||||
}
|
||||
|
||||
getOrDefault(key, def){
|
||||
return this.get(key) || def;
|
||||
}
|
||||
|
||||
keysForTrim(count){
|
||||
let trimKeys = [];
|
||||
let compare = [];
|
||||
for(let [key, value] of this._store){
|
||||
let metaData = this._metaStore.get(value);
|
||||
if(metaData.expired()){
|
||||
trimKeys.push(key);
|
||||
if(count === trimKeys.length){
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
compare.push({
|
||||
key: key,
|
||||
value: this._strategy.valueToCompare(metaData)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let countLeft = count - trimKeys.length;
|
||||
if(countLeft > 0){
|
||||
compare = compare.sort((a, b) => this._strategy.compare(a.value, b.value));
|
||||
trimKeys = trimKeys.concat(compare.splice(0, countLeft).map(a => a.key));
|
||||
}
|
||||
|
||||
return trimKeys;
|
||||
}
|
||||
|
||||
keys(){
|
||||
return this._store.keys();
|
||||
}
|
||||
|
||||
delete(key){
|
||||
return this._store.delete(key);
|
||||
}
|
||||
|
||||
clear(){
|
||||
this._store.clear();
|
||||
}
|
||||
|
||||
trimCount(spaceLeft){
|
||||
let bufferSize = Math.max(Math.round(this._config.maxSize / 100 * this._config.bufferSize), spaceLeft);
|
||||
return Math.min(Math.max(this.size - this._config.maxSize + bufferSize, 0), this.size);
|
||||
}
|
||||
|
||||
trim(count){
|
||||
if(count > 0){
|
||||
let trimKeys = this.keysForTrim(count);
|
||||
if(count > trimKeys.length){
|
||||
console.warn(' ↪ Failed to trim(%i) entries. Only %i in store', count, this.size);
|
||||
}
|
||||
this.debug(' ↪ DELETE min %i keys: %o', count, trimKeys);
|
||||
trimKeys.forEach(key => this.delete(key));
|
||||
}
|
||||
}
|
||||
|
||||
status(){
|
||||
return {
|
||||
config: this._config,
|
||||
store: this._store,
|
||||
metaStore: this._metaStore
|
||||
};
|
||||
}
|
||||
|
||||
static setStrategy(name){
|
||||
switch(name){
|
||||
case 'FIFO': return StrategyFIFO.create();
|
||||
case 'LFU': return StrategyLFU.create();
|
||||
case 'LRU': return StrategyLRU.create();
|
||||
default:
|
||||
throw new ReferenceError('Unknown cache strategy name: ' + name);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cache.defaultConfig = {
|
||||
name: 'Default', // custom unique name for identification
|
||||
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
|
||||
};
|
||||
|
||||
return Cache;
|
||||
});
|
||||
@@ -1,224 +0,0 @@
|
||||
/**
|
||||
* Console module
|
||||
* -> extends default window.console log object
|
||||
*/
|
||||
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* init custom window.console object
|
||||
* -> extend console obj with custom methods for styling and logging
|
||||
*/
|
||||
let initConsole = () => {
|
||||
|
||||
window.console = (origConsole => {
|
||||
// save orig methods for byPassing args to original methods
|
||||
let log = origConsole.log;
|
||||
let info = origConsole.info;
|
||||
let warn = origConsole.warn;
|
||||
let error = origConsole.error;
|
||||
|
||||
let styles = {
|
||||
'indentDefault': {
|
||||
'padding-left': '3px'
|
||||
},
|
||||
'global': {
|
||||
'font-weight': 500,
|
||||
'font-size': '11px',
|
||||
'line-height': '19px',
|
||||
'font-family': '"Fira Code", "Lucida Console"',
|
||||
},
|
||||
'debug': {
|
||||
'color': '#d747d6'
|
||||
},
|
||||
'ok': {
|
||||
'color': '#5cb85c'
|
||||
},
|
||||
'log': {
|
||||
'color': '#adadad'
|
||||
},
|
||||
'info': {
|
||||
'color': '#428bca'
|
||||
},
|
||||
'warn': {
|
||||
'color': '#ffdd9e'
|
||||
},
|
||||
'error': {
|
||||
'color': '#ff8080'
|
||||
},
|
||||
'pf': {
|
||||
'color': '#568a89'
|
||||
},
|
||||
'brand': {
|
||||
'color': '#375959',
|
||||
'line-height': '35px',
|
||||
'font-size': '25px'
|
||||
}
|
||||
};
|
||||
|
||||
let placeholders = {
|
||||
'%s': {
|
||||
'style': ['color: #e93f3b; font-style: italic', 'color: inherit']
|
||||
},
|
||||
'%i': {
|
||||
'style': ['color: #9980ff', 'color: inherit'],
|
||||
},
|
||||
'%d': {
|
||||
'style': ['color: #9980ff', 'color: inherit']
|
||||
},
|
||||
'%f': {
|
||||
'style': ['color: #9980ff', 'color: inherit']
|
||||
},
|
||||
'%o': {
|
||||
'style': ['', '']
|
||||
},
|
||||
'%O': {
|
||||
'style': ['', '']
|
||||
}
|
||||
};
|
||||
|
||||
let findPlaceholders = str => {
|
||||
let exp = new RegExp(Object.keys(placeholders).join('|'), 'g');
|
||||
let matches = str.match(exp);
|
||||
return matches ? matches : [];
|
||||
};
|
||||
|
||||
let addStylePlaceholder = str => {
|
||||
let exp = new RegExp(Object.keys(placeholders).join('|'), 'g');
|
||||
|
||||
return str.replace(exp, function(matched){
|
||||
return '%c' + matched + '%c';
|
||||
});
|
||||
};
|
||||
|
||||
let getStyleByPlaceholder = (placeholder, clear = false) => {
|
||||
let css = '';
|
||||
if(placeholders.hasOwnProperty(placeholder)){
|
||||
css = placeholders[placeholder].style[clear ? 1 : 0];
|
||||
}
|
||||
return css;
|
||||
};
|
||||
|
||||
let getStyleByLogType = (logType, props = []) => {
|
||||
let css = '';
|
||||
if(styles.hasOwnProperty(logType)){
|
||||
css = Object.keys(styles[logType])
|
||||
.filter(prop => props.length ? props.includes(prop) : true)
|
||||
.reduce((css, prop,i, affe) => {
|
||||
css += prop + ':' + styles[logType][prop] + ';';
|
||||
return css;
|
||||
}, '');
|
||||
}
|
||||
return css;
|
||||
};
|
||||
|
||||
let setLineStyleByLogType = (logType, args) => {
|
||||
if(args.length){
|
||||
let lineStyle = getStyleByLogType('global') + getStyleByLogType(logType);
|
||||
lineStyle += ['debug', 'ok', 'log', 'info', 'pf'].includes(logType) ? getStyleByLogType('indentDefault') : '';
|
||||
let bullet = ['debug', 'ok', 'log', 'info', 'pf'].includes(logType) ? '●' : '';
|
||||
|
||||
if(typeof args[0] === 'string'){
|
||||
// prepend placeholder to existing message
|
||||
args[0] = '%c' + bullet + ' ' + args[0];
|
||||
}else{
|
||||
// prepend placeholder as new message
|
||||
args.splice(0, 0, '%c' + bullet + ' ' + logType + ':');
|
||||
}
|
||||
// set line style as 2nd argument
|
||||
args.splice(1, 0, lineStyle);
|
||||
}
|
||||
};
|
||||
|
||||
let setMessageStyleByLogType = (logType, args) => {
|
||||
if(typeof args[0] === 'string') {
|
||||
let placeholdersFound = findPlaceholders(args[0]);
|
||||
let placeholderCount = placeholdersFound.length;
|
||||
|
||||
// add c% placeholders around other placeholders
|
||||
args[0] = addStylePlaceholder(args[0]);
|
||||
|
||||
// add style args for c% placeholders
|
||||
let placeholderIndex = 0;
|
||||
let argIndexStart = 1;
|
||||
let argIndexEnd = argIndexStart + placeholderCount;
|
||||
let argIndexOffset = 0;
|
||||
for (let argIndex = argIndexStart; argIndex < argIndexEnd; argIndex++) {
|
||||
args.splice(argIndex + argIndexOffset, 0, getStyleByPlaceholder(placeholdersFound[placeholderIndex]));
|
||||
argIndexOffset += 2;
|
||||
args.splice(argIndex + argIndexOffset, 0, getStyleByPlaceholder(placeholdersFound[placeholderIndex], true) + ';' + getStyleByLogType('global') + getStyleByLogType(logType));
|
||||
placeholderIndex++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
origConsole.debug = (...args) => {
|
||||
setMessageStyleByLogType('debug', args);
|
||||
setLineStyleByLogType('debug', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.ok = (...args) => {
|
||||
setMessageStyleByLogType('ok', args);
|
||||
setLineStyleByLogType('ok', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.info = (...args) => {
|
||||
setMessageStyleByLogType('info', args);
|
||||
setLineStyleByLogType('info', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.log = (...args) => {
|
||||
setMessageStyleByLogType('log', args);
|
||||
setLineStyleByLogType('log', args);
|
||||
log.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.warn = (...args) => {
|
||||
setMessageStyleByLogType('warn', args);
|
||||
setLineStyleByLogType('warn', args);
|
||||
warn.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.error = (...args) => {
|
||||
setMessageStyleByLogType('error', args);
|
||||
setLineStyleByLogType('error', args);
|
||||
error.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.pf = (...args) => {
|
||||
setMessageStyleByLogType('pf', args);
|
||||
setLineStyleByLogType('pf', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
origConsole.brand = (...args) => {
|
||||
setMessageStyleByLogType('brand', args);
|
||||
setLineStyleByLogType('brand', args);
|
||||
info.apply(origConsole, args);
|
||||
};
|
||||
|
||||
return origConsole;
|
||||
})(window.console);
|
||||
};
|
||||
|
||||
initConsole();
|
||||
|
||||
/**
|
||||
* show current program version information console
|
||||
* @param version
|
||||
*/
|
||||
let showVersionInfo = (version) => {
|
||||
console.ok('%c PATHFINDER',
|
||||
'color: #477372; font-size: 25px; margin-left: 10px; line-height: 50px; text-shadow: 1px 1px 0 #212C30; ' +
|
||||
'background: url(https://i.imgur.com/bhSr6LI.png) no-repeat;');
|
||||
console.pf('Release: %s', version);
|
||||
};
|
||||
|
||||
return {
|
||||
showVersionInfo: showVersionInfo
|
||||
};
|
||||
});
|
||||
@@ -1,482 +0,0 @@
|
||||
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.info('task1 function():', timer.getTotalTimeValues());
|
||||
return 'OK';
|
||||
};
|
||||
task1.start();
|
||||
|
||||
Example2 run task every 3 seconds ---------------------------------------------------------------------------------
|
||||
let task1 = Cron.new('task1', {precision: 'seconds', interval: 3, timeout: 100});
|
||||
task1.task = (timer) => {
|
||||
console.info('task1 function():', timer.getTotalTimeValues());
|
||||
return 'OK';
|
||||
};
|
||||
task1.start();
|
||||
|
||||
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.info('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.info('task2 Promise2():', timer.getTotalTimeValues(), payload);
|
||||
resolve('OK2');
|
||||
});
|
||||
});
|
||||
};
|
||||
task1.start();
|
||||
|
||||
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';
|
||||
task2.start();
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {{}} config
|
||||
* @param {CronManager} manager
|
||||
*/
|
||||
constructor(name, config, manager = null){
|
||||
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 = (timer, task) => {}; // task to run, instanceof Function, can also return a Promise
|
||||
this._manager = manager; // reference to CronManager() that handles this task
|
||||
this._runCount = 0; // counter for run() calls
|
||||
this._runQueue = new Map(); // current run() processes. > 1 requires config.isParallel: true
|
||||
this._lastTotalTimeValues = undefined; // time values of last run()
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get name(){
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {(function())} task
|
||||
*/
|
||||
get task() {
|
||||
return this._task;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(function())} 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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get runCount(){
|
||||
return this._runCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get precision(){
|
||||
return this.get('precision');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get paused(){
|
||||
return this.get('paused');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get targetAchieved(){
|
||||
return this.get('targetRunCount') ? this.runCount >= this.get('targetRunCount') : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get targetProgress(){
|
||||
return parseFloat(
|
||||
parseFloat(
|
||||
(!this.get('targetRunCount') || !this.runCount) ?
|
||||
0 :
|
||||
(100 / this.get('targetRunCount') * this.runCount)
|
||||
).toFixed(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param option
|
||||
* @returns {*}
|
||||
*/
|
||||
get(option){
|
||||
return this._config[option];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} option
|
||||
* @param {*} value
|
||||
*/
|
||||
set(option, value){
|
||||
this._config[option] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* connect CronManager with instance
|
||||
* @param {CronManager} manager
|
||||
*/
|
||||
connect(manager = this._manager){
|
||||
if(manager instanceof CronManager){
|
||||
if(manager !== this._manager){
|
||||
// disconnect from current manager (if exists)
|
||||
this.disconnect();
|
||||
this._manager = manager;
|
||||
}
|
||||
this._manager.set(this);
|
||||
}else{
|
||||
throw new TypeError('Parameter must be instance of CronManager. Type of "' + typeof manager + '" given');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* disconnect from CronManager
|
||||
* @param {CronManager} manager
|
||||
*/
|
||||
disconnect(manager = this._manager){
|
||||
if(manager instanceof CronManager){
|
||||
if(this.isConnected(manager)){
|
||||
this._manager.delete(this._name);
|
||||
}
|
||||
}else{
|
||||
throw new TypeError('Parameter must be instance of CronManager. Type of "' + typeof manager + '" given');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if CronManager is connected with instance
|
||||
* @param {CronManager} manager
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isConnected(manager = this._manager){
|
||||
return (manager instanceof CronManager) &&
|
||||
manager === this._manager &&
|
||||
manager.has(this._name);
|
||||
}
|
||||
|
||||
/**
|
||||
* if task is currently running
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isRunning(){
|
||||
return !!this._runQueue.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timer
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isDue(timer){
|
||||
if(this.get('dueDate') instanceof Date){
|
||||
// run once at dueDate
|
||||
if(new Date().getTime() >= this.get('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.get('interval') === 1 ||
|
||||
totalTimeValuePrecision % this.get('interval') === 0
|
||||
){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timer
|
||||
*/
|
||||
invoke(timer){
|
||||
if(
|
||||
!this.paused &&
|
||||
this.isDue(timer) &&
|
||||
(!this.isRunning() || this.get('isParallel'))
|
||||
){
|
||||
this.run(timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 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.get('timeout') > 0 ? new TimeoutPromise(runExec, this.get('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);
|
||||
|
||||
if(this.get('dueDate') instanceof Date){
|
||||
this.disconnect();
|
||||
}
|
||||
|
||||
if(this.targetAchieved){
|
||||
this.stop();
|
||||
}
|
||||
});
|
||||
|
||||
this._runQueue.set(runId, myProm);
|
||||
}
|
||||
|
||||
// Task controls ----------------------------------------------------------------------------------------------
|
||||
|
||||
start(){
|
||||
this.set('paused', false);
|
||||
this.connect();
|
||||
}
|
||||
|
||||
stop(){
|
||||
this.reset();
|
||||
this.disconnect();
|
||||
}
|
||||
|
||||
pause(){
|
||||
this.set('paused', true);
|
||||
}
|
||||
|
||||
reset(){
|
||||
this._runCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
paused: false, // if true this task will not run() but will be invoce()´ed
|
||||
targetRunCount: 0 // if > 0, task will stop if targetRunCount is reached
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An instance of CronManager() handles multiple Task()´s
|
||||
* -> Task()´s can be set()/delete() from CronManager() instance
|
||||
* @type {CronManager}
|
||||
*/
|
||||
let CronManager = class CronManager {
|
||||
|
||||
/**
|
||||
* @param {{}} config
|
||||
*/
|
||||
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.debug(msg, ...data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {{}} config
|
||||
* @returns {Task}
|
||||
*/
|
||||
new(name, config){
|
||||
return new Task(name, config, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Task} task
|
||||
*/
|
||||
set(task){
|
||||
if(task instanceof Task){
|
||||
// check for unique task name
|
||||
if(!this.has(task.name)){
|
||||
// connect new task
|
||||
// -> must be before connect(this)! (prevents infinite loop)
|
||||
this._tasks.set(task.name, task);
|
||||
task.connect(this);
|
||||
|
||||
this.debug('SET/UPDATE task: %o config: %o', task.name, task);
|
||||
// start timer (if it is not already running)
|
||||
this.auto();
|
||||
}
|
||||
}else{
|
||||
throw new TypeError('Parameter must be instance of Task');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {{}} config
|
||||
*/
|
||||
setNew(name, config){
|
||||
this.set(this.new(name, config));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {Task|undefined}
|
||||
*/
|
||||
get(name){
|
||||
return this._tasks.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(name){
|
||||
return this._tasks.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
delete(name){
|
||||
if(this.has(name)){
|
||||
let task = this._tasks.get(name);
|
||||
// disconnect task
|
||||
// -> must be before disconnect(this)! (prevents infinite loop)
|
||||
this._tasks.delete(name);
|
||||
task.disconnect(this);
|
||||
|
||||
this.debug('DELETE task: %o', name);
|
||||
// stop timer (if no more tasks connected)
|
||||
this.auto();
|
||||
}
|
||||
}
|
||||
|
||||
clear(){
|
||||
this.debug('CLEAR all %o task(s)', this._tasks.size);
|
||||
this._tasks.clear();
|
||||
this.auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} precision
|
||||
* @returns {Task[]}
|
||||
*/
|
||||
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
|
||||
});
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
// Example usage --------------------------------------------------------------------------------------------------
|
||||
// global accessible DataStore instance
|
||||
window.dataStore = new DataStore();
|
||||
|
||||
// extend HTMLElement class with an interface to set/get data to it
|
||||
HTMLElement.prototype.setData = function(key, value){
|
||||
window.dataStore.set(this, key, value);
|
||||
};
|
||||
|
||||
HTMLElement.prototype.getData = function(key){
|
||||
return window.dataStore.get(this, key);
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stores data to an object
|
||||
* -> can be used as a replacement for jQuery $.data()
|
||||
*/
|
||||
return class DataStore {
|
||||
constructor() {
|
||||
this._store = new WeakMap();
|
||||
}
|
||||
|
||||
set(obj, key, value) {
|
||||
if (!this._store.has(obj)) {
|
||||
this._store.set(obj, new Map());
|
||||
}
|
||||
this._store.get(obj).set(key, value);
|
||||
return obj;
|
||||
}
|
||||
|
||||
get(obj, key) {
|
||||
return this._store.has(obj) && (key ? this._store.get(obj).get(key) : this._store.get(obj));
|
||||
}
|
||||
|
||||
has(obj, key) {
|
||||
return this._store.has(obj) && this._store.get(obj).has(key);
|
||||
}
|
||||
|
||||
remove(obj, key) {
|
||||
let ret = false;
|
||||
if (this._store.has(obj)) {
|
||||
ret = this._store.get(obj).delete(key);
|
||||
// remove obj if store is empty
|
||||
// -> 'size' property is does not exist if valueStore is WeakMap
|
||||
if (!this._store.get(obj).size) {
|
||||
this._store.delete(obj);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,402 +0,0 @@
|
||||
define(['app/lib/eventHandler'], (EventHandler) => {
|
||||
'use strict';
|
||||
|
||||
let DragSelect = class DragSelect {
|
||||
|
||||
constructor(config){
|
||||
this._config = Object.assign({}, this.constructor.defaultConfig, config);
|
||||
this._instanceId = ++this.constructor.instanceCount;
|
||||
this._instanceName = [this._config.namespace, this._instanceId].join('-');
|
||||
this._animationFrameId = null;
|
||||
this._mouseIsDown = false;
|
||||
this._cursorPosition = {x: 0, y: 0};
|
||||
this._selectBoxDimHash = undefined;
|
||||
this._deselectedElements = [];
|
||||
|
||||
this._targetDim = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 10,
|
||||
height: 10
|
||||
};
|
||||
|
||||
this._selectBoxOrigin = {
|
||||
left: 0,
|
||||
top: 0
|
||||
};
|
||||
|
||||
this.init();
|
||||
|
||||
this.debug = (msg,...data) => {
|
||||
if(this._config.debug){
|
||||
data = (data || []);
|
||||
console.debug(msg, ...data);
|
||||
}
|
||||
};
|
||||
|
||||
this.debugEvent = e => {
|
||||
if(this._config.debugEvents){
|
||||
let arrow = '?';
|
||||
switch(e.type){
|
||||
case 'mousedown': arrow = '⯆'; break;
|
||||
case 'mousemove': arrow = '⯈'; break;
|
||||
case 'mouseup': arrow = '⯅'; break;
|
||||
}
|
||||
this.debug('ON ' + arrow + ' %s currentTarget: %o event: %o', e.type, e.currentTarget, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* set/update target target element dimension
|
||||
* -> must be updated if target element is scrolled/or pushed by e.g. slide menu
|
||||
*/
|
||||
setTargetDimensions(){
|
||||
let domRect = this._config.target.getBoundingClientRect();
|
||||
Object.assign(this._targetDim, this.filterDomRect(domRect));
|
||||
}
|
||||
|
||||
/**
|
||||
* set/update boundary element dimension [optional]
|
||||
* -> required for 'intersection' check e.g. for scrollable target
|
||||
* @returns {DOMRect}
|
||||
*/
|
||||
setBoundaryDimensions(){
|
||||
if(this._config.boundary){
|
||||
let boundary = this._config.target.closest(this._config.boundary);
|
||||
if(boundary){
|
||||
return (this._boundaryDim = boundary.getBoundingClientRect());
|
||||
}
|
||||
}
|
||||
delete this._boundaryDim;
|
||||
}
|
||||
|
||||
/**
|
||||
* set/update current cursor coordinates
|
||||
* @param e
|
||||
*/
|
||||
setCursorPosition(e){
|
||||
Object.assign(this._cursorPosition, {
|
||||
x: e.pageX,
|
||||
y: e.pageY
|
||||
});
|
||||
}
|
||||
|
||||
init(){
|
||||
this.initSelectBox();
|
||||
this.setTargetDimensions();
|
||||
|
||||
EventHandler.addEventListener(this._config.target, this.getNamespaceEvent('mousedown'), this.onMouseDown.bind(this), {passive: false});
|
||||
EventHandler.addEventListener(this._config.container, this.getNamespaceEvent('mousemove'), this.onMouseMove.bind(this), {passive: false});
|
||||
EventHandler.addEventListener(this._config.container, this.getNamespaceEvent('mouseup'), this.onMouseUp.bind(this), {passive: true});
|
||||
}
|
||||
|
||||
// Mouse events -----------------------------------------------------------------------------------------------
|
||||
onMouseDown(e){
|
||||
if(e.which === 1){
|
||||
this.debugEvent(e);
|
||||
this.setTargetDimensions();
|
||||
this.showSelectBox(e);
|
||||
this._mouseIsDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove(e){
|
||||
if(this._mouseIsDown){
|
||||
e.preventDefault();
|
||||
|
||||
if(this._animationFrameId){
|
||||
cancelAnimationFrame(this._animationFrameId);
|
||||
}
|
||||
|
||||
this._animationFrameId = requestAnimationFrame(() => {
|
||||
this.debugEvent(e);
|
||||
this.setCursorPosition(e);
|
||||
this.update();
|
||||
this._animationFrameId = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUp(e){
|
||||
this.debugEvent(e);
|
||||
this.selectElements();
|
||||
this.hideSelectBox();
|
||||
this._mouseIsDown = false;
|
||||
this._deselectedElements = [];
|
||||
}
|
||||
|
||||
// SelectBox handler ------------------------------------------------------------------------------------------
|
||||
/**
|
||||
* create new selectBox and append to DOM
|
||||
* -> hidden by CSS until selectBox gets updated
|
||||
*/
|
||||
initSelectBox(){
|
||||
this._selectBox = document.createElement('div');
|
||||
this._selectBox.id = this._instanceName;
|
||||
this._selectBox.classList.add(this._config.selectBoxClass);
|
||||
this._config.target.after(this._selectBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* show selectBox -> apply CSS for positioning and dimension
|
||||
* @param e
|
||||
*/
|
||||
showSelectBox(e){
|
||||
Object.assign(this._selectBoxOrigin, {
|
||||
left: e.pageX - this._targetDim.left,
|
||||
top: e.pageY - this._targetDim.top
|
||||
});
|
||||
|
||||
// limit render "reflow" bny adding all properties at once
|
||||
this._selectBox.style.cssText = `--selectBox-left: ${this._selectBoxOrigin.left}px; ` +
|
||||
`--selectBox-top: ${this._selectBoxOrigin.top}px; ` +
|
||||
`--selectBox-width: 1px; ` +
|
||||
`--selectBox-height: 1px;`;
|
||||
|
||||
this._selectBox.classList.add(this._config.activeClass);
|
||||
|
||||
this.callback('onShow');
|
||||
}
|
||||
|
||||
/**
|
||||
* update selectBox position and dimension based on cursorPosition
|
||||
* @returns {boolean}
|
||||
*/
|
||||
updateSelectBox(){
|
||||
let updated = false;
|
||||
if(!this.isActiveSelectBox()){
|
||||
return updated;
|
||||
}
|
||||
|
||||
let left = this._cursorPosition.x - this._targetDim.left;
|
||||
let top = this._cursorPosition.y - this._targetDim.top;
|
||||
let tempWidth = this._selectBoxOrigin.left - left;
|
||||
let tempHeight = this._selectBoxOrigin.top - top;
|
||||
|
||||
let newLeft = this._selectBoxOrigin.left;
|
||||
let newTop = this._selectBoxOrigin.top;
|
||||
let newWidth = left - this._selectBoxOrigin.left;
|
||||
let newHeight = top - this._selectBoxOrigin.top;
|
||||
|
||||
if(newWidth < 0){
|
||||
newLeft = newLeft - tempWidth;
|
||||
newWidth = newWidth * -1;
|
||||
}
|
||||
|
||||
if(newHeight < 0){
|
||||
newTop = newTop - tempHeight;
|
||||
newHeight = newHeight * -1;
|
||||
}
|
||||
|
||||
// check if dimension has changed -> improve performance
|
||||
let dimensionHash = [newWidth, newHeight].join('_');
|
||||
if(this._selectBoxDimHash !== dimensionHash){
|
||||
this._selectBoxDimHash = dimensionHash;
|
||||
this._selectBox.style.cssText = `--selectBox-left: ${newLeft}px; ` +
|
||||
`--selectBox-top: ${newTop}px; ` +
|
||||
`--selectBox-width: ${newWidth}px; ` +
|
||||
`--selectBox-height: ${newHeight}px;`;
|
||||
|
||||
// set drag position data (which corner)
|
||||
this._selectBox.dataset.origin = this.getSelectBoxDragOrigin(left, top, newLeft, newTop).join('|');
|
||||
|
||||
updated = true;
|
||||
this.callback('onUpdate');
|
||||
this.dispatch(this._selectBox, 'update', this);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* hide selectBox
|
||||
*/
|
||||
hideSelectBox(){
|
||||
if(!this.isActiveSelectBox()){
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.callback('onHide', this._deselectedElements) !== false){
|
||||
this._selectBox.classList.remove(this._config.activeClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cursor position corner for selectBox
|
||||
* @param left
|
||||
* @param top
|
||||
* @param newLeft
|
||||
* @param newTop
|
||||
* @returns {[string, string]}
|
||||
*/
|
||||
getSelectBoxDragOrigin(left, top, newLeft, newTop){
|
||||
let position = [];
|
||||
if(
|
||||
left === newLeft &&
|
||||
top === newTop
|
||||
){
|
||||
position = ['top', 'left'];
|
||||
}else if(top === newTop){
|
||||
position = ['top', 'right'];
|
||||
}else if(left === newLeft){
|
||||
position = ['bottom', 'left'];
|
||||
}else{
|
||||
position = ['bottom', 'right'];
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if there is currently an active selectBox visible
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isActiveSelectBox(){
|
||||
return this._selectBox.classList.contains(this._config.activeClass) &&
|
||||
!this._config.target.classList.contains(this._config.disabledClass);
|
||||
}
|
||||
|
||||
// Element select methods -------------------------------------------------------------------------------------
|
||||
selectableElements(){
|
||||
return this._config.target.querySelectorAll(this._config.selectables);
|
||||
}
|
||||
|
||||
selectElements(){
|
||||
if(!this.isActiveSelectBox()){
|
||||
return;
|
||||
}
|
||||
|
||||
let selectables = this.selectableElements();
|
||||
let selectBoxDim = this.filterDomRect(this._selectBox.getBoundingClientRect());
|
||||
|
||||
selectables.forEach(el => {
|
||||
let elDim = this.filterDomRect(el.getBoundingClientRect());
|
||||
|
||||
if(this.percentCovered(selectBoxDim, elDim) > this._config.percentCovered){
|
||||
el.classList.add(this._config.selectedClass);
|
||||
// remove element from "deselected" elements (e.g on add -> remove -> add scenario)
|
||||
this._deselectedElements = this._deselectedElements.filter(tempEl => tempEl !== el);
|
||||
}else{
|
||||
if(el.classList.contains(this._config.selectedClass)){
|
||||
el.classList.remove(this._config.selectedClass);
|
||||
// add to "deselected" elements, if not already in array
|
||||
if(this._deselectedElements.findIndex(tempEl => tempEl === el) === -1){
|
||||
this._deselectedElements.push(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
percentCovered(dim1, dim2){
|
||||
if(
|
||||
(dim1.left <= dim2.left) &&
|
||||
(dim1.top <= dim2.top) &&
|
||||
((dim1.left + dim1.width) >= (dim2.left + dim2.width)) &&
|
||||
((dim1.top + dim1.height) > (dim2.top + dim2.height))
|
||||
){
|
||||
// The whole thing is covering the whole other thing
|
||||
return 100;
|
||||
}else{
|
||||
// Only parts may be covered, calculate percentage
|
||||
dim1.right = dim1.left + dim1.width;
|
||||
dim1.bottom = dim1.top + dim1.height;
|
||||
dim2.right = dim2.left + dim2.width;
|
||||
dim2.bottom = dim2.top + dim2.height;
|
||||
|
||||
let l = Math.max(dim1.left, dim2.left);
|
||||
let r = Math.min(dim1.right, dim2.right);
|
||||
let t = Math.max(dim1.top, dim2.top);
|
||||
let b = Math.min(dim1.bottom, dim2.bottom);
|
||||
|
||||
if(b >= t && r >= l){
|
||||
return (((r - l) * (b - t)) / (dim2.width * dim2.height)) * 100;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Boundary intersection methods ------------------------------------------------------------------------------
|
||||
getIntersection(){
|
||||
let intersection = [];
|
||||
if(this.isActiveSelectBox && this._boundaryDim){
|
||||
let selectBoxDim = this._selectBox.getBoundingClientRect();
|
||||
if(this._boundaryDim.top > selectBoxDim.top){
|
||||
intersection.push('top');
|
||||
}
|
||||
if(this._boundaryDim.bottom < selectBoxDim.bottom){
|
||||
intersection.push('bottom');
|
||||
}
|
||||
if(this._boundaryDim.left > selectBoxDim.left){
|
||||
intersection.push('left');
|
||||
}
|
||||
if(this._boundaryDim.right < selectBoxDim.right){
|
||||
intersection.push('right');
|
||||
}
|
||||
}
|
||||
return intersection;
|
||||
}
|
||||
|
||||
// Util methods -----------------------------------------------------------------------------------------------
|
||||
update(){
|
||||
this.setTargetDimensions();
|
||||
this.setBoundaryDimensions();
|
||||
|
||||
if(this.updateSelectBox() && this._config.selectOnDrag){
|
||||
this.selectElements();
|
||||
}
|
||||
}
|
||||
|
||||
getNamespaceEvent(type){
|
||||
return [type, this._instanceName].join('.');
|
||||
}
|
||||
|
||||
filterDomRect(domRect, filteredKeys = ['left', 'top', 'width', 'height']){
|
||||
let obj = {};
|
||||
filteredKeys.forEach(key => {
|
||||
if(domRect[key] !== undefined){
|
||||
obj[key] = domRect[key];
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
//return filteredKeys.reduce((obj, key) => ({ ...obj, [key]: domRect[key] }), {}); // same result but uses "object destruction" ES5
|
||||
}
|
||||
|
||||
callback(callback, ...args){
|
||||
if(this._config[callback] instanceof Function){
|
||||
return this._config[callback](...args);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(target, type, data = null){
|
||||
let event = new CustomEvent([type, this._config.namespace].join(':'), {
|
||||
bubbles: true,
|
||||
detail: data
|
||||
});
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
DragSelect.defaultConfig = {
|
||||
container: document,
|
||||
target: document.body,
|
||||
namespace: 'dragSelect',
|
||||
activeClass: 'active',
|
||||
disabledClass: 'disabled',
|
||||
selectables: 'div',
|
||||
selectedClass: 'dragSelect-selected',
|
||||
selectBoxClass: 'dragSelect-selectBox',
|
||||
boundary: undefined, // optional selector for boundary parent box (e.g. scrollable viewport)
|
||||
selectOnDrag: true,
|
||||
percentCovered: 25,
|
||||
onShow: undefined,
|
||||
onHide: undefined,
|
||||
onUpdate: undefined,
|
||||
debug: false,
|
||||
debugEvents: false
|
||||
};
|
||||
|
||||
DragSelect.instanceCount = 0;
|
||||
|
||||
return DragSelect;
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
let EventHandler = class EventHandler {
|
||||
|
||||
constructor(){
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
addEventListener(target, type, listener, options){
|
||||
this._listeners.set(type, listener);
|
||||
target.addEventListener(this.constructor.eventParts(type).event, listener, options);
|
||||
}
|
||||
|
||||
removeEventListener(target, type){
|
||||
target.removeEventListener(this.constructor.eventParts(type).event, this._listeners.get(type));
|
||||
this._listeners.delete(type);
|
||||
}
|
||||
|
||||
static eventParts(type){
|
||||
return type.split('.').reduce((acc, val, i) => {
|
||||
acc[i ? 'namespace' : 'event'] = val;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
};
|
||||
|
||||
return new EventHandler();
|
||||
});
|
||||
@@ -1,394 +0,0 @@
|
||||
define([
|
||||
'localForage',
|
||||
'app/promises/promise.queue',
|
||||
'app/promises/promise.deferred',
|
||||
], (LocalForage, PromiseQueue, DeferredPromise) => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Instances of LocalStore handle its own LocalForage instance
|
||||
*/
|
||||
class LocalStore {
|
||||
constructor(config, LocalForageConfig){
|
||||
this._config = Object.assign({}, this.constructor.defaultConfig, config);
|
||||
|
||||
let initPromise = new DeferredPromise();
|
||||
this._processQueue = new PromiseQueue();
|
||||
this._processQueue.enqueue(() => initPromise);
|
||||
|
||||
this._localforage = LocalForage.createInstance(Object.assign({}, LocalStore.LocalForageConfig, LocalForageConfig));
|
||||
this._localforage.ready().then(() => initPromise.resolve());
|
||||
|
||||
this._manager = null; // reference to LocalStoreManager() that manages this LocalStore instance
|
||||
|
||||
this.debug = (msg,...data) => {
|
||||
if(this._config.debug){
|
||||
data = (data || []);
|
||||
data.unshift(this.constructor.name, this._config.name);
|
||||
console.debug('debug: %s %o | ' + msg, ...data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* set scope for this instance
|
||||
* -> all read/write actions are scoped
|
||||
* this is a prefix for all keys!
|
||||
* @param scope
|
||||
*/
|
||||
set scope(scope){
|
||||
if(LocalStore.isString(scope)){
|
||||
this._config.scope = scope;
|
||||
}else{
|
||||
throw new TypeError('Scope must be instance of "String", Type of "' + typeof scope + '" given');
|
||||
}
|
||||
}
|
||||
|
||||
get scope(){
|
||||
return this._config.scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* get item
|
||||
* @param key
|
||||
* @param successCallback
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getItem(key, successCallback = undefined){
|
||||
key = this.fixKey(key);
|
||||
let propArray = LocalStore.keyToArray(key);
|
||||
let rootKey = propArray.shift();
|
||||
|
||||
let getItem = () => this._localforage.getItem(key, successCallback);
|
||||
if(propArray.length){
|
||||
getItem = () => {
|
||||
return this._localforage.getItem(rootKey)
|
||||
.then(data => {
|
||||
if(LocalStore.isObject(data)){
|
||||
// find nested property
|
||||
return LocalStore.findObjProp(data, propArray);
|
||||
}else{
|
||||
// rootKey not found -> propArray path not exists
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return this._processQueue.enqueue(() => getItem());
|
||||
}
|
||||
|
||||
/**
|
||||
* set/update existing value
|
||||
* @param key e.g. nested object key' first.a.b.test'
|
||||
* @param value
|
||||
* @param successCallback
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setItem(key, value, successCallback = undefined){
|
||||
key = this.fixKey(key);
|
||||
let propArray = LocalStore.keyToArray(key);
|
||||
let rootKey = propArray.shift();
|
||||
|
||||
let getItem = () => Promise.resolve(value);
|
||||
if(propArray.length){
|
||||
getItem = () => {
|
||||
return this._localforage.getItem(rootKey)
|
||||
.then(rootVal => {
|
||||
rootVal = (rootVal === null) ? {} : rootVal;
|
||||
// update data with new value (merge obj)
|
||||
LocalStore.updateObjProp(rootVal, value, propArray);
|
||||
return rootVal;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return this._processQueue.enqueue(() =>
|
||||
getItem()
|
||||
.then(rootVal => this._localforage.setItem(rootKey, rootVal, successCallback))
|
||||
.then(() => Promise.resolve(value))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove item by key
|
||||
* -> allows deep obj delete if key points to a nested obj prop
|
||||
* @param key
|
||||
* @param successCallback
|
||||
* @returns {Promise}
|
||||
*/
|
||||
removeItem(key, successCallback = undefined){
|
||||
key = this.fixKey(key);
|
||||
let propArray = LocalStore.keyToArray(key);
|
||||
let rootKey = propArray.shift();
|
||||
|
||||
let removeItem = () => this._localforage.removeItem(rootKey, successCallback);
|
||||
if(propArray.length){
|
||||
removeItem = () => {
|
||||
return this._localforage.getItem(rootKey)
|
||||
.then(data => {
|
||||
if(LocalStore.isObject(data)){
|
||||
// update data -> delete nested prop
|
||||
LocalStore.deleteObjProp(data, propArray);
|
||||
return data;
|
||||
}else{
|
||||
// rootKey not found -> nothing to delete
|
||||
return Promise.reject(new RangeError('No data found for key: ' + rootKey));
|
||||
}
|
||||
})
|
||||
.then(value => this._localforage.setItem(rootKey, value, successCallback))
|
||||
.catch(e => this.debug('removeItem() error',e));
|
||||
};
|
||||
}
|
||||
|
||||
return this._processQueue.enqueue(() => removeItem());
|
||||
}
|
||||
|
||||
/**
|
||||
* clear all items in store
|
||||
* @param successCallback
|
||||
* @returns {Promise}
|
||||
*/
|
||||
clear(successCallback = undefined){
|
||||
return this._processQueue.enqueue(() => this._localforage.clear(successCallback));
|
||||
}
|
||||
|
||||
/**
|
||||
* get number of keys in store
|
||||
* @param successCallback
|
||||
* @returns {Promise}
|
||||
*/
|
||||
length(successCallback = undefined){
|
||||
return this._processQueue.enqueue(() => this._localforage.length(successCallback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of a key based on its index
|
||||
* @param keyIndex
|
||||
* @param successCallback
|
||||
* @returns {Promise|void}
|
||||
*/
|
||||
key(keyIndex, successCallback = undefined){
|
||||
return this._processQueue.enqueue(() => this._localforage.key(keyIndex, successCallback));
|
||||
}
|
||||
|
||||
/**
|
||||
* get list of all keys in store
|
||||
* @param successCallback
|
||||
* @returns {Promise|void}
|
||||
*/
|
||||
keys(successCallback = undefined){
|
||||
return this._processQueue.enqueue(() => this._localforage.keys(successCallback));
|
||||
}
|
||||
|
||||
/**
|
||||
* drop current LocalForage instance
|
||||
* -> removes this from LocalStoreManager
|
||||
* @returns {Promise|void}
|
||||
*/
|
||||
dropInstance(){
|
||||
return this._processQueue.enqueue(() =>
|
||||
this._localforage.dropInstance().then(() => this._manager.deleteStore(this._config.name))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* connect LocalStoreManager with instance
|
||||
* @param {LocalStoreManager} manager
|
||||
*/
|
||||
connect(manager){
|
||||
if(manager instanceof LocalStoreManager){
|
||||
this._manager = manager;
|
||||
}else{
|
||||
throw new TypeError('Parameter must be instance of LocalStoreManager. Type of "' + typeof manager + '" given');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if key is Int or String with Int at pos 0
|
||||
* -> prefix key
|
||||
* @param key
|
||||
* @returns {string}
|
||||
*/
|
||||
fixKey(key){
|
||||
if(LocalStore.isString(this.scope) && this.scope.length){
|
||||
key = [this.scope, key].join('.');
|
||||
}
|
||||
|
||||
if(
|
||||
Number.isInteger(key) ||
|
||||
(LocalStore.isString(key) && parseInt(key.charAt(0), 10))
|
||||
){
|
||||
key = [this._config.name, key].join('_');
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* find data from obj prop
|
||||
* -> deep object search
|
||||
* @param obj
|
||||
* @param propArray
|
||||
* @returns {null|*}
|
||||
*/
|
||||
static findObjProp(obj, propArray){
|
||||
let [head, ...rest] = propArray;
|
||||
if(!rest.length){
|
||||
return obj[head];
|
||||
}else{
|
||||
if(LocalStore.isObject(obj[head])){
|
||||
return LocalStore.findObjProp(obj[head], rest);
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update/extend obj with new value
|
||||
* -> deep object manipulation
|
||||
* @param obj
|
||||
* @param value
|
||||
* @param propArray
|
||||
*/
|
||||
static updateObjProp(obj, value, propArray){
|
||||
let [head, ...rest] = propArray;
|
||||
if(!rest.length){
|
||||
obj[head] = value;
|
||||
}else{
|
||||
if(!LocalStore.isObject(obj[head])) obj[head] = {};
|
||||
LocalStore.updateObjProp(obj[head], value, rest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete object prop by propArray path
|
||||
* -> deep object delete
|
||||
* @param obj
|
||||
* @param propArray
|
||||
*/
|
||||
static deleteObjProp(obj, propArray){
|
||||
let [head, ...rest] = propArray;
|
||||
if(!rest.length){
|
||||
delete obj[head];
|
||||
}else{
|
||||
if(LocalStore.isObject(obj[head])){
|
||||
LocalStore.deleteObjProp(obj[head], rest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* converts string key to array
|
||||
* @param propPath
|
||||
* @returns {*|string[]}
|
||||
*/
|
||||
static keyToArray(propPath){
|
||||
return propPath.split('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* build DB name
|
||||
* @param name
|
||||
* @returns {string}
|
||||
*/
|
||||
static buildDbName(name){
|
||||
return [LocalStore.dbNamePrefix, name].join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* check var for Object
|
||||
* @param obj
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isObject(obj){
|
||||
return (!!obj) && (obj.constructor === Object);
|
||||
}
|
||||
|
||||
/**
|
||||
* check var for Array
|
||||
* @param arr
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isArray(arr){
|
||||
return (!!arr) && (arr.constructor === Array);
|
||||
}
|
||||
|
||||
/**
|
||||
* check var for String
|
||||
* @param str
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isString(str){
|
||||
return typeof str === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
LocalStore.defaultConfig = {
|
||||
name: 'default', // custom unique name for identification
|
||||
debug: false
|
||||
};
|
||||
|
||||
LocalStore.dbNamePrefix = 'PathfinderDB';
|
||||
LocalStore.LocalForageConfig = {
|
||||
driver: [LocalForage.INDEXEDDB, LocalForage.WEBSQL, LocalForage.LOCALSTORAGE],
|
||||
name: LocalStore.dbNamePrefix
|
||||
};
|
||||
|
||||
/**
|
||||
* An instance of LocalStoreManager() handles multiple LocalStore()´s
|
||||
* -> LocalStore()´s can be set()/delete() from LocalStore() instance
|
||||
*/
|
||||
class LocalStoreManager {
|
||||
|
||||
constructor(){
|
||||
if(!this.constructor.instance){
|
||||
this._store = new Map();
|
||||
this.constructor.instance = this;
|
||||
}
|
||||
|
||||
return this.constructor.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* get LocalStore instance by name
|
||||
* @param name
|
||||
* @returns {LocalStore}
|
||||
*/
|
||||
getStore(name){
|
||||
return this.newStore(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* get either existing LocalStore instance
|
||||
* -> or create new instance
|
||||
* @param name
|
||||
* @returns {LocalStore|undefined}
|
||||
*/
|
||||
newStore(name){
|
||||
if(!this._store.has(name)){
|
||||
let store = new LocalStore({
|
||||
name: name
|
||||
}, {
|
||||
name: LocalStore.buildDbName(name)
|
||||
});
|
||||
store.connect(this);
|
||||
this._store.set(name, store);
|
||||
}
|
||||
return this._store.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes LocalStore instance from Manager
|
||||
* -> this will not drop LocalForage instance!
|
||||
* check LocalStore.dropInstance() for graceful delete
|
||||
* @param name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
deleteStore(name){
|
||||
return this._store.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
return new LocalStoreManager();
|
||||
});
|
||||
139
public/js/v2.0.0/app/lib/prototypes.js
vendored
139
public/js/v2.0.0/app/lib/prototypes.js
vendored
@@ -1,139 +0,0 @@
|
||||
define([
|
||||
'app/lib/dataStore'
|
||||
], (DataStore) => {
|
||||
'use strict';
|
||||
|
||||
// DOM node data store ============================================================================================
|
||||
window.dataStore = new DataStore();
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param value
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
HTMLElement.prototype.setData = function(key, value){
|
||||
return window.dataStore.set(this, key, value);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @returns {*}
|
||||
*/
|
||||
HTMLElement.prototype.getData = function(key){
|
||||
return window.dataStore.get(this, key);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @returns {*}
|
||||
*/
|
||||
HTMLElement.prototype.hasData = function(key){
|
||||
return window.dataStore.has(this, key);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @returns {*}
|
||||
*/
|
||||
HTMLElement.prototype.removeData = function(key){
|
||||
return window.dataStore.remove(this, key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Array diff
|
||||
* [1,2,3,4,5].diff([4,5,6]) => [1,2,3]
|
||||
* @param a
|
||||
* @returns {*[]}
|
||||
*/
|
||||
Array.prototype.diff = function(a){
|
||||
return this.filter(i => !a.includes(i));
|
||||
};
|
||||
|
||||
/**
|
||||
* Array intersect
|
||||
* [1,2,3,4,5].intersect([4,5,6]) => [4,5]
|
||||
* @param a
|
||||
* @returns {*[]}
|
||||
*/
|
||||
Array.prototype.intersect = function(a){
|
||||
return this.filter(i => a.includes(i));
|
||||
};
|
||||
|
||||
/**
|
||||
* inverse of Array.filter(),
|
||||
* [1,2,3,4,5].not(val => val === 3) => [1, 2, 4, 5]
|
||||
* [1,2,3,4,5].filter(val => val === 3) => [3]
|
||||
* @param callback
|
||||
* @returns {*[]}
|
||||
*/
|
||||
Array.prototype.not = function(callback) {
|
||||
return this.filter((...args) => !callback(...args));
|
||||
};
|
||||
|
||||
/**
|
||||
* compares two arrays if all elements in a are also in b
|
||||
* element order is ignored
|
||||
* @param a
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Array.prototype.equalValues = function(a){
|
||||
return this.diff(a).concat(a.diff(this)).length === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* like Array.concat() + remove duplicate values
|
||||
* @see https://stackoverflow.com/a/38940354/4329969
|
||||
* @param a
|
||||
* @returns {*[]}
|
||||
*/
|
||||
Array.prototype.concatFilter = function(a){
|
||||
return [...new Set([...this,...a])];
|
||||
};
|
||||
|
||||
/**
|
||||
* sort array of objects by property name
|
||||
* @param p
|
||||
* @returns {Array.<T>}
|
||||
*/
|
||||
Array.prototype.sortBy = function(p){
|
||||
return this.slice(0).sort((a,b) => {
|
||||
return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* capitalize first letter
|
||||
* @returns {string}
|
||||
*/
|
||||
String.prototype.capitalize = function(){
|
||||
return this.charAt(0).toUpperCase() + this.slice(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* get hash from string
|
||||
* @returns {number}
|
||||
*/
|
||||
String.prototype.hashCode = function(){
|
||||
let hash = this.split('').reduce((a,b) => (((a << 5) - a) + b.charCodeAt(0))|0, 0);
|
||||
// make positive
|
||||
return (hash + 2147483647) + 1;
|
||||
};
|
||||
|
||||
String.prototype.trimLeftChars = function(charList){
|
||||
if(charList === undefined)
|
||||
charList = '\\s';
|
||||
return this.replace(new RegExp('^[' + charList + ']+'), '');
|
||||
};
|
||||
|
||||
String.prototype.trimRightChars = function(charList){
|
||||
if(charList === undefined)
|
||||
charList = '\\s';
|
||||
return this.replace(new RegExp('[' + charList + ']+$'), '');
|
||||
};
|
||||
|
||||
String.prototype.trimChars = function(charList){
|
||||
return this.trimLeftChars(charList).trimRightChars(charList);
|
||||
};
|
||||
|
||||
return {};
|
||||
});
|
||||
@@ -1,92 +0,0 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
class ResizeManager {
|
||||
constructor(config = {}) {
|
||||
this._config = Object.assign({}, ResizeManager.defaultConfig, config);
|
||||
this._observables = new WeakMap();
|
||||
this._observer = new ResizeObserver((entries, observer) => { // jshint ignore:line
|
||||
for (let entry of entries) {
|
||||
if (this._observables.has(entry.target)) {
|
||||
this._observables
|
||||
.get(entry.target)
|
||||
.callback(entry.target, entry.contentRect);
|
||||
} else {
|
||||
this._observer.unobserve(entry.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
debounce(callback, ms = this._config.msDebounce, immediate = false) {
|
||||
let timer;
|
||||
return (...args) => {
|
||||
let later = () => {
|
||||
timer = null;
|
||||
if (!immediate) callback(...args);
|
||||
};
|
||||
let callNow = immediate && !timer;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(later, ms);
|
||||
if (callNow) callback(...args);
|
||||
};
|
||||
}
|
||||
|
||||
throttle(callback, ms = this._config.msThrottle) {
|
||||
let lastFunc;
|
||||
let lastRan;
|
||||
return function (...args) {
|
||||
if (!lastRan) {
|
||||
callback(...args);
|
||||
lastRan = Date.now();
|
||||
} else {
|
||||
clearTimeout(lastFunc);
|
||||
lastFunc = setTimeout(() => {
|
||||
if (Date.now() - lastRan >= ms) {
|
||||
callback(...args);
|
||||
lastRan = Date.now();
|
||||
}
|
||||
}, ms - (Date.now() - lastRan));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
observe(target, callback, config = {}, options = ResizeManager.observeOptions) {
|
||||
if (!this._observables.has(target)) {
|
||||
if (config.hasOwnProperty('debounce')) {
|
||||
let {ms, immediate} = config;
|
||||
callback = this.debounce(callback, ms, immediate);
|
||||
}
|
||||
|
||||
if (config.hasOwnProperty('throttle')) {
|
||||
let {ms} = config;
|
||||
callback = this.throttle(callback, ms);
|
||||
}
|
||||
|
||||
this._observables.set(target, {callback});
|
||||
this._observer.observe(target, options);
|
||||
}
|
||||
}
|
||||
|
||||
unobserve(target) {
|
||||
this._observer.unobserve(target);
|
||||
this._observables.delete(target);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this._observer.disconnect();
|
||||
this._observables = new WeakMap();
|
||||
}
|
||||
}
|
||||
|
||||
ResizeManager.observeOptions = {
|
||||
box: 'border-box' // sets which box model the observer will observe changes to
|
||||
};
|
||||
|
||||
ResizeManager.defaultConfig = {
|
||||
msDebounce: 250, // setTimeout for debounced calls
|
||||
msThrottle: 100 // setTimeout for throttled calls
|
||||
};
|
||||
|
||||
return new ResizeManager();
|
||||
});
|
||||
@@ -1,555 +0,0 @@
|
||||
/**
|
||||
* logging
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/counter',
|
||||
'bootbox',
|
||||
'app/lib/resize'
|
||||
], ($, Init, Util, Counter, bootbox) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
let logData = []; // cache object for all log entries
|
||||
let logDataTable = null; // "Datatables" Object
|
||||
|
||||
// Morris charts data
|
||||
let maxGraphDataCount = 30; // max date entries for a graph
|
||||
let chartData = {}; // chart Data object for all Morris Log graphs
|
||||
|
||||
let config = {
|
||||
taskDialogId: 'pf-task-dialog', // id for map "task manager" dialog
|
||||
timestampCounterClass: 'pf-timestamp-counter', // class for "timestamp" counter
|
||||
taskDialogStatusAreaClass: 'pf-task-dialog-status', // class for "status" dynamic area
|
||||
taskDialogLogTableAreaClass: 'pf-task-dialog-table', // class for "log table" dynamic area
|
||||
logGraphClass: 'pf-log-graph', // class for all log Morris graphs
|
||||
moduleHeadlineIconClass: 'pf-module-icon-button' // class for toolbar icons in the head
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
statusArea.destroyTooltips(true);
|
||||
requirejs(['text!templates/modules/sync_status.html', 'mustache'], (templateSyncStatus, Mustache) => {
|
||||
let data = {
|
||||
timestampCounterClass: config.timestampCounterClass,
|
||||
syncStatus: Init.syncStatus,
|
||||
isWebSocket: () => {
|
||||
return (Util.getSyncType() === 'webSocket');
|
||||
},
|
||||
isAjax: () => {
|
||||
return (Util.getSyncType() === 'ajax');
|
||||
}
|
||||
};
|
||||
|
||||
let syncStatusElement = $(Mustache.render(templateSyncStatus, data ));
|
||||
Counter.destroyTimestampCounter(statusArea, true);
|
||||
|
||||
statusArea.html(syncStatusElement);
|
||||
|
||||
let counterElements = syncStatusElement.find('.' + config.timestampCounterClass);
|
||||
Counter.initTimestampCounter(counterElements);
|
||||
|
||||
statusArea.initTooltips({
|
||||
placement: 'right'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* shows the logging dialog
|
||||
*/
|
||||
let showDialog = () => {
|
||||
requirejs(['text!templates/dialog/task_manager.html', 'mustache'], function(templateTaskManagerDialog, Mustache){
|
||||
let data = {
|
||||
id: config.taskDialogId,
|
||||
dialogDynamicAreaClass: Util.config.dynamicAreaClass,
|
||||
taskDialogStatusAreaClass: config.taskDialogStatusAreaClass,
|
||||
taskDialogLogTableAreaClass: config.taskDialogLogTableAreaClass
|
||||
};
|
||||
|
||||
let contentTaskManager = $( Mustache.render(templateTaskManagerDialog, data) );
|
||||
|
||||
let rowElementGraphs = contentTaskManager.find('.row');
|
||||
let taskDialogLogTableAreaElement = contentTaskManager.find('.' + config.taskDialogLogTableAreaClass);
|
||||
|
||||
let logTable = $('<table>', {
|
||||
class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ')
|
||||
});
|
||||
|
||||
taskDialogLogTableAreaElement.append(logTable);
|
||||
|
||||
// init log table
|
||||
logDataTable = logTable.DataTable({
|
||||
dom: '<"flex-row flex-between"<"flex-col"l><"flex-col"B><"flex-col"fS>>' +
|
||||
'<"flex-row"<"flex-col flex-grow"tr>>' +
|
||||
'<"flex-row flex-between"<"flex-col"i><"flex-col"p>>',
|
||||
buttons: {
|
||||
name: 'tableTools',
|
||||
buttons: [
|
||||
{
|
||||
extend: 'copy',
|
||||
tag: 'a',
|
||||
className: config.moduleHeadlineIconClass,
|
||||
text: '<i class="fas fa-fw fa-copy"></i> copy',
|
||||
exportOptions: {
|
||||
orthogonal: 'export'
|
||||
}
|
||||
},
|
||||
{
|
||||
extend: 'csv',
|
||||
tag: 'a',
|
||||
className: config.moduleHeadlineIconClass,
|
||||
text: '<i class="fas fa-fw fa-download"></i> csv',
|
||||
exportOptions: {
|
||||
orthogonal: 'export'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
paging: true,
|
||||
ordering: true,
|
||||
order: [1, 'desc'],
|
||||
hover: false,
|
||||
pageLength: 10,
|
||||
lengthMenu: [[5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, 'All']],
|
||||
data: logData, // load cached logs (if available)
|
||||
language: {
|
||||
emptyTable: 'No entries',
|
||||
zeroRecords: 'No entries found',
|
||||
lengthMenu: 'Show _MENU_',
|
||||
info: 'Showing _START_ to _END_ of _TOTAL_ entries'
|
||||
},
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 0,
|
||||
name: 'status',
|
||||
title: '<i class="fas fa-tag"></i>',
|
||||
width: 18,
|
||||
searchable: false,
|
||||
class: ['text-center'].join(' '),
|
||||
data: 'status',
|
||||
render: {
|
||||
display: (cellData, type, rowData, meta) => {
|
||||
let statusClass = Util.getLogInfo(cellData, 'class');
|
||||
return '<i class="fas fa-fw fa-circle txt-color ' + statusClass + '"></i>';
|
||||
}
|
||||
}
|
||||
},{
|
||||
targets: 1,
|
||||
name: 'time',
|
||||
title: '<i class="far fa-fw fa-clock"></i>',
|
||||
width: 50,
|
||||
searchable: true,
|
||||
class: 'text-right',
|
||||
data: 'timestamp',
|
||||
render: {
|
||||
display: (cellData, type, rowData, meta) => rowData.timestampFormatted
|
||||
}
|
||||
},{
|
||||
targets: 2,
|
||||
name: 'duration',
|
||||
title: '<i class="fas fa-fw fa-history"></i>',
|
||||
width: 35,
|
||||
searchable: false,
|
||||
class: 'text-right',
|
||||
data: 'duration',
|
||||
render: {
|
||||
display: (cellData, type, rowData, meta) => {
|
||||
let logStatus = getLogStatusByDuration(rowData.key, cellData);
|
||||
let statusClass = Util.getLogInfo(logStatus, 'class');
|
||||
return '<span class="txt-color ' + statusClass + '">' + cellData + '<small>ms</small></span>';
|
||||
}
|
||||
}
|
||||
},{
|
||||
targets: 3,
|
||||
name: 'description',
|
||||
title: 'description',
|
||||
searchable: true,
|
||||
data: 'description'
|
||||
},{
|
||||
targets: 4,
|
||||
name: 'logType',
|
||||
title: 'type',
|
||||
width: 40,
|
||||
searchable: true,
|
||||
class: ['text-center'].join(' '),
|
||||
data: 'logType',
|
||||
render: {
|
||||
display: (cellData, type, rowData, meta) => {
|
||||
let typeIconClass = getLogTypeIconClass(cellData);
|
||||
return '<i class="fas ' + typeIconClass + '"></i>';
|
||||
}
|
||||
}
|
||||
},{
|
||||
targets: 5,
|
||||
name: 'process',
|
||||
title: 'Prozess-ID',
|
||||
width: 80,
|
||||
searchable: false,
|
||||
class: 'text-right',
|
||||
data: 'key'
|
||||
}
|
||||
]
|
||||
|
||||
});
|
||||
|
||||
// open dialog
|
||||
let logDialog = bootbox.dialog({
|
||||
title: 'Task-Manager',
|
||||
message: contentTaskManager,
|
||||
size: 'large',
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'close',
|
||||
className: 'btn-default'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// modal dialog is shown
|
||||
logDialog.on('shown.bs.modal', function(e){
|
||||
updateSyncStatus();
|
||||
|
||||
// show Morris graphs ----------------------------------------------------------
|
||||
|
||||
let labelYFormat = val => Math.round(val) + 'ms';
|
||||
|
||||
for(let key in chartData){
|
||||
if(chartData.hasOwnProperty(key)){
|
||||
// create a chart for each key
|
||||
|
||||
let colElementGraph = $('<div>', {
|
||||
class: ['col-md-6'].join(' ')
|
||||
});
|
||||
|
||||
|
||||
// graph element
|
||||
let graphElement = $('<div>', {
|
||||
class: config.logGraphClass
|
||||
});
|
||||
|
||||
let graphArea = $('<div>', {
|
||||
class: Util.config.dynamicAreaClass
|
||||
}).append( graphElement );
|
||||
|
||||
let headline = $('<h4>', {
|
||||
text: key
|
||||
}).prepend(
|
||||
$('<span>', {
|
||||
class: ['txt-color', 'txt-color-grayLight'].join(' '),
|
||||
text: 'Prozess-ID: '
|
||||
})
|
||||
);
|
||||
|
||||
// show update ping between function calls
|
||||
let updateElement = $('<small>', {
|
||||
class: ['txt-color', 'txt-color-blue', 'pull-right'].join(' ')
|
||||
});
|
||||
headline.append(updateElement).append('<br>');
|
||||
|
||||
// show average execution time
|
||||
let averageElement = $('<small>', {
|
||||
class: 'pull-right'
|
||||
});
|
||||
headline.append(averageElement);
|
||||
|
||||
colElementGraph.append(headline);
|
||||
colElementGraph.append(graphArea);
|
||||
|
||||
graphArea.showLoadingAnimation();
|
||||
|
||||
rowElementGraphs.append( colElementGraph );
|
||||
|
||||
// cache DOM Elements that will be updated frequently
|
||||
chartData[key].averageElement = averageElement;
|
||||
chartData[key].updateElement = updateElement;
|
||||
|
||||
chartData[key].graph = Morris.Area({
|
||||
element: graphElement,
|
||||
data: [],
|
||||
xkey: 'x',
|
||||
ykeys: ['y'],
|
||||
labels: [key],
|
||||
lineColors: ['#375959'],
|
||||
lineWidth: 2,
|
||||
pointSize: 3,
|
||||
pointFillColors: ['#477372'],
|
||||
pointStrokeColors: ['#313335'],
|
||||
ymin: 0,
|
||||
smooth: false,
|
||||
hideHover: true,
|
||||
parseTime: false,
|
||||
postUnits: 'ms',
|
||||
yLabelFormat: labelYFormat,
|
||||
goals: [],
|
||||
goalStrokeWidth: 1,
|
||||
goalLineColors: ['#66c84f'],
|
||||
grid: true,
|
||||
gridTextColor: '#63676a',
|
||||
gridTextSize: 9,
|
||||
gridTextFamily: 'Arial, "Oxygen Bold"',
|
||||
gridTextWeight: 'bold',
|
||||
gridStrokeWidth: 0.3,
|
||||
resize: true, // we use our own resize function
|
||||
dataLabels: false,
|
||||
hoverReversed: true,
|
||||
// Area chart specific options
|
||||
behaveLikeLine: true,
|
||||
fillOpacity: 0.5,
|
||||
belowArea: true,
|
||||
areaColors: ['#3c3f41'],
|
||||
// Not documented but working
|
||||
padding: 8,
|
||||
});
|
||||
|
||||
updateLogGraph(key);
|
||||
|
||||
graphArea.hideLoadingAnimation();
|
||||
}
|
||||
}
|
||||
/*
|
||||
Util.getResizeManager().observe(
|
||||
this.querySelector('.modal-dialog'),
|
||||
(el, contentRect) => Object.values(chartData).forEach(data => {
|
||||
if(data.graph) data.graph.redraw();
|
||||
}),
|
||||
{debounce: true, ms: 100}
|
||||
);*/
|
||||
});
|
||||
|
||||
// modal dialog before hide
|
||||
logDialog.on('hide.bs.modal', function(e){
|
||||
Object.entries(chartData).forEach(([key, data]) => {
|
||||
if(data.graph){
|
||||
data.graph.destroy();
|
||||
delete chartData[key].graph;
|
||||
}
|
||||
});
|
||||
|
||||
// destroy logTable
|
||||
logDataTable.destroy(true);
|
||||
logDataTable= null;
|
||||
|
||||
// remove event -> prevent calling this multiple times
|
||||
$(this).off('hide.bs.modal');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* updates the log graph for a log key
|
||||
* @param key
|
||||
* @param duration (if undefined -> just update graph with current data)
|
||||
*/
|
||||
let updateLogGraph = (key, duration) => {
|
||||
|
||||
// check if graph data already exist
|
||||
if(!chartData.hasOwnProperty(key)){
|
||||
chartData[key] = {
|
||||
data: [],
|
||||
graph: null,
|
||||
averageElement: null,
|
||||
updateElement: null
|
||||
};
|
||||
}
|
||||
|
||||
// add new value
|
||||
if(duration !== undefined){
|
||||
chartData[key].data.unshift(duration);
|
||||
}
|
||||
|
||||
if(chartData[key].data.length > maxGraphDataCount){
|
||||
chartData[key].data = chartData[key].data.slice(0, maxGraphDataCount);
|
||||
}
|
||||
|
||||
let getGraphData = data => {
|
||||
let tempChartData = {
|
||||
data: [],
|
||||
dataSum: 0,
|
||||
average: 0
|
||||
};
|
||||
|
||||
for(let x = 0; x < maxGraphDataCount; x++){
|
||||
let value = 0;
|
||||
if(data[x]){
|
||||
value = data[x];
|
||||
tempChartData.dataSum = Number((tempChartData.dataSum + value).toFixed(2));
|
||||
}
|
||||
|
||||
tempChartData.data.push({
|
||||
x: x,
|
||||
y: value
|
||||
});
|
||||
}
|
||||
|
||||
// calculate average
|
||||
tempChartData.average = Number((tempChartData.dataSum / data.length).toFixed(2));
|
||||
|
||||
return tempChartData;
|
||||
};
|
||||
|
||||
let tempChartData = getGraphData(chartData[key].data);
|
||||
|
||||
// add new data to graph (Morris chart) - if is already initialized
|
||||
if(chartData[key].graph){
|
||||
let avgElement = chartData[key].averageElement;
|
||||
let updateElement = chartData[key].updateElement;
|
||||
|
||||
let delay = Util.getCurrentTriggerDelay(key, 0);
|
||||
|
||||
if(delay){
|
||||
updateElement[0].textContent = ' delay: ' + delay.toFixed(2) + ' ms';
|
||||
}
|
||||
|
||||
// set/change average line
|
||||
chartData[key].graph.options.goals = [tempChartData.average];
|
||||
|
||||
// change avg. display
|
||||
avgElement[0].textContent = 'avg. ' + tempChartData.average.toFixed(2) + ' ms';
|
||||
|
||||
let avgStatus = getLogStatusByDuration(key, tempChartData.average);
|
||||
let avgStatusClass = Util.getLogInfo( avgStatus, 'class');
|
||||
|
||||
//change avg. display class
|
||||
if( !avgElement.hasClass(avgStatusClass) ){
|
||||
// avg status changed!
|
||||
avgElement.removeClass().addClass('pull-right txt-color ' + avgStatusClass);
|
||||
|
||||
// change goals line color
|
||||
if(avgStatus === 'warning'){
|
||||
chartData[key].graph.options.goalLineColors = ['#e28a0d'];
|
||||
$(document).setProgramStatus('slow connection');
|
||||
}else{
|
||||
chartData[key].graph.options.goalLineColors = ['#5cb85c'];
|
||||
}
|
||||
}
|
||||
|
||||
// set new data and redraw
|
||||
chartData[key].graph.setData( tempChartData.data );
|
||||
}
|
||||
|
||||
return tempChartData.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* get the log "status" by log duration (ms).
|
||||
* If duration > warning limit -> show as warning
|
||||
* @param logKey
|
||||
* @param logDuration
|
||||
* @returns {string}
|
||||
*/
|
||||
let getLogStatusByDuration = (logKey, logDuration) => {
|
||||
let logStatus = 'info';
|
||||
if(logDuration > Init.timer[logKey].EXECUTION_LIMIT){
|
||||
logStatus = 'warning';
|
||||
}
|
||||
return logStatus;
|
||||
};
|
||||
|
||||
/**
|
||||
* get the css class for a specific log type
|
||||
* @param logType
|
||||
* @returns {string}
|
||||
*/
|
||||
let getLogTypeIconClass = logType => {
|
||||
let logIconClass = '';
|
||||
|
||||
switch(logType){
|
||||
case 'client':
|
||||
logIconClass = 'fa-user';
|
||||
break;
|
||||
case 'server':
|
||||
logIconClass = 'fa-download';
|
||||
break;
|
||||
}
|
||||
|
||||
return logIconClass;
|
||||
};
|
||||
|
||||
/**
|
||||
* init logging -> set global log events
|
||||
*/
|
||||
let init = () => {
|
||||
|
||||
let maxEntries = 150;
|
||||
|
||||
$(window).on('pf:syncStatus', () => {
|
||||
updateSyncStatus();
|
||||
});
|
||||
|
||||
// set global logging listener
|
||||
$(window).on('pf:log', (e, logKey, options) => {
|
||||
|
||||
// check required logging information
|
||||
if(
|
||||
options &&
|
||||
options.duration &&
|
||||
options.description
|
||||
){
|
||||
let logDescription = options.description;
|
||||
let logDuration = options.duration;
|
||||
let logType = options.type;
|
||||
|
||||
// update graph data
|
||||
updateLogGraph(logKey, logDuration);
|
||||
|
||||
let time = Util.getServerTime();
|
||||
let timestamp = time.getTime();
|
||||
let timestampFormatted = time.toLocaleTimeString('en-US', { hour12: false });
|
||||
|
||||
let logRowData = {
|
||||
status: getLogStatusByDuration(logKey, logDuration),
|
||||
timestamp: timestamp,
|
||||
timestampFormatted: timestampFormatted,
|
||||
duration: logDuration,
|
||||
description: logDescription,
|
||||
logType: logType,
|
||||
key: logKey
|
||||
};
|
||||
|
||||
if(logDataTable){
|
||||
// add row if dataTable is initialized before new log
|
||||
logDataTable.row.add( logRowData ).draw(false);
|
||||
}else{
|
||||
// add row data to cache
|
||||
logData.push(logRowData);
|
||||
}
|
||||
}
|
||||
|
||||
// delete old log entries from table ---------------------------------
|
||||
if(logData.length >= maxEntries){
|
||||
if(logDataTable){
|
||||
logDataTable.rows(0, {order:'index'}).remove().draw(false);
|
||||
}else{
|
||||
logData.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// cache logs in order to keep previous logs in table after reopening the dialog
|
||||
if(logDataTable){
|
||||
logData = logDataTable.rows({order:'index'}).data();
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
init: init,
|
||||
showDialog: showDialog
|
||||
};
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
BIN
public/js/v2.0.0/app/login.js.br
Normal file
BIN
public/js/v2.0.0/app/login.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/login.js.map
Normal file
1
public/js/v2.0.0/app/login.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,314 +0,0 @@
|
||||
/**
|
||||
* context menu
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/render'
|
||||
], ($, Render) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
contextMenuContainerId: 'pf-contextmenu-container', // id for container element that holds (hidden) context menus
|
||||
mapContextMenuId: 'pf-map-contextmenu', // id for "maps" context menu
|
||||
connectionContextMenuId: 'pf-map-connection-contextmenu', // id for "connections" context menu
|
||||
endpointContextMenuId: 'pf-map-endpoint-contextmenu', // id for "endpoints" context menu
|
||||
systemContextMenuId: 'pf-map-system-contextmenu', // id for "systems" context menu
|
||||
|
||||
contextMenuClass: 'dropdown-menu', // class for all context menus
|
||||
subMenuLeftClass: 'dropdown-submenu-left', // class moves submenus to the left side
|
||||
|
||||
animationInType: 'transition.flipXIn',
|
||||
animationInDuration: 150,
|
||||
animationOutType: 'transition.flipXOut',
|
||||
animationOutDuration: 150
|
||||
};
|
||||
|
||||
/**
|
||||
* calc menu X coordinate
|
||||
* @param e
|
||||
* @param menuWidth
|
||||
* @returns {number|*}
|
||||
*/
|
||||
let getMenuLeftCoordinate = (e, menuWidth) => {
|
||||
let mouseWidth = e.pageX;
|
||||
let openSubLeft = false;
|
||||
if(mouseWidth + menuWidth > window.innerWidth && menuWidth < mouseWidth){
|
||||
// opening menu would pass the side of the page
|
||||
openSubLeft = true;
|
||||
//return mouseWidth - menuWidth;
|
||||
mouseWidth -= menuWidth;
|
||||
}else if(mouseWidth + menuWidth * 2 > window.innerWidth && menuWidth * 2 < mouseWidth){
|
||||
// opening submenu would pass the side of the page
|
||||
openSubLeft = true;
|
||||
}
|
||||
return {
|
||||
left: mouseWidth,
|
||||
openSubLeft: openSubLeft
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* calc menu Y coordinate
|
||||
* @param e
|
||||
* @param menuHeight
|
||||
* @returns {number|*}
|
||||
*/
|
||||
let getMenuTopCoordinate = (e, menuHeight) => {
|
||||
let mouseHeight = e.pageY;
|
||||
if(mouseHeight + menuHeight > window.innerHeight && menuHeight < mouseHeight){
|
||||
// opening menu would pass the bottom of the page
|
||||
mouseHeight -= menuHeight;
|
||||
}
|
||||
return {
|
||||
top: mouseHeight
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* render context menu template for maps
|
||||
* @returns {*}
|
||||
*/
|
||||
let renderMapContextMenu = () => {
|
||||
let moduleData = {
|
||||
id: config.mapContextMenuId,
|
||||
items: [
|
||||
{icon: 'fa-plus', action: 'add_system', text: 'add system'},
|
||||
{icon: 'fa-object-ungroup', action: 'select_all', text: 'select all'},
|
||||
{icon: 'fa-filter', action: 'filter_scope', text: 'filter scope', subitems: [
|
||||
{subIcon: '', subAction: 'filter_wh', subText: 'wormhole'},
|
||||
{subIcon: '', subAction: 'filter_stargate', subText: 'stargate'},
|
||||
{subIcon: '', subAction: 'filter_jumpbridge', subText: 'jumpbridge'},
|
||||
{subIcon: '', subAction: 'filter_abyssal', subText: 'abyssal'}
|
||||
]},
|
||||
{icon: 'fa-sitemap', action: 'map', text: 'map', subitems: [
|
||||
{subIcon: 'fa-edit', subAction: 'map_edit', subText: 'edit map'},
|
||||
{subIcon: 'fa-street-view', subAction: 'map_info', subText: 'map info'},
|
||||
]},
|
||||
{divider: true, action: 'delete_systems'},
|
||||
{icon: 'fa-trash', action: 'delete_systems', text: 'delete systems'}
|
||||
]
|
||||
};
|
||||
|
||||
return Render.render('modules/contextmenu', moduleData);
|
||||
};
|
||||
|
||||
/**
|
||||
* render context menu template for connections
|
||||
* @returns {*}
|
||||
*/
|
||||
let renderConnectionContextMenu = () => {
|
||||
let moduleData = {
|
||||
id: config.connectionContextMenuId,
|
||||
items: [
|
||||
{icon: 'fa-hourglass-end', action: 'wh_eol', text: 'toggle EOL'},
|
||||
{icon: 'fa-exclamation-triangle', action: 'preserve_mass', text: 'preserve mass'},
|
||||
{icon: 'fa-reply fa-rotate-180', action: 'change_status', text: 'mass status', subitems: [
|
||||
{subIcon: 'fa-circle', subIconClass: 'txt-color txt-color-gray', subAction: 'status_fresh', subText: 'stage 1 (fresh)'},
|
||||
{subIcon: 'fa-circle', subIconClass: 'txt-color txt-color-orange', subAction: 'status_reduced', subText: 'stage 2 (reduced)'},
|
||||
{subIcon: 'fa-circle', subIconClass: 'txt-color txt-color-redDark', subAction: 'status_critical', subText: 'stage 3 (critical)'}
|
||||
|
||||
]},
|
||||
{icon: 'fa-reply fa-rotate-180', action: 'wh_jump_mass_change', text: 'ship size', subitems: [
|
||||
{subIcon: 'fa-char', subChar: 'S', subAction: 'wh_jump_mass_s', subText: 'smallest ships'},
|
||||
{subIcon: 'fa-char', subChar: 'M', subAction: 'wh_jump_mass_m', subText: 'medium ships'},
|
||||
{subIcon: 'fa-char', subChar: 'L', subAction: 'wh_jump_mass_l', subText: 'larger ships'},
|
||||
{subIcon: 'fa-char', subChar: 'XL', subAction: 'wh_jump_mass_xl', subText: 'capital ships'}
|
||||
|
||||
]},
|
||||
{icon: 'fa-crosshairs', action: 'change_scope', text: 'change scope', subitems: [
|
||||
{subIcon: 'fa-minus-circle', subIconClass: '', subAction: 'scope_wh', subText: 'wormhole'},
|
||||
{subIcon: 'fa-minus-circle', subIconClass: 'txt-color txt-color-indigoDarkest', subAction: 'scope_stargate', subText: 'stargate'},
|
||||
{subIcon: 'fa-minus-circle', subIconClass: 'txt-color txt-color-tealLighter', subAction: 'scope_jumpbridge', subText: 'jumpbridge'}
|
||||
|
||||
]},
|
||||
{divider: true, action: 'separator'} ,
|
||||
{icon: 'fa-unlink', action: 'delete_connection', text: 'detach'}
|
||||
]
|
||||
};
|
||||
|
||||
return Render.render('modules/contextmenu', moduleData);
|
||||
};
|
||||
|
||||
/**
|
||||
* render context menu template for endpoints
|
||||
* @returns {*}
|
||||
*/
|
||||
let renderEndpointContextMenu = () => {
|
||||
let moduleData = {
|
||||
id: config.endpointContextMenuId,
|
||||
items: [
|
||||
{icon: 'fa-globe', action: 'bubble', text: 'bubbled'}
|
||||
]
|
||||
};
|
||||
|
||||
return Render.render('modules/contextmenu', moduleData);
|
||||
};
|
||||
|
||||
/**
|
||||
* render context menu template for systems
|
||||
* @param systemStatusData
|
||||
* @returns {*}
|
||||
*/
|
||||
let renderSystemContextMenu = systemStatusData => {
|
||||
let statusData = [];
|
||||
for(let [statusName, data] of Object.entries(systemStatusData)){
|
||||
statusData.push({
|
||||
subIcon: 'fa-tag',
|
||||
subIconClass: data.class,
|
||||
subAction: 'change_status_' + statusName,
|
||||
subText: data.label
|
||||
});
|
||||
}
|
||||
|
||||
let moduleData = {
|
||||
id: config.systemContextMenuId,
|
||||
items: [
|
||||
{icon: 'fa-plus', action: 'add_system', text: 'add system'},
|
||||
{icon: 'fa-lock', action: 'lock_system', text: 'lock system'},
|
||||
{icon: 'fa-volume-up', action: 'set_rally', text: 'set rally point'},
|
||||
{icon: 'fa-tags', text: 'set status', subitems: statusData},
|
||||
{icon: 'fa-route', action: 'find_route', text: 'find route'},
|
||||
{icon: 'fa-object-group', action: 'select_connections', text: 'select connections'},
|
||||
{icon: 'fa-reply fa-rotate-180', text: 'waypoints', subitems: [
|
||||
{subIcon: 'fa-flag', subAction: 'set_destination', subText: 'set destination'},
|
||||
{subDivider: true, action: ''},
|
||||
{subIcon: 'fa-step-backward', subAction: 'add_first_waypoint', subText: 'add new [start]'},
|
||||
{subIcon: 'fa-step-forward', subAction: 'add_last_waypoint', subText: 'add new [end]'}
|
||||
]},
|
||||
{divider: true, action: 'delete_system'},
|
||||
{icon: 'fa-trash', action: 'delete_system', text: 'delete system(s)'}
|
||||
]
|
||||
};
|
||||
|
||||
return Render.render('modules/contextmenu', moduleData);
|
||||
};
|
||||
|
||||
/**
|
||||
* prepare (hide/activate/disable) some menu options
|
||||
* @param menuElement
|
||||
* @param hiddenOptions
|
||||
* @param activeOptions
|
||||
* @param disabledOptions
|
||||
* @returns {*}
|
||||
*/
|
||||
let prepareMenu = (menuElement, hiddenOptions, activeOptions, disabledOptions) => {
|
||||
let menuLiElements = menuElement.find('li');
|
||||
|
||||
// reset all menu entries
|
||||
menuLiElements.removeClass('active').removeClass('disabled').show();
|
||||
|
||||
// hide specific menu entries
|
||||
for(let action of hiddenOptions){
|
||||
menuElement.find('li[data-action="' + action + '"]').hide();
|
||||
}
|
||||
|
||||
//set active specific menu entries
|
||||
for(let action of activeOptions){
|
||||
menuElement.find('li[data-action="' + action + '"]').addClass('active');
|
||||
}
|
||||
|
||||
//disable specific menu entries
|
||||
for(let action of disabledOptions){
|
||||
menuElement.find('li[data-action="' + action + '"]').addClass('disabled');
|
||||
}
|
||||
|
||||
return menuElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* close all context menus (map, connection,...)
|
||||
* @param excludeMenu
|
||||
*/
|
||||
let closeMenus = excludeMenu => {
|
||||
let allMenus = $('.' + config.contextMenuClass + '[role="menu"][style*="display: block"]');
|
||||
if(excludeMenu){
|
||||
allMenus = allMenus.not(excludeMenu);
|
||||
}
|
||||
|
||||
allMenus.velocity(config.animationOutType, {
|
||||
duration: config.animationOutDuration
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* open menu handler
|
||||
* @param menuConfig
|
||||
* @param e
|
||||
* @param context
|
||||
*/
|
||||
let openMenu = (menuConfig, e, context) => {
|
||||
let menuElement = $('#' + menuConfig.id);
|
||||
|
||||
// close all other context menus
|
||||
closeMenus(menuElement);
|
||||
|
||||
// remove menu list click event
|
||||
// -> required in case the close handler could not remove them properly
|
||||
// -> this happens if menu re-opens without closing (2x right click)
|
||||
menuElement.off('click.contextMenuSelect', 'li');
|
||||
|
||||
// hide/activate/disable
|
||||
menuElement = prepareMenu(menuElement, menuConfig.hidden, menuConfig.active, menuConfig.disabled);
|
||||
|
||||
let {left, openSubLeft} = getMenuLeftCoordinate(e, menuElement.width());
|
||||
let {top} = getMenuTopCoordinate(e, menuElement.height());
|
||||
|
||||
menuElement.toggleClass(config.subMenuLeftClass, openSubLeft).css({
|
||||
position: 'absolute',
|
||||
left: left,
|
||||
top: top
|
||||
}).velocity(config.animationInType, {
|
||||
duration: config.animationInDuration,
|
||||
complete: function(){
|
||||
context = {
|
||||
original: {
|
||||
event: e,
|
||||
context: context,
|
||||
},
|
||||
selectCallback: menuConfig.selectCallback
|
||||
};
|
||||
|
||||
$(this).one('click.contextMenuSelect', 'li', context, selectHandler);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* menu item select handler
|
||||
* @param e
|
||||
*/
|
||||
let selectHandler = e => {
|
||||
if(e.data.selectCallback){
|
||||
e.data.selectCallback(
|
||||
$(e.currentTarget).attr('data-action'),
|
||||
e.data.original.context.component,
|
||||
e.data.original.event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* default config (skeleton) for valid context menu configuration
|
||||
* @returns {{hidden: [], active: [], disabled: [], id: string, selectCallback: null}}
|
||||
*/
|
||||
let defaultMenuOptionConfig = () => ({
|
||||
'id': '',
|
||||
'selectCallback': null,
|
||||
'hidden': [],
|
||||
'active': [],
|
||||
'disabled': []
|
||||
});
|
||||
|
||||
return {
|
||||
config: config,
|
||||
defaultMenuOptionConfig: defaultMenuOptionConfig,
|
||||
renderMapContextMenu: renderMapContextMenu,
|
||||
renderConnectionContextMenu: renderConnectionContextMenu,
|
||||
renderEndpointContextMenu: renderEndpointContextMenu,
|
||||
renderSystemContextMenu: renderSystemContextMenu,
|
||||
openMenu: openMenu,
|
||||
closeMenus: closeMenus
|
||||
};
|
||||
});
|
||||
@@ -1,532 +0,0 @@
|
||||
define(() => {
|
||||
'use strict';
|
||||
|
||||
class Position {
|
||||
|
||||
constructor(config){
|
||||
this._defaultConfig = {
|
||||
container: null, // parent DOM container element
|
||||
center: null, // DOM element OR [x,y] coordinates that works as center
|
||||
elementClass: 'pf-system', // class for all elements
|
||||
defaultSteps: 8, // how many potential dimensions are checked on en ellipsis around the center
|
||||
defaultGapX: 50,
|
||||
defaultGapY: 50,
|
||||
gapX: 50, // leave gap between elements (x-axis)
|
||||
gapY: 50, // leave gap between elements (y-axis)
|
||||
minX: 0, // min x for valid elements
|
||||
minY: 0, // min y for valid elements
|
||||
spacingX: 20, // spacing x between elements
|
||||
spacingY: 10, // spacing y between elements
|
||||
loops: 2, // max loops around "center" for search
|
||||
grid: false, // set to [20, 20] to force grid snapping
|
||||
newElementWidth: 100, // width for new element
|
||||
newElementHeight: 22, // height for new element
|
||||
mirrorSearch: false, // if true coordinates are "mirrored" for an "alternating" search
|
||||
debug: false, // render debug elements
|
||||
debugOk: false, // if true, only not overlapped dimensions are rendered for debug
|
||||
debugElementClass: 'pf-system-debug' // class for debug elements
|
||||
};
|
||||
|
||||
this._config = Object.assign({}, this._defaultConfig, config);
|
||||
|
||||
this._config.dimensionCache = {};
|
||||
|
||||
this._cacheKey = (dim, depth) => ['dim', dim.left, dim.top, dim.width, dim.height, depth].join('_');
|
||||
|
||||
/**
|
||||
* convert degree into radial unit
|
||||
* @param deg
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
this._degToRad = deg => deg * Math.PI / 180;
|
||||
|
||||
/**
|
||||
* get element dimension/position of a DOM element
|
||||
* @param element
|
||||
* @param spacingX
|
||||
* @param spacingY
|
||||
* @returns {{a: *, b: *, top: *, left: *, width: *, height: *}}
|
||||
* @private
|
||||
*/
|
||||
this._getElementDimension = (element, spacingX = 0, spacingY = 0) => {
|
||||
let left = 0;
|
||||
let top = 0;
|
||||
let a = 0;
|
||||
let b = 0;
|
||||
let width = this._config.newElementWidth;
|
||||
let height = this._config.newElementHeight;
|
||||
|
||||
if(Array.isArray(element)){
|
||||
// x/y coordinates
|
||||
let point = [
|
||||
element[0] ? parseInt(element[0], 10) : 0,
|
||||
element[1] ? parseInt(element[1], 10) : 0
|
||||
];
|
||||
|
||||
if(this._config.grid){
|
||||
point = this._transformPointToGrid(point);
|
||||
}
|
||||
|
||||
left = point[0];
|
||||
top = point[1];
|
||||
a = this._config.gapX;
|
||||
b = this._config.gapY;
|
||||
}else if(element instanceof Element){
|
||||
// DOM element
|
||||
left = (element.style.left ? parseInt(element.style.left, 10) : 0) - spacingX;
|
||||
top = (element.style.top ? parseInt(element.style.top, 10) : 0) - spacingY;
|
||||
a = parseInt((element.offsetWidth / 2).toString(), 10) + spacingX + this._config.gapX;
|
||||
b = parseInt((element.offsetHeight / 2).toString(), 10) + spacingY + this._config.gapY;
|
||||
width = element.offsetWidth + 2 * spacingX;
|
||||
height = element.offsetHeight + 2 * spacingY;
|
||||
}else if(element instanceof Object){
|
||||
left = element.left - spacingX;
|
||||
top = element.top - spacingY;
|
||||
a = parseInt((element.width / 2).toString(), 10) + spacingX + this._config.gapX;
|
||||
b = parseInt((element.height / 2).toString(), 10) + spacingY + this._config.gapY;
|
||||
width = element.width + 2 * spacingX;
|
||||
height = element.height + 2 * spacingY;
|
||||
}
|
||||
|
||||
// add "gap" to a and b in order to have some space around elements
|
||||
return {
|
||||
left: left,
|
||||
top: top,
|
||||
a: a,
|
||||
b: b,
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* get x/y coordinate on an eclipse around a 2D area by a given radial angle
|
||||
* @param dim
|
||||
* @param angle
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
this._getEllipseCoordinates = (dim, angle) => {
|
||||
let coordinates = null;
|
||||
if(dim){
|
||||
angle = this._degToRad(angle);
|
||||
coordinates = {
|
||||
x: Math.round((dim.a * dim.b) / Math.sqrt(Math.pow(dim.b, 2) + Math.pow(dim.a, 2) * Math.pow(Math.tan(angle), 2) )),
|
||||
y: Math.round((dim.a * dim.b) / Math.sqrt(Math.pow(dim.a, 2) + Math.pow(dim.b, 2) / Math.pow(Math.tan(angle), 2) ))
|
||||
};
|
||||
|
||||
// invert coordinate based on quadrant ------------------------------------------------------------
|
||||
if( angle > (Math.PI / 2) && angle < (3 * Math.PI / 2) ){
|
||||
coordinates.x = coordinates.x * -1;
|
||||
}
|
||||
|
||||
if( angle > Math.PI && angle < (2 * Math.PI) ){
|
||||
coordinates.y = coordinates.y * -1;
|
||||
}
|
||||
}
|
||||
return coordinates;
|
||||
};
|
||||
|
||||
/**
|
||||
* get dimensions of all surrounding elements
|
||||
* @returns {Array}
|
||||
* @private
|
||||
*/
|
||||
this._getAllElementDimensions = () => {
|
||||
let dimensions = [];
|
||||
let surroundingElements = this._getContainer().getElementsByClassName(this._config.elementClass);
|
||||
for(let element of surroundingElements){
|
||||
dimensions.push(this._getElementDimension(element, this._config.spacingX, this._config.spacingY));
|
||||
}
|
||||
return dimensions;
|
||||
};
|
||||
|
||||
/**
|
||||
* transform a x/y point into a x/y point that snaps to grid
|
||||
* @param point
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
this._transformPointToGrid = point => {
|
||||
point[0] = Math.floor(point[0] / this._config.grid[0]) * this._config.grid[0];
|
||||
point[1] = Math.floor(point[1] / this._config.grid[1]) * this._config.grid[1];
|
||||
return point;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform a x/y coordinate into a 2D element with width/height
|
||||
* @param centerDimension
|
||||
* @param coordinate
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
this._transformCoordinate = (centerDimension, coordinate) => {
|
||||
let dim = null;
|
||||
if(centerDimension && coordinate){
|
||||
let left = 0;
|
||||
let top = 0;
|
||||
|
||||
// calculate left/top based on coordinate system quadrant -----------------------------------------
|
||||
// -> flip horizontal in Q2 and Q3
|
||||
if(coordinate.x >= 0 && coordinate.y > 0){
|
||||
// 1. quadrant
|
||||
left = centerDimension.left + centerDimension.a - this._config.gapX + coordinate.x;
|
||||
top = centerDimension.top + 2 * (centerDimension.b - this._config.gapY) - Math.abs(coordinate.y) - this._config.newElementHeight;
|
||||
}else if(coordinate.x < 0 && coordinate.y > 0){
|
||||
// 2. quadrant
|
||||
left = centerDimension.left + centerDimension.a - this._config.gapX + coordinate.x - this._config.newElementWidth;
|
||||
top = centerDimension.top + 2 * (centerDimension.b - this._config.gapY) - Math.abs(coordinate.y) - this._config.newElementHeight;
|
||||
}else if(coordinate.x < 0 && coordinate.y <= 0){
|
||||
// 3. quadrant
|
||||
left = centerDimension.left + centerDimension.a - this._config.gapX + coordinate.x - this._config.newElementWidth;
|
||||
top = centerDimension.top + Math.abs(coordinate.y);
|
||||
}else{
|
||||
// 4. quadrant
|
||||
left = centerDimension.left + centerDimension.a - this._config.gapX + coordinate.x;
|
||||
top = centerDimension.top + Math.abs(coordinate.y);
|
||||
}
|
||||
|
||||
// center horizontal for x = 0 coordinate (top and bottom element) --------------------------------
|
||||
if(coordinate.x === 0){
|
||||
left -= Math.round(this._config.newElementWidth / 2);
|
||||
}
|
||||
|
||||
// transform to grid coordinates (if grid snapping is enabled) ------------------------------------
|
||||
if(this._config.grid){
|
||||
let point = this._transformPointToGrid([left, top]);
|
||||
left = point[0];
|
||||
top = point[1];
|
||||
}
|
||||
|
||||
dim = {
|
||||
left: left,
|
||||
top: top,
|
||||
width: this._config.newElementWidth,
|
||||
height: this._config.newElementHeight
|
||||
};
|
||||
}
|
||||
|
||||
return dim;
|
||||
};
|
||||
|
||||
/**
|
||||
* calc overlapping percent of two given dimensions
|
||||
* @param dim1
|
||||
* @param dim2
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
this._percentCovered = (dim1, dim2) => {
|
||||
let percent = 0;
|
||||
|
||||
if(
|
||||
(dim1.left <= dim2.left) &&
|
||||
(dim1.top <= dim2.top) &&
|
||||
((dim1.left + dim1.width) >= (dim2.left + dim2.width)) &&
|
||||
((dim1.top + dim1.height) > (dim2.top + dim2.height))
|
||||
){
|
||||
// The whole thing is covering the whole other thing
|
||||
percent = 100;
|
||||
}else{
|
||||
// Only parts may be covered, calculate percentage
|
||||
dim1.right = dim1.left + dim1.width;
|
||||
dim1.bottom = dim1.top + dim1.height;
|
||||
dim2.right = dim2.left + dim2.width;
|
||||
dim2.bottom = dim2.top + dim2.height;
|
||||
|
||||
let l = Math.max(dim1.left, dim2.left);
|
||||
let r = Math.min(dim1.right, dim2.right);
|
||||
let t = Math.max(dim1.top, dim2.top);
|
||||
let b = Math.min(dim1.bottom, dim2.bottom);
|
||||
|
||||
if(b >= t && r >= l){
|
||||
percent = (((r - l) * (b - t)) / (dim2.width * dim2.height)) * 100;
|
||||
}
|
||||
}
|
||||
return percent;
|
||||
};
|
||||
|
||||
/**
|
||||
* checks whether dim11 has valid x/y coordinate
|
||||
* -> coordinates are >= "minX/Y" limit
|
||||
* @param dim1
|
||||
* @returns {*|boolean}
|
||||
* @private
|
||||
*/
|
||||
this._valid = dim1 => dim1 && dim1.left >= this._config.minX && dim1.top >= this._config.minY;
|
||||
|
||||
/**
|
||||
* checks whether dim1 is partially overlapped by any other element
|
||||
* @param dim1
|
||||
* @param dimensionContainer
|
||||
* @param allDimensions
|
||||
* @param depth
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
this._isOverlapping = (dim1, dimensionContainer, allDimensions, depth) => {
|
||||
let isOverlapping = false;
|
||||
if(dim1){
|
||||
let cacheKey = this._cacheKey(dim1, depth);
|
||||
// check cache first (e.g. if grid is active some dimensions would be checked multiple times)
|
||||
if(this._config.dimensionCache[cacheKey]){
|
||||
return true;
|
||||
}else if(this._percentCovered(dimensionContainer, dim1) === 100){
|
||||
// element is within parent container
|
||||
for(let dim2 of allDimensions){
|
||||
let percentCovered = this._percentCovered(dim1, dim2);
|
||||
if(percentCovered){
|
||||
isOverlapping = true;
|
||||
this._config.dimensionCache[cacheKey] = percentCovered;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
isOverlapping = true;
|
||||
this._config.dimensionCache[cacheKey] = 100;
|
||||
}
|
||||
}else{
|
||||
isOverlapping = true;
|
||||
}
|
||||
|
||||
return isOverlapping;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dim1
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
this._existDimension = function(dim1){
|
||||
return (
|
||||
dim1.left === this.left &&
|
||||
dim1.top === this.top &&
|
||||
dim1.width === this.width &&
|
||||
dim1.height === this.height
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* find all dimensions around a centerDimension that are not overlapped by other elements
|
||||
* @param maxResults
|
||||
* @param steps
|
||||
* @param allDimensions
|
||||
* @param depth
|
||||
* @param loops
|
||||
* @returns {Array}
|
||||
* @private
|
||||
*/
|
||||
this._findDimensions = (maxResults, steps, allDimensions, depth, loops) => {
|
||||
steps = steps || 1;
|
||||
loops = loops || 1;
|
||||
|
||||
let dimensions = [];
|
||||
let start = 0;
|
||||
let end = 360;
|
||||
let angle = end / steps;
|
||||
|
||||
// as default coordinates get checked clockwise Q4 -> Q3 -> Q2 -> Q1
|
||||
// we could also check "mirrored" coordinates Q4+Q1 -> Q3+Q2
|
||||
if(this._config.mirrorSearch){
|
||||
end /= end;
|
||||
}
|
||||
|
||||
let dimensionContainer = this._getElementDimension(this._getContainer());
|
||||
|
||||
if(loops === 1){
|
||||
// check center element
|
||||
let centerDimension = this._getElementDimension(this._config.center);
|
||||
if(
|
||||
this._valid(centerDimension) &&
|
||||
!dimensions.some(this._existDimension, centerDimension) &&
|
||||
!this._isOverlapping(centerDimension, dimensionContainer, allDimensions, depth)
|
||||
){
|
||||
dimensions.push({
|
||||
left: centerDimension.left,
|
||||
top: centerDimension.top,
|
||||
width: centerDimension.width,
|
||||
height: centerDimension.height
|
||||
});
|
||||
this._config.dimensionCache[this._cacheKey(centerDimension, depth)] = 0;
|
||||
|
||||
maxResults--;
|
||||
}
|
||||
}
|
||||
|
||||
// increase the "gab" between center element and potential found dimensions...
|
||||
// ... for each recursive loop call, to get an elliptical cycle beyond
|
||||
this._config.gapX = this._config.defaultGapX + (loops - 1) * 20;
|
||||
this._config.gapY = this._config.defaultGapY + (loops - 1) * 20;
|
||||
let centerDimension = this._getElementDimension(this._config.center);
|
||||
|
||||
while(maxResults > 0 && start < end){
|
||||
// get all potential coordinates on an eclipse around a given "centerElementDimension"
|
||||
let coordinate = this._getEllipseCoordinates(centerDimension, end);
|
||||
let coordinates = [coordinate];
|
||||
if(this._config.mirrorSearch && coordinate){
|
||||
coordinates.push({x: coordinate.x, y: coordinate.y * -1 });
|
||||
}
|
||||
|
||||
for(let coordinateTemp of coordinates){
|
||||
// transform relative x/y coordinate into a absolute 2D area
|
||||
let checkDimension = this._transformCoordinate(centerDimension, coordinateTemp);
|
||||
if(
|
||||
this._valid(checkDimension) &&
|
||||
!dimensions.some(this._existDimension, checkDimension) &&
|
||||
!this._isOverlapping(checkDimension, dimensionContainer, allDimensions, depth)
|
||||
){
|
||||
dimensions.push({
|
||||
left: checkDimension.left,
|
||||
top: checkDimension.top,
|
||||
width: checkDimension.width,
|
||||
height: checkDimension.height
|
||||
});
|
||||
this._config.dimensionCache[this._cacheKey(checkDimension, depth)] = 0;
|
||||
|
||||
maxResults--;
|
||||
}
|
||||
}
|
||||
|
||||
end -= angle;
|
||||
}
|
||||
|
||||
if(maxResults > 0 && loops < this._config.loops){
|
||||
loops++;
|
||||
steps *= 2;
|
||||
dimensions = dimensions.concat(this._findDimensions(maxResults, steps, allDimensions, depth, loops));
|
||||
}
|
||||
|
||||
return dimensions;
|
||||
};
|
||||
|
||||
/**
|
||||
* get container (parent) element
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
this._getContainer = () => {
|
||||
return this._config.container ? this._config.container : document.body;
|
||||
};
|
||||
|
||||
/**
|
||||
* render debug element into parent container
|
||||
* -> checks overlapping dimension with other elements
|
||||
* @private
|
||||
*/
|
||||
this._showDebugElements = () => {
|
||||
if(this._config.debug){
|
||||
let documentFragment = document.createDocumentFragment();
|
||||
for(let [cacheKey, percentCovered] of Object.entries(this._config.dimensionCache)){
|
||||
if(this._config.debugOk && percentCovered){
|
||||
continue;
|
||||
}
|
||||
|
||||
let element = document.createElement('div');
|
||||
let dimParts = cacheKey.split('_');
|
||||
element.style.left = dimParts[1] + 'px';
|
||||
element.style.top = dimParts[2] + 'px';
|
||||
element.style.width = dimParts[3] + 'px';
|
||||
element.style.height = dimParts[4] + 'px';
|
||||
element.style.backgroundColor = Boolean(percentCovered) ? 'rgba(255,0,0,0.1)' : 'rgba(0,255,0,0.4)';
|
||||
element.style.opacity = Boolean(percentCovered) ? 0.5 : 1;
|
||||
element.style.zIndex = Boolean(percentCovered) ? 1000 : 2000;
|
||||
element.style.border = Boolean(percentCovered) ? 'none' : '1px solid rgba(0,255,0,0.3)';
|
||||
element.innerHTML = Math.round(percentCovered * 100) / 100 + '';
|
||||
element.classList.add(this._config.debugElementClass);
|
||||
element.setAttribute('data-depth', dimParts[5]);
|
||||
documentFragment.appendChild(element);
|
||||
}
|
||||
|
||||
this._getContainer().appendChild(documentFragment);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* hide all debug elements
|
||||
* @private
|
||||
*/
|
||||
this._hideDebugElements = () => {
|
||||
let debugElements = this._getContainer().getElementsByClassName(this._config.debugElementClass);
|
||||
while(debugElements.length > 0){
|
||||
debugElements[0].parentNode.removeChild(debugElements[0]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// public functions ---------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* search for surrounding, non overlapping dimensions
|
||||
* @param maxResults
|
||||
* @param findChain
|
||||
* @returns {Array}
|
||||
*/
|
||||
this.findNonOverlappingDimensions = (maxResults, findChain = false) => {
|
||||
this._hideDebugElements();
|
||||
this._config.dimensionCache = {};
|
||||
|
||||
// element dimensions that exist and should be checked for overlapping
|
||||
let allDimensions = this._getAllElementDimensions();
|
||||
let dimensions = [];
|
||||
let depth = 1;
|
||||
let maxDepth = findChain ? maxResults : 1;
|
||||
maxResults = findChain ? 1 : maxResults;
|
||||
while(depth <= maxDepth){
|
||||
let dimensionsTemp = this._findDimensions(maxResults, this._config.defaultSteps, allDimensions, depth);
|
||||
|
||||
if(dimensionsTemp.length){
|
||||
dimensions = dimensions.concat(dimensionsTemp);
|
||||
|
||||
if(findChain){
|
||||
// if depth > 0 we have 2D dimension as "center" (not a x/y coordinate)
|
||||
// -> increase the gap
|
||||
this._config.defaultGapX = 10;
|
||||
this._config.defaultGapY = 10;
|
||||
this._config.gapX = 50;
|
||||
this._config.gapY = 50;
|
||||
this._config.center = dimensionsTemp[0];
|
||||
allDimensions = allDimensions.concat([this._getElementDimension(dimensionsTemp[0], this._config.spacingX, this._config.spacingY)]);
|
||||
}
|
||||
|
||||
depth++;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._showDebugElements();
|
||||
|
||||
return dimensions;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return mouse coordinates from event
|
||||
* @param e
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
let getEventCoordinates = e => {
|
||||
let posX = 0;
|
||||
let posY = 0;
|
||||
|
||||
if(e.offsetX && e.offsetY){
|
||||
// Chrome
|
||||
posX = e.offsetX;
|
||||
posY = e.offsetY;
|
||||
}else if(e.originalEvent){
|
||||
// Firefox -> #415
|
||||
posX = e.originalEvent.layerX;
|
||||
posY = e.originalEvent.layerY;
|
||||
}
|
||||
|
||||
return {x: posX, y: posY};
|
||||
};
|
||||
|
||||
return {
|
||||
Position: Position,
|
||||
getEventCoordinates: getEventCoordinates
|
||||
};
|
||||
});
|
||||
@@ -1,625 +0,0 @@
|
||||
/**
|
||||
* map overlay functions for "Nearby" table
|
||||
* Created by Exodus on 13.04.2017.
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/map/util',
|
||||
'app/map/overlay/util'
|
||||
], function($, Init, Util, MapUtil, MapOverlayUtil){
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
overlayClass: 'pf-map-overlay', // class for all map overlays
|
||||
overlayLocalClass: 'pf-map-overlay-local', // class for "local" overlay
|
||||
|
||||
// left section
|
||||
overlayLocalContentClass: 'pf-map-overlay-local-content', // class for left area - content
|
||||
overlayLocalHeadlineClass: 'pf-map-overlay-headline', // class for headline
|
||||
overlayLocalTableClass: 'pf-local-table', // class for local tables
|
||||
|
||||
// right section
|
||||
overlayLocalTriggerClass: 'pf-map-overlay-local-trigger', // class for open/close trigger icon
|
||||
overlayLocalOpenClass: 'pf-map-overlay-local-open', // class for open status
|
||||
overlayLocalMainClass: 'pf-map-overlay-local-main', // class for right area (always visible)
|
||||
overlayLocalUsersClass: 'pf-map-overlay-local-users', // class for active user count
|
||||
overlayLocalJumpsClass: 'pf-map-overlay-local-jumps', // class for jump distance for table results
|
||||
|
||||
// dataTable
|
||||
tableCellImageClass: 'pf-table-image-cell', // class for table "image" cells
|
||||
tableCellActionClass: 'pf-table-action-cell', // class for table "action" cells
|
||||
tableCellActionIconClass: 'pf-table-action-icon-cell', // class for table "action" icon (icon is part of cell content)
|
||||
|
||||
// toolbar
|
||||
toolbarClass: 'pf-map-overlay-toolbar', // class for toolbar - content
|
||||
toolbarIconClass: 'pf-map-overlay-toolbar-icon', // class for toolbar icon
|
||||
toolbarCheckboxClass: 'pf-map-overlay-toolbar-checkbox' // class for toolbar checkbox
|
||||
};
|
||||
|
||||
/**
|
||||
* checks whether overlay is currently open or not
|
||||
* @param overlay
|
||||
* @returns {*}
|
||||
*/
|
||||
let isOpen = (overlay) => {
|
||||
return overlay.hasClass(config.overlayLocalOpenClass);
|
||||
};
|
||||
|
||||
/**
|
||||
* open overlay -> animation
|
||||
* @param overlay
|
||||
*/
|
||||
let openOverlay = (overlay) => {
|
||||
if( !isOpen(overlay) ){
|
||||
let overlayMain = overlay.find('.' + config.overlayLocalMainClass);
|
||||
overlayMain.find('.' + config.overlayLocalTriggerClass).addClass('right');
|
||||
overlay.addClass(config.overlayLocalOpenClass);
|
||||
|
||||
overlay.velocity({
|
||||
width: '350px'
|
||||
},{
|
||||
duration: Init.animationSpeed.mapOverlayLocal,
|
||||
easing: 'easeOut'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* close overlay -> animation
|
||||
* @param overlay
|
||||
*/
|
||||
let closeOverlay = (overlay) => {
|
||||
if( isOpen(overlay) ){
|
||||
let overlayMain = overlay.find('.' + config.overlayLocalMainClass);
|
||||
overlayMain.find('.' + config.overlayLocalTriggerClass).removeClass('right');
|
||||
overlay.removeClass(config.overlayLocalOpenClass);
|
||||
|
||||
overlay.velocity({
|
||||
width: '32px'
|
||||
},{
|
||||
duration: Init.animationSpeed.mapOverlayLocal,
|
||||
easing: 'easeOut'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* sets overlay observer
|
||||
* @param overlay
|
||||
* @param mapId
|
||||
*/
|
||||
let setOverlayObserver = (overlay, mapId) => {
|
||||
let overlayMain = overlay.find('.' + config.overlayLocalMainClass);
|
||||
|
||||
// open/close toggle ------------------------------------------------------------------------------------------
|
||||
overlayMain.on('click', function(){
|
||||
let overlayMain = $(this).parent('.' + config.overlayLocalClass);
|
||||
let isOpenStatus = isOpen(overlayMain);
|
||||
|
||||
// store current state in indexDB (client)
|
||||
Util.getLocalStore('map').setItem(`${mapId}.showLocal`, !isOpenStatus);
|
||||
|
||||
// trigger open/close
|
||||
if( isOpenStatus ){
|
||||
closeOverlay(overlay);
|
||||
}else{
|
||||
openOverlay(overlay);
|
||||
}
|
||||
});
|
||||
|
||||
// trigger table re-draw() ------------------------------------------------------------------------------------
|
||||
let areaMap = overlay.closest('.' + Util.getMapTabContentAreaClass('map'));
|
||||
areaMap.on('pf:mapResize', function(e){
|
||||
let tableElement = overlay.find('.' + config.overlayLocalTableClass);
|
||||
let tableApi = tableElement.DataTable();
|
||||
tableApi.draw('full-hold');
|
||||
});
|
||||
|
||||
// tooltips ---------------------------------------------------------------------------------------------------
|
||||
overlayMain.initTooltips({
|
||||
placement: 'bottom'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* filter DataTable rows by column data and return rowIds
|
||||
* @param table
|
||||
* @param data
|
||||
* @param values
|
||||
* @param checkExistence
|
||||
*/
|
||||
let filterRows = (table, data = 'id', values = [], checkExistence = true) => {
|
||||
return table.rows().eq(0).filter( function(rowIdx){
|
||||
let rowExists = values.indexOf( table.row(rowIdx ).data()[data] ) !== -1;
|
||||
|
||||
if( !checkExistence ){
|
||||
rowExists = !rowExists;
|
||||
}
|
||||
|
||||
return rowExists;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the "headline" within the Overlay
|
||||
* @param overlay
|
||||
* @param systemData
|
||||
* @param characterAll
|
||||
* @param characterLocal
|
||||
*/
|
||||
let updateLocaleHeadline = (overlay, systemData, characterAll = 0, characterLocal = 0) => {
|
||||
let headlineElement = overlay.find('.' + config.overlayLocalHeadlineClass);
|
||||
let userCountElement = overlay.find('.' + config.overlayLocalUsersClass);
|
||||
|
||||
|
||||
let secClassBase = Util.getSecurityClassForSystem('security');
|
||||
let secClass = Util.getSecurityClassForSystem(systemData.security);
|
||||
|
||||
let childElements = headlineElement.children('span');
|
||||
childElements.eq(1).removeClass().addClass(
|
||||
[secClassBase, secClass].join(' ')
|
||||
).text(systemData.security);
|
||||
|
||||
childElements.eq(2).text(systemData.alias ? systemData.alias : systemData.name);
|
||||
|
||||
// update userCount for "near by" count -------------------------------------------------------------------
|
||||
if( characterAll > 0){
|
||||
userCountElement.toggleClass('txt-color-green', true).toggleClass('txt-color-red', false);
|
||||
}else{
|
||||
userCountElement.toggleClass('txt-color-green', false).toggleClass('txt-color-red', true);
|
||||
}
|
||||
userCountElement.text(characterAll);
|
||||
|
||||
// update userCount in current system ---------------------------------------------------------------------
|
||||
if( characterLocal > 0){
|
||||
childElements.eq(3).toggleClass('txt-color-green', true).toggleClass('txt-color-red', false);
|
||||
}else{
|
||||
childElements.eq(3).toggleClass('txt-color-green', false).toggleClass('txt-color-red', true);
|
||||
}
|
||||
childElements.eq(3).text(characterLocal);
|
||||
};
|
||||
|
||||
/**
|
||||
* updates all changed table rows
|
||||
* @param systemData
|
||||
* @param userData
|
||||
*/
|
||||
$.fn.updateLocalTable = function(systemData, userData){
|
||||
return this.each(function(){
|
||||
let overlay = $(this);
|
||||
let tableElement = overlay.find('.' + config.overlayLocalTableClass);
|
||||
let localTable = tableElement.DataTable();
|
||||
let mapId = systemData.mapId;
|
||||
|
||||
let characterAllIds = [];
|
||||
let characterLocalIds = [];
|
||||
|
||||
// system is on map (just for security check)
|
||||
for(let jumps in userData){
|
||||
if( userData.hasOwnProperty(jumps) ){
|
||||
jumps = parseInt(jumps);
|
||||
|
||||
for(let j = 0; j < userData[jumps].length; j++){
|
||||
// add jump distance
|
||||
userData[jumps][j].jumps = jumps;
|
||||
|
||||
let rowData = userData[jumps][j];
|
||||
|
||||
// check for existing rows
|
||||
let indexes = filterRows(localTable, 'id', [rowData.id]);
|
||||
|
||||
if(indexes.length > 0){
|
||||
// row exists -> update
|
||||
let changedRow = localTable.row( parseInt(indexes[0]) );
|
||||
let changedRowElement = changedRow.nodes().to$();
|
||||
|
||||
// remove tooltips
|
||||
changedRowElement.find('[title]').tooltip('hide').tooltip('destroy');
|
||||
|
||||
// update data
|
||||
changedRow.data(rowData);
|
||||
}else{
|
||||
// new row
|
||||
localTable.row.add(rowData);
|
||||
}
|
||||
|
||||
if(jumps === 0){
|
||||
characterLocalIds.push(rowData.id);
|
||||
}
|
||||
|
||||
characterAllIds.push(rowData.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove rows that no longer exists ----------------------------------------------------------------------
|
||||
let indexesRemove = filterRows(localTable, 'id', characterAllIds, false);
|
||||
localTable.rows(indexesRemove).remove();
|
||||
|
||||
localTable.draw('full-hold');
|
||||
|
||||
// update system relevant data in overlay -----------------------------------------------------------------
|
||||
updateLocaleHeadline(overlay, systemData, characterAllIds.length, characterLocalIds.length);
|
||||
|
||||
// open Overlay -------------------------------------------------------------------------------------------
|
||||
if( !isOpen(overlay) ){
|
||||
Util.getLocalStore('map').getItem(mapId).then(dataStore => {
|
||||
if(
|
||||
dataStore &&
|
||||
dataStore.showLocal
|
||||
){
|
||||
openOverlay(overlay);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init tooltip for a "DataTables" Cell
|
||||
* @param api
|
||||
* @param cell
|
||||
* @param titleSelector
|
||||
*/
|
||||
let initCellTooltip = (api, cell, titleSelector = '') => {
|
||||
$(cell).hover( function(e){
|
||||
let rowIdx = api.cell(cell).index().row;
|
||||
let rowData = api.row(rowIdx).data();
|
||||
|
||||
$(this).tooltip({
|
||||
container: 'body',
|
||||
title: Util.getObjVal(rowData, titleSelector),
|
||||
placement: 'left',
|
||||
delay: 100
|
||||
}).tooltip('show');
|
||||
}, function(e){
|
||||
$(this).tooltip('hide');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init all map local overlay on a "parent" element
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initLocalOverlay = function(mapId){
|
||||
let parentElements = $(this);
|
||||
|
||||
parentElements.each(function(){
|
||||
let parentElement = $(this);
|
||||
|
||||
let overlay = $('<div>', {
|
||||
class: [config.overlayClass, config.overlayLocalClass].join(' ')
|
||||
});
|
||||
|
||||
let content = $('<div>', {
|
||||
class: [ 'text-right', config.overlayLocalContentClass].join(' ')
|
||||
});
|
||||
|
||||
// crate new route table
|
||||
let table = $('<table>', {
|
||||
class: ['compact', 'order-column', config.overlayLocalTableClass].join(' ')
|
||||
});
|
||||
|
||||
let overlayMain = $('<div>', {
|
||||
text: '',
|
||||
class: config.overlayLocalMainClass
|
||||
}).append(
|
||||
$('<i>', {
|
||||
class: ['fas', 'fa-chevron-down', 'fa-fw', 'pf-animate-rotate', config.overlayLocalTriggerClass].join(' ')
|
||||
}),
|
||||
$('<span>', {
|
||||
class: ['badge', 'txt-color', 'txt-color-red', config.overlayLocalUsersClass].join(' '),
|
||||
text: 0
|
||||
}),
|
||||
$('<div>', {
|
||||
class: config.overlayLocalJumpsClass
|
||||
}).append(
|
||||
$('<span>', {
|
||||
class: ['badge', 'txt-color', 'txt-color-grayLight'].join(' '),
|
||||
text: MapUtil.config.defaultLocalJumpRadius
|
||||
}).attr('title', 'jumps')
|
||||
)
|
||||
);
|
||||
|
||||
let headline = $('<div>', {
|
||||
class: config.overlayLocalHeadlineClass
|
||||
}).append(
|
||||
$('<span>', {
|
||||
html: 'Nearby ',
|
||||
class: 'pull-left'
|
||||
}),
|
||||
$('<span>'),
|
||||
$('<span>'),
|
||||
$('<span>', {
|
||||
class: ['badge', ' txt-color', 'txt-color-red'].join(' '),
|
||||
text: 0
|
||||
})
|
||||
);
|
||||
|
||||
content.append(headline);
|
||||
content.append(table);
|
||||
// toolbar not used for now
|
||||
// content.append(initToolbar());
|
||||
|
||||
overlay.append(overlayMain);
|
||||
overlay.append(content);
|
||||
|
||||
parentElement.append(overlay);
|
||||
|
||||
// set observer
|
||||
setOverlayObserver(overlay, mapId);
|
||||
|
||||
// init local table ---------------------------------------------------------------------------------------
|
||||
table.on('preDraw.dt', function(e, settings){
|
||||
let table = $(this);
|
||||
let areaMap = table.closest('.' + Util.getMapTabContentAreaClass('map'));
|
||||
|
||||
// areaMap should always exist
|
||||
if(areaMap && areaMap.length) {
|
||||
// check available maxHeight for "locale" table based on current map height (resizable)
|
||||
let mapHeight = areaMap[0].offsetHeight;
|
||||
let localOverlay = MapOverlayUtil.getMapOverlay(table, 'local');
|
||||
let paginationElement = localOverlay.find('.dataTables_paginate');
|
||||
|
||||
let tableApi = table.DataTable();
|
||||
let pageInfo = tableApi.page.info();
|
||||
let localTableRowHeight = 26;
|
||||
|
||||
let localTop = localOverlay[0].offsetTop;
|
||||
let bottomSpace = 38 + 10; // "timer" overlay + some spacing top
|
||||
bottomSpace += 16 + 5 + 5; // horizontal scrollBar height + some spacing top + bottom
|
||||
let localHeightMax = mapHeight - bottomSpace - localTop; // max available for local overlay
|
||||
|
||||
let localTableBodyMaxHeight = localHeightMax - 53; // - headline height + <thead> height
|
||||
let newPageLength = Math.floor(localTableBodyMaxHeight / localTableRowHeight);
|
||||
if(pageInfo.recordsDisplay > newPageLength){
|
||||
// show pagination and limit page length
|
||||
localTableBodyMaxHeight -= 30; // - pagination height
|
||||
newPageLength = Math.floor(localTableBodyMaxHeight / localTableRowHeight);
|
||||
}
|
||||
|
||||
if(pageInfo.length !== newPageLength){
|
||||
tableApi.page.len(newPageLength);
|
||||
|
||||
// page length changed -> show/hide pagination
|
||||
pageInfo = tableApi.page.info();
|
||||
if(pageInfo.pages <= 1){
|
||||
paginationElement.hide();
|
||||
}else{
|
||||
paginationElement.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
table.on('draw.dt', function(e, settings){
|
||||
// init table tooltips
|
||||
$(this).find('td').initTooltips({
|
||||
placement: 'left'
|
||||
});
|
||||
});
|
||||
|
||||
// table init complete
|
||||
table.on('init.dt', function(){
|
||||
// init table head tooltips
|
||||
$(this).initTooltips({
|
||||
placement: 'top'
|
||||
});
|
||||
});
|
||||
|
||||
let localTable = table.DataTable({
|
||||
pageLength: 3, // default page length, smaller then max page length (4) if map is vertical resized to min.
|
||||
paging: true,
|
||||
pagingType: 'simple',
|
||||
lengthChange: false,
|
||||
ordering: true,
|
||||
order: [ 0, 'asc' ],
|
||||
info: false,
|
||||
searching: false,
|
||||
hover: false,
|
||||
responsive: false, // true "hides" some columns on init (why?)
|
||||
rowId: function(rowData){
|
||||
return 'pf-local-row_' + rowData.id; // characterId
|
||||
},
|
||||
language: {
|
||||
emptyTable: '<span>You are alone</span>',
|
||||
paginate: {
|
||||
next: ' ',
|
||||
previous: ' '
|
||||
}
|
||||
},
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 0,
|
||||
orderable: true,
|
||||
title: '<span title="jumps" data-toggle="tooltip"> </span>',
|
||||
width: '1px',
|
||||
className: [Util.config.helpDefaultClass, 'text-center'].join(' '),
|
||||
data: 'jumps',
|
||||
render: {
|
||||
_: (data, type, row, meta) => {
|
||||
let value = data;
|
||||
if(type === 'display'){
|
||||
if(value === 0){
|
||||
value = '<i class="fas fa-map-marker-alt"></i>';
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
},
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
let api = this.DataTable();
|
||||
initCellTooltip(api, cell, 'log.system.name');
|
||||
}
|
||||
},{
|
||||
targets: 1,
|
||||
orderable: false,
|
||||
title: '',
|
||||
width: '26px',
|
||||
className: [Util.config.helpDefaultClass, 'text-center', config.tableCellImageClass].join(' '),
|
||||
data: 'log.ship',
|
||||
render: {
|
||||
_: (data, type, row, meta) => {
|
||||
let value = data.typeName;
|
||||
if(type === 'display'){
|
||||
value = '<img src="' + Util.eveImageUrl('types', data.typeId) + '"/>';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
},
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
let api = this.DataTable();
|
||||
initCellTooltip(api, cell, 'log.ship.typeName');
|
||||
}
|
||||
}, {
|
||||
targets: 2,
|
||||
orderable: true,
|
||||
title: 'ship name',
|
||||
width: '80px',
|
||||
data: 'log.ship',
|
||||
render: {
|
||||
_: (data, type, row, meta) => {
|
||||
let value = data.name;
|
||||
if(type === 'display'){
|
||||
value = '<div class="' + MapUtil.config.tableCellEllipsisClass + ' ' + MapUtil.config.tableCellEllipsis80Class + '">' + data.name + '</div>';
|
||||
}
|
||||
return value;
|
||||
},
|
||||
sort: 'name'
|
||||
}
|
||||
},{
|
||||
targets: 3,
|
||||
orderable: true,
|
||||
title: 'pilot',
|
||||
data: 'name',
|
||||
render: {
|
||||
_: (data, type, row, meta) => {
|
||||
let value = data;
|
||||
if(type === 'display'){
|
||||
value = '<div class="' + MapUtil.config.tableCellEllipsisClass + ' ' + MapUtil.config.tableCellEllipsis90Class + '">' + data + '</div>';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
},{
|
||||
targets: 4,
|
||||
orderable: false,
|
||||
title: '',
|
||||
width: '10px',
|
||||
className: [Util.config.helpDefaultClass].join(' '),
|
||||
data: 'log',
|
||||
render: {
|
||||
_: (data, type, row, meta) => {
|
||||
let value = '';
|
||||
if(type === 'display'){
|
||||
if(data.station && data.station.id > 0){
|
||||
value = '<i class="fas fa-home"></i>';
|
||||
}else if(data.structure && data.structure.id > 0){
|
||||
value = '<i class="fas fa-industry"></i>';
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
},
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
let selector = '';
|
||||
if(cellData.station && cellData.station.id > 0){
|
||||
selector = 'log.station.name';
|
||||
}else if(cellData.structure && cellData.structure.id > 0){
|
||||
selector = 'log.structure.name';
|
||||
}
|
||||
let api = this.DataTable();
|
||||
initCellTooltip(api, cell, selector);
|
||||
}
|
||||
},{
|
||||
targets: 5,
|
||||
orderable: false,
|
||||
title: '<i title="open ingame" data-toggle="tooltip" class="fas fa-id-card text-right"></i>',
|
||||
width: '10px',
|
||||
className: [config.tableCellActionClass].join(' '),
|
||||
data: 'id',
|
||||
render: {
|
||||
_: (data, type, row, meta) => {
|
||||
let value = data;
|
||||
if(type === 'display'){
|
||||
value = '<i class="fas fa-id-card ' + config.tableCellActionIconClass + '"></i>';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
},
|
||||
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
|
||||
// open character information window (ingame)
|
||||
$(cell).on('click', { tableApi: this.DataTable(), cellData: cellData }, function(e){
|
||||
let cellData = e.data.tableApi.cell(this).data();
|
||||
Util.openIngameWindow(e.data.cellData);
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let initToolbar = () => {
|
||||
|
||||
let getCheckbox = (options) => {
|
||||
return $('<div>', {
|
||||
class: [config.toolbarCheckboxClass, 'checkbox'].join(' ')
|
||||
}).append(
|
||||
$('<input>', {
|
||||
type: 'checkbox',
|
||||
id: options.id,
|
||||
name: options.name,
|
||||
value: options.value,
|
||||
checked: 'checked'
|
||||
}),
|
||||
$('<label>',{
|
||||
'for': options.id,
|
||||
html: options.label
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
let toolbar = $('<div>', {
|
||||
class: [config.toolbarClass].join(' ')
|
||||
}).append(
|
||||
$('<i>', {
|
||||
class: ['fas', 'fa-fw', 'fa-lg', 'fa-filter', config.toolbarIconClass, 'pull-left'].join(' ')
|
||||
}),
|
||||
getCheckbox({
|
||||
id: 'test',
|
||||
name: 'filter_character_active',
|
||||
value: 1,
|
||||
checked: true,
|
||||
label: 'active'
|
||||
})
|
||||
);
|
||||
|
||||
return toolbar;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear Overlay and "Reset"
|
||||
* @param mapId
|
||||
*/
|
||||
$.fn.clearLocalTable = function(mapId){
|
||||
return this.each(function(){
|
||||
let overlay = $(this);
|
||||
|
||||
// update locale overlay headline -------------------------------------------------------------------------
|
||||
updateLocaleHeadline(overlay, {
|
||||
name: 'unknown',
|
||||
security: ''
|
||||
});
|
||||
|
||||
// clear all table rows -----------------------------------------------------------------------------------
|
||||
let tableElement = overlay.find('.' + config.overlayLocalTableClass);
|
||||
let localTable = tableElement.DataTable();
|
||||
localTable.rows().remove().draw();
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,224 +0,0 @@
|
||||
/**
|
||||
* Map "magnetizing" feature
|
||||
* jsPlumb extension used: https://github.com/ThomasChan/farahey
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/map/util',
|
||||
'farahey'
|
||||
], ($, MapUtil) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* active magnetizer instances (cache object)
|
||||
* @type {{}}
|
||||
*/
|
||||
let magnetizerInstances = {};
|
||||
|
||||
/**
|
||||
* magnetizer instance exists for mapId
|
||||
* @param mapId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
let hasInstance = mapId => magnetizerInstances.hasOwnProperty(mapId);
|
||||
|
||||
/**
|
||||
* get magnetizer instance by mapId
|
||||
* @param mapId
|
||||
* @returns {null}
|
||||
*/
|
||||
let getInstance = mapId => hasInstance(mapId) ? magnetizerInstances[mapId] : null;
|
||||
|
||||
/**
|
||||
* set new magnetizer instance for mapId
|
||||
* @param mapId
|
||||
* @param magnetizer
|
||||
*/
|
||||
let setInstance = (mapId, magnetizer) => {
|
||||
if(mapId && magnetizer){
|
||||
magnetizerInstances[mapId] = magnetizer;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* init new magnetizer instance for a map
|
||||
* @param mapContainer
|
||||
*/
|
||||
let initMagnetizer = mapContainer => {
|
||||
let mapId = mapContainer.data('id');
|
||||
|
||||
if(!hasInstance(mapId)){
|
||||
// magnetizer not exist -> new instance
|
||||
let systems = mapContainer.getSystems();
|
||||
|
||||
/**
|
||||
* function that takes an element from your list and returns its position as a JS object
|
||||
* @param system
|
||||
* @returns {{top: number, left: number}}
|
||||
* @private
|
||||
*/
|
||||
let _offset = system => {
|
||||
let _ = p => {
|
||||
let v = system.style[p];
|
||||
return parseInt(v.substring(0, v.length - 2));
|
||||
};
|
||||
return {left: _('left'), top: _('top')};
|
||||
};
|
||||
|
||||
/**
|
||||
* function that takes an element id and position, and applies that position to the related element
|
||||
* @param system
|
||||
* @param o
|
||||
* @private
|
||||
*/
|
||||
let _setOffset = (system, o) => {
|
||||
o.left = Math.round(o.left);
|
||||
o.top = Math.round(o.top);
|
||||
let left = o.left + 'px';
|
||||
let top = o.top + 'px';
|
||||
let markAsUpdated = false;
|
||||
|
||||
// new position must be within parent container
|
||||
// no negative offset!
|
||||
if(
|
||||
o.left >= 0 && o.left <= 2300 &&
|
||||
system.style.left !== left
|
||||
){
|
||||
system.style.left = left;
|
||||
markAsUpdated = true;
|
||||
}
|
||||
|
||||
if(
|
||||
o.top >= 0 && o.top <= 1400 &&
|
||||
system.style.top !== top
|
||||
){
|
||||
system.style.top = top;
|
||||
markAsUpdated = true;
|
||||
}
|
||||
|
||||
if(markAsUpdated){
|
||||
MapUtil.markAsChanged($(system));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* filter some element8s) from being moved
|
||||
* @param systemId
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
let _dragFilter = systemId => {
|
||||
let filterClasses = ['jtk-drag', MapUtil.config.systemLockedClass];
|
||||
return ![...document.getElementById(systemId).classList].some(className => filterClasses.indexOf(className) >= 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* grid snap constraint
|
||||
* @param gridX
|
||||
* @param gridY
|
||||
* @returns {Function}
|
||||
*/
|
||||
let gridConstrain = (gridX, gridY) => {
|
||||
return (id, current, delta) => {
|
||||
if(mapContainer.hasClass(MapUtil.config.mapGridClass)){
|
||||
// active grid
|
||||
return {
|
||||
left: (gridX * Math.floor( (Math.round(current[0]) + delta.left) / gridX )) - current[0],
|
||||
top: (gridY * Math.floor( (Math.round(current[1]) + delta.top) / gridY )) - current[1]
|
||||
};
|
||||
}else{
|
||||
// no grid
|
||||
delta.left = Math.round(delta.left);
|
||||
delta.top = Math.round(delta.top);
|
||||
return delta;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// create new magnetizer instance -------------------------------------------------------------------------
|
||||
setInstance(mapId, window.Farahey.getInstance({
|
||||
container: mapContainer,
|
||||
getContainerPosition: mapContainer => mapContainer.offset(),
|
||||
getPosition:_offset,
|
||||
getSize: system => {
|
||||
let clientRect = system.getBoundingClientRect();
|
||||
return [Math.floor(clientRect.width), Math.floor(clientRect.height)];
|
||||
},
|
||||
getId : system => system.id,
|
||||
setPosition:_setOffset,
|
||||
elements: systems.toArray(),
|
||||
filter: _dragFilter,
|
||||
padding: [3, 3],
|
||||
constrain: gridConstrain(MapUtil.config.mapSnapToGridDimension, MapUtil.config.mapSnapToGridDimension),
|
||||
executeNow: false, // no initial rearrange after initialization
|
||||
excludeFocus: true
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* destroy Magnetizer instance
|
||||
*/
|
||||
let destroyMagnetizer = mapContainer => {
|
||||
let mapId = mapContainer.data('id');
|
||||
let magnetizer = getInstance(mapId);
|
||||
if(magnetizer){
|
||||
magnetizer.reset();
|
||||
delete magnetizerInstances[mapId];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* update system positions for "all" systems that are effected by drag&drop
|
||||
* @param map
|
||||
* @param e
|
||||
*/
|
||||
let executeAtEvent = (map, e) => {
|
||||
let mapContainer = $(map.getContainer());
|
||||
let mapId = mapContainer.data('id');
|
||||
let magnetizer = getInstance(mapId);
|
||||
|
||||
if(magnetizer && e){
|
||||
magnetizer.executeAtEvent(e, {
|
||||
iterations: 2,
|
||||
excludeFocus: true
|
||||
});
|
||||
map.repaintEverything();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* add system to magnetizer instance
|
||||
* @param mapId
|
||||
* @param system
|
||||
* @param doNotTestForDuplicates
|
||||
*/
|
||||
let addElement = (mapId, system, doNotTestForDuplicates) => {
|
||||
let magnetizer = getInstance(mapId);
|
||||
if(magnetizer){
|
||||
magnetizer.addElement(system, doNotTestForDuplicates);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* remove system element from magnetizer instance
|
||||
* @param mapId
|
||||
* @param system
|
||||
*/
|
||||
let removeElement = (mapId, system) => {
|
||||
let magnetizer = getInstance(mapId);
|
||||
if(magnetizer){
|
||||
magnetizer.removeElement(system);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
initMagnetizer: initMagnetizer,
|
||||
destroyMagnetizer: destroyMagnetizer,
|
||||
executeAtEvent: executeAtEvent,
|
||||
addElement: addElement,
|
||||
removeElement: removeElement
|
||||
};
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,869 +0,0 @@
|
||||
/**
|
||||
* map overlay functions
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/map/overlay/util',
|
||||
'app/map/util',
|
||||
'app/lib/cron'
|
||||
], ($, Init, Util, MapOverlayUtil, MapUtil, Cron) => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* get map object (jsPlumb) from iconElement
|
||||
* @param overlayIcon
|
||||
* @returns {*}
|
||||
*/
|
||||
let getMapObjectFromOverlayIcon = overlayIcon => {
|
||||
return MapUtil.getMapInstance(MapOverlayUtil.getMapElementFromOverlay(overlayIcon).data('id'));
|
||||
};
|
||||
|
||||
/**
|
||||
* add/update endpoints with overlays from signature mapping
|
||||
* @param endpoint
|
||||
* @param labelData
|
||||
*/
|
||||
let updateEndpointOverlaySignatureLabel = (endpoint, labelData) => {
|
||||
let labels = labelData.labels;
|
||||
let names = labelData.names;
|
||||
let overlay = endpoint.getOverlay(MapOverlayUtil.config.endpointOverlayId);
|
||||
|
||||
if(overlay instanceof jsPlumb.Overlays.Label){
|
||||
// update existing overlay
|
||||
if(
|
||||
!labels.equalValues(overlay.getParameter('signatureLabels')) ||
|
||||
!names.equalValues(overlay.getParameter('signatureNames'))
|
||||
){
|
||||
// update label only on label changes
|
||||
overlay.setLabel(MapUtil.formatEndpointOverlaySignatureLabel(labels));
|
||||
overlay.setParameter('fullSize', false);
|
||||
overlay.setParameter('signatureLabels', labels);
|
||||
overlay.setParameter('signatureNames', names);
|
||||
overlay.updateClasses(labels.length ? 'small' : 'icon', labels.length ? 'icon' : 'small');
|
||||
overlay.setLocation(MapUtil.getEndpointOverlaySignatureLocation(endpoint, labels));
|
||||
}
|
||||
}else{
|
||||
// add new overlay
|
||||
endpoint.addOverlay([
|
||||
'Label',
|
||||
{
|
||||
label: MapUtil.formatEndpointOverlaySignatureLabel(labels),
|
||||
id: MapOverlayUtil.config.endpointOverlayId,
|
||||
cssClass: [MapOverlayUtil.config.componentOverlayClass, labels.length ? 'small' : 'icon'].join(' '),
|
||||
location: MapUtil.getEndpointOverlaySignatureLocation(endpoint, labels),
|
||||
events: {
|
||||
toggleSize: function(fullSize){
|
||||
let signatureNames = this.getParameter('signatureNames');
|
||||
if(fullSize && !this.getParameter('fullSize') && signatureNames.length){
|
||||
this.setLabel(this.getLabel() + '<br>' + '<span class="initialism">' + signatureNames.join(', ') + '</span>');
|
||||
this.setParameter('fullSize', true);
|
||||
}else if(this.getParameter('fullSize')){
|
||||
this.setLabel(MapUtil.formatEndpointOverlaySignatureLabel(this.getParameter('signatureLabels')));
|
||||
this.setParameter('fullSize', false);
|
||||
}
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
fullSize: false,
|
||||
signatureLabels: labels,
|
||||
signatureNames: names
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* get overlay parameters for connection overlay (type 'diamond' or 'arrow')
|
||||
* @param overlayType
|
||||
* @param direction
|
||||
* @returns {{length: number, foldback: number, direction: number}}
|
||||
*/
|
||||
let getConnectionArrowOverlayParams = (overlayType, direction = 1) => {
|
||||
switch(overlayType){
|
||||
case 'arrow':
|
||||
return {
|
||||
length: 15,
|
||||
direction: direction,
|
||||
foldback: 0.8
|
||||
};
|
||||
default: // diamond
|
||||
return {
|
||||
length: 10,
|
||||
direction: 1,
|
||||
foldback: 2
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* add overlays to connections (signature based data)
|
||||
* @param map
|
||||
* @param connectionsData
|
||||
*/
|
||||
let updateInfoSignatureOverlays = (map, connectionsData) => {
|
||||
let type = 'info_signature';
|
||||
connectionsData = Util.arrayToObject(connectionsData);
|
||||
|
||||
map.setSuspendDrawing(true);
|
||||
|
||||
map.getAllConnections().forEach(connection => {
|
||||
let connectionId = connection.getParameter('connectionId');
|
||||
let sourceEndpoint = connection.endpoints[0];
|
||||
let targetEndpoint = connection.endpoints[1];
|
||||
|
||||
let connectionData = Util.getObjVal(connectionsData, `${connectionId}`);
|
||||
let signatureTypeData = MapUtil.getConnectionDataFromSignatures(connection, connectionData);
|
||||
|
||||
let sizeLockedBySignature = false;
|
||||
|
||||
if(connection.scope === 'wh'){
|
||||
if(!connection.hasType(type)){
|
||||
connection.addType(type);
|
||||
}
|
||||
|
||||
let overlayArrow = connection.getOverlay(MapOverlayUtil.config.connectionOverlayArrowId);
|
||||
|
||||
// Arrow overlay needs to be cleared() (removed) if 'info_signature' gets removed!
|
||||
// jsPlumb does not handle overlay updates for Arrow overlays... so we need to re-apply the the overlay manually
|
||||
if(overlayArrow.path && !overlayArrow.path.isConnected){
|
||||
connection.canvas.appendChild(overlayArrow.path);
|
||||
}
|
||||
|
||||
// since there "could" be multiple sig labels on each endpoint,
|
||||
// there can only one "primary label picked up for wormhole jump mass detection!
|
||||
let primeLabel;
|
||||
|
||||
let overlayType = 'diamond'; // not specified
|
||||
let arrowDirection = 1;
|
||||
|
||||
if(connectionData && connectionData.signatures){
|
||||
// signature data found for current connection
|
||||
let sourceLabel = signatureTypeData.source.labels;
|
||||
let targetLabel = signatureTypeData.target.labels;
|
||||
|
||||
// add arrow (connection) overlay that points from "XXX" => "K162" ----------------------------
|
||||
if(
|
||||
(sourceLabel.includes('K162') && targetLabel.includes('K162')) ||
|
||||
(sourceLabel.length === 0 && targetLabel.length === 0) ||
|
||||
(
|
||||
sourceLabel.length > 0 && targetLabel.length > 0 &&
|
||||
!sourceLabel.includes('K162') && !targetLabel.includes('K162')
|
||||
)
|
||||
){
|
||||
// unknown direction -> show default 'diamond' overlay
|
||||
overlayType = 'diamond';
|
||||
}else if(
|
||||
(sourceLabel.includes('K162')) ||
|
||||
(sourceLabel.length === 0 && !targetLabel.includes('K162'))
|
||||
){
|
||||
// convert default arrow direction
|
||||
overlayType = 'arrow';
|
||||
arrowDirection = -1;
|
||||
|
||||
primeLabel = targetLabel.find(label => label !== 'K162');
|
||||
}else{
|
||||
// default arrow direction is fine
|
||||
overlayType = 'arrow';
|
||||
|
||||
primeLabel = sourceLabel.find(label => label !== 'K162');
|
||||
}
|
||||
}
|
||||
|
||||
// class changes must be done on "connection" itself not on "overlayArrow"
|
||||
// -> because Arrow might not be rendered to map at this point (if it does not exist already)
|
||||
if(overlayType === 'arrow'){
|
||||
connection.updateClasses(
|
||||
MapOverlayUtil.config.connectionArrowOverlaySuccessClass,
|
||||
MapOverlayUtil.config.connectionArrowOverlayDangerClass
|
||||
);
|
||||
}else{
|
||||
connection.updateClasses(
|
||||
MapOverlayUtil.config.connectionArrowOverlayDangerClass,
|
||||
MapOverlayUtil.config.connectionArrowOverlaySuccessClass
|
||||
);
|
||||
}
|
||||
|
||||
overlayArrow.updateFrom(getConnectionArrowOverlayParams(overlayType, arrowDirection));
|
||||
|
||||
// update/add endpoint overlays -------------------------------------------------------------------
|
||||
updateEndpointOverlaySignatureLabel(sourceEndpoint, signatureTypeData.source);
|
||||
updateEndpointOverlaySignatureLabel(targetEndpoint, signatureTypeData.target);
|
||||
|
||||
// fix/overwrite existing jump mass connection type -----------------------------------------------
|
||||
// if a connection type for "jump mass" (e.g. S, M, L, XL) is set for this connection
|
||||
// we should check/compare it with the current primary signature label from signature mapping
|
||||
// and change it if necessary
|
||||
if(Init.wormholes.hasOwnProperty(primeLabel)){
|
||||
// connection size from mapped signature
|
||||
sizeLockedBySignature = true;
|
||||
|
||||
// get 'connection mass type' from wormholeData
|
||||
let massType = Util.getObjVal(Object.assign({}, Init.wormholes[primeLabel]), 'size.type');
|
||||
|
||||
if(massType && !connection.hasType(massType)){
|
||||
MapOverlayUtil.getMapOverlay(connection.canvas, 'timer').startMapUpdateCounter();
|
||||
MapUtil.setConnectionJumpMassType(connection, massType);
|
||||
MapUtil.markAsChanged(connection);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// connection is not 'wh' scope
|
||||
if(connection.hasType(type)){
|
||||
connection.removeType(type);
|
||||
}
|
||||
}
|
||||
|
||||
// lock/unlock connection for manual size changes (from contextmenu)
|
||||
connection.setParameter('sizeLocked', sizeLockedBySignature);
|
||||
});
|
||||
|
||||
map.setSuspendDrawing(false, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* hide default icon and replace it with "loading" icon
|
||||
* @param iconElement
|
||||
*/
|
||||
let showLoading = iconElement => {
|
||||
iconElement = $(iconElement);
|
||||
let dataName = 'default-icon';
|
||||
let defaultIconClass = iconElement.data(dataName);
|
||||
|
||||
// get default icon class
|
||||
if( !defaultIconClass ){
|
||||
// index 0 == 'fa-fw', index 1 == IconName
|
||||
defaultIconClass = $(iconElement).attr('class').match(/\bfa-\S*/g)[1];
|
||||
iconElement.data(dataName, defaultIconClass);
|
||||
}
|
||||
|
||||
iconElement.toggleClass( defaultIconClass + ' fa-sync fa-spin');
|
||||
};
|
||||
|
||||
/**
|
||||
* hide "loading" icon and replace with default icon
|
||||
* @param iconElement
|
||||
*/
|
||||
let hideLoading = iconElement => {
|
||||
iconElement = $(iconElement);
|
||||
let dataName = 'default-icon';
|
||||
let defaultIconClass = iconElement.data(dataName);
|
||||
|
||||
iconElement.toggleClass( defaultIconClass + ' fa-sync fa-spin');
|
||||
};
|
||||
|
||||
/**
|
||||
* get overlay icon from e.g. mapElement
|
||||
* @param element
|
||||
* @param iconClass
|
||||
* @param overlayType
|
||||
* @returns {*}
|
||||
*/
|
||||
let getOverlayIcon = (element, iconClass, overlayType = 'info') => {
|
||||
return MapOverlayUtil.getMapOverlay(element, overlayType).find('.' + iconClass);
|
||||
};
|
||||
|
||||
/**
|
||||
* showInfoSignatureOverlays
|
||||
* -> used by "refresh" overlays (hover) AND/OR initial menu trigger
|
||||
*/
|
||||
let showInfoSignatureOverlays = mapElement => {
|
||||
let mapId = mapElement.data('id');
|
||||
let map = MapUtil.getMapInstance(mapId);
|
||||
let mapData = Util.getCurrentMapData(mapId);
|
||||
let connectionsData = Util.getObjVal(mapData, 'data.connections');
|
||||
|
||||
if(connectionsData){
|
||||
let overlayIcon = getOverlayIcon(mapElement, options.mapSignatureOverlays.class);
|
||||
showLoading(overlayIcon);
|
||||
updateInfoSignatureOverlays(map, connectionsData);
|
||||
hideLoading(overlayIcon);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* hideInfoSignatureOverlays
|
||||
* -> see showInfoSignatureOverlays()
|
||||
*/
|
||||
let hideInfoSignatureOverlays = mapElement => {
|
||||
let mapId = mapElement.data('id');
|
||||
let map = MapUtil.getMapInstance(mapId);
|
||||
let type = 'info_signature';
|
||||
|
||||
map.setSuspendDrawing(true);
|
||||
|
||||
map.getAllConnections().forEach(connection => {
|
||||
let overlayArrow = connection.getOverlay(MapOverlayUtil.config.connectionOverlayArrowId);
|
||||
|
||||
if(overlayArrow){
|
||||
overlayArrow.cleanup();
|
||||
}
|
||||
|
||||
if(connection.hasType(type)){
|
||||
connection.removeType(type, {}, true);
|
||||
}
|
||||
});
|
||||
|
||||
map.selectEndpoints().removeOverlay(MapOverlayUtil.config.endpointOverlayId);
|
||||
|
||||
map.setSuspendDrawing(false, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Overlay options (all available map options shown in overlay)
|
||||
* "active": (active || hover) indicated whether an icon/option
|
||||
* is marked as "active".
|
||||
* "active": Makes icon active when visible
|
||||
* "hover": Make icon active on hover
|
||||
*/
|
||||
let options = {
|
||||
filter: {
|
||||
title: 'active filter',
|
||||
trigger: 'active',
|
||||
class: 'pf-map-overlay-filter',
|
||||
iconClass: ['fas', 'fa-fw', 'fa-filter'],
|
||||
onClick: function(e){
|
||||
// clear all filter
|
||||
let mapElement = MapOverlayUtil.getMapElementFromOverlay(this);
|
||||
let map = getMapObjectFromOverlayIcon(this);
|
||||
|
||||
MapUtil.storeLocalData('map', mapElement.data('id'), 'filterScopes', []);
|
||||
MapUtil.filterMapByScopes(map, []);
|
||||
}
|
||||
},
|
||||
mapSnapToGrid: {
|
||||
title: 'active grid',
|
||||
trigger: 'active',
|
||||
class: 'pf-map-overlay-grid',
|
||||
iconClass: ['fas', 'fa-fw', 'fa-th']
|
||||
},
|
||||
mapMagnetizer: {
|
||||
title: 'active magnetizer',
|
||||
trigger: 'active',
|
||||
class: 'pf-map-overlay-magnetizer',
|
||||
iconClass: ['fas', 'fa-fw', 'fa-magnet']
|
||||
},
|
||||
systemRegion: {
|
||||
title: 'show regions',
|
||||
trigger: 'hover',
|
||||
class: 'pf-map-overlay-region',
|
||||
iconClass: ['fas', 'fa-fw', 'fa-tags'],
|
||||
hoverIntent: {
|
||||
over: function(e){
|
||||
let mapElement = MapOverlayUtil.getMapElementFromOverlay(this);
|
||||
mapElement.find('.' + MapOverlayUtil.config.systemHeadClass).each(function(){
|
||||
let systemHead = $(this);
|
||||
// init popover if not already exists
|
||||
if(!systemHead.data('bs.popover')){
|
||||
let system = systemHead.parent();
|
||||
let systemData = system.data();
|
||||
systemHead.popover({
|
||||
placement: 'bottom',
|
||||
html: true,
|
||||
trigger: 'manual',
|
||||
container: mapElement,
|
||||
title: false,
|
||||
content: Util.getSystemRegionTable(
|
||||
Util.getObjVal(systemData, 'region'),
|
||||
Util.getObjVal(systemData, 'sovereignty')
|
||||
)
|
||||
});
|
||||
}
|
||||
systemHead.setPopoverSmall();
|
||||
systemHead.popover('show');
|
||||
});
|
||||
},
|
||||
out: function(e){
|
||||
let mapElement = MapOverlayUtil.getMapElementFromOverlay(this);
|
||||
mapElement.find('.' + MapOverlayUtil.config.systemHeadClass).popover('hide');
|
||||
}
|
||||
}
|
||||
},
|
||||
mapSignatureOverlays: {
|
||||
title: 'active signature overlays',
|
||||
trigger: 'active',
|
||||
class: 'pf-map-overlay-endpoint',
|
||||
iconClass: ['fas', 'fa-fw', 'fa-link']
|
||||
},
|
||||
mapCompact: {
|
||||
title: 'compact layout',
|
||||
trigger: 'active',
|
||||
class: 'pf-map-overlay-compact',
|
||||
iconClass: ['fas', 'fa-fw', 'fa-compress']
|
||||
},
|
||||
connection: {
|
||||
title: 'WH data',
|
||||
trigger: 'hover',
|
||||
class: 'pf-map-overlay-connection-wh',
|
||||
iconClass: ['fas', 'fa-fw', 'fa-fighter-jet'],
|
||||
hoverIntent: {
|
||||
over: function(e){
|
||||
let map = getMapObjectFromOverlayIcon(this);
|
||||
let connections = MapUtil.searchConnectionsByScopeAndType(map, 'wh');
|
||||
let serverDate = Util.getServerTime();
|
||||
|
||||
// show connection overlays -----------------------------------------------------------------------
|
||||
for(let connection of connections){
|
||||
let createdTimestamp = connection.getParameter('created');
|
||||
let updatedTimestamp = connection.getParameter('updated');
|
||||
|
||||
let createdDate = Util.convertTimestampToServerTime(createdTimestamp);
|
||||
let updatedDate = Util.convertTimestampToServerTime(updatedTimestamp);
|
||||
|
||||
let createdDiff = Util.getTimeDiffParts(createdDate, serverDate);
|
||||
let updatedDiff = Util.getTimeDiffParts(updatedDate, serverDate);
|
||||
|
||||
// format overlay label
|
||||
let labels = [
|
||||
Util.formatTimeParts(createdDiff) + ' <i class="fas fa-fw fa-plus-square"></i>',
|
||||
Util.formatTimeParts(updatedDiff) + ' <i class="fas fa-fw fa-pen-square"></i>'
|
||||
];
|
||||
|
||||
// add label overlay --------------------------------------------------------------------------
|
||||
connection.addOverlay([
|
||||
'Label',
|
||||
{
|
||||
label: labels.join('<br>'),
|
||||
id: MapOverlayUtil.config.connectionOverlayWhId,
|
||||
cssClass: [MapOverlayUtil.config.componentOverlayClass, 'small', 'text-right'].join(' '),
|
||||
location: 0.35
|
||||
}
|
||||
]);
|
||||
}
|
||||
},
|
||||
out: function(e){
|
||||
let map = getMapObjectFromOverlayIcon(this);
|
||||
let connections = MapUtil.searchConnectionsByScopeAndType(map, 'wh');
|
||||
|
||||
for(let connection of connections){
|
||||
connection.removeOverlay(MapOverlayUtil.config.connectionOverlayWhId);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
connectionEol: {
|
||||
title: 'EOL timer',
|
||||
trigger: 'hover',
|
||||
class: 'pf-map-overlay-connection-eol',
|
||||
iconClass: ['fas', 'fa-fw', 'fa-hourglass-end'],
|
||||
hoverIntent: {
|
||||
over: function(e){
|
||||
let map = getMapObjectFromOverlayIcon(this);
|
||||
let connections = MapUtil.searchConnectionsByScopeAndType(map, 'wh', ['wh_eol']);
|
||||
let serverDate = Util.getServerTime();
|
||||
|
||||
for(let connection of connections){
|
||||
let eolTimestamp = connection.getParameter('eolUpdated');
|
||||
let eolDate = Util.convertTimestampToServerTime(eolTimestamp);
|
||||
let diff = Util.getTimeDiffParts(eolDate, serverDate);
|
||||
|
||||
connection.addOverlay([
|
||||
'Label',
|
||||
{
|
||||
label: '<i class="fas fa-fw fa-hourglass-end"></i> ' + Util.formatTimeParts(diff),
|
||||
id: MapOverlayUtil.config.connectionOverlayEolId,
|
||||
cssClass: [MapOverlayUtil.config.componentOverlayClass, 'eol'].join(' '),
|
||||
location: 0.25
|
||||
}
|
||||
]);
|
||||
}
|
||||
},
|
||||
out: function(e){
|
||||
let map = getMapObjectFromOverlayIcon(this);
|
||||
let connections = MapUtil.searchConnectionsByScopeAndType(map, 'wh', ['wh_eol']);
|
||||
|
||||
for(let connection of connections){
|
||||
connection.removeOverlay(MapOverlayUtil.config.connectionOverlayEolId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* draws the map update counter to the map overlay timer
|
||||
* @param mapOverlayTimer
|
||||
* @param percent
|
||||
* @param value
|
||||
*/
|
||||
let setMapUpdateCounter = (mapOverlayTimer, percent, value = '') => {
|
||||
// check if counter already exists
|
||||
if(!MapOverlayUtil.getMapCounter(mapOverlayTimer)){
|
||||
// create new counter
|
||||
let chartEl = Object.assign(document.createElement('div'), {
|
||||
className: `${Init.classes.pieChart.class} ${Init.classes.pieChart.pieChartMapCounterClass}`
|
||||
});
|
||||
|
||||
let chartInnerEl = Object.assign(document.createElement('span'), {
|
||||
textContent: value
|
||||
});
|
||||
let iconEl = Object.assign(document.createElement('i'), {
|
||||
className: ['fas', 'fa-fw', 'fa-lock'].join(' ')
|
||||
});
|
||||
|
||||
chartInnerEl.append(iconEl);
|
||||
chartEl.append(chartInnerEl);
|
||||
mapOverlayTimer.append(chartEl);
|
||||
|
||||
// init counter
|
||||
$(chartEl).initMapUpdateCounter();
|
||||
|
||||
// set tooltip
|
||||
mapOverlayTimer.dataset.placement = 'left';
|
||||
mapOverlayTimer.setAttribute('title', 'update counter');
|
||||
$(mapOverlayTimer).tooltip();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* start the map update counter or reset
|
||||
*/
|
||||
$.fn.startMapUpdateCounter = function(){
|
||||
if(!this.length){
|
||||
console.warn('startMapUpdateCounter() failed. Missing DOM node');
|
||||
return;
|
||||
}
|
||||
let mapOverlayTimer = this[0];
|
||||
let counterChart = MapOverlayUtil.getMapCounter(mapOverlayTimer);
|
||||
let pieChart = $(counterChart).data('easyPieChart');
|
||||
|
||||
if(!pieChart){
|
||||
console.warn('startMapUpdateCounter() failed. easyPieChart not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
let updateChart = (percent = 0) => {
|
||||
if(pieChart){
|
||||
pieChart.update(percent);
|
||||
}
|
||||
};
|
||||
|
||||
let task = counterChart.getData('counterTask');
|
||||
if(!task){
|
||||
let tabContentEl = mapOverlayTimer.closest(`.${Util.config.mapTabContentClass}`);
|
||||
let mapId = parseInt(tabContentEl.dataset.mapId) || 0;
|
||||
task = Cron.new(`mapUpdateCounter_${mapId}`, {
|
||||
precision: 'secondTenths',
|
||||
isParallel: true,
|
||||
targetRunCount: 10 * MapOverlayUtil.config.logTimerCount
|
||||
});
|
||||
|
||||
task.task = (timer, task) => {
|
||||
// debounce 80% (reduce repaint)
|
||||
if(task.runCount % 5 === 0){
|
||||
let progress = Math.round(task.targetProgress);
|
||||
updateChart(100 - progress);
|
||||
}
|
||||
|
||||
if(task.targetAchieved){
|
||||
$(mapOverlayTimer).velocity('transition.whirlOut', {
|
||||
duration: Init.animationSpeed.mapOverlay,
|
||||
complete: function(){
|
||||
MapOverlayUtil.getMapElementFromOverlay(mapOverlayTimer).trigger('pf:unlocked');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
counterChart.setData('counterTask', task);
|
||||
}
|
||||
|
||||
// task is not connected if: 'targetAchieved' or not started
|
||||
if(!task.isConnected()){
|
||||
$(mapOverlayTimer).velocity('stop').velocity('transition.whirlIn', { duration: Init.animationSpeed.mapOverlay });
|
||||
}
|
||||
updateChart(100);
|
||||
task.reset();
|
||||
task.start();
|
||||
};
|
||||
|
||||
/**
|
||||
* update (show/hide) a overlay icon in the "info"-overlay
|
||||
* show/hide the overlay itself is no icons are visible
|
||||
* @param option
|
||||
* @param viewType
|
||||
*/
|
||||
$.fn.updateOverlayIcon = function(option, viewType){
|
||||
let mapOverlayInfo = $(this);
|
||||
|
||||
let showOverlay = false;
|
||||
|
||||
let mapOverlayIconClass = options[option].class;
|
||||
|
||||
// look for the overlay icon that should be updated
|
||||
let iconElement = mapOverlayInfo.find('.' + mapOverlayIconClass);
|
||||
|
||||
if(iconElement){
|
||||
if(viewType === 'show'){
|
||||
showOverlay = true;
|
||||
|
||||
// check "trigger" and mark as "active"
|
||||
if(
|
||||
options[option].trigger === 'active' ||
|
||||
options[option].trigger === 'refresh'
|
||||
){
|
||||
iconElement.addClass('active');
|
||||
}
|
||||
|
||||
// check if icon is not already visible
|
||||
// -> prevents unnecessary "show" animation
|
||||
if( !iconElement.data('visible') ){
|
||||
// display animation for icon
|
||||
iconElement.velocity({
|
||||
opacity: [0.8, 0],
|
||||
scale: [1, 0],
|
||||
width: ['20px', 0],
|
||||
height: ['20px', 0],
|
||||
marginRight: ['10px', 0]
|
||||
},{
|
||||
duration: 240,
|
||||
easing: 'easeInOutQuad'
|
||||
});
|
||||
|
||||
iconElement.data('visible', true);
|
||||
}
|
||||
}else if(viewType === 'hide'){
|
||||
// check if icon is not already visible
|
||||
// -> prevents unnecessary "hide" animation
|
||||
if(iconElement.data('visible')){
|
||||
iconElement.removeClass('active').velocity('reverse');
|
||||
iconElement.data('visible', false);
|
||||
}
|
||||
|
||||
// check if there is any visible icon remaining
|
||||
let visibleIcons = mapOverlayInfo.find('i:visible');
|
||||
if(visibleIcons.length > 0){
|
||||
showOverlay = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// show the entire overlay if there is at least one active icon
|
||||
if(
|
||||
showOverlay === true &&
|
||||
mapOverlayInfo.is(':hidden')
|
||||
){
|
||||
// show overlay
|
||||
mapOverlayInfo.velocity('stop').velocity('transition.whirlIn', { duration: Init.animationSpeed.mapOverlay });
|
||||
}else if(
|
||||
showOverlay === false &&
|
||||
mapOverlayInfo.is(':visible')
|
||||
){
|
||||
// hide overlay
|
||||
mapOverlayInfo.velocity('stop').velocity('transition.whirlOut', { duration: Init.animationSpeed.mapOverlay });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* update map zoom overlay information
|
||||
* @param map
|
||||
*/
|
||||
let updateZoomOverlay = map => {
|
||||
let zoom = map.getZoom();
|
||||
let zoomPercent = Math.round(zoom * 1000) / 10;
|
||||
let zoomOverlay = MapOverlayUtil.getMapOverlay(map.getContainer(), 'zoom');
|
||||
let zoomValue = zoomOverlay.find('.' + MapOverlayUtil.config.zoomOverlayValueClass);
|
||||
let zoomUp = zoomOverlay.find('.' + MapOverlayUtil.config.zoomOverlayUpClass);
|
||||
let zoomDown = zoomOverlay.find('.' + MapOverlayUtil.config.zoomOverlayDownClass);
|
||||
zoomValue.toggleClass('active', zoom !== 1).text(zoomPercent);
|
||||
zoomUp.toggleClass('disabled', zoom >= MapUtil.config.zoomMax);
|
||||
zoomDown.toggleClass('disabled', zoom <= MapUtil.config.zoomMin);
|
||||
};
|
||||
|
||||
/**
|
||||
* map debug overlays for connections/endpoints
|
||||
* -> requires manual added "debug" GET param to URL
|
||||
* @param map
|
||||
*/
|
||||
let initMapDebugOverlays = map => {
|
||||
let url = new URL(window.location.href);
|
||||
if(url.searchParams.has('debug')){
|
||||
let mapContainer = $(map.getContainer());
|
||||
|
||||
// debug map overlays for connection/endpoints
|
||||
mapContainer.on('mouseover', '.jtk-connector', function(e){
|
||||
e.stopPropagation();
|
||||
|
||||
if(e.target.classList.contains('jtk-connector-outline')){
|
||||
let connection = e.currentTarget._jsPlumb;
|
||||
// show debug overlay only if there is no active debug
|
||||
if(!connection.getOverlay(MapOverlayUtil.config.debugOverlayId)){
|
||||
// find nearby connections
|
||||
let connections = [];
|
||||
let endpoints = [];
|
||||
let hasComponentId = id => {
|
||||
return component => component.id === id;
|
||||
};
|
||||
|
||||
for(let endpoint of connection.endpoints){
|
||||
let connectionsInfo = map.anchorManager.getConnectionsFor(endpoint.elementId);
|
||||
for(let connectionInfo of connectionsInfo){
|
||||
if(!connections.some(hasComponentId(connectionInfo[0].id))){
|
||||
connections.push(connectionInfo[0]);
|
||||
for(let endpointTemp of connectionInfo[0].endpoints){
|
||||
if(!endpoints.some(hasComponentId(endpointTemp.id))){
|
||||
endpoints.push(endpointTemp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let createConnectionOverlay = connection => {
|
||||
let data = MapUtil.getDataByConnection(connection);
|
||||
|
||||
let html = '<div><table>';
|
||||
html += '<tr><td>' + connection.id + '</td><td class="text-right">' + data.id + '</td></tr>';
|
||||
html += '<tr><td>Scope:</td><td class="text-right">' + data.scope + '</td></tr>';
|
||||
html += '<tr><td>Type:</td><td class="text-right">' + data.type.toString() + '</td></tr>';
|
||||
html += '</table></div>';
|
||||
|
||||
return $(html).on('click', function(){
|
||||
console.info(connection);
|
||||
});
|
||||
};
|
||||
|
||||
for(let connection of connections){
|
||||
connection.addOverlay([
|
||||
'Custom',
|
||||
{
|
||||
id: MapOverlayUtil.config.debugOverlayId,
|
||||
cssClass: [MapOverlayUtil.config.componentOverlayClass, 'debug'].join(' '),
|
||||
create: createConnectionOverlay
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
let createEndpointOverlay = endpoint => {
|
||||
let types = MapUtil.filterDefaultTypes(endpoint.getType());
|
||||
|
||||
let html = '<div><table>';
|
||||
html += '<tr><td>' + endpoint.id + '</td><td class="text-right"></td></tr>';
|
||||
html += '<tr><td>Scope:</td><td class="text-right">' + endpoint.scope + '</td></tr>';
|
||||
html += '<tr><td>Type:</td><td class="text-right">' + types.toString() + '</td></tr>';
|
||||
html += '</table></div>';
|
||||
|
||||
return $(html).on('click', function(){
|
||||
console.info(endpoint);
|
||||
});
|
||||
};
|
||||
|
||||
for(let endpoint of endpoints){
|
||||
endpoint.addOverlay([
|
||||
'Custom',
|
||||
{
|
||||
id: MapOverlayUtil.config.debugOverlayId,
|
||||
cssClass: [MapOverlayUtil.config.componentOverlayClass, 'debug'].join(' '),
|
||||
create: createEndpointOverlay
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mapContainer.on('mouseover', function(e){
|
||||
e.stopPropagation();
|
||||
if(e.target === e.currentTarget){
|
||||
map.select().removeOverlay(MapOverlayUtil.config.debugOverlayId);
|
||||
map.selectEndpoints().removeOverlay(MapOverlayUtil.config.debugOverlayId);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* init map zoom overlay
|
||||
* @returns {jQuery}
|
||||
*/
|
||||
let initZoomOverlay = () => {
|
||||
let clickHandler = e => {
|
||||
let zoomIcon = $(e.target);
|
||||
if(!zoomIcon.hasClass('disabled')){
|
||||
let zoomAction = zoomIcon.attr('data-zoom');
|
||||
let map = getMapObjectFromOverlayIcon(zoomIcon);
|
||||
MapUtil.changeZoom(map, zoomAction);
|
||||
}
|
||||
};
|
||||
|
||||
return $('<div>', {
|
||||
class: [MapOverlayUtil.config.mapOverlayClass, MapOverlayUtil.config.mapOverlayZoomClass].join(' ')
|
||||
}).append(
|
||||
$('<i>', {
|
||||
class: ['fas', 'fa-caret-up', MapOverlayUtil.config.zoomOverlayUpClass].join(' ')
|
||||
}).attr('data-zoom', 'up').on('click', clickHandler),
|
||||
$('<span>', {
|
||||
class: MapOverlayUtil.config.zoomOverlayValueClass,
|
||||
text: '100'
|
||||
}),
|
||||
$('<i>', {
|
||||
class: ['fas', 'fa-caret-down', MapOverlayUtil.config.zoomOverlayDownClass].join(' ')
|
||||
}).attr('data-zoom', 'down').on('click', clickHandler)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* init all map overlays on a "parent" element
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initMapOverlays = function(){
|
||||
return this.each(function(){
|
||||
let parentElement = $(this);
|
||||
|
||||
let mapOverlayTimer = $('<div>', {
|
||||
class: [MapOverlayUtil.config.mapOverlayClass, MapOverlayUtil.config.mapOverlayTimerClass].join(' ')
|
||||
});
|
||||
parentElement.append(mapOverlayTimer);
|
||||
|
||||
|
||||
parentElement.append(initZoomOverlay());
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------
|
||||
// add map overlay info. after scrollbar is initialized
|
||||
let mapOverlayInfo = $('<div>', {
|
||||
class: [MapOverlayUtil.config.mapOverlayClass, MapOverlayUtil.config.mapOverlayInfoClass].join(' ')
|
||||
});
|
||||
|
||||
// add all overlay elements
|
||||
Object.entries(options).forEach(([key, option]) => {
|
||||
let icon = $('<i>', {
|
||||
class: option.iconClass.concat(['pull-right', option.class]).join(' ')
|
||||
}).attr('title', option.title).tooltip({
|
||||
placement: 'bottom',
|
||||
container: 'body',
|
||||
delay: 150
|
||||
});
|
||||
|
||||
// add "hover" action for some icons
|
||||
if(
|
||||
option.trigger === 'hover' ||
|
||||
option.trigger === 'refresh'
|
||||
){
|
||||
icon.hoverIntent(option.hoverIntent);
|
||||
}
|
||||
|
||||
// add "click" handler for some icons
|
||||
if(option.hasOwnProperty('onClick')){
|
||||
icon.on('click', option.onClick);
|
||||
}
|
||||
|
||||
mapOverlayInfo.append(icon);
|
||||
});
|
||||
|
||||
parentElement.append(mapOverlayInfo);
|
||||
|
||||
// reset map update timer
|
||||
setMapUpdateCounter(mapOverlayTimer[0], 100);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
showInfoSignatureOverlays: showInfoSignatureOverlays,
|
||||
hideInfoSignatureOverlays: hideInfoSignatureOverlays,
|
||||
updateZoomOverlay: updateZoomOverlay,
|
||||
initMapDebugOverlays: initMapDebugOverlays
|
||||
};
|
||||
});
|
||||
@@ -1,115 +0,0 @@
|
||||
/**
|
||||
* map overlay util functions
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/map/util'
|
||||
], ($, Init, Util) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
logTimerCount: 3, // map log timer in seconds
|
||||
|
||||
// map overlays sections
|
||||
mapOverlayClass: 'pf-map-overlay', // class for all map overlays
|
||||
mapOverlayTimerClass: 'pf-map-overlay-timer', // class for map overlay timer e.g. map timer
|
||||
mapOverlayZoomClass: 'pf-map-overlay-zoom', // class for map overlay zoom
|
||||
mapOverlayInfoClass: 'pf-map-overlay-info', // class for map overlay info e.g. map info
|
||||
overlayLocalClass: 'pf-map-overlay-local', // class for map overlay "local" table
|
||||
|
||||
// system
|
||||
systemHeadClass: 'pf-system-head', // class for system head
|
||||
|
||||
// connection overlay ids (they are not unique like CSS ids!)
|
||||
connectionOverlayArrowId: 'pf-map-connection-arrow-overlay', // connection Arrows overlay ID (jsPlumb)
|
||||
connectionOverlayWhId: 'pf-map-connection-wh-overlay', // connection WH overlay ID (jsPlumb)
|
||||
connectionOverlayEolId: 'pf-map-connection-eol-overlay', // connection EOL overlay ID (jsPlumb)
|
||||
|
||||
debugOverlayId: 'pf-map-debug-overlay', // connection/endpoint overlay ID (jsPlumb)
|
||||
|
||||
endpointOverlayId: 'pf-map-endpoint-overlay', // endpoint overlay ID (jsPlumb)
|
||||
|
||||
// connection overlay classes classes
|
||||
componentOverlayClass: 'pf-map-component-overlay', // class for "normal size" overlay
|
||||
|
||||
connectionArrowOverlaySuccessClass: 'pf-map-connection-arrow-overlay-success', // class for "success" arrow overlays
|
||||
connectionArrowOverlayDangerClass: 'pf-map-connection-arrow-overlay-danger', // class for "danger" arrow overlays
|
||||
|
||||
// zoom overlay
|
||||
zoomOverlayUpClass: 'pf-zoom-overlay-up',
|
||||
zoomOverlayDownClass: 'pf-zoom-overlay-down',
|
||||
zoomOverlayValueClass: 'pf-zoom-overlay-value'
|
||||
};
|
||||
|
||||
/**
|
||||
* get map overlay element by type e.g. timer/counter, info - overlay
|
||||
* @param element
|
||||
* @param overlayType
|
||||
* @returns {null}
|
||||
*/
|
||||
let getMapOverlay = (element, overlayType) => {
|
||||
let areaMap = $(element).closest('.' + Util.getMapTabContentAreaClass('map'));
|
||||
|
||||
let mapOverlay = null;
|
||||
switch(overlayType){
|
||||
case 'timer':
|
||||
mapOverlay = areaMap.find('.' + config.mapOverlayTimerClass);
|
||||
break;
|
||||
case 'info':
|
||||
mapOverlay = areaMap.find('.' + config.mapOverlayInfoClass);
|
||||
break;
|
||||
case 'zoom':
|
||||
mapOverlay = areaMap.find('.' + config.mapOverlayZoomClass);
|
||||
break;
|
||||
case 'local':
|
||||
mapOverlay = areaMap.find('.' + config.overlayLocalClass);
|
||||
break;
|
||||
}
|
||||
|
||||
return mapOverlay;
|
||||
};
|
||||
|
||||
/**
|
||||
* get mapElement from overlay or any child of that
|
||||
* @param mapOverlay
|
||||
* @returns {jQuery}
|
||||
*/
|
||||
let getMapElementFromOverlay = mapOverlay => {
|
||||
return $(mapOverlay).closest('.' + Util.getMapTabContentAreaClass('map')).find('.' + Util.config.mapClass);
|
||||
};
|
||||
|
||||
/**
|
||||
* get the map counter chart from overlay
|
||||
* @param element
|
||||
* @returns {Element}
|
||||
*/
|
||||
let getMapCounter = element => element.querySelector(`.${Init.classes.pieChart.pieChartMapCounterClass}`);
|
||||
|
||||
/**
|
||||
* if there is an "active" (connected) counter task
|
||||
* -> lock overlay
|
||||
* @param {HTMLElement} element
|
||||
* @returns {boolean}
|
||||
*/
|
||||
let isMapCounterOverlayActive = element => {
|
||||
let mapOverlay = getMapOverlay(element, 'timer');
|
||||
if(mapOverlay){
|
||||
let mapCounter = getMapCounter(mapOverlay[0]);
|
||||
if(mapCounter && mapCounter.getData('counterTask')){
|
||||
return mapCounter.getData('counterTask').isConnected();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return {
|
||||
config: config,
|
||||
getMapOverlay: getMapOverlay,
|
||||
getMapElementFromOverlay: getMapElementFromOverlay,
|
||||
getMapCounter: getMapCounter,
|
||||
isMapCounterOverlayActive: isMapCounterOverlayActive
|
||||
};
|
||||
});
|
||||
@@ -1,312 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'mousewheel',
|
||||
'customScrollbar'
|
||||
], ($) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
autoScrollClass: 'auto-scroll',
|
||||
autoScrollTopClass: 'auto-scroll-top',
|
||||
autoScrollLeftClass: 'auto-scroll-left',
|
||||
autoScrollBottomClass: 'auto-scroll-bottom',
|
||||
autoScrollRightClass: 'auto-scroll-right',
|
||||
};
|
||||
|
||||
let defaultConfig = {
|
||||
axis: 'yx',
|
||||
theme: 'light-3',
|
||||
scrollInertia: 200,
|
||||
autoExpandScrollbar: false,
|
||||
scrollButtons: {
|
||||
enable: true,
|
||||
scrollAmount: 30,
|
||||
scrollType: 'stepless'
|
||||
},
|
||||
callbacks: {
|
||||
onTotalScrollOffset: 0,
|
||||
onTotalScrollBackOffset: 0,
|
||||
alwaysTriggerOffsets: true,
|
||||
onScroll: function(){
|
||||
if($(this).data('mCS').trigger === 'internal'){
|
||||
autoScrollOff(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
advanced: {
|
||||
autoUpdateTimeout: 120, // auto-update timeout (default: 60)
|
||||
updateOnContentResize: true,
|
||||
autoExpandHorizontalScroll: false, // on resize css scale() scroll content should not change
|
||||
//autoExpandHorizontalScroll: 2,
|
||||
autoScrollOnFocus: 'div',
|
||||
},
|
||||
mouseWheel: {
|
||||
enable: false, // scroll wheel currently disabled
|
||||
scrollAmount: 'auto',
|
||||
axis: 'x',
|
||||
preventDefault: true
|
||||
},
|
||||
keyboard: {
|
||||
enable: false, // not working with pathfinder "shortcuts"
|
||||
scrollType: 'stepless',
|
||||
scrollAmount: 'auto'
|
||||
},
|
||||
scrollbarPosition: 'inside',
|
||||
autoDraggerLength: true,
|
||||
autoHideScrollbar: false
|
||||
};
|
||||
|
||||
let defaultScrollToOptions = {
|
||||
scrollInertia: 2000,
|
||||
scrollEasing: 'easeInOutSmooth',
|
||||
timeout: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* init map scrollbar
|
||||
* @param scrollWrapper
|
||||
* @param customConfig
|
||||
*/
|
||||
let initScrollbar = (scrollWrapper, customConfig) => {
|
||||
customConfig = $.extend(true, {}, defaultConfig, customConfig);
|
||||
// wrap callbacks -> callbacks from defaultConfig should run first
|
||||
customConfig.callbacks = wrapObjectFunctions(customConfig.callbacks, defaultConfig.callbacks);
|
||||
scrollWrapper.mCustomScrollbar(customConfig);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param scrollWrapper
|
||||
* @param position
|
||||
* @param options
|
||||
*/
|
||||
let autoScroll = (scrollWrapper, position, options) => {
|
||||
if(position.some(position => position !== null)){
|
||||
// scroll position -> start auto scroll
|
||||
autoScrollOn(scrollWrapper, position, options);
|
||||
}else{
|
||||
// no scroll position -> stop auto scroll
|
||||
autoScrollOff(scrollWrapper);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param scrollWrapper
|
||||
* @param position
|
||||
* @param options
|
||||
*/
|
||||
let autoScrollOn = (scrollWrapper, position, options) => {
|
||||
let scrollToOptions = Object.assign({}, defaultScrollToOptions, options);
|
||||
let scrollInertia = 0;
|
||||
let autoScrollClasses = [];
|
||||
['top', 'left', 'bottom', 'right'].forEach((direction, i) => {
|
||||
if(position.includes(direction)){
|
||||
autoScrollClasses.push(config['autoScroll' + direction.capitalize() + 'Class']);
|
||||
if(i % 2){ // left || right
|
||||
scrollInertia = scrollToOptions.scrollInertia * scrollWrapper.mcs.leftPct / 100;
|
||||
}else{ // top || bottom
|
||||
scrollInertia = scrollToOptions.scrollInertia * scrollWrapper.mcs.topPct / 100;
|
||||
}
|
||||
if(i === 2 || i === 3){ // bottom || right
|
||||
scrollInertia = scrollToOptions.scrollInertia - scrollInertia;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(autoScrollClasses.length){
|
||||
// scroll position -> check if scroll direction changed
|
||||
let compareClasses = getAutoScrollClasses();
|
||||
let currentClasses = [...scrollWrapper.classList].filter(cls => compareClasses.includes(cls));
|
||||
let newClasses = autoScrollClasses.diff(currentClasses);
|
||||
let oldClasses = currentClasses.diff(autoScrollClasses);
|
||||
|
||||
if(newClasses.length || oldClasses.length){
|
||||
// changed scroll direction (e.g. null -> y; y -> x; y -> xy, xy -> null)
|
||||
// -> stop current autos scroll and start with new scroll direction
|
||||
autoScrollOff(scrollWrapper, oldClasses);
|
||||
|
||||
scrollWrapper.classList.add(...newClasses);
|
||||
scrollToOptions.scrollInertia = scrollInertia;
|
||||
$(scrollWrapper).mCustomScrollbar('scrollTo', position, scrollToOptions);
|
||||
}
|
||||
}else{
|
||||
// no scroll position -> stop auto scroll
|
||||
autoScrollOff(scrollWrapper);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param scrollWrapper
|
||||
* @param classes
|
||||
*/
|
||||
let autoScrollOff = (scrollWrapper, classes) => {
|
||||
classes = classes || getAutoScrollClasses();
|
||||
scrollWrapper.classList.remove(...classes);
|
||||
$(scrollWrapper).mCustomScrollbar('stop');
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {[string, string, string, string]}
|
||||
*/
|
||||
let getAutoScrollClasses = () => {
|
||||
return [config.autoScrollTopClass, config.autoScrollLeftClass, config.autoScrollBottomClass, config.autoScrollRightClass];
|
||||
};
|
||||
|
||||
/**
|
||||
* get mCustomScrollbar container
|
||||
* @param element
|
||||
* @returns {*|[]}
|
||||
*/
|
||||
let getContainer = element => element.parents('.mCSB_container');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param container
|
||||
* @param element
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
let getElementPos = (container, element) => {
|
||||
return {
|
||||
x: element.offset().left - container.offset().left,
|
||||
y: element.offset().top - container.offset().top
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param element
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
let getElementDim = element => {
|
||||
return {
|
||||
x: element.outerWidth(false),
|
||||
y: element.outerHeight(false)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* check if an element is 100% visible
|
||||
* -> scrolled into viewport
|
||||
* @param element
|
||||
* @returns {boolean}
|
||||
*/
|
||||
let isInView = element => {
|
||||
let container = getContainer(element);
|
||||
let wrapper = container.parent();
|
||||
let cPos = {x: container[0].offsetLeft, y: container[0].offsetTop};
|
||||
let ePos = getElementPos(container, element);
|
||||
let eDim = getElementDim(element);
|
||||
|
||||
return cPos.y + ePos.y >= 0 &&
|
||||
cPos.y + ePos.y < wrapper.height() - eDim.y &&
|
||||
cPos.x + ePos.x >= 0 &&
|
||||
cPos.x + ePos.x < wrapper.width() - eDim.x;
|
||||
};
|
||||
|
||||
/**
|
||||
* get new scrollTo coordinates to center element in viewport
|
||||
* @param element
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
let getCenterScrollPosition = element => {
|
||||
let container = getContainer(element);
|
||||
let wrapper = container.parent();
|
||||
let cDim = getElementDim(container);
|
||||
let wDim = {x: wrapper.width(), y: wrapper.height()};
|
||||
let eDim = getElementDim(element);
|
||||
let ePos = getElementPos(container, element);
|
||||
|
||||
let eOff = {
|
||||
x: (-wDim.x / 2) + (eDim.x / 2),
|
||||
y: (-wDim.y / 2) + (eDim.y / 2)
|
||||
};
|
||||
|
||||
return adjustPos(addOffset(ePos, eOff), cDim);
|
||||
};
|
||||
|
||||
/**
|
||||
* scroll to a specific position on map
|
||||
* demo: http://manos.malihu.gr/repository/custom-scrollbar/demo/examples/scrollTo_demo.html
|
||||
* @param scrollArea
|
||||
* @param position
|
||||
* @param options
|
||||
*/
|
||||
let scrollToPosition = (scrollArea, position, options) => {
|
||||
$(scrollArea).mCustomScrollbar('scrollTo', position, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* scroll to center an element
|
||||
* -> subtract some offset for tooltips/connections
|
||||
* @param scrollArea
|
||||
* @param element
|
||||
*/
|
||||
let scrollToCenter = (scrollArea, element) => {
|
||||
// no scroll if element is already FULL visible in scrollable viewport
|
||||
if(!isInView(element)){
|
||||
// get scrollTo position for centered element
|
||||
scrollToPosition(scrollArea, getCenterScrollPosition(element));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* add/subtract offset coordinates from position
|
||||
* @param {{x: number, y: number}} position
|
||||
* @param {{x: number, y: number}} offset
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
let addOffset = (position, offset) => mapObject(position, (v, k) => v + offset[k]);
|
||||
|
||||
/**
|
||||
* round position
|
||||
* @param {{x: number, y: number}} position
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
let roundPos = position => mapObject(position, Math.round);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{x: number, y: number}} position
|
||||
* @param {{x: number, y: number}} dimension
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
let adjustPos = (position, dimension) => mapObject(roundPos(position), (v, k) => Math.max(1, Math.min(dimension[k], v)) );
|
||||
|
||||
/**
|
||||
* wrap functions that exists in both objects (same key)
|
||||
* -> o2 fkt run 1st (returned value ignored)
|
||||
* -> o1 fkt run 2nd
|
||||
* @param o1
|
||||
* @param o2
|
||||
* @returns {any}
|
||||
*/
|
||||
let wrapObjectFunctions = (o1 = {}, o2 = {}) => {
|
||||
return mapObject(o1, function(v1, k1){
|
||||
// check both obj has the same key and are functions
|
||||
if([v1, o2[k1]].every(v => typeof v === 'function')){
|
||||
return function(...args){
|
||||
// run 'default' fkt first, then orig fkt
|
||||
o2[k1].apply(this, ...args);
|
||||
return v1.apply(this, ...args);
|
||||
};
|
||||
}
|
||||
return v1;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* like Array.map() for objects
|
||||
* -> callback f is called for each property
|
||||
* @see https://stackoverflow.com/a/38829074/4329969
|
||||
* @param o
|
||||
* @param f
|
||||
* @returns {Object}
|
||||
*/
|
||||
let mapObject = (o, f) => Object.assign(...Object.entries(o).map(([k, v]) => ({[k]: f(v, k) })));
|
||||
|
||||
return {
|
||||
initScrollbar: initScrollbar,
|
||||
scrollToPosition: scrollToPosition,
|
||||
scrollToCenter: scrollToCenter,
|
||||
autoScroll: autoScroll
|
||||
};
|
||||
});
|
||||
@@ -1,821 +0,0 @@
|
||||
/**
|
||||
* map system functions
|
||||
*/
|
||||
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox',
|
||||
'app/map/util',
|
||||
'app/map/magnetizing'
|
||||
], ($, Init, Util, bootbox, MapUtil, Magnetizer) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
newSystemOffset: {
|
||||
x: 130,
|
||||
y: 0
|
||||
},
|
||||
|
||||
systemHeadInfoClass: 'pf-system-head-info', // class for system info
|
||||
systemHeadInfoLeftClass: 'pf-system-head-info-left', // class for left system info
|
||||
systemHeadInfoRightClass: 'pf-system-head-info-right', // class for right system info
|
||||
|
||||
systemTooltipInnerIdPrefix: 'pf-system-tooltip-inner-', // id prefix for system tooltip content
|
||||
systemTooltipInnerClass: 'pf-system-tooltip-inner', // class for system tooltip content
|
||||
|
||||
// dialogs
|
||||
dialogSystemId: 'pf-system-dialog', // id for system dialog
|
||||
dialogSystemSelectClass: 'pf-system-dialog-select', // class for system select element
|
||||
dialogSystemStatusSelectId: 'pf-system-dialog-status-select', // id for "status" select
|
||||
dialogSystemLockId: 'pf-system-dialog-lock', // id for "locked" checkbox
|
||||
dialogSystemRallyId: 'pf-system-dialog-rally', // id for "rally" checkbox
|
||||
|
||||
dialogSystemSectionInfoId: 'pf-system-dialog-section-info', // id for "info" section element
|
||||
dialogSystemSectionInfoStatusId: 'pf-system-dialog-section-info-status', // id for "status" message in "info" element
|
||||
dialogSystemAliasId: 'pf-system-dialog-alias', // id for "alias" static element
|
||||
dialogSystemSignaturesId: 'pf-system-dialog-signatures', // id for "signatures" count static element
|
||||
dialogSystemDescriptionId: 'pf-system-dialog-description', // id for "description" static element
|
||||
dialogSystemCreatedId: 'pf-system-dialog-created', // id for "created" static element
|
||||
dialogSystemUpdatedId: 'pf-system-dialog-updated', // id for "updated" static element
|
||||
|
||||
dialogRallyId: 'pf-rally-dialog', // id for "Rally point" dialog
|
||||
dialogRallyPokeDesktopId: 'pf-rally-dialog-poke-desktop', // id for "desktop" poke checkbox
|
||||
dialogRallyPokeSlackId: 'pf-rally-dialog-poke-slack', // id for "Slack" poke checkbox
|
||||
dialogRallyPokeDiscordId: 'pf-rally-dialog-poke-discord', // id for "Discord" poke checkbox
|
||||
dialogRallyPokeMailId: 'pf-rally-dialog-poke-mail', // id for "mail" poke checkbox
|
||||
dialogRallyMessageId: 'pf-rally-dialog-message', // id for "message" textarea
|
||||
|
||||
dialogRallyMessageDefault: '' +
|
||||
'I need some help!\n\n' +
|
||||
'- Potential PvP options around\n' +
|
||||
'- DPS and Logistic ships needed'
|
||||
};
|
||||
|
||||
/**
|
||||
* open "new system" dialog and add the system to map
|
||||
* optional the new system is connected to a "sourceSystem" (if available)
|
||||
* @param map
|
||||
* @param options
|
||||
* @param callback
|
||||
*/
|
||||
let showNewSystemDialog = (map, options, callback) => {
|
||||
let mapContainer = $(map.getContainer());
|
||||
let mapId = mapContainer.data('id');
|
||||
|
||||
/**
|
||||
* update new system dialog with some "additional" data
|
||||
* -> if system was mapped before
|
||||
* @param dialogElement
|
||||
* @param systemData
|
||||
*/
|
||||
let updateDialog = (dialogElement, systemData = null) => {
|
||||
let labelEmpty = '<span class="editable-empty">empty</span>';
|
||||
let labelUnknown = '<span class="editable-empty">unknown</span>';
|
||||
let labelExist = '<span class="txt-color txt-color-success">loaded</span>';
|
||||
|
||||
let showInfoHeadline = 'fadeOut';
|
||||
let showInfoSection = 'hide';
|
||||
let info = labelEmpty;
|
||||
|
||||
let statusId = false; // -> no value change
|
||||
let alias = labelEmpty;
|
||||
let signaturesCount = 0;
|
||||
let description = labelEmpty;
|
||||
let createdTime = labelUnknown;
|
||||
let updatedTime = labelUnknown;
|
||||
|
||||
if(systemData){
|
||||
// system data found for selected system
|
||||
showInfoHeadline = 'fadeIn';
|
||||
showInfoSection = 'show';
|
||||
info = labelExist;
|
||||
statusId = parseInt(Util.getObjVal(systemData, 'status.id')) || statusId;
|
||||
alias = systemData.alias.length ? Util.htmlEncode(systemData.alias) : alias;
|
||||
signaturesCount = (Util.getObjVal(systemData, 'signatures') || []).length;
|
||||
description = systemData.description.length ? systemData.description : description;
|
||||
|
||||
let dateCreated = new Date(systemData.created.created * 1000);
|
||||
let dateUpdated = new Date(systemData.updated.updated * 1000);
|
||||
let dateCreatedUTC = Util.convertDateToUTC(dateCreated);
|
||||
let dateUpdatedUTC = Util.convertDateToUTC(dateUpdated);
|
||||
|
||||
createdTime = Util.convertDateToString(dateCreatedUTC);
|
||||
updatedTime = Util.convertDateToString(dateUpdatedUTC);
|
||||
|
||||
}else if(systemData === null){
|
||||
// no system found for selected system
|
||||
showInfoHeadline = 'fadeIn';
|
||||
}
|
||||
|
||||
// update new system dialog with new default data
|
||||
dialogElement.find('#' + config.dialogSystemSectionInfoStatusId).html(info);
|
||||
if(statusId !== false){
|
||||
dialogElement.find('#' + config.dialogSystemStatusSelectId).val(statusId).trigger('change');
|
||||
}
|
||||
dialogElement.find('#' + config.dialogSystemAliasId).html(alias);
|
||||
dialogElement.find('#' + config.dialogSystemSignaturesId).toggleClass('txt-color-green', signaturesCount > 0).html(signaturesCount);
|
||||
dialogElement.find('#' + config.dialogSystemDescriptionId).html(description);
|
||||
dialogElement.find('#' + config.dialogSystemCreatedId).html('<i class="fas fa-fw fa-plus"></i> ' + createdTime);
|
||||
dialogElement.find('#' + config.dialogSystemUpdatedId).html('<i class="fas fa-fw fa-pen"></i> ' + updatedTime);
|
||||
dialogElement.find('[data-target="#' + config.dialogSystemSectionInfoId + '"]').velocity('stop').velocity(showInfoHeadline, {duration: 120});
|
||||
dialogElement.find('[data-type="spinner"]').removeClass('in');
|
||||
dialogElement.find('#' + config.dialogSystemSectionInfoId).collapse(showInfoSection);
|
||||
};
|
||||
|
||||
/**
|
||||
* request system data from server for persistent data -> update dialog
|
||||
* @param dialogElement
|
||||
* @param mapId
|
||||
* @param systemId
|
||||
*/
|
||||
let requestSystemData = (dialogElement, mapId, systemId) => {
|
||||
// show loading animation
|
||||
dialogElement.find('[data-type="spinner"]').addClass('in');
|
||||
|
||||
Util.request('GET', 'System', systemId, {mapId: mapId, isCcpId: 1}, {dialogElement: dialogElement})
|
||||
.then(payload => updateDialog(payload.context.dialogElement, payload.data))
|
||||
.catch(payload => updateDialog(payload.context.dialogElement));
|
||||
};
|
||||
|
||||
// format system status for form select -----------------------------------------------------------------------
|
||||
// "default" selection (id = 0) prevents status from being overwritten
|
||||
// -> e.g. keep status information if system was just inactive (active = 0)
|
||||
let statusData = [{id: 0, text: 'auto'}];
|
||||
|
||||
// get current map data ---------------------------------------------------------------------------------------
|
||||
let mapData = mapContainer.getMapDataFromClient(['hasId']);
|
||||
let mapSystems = mapData.data.systems;
|
||||
let mapSystemCount = mapSystems.length;
|
||||
let mapTypeName = mapContainer.data('typeName');
|
||||
let maxAllowedSystems = Init.mapTypes[mapTypeName].defaultConfig.max_systems;
|
||||
|
||||
// show error if system max count reached ---------------------------------------------------------------------
|
||||
if(mapSystemCount >= maxAllowedSystems){
|
||||
Util.showNotify({title: 'Max system count exceeded', text: 'Limit of ' + maxAllowedSystems + ' systems reached', type: 'warning'});
|
||||
return;
|
||||
}
|
||||
|
||||
// disable systems that are already on it ---------------------------------------------------------------------
|
||||
let mapSystemIds = mapSystems.map(systemData => systemData.systemId);
|
||||
|
||||
// dialog data ------------------------------------------------------------------------------------------------
|
||||
let data = {
|
||||
id: config.dialogSystemId,
|
||||
select2Class: Util.config.select2Class,
|
||||
systemSelectClass: config.dialogSystemSelectClass,
|
||||
statusSelectId: config.dialogSystemStatusSelectId,
|
||||
lockId: config.dialogSystemLockId,
|
||||
rallyId: config.dialogSystemRallyId,
|
||||
|
||||
sectionInfoId: config.dialogSystemSectionInfoId,
|
||||
sectionInfoStatusId: config.dialogSystemSectionInfoStatusId,
|
||||
aliasId: config.dialogSystemAliasId,
|
||||
signaturesId: config.dialogSystemSignaturesId,
|
||||
descriptionId: config.dialogSystemDescriptionId,
|
||||
createdId: config.dialogSystemCreatedId,
|
||||
updatedId: config.dialogSystemUpdatedId,
|
||||
statusData: statusData
|
||||
};
|
||||
|
||||
// check for pre-selected system ------------------------------------------------------------------------------
|
||||
let systemData;
|
||||
if(options.systemData){
|
||||
systemData = options.systemData;
|
||||
}else{
|
||||
// ... check for current active system (characterLog) -----------------------------------------------------
|
||||
let currentCharacterLog = Util.getCurrentCharacterData('log');
|
||||
if(currentCharacterLog !== false){
|
||||
// set system from 'characterLog' data as pre-selected system
|
||||
systemData = Util.getObjVal(currentCharacterLog, 'system');
|
||||
}
|
||||
}
|
||||
|
||||
// check if pre-selected system is NOT already on this map
|
||||
if(mapSystemIds.indexOf(Util.getObjVal(systemData, 'id')) === -1){
|
||||
data.currentSystem = systemData;
|
||||
}
|
||||
|
||||
requirejs(['text!templates/dialog/system.html', 'mustache'], (template, Mustache) => {
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
let systemDialog = bootbox.dialog({
|
||||
title: 'Add new system',
|
||||
message: content,
|
||||
show: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
className: 'btn-default'
|
||||
},
|
||||
success: {
|
||||
label: '<i class="fas fa-fw fa-check"></i> save',
|
||||
className: 'btn-success',
|
||||
callback: function(e){
|
||||
// get form Values
|
||||
let form = this.find('form');
|
||||
|
||||
let formData = $(form).getFormValues();
|
||||
|
||||
// validate form
|
||||
form.validator('validate');
|
||||
|
||||
// check whether the form is valid
|
||||
let formValid = form.isValidForm();
|
||||
|
||||
// don't close dialog on invalid data
|
||||
if(formValid === false) return false;
|
||||
|
||||
// calculate new system position ----------------------------------------------------------
|
||||
let newPosition;
|
||||
|
||||
// add new position
|
||||
let sourceSystem = null;
|
||||
let connectionData = null;
|
||||
if(options.sourceSystem !== undefined){
|
||||
// new position based on sourceSystem´s position
|
||||
sourceSystem = options.sourceSystem;
|
||||
connectionData = options.connectionData || null;
|
||||
|
||||
newPosition = newSystemPositionBySystem(sourceSystem);
|
||||
}else if(options.position){
|
||||
// new position based on coordinated (e.g. mouse event)
|
||||
newPosition = {
|
||||
x: options.position.x,
|
||||
y: options.position.y
|
||||
};
|
||||
}else{
|
||||
// new position based on current map scroll offset
|
||||
newPosition = MapUtil.newSystemPositionsByMapOffset(mapContainer)[0];
|
||||
}
|
||||
|
||||
formData.position = newPosition;
|
||||
formData.mapId = mapId;
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
|
||||
this.find('.modal-content').showLoadingAnimation();
|
||||
|
||||
Util.request('PUT', 'System', [], formData, {
|
||||
systemDialog: systemDialog,
|
||||
formElement: form,
|
||||
map: map,
|
||||
sourceSystem: sourceSystem,
|
||||
connectionData: connectionData
|
||||
}, context => {
|
||||
// always do
|
||||
context.systemDialog.find('.modal-content').hideLoadingAnimation();
|
||||
}).then(
|
||||
payload => {
|
||||
Util.showNotify({title: 'New system', text: payload.data.name, type: 'success'});
|
||||
|
||||
callback(payload.context.map, payload.data, payload.context.sourceSystem, payload.context.connectionData);
|
||||
bootbox.hideAll();
|
||||
},
|
||||
Util.handleAjaxErrorResponse
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
systemDialog.on('show.bs.modal', function(e){
|
||||
let dialogElement = $(this);
|
||||
|
||||
// init "status" select2 ------------------------------------------------------------------------------
|
||||
for(let [statusName, data] of Object.entries(Init.systemStatus)){
|
||||
statusData.push({id: data.id, text: data.label, class: data.class});
|
||||
}
|
||||
|
||||
dialogElement.find('#' + config.dialogSystemStatusSelectId).initStatusSelect({
|
||||
data: statusData,
|
||||
iconClass: 'fa-tag'
|
||||
});
|
||||
|
||||
// initial dialog update with persistent system data --------------------------------------------------
|
||||
// -> only if system is preselected (e.g. current active system)
|
||||
let systemId = parseInt(dialogElement.find('.' + config.dialogSystemSelectClass).val()) || 0;
|
||||
if(systemId){
|
||||
requestSystemData(dialogElement, mapId, systemId);
|
||||
}
|
||||
});
|
||||
|
||||
systemDialog.on('shown.bs.modal', function(e){
|
||||
let dialogElement = $(this);
|
||||
|
||||
// no system selected
|
||||
updateDialog(dialogElement, false);
|
||||
|
||||
dialogElement.initTooltips();
|
||||
|
||||
// init system select live search - some delay until modal transition has finished
|
||||
let selectElement = dialogElement.find('.' + config.dialogSystemSelectClass);
|
||||
selectElement.delay(240).initSystemSelect({
|
||||
key: 'id',
|
||||
disabledOptions: mapSystemIds,
|
||||
onChange: systemId => {
|
||||
// on system select -> update dialog with persistent system data
|
||||
if(systemId){
|
||||
requestSystemData(dialogElement, mapId, systemId);
|
||||
}else{
|
||||
// no system selected
|
||||
updateDialog(dialogElement, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// show dialog
|
||||
systemDialog.modal('show');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* show "set rally point" dialog for system
|
||||
* @param system
|
||||
*/
|
||||
$.fn.showRallyPointDialog = (system) => {
|
||||
let mapId = system.data('mapid');
|
||||
let systemId = system.data('id');
|
||||
let mapData = Util.getCurrentMapData(mapId);
|
||||
|
||||
requirejs(['text!templates/dialog/system_rally.html', 'mustache'], function(template, Mustache){
|
||||
|
||||
let setCheckboxObserver = (checkboxes) => {
|
||||
checkboxes.each(function(){
|
||||
$(this).on('change', function(){
|
||||
// check all others
|
||||
let allUnchecked = true;
|
||||
checkboxes.each(function(){
|
||||
if(this.checked){
|
||||
allUnchecked = false;
|
||||
}
|
||||
});
|
||||
let textareaElement = $('#' + config.dialogRallyMessageId);
|
||||
if(allUnchecked){
|
||||
textareaElement.prop('disabled', true);
|
||||
}else{
|
||||
textareaElement.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let sendPoke = (requestData, context) => {
|
||||
// lock dialog
|
||||
let dialogContent = context.rallyDialog.find('.modal-content');
|
||||
dialogContent.showLoadingAnimation();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.pokeRally,
|
||||
data: requestData,
|
||||
dataType: 'json',
|
||||
context: context
|
||||
}).done(function(data){
|
||||
|
||||
}).fail(function(jqXHR, status, error){
|
||||
let reason = status + ' ' + error;
|
||||
Util.showNotify({title: jqXHR.status + ': sendPoke', text: reason, type: 'warning'});
|
||||
}).always(function(){
|
||||
this.rallyDialog.find('.modal-content').hideLoadingAnimation();
|
||||
});
|
||||
};
|
||||
|
||||
let data = {
|
||||
id: config.dialogRallyId,
|
||||
|
||||
dialogRallyPokeDesktopId: config.dialogRallyPokeDesktopId,
|
||||
dialogRallyPokeSlackId: config.dialogRallyPokeSlackId,
|
||||
dialogRallyPokeDiscordId: config.dialogRallyPokeDiscordId,
|
||||
dialogRallyPokeMailId: config.dialogRallyPokeMailId,
|
||||
dialogRallyMessageId: config.dialogRallyMessageId ,
|
||||
|
||||
desktopRallyEnabled: true,
|
||||
slackRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.slackRally')),
|
||||
discordRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.discordRally')),
|
||||
mailRallyEnabled: Boolean(Util.getObjVal(mapData, 'config.logging.mailRally')),
|
||||
dialogRallyMessageDefault: config.dialogRallyMessageDefault,
|
||||
|
||||
systemUrl: MapUtil.getMapDeeplinkUrl(mapId, systemId),
|
||||
systemId: systemId
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
let rallyDialog = bootbox.dialog({
|
||||
message: content,
|
||||
title: 'Set rally point in "' + system.getSystemInfo( ['alias'] ) + '"',
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
className: 'btn-default'
|
||||
},
|
||||
success: {
|
||||
label: '<i class="fas fa-fw fa-volume-up"></i> set rally point',
|
||||
className: 'btn-success',
|
||||
callback: function(){
|
||||
let form = $('#' + config.dialogRallyId).find('form');
|
||||
// get form data
|
||||
let formData = form.getFormValues();
|
||||
|
||||
// update map
|
||||
system.setSystemRally(1, {
|
||||
poke: Boolean(formData.pokeDesktop)
|
||||
});
|
||||
MapUtil.markAsChanged(system);
|
||||
|
||||
// send poke data to server
|
||||
sendPoke(formData, {
|
||||
rallyDialog: this
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rallyDialog.initTooltips();
|
||||
|
||||
// after modal is shown ==================================================================================
|
||||
rallyDialog.on('shown.bs.modal', function(e){
|
||||
// set event for checkboxes
|
||||
setCheckboxObserver(rallyDialog.find(':checkbox'));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* shows delete dialog for systems that should be deleted
|
||||
* @param map
|
||||
* @param systems
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.showDeleteSystemDialog = (map, systems = []) => {
|
||||
let mapContainer = $( map.getContainer() );
|
||||
let validDeleteSystems = [];
|
||||
let activeCharacters = 0;
|
||||
// check if systems belong to map -> security check
|
||||
for(let system of systems){
|
||||
let systemElement = $(system);
|
||||
if(
|
||||
systemElement.data('mapid') === mapContainer.data('id') &&
|
||||
!systemElement.data('locked')
|
||||
){
|
||||
// system belongs to map -> valid system
|
||||
validDeleteSystems.push(system);
|
||||
|
||||
activeCharacters += (systemElement.data('userCount') ? parseInt( systemElement.data('userCount') ) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(validDeleteSystems.length){
|
||||
let msg = '';
|
||||
if(validDeleteSystems.length === 1){
|
||||
let deleteSystem = $(validDeleteSystems[0]);
|
||||
let systemName = deleteSystem.data('name');
|
||||
let systemAlias = deleteSystem.getSystemInfo( ['alias'] );
|
||||
|
||||
let systemNameStr = (systemName === systemAlias) ? '"' + systemName + '"' : '"' + systemAlias + '" (' + systemName + ')';
|
||||
systemNameStr = '<span class="txt-color txt-color-warning">' + systemNameStr + '</span>';
|
||||
msg = 'Delete system ' + systemNameStr + ' and all its connections?';
|
||||
}else{
|
||||
msg = 'Delete ' + validDeleteSystems.length + ' selected systems and their connections?';
|
||||
}
|
||||
|
||||
// add warning for active characters
|
||||
if(activeCharacters > 0){
|
||||
msg += ' <span class="txt-color txt-color-warning">Warning: ' + activeCharacters + ' active characters</span>';
|
||||
}
|
||||
|
||||
let systemDeleteDialog = bootbox.confirm(msg, result => {
|
||||
if(result){
|
||||
deleteSystems(map, validDeleteSystems, (deletedSystems) => {
|
||||
// callback function after deleted -> close dialog
|
||||
systemDeleteDialog.modal('hide');
|
||||
|
||||
// check whether all systems were deleted properly
|
||||
if(deletedSystems.length !== validDeleteSystems.length){
|
||||
let notDeletedCount = validDeleteSystems.length - deletedSystems.length;
|
||||
|
||||
Util.showNotify({
|
||||
title: 'Failed to delete systems',
|
||||
text: '(' + notDeletedCount + '/' + validDeleteSystems.length + ') systems could not be deleted',
|
||||
type: 'warning'}
|
||||
);
|
||||
}else if(deletedSystems.length === 1){
|
||||
Util.showNotify({title: 'System deleted', text: $(deletedSystems[0]).data('name'), type: 'success'});
|
||||
}else{
|
||||
Util.showNotify({title: systems.length + ' systems deleted', type: 'success'});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}else{
|
||||
Util.showNotify({title: 'No systems selected', type: 'warning'});
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* toggle system tooltip (current pilot count)
|
||||
* @param show
|
||||
* @param tooltipOptions
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.toggleSystemTooltip = function(show, tooltipOptions){
|
||||
let highlightData = {
|
||||
good: {
|
||||
colorClass: 'txt-color-green',
|
||||
iconClass: 'fa-caret-up'
|
||||
},
|
||||
bad: {
|
||||
colorClass: 'txt-color-red',
|
||||
iconClass: 'fa-caret-down'
|
||||
}
|
||||
};
|
||||
|
||||
let getHighlightClass = (highlight) => {
|
||||
return Util.getObjVal(highlightData, highlight + '.colorClass') || '';
|
||||
};
|
||||
|
||||
let getHighlightIcon = (highlight) => {
|
||||
return Util.getObjVal(highlightData, highlight + '.iconClass') || '';
|
||||
};
|
||||
|
||||
let getTitle = (userCounter, highlight) => {
|
||||
return '<i class="fas ' + getHighlightIcon(highlight) + '"></i> ' + userCounter + '';
|
||||
};
|
||||
|
||||
return this.each(function(){
|
||||
let system = $(this);
|
||||
switch(show){
|
||||
case 'destroy':
|
||||
//destroy tooltip and remove some attributes which are not deleted by 'destroy'
|
||||
system.tooltip('destroy').removeAttr('title data-original-title');
|
||||
break;
|
||||
case 'hide':
|
||||
system.tooltip('hide');
|
||||
break;
|
||||
case 'show':
|
||||
// initial "show" OR "update" open tooltip
|
||||
// -> do not update tooltips while a system is dragged
|
||||
let showTooltip = !system.hasClass('jsPlumb_dragged');
|
||||
|
||||
if(system.data('bs.tooltip')){
|
||||
// tooltip initialized but could be hidden
|
||||
// check for title update
|
||||
if(
|
||||
tooltipOptions.hasOwnProperty('userCount') &&
|
||||
tooltipOptions.hasOwnProperty('highlight')
|
||||
){
|
||||
let currentTitle = system.attr('data-original-title');
|
||||
let newTitle = getTitle(tooltipOptions.userCount, tooltipOptions.highlight);
|
||||
|
||||
if(currentTitle !== newTitle){
|
||||
// update tooltip
|
||||
let tooltipInner = system.attr('title', newTitle).tooltip('fixTitle').data('bs.tooltip').$tip.find('.tooltip-inner');
|
||||
tooltipInner.html(newTitle);
|
||||
|
||||
// change highlight class
|
||||
let highlightClass = getHighlightClass(tooltipOptions.highlight);
|
||||
if( !tooltipInner.hasClass(highlightClass) ){
|
||||
tooltipInner.removeClass( getHighlightClass('good') + ' ' + getHighlightClass('bad')).addClass(highlightClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tip = system.data('bs.tooltip').tip();
|
||||
if( !tip.hasClass('in') && showTooltip){
|
||||
// update tooltip placement based on system position
|
||||
system.data('bs.tooltip').options.placement = getSystemTooltipPlacement(system);
|
||||
|
||||
system.tooltip('show');
|
||||
}
|
||||
}else{
|
||||
// no tooltip initialized
|
||||
// "some" config data is required
|
||||
if(
|
||||
tooltipOptions.hasOwnProperty('systemId') &&
|
||||
tooltipOptions.hasOwnProperty('userCount') &&
|
||||
tooltipOptions.hasOwnProperty('highlight')
|
||||
){
|
||||
let innerTooltipId = config.systemTooltipInnerIdPrefix + tooltipOptions.systemId;
|
||||
|
||||
let template = '<div class="tooltip" role="tooltip">' +
|
||||
'<div class="tooltip-arrow"></div>' +
|
||||
'<div id="' + innerTooltipId + '" class="tooltip-inner txt-color ' + config.systemTooltipInnerClass + '"></div>' +
|
||||
'</div>';
|
||||
|
||||
let options = {
|
||||
trigger: 'manual',
|
||||
placement: getSystemTooltipPlacement(system),
|
||||
html: true,
|
||||
animation: true,
|
||||
template: template,
|
||||
container: system.closest('.' + Util.config.mapClass)
|
||||
};
|
||||
|
||||
// init new tooltip -> Do not show automatic maybe system is currently dragged
|
||||
system.attr('title', getTitle(tooltipOptions.userCount, tooltipOptions.highlight));
|
||||
system.tooltip(options);
|
||||
|
||||
system.one('shown.bs.tooltip', function(){
|
||||
// set highlight only on FIRST show
|
||||
$('#' + this.innerTooltipId).addClass(this.highlightClass);
|
||||
}.bind({
|
||||
highlightClass: getHighlightClass(tooltipOptions.highlight),
|
||||
innerTooltipId: innerTooltipId
|
||||
}));
|
||||
|
||||
if(showTooltip){
|
||||
system.tooltip('show');
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* get tooltip position (top, bottom) based on system position
|
||||
* @param system
|
||||
* @returns {string}
|
||||
*/
|
||||
let getSystemTooltipPlacement = system => {
|
||||
let offsetParent = system.parent().offset();
|
||||
let offsetSystem = system.offset();
|
||||
|
||||
return (offsetSystem.top - offsetParent.top < 27) ? 'bottom' : 'top';
|
||||
};
|
||||
|
||||
/**
|
||||
* delete system(s) with all their connections
|
||||
* (ajax call) remove system from DB
|
||||
* @param map
|
||||
* @param systems
|
||||
* @param callback function
|
||||
*/
|
||||
let deleteSystems = (map, systems = [], callback = systems => {}) => {
|
||||
let mapContainer = $( map.getContainer() );
|
||||
let systemIds = systems.map(system => $(system).data('id'));
|
||||
|
||||
Util.request('DELETE', 'System', systemIds, {
|
||||
mapId: mapContainer.data('id')
|
||||
}, {
|
||||
map: map,
|
||||
systems: systems
|
||||
}).then(
|
||||
payload => {
|
||||
// check if all systems were deleted that should get deleted
|
||||
let deletedSystems = payload.context.systems.filter(
|
||||
function(system){
|
||||
return this.indexOf( $(system).data('id') ) !== -1;
|
||||
}, payload.data
|
||||
);
|
||||
|
||||
// remove systems from map
|
||||
removeSystems(payload.context.map, deletedSystems);
|
||||
|
||||
callback(deletedSystems);
|
||||
},
|
||||
Util.handleAjaxErrorResponse
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* remove system(s) from map (no backend requests)
|
||||
* @param map
|
||||
* @param systems
|
||||
*/
|
||||
let removeSystems = (map, systems) => {
|
||||
let removeSystemCallback = deleteSystem => {
|
||||
map.remove(deleteSystem);
|
||||
};
|
||||
|
||||
for(let system of systems){
|
||||
system = $(system);
|
||||
let mapId = parseInt(system.data('mapid')) || 0;
|
||||
|
||||
// check if system is "active"
|
||||
if(system.hasClass(MapUtil.config.systemActiveClass)){
|
||||
Util.deleteCurrentSystemData(mapId);
|
||||
|
||||
// get parent Tab Content and fire clear modules event
|
||||
let tabContentElement = MapUtil.getTabContentElementByMapElement(system);
|
||||
$(tabContentElement).trigger('pf:removeSystemModules');
|
||||
}
|
||||
|
||||
// remove connections do not fire a "connectionDetached" event
|
||||
map.deleteConnectionsForElement(system, {fireEvent: false});
|
||||
|
||||
// unregister from "magnetizer"
|
||||
Magnetizer.removeElement(mapId, system[0]);
|
||||
|
||||
// destroy tooltip/popover
|
||||
system.toggleSystemTooltip('destroy', {});
|
||||
system.destroyPopover(true);
|
||||
|
||||
// remove system
|
||||
system.velocity('transition.whirlOut', {
|
||||
duration: Init.animationSpeed.mapDeleteSystem,
|
||||
complete: removeSystemCallback
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* calculate the x/y coordinates for a new system - relative to a source system
|
||||
* -> in case no coordinates found -> return default calculated coordinates
|
||||
* @param sourceSystem
|
||||
* @returns {{x: *, y: *}}
|
||||
*/
|
||||
let newSystemPositionBySystem = sourceSystem => {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
let dimensions = MapUtil.newSystemPositionBySystem(sourceSystem);
|
||||
if(dimensions.length){
|
||||
//... empty map space found
|
||||
x = dimensions[0].left;
|
||||
y = dimensions[0].top;
|
||||
}else{
|
||||
//... fallback
|
||||
// related system is available
|
||||
let currentX = sourceSystem.css('left');
|
||||
let currentY = sourceSystem.css('top');
|
||||
|
||||
// remove "px"
|
||||
currentX = parseInt( currentX.substring(0, currentX.length - 2) );
|
||||
currentY = parseInt( currentY.substring(0, currentY.length - 2) );
|
||||
x = currentX + config.newSystemOffset.x;
|
||||
y = currentY + config.newSystemOffset.y;
|
||||
}
|
||||
|
||||
return {x: x, y: y};
|
||||
};
|
||||
|
||||
/**
|
||||
* get new dom element for systemData that shows "info" data (additional data)
|
||||
* -> this is show below the system base data on map
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
let getHeadInfoElement = data => {
|
||||
let headInfo = null;
|
||||
let headInfoLeft = [];
|
||||
let headInfoRight = [];
|
||||
|
||||
if(data.drifter){
|
||||
headInfoLeft.push('<i class="fas fa-fw fa-wave-square ' + Util.getSecurityClassForSystem(data.security) + '" title="drifter"></i>');
|
||||
}
|
||||
|
||||
if(data.shattered){
|
||||
headInfoLeft.push('<i class="fas fa-fw fa-chart-pie ' + Util.getSecurityClassForSystem('SH') + '" title="shattered"></i>');
|
||||
}
|
||||
|
||||
// check systemData if headInfo element is needed
|
||||
if(data.statics && data.statics.length){
|
||||
// format wh statics
|
||||
for(let wormholeName of data.statics){
|
||||
let staticData = Object.assign({}, Init.wormholes[wormholeName]);
|
||||
headInfoRight.push(
|
||||
'<span class="' +
|
||||
Util.getSecurityClassForSystem(staticData.security) + ' ' +
|
||||
Util.config.popoverTriggerClass + '" data-name="' + staticData.name +
|
||||
'">' + staticData.security + '</span>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(headInfoLeft.length || headInfoRight.length){
|
||||
headInfo = $('<div>', {
|
||||
class: config.systemHeadInfoClass
|
||||
}).append(
|
||||
$('<div>', {
|
||||
class: config.systemHeadInfoLeftClass,
|
||||
html: headInfoLeft.join(' ')
|
||||
}),
|
||||
$('<div>', {
|
||||
class: config.systemHeadInfoRightClass,
|
||||
html: headInfoRight.join(' ')
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return headInfo;
|
||||
};
|
||||
|
||||
return {
|
||||
showNewSystemDialog: showNewSystemDialog,
|
||||
deleteSystems: deleteSystems,
|
||||
removeSystems: removeSystems,
|
||||
getHeadInfoElement: getHeadInfoElement
|
||||
};
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,154 +0,0 @@
|
||||
/**
|
||||
* SharedWorker config for map
|
||||
*/
|
||||
|
||||
define([
|
||||
'app/util'
|
||||
], (Util) => {
|
||||
'use strict';
|
||||
|
||||
let sharedWorker = null;
|
||||
let MsgWorker = null;
|
||||
let characterId = null;
|
||||
|
||||
/**
|
||||
* get WebSocket URL for SharedWorker script
|
||||
* @returns {string}
|
||||
*/
|
||||
let getWebSocketURL = () => {
|
||||
let domain = location.host;
|
||||
let workerProtocol = (window.location.protocol === 'https:') ? 'wss:' : 'ws:';
|
||||
return workerProtocol + '//' + domain + '/ws/map/update';
|
||||
};
|
||||
|
||||
/**
|
||||
* get SharedWorker Script path
|
||||
* @returns {string}
|
||||
*/
|
||||
let getWorkerScript = () => '/public/js/' + Util.getVersion() + '/app/worker/map.js';
|
||||
|
||||
/**
|
||||
* get path to message object
|
||||
* @returns {string}
|
||||
*/
|
||||
let getMessageWorkerObjectPath = () => '/public/js/' + Util.getVersion() + '/app/worker/message.js';
|
||||
|
||||
/**
|
||||
* init (connect) WebSocket within SharedWorker
|
||||
*/
|
||||
let initSocket = () => {
|
||||
let MsgWorkerInit = new MsgWorker('ws:init');
|
||||
MsgWorkerInit.data({
|
||||
uri: getWebSocketURL(),
|
||||
characterId: characterId,
|
||||
});
|
||||
|
||||
sendMessage(MsgWorkerInit);
|
||||
};
|
||||
|
||||
/**
|
||||
* init (start/connect) to "SharedWorker" thread
|
||||
* -> set worker events
|
||||
*/
|
||||
let init = config => {
|
||||
// set characterId that is connected with this SharedWorker PORT
|
||||
characterId = parseInt(config.characterId);
|
||||
|
||||
// get message Class for App <=> SharedWorker MessageEvent communication
|
||||
requirejs([getMessageWorkerObjectPath()], () => {
|
||||
MsgWorker = window.MsgWorker;
|
||||
|
||||
// start/connect to "SharedWorker"
|
||||
sharedWorker = new SharedWorker(getWorkerScript(), getMessageWorkerObjectPath());
|
||||
|
||||
sharedWorker.port.addEventListener('message', e => {
|
||||
let MsgWorkerMessage = e.data;
|
||||
Object.setPrototypeOf(MsgWorkerMessage, MsgWorker.prototype);
|
||||
|
||||
switch(MsgWorkerMessage.command){
|
||||
case 'ws:open':
|
||||
config.callbacks.onOpen(MsgWorkerMessage);
|
||||
break;
|
||||
case 'ws:send':
|
||||
config.callbacks.onGet(MsgWorkerMessage);
|
||||
break;
|
||||
case 'ws:closed':
|
||||
config.callbacks.onClosed(MsgWorkerMessage);
|
||||
break;
|
||||
case 'ws:error':
|
||||
config.callbacks.onError(MsgWorkerMessage);
|
||||
break;
|
||||
|
||||
}
|
||||
}, false);
|
||||
|
||||
sharedWorker.onerror = e => {
|
||||
// could not connect to SharedWorker script -> send error back
|
||||
let MsgWorkerError = new MsgWorker('sw:error');
|
||||
MsgWorkerError.meta({
|
||||
reason: 'Could not connect to SharedWorker: ' + getWorkerScript()
|
||||
});
|
||||
|
||||
config.callbacks.onError(MsgWorkerError);
|
||||
};
|
||||
|
||||
sharedWorker.port.start();
|
||||
|
||||
// SharedWorker initialized
|
||||
let MsgWorkerInit = new MsgWorker('sw:init');
|
||||
config.callbacks.onInit(MsgWorkerInit);
|
||||
|
||||
// startWebSocket
|
||||
initSocket();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* send data to "SharedWorker" thread
|
||||
* @param task
|
||||
* @param data
|
||||
*/
|
||||
let send = (task, data) => {
|
||||
let MsgWorkerSend = new MsgWorker('ws:send');
|
||||
MsgWorkerSend.task(task);
|
||||
MsgWorkerSend.data(data);
|
||||
|
||||
sendMessage(MsgWorkerSend);
|
||||
};
|
||||
|
||||
/**
|
||||
* send close port task to "SharedWorker" thread
|
||||
* -> this removes the port from its port collection and closes it
|
||||
*/
|
||||
let close = () => {
|
||||
// check if MsgWorker is available (SharedWorker was initialized)
|
||||
if(MsgWorker){
|
||||
let MsgWorkerClose = new MsgWorker('sw:closePort');
|
||||
MsgWorkerClose.task('unsubscribe');
|
||||
sendMessage(MsgWorkerClose);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {window.MsgWorker} MsgWorkerSend
|
||||
*/
|
||||
let sendMessage = MsgWorkerSend => {
|
||||
if(sharedWorker instanceof SharedWorker){
|
||||
if(MsgWorkerSend instanceof window.MsgWorker){
|
||||
sharedWorker.port.postMessage(MsgWorkerSend);
|
||||
}else{
|
||||
console.error('MsgWorkerSend must be instance of window.MsgWorker');
|
||||
}
|
||||
}else{
|
||||
console.error('SharedWorker thread not found');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getWebSocketURL: getWebSocketURL,
|
||||
init: init,
|
||||
send: send,
|
||||
close: close
|
||||
};
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
BIN
public/js/v2.0.0/app/mappage.js.br
Normal file
BIN
public/js/v2.0.0/app/mappage.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/mappage.js.map
Normal file
1
public/js/v2.0.0/app/mappage.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
BIN
public/js/v2.0.0/app/pnotify.loader.js.br
Normal file
BIN
public/js/v2.0.0/app/pnotify.loader.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/pnotify.loader.js.map
Normal file
1
public/js/v2.0.0/app/pnotify.loader.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,39 +0,0 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Deferred Promise implementation
|
||||
* @see https://stackoverflow.com/a/47112177/4329969
|
||||
*/
|
||||
return class DeferredPromise {
|
||||
|
||||
constructor(){
|
||||
this._promise = new Promise((resolve, reject) => {
|
||||
// assign the resolve and reject functions to `this`
|
||||
// making them usable on the class instance
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
|
||||
// bind `then` and `catch` to implement the same interface as Promise
|
||||
this.then = this._promise.then.bind(this._promise);
|
||||
this.catch = this._promise.catch.bind(this._promise);
|
||||
}
|
||||
|
||||
set data(data){
|
||||
if(data){
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
return this._data;
|
||||
}
|
||||
|
||||
get data(){
|
||||
return this._data;
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return 'Promise';
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,82 +0,0 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* sequential promise queue
|
||||
* @see https://medium.com/@karenmarkosyan/how-to-manage-promises-into-dynamic-queue-with-vanilla-javascript-9d0d1f8d4df5
|
||||
* @see https://codepen.io/exodus4d/pen/QWwgKay
|
||||
*/
|
||||
return class Queue {
|
||||
|
||||
constructor() {
|
||||
this._queue = [];
|
||||
this._pendingPromise = false;
|
||||
this._stop = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* wraps a promise that needs to be sequentially resolved
|
||||
* -> dequeue() process starts immediately (if not already pending)
|
||||
* @param promise
|
||||
* @param {'end'|'start'} position
|
||||
* @param data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
enqueue(promise, position = 'end', data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._queue[position === 'end' ? 'push' : 'unshift']({
|
||||
promise,
|
||||
resolve,
|
||||
reject,
|
||||
data,
|
||||
});
|
||||
this.dequeue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve promise queue recursive until queue is empty
|
||||
* @returns {boolean}
|
||||
*/
|
||||
dequeue() {
|
||||
if (this._pendingPromise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._stop) {
|
||||
this._queue = [];
|
||||
this._stop = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
let item = this._queue.shift();
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this._pendingPromise = true;
|
||||
item.promise()
|
||||
.then((value) => {
|
||||
this._pendingPromise = false;
|
||||
item.resolve(value);
|
||||
this.dequeue();
|
||||
})
|
||||
.catch(err => {
|
||||
this._pendingPromise = false;
|
||||
item.reject(err);
|
||||
this.dequeue();
|
||||
});
|
||||
} catch (err) {
|
||||
this._pendingPromise = false;
|
||||
item.reject(err);
|
||||
this.dequeue();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
filterQueue(callback) {
|
||||
return this._queue.filter(callback);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,67 +0,0 @@
|
||||
define([], () => {
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
Example1 callback -------------------------------------------------------------------------------------------------
|
||||
new TimeoutPromise((resolve, reject) => {
|
||||
request.get(options, (error, response, body) => {
|
||||
if (!error && response.statusCode === 200) {
|
||||
resolve(body);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}, 2000).then(data => {
|
||||
console.info((Date.now() - start)/1000, '--data --> = ', data);
|
||||
}).catch(error => {
|
||||
console.info((Date.now() - start)/1000, '--error --> = ', error);
|
||||
});
|
||||
|
||||
Example2 DeferredPromise ------------------------------------------------------------------------------------------
|
||||
let deferredPromise = new DeferredPromise();
|
||||
|
||||
new TimeoutPromise(deferredPromise, 2000).then(data => {
|
||||
console.info((Date.now() - start)/1000, '--data --> = ', data);
|
||||
}).catch(error => {
|
||||
console.info((Date.now() - start)/1000, '--error --> = ', error);
|
||||
});
|
||||
|
||||
deferredPromise.resolve('OK');
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Timeout Promise implementation
|
||||
* -> wraps Promise into TimeoutPromise
|
||||
* -> rejects Promise after timeout
|
||||
*
|
||||
* Example1:
|
||||
*
|
||||
*/
|
||||
return class TimeoutPromise extends Promise {
|
||||
|
||||
constructor(callback, timeout = 6000){
|
||||
let timer;
|
||||
let promise = callback[Symbol.toStringTag] === 'Promise' ? callback : new Promise(callback);
|
||||
|
||||
let wrapperPromise = Promise.race([
|
||||
promise,
|
||||
new Promise((resolve, reject) => {
|
||||
timer = setTimeout(timeout => {
|
||||
reject(new Error('Promise timeout after ' + timeout + 'ms'));
|
||||
}, timeout, timeout);
|
||||
}),
|
||||
]);
|
||||
|
||||
super(function(resolve, reject){
|
||||
wrapperPromise.then(data => {
|
||||
clearTimeout(timer);
|
||||
resolve(data);
|
||||
}).catch(error => {
|
||||
clearTimeout(timer);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,167 +0,0 @@
|
||||
/**
|
||||
* Render controller
|
||||
*/
|
||||
|
||||
define(['jquery', 'mustache'], ($, Mustache) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* render Mustache template
|
||||
* @param path
|
||||
* @param data
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
let render = (path, data) => {
|
||||
let renderExecutor = resolve => {
|
||||
requirejs(['text!templates/' + path + '.html'], template => {
|
||||
resolve(Mustache.render(template, data));
|
||||
});
|
||||
};
|
||||
return new Promise(renderExecutor);
|
||||
};
|
||||
|
||||
/**
|
||||
* convert JSON object into HTML highlighted string
|
||||
* @param obj
|
||||
* @param options
|
||||
*/
|
||||
let highlightJson = (obj, options = {}) => {
|
||||
let maxLinesFunctions = options.maxLinesFunctions || 5;
|
||||
|
||||
let multiplyString = (num, str) => {
|
||||
let sb = [];
|
||||
for(let i = 0; i < num; i++){
|
||||
sb.push(str);
|
||||
}
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
let dateObj = new Date();
|
||||
let regexpObj = new RegExp();
|
||||
let tab = multiplyString(1, ' ');
|
||||
let isCollapsible = true;
|
||||
let quoteKeys = false;
|
||||
let expImageClicked = '(() => {this.classList.toggle(\'fa-minus-square\'); ' +
|
||||
'this.classList.toggle(\'fa-plus-square\'); ' +
|
||||
'let container=this.parentNode.nextSibling; ' +
|
||||
'container.style.display=container.style.display===\'none\'?\'inline\':\'none\'})();';
|
||||
|
||||
let checkForArray = function(obj){
|
||||
return obj &&
|
||||
typeof obj === 'object' &&
|
||||
typeof obj.length === 'number' &&
|
||||
!(obj.propertyIsEnumerable('length'));
|
||||
};
|
||||
|
||||
let getRow = function(indent, data, isPropertyContent){
|
||||
let tabs = '';
|
||||
for(let i = 0; i < indent && !isPropertyContent; i++) tabs += tab;
|
||||
if(data !== null && data.length > 0 && data.charAt(data.length - 1) !== '\n')
|
||||
data = data + '\n';
|
||||
return tabs + data;
|
||||
};
|
||||
|
||||
let formatLiteral = function(literal, quote, comma, indent, isArray, style){
|
||||
if(typeof literal === 'string')
|
||||
literal = literal.split('<').join('<').split('>').join('>');
|
||||
let str = '<span class="' + style + '">' + quote + literal + quote + comma + '</span>';
|
||||
if(isArray) str = getRow(indent, str);
|
||||
return str;
|
||||
};
|
||||
|
||||
let formatFunction = function(indent, obj){
|
||||
let tabs = '';
|
||||
for(let i = 0; i < indent; i++) tabs += tab;
|
||||
let funcStrArray = obj.toString().split('\n', maxLinesFunctions);
|
||||
let str = '';
|
||||
for(let i = 0; i < funcStrArray.length; i++){
|
||||
str += ((i === 0) ? '' : '') + funcStrArray[i] + '\n';
|
||||
}
|
||||
return str + tabs;
|
||||
};
|
||||
|
||||
|
||||
let highlight = (obj, indent, addComma, isArray, isPropertyContent) => {
|
||||
// check if recursive call depth leads to collapsed data
|
||||
let startCollapseIcon = options.collapseDepth <= indent ? 'fa-plus-square' : 'fa-minus-square';
|
||||
let startCollapseStyle = options.collapseDepth <= indent ? 'none' : 'inline';
|
||||
|
||||
let html = '';
|
||||
|
||||
// check max recursion depth
|
||||
if(indent > (options.maxDepth || 8)){
|
||||
return html;
|
||||
}
|
||||
|
||||
let comma = (addComma) ? '<span class="pf-code-Comma">,</span> ' : '';
|
||||
let type = typeof obj;
|
||||
let clpsHtml = '';
|
||||
if(checkForArray(obj)){
|
||||
if(obj.length === 0){
|
||||
html += getRow(indent, '<span class="pf-code-ArrayBrace">[ ]</span>' + comma, isPropertyContent);
|
||||
}else{
|
||||
clpsHtml = isCollapsible ? '<span><i class="pf-module-icon-button fas fa-fw ' + startCollapseIcon + '" onClick="' + expImageClicked + '"></i></span>' +
|
||||
'<span class="collapsible" style="display:'+ startCollapseStyle +'">' : '';
|
||||
html += getRow(indent, '<span class="pf-code-ArrayBrace">[</span>' + clpsHtml, isPropertyContent);
|
||||
for(let i = 0; i < obj.length; i++){
|
||||
html += highlight(obj[i], indent + 1, i < (obj.length - 1), true, false);
|
||||
}
|
||||
clpsHtml = isCollapsible ? '</span>' : '';
|
||||
html += getRow(indent, clpsHtml + '<span class="pf-code-ArrayBrace">]</span>' + comma);
|
||||
}
|
||||
}else if(type === 'object'){
|
||||
if(obj === null){
|
||||
html += formatLiteral('null', '', comma, indent, isArray, 'pf-code-Null');
|
||||
}else if(obj.constructor === dateObj.constructor){
|
||||
html += formatLiteral('new Date(' + obj.getTime() + ') <' + obj.toLocaleString('en-GB') + '>', '', comma, indent, isArray, 'pf-code-Date');
|
||||
}else if(obj.constructor === regexpObj.constructor){
|
||||
html += formatLiteral('new RegExp(' + obj + ')', '', comma, indent, isArray, 'RegExp');
|
||||
}else{
|
||||
let numProps = 0;
|
||||
for(let prop in obj) numProps++;
|
||||
if(numProps === 0){
|
||||
html += getRow(indent, '<span class="pf-code-ObjectBrace">{ }</span>' + comma, isPropertyContent);
|
||||
}else{
|
||||
clpsHtml = isCollapsible ? '<span><i class="pf-module-icon-button fas fa-fw ' + startCollapseIcon + '" onClick="' + expImageClicked + '"></i></span>' +
|
||||
'<span class="collapsible" style="display:'+ startCollapseStyle +'">' : '';
|
||||
html += getRow(indent, '<span class="pf-code-ObjectBrace">{</span>' + clpsHtml, isPropertyContent);
|
||||
let j = 0;
|
||||
for(let prop in obj){
|
||||
if(obj.hasOwnProperty(prop)){
|
||||
let quote = quoteKeys ? '"' : '';
|
||||
html += getRow(indent + 1, '<span class="pf-code-PropertyName">' + quote + prop + quote + '</span>: ' + highlight(obj[prop], indent + 1, ++j < numProps, false, true));
|
||||
}
|
||||
}
|
||||
clpsHtml = isCollapsible ? '</span>' : '';
|
||||
html += getRow(indent, clpsHtml + '<span class="pf-code-ObjectBrace">}</span>' + comma);
|
||||
}
|
||||
}
|
||||
}else if(type === 'number'){
|
||||
html += formatLiteral(obj, '', comma, indent, isArray, 'pf-code-Number');
|
||||
}else if(type === 'boolean'){
|
||||
html += formatLiteral(obj, '', comma, indent, isArray, 'pf-code-Boolean');
|
||||
}else if(type === 'function'){
|
||||
if(obj.constructor === regexpObj.constructor){
|
||||
html += formatLiteral('new RegExp(' + obj + ')', '', comma, indent, isArray, 'RegExp');
|
||||
}else{
|
||||
obj = formatFunction(indent, obj);
|
||||
html += formatLiteral(obj, '', comma, indent, isArray, 'pf-code-Function');
|
||||
}
|
||||
}else if(type === 'undefined'){
|
||||
html += formatLiteral('undefined', '', comma, indent, isArray, 'pf-code-Null');
|
||||
}else{
|
||||
html += formatLiteral(obj.toString().split('\\').join('\\\\').split('"').join('\\"'), '"', comma, indent, isArray, 'pf-code-String');
|
||||
}
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
return highlight(obj, 0, false, false, false);
|
||||
};
|
||||
|
||||
return {
|
||||
render: render,
|
||||
highlightJson: highlightJson
|
||||
};
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
BIN
public/js/v2.0.0/app/setup.js.br
Normal file
BIN
public/js/v2.0.0/app/setup.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/setup.js.map
Normal file
1
public/js/v2.0.0/app/setup.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
public/js/v2.0.0/app/summernote.loader.js.br
Normal file
BIN
public/js/v2.0.0/app/summernote.loader.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/summernote.loader.js.map
Normal file
1
public/js/v2.0.0/app/summernote.loader.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,209 +0,0 @@
|
||||
/**
|
||||
* user settings/share dialog
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox'
|
||||
], ($, Init, Util, bootbox) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
// select character dialog
|
||||
settingsDialogId: 'pf-settings-dialog', // id for "settings" dialog
|
||||
settingsAccountContainerId: 'pf-settings-dialog-account', // id for the "account" container
|
||||
settingsShareContainerId: 'pf-settings-dialog-share', // id for the "share" container
|
||||
settingsCharacterContainerId: 'pf-settings-dialog-character', // id for the "character" container
|
||||
|
||||
// captcha
|
||||
captchaKeyUpdateAccount: 'SESSION.CAPTCHA.ACCOUNT.UPDATE', // key for captcha reason
|
||||
captchaImageWrapperId: 'pf-dialog-captcha-wrapper', // id for "captcha image" wrapper
|
||||
captchaImageId: 'pf-dialog-captcha-image', // id for "captcha image"
|
||||
|
||||
loadingOptions: { // config for loading overlay
|
||||
icon: {
|
||||
size: 'fa-2x'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* show "register/settings" dialog
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$.fn.showSettingsDialog = function(){
|
||||
|
||||
// check if there are other dialogs open
|
||||
let openDialogs = Util.getOpenDialogs();
|
||||
if(openDialogs.length > 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
requirejs(['text!templates/dialog/settings.html', 'mustache'], function(template, Mustache){
|
||||
|
||||
let data = {
|
||||
id: config.settingsDialogId,
|
||||
settingsAccountContainerId: config.settingsAccountContainerId,
|
||||
settingsShareContainerId: config.settingsShareContainerId,
|
||||
settingsCharacterContainerId: config.settingsCharacterContainerId,
|
||||
userData: Init.currentUserData,
|
||||
captchaImageWrapperId: config.captchaImageWrapperId,
|
||||
captchaImageId: config.captchaImageId,
|
||||
formErrorContainerClass: Util.config.formErrorContainerClass,
|
||||
ccpImageServer: Init.url.ccpImageServer,
|
||||
roleLabel: Util.getLabelByRole(Util.getCurrentCharacterData('role')).prop('outerHTML'),
|
||||
characterAutoLocationSelectEnabled: Boolean(Util.getObjVal(Init, 'character.autoLocationSelect')),
|
||||
hasRightCorporationShare: Util.hasRight('map_share', 'corporation')
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
let accountSettingsDialog = bootbox.dialog({
|
||||
title: 'Account settings',
|
||||
message: content,
|
||||
show: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
className: 'btn-default'
|
||||
},
|
||||
success: {
|
||||
label: '<i class="fas fa-check fa-fw"></i> save',
|
||||
className: 'btn-success',
|
||||
callback: function(){
|
||||
|
||||
// get the current active form
|
||||
let form = $('#' + config.settingsDialogId).find('form').filter(':visible');
|
||||
|
||||
// validate form
|
||||
form.validator('validate');
|
||||
|
||||
// check whether the form is valid
|
||||
let formValid = form.isValidForm();
|
||||
|
||||
if(formValid === true){
|
||||
let tabFormValues = form.getFormValues();
|
||||
|
||||
// send Tab data and store values
|
||||
let requestData = {
|
||||
formData: tabFormValues
|
||||
};
|
||||
|
||||
accountSettingsDialog.find('.modal-content').showLoadingAnimation();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.saveUserConfig,
|
||||
data: requestData,
|
||||
dataType: 'json'
|
||||
}).done(function(responseData){
|
||||
accountSettingsDialog.find('.modal-content').hideLoadingAnimation();
|
||||
|
||||
// set new captcha for any request
|
||||
// captcha is required for sensitive data (not for all data)
|
||||
if(
|
||||
responseData.error &&
|
||||
responseData.error.length > 0
|
||||
){
|
||||
form.showFormMessage(responseData.error);
|
||||
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage(config.captchaKeyUpdateAccount, function(){
|
||||
$('#captcha').resetFormFields();
|
||||
});
|
||||
}else{
|
||||
// store new/updated user data -> update head
|
||||
if(responseData.userData){
|
||||
Util.setCurrentUserData(responseData.userData);
|
||||
}
|
||||
|
||||
form.find('.alert').velocity('transition.slideDownOut',{
|
||||
duration: 500,
|
||||
complete: function(){
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage(config.captchaKeyUpdateAccount, function(){
|
||||
$('#captcha').resetFormFields();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Util.showNotify({title: 'Account saved', type: 'success'});
|
||||
|
||||
// close dialog/menu
|
||||
Util.triggerMenuAction(document, 'Close');
|
||||
accountSettingsDialog.modal('hide');
|
||||
}
|
||||
}).fail(function(jqXHR, status, error){
|
||||
accountSettingsDialog.find('.modal-content').hideLoadingAnimation();
|
||||
|
||||
let reason = status + ' ' + error;
|
||||
Util.showNotify({title: jqXHR.status + ': saveAccountSettings', text: reason, type: 'error'});
|
||||
|
||||
// set new captcha for any request
|
||||
// captcha is required for sensitive data (not for all)
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage(config.captchaKeyUpdateAccount, function(){
|
||||
$('#captcha').resetFormFields();
|
||||
});
|
||||
|
||||
// check for DB errors
|
||||
if(jqXHR.status === 500){
|
||||
|
||||
if(jqXHR.responseText){
|
||||
let errorObj = $.parseJSON(jqXHR.responseText);
|
||||
|
||||
if(
|
||||
errorObj.error &&
|
||||
errorObj.error.length > 0
|
||||
){
|
||||
form.showFormMessage(errorObj.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(document).setProgramStatus('problem');
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
accountSettingsDialog.on('show.bs.modal', function(e){
|
||||
// request captcha image and show
|
||||
let captchaImageWrapperContainer = $('#' + config.captchaImageWrapperId);
|
||||
captchaImageWrapperContainer.showCaptchaImage(config.captchaKeyUpdateAccount);
|
||||
|
||||
// init captcha refresh button
|
||||
captchaImageWrapperContainer.find('i').on('click', function(){
|
||||
captchaImageWrapperContainer.showCaptchaImage(config.captchaKeyUpdateAccount);
|
||||
});
|
||||
});
|
||||
|
||||
// after modal is shown =======================================================================
|
||||
accountSettingsDialog.on('shown.bs.modal', function(e){
|
||||
let dialogElement = $(this);
|
||||
let form = dialogElement.find('form');
|
||||
|
||||
dialogElement.initTooltips();
|
||||
|
||||
form.initFormValidation();
|
||||
|
||||
// init "toggle" switches
|
||||
dialogElement.find('input[type="checkbox"][data-toggle="toggle"]').bootstrapToggle({
|
||||
on: '<i class="fas fa-fw fa-check"></i> Enable',
|
||||
off: 'Disable <i class="fas fa-fw fa-ban"></i>',
|
||||
onstyle: 'success',
|
||||
offstyle: 'warning',
|
||||
width: 100,
|
||||
height: 30
|
||||
});
|
||||
});
|
||||
|
||||
// show dialog
|
||||
accountSettingsDialog.modal('show');
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* changelog dialog (GitHub API repository information)
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox'
|
||||
], ($, Init, Util, bootbox) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
apiStatusDialogClass: 'pf-api-status-dialog' // class for "api status" dialog
|
||||
};
|
||||
|
||||
/**
|
||||
* show api status dialog
|
||||
* @param apiData
|
||||
*/
|
||||
$.fn.apiStatusDialog = function(apiData){
|
||||
|
||||
let data = {
|
||||
apiData: apiData,
|
||||
methodFormat: () => {
|
||||
return (val, render) => {
|
||||
switch(render(val)){
|
||||
case 'get': return 'txt-color-blue';
|
||||
case 'post': return 'txt-color-green';
|
||||
case 'put': return 'txt-color-yellow';
|
||||
case 'delete': return 'txt-color-red';
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
},
|
||||
statusTitle: () => {
|
||||
return (val, render) => {
|
||||
switch(render(val)){
|
||||
case 'green': return 'ok';
|
||||
case 'yellow': return 'degraded: Slow or potentially dropping requests';
|
||||
case 'orange': return 'bad: Most requests are not succeeding and/or are very slow (5s+) on average';
|
||||
case 'red': return 'error: Status data not available. Either offline or any other fatal error';
|
||||
default: return 'unknown';
|
||||
}
|
||||
};
|
||||
},
|
||||
secondsFormat: () => {
|
||||
return (val, render) => {
|
||||
return parseFloat(render(val)).toFixed(2) + 's';
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
requirejs(['text!templates/dialog/api_status.html', 'mustache'], (template, Mustache) => {
|
||||
let apiStatusDialog = bootbox.dialog({
|
||||
className: config.apiStatusDialogClass,
|
||||
title: 'API status',
|
||||
message: Mustache.render(template, data),
|
||||
show: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
className: 'btn-default'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
apiStatusDialog.initTooltips();
|
||||
|
||||
// show dialog
|
||||
apiStatusDialog.modal('show');
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* changelog dialog (GitHub API repository information)
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox'
|
||||
], ($, Init, Util, bootbox) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
changelogDialogClass: 'pf-changelog-dialog', // class for "changelog" dialog
|
||||
dynamicMessageContainerClass: 'pf-dynamic-message-container', // class for "dynamic" (JS) message container
|
||||
timelineClass: 'timeline' // class for "timeline"
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* show version information
|
||||
* @param changelogDialog
|
||||
* @param versionData
|
||||
*/
|
||||
let showVersion = (changelogDialog, versionData) => {
|
||||
let type = 'error';
|
||||
let title = versionData.current;
|
||||
let text = 'Installed version check failed';
|
||||
|
||||
if(versionData.dev){
|
||||
// developer version
|
||||
type = 'info';
|
||||
title = versionData.current + ' (dev)';
|
||||
text = 'This installation is ahead of current stable version <kbd>' + versionData.last + '</kbd>.';
|
||||
}else{
|
||||
// standard version
|
||||
if(versionData.delta === 0){
|
||||
// last stable
|
||||
type = 'success';
|
||||
title = versionData.current;
|
||||
text = 'This installation is up2date.';
|
||||
}else{
|
||||
// outdated...
|
||||
type = 'warning';
|
||||
title = versionData.current;
|
||||
text = 'This installation is ' + versionData.delta + ' version behind current stable <kbd>' + versionData.last + '</kbd>.';
|
||||
}
|
||||
}
|
||||
|
||||
changelogDialog.find('.' + config.dynamicMessageContainerClass).showMessage({
|
||||
dismissible: false,
|
||||
type: type,
|
||||
title: title,
|
||||
text: text
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* load changelog information in dialog
|
||||
* @param changelogDialog
|
||||
*/
|
||||
let loadDialogData = (changelogDialog) => {
|
||||
|
||||
// lock dialog
|
||||
let dialogContent = changelogDialog.find('.modal-content');
|
||||
dialogContent.showLoadingAnimation();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.gitHubReleases,
|
||||
dataType: 'json',
|
||||
context: {
|
||||
changelogDialog: changelogDialog
|
||||
}
|
||||
}).done(function(data){
|
||||
let changelogDialog = this.changelogDialog;
|
||||
let versionData = data.version;
|
||||
let releasesData = data.releasesData;
|
||||
|
||||
showVersion(changelogDialog, versionData);
|
||||
|
||||
requirejs(['text!templates/ui/timeline_element.html', 'mustache'], function(template, Mustache){
|
||||
for(let i = 0; i < releasesData.length; i++){
|
||||
let releaseData = releasesData[i];
|
||||
|
||||
// template vars
|
||||
let data = {
|
||||
isFirst: (i === 0),
|
||||
isOdd: (i % 2 !== 0),
|
||||
releaseDate: releaseData.publishedAt.substr(0, 10),
|
||||
releaseData: releaseData
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
changelogDialog.find('ul.' + config.timelineClass).append(content);
|
||||
}
|
||||
|
||||
changelogDialog.find('.timeline > li').velocity('transition.expandIn', {
|
||||
stagger: 300,
|
||||
duration: 240,
|
||||
//display: 'auto',
|
||||
complete: function(){}
|
||||
});
|
||||
});
|
||||
}).fail(function(jqXHR, status, error){
|
||||
let reason = status + ' ' + jqXHR.status + ': ' + error;
|
||||
Util.showNotify({title: jqXHR.status + ': login', text: reason, type: 'error'});
|
||||
}).always(function(){
|
||||
dialogContent.hideLoadingAnimation();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* show changelog dialog
|
||||
*/
|
||||
$.fn.changelogsDialog = function(){
|
||||
let content = $('<div>').append(
|
||||
$('<div>', {
|
||||
class: config.dynamicMessageContainerClass
|
||||
}),
|
||||
$('<ul>', {
|
||||
class: config.timelineClass
|
||||
})
|
||||
);
|
||||
|
||||
let changelogDialog = bootbox.dialog({
|
||||
className: config.changelogDialogClass,
|
||||
title: 'Changelog',
|
||||
size: 'large',
|
||||
message: content
|
||||
});
|
||||
|
||||
// after modal is shown =======================================================================
|
||||
changelogDialog.on('shown.bs.modal', function(e){
|
||||
loadDialogData(changelogDialog);
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* credits dialog
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox'
|
||||
], ($, Init, Util, bootbox) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
creditsDialogClass: 'pf-credits-dialog'
|
||||
};
|
||||
|
||||
/**
|
||||
* show jump info dialog
|
||||
*/
|
||||
$.fn.showCreditsDialog = function(){
|
||||
requirejs(['text!templates/dialog/credit.html', 'mustache'], (template, Mustache) => {
|
||||
let data = {
|
||||
version: Util.getVersion(),
|
||||
imgSrcBackground: `${Util.imgRoot()}header/pf-header-780.webp`,
|
||||
imgSrcPatreon: `${Util.imgRoot()}misc/donate_patreon.png`,
|
||||
imgSrcPaypal: `${Util.imgRoot()}misc/donate_paypal.png`,
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
bootbox.dialog({
|
||||
className: config.creditsDialogClass,
|
||||
title: 'Licence',
|
||||
message: content
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
/**
|
||||
* delete account dialog
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox'
|
||||
], ($, Init, Util, bootbox) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
// global dialog
|
||||
deleteAccountId: 'pf-dialog-delete-account', // dialog id
|
||||
|
||||
// captcha
|
||||
captchaKeyDeleteAccount: 'SESSION.CAPTCHA.ACCOUNT.DELETE', // key for captcha reason
|
||||
captchaImageWrapperId: 'pf-dialog-captcha-wrapper' // id for "captcha image" wrapper
|
||||
};
|
||||
|
||||
/**
|
||||
* shows delete account dialog
|
||||
*/
|
||||
$.fn.showDeleteAccountDialog = function(){
|
||||
|
||||
requirejs(['text!templates/dialog/delete_account.html', 'mustache'], (template, Mustache) => {
|
||||
|
||||
let data = {
|
||||
deleteAccountId: config.deleteAccountId,
|
||||
userData: Util.getCurrentUserData(),
|
||||
captchaImageWrapperId: config.captchaImageWrapperId,
|
||||
formErrorContainerClass: Util.config.formErrorContainerClass
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
let deleteAccountDialog = bootbox.dialog({
|
||||
title: 'Delete account',
|
||||
message: content,
|
||||
show: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
className: 'btn-default'
|
||||
},
|
||||
success: {
|
||||
label: '<i class="fas fa-user-times fa-fw"></i> delete account',
|
||||
className: 'btn-danger',
|
||||
callback: function(){
|
||||
let dialogElement = $(this);
|
||||
let form = dialogElement.find('form');
|
||||
|
||||
// validate form
|
||||
form.validator('validate');
|
||||
let formValid = form.isValidForm();
|
||||
|
||||
if(formValid){
|
||||
|
||||
let formValues = form.getFormValues();
|
||||
|
||||
if(! $.isEmptyObject(formValues) ){
|
||||
// send Tab data and store values
|
||||
let requestData = {
|
||||
formData: formValues
|
||||
};
|
||||
|
||||
dialogElement.find('.modal-content').showLoadingAnimation();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.deleteAccount,
|
||||
data: requestData,
|
||||
dataType: 'json'
|
||||
}).done(function(responseData){
|
||||
dialogElement.find('.modal-content').hideLoadingAnimation();
|
||||
|
||||
if(responseData.reroute !== undefined){
|
||||
Util.redirect(responseData.reroute);
|
||||
}else if(
|
||||
responseData.error &&
|
||||
responseData.error.length > 0
|
||||
){
|
||||
form.showFormMessage(responseData.error);
|
||||
|
||||
$('#' + config.captchaImageWrapperId).showCaptchaImage(config.captchaKeyDeleteAccount, function(){
|
||||
form.find('[name="captcha"]').resetFormFields();
|
||||
});
|
||||
}
|
||||
|
||||
}).fail(function(jqXHR, status, error){
|
||||
dialogElement.find('.modal-content').hideLoadingAnimation();
|
||||
|
||||
let reason = status + ' ' + error;
|
||||
Util.showNotify({title: jqXHR.status + ': deleteAccount', text: reason, type: 'error'});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
deleteAccountDialog.on('show.bs.modal', function(e){
|
||||
// request captcha image and show
|
||||
let captchaImageWrapperContainer = $('#' + config.captchaImageWrapperId);
|
||||
captchaImageWrapperContainer.showCaptchaImage(config.captchaKeyDeleteAccount);
|
||||
|
||||
// init captcha refresh button
|
||||
captchaImageWrapperContainer.find('i').on('click', function(){
|
||||
captchaImageWrapperContainer.showCaptchaImage(config.captchaKeyDeleteAccount);
|
||||
});
|
||||
});
|
||||
|
||||
// after modal is shown =======================================================================
|
||||
deleteAccountDialog.on('shown.bs.modal', function(e){
|
||||
let dialogElement = $(this);
|
||||
|
||||
dialogElement.initTooltips();
|
||||
});
|
||||
|
||||
// show dialog
|
||||
deleteAccountDialog.modal('show');
|
||||
});
|
||||
|
||||
};
|
||||
});
|
||||
@@ -1,225 +0,0 @@
|
||||
/**
|
||||
* jump info dialog
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox',
|
||||
'app/map/util'
|
||||
], ($, Init, Util, bootbox, MapUtil) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
// jump info dialog
|
||||
jumpInfoDialogClass: 'pf-jump-info-dialog', // class for jump info dialog
|
||||
|
||||
wormholeInfoDialogListId: 'pf-wormhole-info-dialog-list', // id for map "list" container
|
||||
wormholeInfoDialogStaticId: 'pf-wormhole-info-dialog-static', // id for map "static" container
|
||||
wormholeInfoDialogJumpId: 'pf-wormhole-info-dialog-jump', // id for map "jump" container
|
||||
|
||||
wormholeInfoMassTableClass: 'pf-wormhole-info-mass-table', // class for "wormhole mass" table
|
||||
wormholeInfoStaticTableClass: 'pf-wormhole-info-static-table', // class for "static" table
|
||||
wormholeInfoJumpTableClass: 'pf-wormhole-info-jump-table' // class for "wormhole jump" table
|
||||
};
|
||||
|
||||
/**
|
||||
* show jump info dialog
|
||||
*/
|
||||
$.fn.showJumpInfoDialog = function(){
|
||||
requirejs(['text!templates/dialog/jump_info.html', 'mustache'], (template, Mustache) => {
|
||||
let iconShattered = '<i class="fas fa-fw fa-chart-pie pf-system-sec-unknown"></i>';
|
||||
let iconDrifter = '<i class="fas fa-fw fa-wave-square pf-system-sec-drifter"></i>';
|
||||
|
||||
let formatTableBodyData = (head, matrixBody) => {
|
||||
return matrixBody.map((row, rowIndex) => {
|
||||
return row.map((label, colIndex) => {
|
||||
// get security name from "matrix Head" data if NOT first column
|
||||
let secName = colIndex ? head[0][colIndex] : label;
|
||||
return {
|
||||
label: label,
|
||||
class: Util.getSecurityClassForSystem(secName),
|
||||
hasPopover: colIndex && label.length
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Statics table first ------------------------------------------------------------------------------------
|
||||
let headGroupFirst = [
|
||||
[
|
||||
{label: '', class: 'separator-right', style: 'width: 55px;'},
|
||||
{colspan: 6, label: 'W-space', class: 'separator-right'},
|
||||
{colspan: 3, label: 'K-space', class: 'separator-right'},
|
||||
{label: 'Thera', class: 'separator-right'},
|
||||
{label: iconShattered}
|
||||
]
|
||||
];
|
||||
|
||||
let headFirst = [
|
||||
['From╲To', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'H', 'L', '0.0', 'C12', 'C13']
|
||||
];
|
||||
|
||||
let matrixBodyFirst = [
|
||||
['C1', 'H121', 'C125', 'O883', 'M609', 'L614', 'S804', 'N110', 'J244', 'Z060', 'F353', ''],
|
||||
['C2', 'Z647', 'D382', 'O477', 'Y683', 'N062', 'R474', 'B274', 'A239', 'E545', 'F135', ''],
|
||||
['C3', 'V301', 'I182', 'N968', 'T405', 'N770', 'A982', 'D845', 'U210', 'K346', 'F135', ''],
|
||||
['C4', 'P060', 'N766', 'C247', 'X877', 'H900', 'U574', 'S047', 'N290', 'K329', '' , ''],
|
||||
['C5', 'Y790', 'D364', 'M267', 'E175', 'H296', 'V753', 'D792', 'C140', 'Z142', '' , ''],
|
||||
['C6', 'Q317', 'G024', 'L477', 'Z457', 'V911', 'W237', ['B520', 'D792'], ['C140', 'C391'], ['C248', 'Z142'], '', ''],
|
||||
['H', 'Z971', 'R943', 'X702', 'O128', 'M555', 'B041', 'A641', 'R051', 'V283', 'T458', ''],
|
||||
['L', 'Z971', 'R943', 'X702', 'O128', 'N432', 'U319', 'B449', 'N944', 'S199', 'M164', ''],
|
||||
['0.0', 'Z971', 'R943', 'X702', 'O128', 'N432', 'U319', 'B449', 'N944', 'S199', 'L031', ''],
|
||||
['C12', '' , '' , '' , '' , '' , '' , 'Q063', 'V898', 'E587', '' , ''],
|
||||
['?', 'E004', 'L005', 'Z006', 'M001', 'C008', 'G008', '' , '' , 'Q003', '' , 'A009']
|
||||
];
|
||||
|
||||
|
||||
let staticsTableDataFirst = {
|
||||
headGroup: headGroupFirst,
|
||||
head: headFirst,
|
||||
body: formatTableBodyData(headFirst, matrixBodyFirst)
|
||||
};
|
||||
|
||||
// Statics table second -----------------------------------------------------------------------------------
|
||||
|
||||
let headGroupSecond = [
|
||||
[
|
||||
{label: '', class: 'separator-right', style: 'width: 55px;'},
|
||||
{label: iconDrifter + ' ' + 'Sentinel', class: 'separator-right'},
|
||||
{label: iconDrifter + ' ' + 'Barbican', class: 'separator-right'},
|
||||
{label: iconDrifter + ' ' + 'Vidette', class: 'separator-right'},
|
||||
{label: iconDrifter + ' ' + 'Conflux', class: 'separator-right'},
|
||||
{label: iconDrifter + ' ' + 'Redoubt'}
|
||||
]
|
||||
];
|
||||
|
||||
let headSecond = [
|
||||
['From╲To', 'C14', 'C15', 'C16', 'C17', 'C18']
|
||||
];
|
||||
|
||||
let matrixBodySecond = [
|
||||
['?', 'S877', 'B735', 'V928', 'C414', 'R259']
|
||||
];
|
||||
|
||||
let staticsTableDataSecond = {
|
||||
headline: 'Drifter W-space',
|
||||
headGroup: headGroupSecond,
|
||||
head: headSecond,
|
||||
body: formatTableBodyData(headSecond, matrixBodySecond)
|
||||
};
|
||||
|
||||
let staticsTablesData = [staticsTableDataFirst, staticsTableDataSecond];
|
||||
|
||||
let data = {
|
||||
config: config,
|
||||
popoverTriggerClass: Util.config.popoverTriggerClass,
|
||||
wormholes: Object.keys(Init.wormholes).map(function(k){ return Init.wormholes[k]; }), // convert Json to array
|
||||
staticsTablesData: staticsTablesData,
|
||||
massValue: function(){
|
||||
return function(value, render){
|
||||
let mass = render(value);
|
||||
switch(mass.length){
|
||||
case 0: return '';
|
||||
case 1: return 'Yes';
|
||||
default: return this.Util.formatMassValue(mass);
|
||||
}
|
||||
}.bind(this);
|
||||
}.bind({
|
||||
Util: Util
|
||||
}),
|
||||
formatStatic: function(){
|
||||
return function(value, render){
|
||||
let isStatic = render(value) === 'true';
|
||||
if(isStatic){
|
||||
return '<i class="fas fa-check"></i>';
|
||||
}else{
|
||||
return '';
|
||||
}
|
||||
};
|
||||
},
|
||||
formatTime: function(){
|
||||
return function(value, render){
|
||||
let time = render(value);
|
||||
return time.length ? time + ' h' : 'unknown';
|
||||
};
|
||||
},
|
||||
sigStrengthValue: function(){
|
||||
return function(value, render){
|
||||
let float = render(value);
|
||||
return float.length ? parseFloat(float).toLocaleString() + ' %' : 'unknown';
|
||||
};
|
||||
},
|
||||
securityClass: function(){
|
||||
return function(value, render){
|
||||
return Util.getSecurityClassForSystem(this);
|
||||
};
|
||||
}
|
||||
};
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
let jumpDialog = bootbox.dialog({
|
||||
className: config.jumpInfoDialogClass,
|
||||
title: 'Wormhole data',
|
||||
message: content,
|
||||
show: false
|
||||
});
|
||||
|
||||
jumpDialog.on('show.bs.modal', function(e){
|
||||
$(this).find('.' + config.wormholeInfoMassTableClass).DataTable({
|
||||
pageLength: 35,
|
||||
lengthMenu: [[15, 25, 35, 50, -1], [15, 25, 35, 50, 'All']],
|
||||
autoWidth: false,
|
||||
language: {
|
||||
emptyTable: 'No wormholes',
|
||||
zeroRecords: 'No wormholes found',
|
||||
lengthMenu: 'Show _MENU_ wormholes',
|
||||
info: 'Showing _START_ to _END_ of _TOTAL_ wormholes'
|
||||
},
|
||||
columnDefs: [],
|
||||
data: null // use DOM data overwrites [] default -> data.loader.js
|
||||
});
|
||||
|
||||
$(this).find('.' + config.wormholeInfoStaticTableClass).DataTable({
|
||||
pageLength: -1,
|
||||
paging: false,
|
||||
lengthChange: false,
|
||||
ordering: false,
|
||||
searching: false,
|
||||
info: false,
|
||||
autoWidth: false,
|
||||
columnDefs: [],
|
||||
data: null // use DOM data overwrites [] default -> data.loader.js
|
||||
});
|
||||
|
||||
$(this).find('.' + config.wormholeInfoJumpTableClass).DataTable({
|
||||
pageLength: -1,
|
||||
paging: false,
|
||||
lengthChange: false,
|
||||
ordering: false,
|
||||
searching: false,
|
||||
info: false,
|
||||
autoWidth: false,
|
||||
language: {
|
||||
emptyTable: 'No wormholes',
|
||||
zeroRecords: 'No wormholes found',
|
||||
lengthMenu: 'Show _MENU_ wormholes',
|
||||
info: 'Showing _START_ to _END_ of _TOTAL_ wormholes'
|
||||
},
|
||||
columnDefs: [],
|
||||
data: null // use DOM data overwrites [] default -> data.loader.js
|
||||
});
|
||||
|
||||
MapUtil.initWormholeInfoTooltip(
|
||||
$(this).find('.' + config.wormholeInfoStaticTableClass),
|
||||
'.' + Util.config.popoverTriggerClass
|
||||
);
|
||||
});
|
||||
|
||||
jumpDialog.initTooltips();
|
||||
|
||||
jumpDialog.modal('show');
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -1,168 +0,0 @@
|
||||
/**
|
||||
* map manual dialog
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox',
|
||||
], ($, Init, Util, bootbox) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
// global dialog
|
||||
dialogNavigationClass: 'pf-dialog-navigation-list', // class for dialog navigation bar
|
||||
dialogNavigationListItemClass: 'pf-dialog-navigation-list-item', // class for map manual li main navigation elements
|
||||
|
||||
// map manual dialog
|
||||
mapManualScrollspyId: 'pf-manual-scrollspy' // id for map manual scrollspy
|
||||
};
|
||||
|
||||
/**
|
||||
* shows the map manual modal dialog
|
||||
*/
|
||||
$.fn.showMapManual = function(){
|
||||
|
||||
requirejs(['text!templates/dialog/map_manual.html', 'mustache'], (template, Mustache) => {
|
||||
let data = {
|
||||
dialogNavigationClass: config.dialogNavigationClass,
|
||||
dialogNavLiClass: config.dialogNavigationListItemClass,
|
||||
scrollspyId: config.mapManualScrollspyId,
|
||||
pieChartClass : Init.classes.pieChart.class,
|
||||
mapCounterClass : Init.classes.pieChart.pieChartMapCounterClass,
|
||||
imgSrcBubble: `${Util.imgRoot()}svg/bubble.svg`,
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
// show dialog
|
||||
let manualDialog = bootbox.dialog({
|
||||
title: 'Manual',
|
||||
message: content,
|
||||
size: 'large',
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'close',
|
||||
className: 'btn-default'
|
||||
}
|
||||
},
|
||||
show: false
|
||||
});
|
||||
|
||||
let dialogEl = manualDialog[0];
|
||||
|
||||
// scroll breakpoints
|
||||
let scrollBreakpointElements = dialogEl.getElementsByClassName('pf-manual-scroll-break');
|
||||
|
||||
// scroll navigation links
|
||||
let scrollNavLiElements = dialogEl.getElementsByClassName(config.dialogNavigationListItemClass);
|
||||
|
||||
let scrollspyElement = dialogEl.querySelector(`#${config.mapManualScrollspyId}`);
|
||||
|
||||
// set navigation button observer
|
||||
let mainNavigationLinks = dialogEl.querySelectorAll(`.${config.dialogNavigationClass} a`);
|
||||
// text anchor links
|
||||
let subNavigationLinks = scrollspyElement.querySelectorAll('a[data-target]');
|
||||
let navigationLinks = [...mainNavigationLinks, ...subNavigationLinks];
|
||||
|
||||
manualDialog.on('shown.bs.modal', e => {
|
||||
// disable on scroll event
|
||||
let disableOnScrollEvent = false;
|
||||
|
||||
let whileScrolling = () => {
|
||||
if(disableOnScrollEvent === false){
|
||||
let scrollOffset = scrollspyElement.mcs.top;
|
||||
for(let [i, scrollBreakpointEl] of Object.entries(scrollBreakpointElements)){
|
||||
let offset = scrollBreakpointEl.offsetTop;
|
||||
if((offset + scrollOffset) > 0){
|
||||
if(!scrollNavLiElements[i].classList.contains('active')){
|
||||
// remove all active classes
|
||||
// -> remove focus on links
|
||||
[...scrollNavLiElements].forEach(el => {
|
||||
el.classList.remove('active');
|
||||
el.getElementsByTagName('a')[0].blur();
|
||||
});
|
||||
|
||||
scrollNavLiElements[i].classList.add('active');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// init scrollbar
|
||||
$(scrollspyElement).mCustomScrollbar({
|
||||
axis: 'y',
|
||||
theme: 'light-3',
|
||||
scrollInertia: 200,
|
||||
autoExpandScrollbar: false,
|
||||
scrollButtons:{
|
||||
enable: true,
|
||||
scrollAmount: 30
|
||||
},
|
||||
advanced: {
|
||||
updateOnContentResize: true
|
||||
},
|
||||
callbacks:{
|
||||
onInit: function(){
|
||||
// init fake-map update counter
|
||||
let counterEl = scrollspyElement.querySelector(`.${data.mapCounterClass}`);
|
||||
$(counterEl).initMapUpdateCounter();
|
||||
|
||||
$(navigationLinks).on('click', function(e){
|
||||
e.preventDefault();
|
||||
disableOnScrollEvent = true;
|
||||
|
||||
// scroll to anchor
|
||||
$(scrollspyElement).mCustomScrollbar('scrollTo', $(this).attr('data-target'));
|
||||
|
||||
let mainNavigationLiElement = this.closest(`.${config.dialogNavigationListItemClass}`);
|
||||
|
||||
whileScrolling();
|
||||
|
||||
// if link is a main navigation link (not an anchor link)
|
||||
if(mainNavigationLiElement){
|
||||
// remove all active classes
|
||||
[...scrollNavLiElements].forEach(el => el.classList.remove('active'));
|
||||
|
||||
// set new active class
|
||||
this.parentNode.classList.add('active');
|
||||
}
|
||||
});
|
||||
},
|
||||
onScroll: function(){
|
||||
disableOnScrollEvent = false;
|
||||
whileScrolling();
|
||||
},
|
||||
whileScrolling: whileScrolling
|
||||
},
|
||||
mouseWheel:{
|
||||
enable: true,
|
||||
scrollAmount: 200,
|
||||
axis: 'y',
|
||||
preventDefault: true // do not scroll parent at the end
|
||||
},
|
||||
scrollbarPosition: 'outsite',
|
||||
autoDraggerLength: true
|
||||
});
|
||||
});
|
||||
|
||||
manualDialog.on('hide.bs.modal', e => {
|
||||
let dialogEl = e.target;
|
||||
|
||||
$(navigationLinks).off('click');
|
||||
|
||||
$(dialogEl.querySelector(`#${config.mapManualScrollspyId}`))
|
||||
.mCustomScrollbar('destroy');
|
||||
});
|
||||
|
||||
// show dialog
|
||||
manualDialog.modal('show');
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,773 +0,0 @@
|
||||
/**
|
||||
* map settings dialogs
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/render',
|
||||
'bootbox',
|
||||
'app/map/util',
|
||||
'app/module_map',
|
||||
'app/map/overlay/util',
|
||||
], ($, Init, Util, Render, bootbox, MapUtil, ModuleMap, MapOverlayUtil) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
// map dialog
|
||||
newMapDialogId: 'pf-map-dialog', // id for map settings dialog
|
||||
dialogMapNewContainerId: 'pf-map-dialog-new', // id for the "new map" container
|
||||
dialogMapEditContainerId: 'pf-map-dialog-edit', // id for the "edit" container
|
||||
dialogMapSettingsContainerId: 'pf-map-dialog-settings', // id for the "settings" container
|
||||
dialogMapDownloadContainerId: 'pf-map-dialog-download', // id for the "download" container
|
||||
|
||||
// new map form
|
||||
newNameInputId: 'pf-map-dialog-new-name-input', // id for "name" input
|
||||
newIconSelectId: 'pf-map-dialog-new-icon-select', // id for "icon" select
|
||||
newScopeSelectId: 'pf-map-dialog-new-scope-select', // id for "scope" select
|
||||
newTypeSelectId: 'pf-map-dialog-new-type-select', // id for "type" select
|
||||
|
||||
// edit map form
|
||||
editNameInputId: 'pf-map-dialog-edit-name-input', // id for "name" input
|
||||
editIconSelectId: 'pf-map-dialog-edit-icon-select', // id for "icon" select
|
||||
editScopeSelectId: 'pf-map-dialog-edit-scope-select', // id for "scope" select
|
||||
editTypeSelectId: 'pf-map-dialog-edit-type-select', // id for "type" select
|
||||
|
||||
// settings map form
|
||||
deleteExpiredConnectionsId: 'pf-map-dialog-delete-connections-expired', // id for "deleteExpiredConnections" checkbox
|
||||
deleteEolConnectionsId: 'pf-map-dialog-delete-connections-eol', // id for "deleteEOLConnections" checkbox
|
||||
persistentAliasesId: 'pf-map-dialog-persistent-aliases', // id for "persistentAliases" checkbox
|
||||
persistentSignaturesId: 'pf-map-dialog-persistent-signatures', // id for "persistentSignatures" checkbox
|
||||
trackAbyssalJumpsId: 'pf-map-dialog-track-abyss-jumps', // id for "trackAbyssalJumps" checkbox
|
||||
|
||||
logHistoryId: 'pf-map-dialog-history', // id for "history logging" checkbox
|
||||
logActivityId: 'pf-map-dialog-activity', // id for "activity" checkbox
|
||||
|
||||
slackWebHookURLId: 'pf-map-dialog-slack-url', // id for Slack "webHookUrl"
|
||||
slackUsernameId: 'pf-map-dialog-slack-username', // id for Slack "username"
|
||||
slackIconId: 'pf-map-dialog-slack-icon', // id for Slack "icon"
|
||||
slackChannelHistoryId: 'pf-map-dialog-slack-channel-history', // id for Slack channel "history"
|
||||
slackChannelRallyId: 'pf-map-dialog-slack-channel-rally', // id for Slack channel "rally"
|
||||
|
||||
discordUsernameId: 'pf-map-dialog-discord-username', // id for Discord "username"
|
||||
discordWebHookURLRallyId: 'pf-map-dialog-discord-url-rally', // id for Discord "rally" webHookUrl
|
||||
discordWebHookURLHistoryId: 'pf-map-dialog-discord-url-history', // id for Discord "history" webHookUrl
|
||||
|
||||
characterSelectId: 'pf-map-dialog-character-select', // id for "character" select
|
||||
corporationSelectId: 'pf-map-dialog-corporation-select', // id for "corporation" select
|
||||
allianceSelectId: 'pf-map-dialog-alliance-select', // id for "alliance" select
|
||||
|
||||
dialogMapExportFormId: 'pf-map-dialog-form-export', // id for "export" form
|
||||
dialogMapImportFormId: 'pf-map-dialog-form-import', // id for "import" form
|
||||
|
||||
buttonExportId: 'pf-map-dialog-button-export', // id for "export" button
|
||||
buttonImportId: 'pf-map-dialog-button-import', // id for "import" button
|
||||
|
||||
fieldExportId: 'pf-map-filename-export', // id for "export" filename field
|
||||
fieldImportId: 'pf-map-filename-import', // id for "import" filename field
|
||||
dialogMapImportInfoId: 'pf-map-import-container', // id for "info" container
|
||||
dragDropElementClass: 'pf-form-dropzone' // class for "drag&drop" zone
|
||||
};
|
||||
|
||||
/**
|
||||
* format a given string into a valid filename
|
||||
* @param filename
|
||||
* @returns {string}
|
||||
*/
|
||||
let formatFilename = function(filename){
|
||||
filename = filename.replace(/[^a-zA-Z0-9]/g,'_');
|
||||
|
||||
let nowDate = new Date();
|
||||
let filenameDate = nowDate.toISOString().slice(0,10).replace(/-/g, '_');
|
||||
|
||||
return (filename + '_' + filenameDate).replace(/__/g,'_');
|
||||
};
|
||||
|
||||
/**
|
||||
* shows the add/edit map dialog
|
||||
* @param mapData
|
||||
* @param options
|
||||
*/
|
||||
$.fn.showMapSettingsDialog = function(mapData, options){
|
||||
|
||||
// check if dialog is already open
|
||||
let mapInfoDialogElement = $('#' + config.newMapDialogId);
|
||||
if(!mapInfoDialogElement.is(':visible')){
|
||||
|
||||
requirejs([
|
||||
'text!templates/dialog/map.html',
|
||||
'text!templates/form/map.html',
|
||||
'mustache'
|
||||
], (templateMapDialog, templateMapForm, Mustache) => {
|
||||
let selectOption = value => () => (val, render) => {
|
||||
if(render(val) === String(value)){
|
||||
return 'selected';
|
||||
}
|
||||
};
|
||||
|
||||
let dialogTitle = 'Map settings';
|
||||
|
||||
// if there are no maps -> hide settings tab
|
||||
let hideEditTab = mapData === false;
|
||||
let hideSettingsTab = mapData === false;
|
||||
let hideDownloadTab = mapData === false;
|
||||
|
||||
let hasRightMapCreate = true;
|
||||
let hasRightMapUpdate = MapUtil ? MapUtil.checkRight('map_update', mapData.config) : true;
|
||||
let hasRightMapExport = MapUtil ? MapUtil.checkRight('map_export', mapData.config) : true;
|
||||
let hasRightMapImport = MapUtil ? MapUtil.checkRight('map_import', mapData.config) : true;
|
||||
let hasRightMapShare = MapUtil ? MapUtil.checkRight('map_share', mapData.config) : true;
|
||||
|
||||
// available map "type" options data
|
||||
// -> for "new" map tab
|
||||
let mapTypesCreate = MapUtil.getMapTypes(true, 'map_create');
|
||||
// -> for "edit" map tab
|
||||
let mapTypesUpdate = MapUtil.getMapTypes(true, 'map_update');
|
||||
|
||||
// render main dialog ---------------------------------------------------------------------------------
|
||||
let mapDialogData = {
|
||||
id: config.newMapDialogId,
|
||||
mapData: mapData,
|
||||
type: mapTypesCreate,
|
||||
select2Class: Util.config.select2Class,
|
||||
|
||||
hasRightMapUpdate,
|
||||
hasRightMapExport,
|
||||
hasRightMapImport,
|
||||
hasRightMapShare,
|
||||
|
||||
// message container
|
||||
formErrorContainerClass: Util.config.formErrorContainerClass,
|
||||
formWarningContainerClass: Util.config.formWarningContainerClass,
|
||||
formInfoContainerClass: Util.config.formInfoContainerClass,
|
||||
|
||||
// default open tab ----------
|
||||
openTabNew: options.tab === 'new',
|
||||
openTabEdit: options.tab === 'edit',
|
||||
openTabSettings: options.tab === 'settings',
|
||||
openTabDownload: options.tab === 'download',
|
||||
|
||||
dialogMapNewContainerId: config.dialogMapNewContainerId,
|
||||
dialogMapEditContainerId: config.dialogMapEditContainerId,
|
||||
dialogMapSettingsContainerId: config.dialogMapSettingsContainerId,
|
||||
dialogMapDownloadContainerId: config.dialogMapDownloadContainerId,
|
||||
|
||||
hideEditTab,
|
||||
hideSettingsTab,
|
||||
hideDownloadTab,
|
||||
|
||||
// settings tab --------------
|
||||
deleteExpiredConnectionsId : config.deleteExpiredConnectionsId,
|
||||
deleteEolConnectionsId : config.deleteEolConnectionsId,
|
||||
persistentAliasesId : config.persistentAliasesId,
|
||||
persistentSignaturesId : config.persistentSignaturesId,
|
||||
trackAbyssalJumpsId : config.trackAbyssalJumpsId,
|
||||
logHistoryId: config.logHistoryId,
|
||||
logActivityId: config.logActivityId,
|
||||
|
||||
deleteExpiredConnections: true,
|
||||
deleteEolConnections: true,
|
||||
persistentAliases: true,
|
||||
persistentSignatures: true,
|
||||
trackAbyssalJumps: true,
|
||||
logActivity: true,
|
||||
logHistory: true,
|
||||
|
||||
slackWebHookURLId: config.slackWebHookURLId,
|
||||
slackUsernameId: config.slackUsernameId,
|
||||
slackIconId: config.slackIconId,
|
||||
slackChannelHistoryId: config.slackChannelHistoryId,
|
||||
slackChannelRallyId: config.slackChannelRallyId,
|
||||
|
||||
slackWebHookURL: '',
|
||||
slackUsername: '',
|
||||
slackIcon: '',
|
||||
slackChannelHistory: '',
|
||||
slackChannelRally: '',
|
||||
slackEnabled: false,
|
||||
slackHistoryEnabled: false,
|
||||
slackRallyEnabled: false,
|
||||
slackSectionShow: false,
|
||||
|
||||
discordUsernameId: config.discordUsernameId,
|
||||
discordWebHookURLRallyId: config.discordWebHookURLRallyId,
|
||||
discordWebHookURLHistoryId: config.discordWebHookURLHistoryId,
|
||||
|
||||
discordUsername: '',
|
||||
discordWebHookURLRally: '',
|
||||
discordWebHookURLHistory: '',
|
||||
discordEnabled: false,
|
||||
discordRallyEnabled: false,
|
||||
discordHistoryEnabled: false,
|
||||
discordSectionShow: false,
|
||||
|
||||
// map access ----------------
|
||||
characterSelectId: config.characterSelectId,
|
||||
corporationSelectId: config.corporationSelectId,
|
||||
allianceSelectId: config.allianceSelectId,
|
||||
|
||||
// access limitations --------
|
||||
maxCharacter: Init.mapTypes.private.defaultConfig.max_shared,
|
||||
maxCorporation: Init.mapTypes.corporation.defaultConfig.max_shared,
|
||||
maxAlliance: Init.mapTypes.alliance.defaultConfig.max_shared,
|
||||
|
||||
accessCharacter: [],
|
||||
accessCorporation: [],
|
||||
accessAlliance: [],
|
||||
|
||||
// download tab --------------
|
||||
dialogMapExportFormId: config.dialogMapExportFormId,
|
||||
dialogMapImportFormId: config.dialogMapImportFormId,
|
||||
buttonExportId: config.buttonExportId,
|
||||
buttonImportId: config.buttonImportId,
|
||||
fieldExportId: config.fieldExportId,
|
||||
fieldImportId: config.fieldImportId,
|
||||
dialogMapImportInfoId: config.dialogMapImportInfoId,
|
||||
|
||||
formatFilename: () => (mapName, render) => formatFilename(render(mapName))
|
||||
};
|
||||
|
||||
if(mapData !== false){
|
||||
Object.assign(mapDialogData, {
|
||||
deleteExpiredConnections: mapData.config.deleteExpiredConnections,
|
||||
deleteEolConnections: mapData.config.deleteEolConnections,
|
||||
persistentAliases: mapData.config.persistentAliases,
|
||||
persistentSignatures: mapData.config.persistentSignatures,
|
||||
trackAbyssalJumps: mapData.config.trackAbyssalJumps,
|
||||
logActivity: mapData.config.logging.activity,
|
||||
logHistory: mapData.config.logging.history,
|
||||
|
||||
slackWebHookURL: mapData.config.logging.slackWebHookURL,
|
||||
slackUsername: mapData.config.logging.slackUsername,
|
||||
slackIcon: mapData.config.logging.slackIcon,
|
||||
slackChannelHistory: mapData.config.logging.slackChannelHistory,
|
||||
slackChannelRally: mapData.config.logging.slackChannelRally,
|
||||
slackEnabled: Boolean(Util.getObjVal(Init, 'slack.status')),
|
||||
|
||||
discordUsername: Util.getObjVal(mapData, 'config.logging.discordUsername'),
|
||||
discordWebHookURLRally: Util.getObjVal(mapData, 'config.logging.discordWebHookURLRally'),
|
||||
discordWebHookURLHistory: Util.getObjVal(mapData, 'config.logging.discordWebHookURLHistory'),
|
||||
discordEnabled: Boolean(Util.getObjVal(Init, 'discord.status')),
|
||||
|
||||
accessCharacter: mapData.config.access.character,
|
||||
accessCorporation: mapData.config.access.corporation,
|
||||
accessAlliance: mapData.config.access.alliance
|
||||
});
|
||||
|
||||
Object.assign(mapDialogData, {
|
||||
// remove "#" from Slack channels
|
||||
slackChannelHistory: mapDialogData.slackChannelHistory.indexOf('#') === 0 ? mapDialogData.slackChannelHistory.substr(1) : mapDialogData.slackChannelHistory,
|
||||
slackChannelRally: mapDialogData.slackChannelRally.indexOf('#') === 0 ? mapDialogData.slackChannelRally.substr(1) : mapDialogData.slackChannelRally,
|
||||
|
||||
slackHistoryEnabled: mapDialogData.slackEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_history_slack_enabled')),
|
||||
slackRallyEnabled: mapDialogData.slackEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_rally_slack_enabled')),
|
||||
slackSectionShow: (mapDialogData.slackEnabled && mapDialogData.slackWebHookURL.length > 0),
|
||||
|
||||
discordRallyEnabled: mapDialogData.discordEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_rally_discord_enabled')),
|
||||
discordHistoryEnabled: mapDialogData.discordEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_history_discord_enabled')),
|
||||
discordSectionShow: (mapDialogData.discordEnabled && (mapDialogData.discordWebHookURLRally.length > 0 || mapDialogData.discordWebHookURLHistory.length > 0)),
|
||||
});
|
||||
}
|
||||
|
||||
let contentDialog = Mustache.render(templateMapDialog, mapDialogData);
|
||||
contentDialog = $(contentDialog);
|
||||
|
||||
// "new map" + "edit map" tab base --------------------------------------------------------------------
|
||||
let mapFormData = {
|
||||
select2Class: Util.config.select2Class,
|
||||
scope: MapUtil.getMapScopes(),
|
||||
icon: MapUtil.getMapIcons(),
|
||||
formErrorContainerClass: Util.config.formErrorContainerClass,
|
||||
formWarningContainerClass: Util.config.formWarningContainerClass,
|
||||
formInfoContainerClass: Util.config.formInfoContainerClass
|
||||
};
|
||||
|
||||
// render "new map" tab content -----------------------------------------------------------------------
|
||||
let mapFormDataNew = Object.assign({}, mapFormData, {
|
||||
type: mapTypesCreate,
|
||||
hasRightMapForm: hasRightMapCreate,
|
||||
nameInputId: config.newNameInputId,
|
||||
iconSelectId: config.newIconSelectId,
|
||||
scopeSelectId: config.newScopeSelectId,
|
||||
typeSelectId: config.newTypeSelectId,
|
||||
|
||||
mapId: 0,
|
||||
mapIcon: undefined,
|
||||
mapName: undefined,
|
||||
mapScopeId: undefined,
|
||||
mapTypeId: undefined
|
||||
});
|
||||
let contentNewMap = Mustache.render(templateMapForm, mapFormDataNew);
|
||||
$('#' + config.dialogMapNewContainerId, contentDialog).html(contentNewMap);
|
||||
|
||||
// render "edit map" tab content ----------------------------------------------------------------------
|
||||
if(!hideEditTab){
|
||||
let mapFormDataEdit = Object.assign({}, mapFormData, {
|
||||
type: mapTypesUpdate,
|
||||
hasRightMapForm: hasRightMapUpdate,
|
||||
nameInputId: config.editNameInputId,
|
||||
iconSelectId: config.editIconSelectId,
|
||||
scopeSelectId: config.editScopeSelectId,
|
||||
typeSelectId: config.editTypeSelectId,
|
||||
|
||||
mapId: mapData.config.id,
|
||||
mapIcon: selectOption(mapData.config.icon),
|
||||
mapName: mapData.config.name,
|
||||
mapScopeId: selectOption(mapData.config.scope.id),
|
||||
mapTypeId: selectOption(mapData.config.type.id)
|
||||
});
|
||||
let contentEditMap = Mustache.render(templateMapForm, mapFormDataEdit);
|
||||
$('#' + config.dialogMapEditContainerId, contentDialog).html(contentEditMap);
|
||||
}
|
||||
|
||||
let mapInfoDialog = bootbox.dialog({
|
||||
title: dialogTitle,
|
||||
message: contentDialog,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
className: 'btn-default'
|
||||
},
|
||||
success: {
|
||||
label: '<i class="fas fa-check fa-fw"></i> save',
|
||||
className: 'btn-success',
|
||||
callback: function(){
|
||||
// get the current active form
|
||||
let form = $('#' + config.newMapDialogId).find('form').filter(':visible');
|
||||
|
||||
// validate form
|
||||
form.validator('validate');
|
||||
|
||||
// validate select2 fields (settings tab)
|
||||
form.find('select').each(function(){
|
||||
let selectField = $(this);
|
||||
let selectValues = selectField.val();
|
||||
|
||||
if(selectValues && selectValues.length > 0){
|
||||
selectField.parents('.form-group').removeClass('has-error');
|
||||
}else{
|
||||
selectField.parents('.form-group').addClass('has-error');
|
||||
}
|
||||
});
|
||||
|
||||
// check whether the form is valid
|
||||
if(form.isValidForm()){
|
||||
// lock dialog
|
||||
let dialogContent = mapInfoDialog.find('.modal-content');
|
||||
dialogContent.showLoadingAnimation();
|
||||
|
||||
// get form data
|
||||
let formData = form.getFormValues();
|
||||
|
||||
// add value prefixes (Slack channels)
|
||||
Object.keys(formData).map((key, index) => {
|
||||
if(['slackChannelHistory', 'slackChannelRally'].includes(key))
|
||||
formData[key] = (formData[key].length ? '#' : '') + formData[key];
|
||||
});
|
||||
|
||||
if(mapData){
|
||||
// no map data found -> probably new user
|
||||
MapOverlayUtil.getMapOverlay(mapData.map.getContainer(), 'timer').startMapUpdateCounter();
|
||||
}
|
||||
|
||||
let method = formData.id ? 'PATCH' : 'PUT';
|
||||
|
||||
Util.request(method, 'Map', formData.id, formData, {
|
||||
formElement: form // for error form messages
|
||||
}, context => {
|
||||
// always do
|
||||
dialogContent.hideLoadingAnimation();
|
||||
}).then(
|
||||
payload => {
|
||||
let mapData = Util.getObjVal(payload, 'data.mapData');
|
||||
Util.showNotify({title: dialogTitle, text: `Map: ${Util.getObjVal(mapData, 'name')}`, type: 'success'});
|
||||
|
||||
// update map-tab Element
|
||||
let tabLinkEls = Util.getMapTabLinkElements(Util.getMapModule()[0], Util.getObjVal(mapData, 'id'));
|
||||
if(tabLinkEls.length === 1){
|
||||
ModuleMap.updateTabData(tabLinkEls[0], mapData);
|
||||
}
|
||||
|
||||
$(mapInfoDialog).modal('hide');
|
||||
Util.triggerMenuAction(document, 'Close');
|
||||
},
|
||||
Util.handleAjaxErrorResponse
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// after modal is shown ===============================================================================
|
||||
mapInfoDialog.on('shown.bs.modal', function(e){
|
||||
mapInfoDialog.initTooltips();
|
||||
|
||||
// manually trigger the "show" event for the initial active tab (not triggered by default...)
|
||||
mapInfoDialog.find('.navbar li.active a[data-toggle=tab]').trigger('shown.bs.tab');
|
||||
|
||||
// prevent "disabled" tabs from being clicked... "bootstrap" bugFix...
|
||||
mapInfoDialog.find('.navbar a[data-toggle=tab]').on('click', function(e){
|
||||
if($(this).hasClass('disabled')){
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// make <select>s to Select2 fields
|
||||
mapInfoDialog.find(
|
||||
'#' + config.dialogMapNewContainerId + ' .' + Util.config.select2Class + ', ' +
|
||||
'#' + config.dialogMapEditContainerId + ' .' + Util.config.select2Class + ', ' +
|
||||
'#' + config.dialogMapDownloadContainerId + ' .' + Util.config.select2Class
|
||||
).select2({
|
||||
minimumResultsForSearch: -1,
|
||||
width: '100%'
|
||||
});
|
||||
|
||||
// set form validator
|
||||
mapInfoDialog.find('form').initFormValidation();
|
||||
|
||||
// show form messages -------------------------------------
|
||||
// get current active form(tab)
|
||||
let form = $('#' + config.newMapDialogId).find('form').filter(':visible');
|
||||
|
||||
form.showFormMessage([{type: 'info', text: 'Creating new maps or change settings may take a few seconds'}]);
|
||||
|
||||
if(!mapData){
|
||||
// no map data found -> probably new user
|
||||
form.showFormMessage([{type: 'warning', text: 'No maps found. Create a new map before you can start'}]);
|
||||
}
|
||||
|
||||
// init "download tab" ============================================================================
|
||||
let downloadTabElement = mapInfoDialog.find('#' + config.dialogMapDownloadContainerId);
|
||||
if(downloadTabElement.length){
|
||||
// tab exists
|
||||
|
||||
// export map data ----------------------------------------------------------------------------
|
||||
downloadTabElement.find('#' + config.buttonExportId).on('click', {mapData}, function(e){
|
||||
|
||||
let exportForm = $('#' + config.dialogMapExportFormId);
|
||||
let validExportForm = exportForm.isValidForm();
|
||||
|
||||
if(validExportForm){
|
||||
let mapElement = Util.getMapModule().getActiveMap();
|
||||
|
||||
if(mapElement){
|
||||
// IMPORTANT: Get map data from client (NOT from global mapData which is available in here)
|
||||
// -> This excludes some data (e.g. wh statics)
|
||||
// -> Bring export inline with main map toggle requests
|
||||
let exportMapData = mapElement.getMapDataFromClient(['hasId']);
|
||||
|
||||
let exportButton = $(this);
|
||||
// set map data right before download
|
||||
setExportMapData(exportButton, exportMapData);
|
||||
|
||||
// disable button
|
||||
exportButton.attr('disabled', true);
|
||||
}else{
|
||||
console.error('Map not found');
|
||||
}
|
||||
}else{
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// import map data ----------------------------------------------------------------------------
|
||||
// check if "FileReader" API is supported
|
||||
let importFormElement = downloadTabElement.find('#' + config.dialogMapImportFormId);
|
||||
if(window.File && window.FileReader && window.FileList && window.Blob){
|
||||
|
||||
// show file info in UI
|
||||
downloadTabElement.find('#' + config.fieldImportId).on('change', function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
let infoContainerElement = importFormElement.find('#' + config.dialogMapImportInfoId);
|
||||
infoContainerElement.hide().empty();
|
||||
importFormElement.hideFormMessage('all');
|
||||
|
||||
let output = [];
|
||||
let files = e.target.files;
|
||||
|
||||
for(let i = 0, f; !!(f = files[i]); i++){
|
||||
output.push(( i + 1 ) + '. file: ' + f.name + ' - ' +
|
||||
f.size + ' bytes; last modified: ' +
|
||||
f.lastModifiedDate.toLocaleDateString() );
|
||||
}
|
||||
|
||||
if(output.length > 0){
|
||||
infoContainerElement.html( output ).show();
|
||||
}
|
||||
|
||||
importFormElement.validator('validate');
|
||||
});
|
||||
|
||||
// drag&drop
|
||||
let importData = {};
|
||||
importData.mapData = [];
|
||||
let files = [];
|
||||
let filesCount = 0;
|
||||
let filesCountFail = 0;
|
||||
|
||||
// onLoad for FileReader API
|
||||
let readerOnLoad = function(readEvent){
|
||||
|
||||
// get file content
|
||||
try{
|
||||
importData.mapData.push( JSON.parse( readEvent.target.result ) );
|
||||
}catch(error){
|
||||
filesCountFail++;
|
||||
importFormElement.showFormMessage([{type: 'error', text: 'File can not be parsed'}]);
|
||||
}
|
||||
|
||||
// start import when all files are parsed
|
||||
if(
|
||||
filesCount === files.length &&
|
||||
filesCountFail === 0
|
||||
){
|
||||
importMaps(importData, () => {
|
||||
mapInfoDialog.modal('hide');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let handleDragOver = function(dragEvent){
|
||||
dragEvent.stopPropagation();
|
||||
dragEvent.preventDefault();
|
||||
dragEvent.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
|
||||
};
|
||||
|
||||
let handleFileSelect = function(evt){
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
|
||||
importData = importFormElement.getFormValues();
|
||||
importData.mapData = [];
|
||||
filesCount = 0;
|
||||
filesCountFail = 0;
|
||||
|
||||
files = evt.dataTransfer.files; // FileList object.
|
||||
|
||||
for(let file; !!(file = files[filesCount]); filesCount++){
|
||||
let reader = new FileReader();
|
||||
reader.onload = readerOnLoad;
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
let dropZone = downloadTabElement.find('.' + config.dragDropElementClass);
|
||||
if(dropZone.length){
|
||||
dropZone[0].addEventListener('dragover', handleDragOver, false);
|
||||
dropZone[0].addEventListener('drop', handleFileSelect, false);
|
||||
}
|
||||
|
||||
// import "button"
|
||||
downloadTabElement.find('#' + config.buttonImportId).on('click', function(e){
|
||||
|
||||
importFormElement.validator('validate');
|
||||
let validImportForm = importFormElement.isValidForm();
|
||||
|
||||
if(validImportForm){
|
||||
importData = importFormElement.getFormValues();
|
||||
importData.mapData = [];
|
||||
|
||||
let fileElement = downloadTabElement.find('#' + config.fieldImportId);
|
||||
files = fileElement[0].files;
|
||||
filesCount = 0;
|
||||
filesCountFail = 0;
|
||||
|
||||
for(let file; !!(file = files[filesCount]); filesCount++){
|
||||
let reader = new FileReader();
|
||||
reader.onload = readerOnLoad;
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
}else{
|
||||
importFormElement.showFormMessage([{type: 'error', text: 'The File APIs are not fully supported in this browser.'}]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// events for tab change ------------------------------------------------------------------------------
|
||||
mapInfoDialog.find('.navbar a').on('shown.bs.tab', function(e){
|
||||
let modalDialog = mapInfoDialog.find('div.modal-dialog');
|
||||
let tabContentId = $(e.target).attr('href');
|
||||
let tabContentForms = $(tabContentId).find('form.form-horizontal');
|
||||
let selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
|
||||
let selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
|
||||
let selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
|
||||
|
||||
if(tabContentId === '#' + config.dialogMapSettingsContainerId){
|
||||
// "settings" tab -> resize modal
|
||||
modalDialog.toggleClass('modal-lg', true);
|
||||
initSettingsSelectFields(mapInfoDialog);
|
||||
}else{
|
||||
// resize modal
|
||||
modalDialog.toggleClass('modal-lg', false);
|
||||
|
||||
if( $(selectElementCharacter).data('select2') !== undefined ){
|
||||
$(selectElementCharacter).select2('destroy');
|
||||
}
|
||||
|
||||
if( $(selectElementCorporation).data('select2') !== undefined ){
|
||||
$(selectElementCorporation).select2('destroy');
|
||||
}
|
||||
|
||||
if( $(selectElementAlliance).data('select2') !== undefined ){
|
||||
$(selectElementAlliance).select2('destroy');
|
||||
}
|
||||
}
|
||||
|
||||
// no "save" dialog button on "in/export" tab
|
||||
if(
|
||||
tabContentId === '#' + config.dialogMapDownloadContainerId || // no "save" dialog button on "in/export" tab
|
||||
!tabContentForms.length // no <form> in tab (e.g. restricted by missing right)
|
||||
){
|
||||
mapInfoDialog.find('button.btn-success').hide();
|
||||
}else{
|
||||
mapInfoDialog.find('button.btn-success').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* import new map(s) data
|
||||
* @param importData
|
||||
* @param callback
|
||||
*/
|
||||
let importMaps = (importData, callback) => {
|
||||
let importForm = $('#' + config.dialogMapImportFormId);
|
||||
importForm.hideFormMessage('all');
|
||||
|
||||
// lock dialog
|
||||
let dialogContent = importForm.parents('.modal-content');
|
||||
dialogContent.showLoadingAnimation();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.importMap,
|
||||
data: importData,
|
||||
dataType: 'json'
|
||||
}).done(function(responseData){
|
||||
if(responseData.error.length){
|
||||
// form.showFormMessage(responseData.error);
|
||||
importForm.showFormMessage(responseData.error);
|
||||
}else{
|
||||
// success
|
||||
if(responseData.warning.length){
|
||||
importForm.showFormMessage(responseData.warning);
|
||||
}
|
||||
|
||||
if(callback){
|
||||
callback();
|
||||
}
|
||||
|
||||
Util.showNotify({title: 'Import finished', text: 'Map(s) imported', type: 'success'});
|
||||
}
|
||||
}).fail(function(jqXHR, status, error){
|
||||
let reason = status + ' ' + error;
|
||||
Util.showNotify({title: jqXHR.status + ': importMap', text: reason, type: 'error'});
|
||||
}).always(function(){
|
||||
importForm.find('input, select').resetFormFields().trigger('change');
|
||||
dialogContent.hideLoadingAnimation();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* set json map data for export to an element (e.g. <a>-Tag or button) for download
|
||||
* @param element
|
||||
* @param mapData
|
||||
*/
|
||||
let setExportMapData = (element, mapData) => {
|
||||
|
||||
let fieldExport = $('#' + config.fieldExportId);
|
||||
let filename = '';
|
||||
let mapDataEncoded = '';
|
||||
|
||||
if(fieldExport.length){
|
||||
filename = fieldExport.val();
|
||||
|
||||
if(filename.length > 0){
|
||||
mapDataEncoded = 'text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify( mapData ));
|
||||
}
|
||||
}
|
||||
|
||||
element.attr('href', 'data:' + mapDataEncoded);
|
||||
element.attr('download', filename + '.json');
|
||||
};
|
||||
|
||||
/**
|
||||
* init select2 fields within the settings dialog
|
||||
* @param mapInfoDialog
|
||||
*/
|
||||
let initSettingsSelectFields = mapInfoDialog => {
|
||||
|
||||
let selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
|
||||
let selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
|
||||
let selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
|
||||
|
||||
// init character select live search
|
||||
selectElementCharacter.initAccessSelect({
|
||||
type: 'character',
|
||||
maxSelectionLength: Init.mapTypes.private.defaultConfig.max_shared
|
||||
});
|
||||
|
||||
// init corporation select live search
|
||||
selectElementCorporation.initAccessSelect({
|
||||
type: 'corporation',
|
||||
maxSelectionLength: Init.mapTypes.corporation.defaultConfig.max_shared
|
||||
});
|
||||
|
||||
// init alliance select live search
|
||||
selectElementAlliance.initAccessSelect({
|
||||
type: 'alliance',
|
||||
maxSelectionLength: Init.mapTypes.alliance.defaultConfig.max_shared
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* shows the delete map Dialog
|
||||
* @param mapData
|
||||
*/
|
||||
$.fn.showDeleteMapDialog = function(mapData){
|
||||
let mapId = Util.getObjVal(mapData, 'config.id');
|
||||
let mapName = Util.getObjVal(mapData, 'config.name');
|
||||
if(!mapId) return;
|
||||
|
||||
let mapNameStr = `<span class="txt-color txt-color-danger">${mapName}</span>`;
|
||||
|
||||
let mapDeleteDialog = bootbox.confirm({
|
||||
message: `Delete map "${mapNameStr}"?`,
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: '<i class="fas fa-trash fa-fw"></i> delete map',
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
callback: result => {
|
||||
if(result){
|
||||
// lock dialog
|
||||
let dialogContent = mapDeleteDialog.find('.modal-content');
|
||||
dialogContent.showLoadingAnimation();
|
||||
|
||||
Util.request('DELETE', 'Map', mapId, {}, {}).then(
|
||||
payload => {
|
||||
Util.showNotify({title: 'Map deleted', text: 'Map: ' + mapName, type: 'success'});
|
||||
},
|
||||
Util.handleAjaxErrorResponse
|
||||
).finally(() => mapDeleteDialog.modal('hide'));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* notification dialog
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox'
|
||||
], ($, Init, Util, bootbox) => {
|
||||
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
|
||||
// shutdown dialog
|
||||
notificationDialogId: 'pf-notification-dialog', // id for "notification" dialog
|
||||
notificationDialogClass: 'pf-notification-dialog' // class for "notification" dialog
|
||||
};
|
||||
|
||||
/**
|
||||
* show/animate dialog page content
|
||||
* @param dialog
|
||||
*/
|
||||
let showPageContent = function(dialog){
|
||||
let headlineElement = dialog.find('h1');
|
||||
|
||||
headlineElement.delay(300).velocity('transition.shrinkIn', {
|
||||
duration: 500
|
||||
}).delay(500);
|
||||
|
||||
headlineElement.velocity({
|
||||
scale: 1.05
|
||||
}, {
|
||||
duration: 600,
|
||||
loop: 5
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* show "notification" dialog
|
||||
* @param dialogData
|
||||
*/
|
||||
$.fn.showNotificationDialog = function(dialogData){
|
||||
|
||||
// check if there is already a notification dialog open
|
||||
let notificationDialogClassDialoges = $('.' + config.notificationDialogClass);
|
||||
|
||||
if(notificationDialogClassDialoges.length === 0){
|
||||
|
||||
// close all modals
|
||||
$('.modal').modal('hide');
|
||||
|
||||
requirejs(['text!templates/dialog/notification.html', 'mustache'], function(template, Mustache){
|
||||
|
||||
let data = {
|
||||
id: config.notificationDialogId,
|
||||
content: dialogData.content
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
// show dialog
|
||||
let shutdownDialog = bootbox.dialog({
|
||||
title: dialogData.content.title,
|
||||
message: content,
|
||||
className: config.notificationDialogClass,
|
||||
buttons: dialogData.buttons
|
||||
});
|
||||
|
||||
|
||||
shutdownDialog.on('shown.bs.modal', function(e){
|
||||
// remove close button
|
||||
let dialog = $(this);
|
||||
|
||||
dialog.find('.bootbox-close-button').remove();
|
||||
dialog.find('button').blur();
|
||||
|
||||
// show error message
|
||||
showPageContent(dialog);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* shortcuts dialog
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox',
|
||||
'app/key',
|
||||
], function($, Init, Util, bootbox, Key){
|
||||
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
// map dialog
|
||||
shortcutsDialogId: 'pf-shortcuts-dialog', // id for shortcuts dialog
|
||||
};
|
||||
|
||||
/**
|
||||
* shows the map manual modal dialog
|
||||
*/
|
||||
$.fn.showShortcutsDialog = function(){
|
||||
requirejs(['text!templates/dialog/shortcuts.html', 'mustache'], function(template, Mustache){
|
||||
|
||||
let data = {
|
||||
id: config.shortcutsDialogId,
|
||||
shortcuts: Key.getGroupedShortcuts()
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
// show dialog
|
||||
let shortcutsDialog = bootbox.dialog({
|
||||
title: 'Keyboard Shortcuts',
|
||||
message: content,
|
||||
size: 'large',
|
||||
buttons: {
|
||||
success: {
|
||||
label: 'close',
|
||||
className: 'btn-default'
|
||||
}
|
||||
},
|
||||
show: true
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -1,871 +0,0 @@
|
||||
/**
|
||||
* activity stats dialog
|
||||
*/
|
||||
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/render',
|
||||
'app/counter',
|
||||
'bootbox',
|
||||
'peityInlineChart'
|
||||
], ($, Init, Util, Render, Counter, bootbox) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
// dialog
|
||||
statsDialogId: 'pf-stats-dialog', // id for "stats" dialog
|
||||
dialogNavigationClass: 'pf-dialog-navigation-list', // class for dialog navigation bar
|
||||
dialogNavigationListItemClass: 'pf-dialog-navigation-list-item', // class for map manual li main navigation elements
|
||||
|
||||
dialogNavigationOffsetClass : 'pf-dialog-navigation-offset', // class for "current" offset filter
|
||||
dialogNavigationPrevClass : 'pf-dialog-navigation-prev', // class for "prev" period load
|
||||
dialogNavigationNextClass : 'pf-dialog-navigation-next', // class for "next" period load
|
||||
|
||||
// stats/dataTable
|
||||
statsContainerId: 'pf-stats-dialog-container', // class for statistics container (dynamic ajax content)
|
||||
statsTableId: 'pf-stats-table', // id for statistics table element
|
||||
tableCellImageClass: 'pf-table-image-cell', // class for table "image" cells
|
||||
moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head
|
||||
|
||||
// charts
|
||||
statsLineChartClass: 'pf-line-chart' // class for inline chart elements
|
||||
};
|
||||
|
||||
/**
|
||||
* init blank statistics dataTable
|
||||
* @param dialogElement
|
||||
*/
|
||||
let initStatsTable = dialogElement => {
|
||||
let columnNumberWidth = 28;
|
||||
let cellPadding = 4;
|
||||
let lineChartWidth = columnNumberWidth + (2 * cellPadding);
|
||||
let lineColor = '#477372';
|
||||
|
||||
// render function for inline-chart columns
|
||||
let renderInlineChartColumn = function(data, type, row, meta){
|
||||
/*
|
||||
switch(data.type){
|
||||
case 'C': lineColor = '#5cb85c'; break;
|
||||
case 'U': lineColor = '#e28a0d'; break;
|
||||
case 'D': lineColor = '#a52521'; break;
|
||||
}*/
|
||||
|
||||
if( /^\d+$/.test(data.data) ){
|
||||
// single digit (e.g. single week filter)
|
||||
return data.data;
|
||||
}else{
|
||||
// period -> prepare line chart
|
||||
return '<span class="' + config.statsLineChartClass + '" data-peity=\'{ "stroke": "' + lineColor + '" }\'>' + data.data + '</span>';
|
||||
}
|
||||
};
|
||||
|
||||
// render function for numeric columns
|
||||
let renderNumericColumn = function(data, type, row, meta){
|
||||
let value = data;
|
||||
if(type === 'display'){
|
||||
value = data.toLocaleString();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
// get table element
|
||||
// Due to "complex" table headers, they are already rendered and part of the stats.html file
|
||||
let table = dialogElement.find('#' + config.statsTableId);
|
||||
|
||||
let statsTable = table.DataTable({
|
||||
dom: '<"flex-row flex-between"<"flex-col"l><"flex-col"B><"flex-col"fS>>' +
|
||||
'<"flex-row"<"flex-col flex-grow"tr>>' +
|
||||
'<"flex-row flex-between"<"flex-col"i><"flex-col"p>>',
|
||||
buttons: {
|
||||
name: 'tableTools',
|
||||
buttons: [
|
||||
{
|
||||
extend: 'copy',
|
||||
tag: 'a',
|
||||
className: config.moduleHeadlineIconClass,
|
||||
text: '<i class="fas fa-fw fa-copy"></i> copy',
|
||||
exportOptions: { orthogonal: 'filter' }
|
||||
},
|
||||
{
|
||||
extend: 'csv',
|
||||
tag: 'a',
|
||||
className: config.moduleHeadlineIconClass,
|
||||
text: '<i class="fas fa-fw fa-download"></i> csv',
|
||||
exportOptions: { orthogonal: 'filter' }
|
||||
}
|
||||
]
|
||||
},
|
||||
pageLength: 30,
|
||||
lengthMenu: [[10, 20, 30, 50], [10, 20, 30, 50]],
|
||||
paging: true,
|
||||
ordering: true,
|
||||
order: [ 20, 'desc' ],
|
||||
info: true,
|
||||
searching: true,
|
||||
hover: false,
|
||||
language: {
|
||||
emptyTable: 'No statistics found',
|
||||
zeroRecords: 'No characters found',
|
||||
lengthMenu: 'Show _MENU_ characters',
|
||||
info: 'Showing _START_ to _END_ of _TOTAL_ characters'
|
||||
},
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 0,
|
||||
name: 'rowIndex',
|
||||
title: '<i class="fas fa-hashtag"></i>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: 10,
|
||||
class: 'text-right',
|
||||
data: 'character.id'
|
||||
},{
|
||||
targets: 1,
|
||||
name: 'image',
|
||||
title: '',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: 26,
|
||||
className: ['text-center', config.tableCellImageClass].join(' '),
|
||||
data: 'character',
|
||||
render: {
|
||||
_: function(data, type, row, meta){
|
||||
return '<img src="' + Util.eveImageUrl('characters', parseInt(data.id)) + '"/>';
|
||||
}
|
||||
}
|
||||
},{
|
||||
targets: 2,
|
||||
name: 'name',
|
||||
title: 'name',
|
||||
width: 200,
|
||||
data: 'character',
|
||||
render: {
|
||||
_: 'name',
|
||||
sort: 'name'
|
||||
}
|
||||
},{
|
||||
targets: 3,
|
||||
name: 'lastLogin',
|
||||
title: 'last login',
|
||||
searchable: false,
|
||||
width: 70,
|
||||
className: ['text-right', 'separator-right'].join(' '),
|
||||
data: 'character.lastLogin',
|
||||
defaultContent: ''
|
||||
},{
|
||||
targets: 4,
|
||||
name: 'mapCreate',
|
||||
title: '<span title="created" data-toggle="tooltip">C </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'mapCreate',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 5,
|
||||
name: 'mapUpdate',
|
||||
title: '<span title="updated" data-toggle="tooltip">U </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'mapUpdate',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 6,
|
||||
name: 'mapDelete',
|
||||
title: '<span title="deleted" data-toggle="tooltip">D </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'mapDelete',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 7,
|
||||
name: 'mapSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
className: ['text-right', 'separator-right'].join(' ') ,
|
||||
data: 'mapSum',
|
||||
render: {
|
||||
_: renderNumericColumn
|
||||
}
|
||||
},{
|
||||
targets: 8,
|
||||
name: 'systemCreate',
|
||||
title: '<span title="created" data-toggle="tooltip">C </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'systemCreate',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 9,
|
||||
name: 'systemUpdate',
|
||||
title: '<span title="updated" data-toggle="tooltip">U </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'systemUpdate',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 10,
|
||||
name: 'systemDelete',
|
||||
title: '<span title="deleted" data-toggle="tooltip">D </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'systemDelete',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 11,
|
||||
name: 'systemSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
className: ['text-right', 'separator-right'].join(' ') ,
|
||||
data: 'systemSum',
|
||||
render: {
|
||||
_: renderNumericColumn
|
||||
}
|
||||
},{
|
||||
targets: 12,
|
||||
name: 'connectionCreate',
|
||||
title: '<span title="created" data-toggle="tooltip">C </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'connectionCreate',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 13,
|
||||
name: 'connectionUpdate',
|
||||
title: '<span title="updated" data-toggle="tooltip">U </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'connectionUpdate',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 14,
|
||||
name: 'connectionDelete',
|
||||
title: '<span title="deleted" data-toggle="tooltip">D </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'connectionDelete',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 15,
|
||||
name: 'connectionSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
className: ['text-right', 'separator-right'].join(' '),
|
||||
data: 'connectionSum',
|
||||
render: {
|
||||
_: renderNumericColumn
|
||||
}
|
||||
},{
|
||||
targets: 16,
|
||||
name: 'signatureCreate',
|
||||
title: '<span title="created" data-toggle="tooltip">C </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'signatureCreate',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 17,
|
||||
name: 'signatureUpdate',
|
||||
title: '<span title="updated" data-toggle="tooltip">U </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'signatureUpdate',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 18,
|
||||
name: 'signatureDelete',
|
||||
title: '<span title="deleted" data-toggle="tooltip">D </span>',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
width: columnNumberWidth,
|
||||
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
|
||||
data: 'signatureDelete',
|
||||
render: {
|
||||
_: renderInlineChartColumn
|
||||
}
|
||||
},{
|
||||
targets: 19,
|
||||
name: 'signatureSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
className: ['text-right', 'separator-right'].join(' '),
|
||||
data: 'signatureSum',
|
||||
render: {
|
||||
_: renderNumericColumn
|
||||
}
|
||||
},{
|
||||
targets: 20,
|
||||
name: 'totalSum',
|
||||
title: 'Σ ',
|
||||
searchable: false,
|
||||
width: 20,
|
||||
className: 'text-right',
|
||||
data: 'totalSum',
|
||||
render: {
|
||||
_: renderNumericColumn
|
||||
}
|
||||
}
|
||||
],
|
||||
initComplete: function(settings){
|
||||
let tableApi = this.api();
|
||||
|
||||
// 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){
|
||||
$($(row).find('.' + config.statsLineChartClass)).peity('line', {
|
||||
fill: 'transparent',
|
||||
height: 18,
|
||||
min: 0,
|
||||
width: lineChartWidth
|
||||
});
|
||||
});
|
||||
},
|
||||
footerCallback: function(row, data, start, end, display ){
|
||||
let api = this.api();
|
||||
let sumColumnIndexes = [7, 11, 15, 19, 20];
|
||||
|
||||
// column data for "sum" columns over this page
|
||||
let pageTotalColumns = api
|
||||
.columns( sumColumnIndexes, { page: 'current'} )
|
||||
.data();
|
||||
|
||||
// sum columns for "total" sum
|
||||
pageTotalColumns.each(function(colData, index){
|
||||
pageTotalColumns[index] = colData.reduce(function(a, b){
|
||||
return a + b;
|
||||
}, 0);
|
||||
});
|
||||
|
||||
$(sumColumnIndexes).each(function(index, value){
|
||||
$(api.column(value).footer()).text( renderNumericColumn(pageTotalColumns[index], 'display') );
|
||||
});
|
||||
},
|
||||
data: [] // will be added dynamic
|
||||
});
|
||||
|
||||
statsTable.on('order.dt search.dt', function(){
|
||||
statsTable.column(0, {search:'applied', order:'applied'}).nodes().each(function(cell, i){
|
||||
let rowCount = i + 1;
|
||||
let content = '';
|
||||
switch(rowCount){
|
||||
case 1: content = '<i class="fas fa-fw fa-trophy txt-color txt-color-gold"></i>'; break;
|
||||
case 2: content = '<i class="fas fa-fw fa-trophy txt-color txt-color-silver"></i>'; break;
|
||||
case 3: content = '<i class="fas fa-fw fa-trophy txt-color txt-color-bronze"></i>'; break;
|
||||
default: content = rowCount + '. ';
|
||||
}
|
||||
|
||||
$(cell).html(content);
|
||||
});
|
||||
}).draw();
|
||||
|
||||
let tooltipElements = dialogElement.find('[data-toggle="tooltip"]');
|
||||
tooltipElements.tooltip();
|
||||
};
|
||||
|
||||
/**
|
||||
* request raw statistics data and execute callback
|
||||
* @param requestData
|
||||
* @param context
|
||||
*/
|
||||
let getStatsData = (requestData, context) => {
|
||||
context.dynamicArea = $('#' + config.statsContainerId + ' .' + Util.config.dynamicAreaClass);
|
||||
context.dynamicArea.showLoadingAnimation();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: Init.path.getStatisticsData,
|
||||
data: requestData,
|
||||
dataType: 'json',
|
||||
context: context
|
||||
}).done(function(data){
|
||||
this.dynamicArea.hideLoadingAnimation();
|
||||
|
||||
this.callback(data, this);
|
||||
}).fail(function(jqXHR, status, error){
|
||||
let reason = status + ' ' + error;
|
||||
Util.showNotify({title: jqXHR.status + ': loadStatistics', text: reason, type: 'warning'});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* update dataTable with response data
|
||||
* update "header"/"filter" elements in dialog
|
||||
* @param responseData
|
||||
*/
|
||||
let drawStatsTable = (responseData, context) => {
|
||||
let dialogElement = $('#' + config.statsDialogId);
|
||||
|
||||
// update filter/header -----------------------------------------------------------------------------
|
||||
let navigationListElements = $('.' + config.dialogNavigationClass);
|
||||
navigationListElements.find('a[data-type="typeId"][data-value="' + responseData.typeId + '"]').tab('show');
|
||||
navigationListElements.find('a[data-type="period"][data-value="' + responseData.period + '"]').tab('show');
|
||||
|
||||
// update period pagination -------------------------------------------------------------------------
|
||||
let prevButton = dialogElement.find('.' + config.dialogNavigationPrevClass);
|
||||
prevButton.data('newOffset', responseData.prev);
|
||||
prevButton.find('span').text('Week ' + responseData.prev.week + ', ' + responseData.prev.year);
|
||||
prevButton.css('visibility', 'visible');
|
||||
|
||||
let nextButton = dialogElement.find('.' + config.dialogNavigationNextClass);
|
||||
if(responseData.next){
|
||||
nextButton.data('newOffset', responseData.next);
|
||||
nextButton.find('span').text('Week ' + responseData.next.week + ', ' + responseData.next.year);
|
||||
nextButton.css('visibility', 'visible');
|
||||
}else{
|
||||
nextButton.css('visibility', 'hidden');
|
||||
}
|
||||
|
||||
// update current period information label ----------------------------------------------------------
|
||||
// if period == "weekly" there is no "offset" -> just a single week
|
||||
let offsetText = 'Week ' + responseData.start.week + ', ' + responseData.start.year;
|
||||
if(responseData.period !== 'weekly'){
|
||||
offsetText += ' <small><i class="fas fa-fw fa-minus"></i></small> ' +
|
||||
'Week ' + responseData.offset.week + ', ' + responseData.offset.year;
|
||||
}
|
||||
dialogElement.find('.' + config.dialogNavigationOffsetClass)
|
||||
.data('start', responseData.start)
|
||||
.data('period', responseData.period)
|
||||
.html(offsetText);
|
||||
|
||||
// clear and (re)-fill table ------------------------------------------------------------------------
|
||||
let formattedData = formatStatisticsData(responseData);
|
||||
context.tableApi.clear();
|
||||
context.tableApi.rows.add(formattedData).draw();
|
||||
};
|
||||
|
||||
/**
|
||||
* format statistics data for dataTable
|
||||
* -> e.g. format inline-chart data
|
||||
* @param statsData
|
||||
* @returns {Array}
|
||||
*/
|
||||
let formatStatisticsData = statsData => {
|
||||
let formattedData = [];
|
||||
let yearStart = statsData.start.year;
|
||||
let weekStart = statsData.start.week;
|
||||
let weekCount = statsData.weekCount;
|
||||
let yearWeeks = statsData.yearWeeks;
|
||||
|
||||
let tempRand = function(min, max){
|
||||
return Math.random() * (max - min) + min;
|
||||
};
|
||||
|
||||
// format/sum week statistics data for inline charts
|
||||
let formatWeekData = function(weeksData){
|
||||
let currentYear = yearStart;
|
||||
let currentWeek = weekStart;
|
||||
|
||||
let formattedWeeksData = {
|
||||
mapCreate: [],
|
||||
mapUpdate: [],
|
||||
mapDelete: [],
|
||||
systemCreate: [],
|
||||
systemUpdate: [],
|
||||
systemDelete: [],
|
||||
connectionCreate: [],
|
||||
connectionUpdate: [],
|
||||
connectionDelete: [],
|
||||
signatureCreate: [],
|
||||
signatureUpdate: [],
|
||||
signatureDelete: [],
|
||||
mapSum: 0,
|
||||
systemSum: 0,
|
||||
connectionSum: 0,
|
||||
signatureSum: 0
|
||||
};
|
||||
|
||||
for(let i = 0; i < weekCount; i++){
|
||||
let yearWeekProp = currentYear + '' + currentWeek;
|
||||
|
||||
if(weeksData.hasOwnProperty( yearWeekProp )){
|
||||
let weekData = weeksData[ yearWeekProp ];
|
||||
|
||||
// map ----------------------------------------------------------------------------------
|
||||
formattedWeeksData.mapCreate.push( weekData.mapCreate );
|
||||
formattedWeeksData.mapSum += parseInt( weekData.mapCreate );
|
||||
|
||||
formattedWeeksData.mapUpdate.push( weekData.mapUpdate );
|
||||
formattedWeeksData.mapSum += parseInt( weekData.mapUpdate );
|
||||
|
||||
formattedWeeksData.mapDelete.push( weekData.mapDelete );
|
||||
formattedWeeksData.mapSum += parseInt( weekData.mapDelete );
|
||||
|
||||
// system -------------------------------------------------------------------------------
|
||||
formattedWeeksData.systemCreate.push( weekData.systemCreate );
|
||||
formattedWeeksData.systemSum += parseInt( weekData.systemCreate );
|
||||
|
||||
formattedWeeksData.systemUpdate.push( weekData.systemUpdate );
|
||||
formattedWeeksData.systemSum += parseInt( weekData.systemUpdate );
|
||||
|
||||
formattedWeeksData.systemDelete.push( weekData.systemDelete );
|
||||
formattedWeeksData.systemSum += parseInt( weekData.systemDelete );
|
||||
|
||||
// connection ---------------------------------------------------------------------------
|
||||
formattedWeeksData.connectionCreate.push( weekData.connectionCreate );
|
||||
formattedWeeksData.connectionSum += parseInt( weekData.connectionCreate );
|
||||
|
||||
formattedWeeksData.connectionUpdate.push( weekData.connectionUpdate );
|
||||
formattedWeeksData.connectionSum += parseInt( weekData.connectionUpdate );
|
||||
|
||||
formattedWeeksData.connectionDelete.push( weekData.connectionDelete );
|
||||
formattedWeeksData.connectionSum += parseInt( weekData.connectionDelete );
|
||||
|
||||
// signature ----------------------------------------------------------------------------
|
||||
formattedWeeksData.signatureCreate.push( weekData.signatureCreate );
|
||||
formattedWeeksData.signatureSum += parseInt( weekData.signatureCreate );
|
||||
|
||||
formattedWeeksData.signatureUpdate.push( weekData.signatureUpdate );
|
||||
formattedWeeksData.signatureSum += parseInt( weekData.signatureUpdate );
|
||||
|
||||
formattedWeeksData.signatureDelete.push( weekData.signatureDelete );
|
||||
formattedWeeksData.signatureSum += parseInt( weekData.signatureDelete );
|
||||
}else{
|
||||
// map -------------------------------------------------------------------------------
|
||||
formattedWeeksData.mapCreate.push(0);
|
||||
formattedWeeksData.mapUpdate.push(0);
|
||||
formattedWeeksData.mapDelete.push(0);
|
||||
|
||||
// system -------------------------------------------------------------------------------
|
||||
formattedWeeksData.systemCreate.push(0);
|
||||
formattedWeeksData.systemUpdate.push(0);
|
||||
formattedWeeksData.systemDelete.push(0);
|
||||
|
||||
// connection ---------------------------------------------------------------------------
|
||||
formattedWeeksData.connectionCreate.push(0);
|
||||
formattedWeeksData.connectionUpdate.push(0);
|
||||
formattedWeeksData.connectionDelete.push(0);
|
||||
|
||||
// signature ----------------------------------------------------------------------------
|
||||
formattedWeeksData.signatureCreate.push(0);
|
||||
formattedWeeksData.signatureUpdate.push(0);
|
||||
formattedWeeksData.signatureDelete.push(0);
|
||||
}
|
||||
|
||||
currentWeek++;
|
||||
|
||||
if( currentWeek > yearWeeks[currentYear] ){
|
||||
currentWeek = 1;
|
||||
currentYear++;
|
||||
}
|
||||
}
|
||||
|
||||
// map ---------------------------------------------------------------------------------------
|
||||
formattedWeeksData.mapCreate = formattedWeeksData.mapCreate.join(',');
|
||||
formattedWeeksData.mapUpdate = formattedWeeksData.mapUpdate.join(',');
|
||||
formattedWeeksData.mapDelete = formattedWeeksData.mapDelete.join(',');
|
||||
|
||||
// system ---------------------------------------------------------------------------------------
|
||||
formattedWeeksData.systemCreate = formattedWeeksData.systemCreate.join(',');
|
||||
formattedWeeksData.systemUpdate = formattedWeeksData.systemUpdate.join(',');
|
||||
formattedWeeksData.systemDelete = formattedWeeksData.systemDelete.join(',');
|
||||
|
||||
// connection -----------------------------------------------------------------------------------
|
||||
formattedWeeksData.connectionCreate = formattedWeeksData.connectionCreate.join(',');
|
||||
formattedWeeksData.connectionUpdate = formattedWeeksData.connectionUpdate.join(',');
|
||||
formattedWeeksData.connectionDelete = formattedWeeksData.connectionDelete.join(',');
|
||||
|
||||
// signature ------------------------------------------------------------------------------------
|
||||
formattedWeeksData.signatureCreate = formattedWeeksData.signatureCreate.join(',');
|
||||
formattedWeeksData.signatureUpdate = formattedWeeksData.signatureUpdate.join(',');
|
||||
formattedWeeksData.signatureDelete = formattedWeeksData.signatureDelete.join(',');
|
||||
|
||||
return formattedWeeksData;
|
||||
};
|
||||
|
||||
$.each(statsData.statistics, function(characterId, data){
|
||||
|
||||
let formattedWeeksData = formatWeekData(data.weeks);
|
||||
|
||||
let rowData = {
|
||||
character: {
|
||||
id: characterId,
|
||||
name: data.name,
|
||||
lastLogin: data.lastLogin
|
||||
},
|
||||
mapCreate: {
|
||||
type: 'C',
|
||||
data: formattedWeeksData.mapCreate
|
||||
},
|
||||
mapUpdate: {
|
||||
type: 'U',
|
||||
data: formattedWeeksData.mapUpdate
|
||||
},
|
||||
mapDelete: {
|
||||
type: 'D',
|
||||
data: formattedWeeksData.mapDelete
|
||||
},
|
||||
mapSum: formattedWeeksData.mapSum,
|
||||
systemCreate: {
|
||||
type: 'C',
|
||||
data: formattedWeeksData.systemCreate
|
||||
},
|
||||
systemUpdate: {
|
||||
type: 'U',
|
||||
data: formattedWeeksData.systemUpdate
|
||||
},
|
||||
systemDelete: {
|
||||
type: 'D',
|
||||
data: formattedWeeksData.systemDelete
|
||||
},
|
||||
systemSum: formattedWeeksData.systemSum,
|
||||
connectionCreate: {
|
||||
type: 'C',
|
||||
data: formattedWeeksData.connectionCreate
|
||||
},
|
||||
connectionUpdate: {
|
||||
type: 'U',
|
||||
data: formattedWeeksData.connectionUpdate
|
||||
},
|
||||
connectionDelete: {
|
||||
type: 'D',
|
||||
data: formattedWeeksData.connectionDelete
|
||||
},
|
||||
connectionSum: formattedWeeksData.connectionSum,
|
||||
signatureCreate: {
|
||||
type: 'C',
|
||||
data: formattedWeeksData.signatureCreate
|
||||
},
|
||||
signatureUpdate: {
|
||||
type: 'U',
|
||||
data: formattedWeeksData.signatureUpdate
|
||||
},
|
||||
signatureDelete: {
|
||||
type: 'D',
|
||||
data: formattedWeeksData.signatureDelete
|
||||
},
|
||||
signatureSum: formattedWeeksData.signatureSum,
|
||||
totalSum: formattedWeeksData.mapSum + formattedWeeksData.systemSum +
|
||||
formattedWeeksData.connectionSum + formattedWeeksData.signatureSum
|
||||
};
|
||||
|
||||
formattedData.push(rowData);
|
||||
});
|
||||
|
||||
return formattedData;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dialogElement
|
||||
* @returns {{}}
|
||||
*/
|
||||
let getRequestDataFromTabPanels = dialogElement => {
|
||||
let requestData = {};
|
||||
|
||||
// get data from "tab" panel links ------------------------------------------------------------------
|
||||
let navigationListElements = dialogElement.find('.' + config.dialogNavigationClass);
|
||||
navigationListElements.find('.' + config.dialogNavigationListItemClass + '.active a').each(function(){
|
||||
let linkElement = $(this);
|
||||
requestData[linkElement.data('type')]= linkElement.data('value');
|
||||
});
|
||||
|
||||
// get current period (no offset) data (if available) -----------------------------------------------
|
||||
let navigationOffsetElement = dialogElement.find('.' + config.dialogNavigationOffsetClass);
|
||||
let startData = navigationOffsetElement.data('start');
|
||||
let periodOld = navigationOffsetElement.data('period');
|
||||
|
||||
// if period switch was detected
|
||||
// -> "year" and "week" should not be send
|
||||
// -> start from "now"
|
||||
if(
|
||||
requestData.period === periodOld &&
|
||||
startData
|
||||
){
|
||||
requestData.year = startData.year;
|
||||
requestData.week = startData.week;
|
||||
}
|
||||
|
||||
return requestData;
|
||||
};
|
||||
|
||||
/**
|
||||
* check if "activity log" type is enabled for a group
|
||||
* @param type
|
||||
* @returns {boolean}
|
||||
*/
|
||||
let isTabTypeEnabled = (type) => {
|
||||
let enabled = false;
|
||||
|
||||
switch(type){
|
||||
case 'private':
|
||||
if( Boolean(Util.getObjVal(Init.mapTypes, type + '.defaultConfig.log_activity_enabled')) ){
|
||||
enabled = true;
|
||||
}
|
||||
break;
|
||||
case 'corporation':
|
||||
if(
|
||||
Boolean(Util.getObjVal(Init.mapTypes, type + '.defaultConfig.log_activity_enabled')) &&
|
||||
Util.getCurrentUserInfo('corporationId')
|
||||
){
|
||||
enabled = true;
|
||||
}
|
||||
break;
|
||||
case 'alliance':
|
||||
if(
|
||||
Boolean(Util.getObjVal(Init.mapTypes, type + '.defaultConfig.log_activity_enabled')) &&
|
||||
Util.getCurrentUserInfo('allianceId')
|
||||
){
|
||||
enabled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return enabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* show activity stats dialog
|
||||
*/
|
||||
$.fn.showStatsDialog = function(){
|
||||
requirejs(['text!templates/dialog/stats.html', 'mustache'], (template, Mustache) => {
|
||||
// get current statistics map settings
|
||||
let logActivityEnabled = false;
|
||||
let activeMap = Util.getMapModule().getActiveMap();
|
||||
if(activeMap){
|
||||
let activeMapId = activeMap.data('id');
|
||||
let activeMapData = Util.getCurrentMapData(activeMapId);
|
||||
if(activeMapData){
|
||||
logActivityEnabled = Boolean(Util.getObjVal(activeMapData, 'config.logging.activity'));
|
||||
}
|
||||
}
|
||||
|
||||
// check which dialog tab is default active
|
||||
let enablePrivateTab = isTabTypeEnabled('private');
|
||||
let enableCorporationTab = isTabTypeEnabled('corporation');
|
||||
let enableAllianceTab = isTabTypeEnabled('alliance');
|
||||
|
||||
let activePrivateTab = false;
|
||||
let activeCorporationTab = false;
|
||||
let activeAllianceTab = false;
|
||||
|
||||
if(enableCorporationTab){
|
||||
activeCorporationTab = true;
|
||||
}else if(enableAllianceTab){
|
||||
activeAllianceTab = true;
|
||||
}else if(enablePrivateTab){
|
||||
activePrivateTab = true;
|
||||
}
|
||||
|
||||
let data = {
|
||||
id: config.statsDialogId,
|
||||
dialogNavigationClass: config.dialogNavigationClass,
|
||||
dialogNavLiClass: config.dialogNavigationListItemClass,
|
||||
enablePrivateTab: enablePrivateTab,
|
||||
enableCorporationTab: enableCorporationTab,
|
||||
enableAllianceTab: enableAllianceTab,
|
||||
activePrivateTab: activePrivateTab,
|
||||
activeCorporationTab: activeCorporationTab,
|
||||
activeAllianceTab: activeAllianceTab,
|
||||
logActivityEnabled: logActivityEnabled,
|
||||
statsContainerId: config.statsContainerId,
|
||||
statsTableId: config.statsTableId,
|
||||
dialogNavigationOffsetClass: config.dialogNavigationOffsetClass,
|
||||
dialogNavigationPrevClass: config.dialogNavigationPrevClass,
|
||||
dialogNavigationNextClass: config.dialogNavigationNextClass
|
||||
};
|
||||
|
||||
let content = Mustache.render(template, data);
|
||||
|
||||
let statsDialog = bootbox.dialog({
|
||||
title: 'Statistics',
|
||||
message: content,
|
||||
size: 'large',
|
||||
show: false
|
||||
});
|
||||
|
||||
// model events
|
||||
statsDialog.on('show.bs.modal', function(e){
|
||||
let dialogElement = $(e.target);
|
||||
initStatsTable(dialogElement);
|
||||
});
|
||||
|
||||
// Tab module events
|
||||
statsDialog.find('a[data-toggle="tab"]').on('show.bs.tab', function(e, b, c){
|
||||
if( $(e.target).parent().hasClass('disabled') ){
|
||||
// no action on "disabled" tabs
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
statsDialog.find('a[data-toggle="tab"]').on('shown.bs.tab', function(e){
|
||||
let requestData = getRequestDataFromTabPanels(statsDialog);
|
||||
let tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
|
||||
|
||||
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
|
||||
});
|
||||
|
||||
// offset change links
|
||||
statsDialog.find('.' + config.dialogNavigationPrevClass + ', .' + config.dialogNavigationNextClass).on('click', function(){
|
||||
let offsetData = $(this).data('newOffset');
|
||||
if(offsetData){
|
||||
// this should NEVER fail!
|
||||
// get "base" request data (e.g. typeId, period)
|
||||
// --> overwrite period data with new period data
|
||||
let tmpRequestData = getRequestDataFromTabPanels(statsDialog);
|
||||
let requestData = $.extend({}, tmpRequestData, offsetData);
|
||||
let tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
|
||||
|
||||
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
|
||||
}
|
||||
});
|
||||
|
||||
// show dialog
|
||||
statsDialog.modal('show');
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -1,194 +0,0 @@
|
||||
/**
|
||||
* system effects dialog
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'bootbox',
|
||||
'app/map/util'
|
||||
], ($, Init, Util, bootbox, MapUtil) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
// system effect dialog
|
||||
systemEffectDialogClass: 'pf-system-effect-dialog', // class for system effect dialog
|
||||
|
||||
systemEffectTableClass: 'pf-system-effect-table' // Table class for effect tables
|
||||
};
|
||||
|
||||
let cache = {
|
||||
systemEffectDialog: false // system effect info dialog
|
||||
};
|
||||
|
||||
/**
|
||||
* show system effect dialog
|
||||
*/
|
||||
$.fn.showSystemEffectInfoDialog = function(){
|
||||
let rowElement = $('<div>', {
|
||||
class: 'row'
|
||||
});
|
||||
|
||||
let systemEffectData = Util.getSystemEffectData();
|
||||
|
||||
// last active (hover) table columnName
|
||||
let lastActiveColName = null;
|
||||
|
||||
let colCount = 0;
|
||||
for(let [effectName, effectData] of Object.entries(systemEffectData.wh)){
|
||||
colCount++;
|
||||
|
||||
let table = $('<table>', {
|
||||
class: ['compact', 'stripe', 'order-column', 'row-border', config.systemEffectTableClass].join(' ')
|
||||
});
|
||||
|
||||
let tbody = $('<tbody>');
|
||||
let thead = $('<thead>');
|
||||
|
||||
let rows = [];
|
||||
|
||||
// get formatted system effect name
|
||||
let systemEffectName = MapUtil.getEffectInfoForSystem(effectName, 'name');
|
||||
let systemEffectClass = MapUtil.getEffectInfoForSystem(effectName, 'class');
|
||||
|
||||
for(let [areaId, areaData] of Object.entries(effectData)){
|
||||
let systemType = 'C' + areaId;
|
||||
let securityClass = Util.getSecurityClassForSystem(systemType);
|
||||
|
||||
if(areaId === '1'){
|
||||
rows.push( $('<tr>') );
|
||||
rows.push( $('<tr>') );
|
||||
thead.append( rows[0] );
|
||||
thead.append( rows[1] );
|
||||
|
||||
rows[0].append(
|
||||
$('<th>').html(' ' + systemEffectName).prepend(
|
||||
$('<i>', {
|
||||
class: ['fas', 'fa-square', systemEffectClass].join(' ')
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
rows[1].append($('<th>'));
|
||||
}
|
||||
|
||||
rows[0].append( $('<th>', {
|
||||
class: ['text-right', 'col-xs-1', securityClass].join(' ')
|
||||
}).text( systemType ).attr('data-name', systemType));
|
||||
|
||||
rows[1].append( $('<th>', {
|
||||
class: ['text-right', 'txt-color', 'txt-color-grayLight'].join(' ')
|
||||
}).text(Util.getSystemEffectMultiplierByAreaId(parseInt(areaId)) + ' x').attr('data-name', systemType));
|
||||
|
||||
for(let [i, data] of Object.entries(areaData)){
|
||||
i = parseInt(i);
|
||||
if(areaId === '1'){
|
||||
rows.push( $('<tr>') );
|
||||
tbody.append(rows[i + 2]);
|
||||
|
||||
// add label
|
||||
rows[i + 2].append( $('<td>').text( data.effect ));
|
||||
}
|
||||
|
||||
|
||||
rows[i + 2].append( $('<td>', {
|
||||
class: 'text-right'
|
||||
}).text( data.value ));
|
||||
}
|
||||
}
|
||||
|
||||
let colElement = $('<div>', {
|
||||
class: ['col-md-6'].join(' ')
|
||||
}).append(
|
||||
$('<div>', {
|
||||
class: [Util.config.dynamicAreaClass].join(' ')
|
||||
}).append(
|
||||
table.append(thead).append(tbody)
|
||||
)
|
||||
);
|
||||
|
||||
rowElement.append(colElement);
|
||||
|
||||
// add clearfix after even col count
|
||||
if(colCount % 2 === 0){
|
||||
rowElement.append(
|
||||
$('<div>', {
|
||||
class: ['clearfix', 'visible-md', 'visible-lg'].join(' ')
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
cache.systemEffectDialog = rowElement;
|
||||
}
|
||||
|
||||
let effectsDialog = bootbox.dialog({
|
||||
className: config.systemEffectDialogClass,
|
||||
title: 'System effect information',
|
||||
message: cache.systemEffectDialog,
|
||||
size: 'large',
|
||||
show: false
|
||||
});
|
||||
|
||||
effectsDialog.on('show.bs.modal', function(e){
|
||||
let headerAll = $();
|
||||
let columnsAll = $();
|
||||
|
||||
let removeColumnHighlight = () => {
|
||||
headerAll.removeClass('colHighlight');
|
||||
columnsAll.removeClass('colHighlight');
|
||||
};
|
||||
|
||||
let tableApis = $(this).find('table').DataTable({
|
||||
pageLength: -1,
|
||||
paging: false,
|
||||
lengthChange: false,
|
||||
ordering: false,
|
||||
searching: false,
|
||||
info: false,
|
||||
columnDefs: [],
|
||||
data: null, // use DOM data overwrites [] default -> data.loader.js
|
||||
initComplete: function(settings, json){
|
||||
let tableApi = this.api();
|
||||
|
||||
tableApi.tables().nodes().to$().on('mouseover', 'td', function(){
|
||||
// inside table cell -> get current hover colIndex
|
||||
let colIndex = tableApi.cell(this).index().column;
|
||||
let colName = tableApi.column(colIndex).header().dataset.name || '';
|
||||
|
||||
if(colName !== lastActiveColName){
|
||||
removeColumnHighlight();
|
||||
|
||||
lastActiveColName = colName;
|
||||
|
||||
if(colName.length){
|
||||
// active column changed -> highlight same colName on other tables
|
||||
let tableApis = $.fn.dataTable.tables({ visible: false, api: true })
|
||||
.tables('.' + config.systemEffectTableClass);
|
||||
|
||||
let columns = tableApis.columns([colName + ':name']);
|
||||
columns.header().flatten().to$().addClass('colHighlight');
|
||||
columns.nodes().flatten().to$().addClass('colHighlight');
|
||||
}
|
||||
}
|
||||
}).on('mouseleave', function(){
|
||||
// no longer inside table
|
||||
lastActiveColName = null;
|
||||
removeColumnHighlight();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// table cells will not change so we should cache them once
|
||||
headerAll = tableApis.columns().header().to$();
|
||||
columnsAll = tableApis.cells().nodes().to$();
|
||||
});
|
||||
|
||||
effectsDialog.on('hide.bs.modal', function(e){
|
||||
// destroy logTable
|
||||
$(this).find('table').DataTable().destroy(true);
|
||||
});
|
||||
|
||||
effectsDialog.modal('show');
|
||||
};
|
||||
});
|
||||
@@ -1,920 +0,0 @@
|
||||
/**
|
||||
* form elements
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/map/util'
|
||||
], ($, Init, Util, MapUtil) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
// Select2
|
||||
resultOptionImageClass: 'pf-result-image', // class for Select2 result option entry with image
|
||||
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad' // class for Select2 result images that should be lazy loaded
|
||||
};
|
||||
|
||||
/**
|
||||
* format result data
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
let formatCategoryTypeResultData = data => {
|
||||
if(data.loading) return data.text;
|
||||
if(data.placeholder) return data.placeholder;
|
||||
|
||||
let markup = `<div class="clearfix ${config.resultOptionImageClass}">`;
|
||||
|
||||
if(data.hasOwnProperty('children')){
|
||||
// category group label
|
||||
markup += `<div class="col-xs-9">${data.text}</div>`;
|
||||
markup += `<div class="col-xs-3 text-right">(${data.children.length})</div>`;
|
||||
}else{
|
||||
let imagePath = '';
|
||||
let iconName = '';
|
||||
let thumb = '';
|
||||
|
||||
switch(data.categoryType){
|
||||
case 'character':
|
||||
imagePath = Util.eveImageUrl('characters', data.id);
|
||||
break;
|
||||
case 'corporation':
|
||||
imagePath = Util.eveImageUrl('corporations', data.id);
|
||||
break;
|
||||
case 'alliance':
|
||||
imagePath = Util.eveImageUrl('alliances', data.id);
|
||||
break;
|
||||
case 'inventoryType':
|
||||
imagePath = Util.eveImageUrl('types', data.id);
|
||||
break;
|
||||
case 'render':
|
||||
imagePath = Util.eveImageUrl('types', data.id);
|
||||
break;
|
||||
case 'station':
|
||||
iconName = 'fa-home';
|
||||
break;
|
||||
case 'system':
|
||||
iconName = 'fa-sun';
|
||||
break;
|
||||
}
|
||||
|
||||
if(imagePath){
|
||||
thumb = `<img class="${config.select2ImageLazyLoadClass}" src="${Util.imgRoot()}svg/logo_simple.svg" data-src="${imagePath}" style="max-width: 100%"/>`;
|
||||
}else if(iconName){
|
||||
thumb = `<i class="fas fa-fw ${iconName}"></i>`;
|
||||
}
|
||||
|
||||
markup += `<div class="col-xs-2">${thumb}</div>`;
|
||||
markup += `<div class="col-xs-10 pf-text-ellipsis">${data.text}</div>`;
|
||||
}
|
||||
markup += '</div>';
|
||||
|
||||
return markup;
|
||||
};
|
||||
|
||||
/**
|
||||
* format results data for signature type select
|
||||
* @param state
|
||||
* @param container
|
||||
* @param customOptions
|
||||
* @returns {*|k.fn.init|jQuery|HTMLElement}
|
||||
*/
|
||||
let formatSignatureTypeSelectionData = (state, container, customOptions) => {
|
||||
let parts = state.text.split(' - ');
|
||||
|
||||
let markup = '';
|
||||
if(parts.length === 2){
|
||||
// wormhole data -> 2 columns
|
||||
let name = parts[0];
|
||||
let sizeLabel;
|
||||
if(Util.getObjVal(customOptions, 'showWhSizeLabel')){
|
||||
sizeLabel = Util.getObjVal(Init, `wormholes.${name}.size.label`) || '';
|
||||
}
|
||||
|
||||
let securityClass = Util.getSecurityClassForSystem(getSystemSecurityFromLabel(parts[1]));
|
||||
// some labels have a "suffix" label that should not have the securityClass
|
||||
let labelParts = parts[1].split(/\s(.+)/);
|
||||
let label = labelParts[0];
|
||||
let suffix = labelParts[1] ? labelParts[1] : '';
|
||||
|
||||
let classes = [securityClass, Util.config.popoverTriggerClass, Util.config.helpDefaultClass];
|
||||
|
||||
markup += `<span>${name}</span>`;
|
||||
if(sizeLabel !== undefined){
|
||||
markup += `<span><kbd>${sizeLabel}</kbd></span>`;
|
||||
}
|
||||
markup += '<i class="fas fa-long-arrow-alt-right txt-color txt-color-grayLight"></i>';
|
||||
markup += `<span class="${classes.join(' ')}" data-name="${name}"> ${label}</span>`;
|
||||
if(suffix.length){
|
||||
markup += ` <span>${suffix}</span>`;
|
||||
}
|
||||
}else{
|
||||
markup += `<span>${state.text}</span>`;
|
||||
}
|
||||
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
/**
|
||||
* format result data for signature type OR signature connection data
|
||||
* @param data
|
||||
* @param formatType
|
||||
* @returns {*}
|
||||
*/
|
||||
let formatSignatureTypeConnectionResultData = (data, formatType) => {
|
||||
if(data.loading) return data.text;
|
||||
if(data.placeholder) return data.placeholder;
|
||||
|
||||
let markup = '<div class="clearfix">';
|
||||
|
||||
if(data.hasOwnProperty('children')){
|
||||
// optgroup label
|
||||
markup += '<div class="col-xs-9">' + data.text + '</div>';
|
||||
markup += '<div class="col-xs-3 text-right">(' + data.children.length + ')</div>';
|
||||
}else{
|
||||
// child label
|
||||
let parts = data.text.split(' - ');
|
||||
if(parts.length === 2){
|
||||
// wormhole data -> 2 columns
|
||||
let securityClass = Util.getSecurityClassForSystem(getSystemSecurityFromLabel(parts[1]));
|
||||
|
||||
switch(formatType){
|
||||
case 'wormhole':
|
||||
// some labels have a "suffix" label that should not have the securityClass
|
||||
let labelParts = parts[1].split(/\s(.+)/);
|
||||
let label = labelParts[0];
|
||||
let suffix = labelParts[1] ? labelParts[1] : '';
|
||||
|
||||
|
||||
markup += '<div class="col-xs-3">' + parts[0] + '</div>';
|
||||
markup += '<div class="col-xs-2 text-center"><i class="fas fa-long-arrow-alt-right txt-color txt-color-grayLight"></i></div>';
|
||||
markup += '<div class="col-xs-3 ' + securityClass + '">' + label + '</div>';
|
||||
markup += '<div class="col-xs-4 text-right">' + suffix + '</div>';
|
||||
break;
|
||||
case 'system':
|
||||
markup += '<div class="col-xs-10">' + parts[0] + '</div>';
|
||||
markup += '<div class="col-xs-2 ' + securityClass + '">' + parts[1] + '</div>';
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
markup += '<div class="col-xs-12">' + data.text + '</div>';
|
||||
}
|
||||
}
|
||||
markup += '</div>';
|
||||
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
/**
|
||||
* format results data for signature connection select
|
||||
* @param state
|
||||
* @returns {*|jQuery|HTMLElement}
|
||||
*/
|
||||
let formatSignatureConnectionSelectionData = state => {
|
||||
let parts = state.text.split(' - ');
|
||||
|
||||
let markup = '';
|
||||
if(parts.length === 2){
|
||||
// wormhole data -> 2 columns
|
||||
|
||||
let styleClass = ['pf-fake-connection-text'];
|
||||
if(state.metaData){
|
||||
let metaData = state.metaData;
|
||||
if(metaData.type){
|
||||
let type = metaData.type;
|
||||
if(type.includes('wh_eol')){
|
||||
styleClass.push('pf-wh-eol');
|
||||
}
|
||||
if(type.includes('wh_reduced')){
|
||||
styleClass.push('pf-wh-reduced');
|
||||
}
|
||||
if(type.includes('wh_critical')){
|
||||
styleClass.push('pf-wh-critical');
|
||||
}
|
||||
if(type.includes('wh_jump_mass_s')){
|
||||
styleClass.push('pf-wh-frig');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let securityClass = Util.getSecurityClassForSystem(parts[1]);
|
||||
markup += `<span class="${styleClass.join(' ')}">${parts[0]}</span>`;
|
||||
markup += `<span class="${securityClass}"> ${parts[1]}</span>`;
|
||||
}else{
|
||||
markup += `<span>${state.text}</span>`;
|
||||
}
|
||||
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
/**
|
||||
* try to parse a security label into security name
|
||||
* -> "C1/2/3 (unknown)" -> C1
|
||||
* "C3" -> C3
|
||||
* "H" -> H
|
||||
* "0.0" -> 0.0
|
||||
* "C12 Thera" -> C12
|
||||
* @param security
|
||||
* @returns {string}
|
||||
*/
|
||||
let getSystemSecurityFromLabel = security => {
|
||||
let matches = security.match(/^(\w+\.?\w?)/i);
|
||||
return matches ? matches[1] : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as "select2" for map selection
|
||||
*/
|
||||
$.fn.initMapSelect = function(){
|
||||
let selectElement = $(this);
|
||||
|
||||
$.when(
|
||||
selectElement.select2({
|
||||
dropdownParent: selectElement.parents('.modal-body'),
|
||||
maximumSelectionLength: 5
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as "select2" for connection size types
|
||||
* @param options
|
||||
*/
|
||||
$.fn.initConnectionSizeSelect = function(options){
|
||||
let selectElement = $(this);
|
||||
|
||||
let defaultConfig = {
|
||||
dropdownParent: selectElement.parents('.modal-body'),
|
||||
minimumResultsForSearch: Infinity,
|
||||
width: '100%',
|
||||
maxSelectionLength: 1
|
||||
};
|
||||
options = $.extend({}, defaultConfig, options);
|
||||
|
||||
let formatConnectionSizeResultData = data => {
|
||||
if(data.loading) return data.text;
|
||||
if(data.placeholder) return data.placeholder;
|
||||
|
||||
let connectionClass = MapUtil.getConnectionInfo(data.text, 'cssClass');
|
||||
|
||||
let label = Util.getObjVal(Init.wormholeSizes, data.text + '.label') || '?';
|
||||
let text = Util.getObjVal(Init.wormholeSizes, data.text + '.text') || 'all';
|
||||
|
||||
let markup = '<div class="clearfix">';
|
||||
markup += '<div class="col-xs-1">';
|
||||
markup += '<i class="fas fa-char fa-fw" data-char-content="' + label + '"></i>';
|
||||
markup += '</div>';
|
||||
markup += '<div class="col-xs-3">';
|
||||
markup += '<div class="pf-fake-connection ' + connectionClass + '"></div>';
|
||||
markup += '</div>';
|
||||
markup += '<div class="col-xs-8">';
|
||||
markup += text;
|
||||
markup += '</div>';
|
||||
markup += '</div>';
|
||||
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
options.templateSelection = formatConnectionSizeResultData;
|
||||
options.templateResult = formatConnectionSizeResultData;
|
||||
|
||||
$.when(
|
||||
selectElement.select2(options)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* init a sselect element as "select2" for "status" selection
|
||||
* @param options
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initStatusSelect = function(options){
|
||||
|
||||
let defaultConfig = {
|
||||
minimumResultsForSearch: Infinity,
|
||||
width: '100%',
|
||||
iconClass: 'fa-circle'
|
||||
};
|
||||
|
||||
options = $.extend({}, defaultConfig, options);
|
||||
|
||||
let formatStatusSelectionData = state => {
|
||||
return '<i class="fas ' + options.iconClass + ' ' + state.class + '"></i> ' + state.text;
|
||||
};
|
||||
|
||||
let formatStatusResultData = data => {
|
||||
if(data.loading) return data.text;
|
||||
if(data.placeholder) return data.placeholder;
|
||||
|
||||
let markup = '<div class="clearfix ' + config.resultOptionImageClass + '">';
|
||||
markup += '<div class="col-xs-2">';
|
||||
markup += '<i class="fas ' + options.iconClass + ' ' + data.class + '"></i>';
|
||||
markup += '</div>';
|
||||
markup += '<div class="col-xs-10">' + data.text + '</div>';
|
||||
markup += '</div>';
|
||||
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
options.templateSelection = formatStatusSelectionData;
|
||||
options.templateResult = formatStatusResultData;
|
||||
|
||||
return this.each(function(){
|
||||
let selectElement = $(this);
|
||||
selectElement.select2(options);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as an ajax based "select2" object for system search
|
||||
* @param options
|
||||
*/
|
||||
$.fn.initSystemSelect = function(options){
|
||||
let selectElement = $(this);
|
||||
|
||||
let defaultConfig = {
|
||||
maxSelectionLength: 1
|
||||
};
|
||||
options = $.extend({}, defaultConfig, options);
|
||||
|
||||
let shatteredClass = Util.getSecurityClassForSystem('SH');
|
||||
|
||||
// format result data
|
||||
function formatResultData (data){
|
||||
if(data.loading) return data.text;
|
||||
|
||||
// abyss system font
|
||||
let systemNameClass = data.security === 'A' ? Util.config.fontTriglivianClass : '';
|
||||
|
||||
// show effect info just for wormholes
|
||||
let hideEffectClass = data.effect === null ? 'hide' : '';
|
||||
|
||||
let hideShatteredClass = !data.shattered ? 'hide' : '';
|
||||
|
||||
let markup = '<div class="clearfix ' + config.resultOptionImageClass + '">';
|
||||
markup += '<div class="col-xs-4 pf-select-item-anchor ' + systemNameClass + '">' + data.text + '</div>';
|
||||
markup += '<div class="col-xs-2 text-right ' + data.effectClass + '">';
|
||||
markup += '<i class="fas fa-fw fa-square ' + hideEffectClass + '"></i>';
|
||||
markup += '</div>';
|
||||
markup += '<div class="col-xs-2 text-right ' + data.secClass + '">' + data.security + '</div>';
|
||||
markup += '<div class="col-xs-2 text-right ' + shatteredClass + '">';
|
||||
markup += '<i class="fas fa-fw fa-chart-pie ' + hideShatteredClass + '"></i>';
|
||||
markup += '</div>';
|
||||
markup += '<div class="col-xs-2 text-right ' + data.trueSecClass + '">' + data.trueSec + '</div></div>';
|
||||
|
||||
return markup;
|
||||
}
|
||||
|
||||
$.when(
|
||||
selectElement.select2({
|
||||
ajax: {
|
||||
url: function(params){
|
||||
// add params to URL
|
||||
return Init.path.searchUniverseSystemData + '/' + params.term.trim();
|
||||
},
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
timeout: 5000,
|
||||
cache: true,
|
||||
data: function(params){
|
||||
return {
|
||||
page: params.page || 1
|
||||
};
|
||||
},
|
||||
processResults: function(data, params){
|
||||
// parse the results into the format expected by Select2.
|
||||
return {
|
||||
results: data.results.map( function(item){
|
||||
// "id" or "name"
|
||||
let id = item[options.key];
|
||||
let disabled = false;
|
||||
let trueSec = parseFloat(item.trueSec);
|
||||
let secClass = Util.getSecurityClassForSystem(item.security);
|
||||
let trueSecClass = Util.getTrueSecClassForSystem( trueSec );
|
||||
let effectClass = MapUtil.getEffectInfoForSystem(item.effect, 'class');
|
||||
|
||||
// check if system is dialed
|
||||
if(
|
||||
options.disabledOptions &&
|
||||
options.disabledOptions.indexOf(parseInt(id, 10)) !== -1
|
||||
){
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
// "fix" security level
|
||||
if(
|
||||
trueSec > 0 &&
|
||||
trueSec < 0.1
|
||||
){
|
||||
trueSec = 0.1;
|
||||
}else{
|
||||
trueSec = Math.round(trueSec * 10) / 10;
|
||||
}
|
||||
|
||||
return {
|
||||
id: id,
|
||||
text: item.name,
|
||||
// systemId: parseInt(item.systemId),
|
||||
security: item.security,
|
||||
secClass: secClass,
|
||||
trueSec: trueSec.toFixed(1),
|
||||
trueSecClass: trueSecClass,
|
||||
effect: item.effect,
|
||||
effectClass: effectClass,
|
||||
shattered: item.shattered,
|
||||
disabled: disabled
|
||||
};
|
||||
}),
|
||||
pagination: {
|
||||
more: data.pagination.more
|
||||
}
|
||||
};
|
||||
},
|
||||
error: function(jqXHR, status, error){
|
||||
if( !Util.isXHRAborted(jqXHR) ){
|
||||
|
||||
let reason = status + ' ' + jqXHR.status + ': ' + error;
|
||||
Util.showNotify({title: 'System select warning', text: reason + ' deleted', type: 'warning'});
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
dropdownParent: selectElement.parents('.modal-body'),
|
||||
minimumInputLength: 3,
|
||||
templateResult: formatResultData,
|
||||
placeholder: 'Name or ID',
|
||||
allowClear: true,
|
||||
maximumSelectionLength: options.maxSelectionLength
|
||||
}).on('change', function(e){
|
||||
// select changed
|
||||
if(options.onChange){
|
||||
options.onChange(parseInt($(this).val()) || 0);
|
||||
}
|
||||
}).on('select2:open', function(){
|
||||
// clear selected system (e.g. default system)
|
||||
// => improves usability (not necessary). There is a small "x" if field can be cleared manually
|
||||
if(
|
||||
options.maxSelectionLength === 1 &&
|
||||
$(this).val() !== null
|
||||
){
|
||||
$(this).val('').trigger('change');
|
||||
}
|
||||
})
|
||||
).done(function(a,b){
|
||||
// open select if not already pre-selected
|
||||
if($(this).val() === null){
|
||||
selectElement.select2('open');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as an ajax based "select2" object for Access resources
|
||||
* character (private map), corporation (corp map), alliance (ally map)
|
||||
* @param options
|
||||
*/
|
||||
$.fn.initAccessSelect = function(options){
|
||||
|
||||
let formatSelectionData = data => {
|
||||
if(data.loading) return data.text;
|
||||
|
||||
let markup = '<div class="clearfix">';
|
||||
markup += '<div class="col-sm-10">' + data.text + '</div></div>';
|
||||
|
||||
return markup;
|
||||
};
|
||||
|
||||
return this.each(function(){
|
||||
let selectElement = $(this);
|
||||
|
||||
$.when(
|
||||
selectElement.select2({
|
||||
ajax: {
|
||||
url: function(params){
|
||||
// add params to URL
|
||||
return Init.path.searchAccess + '/' + options.type + '/' + params.term;
|
||||
},
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
timeout: 5000,
|
||||
cache: true,
|
||||
data: function(params){
|
||||
// no url params here
|
||||
return;
|
||||
},
|
||||
processResults: function(data, page){
|
||||
// parse the results into the format expected by Select2.
|
||||
return {
|
||||
results: data.map( function(item){
|
||||
return {
|
||||
id: item.id,
|
||||
text: item.name,
|
||||
categoryType: options.type
|
||||
};
|
||||
})
|
||||
};
|
||||
},
|
||||
error: function(jqXHR, status, error){
|
||||
if( !Util.isXHRAborted(jqXHR) ){
|
||||
|
||||
let reason = status + ' ' + jqXHR.status + ': ' + error;
|
||||
Util.showNotify({title: 'Access select warning', text: reason + ' deleted', type: 'warning'});
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
dropdownParent: selectElement.parents('.modal-body'),
|
||||
minimumInputLength: 3,
|
||||
placeholder: options.type + ' names',
|
||||
allowClear: false,
|
||||
maximumSelectionLength: options.maxSelectionLength,
|
||||
templateResult: formatCategoryTypeResultData,
|
||||
templateSelection: formatSelectionData
|
||||
}).on('change', function(e){
|
||||
// select changed
|
||||
|
||||
})
|
||||
).done(function(){
|
||||
// after init finish
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as an ajax based "select2" object for universeTypes
|
||||
* e.g. 'alliance', 'corporation', 'character', ...
|
||||
* @param options
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initUniverseSearch = function(options){
|
||||
|
||||
let showErrorNotification = (reason) => {
|
||||
Util.showNotify({title: 'Search failed', text: reason + ' deleted', type: 'warning'});
|
||||
};
|
||||
|
||||
/**
|
||||
* format selection data
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
function formatSelectionData (data){
|
||||
if(data.loading) return data.text;
|
||||
if(data.placeholder) return data.placeholder;
|
||||
|
||||
let markup = '<div class="clearfix">';
|
||||
markup += '<div class="col-sm-10">' + data.text + '</div></div>';
|
||||
|
||||
return markup;
|
||||
}
|
||||
|
||||
/**
|
||||
* sort universe data
|
||||
* @param data universe search result array
|
||||
* @param term search term
|
||||
*/
|
||||
function sortResultData (data, term){
|
||||
let levenshtein = (a,b) => {
|
||||
let matrix = new Array(a.length+1);
|
||||
for(let i = 0; i < matrix.length; i++){
|
||||
matrix[i] = new Array(b.length+1).fill(0);
|
||||
}
|
||||
|
||||
for(let ai = 1; ai <= a.length; ai++){
|
||||
matrix[ai][0] = ai;
|
||||
}
|
||||
|
||||
for(let bi = 1; bi <= b.length; bi++){
|
||||
matrix[0][bi] = bi;
|
||||
}
|
||||
|
||||
for(let bi = 1; bi <= b.length; bi++){
|
||||
for(let ai = 1; ai <= a.length; ai++){
|
||||
matrix[ai][bi] = Math.min(
|
||||
matrix[ai-1][bi]+1,
|
||||
matrix[ai][bi-1]+1,
|
||||
matrix[ai-1][bi-1]+(a[ai-1] === b[bi-1] ? 0 : 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return matrix[a.length][b.length];
|
||||
};
|
||||
|
||||
data.sort((a,b) => {
|
||||
let levA = levenshtein(term, a.name.toLowerCase());
|
||||
let levB = levenshtein(term, b.name.toLowerCase());
|
||||
return levA === levB ? 0 : (levA > levB ? 1 : -1);
|
||||
});
|
||||
}
|
||||
|
||||
return this.each(function(){
|
||||
let selectElement = $(this);
|
||||
|
||||
$.when(
|
||||
selectElement.select2({
|
||||
ajax: {
|
||||
type: 'POST',
|
||||
url: function(params){
|
||||
// add params to URL
|
||||
return Init.path.searchUniverseData + '/' + encodeURI(params.term);
|
||||
},
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
timeout: 5000,
|
||||
cache: true,
|
||||
data: function(params){
|
||||
return {
|
||||
categories: options.categoryNames
|
||||
};
|
||||
},
|
||||
processResults: function(result, page){
|
||||
let data = {results: []};
|
||||
if(result.hasOwnProperty('error')){
|
||||
showErrorNotification(result.error);
|
||||
}else{
|
||||
let mapChildren = function(item){
|
||||
return {
|
||||
id: item.id,
|
||||
text: item.name,
|
||||
categoryType: this
|
||||
};
|
||||
};
|
||||
|
||||
for(let category in result){
|
||||
// skip custom functions in case result = [] (array functions)
|
||||
if(result.hasOwnProperty(category)){
|
||||
// sort results (optional)
|
||||
sortResultData(result[category], page.term);
|
||||
data.results.push({
|
||||
text: category,
|
||||
children: result[category].map(mapChildren, category)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
error: function(jqXHR, status, error){
|
||||
if( !Util.isXHRAborted(jqXHR) ){
|
||||
let reason = status + ' ' + jqXHR.status + ': ' + error;
|
||||
showErrorNotification(reason);
|
||||
}
|
||||
}
|
||||
},
|
||||
dropdownParent: selectElement.parents('.modal-body') ,
|
||||
minimumInputLength: 3,
|
||||
placeholder: '',
|
||||
/* alphabetic search not always fits the users need
|
||||
sorter: data => {
|
||||
// sort nested data options by "text" prop
|
||||
return data.map((group, index) => {
|
||||
group.children = group.children.sort((a,b) => a.text.localeCompare(b.text) );
|
||||
return group;
|
||||
});
|
||||
},*/
|
||||
disabled: options.hasOwnProperty('disabled') ? options.disabled : false,
|
||||
allowClear: options.maxSelectionLength <= 1,
|
||||
maximumSelectionLength: options.maxSelectionLength,
|
||||
templateResult: formatCategoryTypeResultData
|
||||
// templateSelection: formatSelectionData, // some issues with "clear" selection on single selects (empty option is needed)
|
||||
}).on('change', function(e){
|
||||
// select changed
|
||||
}).on('select2:open', function(){
|
||||
// clear selected system (e.g. default system)
|
||||
// => improves usability (not necessary). There is a small "x" if field can be cleared manually
|
||||
if(
|
||||
options.maxSelectionLength === 1 &&
|
||||
$(this).val() !== null
|
||||
){
|
||||
$(this).val('').trigger('change');
|
||||
}
|
||||
})
|
||||
).done(function(){
|
||||
// after init finish
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as an "select2" object for system search
|
||||
* @param options
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initUniverseTypeSelect = function(options){
|
||||
|
||||
/**
|
||||
* get select option data by categoryIds
|
||||
* @param categoryIds
|
||||
* @returns {Array}
|
||||
*/
|
||||
let getOptionsData = categoryIds => {
|
||||
let data = [];
|
||||
|
||||
let mapChildren = function(type){
|
||||
return {
|
||||
id: type.id,
|
||||
text: type.name,
|
||||
mass: type.hasOwnProperty('mass') ? type.mass : null,
|
||||
groupId: this.groupId,
|
||||
categoryId: this.categoryId,
|
||||
categoryType: this.categoryType
|
||||
};
|
||||
};
|
||||
|
||||
for(let categoryId of categoryIds){
|
||||
let categoryData = Util.getObjVal(Init, 'universeCategories.' + categoryId);
|
||||
if(categoryData && categoryData.groups){
|
||||
// categoryId data exists and has groups...
|
||||
for(let groupData of categoryData.groups){
|
||||
if(groupData && groupData.types){
|
||||
// groupData exists and has types...
|
||||
data.push({
|
||||
text: groupData.name,
|
||||
children: groupData.types.map(mapChildren, {
|
||||
groupId: groupData.id,
|
||||
categoryId: categoryData.id,
|
||||
categoryType: 'inventoryType',
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return this.each(function(){
|
||||
let selectElement = $(this);
|
||||
|
||||
$.when(
|
||||
selectElement.select2({
|
||||
data: getOptionsData(options.categoryIds),
|
||||
dropdownParent: selectElement.parents('.modal-body'),
|
||||
minimumInputLength: 0, // minimum number of characters required to start a search
|
||||
maximumInputLength: 100, // maximum number of characters that may be provided for a search term
|
||||
placeholder: '',
|
||||
allowClear: options.maxSelectionLength <= 1,
|
||||
multiple: options.maxSelectionLength > 1,
|
||||
maximumSelectionLength: options.maxSelectionLength,
|
||||
// maximumSelectionLength: options.maxSelectionLength > 1 ? options.maxSelectionLength > 1 : 0,
|
||||
// minimumResultsForSearch: 5, // minimum number of results required to display the search box
|
||||
templateResult: formatCategoryTypeResultData
|
||||
}).on('select2:open', function(){
|
||||
// clear selected system (e.g. default system)
|
||||
// => improves usability (not necessary). There is a small "x" if field can be cleared manually
|
||||
if(
|
||||
options.maxSelectionLength === 1 &&
|
||||
$(this).val() !== null
|
||||
){
|
||||
$(this).val('').trigger('change');
|
||||
}
|
||||
}).val(options.selected).trigger('change')
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as an "select2" object for signature group data
|
||||
* @param options
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initSignatureGroupSelect = function(options){
|
||||
let defaultConfig = {
|
||||
minimumResultsForSearch: Infinity,
|
||||
width: '110px',
|
||||
dropdownParent: this.parents('.popover-content')
|
||||
};
|
||||
|
||||
options = $.extend({}, defaultConfig, options);
|
||||
|
||||
return this.each(function(){
|
||||
let selectElement = $(this);
|
||||
selectElement.select2(options);
|
||||
|
||||
// initial open dropDown
|
||||
if( !parseInt(selectElement.val()) ){
|
||||
// setTimeout() required because of dropDown positioning
|
||||
setTimeout(() => {
|
||||
selectElement.select2('open');
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as an "select2" object for signature types data
|
||||
* @param options
|
||||
* @param hasOptGroups
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initSignatureTypeSelect = function(options, hasOptGroups){
|
||||
let defaultConfig = {
|
||||
minimumResultsForSearch: 10,
|
||||
width: '220px',
|
||||
dropdownParent: this.parents('.popover-content')
|
||||
};
|
||||
|
||||
options = $.extend({}, defaultConfig, options);
|
||||
|
||||
let formatSignatureTypeResultData = data => {
|
||||
return formatSignatureTypeConnectionResultData(data, 'wormhole');
|
||||
};
|
||||
|
||||
let search = (params, data) => {
|
||||
if($.trim(params.term) === '') return data; // If there are no search terms, return all of the data
|
||||
if(typeof data.children === 'undefined') return null; // Skip if there is no 'children' property
|
||||
|
||||
// `data.children` contains the actual options that we are matching against
|
||||
let filteredChildren = [];
|
||||
for(let [idx, child] of Object.entries(data.children)){
|
||||
if(child.text.toUpperCase().indexOf(params.term.toUpperCase()) === 0){
|
||||
filteredChildren.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
// If we matched any of the timezone group's children, then set the matched children on the group
|
||||
// and return the group object
|
||||
if(filteredChildren.length){
|
||||
let modifiedData = $.extend({}, data, true);
|
||||
modifiedData.children = filteredChildren;
|
||||
|
||||
// You can return modified objects from here
|
||||
// This includes matching the `children` how you want in nested data sets
|
||||
return modifiedData;
|
||||
}
|
||||
|
||||
// Return `null` if the term should not be displayed
|
||||
return null;
|
||||
};
|
||||
|
||||
options.templateSelection = formatSignatureTypeSelectionData;
|
||||
options.templateResult = formatSignatureTypeResultData;
|
||||
|
||||
if(hasOptGroups){
|
||||
// NOT nested selects don´t need the custom search() function
|
||||
options.matcher = search;
|
||||
}
|
||||
|
||||
return this.each(function(){
|
||||
let selectElement = $(this);
|
||||
selectElement.select2(options);
|
||||
|
||||
// initial open dropDown
|
||||
if( !parseInt(selectElement.val()) ){
|
||||
// setTimeout() required because of dropDown positioning
|
||||
setTimeout(() => {
|
||||
selectElement.select2('open');
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as an "select2" object for signature group data
|
||||
* @param options
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initSignatureConnectionSelect = function(options){
|
||||
let defaultConfig = {
|
||||
minimumResultsForSearch: Infinity,
|
||||
width: '140px',
|
||||
dropdownParent: this.parents('.popover-content')
|
||||
};
|
||||
|
||||
options = $.extend({}, defaultConfig, options);
|
||||
|
||||
let formatSignatureConnectionResultData = data => {
|
||||
return formatSignatureTypeConnectionResultData(data, 'system');
|
||||
};
|
||||
|
||||
options.templateSelection = formatSignatureConnectionSelectionData;
|
||||
options.templateResult = formatSignatureConnectionResultData;
|
||||
|
||||
return this.each(function(){
|
||||
let selectElement = $(this);
|
||||
|
||||
// remove existing <options> from DOM in case "data" is explicit set
|
||||
if(options.data){
|
||||
selectElement.empty();
|
||||
}
|
||||
selectElement.select2(options);
|
||||
|
||||
// initial open dropDown
|
||||
if( !parseInt(selectElement.val()) ){
|
||||
// setTimeout() required because of dropDown positioning
|
||||
setTimeout(() => {
|
||||
selectElement.select2('open');
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
formatSignatureTypeSelectionData: formatSignatureTypeSelectionData,
|
||||
formatSignatureConnectionSelectionData: formatSignatureConnectionSelectionData
|
||||
};
|
||||
});
|
||||
@@ -1,568 +0,0 @@
|
||||
/**
|
||||
* Header animation
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/util'
|
||||
], ($, Util) => {
|
||||
'use strict';
|
||||
|
||||
let config = {
|
||||
headerId: 'pf-landing-top', // id for page header
|
||||
canvasId: 'pf-header-canvas', // id for canvas background
|
||||
previewElementClass: 'pf-header-preview-element' // class for "preview" elements
|
||||
};
|
||||
|
||||
class Color {
|
||||
constructor(r, g, b, a = 1){
|
||||
this._r = r;
|
||||
this._g = g;
|
||||
this._b = b;
|
||||
this._a = a;
|
||||
}
|
||||
|
||||
get r(){
|
||||
return this._r;
|
||||
}
|
||||
|
||||
get g(){
|
||||
return this._g;
|
||||
}
|
||||
|
||||
get b(){
|
||||
return this._b;
|
||||
}
|
||||
|
||||
style(a = this.a){
|
||||
return `rgba(${this.r}, ${this.g}, ${this.b}, ${a})`;
|
||||
}
|
||||
}
|
||||
|
||||
class Node {
|
||||
constructor(x, y, ctx, config = {}){
|
||||
this._anchorX = x;
|
||||
this._anchorY = y;
|
||||
this._ctx = ctx;
|
||||
this._config = config;
|
||||
this._x = Math.random() * (x - (x - this._config.anchorLength)) + (x - this._config.anchorLength);
|
||||
this._y = Math.random() * (y - (y - this._config.anchorLength)) + (y - this._config.anchorLength);
|
||||
this._vx = Math.random() * 2 - 1;
|
||||
this._vy = Math.random() * 2 - 1;
|
||||
this._energy = Math.random() * 100;
|
||||
this._radius = Math.random();
|
||||
this._siblings = [];
|
||||
this._brightness = 0;
|
||||
this._isPointer = false;
|
||||
}
|
||||
|
||||
set x(x){
|
||||
this._x = x;
|
||||
}
|
||||
|
||||
get x(){
|
||||
return this._x;
|
||||
}
|
||||
|
||||
set y(y){
|
||||
this._y = y;
|
||||
}
|
||||
|
||||
get y(){
|
||||
return this._y;
|
||||
}
|
||||
|
||||
set siblings(siblings){
|
||||
this._siblings = siblings;
|
||||
}
|
||||
|
||||
get siblings(){
|
||||
return this._siblings;
|
||||
}
|
||||
|
||||
get radius(){
|
||||
if(this.isPointer){
|
||||
return 3;
|
||||
}
|
||||
return 2 * this._radius + 2 * this._siblings.length / this._config.siblingsLimit;
|
||||
}
|
||||
|
||||
set brightness(brightness){
|
||||
this._brightness = brightness;
|
||||
}
|
||||
|
||||
get brightness(){
|
||||
return this.isPointer ? 1 : this._brightness;
|
||||
}
|
||||
|
||||
set color(color){
|
||||
this._color = color;
|
||||
}
|
||||
|
||||
get color(){
|
||||
return this._color;
|
||||
}
|
||||
|
||||
set isPointer(isPointer){
|
||||
this._isPointer = isPointer;
|
||||
}
|
||||
|
||||
get isPointer(){
|
||||
return this._isPointer;
|
||||
}
|
||||
|
||||
drawNode(){
|
||||
this._ctx.beginPath();
|
||||
this._ctx.arc(this.x, this.y, this.radius, 0, StarCanvas.circ);
|
||||
this._ctx.fillStyle = this.color.style(this.brightness * this._config.brightnessMultiplierNode);
|
||||
this._ctx.fill();
|
||||
}
|
||||
|
||||
drawConnections(){
|
||||
for(let i = 0; i < this._siblings.length; i++){
|
||||
this._ctx.beginPath();
|
||||
this._ctx.moveTo(this.x, this.y);
|
||||
this._ctx.lineTo(this._siblings[i].x, this._siblings[i].y);
|
||||
this._ctx.lineWidth = 1 - StarCanvas.calcDistance(this, this._siblings[i]) / this._config.sensitivity;
|
||||
if(this.color === this._siblings[i].color){
|
||||
// no gradient
|
||||
this._ctx.strokeStyle = this.color.style(this.brightness * this._config.brightnessMultiplierConnection);
|
||||
}else{
|
||||
// gradient
|
||||
this._ctx.strokeStyle = this.gradient(this._siblings[i], StarCanvas.lineStyle(this, this._siblings[i])) ;
|
||||
}
|
||||
this._ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
gradient(node2, midColor){
|
||||
let grad = this._ctx.createLinearGradient(Math.floor(this.x), Math.floor(this.y), Math.floor(node2.x), Math.floor(node2.y));
|
||||
grad.addColorStop(0, this.color.style(this.brightness * this._config.brightnessMultiplierConnection));
|
||||
grad.addColorStop(0.5, midColor);
|
||||
grad.addColorStop(1, node2.color.style(node2.brightness * this._config.brightnessMultiplierConnection));
|
||||
return grad;
|
||||
}
|
||||
|
||||
moveNode(){
|
||||
this._energy -= 2;
|
||||
if(this._energy < 1){
|
||||
this._energy = Math.random() * 100;
|
||||
if(this.x - this._anchorX < -this._config.anchorLength){
|
||||
this._vx = Math.random() * 2;
|
||||
}else if(this.x - this._anchorX > this._config.anchorLength){
|
||||
this._vx = Math.random() * -2;
|
||||
}else{
|
||||
this._vx = Math.random() * 4 - 2;
|
||||
}
|
||||
if(this.y - this._anchorY < -this._config.anchorLength){
|
||||
this._vy = Math.random() * 2;
|
||||
}else if (this.y - this._anchorY > this._config.anchorLength){
|
||||
this._vy = Math.random() * -2;
|
||||
}else{
|
||||
this._vy = Math.random() * 4 - 2;
|
||||
}
|
||||
}
|
||||
this.x += this._vx * this._energy / 100;
|
||||
this.y += this._vy * this._energy / 100;
|
||||
}
|
||||
}
|
||||
|
||||
class StarCanvas {
|
||||
constructor(canvas, config = {}) {
|
||||
this._canvas = canvas;
|
||||
this._config = Object.assign({}, new.target.defaultConfig, config);
|
||||
this._nodes = [];
|
||||
this._nodesQty = 0;
|
||||
this._updateActive = true;
|
||||
this._minWait = Math.floor(1 / this._config.fps * 1000);
|
||||
|
||||
this.resizeWindow();
|
||||
this._mouse = this._config.startCoordinates(this._canvas);
|
||||
|
||||
this._ctx = this._canvas.getContext('2d', {alpha: true, desynchronized: true});
|
||||
this.initHandlers();
|
||||
|
||||
// must be bind to this instance -> https://stackoverflow.com/a/46014225/4329969
|
||||
this.onPointerDown = this.onPointerDown.bind(this);
|
||||
this.onPointerEnter = this.onPointerEnter.bind(this);
|
||||
this.onPointerLeave = this.onPointerLeave.bind(this);
|
||||
this.onPointerMove = this.onPointerMove.bind(this);
|
||||
|
||||
this.initPointerLock();
|
||||
this.initIntersectionObserver();
|
||||
this.setColorBase();
|
||||
this.initNodes();
|
||||
this.redrawCheck();
|
||||
}
|
||||
|
||||
setColorBase(){
|
||||
// if base color does not change -> re-use same instance for all nodes
|
||||
this._colorBase = Array.isArray(this._config.colorBase) ? new Color(...this._config.colorBase) : null;
|
||||
}
|
||||
|
||||
isPaused(){
|
||||
return (typeof this._config.isPaused === 'function') ? this._config.isPaused(this) : this._config.isPaused;
|
||||
}
|
||||
|
||||
findSiblings(){
|
||||
let node1, node2, distance;
|
||||
for(let i = 0; i < this._nodesQty; i++){
|
||||
node1 = this._nodes[i];
|
||||
node1.siblings = [];
|
||||
for(let j = 0; j < this._nodesQty; j++){
|
||||
node2 = this._nodes[j];
|
||||
if(node1 !== node2){
|
||||
distance = StarCanvas.calcDistance(node1, node2);
|
||||
if(distance < this._config.sensitivity){
|
||||
if(node1.siblings.length < this._config.siblingsLimit){
|
||||
node1.siblings.push(node2);
|
||||
}else{
|
||||
let node_sibling_distance = 0;
|
||||
let max_distance = 0;
|
||||
let s;
|
||||
for(let k = 0; k < this._config.siblingsLimit; k++){
|
||||
node_sibling_distance = StarCanvas.calcDistance(node1, node1.siblings[k]);
|
||||
if(node_sibling_distance > max_distance){
|
||||
max_distance = node_sibling_distance;
|
||||
s = k;
|
||||
}
|
||||
}
|
||||
if(distance < max_distance){
|
||||
node1.siblings.splice(s, 1);
|
||||
node1.siblings.push(node2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redrawScene(){
|
||||
//this.resizeWindow();
|
||||
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
this.findSiblings();
|
||||
// skip nodes move to move if they are outside the visible radius -> performance boost
|
||||
let haltRadius = this._config.mouseRadius + this._config.anchorLength;
|
||||
// mouse pointer node moves on mousemove
|
||||
this._pointerNode.x = this._mouse.x;
|
||||
this._pointerNode.y = this._mouse.y;
|
||||
let skipNodesMove = [0]; // pointer node
|
||||
|
||||
let i, node, distance;
|
||||
for(i = 0; i < this._nodesQty; i++){
|
||||
node = this._nodes[i];
|
||||
distance = StarCanvas.calcDistance({
|
||||
x: this._mouse.x,
|
||||
y: this._mouse.y
|
||||
}, node);
|
||||
|
||||
if(distance < this._config.mouseRadius){
|
||||
node.brightness = 1 - distance / this._config.mouseRadius;
|
||||
}else{
|
||||
node.brightness = 0;
|
||||
}
|
||||
|
||||
if(distance > haltRadius){
|
||||
skipNodesMove.push(i);
|
||||
}
|
||||
skipNodesMove = [];
|
||||
}
|
||||
|
||||
for(i = 0; i < this._nodesQty; i++){
|
||||
node = this._nodes[i];
|
||||
if(node.brightness){
|
||||
node.drawNode();
|
||||
node.drawConnections();
|
||||
}
|
||||
|
||||
if(!skipNodesMove.includes(i)){
|
||||
node.moveNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redrawCheck(){
|
||||
if(this._animationFrameId){
|
||||
cancelAnimationFrame(this._animationFrameId);
|
||||
}
|
||||
|
||||
this._animationFrameId = requestAnimationFrame(() => {
|
||||
let now = Date.now();
|
||||
|
||||
if(
|
||||
this._updateActive && !this.isPaused() &&
|
||||
now - (this._lastRender || 0) >= this._minWait
|
||||
){
|
||||
this._lastRender = now;
|
||||
this.redrawScene();
|
||||
}
|
||||
|
||||
|
||||
this._animationFrameId = null;
|
||||
this.redrawCheck();
|
||||
});
|
||||
}
|
||||
|
||||
initNodes(){
|
||||
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
this._nodes = [];
|
||||
for(let i = this._config.density; i < this._canvas.width; i += this._config.density) {
|
||||
for(let j = this._config.density; j < this._canvas.height; j += this._config.density) {
|
||||
let node = new Node(i, j, this._ctx, this._config);
|
||||
if(typeof this._config.colorBase === 'function'){
|
||||
node.color = this._config.colorBase(node, this);
|
||||
}else{
|
||||
node.color = this._colorBase;
|
||||
}
|
||||
this._nodes.push(node);
|
||||
this._nodesQty++;
|
||||
}
|
||||
}
|
||||
|
||||
// mouse cursor node
|
||||
this._pointerNode = new Node(
|
||||
this._mouse.x,
|
||||
this._mouse.y,
|
||||
this._ctx,
|
||||
this._config
|
||||
);
|
||||
this._pointerNode.color = new Color(...this._config.colorCursor);
|
||||
this._pointerNode.brightness = 1;
|
||||
this._pointerNode.isPointer = true;
|
||||
this._nodes.unshift(this._pointerNode);
|
||||
this._nodesQty++;
|
||||
}
|
||||
|
||||
initHandlers(){
|
||||
this._canvas.addEventListener('pointerover', e => this.onPointerEnter(e), {passive: true});
|
||||
this._canvas.addEventListener('pointermove', e => this.onPointerMove(e), {passive: true});
|
||||
}
|
||||
|
||||
initPointerLock(){
|
||||
if(!this._config.pointerLock){
|
||||
return;
|
||||
}
|
||||
|
||||
let lockChange = (e) => {
|
||||
/*
|
||||
if(document.pointerLockElement === this._canvas){
|
||||
this._canvas.addEventListener('pointermove', this.onPointerMove, {passive: true});
|
||||
}else{
|
||||
this._canvas.removeEventListener('pointermove', this.onPointerMove, {passive: true});
|
||||
}*/
|
||||
};
|
||||
|
||||
//this._canvas.requestPointerLock()
|
||||
this._canvas.addEventListener('pointerdown', this.onPointerDown, false);
|
||||
//this._canvas.addEventListener('mouseenter', this.onPointerEnter, false);
|
||||
//this._canvas.addEventListener('mouseleave', this.onPointerLeave, false);
|
||||
|
||||
document.addEventListener('pointerlockchange', lockChange, false);
|
||||
}
|
||||
|
||||
initIntersectionObserver(){
|
||||
let intersectionCallback = entries => {
|
||||
let visiblePct = Math.floor(entries[0].intersectionRatio * 100);
|
||||
this._updateActive = visiblePct > 0;
|
||||
};
|
||||
|
||||
this._intersectionObserver = new IntersectionObserver(intersectionCallback, {
|
||||
threshold: [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
|
||||
});
|
||||
this._intersectionObserver.observe(this._canvas);
|
||||
}
|
||||
|
||||
mousemoveHandler(e){
|
||||
this._mouse.x = e.clientX;
|
||||
this._mouse.y = e.clientY;
|
||||
}
|
||||
|
||||
onPointerMove(e){
|
||||
let x = this._mouse.x + Math.floor(e.movementX);
|
||||
let y = this._mouse.y + Math.floor(e.movementY);
|
||||
|
||||
this._mouse.x = Math.min(this._canvas.width, Math.max(0, x));
|
||||
this._mouse.y = Math.min(this._canvas.height, Math.max(0, y));
|
||||
|
||||
/*
|
||||
if(x !== this._mouse.x || y !== this._mouse.y){
|
||||
// cursor outside canvas
|
||||
document.exitPointerLock();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
onPointerDown(e){
|
||||
this._mouse.x = e.clientX;
|
||||
this._mouse.y = e.clientY;
|
||||
if(document.pointerLockElement === this._canvas){
|
||||
document.exitPointerLock();
|
||||
}else{
|
||||
this._canvas.requestPointerLock();
|
||||
}
|
||||
}
|
||||
|
||||
onPointerEnter(e){
|
||||
this._mouse.x = e.clientX;
|
||||
this._mouse.y = e.clientY;
|
||||
}
|
||||
|
||||
onPointerLeave(e){
|
||||
document.exitPointerLock();
|
||||
}
|
||||
|
||||
resizeWindow(){
|
||||
let dimension = this._config.newDimension(this);
|
||||
this._canvas.width = dimension.width;
|
||||
this._canvas.height = dimension.height;
|
||||
}
|
||||
}
|
||||
|
||||
StarCanvas.circ = 2 * Math.PI;
|
||||
|
||||
StarCanvas.calcDistance = (node1, node2) => {
|
||||
return Math.sqrt(Math.pow(node1.x - node2.x, 2) + (Math.pow(node1.y - node2.y, 2)));
|
||||
};
|
||||
|
||||
StarCanvas.lineStyle = (node1, node2) => {
|
||||
let r = StarCanvas.mixComponents(node1.color.r, node2.color.r, node1.radius, node2.radius);
|
||||
let g = StarCanvas.mixComponents(node1.color.g, node2.color.g, node1.radius, node2.radius);
|
||||
let b = StarCanvas.mixComponents(node1.color.b, node2.color.b, node1.radius, node2.radius);
|
||||
let a = (node1.brightness + node2.brightness) / 2;
|
||||
return `rgba(${Math.floor(r)}, ${Math.floor(g)}, ${Math.floor(b)}, ${a})`;
|
||||
};
|
||||
|
||||
StarCanvas.mixComponents = (comp1, comp2, weight1, weight2) => {
|
||||
return (comp1*weight1 + comp2*weight2) / (weight1 + weight2);
|
||||
};
|
||||
|
||||
StarCanvas.getRandomIntInclusive = (min, max) => {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min +1)) + min;
|
||||
};
|
||||
|
||||
|
||||
StarCanvas.defaultConfig = {
|
||||
// limit render interval to max fps
|
||||
// lower fps == less CPU
|
||||
fps: 80,
|
||||
// how close next node must be to activate connection (in px)
|
||||
// shorter distance == better connection (line width)
|
||||
sensitivity: 90,
|
||||
// note that siblings limit is not 'accurate' as the node can actually have more connections than this value
|
||||
// that's because the node accepts sibling nodes with no regard to their current connections this is acceptable
|
||||
// because potential fix would not result in significant visual difference
|
||||
// more siblings == bigger node
|
||||
siblingsLimit: 15,
|
||||
// default node margin
|
||||
density: 50,
|
||||
// avoid nodes spreading
|
||||
anchorLength: 80,
|
||||
// highlight radius
|
||||
mouseRadius: 150,
|
||||
// values < 1 will lower the calculated node brightness [0-1]
|
||||
brightnessMultiplierNode: 1,
|
||||
// values < 1 will lower the calculated connection brightness [0-1]
|
||||
brightnessMultiplierConnection: 1,
|
||||
colorBase: [108, 174, 173], // teal
|
||||
colorCursor: [226, 138, 13], // orange
|
||||
// callback for canvas dimension re-calc
|
||||
newDimension: () => ({width: window.innerWidth, height: window.innerHeight}),
|
||||
// start coordinates (before mouse move)
|
||||
startCoordinates: canvas => ({
|
||||
x: canvas.width / 2,
|
||||
y: canvas.height / 2
|
||||
}),
|
||||
// callback/boolean to pause canvas updated (e.g. while page scroll). Better scroll performance
|
||||
isPaused: false,
|
||||
pointerLock: true
|
||||
};
|
||||
|
||||
// custom configuration -------------------------------------------------------------------------------------------
|
||||
let defaultConfig = {
|
||||
brightnessMultiplierConnection: 0.95,
|
||||
colorCursor: [200, 184, 71], // yellow dark
|
||||
newDimension: starCanvas => ({
|
||||
width: window.innerWidth,
|
||||
height: 354 // max height + 1px border
|
||||
}),
|
||||
startCoordinates: canvas => ({
|
||||
x: canvas.width / 2 + 500,
|
||||
y: canvas.height / 2 + 50
|
||||
}),
|
||||
isPaused: () => document.body.classList.contains('on-scroll')
|
||||
};
|
||||
|
||||
if(navigator.userAgent.indexOf('Chrome') > -1){
|
||||
// Chrome user
|
||||
Object.assign(defaultConfig, {
|
||||
sensitivity: 85,
|
||||
siblingsLimit: 10,
|
||||
density: 60,
|
||||
anchorLength: 20,
|
||||
colorBase: (node, instance) => {
|
||||
let colorId = StarCanvas.getRandomIntInclusive(0, 4);
|
||||
let colorKey = `_randColor${colorId}`;
|
||||
if(instance[colorKey]){
|
||||
return instance[colorKey];
|
||||
}
|
||||
|
||||
let rgb = StarCanvas.defaultConfig.colorBase;
|
||||
switch(colorId){
|
||||
case 1: rgb = [ 92, 184, 92]; break; // green
|
||||
case 2: rgb = [ 68, 170, 130]; break; // aqua
|
||||
case 3: rgb = [194, 118, 12]; break; // orange dark
|
||||
case 4: rgb = StarCanvas.defaultConfig.colorBase; break;
|
||||
}
|
||||
|
||||
instance[colorKey] = new Color(...rgb);
|
||||
return instance[colorKey];
|
||||
}
|
||||
});
|
||||
}else{
|
||||
// Non Chrome user
|
||||
Object.assign(defaultConfig, {
|
||||
sensitivity: 85,
|
||||
siblingsLimit: 6,
|
||||
density: 60,
|
||||
anchorLength: 20,
|
||||
mouseRadius: 120
|
||||
});
|
||||
}
|
||||
|
||||
let init = headerEl => {
|
||||
let previewEls = headerEl.getElementsByClassName(config.previewElementClass);
|
||||
|
||||
$(previewEls).velocity('transition.bounceIn', {
|
||||
duration: 600,
|
||||
stagger: 60,
|
||||
delay: 120,
|
||||
complete: function(){
|
||||
let canvas = document.getElementById(config.canvasId);
|
||||
// not on mobile
|
||||
if(canvas){
|
||||
let starCanvasInstance = new StarCanvas(canvas, defaultConfig);
|
||||
canvas.classList.add('in');
|
||||
|
||||
// watch for resize
|
||||
Util.getResizeManager().observe(
|
||||
canvas.parentNode,
|
||||
(el, contentRect) => {
|
||||
// ignore "height" change (css transition) (no canvas repaint)
|
||||
if(canvas.width !== contentRect.width){
|
||||
starCanvasInstance.resizeWindow();
|
||||
}
|
||||
},
|
||||
{debounce: true, ms: 260}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
init
|
||||
};
|
||||
});
|
||||
@@ -1,513 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/map/util',
|
||||
'app/lib/cache',
|
||||
'app/promises/promise.deferred',
|
||||
'app/promises/promise.queue'
|
||||
], ($, Init, Util, MapUtil, Cache, DeferredPromise, PromiseQueue) => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* abstract BaseModel class
|
||||
* -> custom/plugin modules must extend from it
|
||||
* @type {BaseModule}
|
||||
*/
|
||||
let BaseModule = class BaseModule {
|
||||
|
||||
constructor(config= {}){
|
||||
if(new.target === BaseModule){
|
||||
throw new TypeError('Cannot construct ' + this.constructor.name + ' instances directly');
|
||||
}
|
||||
|
||||
// check for abstract methods to be implemented in child
|
||||
if(this.render === undefined){
|
||||
throw new TypeError('Abstract method render() missing in ' + new.target.name + ' class');
|
||||
}
|
||||
|
||||
this._config = Object.assign({}, BaseModule.defaultConfig, config);
|
||||
this._updateQueue = new PromiseQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* get current module configuration
|
||||
* @returns {*}
|
||||
*/
|
||||
get config(){
|
||||
return this._config;
|
||||
}
|
||||
|
||||
/**
|
||||
* get root node for this module
|
||||
* -> parent container for custom body HTML
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
get moduleElement(){
|
||||
if(!this._moduleEl){
|
||||
// init new moduleElement
|
||||
this._moduleEl = Object.assign(document.createElement('div'), {
|
||||
className: `${BaseModule.className} ${this._config.className}`,
|
||||
style: {
|
||||
opacity: '0'
|
||||
}
|
||||
}).setData('module', this);
|
||||
|
||||
this._moduleEl.dataset.position = this._config.position;
|
||||
this._moduleEl.dataset.module = this.constructor.name;
|
||||
|
||||
// module header
|
||||
this._moduleEl.append(this.newHeaderElement());
|
||||
}
|
||||
return this._moduleEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* module header element
|
||||
* -> dragHandler + headline
|
||||
* @param text
|
||||
* @returns {HTMLDivElement}
|
||||
*/
|
||||
newHeaderElement(text){
|
||||
let headEl = this.newHeadElement();
|
||||
headEl.append(
|
||||
this.newHandlerElement(),
|
||||
this.newHeadlineElement(text || this._config.headline)
|
||||
);
|
||||
return headEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* module head element
|
||||
* @returns {HTMLDivElement}
|
||||
*/
|
||||
newHeadElement(){
|
||||
return Object.assign(document.createElement('div'), {
|
||||
className: this._config.headClassName
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* module dragHandler element
|
||||
* @returns {HTMLHeadingElement}
|
||||
*/
|
||||
newHandlerElement(){
|
||||
return Object.assign(document.createElement('h5'), {
|
||||
className: this._config.handlerClassName
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* module headline element
|
||||
* @param text
|
||||
* @returns {HTMLHeadingElement}
|
||||
*/
|
||||
newHeadlineElement(text){
|
||||
return Object.assign(document.createElement('h5'), {
|
||||
textContent: typeof text === 'string' ? text : ''
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* module toolbar element (wrapper)
|
||||
* @returns {HTMLHeadingElement}
|
||||
*/
|
||||
newHeadlineToolbarElement(){
|
||||
return Object.assign(document.createElement('h5'), {
|
||||
className: 'pull-right'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* icon element
|
||||
* @param cls
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
newIconElement(cls = []){
|
||||
return Object.assign(document.createElement('i'), {
|
||||
className: ['fas', ...cls].join(' ')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* label element
|
||||
* @param text
|
||||
* @param cls
|
||||
* @returns {HTMLSpanElement}
|
||||
*/
|
||||
newLabelElement(text, cls = []){
|
||||
let labelEl = document.createElement('span');
|
||||
labelEl.classList.add('label', 'center-block', ...cls);
|
||||
labelEl.textContent = text || '';
|
||||
return labelEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* control button element
|
||||
* @param text
|
||||
* @param cls
|
||||
* @param iconCls
|
||||
* @returns {HTMLDivElement}
|
||||
*/
|
||||
newControlElement(text, cls = [], iconCls = ['fa-sync']){
|
||||
let controlEl = document.createElement('div');
|
||||
controlEl.classList.add(...[BaseModule.Util.config.dynamicAreaClass, this._config.controlAreaClass, ...cls]);
|
||||
controlEl.insertAdjacentHTML('beforeend', ` ${text}`);
|
||||
controlEl.prepend(this.newIconElement(iconCls));
|
||||
return controlEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP request handler for internal (Pathfinder) ajax calls
|
||||
* @param args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
request(...args){
|
||||
return BaseModule.Util.request(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* scoped instance for LocalStore for current module
|
||||
* @returns {LocalStore}
|
||||
*/
|
||||
getLocalStore(){
|
||||
if(!this._localStore){
|
||||
// make accessible -> scope Store keys!
|
||||
this._localStore = BaseModule.Util.getLocalStore('module');
|
||||
this._localStore.scope = this.constructor.name;
|
||||
}
|
||||
return this._localStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* visual notification handler (UI popover)
|
||||
* -> can be used for info/error on-screen messages
|
||||
* @param args
|
||||
*/
|
||||
showNotify(...args){
|
||||
return BaseModule.Util.showNotify(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* responsible for dispatching all incoming method calls
|
||||
* @param handler
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
handle(handler, ...data){
|
||||
try{
|
||||
if(BaseModule.handler.includes(handler)){
|
||||
// .. run module handler
|
||||
let returnData = this[handler].apply(this, data);
|
||||
if(returnData instanceof Promise){
|
||||
// log returned Promise from handler call resolved
|
||||
returnData.then(() => { this.logHandler(handler, 0);});
|
||||
}
|
||||
// log handler call
|
||||
this.logHandler(handler);
|
||||
|
||||
return returnData;
|
||||
}else{
|
||||
console.error('Error in module %o. Invalid handler %o', this.constructor.name, handler);
|
||||
}
|
||||
}catch(e){
|
||||
console.error('Error in module %o in handler %s() %o', this.constructor.name, handler, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* log handler calls for this instance
|
||||
* -> can be helpful for debugging
|
||||
* @param handler
|
||||
* @param increment
|
||||
*/
|
||||
logHandler(handler, increment = 1){
|
||||
if(increment){
|
||||
if(!this._config.logHandler){
|
||||
this._config.logHandler = {};
|
||||
}
|
||||
|
||||
this._config.logHandler[handler] = (this._config.logHandler[handler] || 0) + increment;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* init module
|
||||
*/
|
||||
init(){}
|
||||
|
||||
/**
|
||||
* update module
|
||||
* @param data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
update(data){
|
||||
return this._updateQueue.enqueue(() => Promise.resolve(data), 'end', 'upd');
|
||||
}
|
||||
|
||||
beforeHide(){}
|
||||
|
||||
beforeDestroy(){
|
||||
$(this.moduleElement).destroyPopover(true);
|
||||
|
||||
// destroy DataTable instances
|
||||
for(let table of $(this.moduleElement).find('table.dataTable')){
|
||||
$(table).DataTable().destroy(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* events from 'Sortable' lib
|
||||
* @see https://github.com/SortableJS/Sortable
|
||||
* @param name
|
||||
* @param e
|
||||
*/
|
||||
onSortableEvent(name, e){
|
||||
if(name === 'onUnchoose' && this._sortableChoosePromise){
|
||||
this._sortableChoosePromise.resolve();
|
||||
}
|
||||
|
||||
if(name === 'onChoose' && !this._sortableChoosePromise){
|
||||
this._sortableChoosePromise = BaseModule.newDeferredPromise();
|
||||
this._updateQueue.enqueue(() => this._sortableChoosePromise.then(() => {
|
||||
this._sortableChoosePromise = null;
|
||||
}), 'start');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get a unique cache key name for "source"/"target"-name
|
||||
* @param sourceName
|
||||
* @param targetName
|
||||
* @returns {string|boolean}
|
||||
*/
|
||||
static getConnectionDataCacheKey(sourceName, targetName){
|
||||
let key = false;
|
||||
if(sourceName && targetName){
|
||||
// names can be "undefined" in case system is currently in drag/drop state
|
||||
// sort() is important -> ignore direction
|
||||
key = `con_` + `${ [String(sourceName).toLowerCase(), String(targetName).toLowerCase()].sort() }`.hashCode();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a connectionsData object that holds all connections for given mapIds (used as cache for route search)
|
||||
* @param mapIds
|
||||
* @returns {{}}
|
||||
*/
|
||||
static getConnectionsDataFromMaps(mapIds){
|
||||
let data = {};
|
||||
for(let mapId of mapIds){
|
||||
let map = MapUtil.getMapInstance(mapId);
|
||||
if(map){
|
||||
let cacheKey = `map_${mapId}`;
|
||||
let cache = BaseModule.getCache('mapConnections');
|
||||
let mapConnectionsData = cache.get(cacheKey);
|
||||
|
||||
if(!mapConnectionsData){
|
||||
mapConnectionsData = this.getConnectionsDataFromConnections(mapId, map.getAllConnections());
|
||||
// update cache
|
||||
cache.set(cacheKey, mapConnectionsData);
|
||||
}
|
||||
Object.assign(data, mapConnectionsData);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a connectionsData object for all connections
|
||||
* @param mapId
|
||||
* @param connections
|
||||
* @returns {{}}
|
||||
*/
|
||||
static getConnectionsDataFromConnections(mapId = 0, connections = []){
|
||||
let data = {};
|
||||
if(connections.length){
|
||||
let connectionsData = MapUtil.getDataByConnections(connections);
|
||||
for(let connectionData of connectionsData){
|
||||
let connectionDataCacheKey = BaseModule.getConnectionDataCacheKey(connectionData.sourceName, connectionData.targetName);
|
||||
|
||||
// skip double connections between same systems
|
||||
if(connectionDataCacheKey && !Object.keys(data).includes(connectionDataCacheKey)){
|
||||
data[connectionDataCacheKey] = {
|
||||
map: {
|
||||
id: mapId
|
||||
},
|
||||
connection: {
|
||||
id: connectionData.id,
|
||||
type: connectionData.type,
|
||||
scope: connectionData.scope,
|
||||
updated: connectionData.updated
|
||||
},
|
||||
source: {
|
||||
id: connectionData.source,
|
||||
name: connectionData.sourceName,
|
||||
alias: connectionData.sourceAlias
|
||||
},
|
||||
target: {
|
||||
id: connectionData.target,
|
||||
name: connectionData.targetName,
|
||||
alias: connectionData.targetAlias
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* search for a specific connection by "source"/"target"-name inside connectionsData cache
|
||||
* @param connectionsData
|
||||
* @param sourceName
|
||||
* @param targetName
|
||||
* @returns {*}
|
||||
*/
|
||||
static findConnectionsData(connectionsData, sourceName, targetName){
|
||||
return this.Util.getObjVal(connectionsData, this.getConnectionDataCacheKey(sourceName, targetName));
|
||||
}
|
||||
|
||||
/**
|
||||
* get fake connection data (default connection type in case connection was not found on a map)
|
||||
* @param sourceSystemData
|
||||
* @param targetSystemData
|
||||
* @param scope
|
||||
* @param types
|
||||
* @returns {{connection: {scope: string, id: number, type: [*]}, source: {name: number, alias: number, id: number}, target: {name: number, alias: number, id: number}}}
|
||||
*/
|
||||
static getFakeConnectionData(sourceSystemData, targetSystemData, scope = 'stargate', types = []){
|
||||
return {
|
||||
connection: {
|
||||
id: 0,
|
||||
scope: scope,
|
||||
type: types.length ? types : [MapUtil.getDefaultConnectionTypeByScope(scope)],
|
||||
updated: 0
|
||||
},
|
||||
source: {
|
||||
id: 0,
|
||||
name: sourceSystemData.system,
|
||||
alias: sourceSystemData.system
|
||||
},
|
||||
target: {
|
||||
id: 0,
|
||||
name: targetSystemData.system,
|
||||
alias: targetSystemData.system
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get fake connection Element
|
||||
* @param connectionData
|
||||
* @returns {string}
|
||||
*/
|
||||
static getFakeConnectionElement(connectionData){
|
||||
let mapId = this.Util.getObjVal(connectionData, 'map.id') || 0;
|
||||
let connectionId = this.Util.getObjVal(connectionData, 'connection.id') || 0;
|
||||
let scope = this.Util.getObjVal(connectionData, 'connection.scope') || '';
|
||||
let classes = MapUtil.getConnectionFakeClassesByTypes(this.Util.getObjVal(connectionData, 'connection.type') || []);
|
||||
let disabled = !mapId || !connectionId;
|
||||
|
||||
let connectionElement = '<div data-mapId="' + mapId + '" data-connectionId="' + connectionId + '" ';
|
||||
connectionElement += (disabled ? 'data-disabled' : '');
|
||||
connectionElement += ' class="' + classes.join(' ') + '" ';
|
||||
connectionElement += ' title="' + scope + '" data-placement="bottom"></div>';
|
||||
return connectionElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* get static instance of in-memory Cache() store by 'name'
|
||||
* -> not persistent across page reloads
|
||||
* -> persistent across module instances (different and same maps)
|
||||
* @param name
|
||||
* @returns {Cache}
|
||||
*/
|
||||
static getCache(name){
|
||||
let key = `CACHE-${name}`;
|
||||
if(!this.Util.getObjVal(this, key)){
|
||||
let configKey = `cacheConfig.${name}`;
|
||||
let cacheConfig = this.Util.getObjVal(this, configKey);
|
||||
if(!cacheConfig){
|
||||
console.warn('Missing Cache config for %o. Expected at %o. Default config loaded…',
|
||||
name, `${this.name}.${configKey}`
|
||||
);
|
||||
cacheConfig = {};
|
||||
}else{
|
||||
// set cache name
|
||||
cacheConfig.name = name;
|
||||
}
|
||||
|
||||
this[key] = new Cache(cacheConfig);
|
||||
}
|
||||
return this[key];
|
||||
}
|
||||
|
||||
static now(){
|
||||
return new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
static getOrderPrio(){
|
||||
return this.isPlugin ?
|
||||
this.scopeOrder.indexOf('plugin') :
|
||||
(this.scopeOrder.indexOf(this.scope) !== -1 ?
|
||||
this.scopeOrder.indexOf(this.scope) :
|
||||
this.scopeOrder.length - 1
|
||||
);
|
||||
}
|
||||
|
||||
static newDeferredPromise(){
|
||||
return new DeferredPromise();
|
||||
}
|
||||
};
|
||||
|
||||
BaseModule.isPlugin = true; // module is defined as 'plugin'
|
||||
BaseModule.scope = 'system'; // static module scope controls how module gets updated and what type of data is injected
|
||||
BaseModule.sortArea = 'a'; // static default sortable area
|
||||
BaseModule.position = 0; // static default sort/order position within sortable area
|
||||
BaseModule.label = '???'; // static module label (e.g. description)
|
||||
BaseModule.className = 'pf-module'; // static CSS class name
|
||||
BaseModule.fullDataUpdate = false; // static module requires additional data (e.g. system description,...)
|
||||
BaseModule.Util = Util; // static access to Pathfinders Util object
|
||||
|
||||
BaseModule.scopeOrder = [
|
||||
'system',
|
||||
'connection',
|
||||
'global',
|
||||
'plugin',
|
||||
'undefined'
|
||||
];
|
||||
|
||||
BaseModule.handler = [
|
||||
'render',
|
||||
'init',
|
||||
'update',
|
||||
'beforeHide',
|
||||
'beforeDestroy',
|
||||
'onSortableEvent'
|
||||
];
|
||||
|
||||
BaseModule.cacheConfig = {
|
||||
mapConnections: {
|
||||
ttl: 5,
|
||||
maxSize: 600,
|
||||
debug: false
|
||||
}
|
||||
};
|
||||
|
||||
BaseModule.defaultConfig = {
|
||||
position: 1,
|
||||
className: 'pf-base-module', // class for module
|
||||
headClassName: 'pf-module-head', // class for module header
|
||||
handlerClassName: 'pf-sortable-handle', // class for "drag" handler
|
||||
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
|
||||
headline: 'Base headline', // module headline
|
||||
bodyClassName: 'pf-module-body', // class for module body [optional: can be used]
|
||||
controlAreaClass: 'pf-module-control-area', // class for "control" areas
|
||||
|
||||
moduleHeadlineIconClass: 'pf-module-icon-button' // class for toolbar icons in the head
|
||||
};
|
||||
|
||||
return BaseModule;
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,311 +1,2 @@
|
||||
define([ // dependencies for this module
|
||||
'module/base', // abstract `parent` module class definition [required]
|
||||
'app/render' // ... for demo purpose, we load a Render helper object
|
||||
], (BaseModule, Render) => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* DemoModule class
|
||||
* -> skeleton for custom module plugins
|
||||
* @type {DemoModule}
|
||||
*/
|
||||
let DemoModule = class DemoModule extends BaseModule {
|
||||
constructor(config = {}) {
|
||||
super(Object.assign({}, new.target.defaultConfig, config));
|
||||
}
|
||||
|
||||
/**
|
||||
* module header
|
||||
* @param text
|
||||
* @returns {HTMLDivElement}
|
||||
*/
|
||||
newHeaderElement(text){
|
||||
let headEl = super.newHeaderElement(text);
|
||||
let toolbarEl = this.newHeadlineToolbarElement();
|
||||
let iconPlayEl = this.newIconElement([
|
||||
'fa-play', 'fa-fw',
|
||||
'txt-color', 'txt-color-success',
|
||||
this._config.moduleHeadlineIconClass
|
||||
]);
|
||||
iconPlayEl.setAttribute('title', 'pause update()');
|
||||
iconPlayEl.onclick = e => this.toggleUpdates(e.target);
|
||||
|
||||
toolbarEl.append(iconPlayEl);
|
||||
headEl.append(toolbarEl);
|
||||
return headEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* extends logHandler() from BaseModule
|
||||
* -> updates moduleBody (demo)
|
||||
* @param handler
|
||||
* @param increment
|
||||
*/
|
||||
logHandler(handler, increment = 1){
|
||||
super.logHandler(handler, increment);
|
||||
|
||||
/**
|
||||
* @param handler
|
||||
* @returns {[HTMLSpanElement, HTMLSpanElement]}
|
||||
*/
|
||||
let newLiContent = handler => {
|
||||
let count = this._config.logHandler[handler] || 0;
|
||||
let label = count ? 'success' : false;
|
||||
let icon = 'fa-circle';
|
||||
let handlerQueueLength;
|
||||
if(this[`_${handler}Queue`]){
|
||||
handlerQueueLength = this[`_${handler}Queue`].filterQueue(item => item.data === 'upd').length;
|
||||
label = handlerQueueLength ? 'warning' : label;
|
||||
icon = handlerQueueLength ? 'fa-sync fa-spin' : icon;
|
||||
}
|
||||
|
||||
let iconLiEl = Object.assign(document.createElement('span'), {
|
||||
className: 'fa-li'
|
||||
});
|
||||
iconLiEl.append(this.newIconElement([icon, 'fa-fw', 'txt-color', label ? `txt-color-${label}` : ``]));
|
||||
|
||||
let textLiEl = Object.assign(document.createElement('span'), {
|
||||
textContent: `${handler} [${count}]${Number.isInteger(handlerQueueLength) ? `[${handlerQueueLength}]`: ``}`,
|
||||
className: label ? `pf-animation-pulse-${label}` : ``
|
||||
});
|
||||
return [iconLiEl, textLiEl];
|
||||
};
|
||||
|
||||
let ulEl = this.queryGridItem('info').querySelector(`.fa-ul`);
|
||||
if(!ulEl){
|
||||
ulEl = Object.assign(document.createElement('ul'), {
|
||||
className: 'fa-ul'
|
||||
});
|
||||
|
||||
let liEls = BaseModule.handler.map(handler => {
|
||||
let liEl = document.createElement('li');
|
||||
liEl.dataset.handler = handler;
|
||||
liEl.prepend(...newLiContent(handler));
|
||||
return liEl;
|
||||
});
|
||||
|
||||
ulEl.append(...liEls);
|
||||
this.queryGridItem('info').querySelector(`code`).insertAdjacentElement('beforebegin', ulEl);
|
||||
}else{
|
||||
ulEl.querySelector(`[data-handler="${handler}"]`).innerHTML = newLiContent(handler).map(el => el.outerHTML).join('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initial module render method
|
||||
* -> implementation is enforced by BaseModule
|
||||
* -> must return a single node element
|
||||
* @param mapId
|
||||
* @param systemData
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
render(mapId, systemData){
|
||||
this._systemData = systemData;
|
||||
|
||||
// ... append your custom module body
|
||||
let bodyEl = Object.assign(document.createElement('div'), {
|
||||
className: [this._config.bodyClassName, 'grid'].join(' ')
|
||||
});
|
||||
|
||||
let gridItems = [];
|
||||
for(let [area, conf] of Object.entries(this._config.gridItems)){
|
||||
gridItems.push(this.newGridItemEl(area, conf.label));
|
||||
}
|
||||
bodyEl.append(...gridItems);
|
||||
this.moduleElement.append(bodyEl);
|
||||
|
||||
this.renderJson('_config', this._config, 'info');
|
||||
this.renderJson('render()', {mapId, systemData});
|
||||
|
||||
let {config, data} = BaseModule.Util.getCurrentMapData(this._systemData.mapId);
|
||||
this.renderJson('currentMapData', {config, data}, 'mapData');
|
||||
return this.moduleElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* update module
|
||||
* @param systemData
|
||||
* @returns {Promise}
|
||||
*/
|
||||
update(systemData){
|
||||
return super.update(systemData).then(systemData => new Promise(resolve => {
|
||||
this.renderJson('update()', {systemData});
|
||||
|
||||
// ... custom (async) code e.g. request external API and update module
|
||||
// -> resolve() Promise when module is updated.
|
||||
resolve({
|
||||
action: 'update',
|
||||
data: {
|
||||
module: this
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* init module
|
||||
*/
|
||||
init(){
|
||||
super.init();
|
||||
this.renderJson('init()', null);
|
||||
this.renderJson('currentUserData', BaseModule.Util.getCurrentUserData(), 'userData');
|
||||
}
|
||||
|
||||
beforeHide(){
|
||||
super.beforeHide();
|
||||
this.renderJson('beforeHide()', null);
|
||||
}
|
||||
|
||||
beforeDestroy(){
|
||||
super.beforeDestroy();
|
||||
this.renderJson('beforeDestroy()', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @param e
|
||||
*/
|
||||
onSortableEvent(name, e){
|
||||
super.onSortableEvent(name, e);
|
||||
this.renderJson(`${name}()`, e, 'sortableJs');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param label
|
||||
* @param data
|
||||
* @param area
|
||||
*/
|
||||
renderJson(label, data, area = 'trigger'){
|
||||
let now = new Date();
|
||||
let codeEl = this.queryGridItem(area).querySelector(`code`);
|
||||
codeEl.prepend(Object.assign(document.createElement('section'), {
|
||||
className: this._config.highlightClassName,
|
||||
innerHTML: `${++this._config.counter}. ${now.toLocaleTimeString('en-GB')}.${String(now.getMilliseconds()).padStart(3, '0')} ${label} \n` +
|
||||
`${Render.highlightJson(data, this._config.gridItems[area].jsonConf)}`
|
||||
}));
|
||||
// limit code blocks
|
||||
if(codeEl.childElementCount > this._config.maxCodeSections){
|
||||
codeEl.removeChild(codeEl.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iconEl
|
||||
*/
|
||||
toggleUpdates(iconEl){
|
||||
iconEl.classList.toggle('fa-pause');
|
||||
iconEl.classList.toggle('txt-color-danger');
|
||||
iconEl.classList.toggle('fa-play');
|
||||
iconEl.classList.toggle('txt-color-success');
|
||||
|
||||
if(this._pauseUpdatesPromise){
|
||||
this._pauseUpdatesPromise.resolve();
|
||||
}else{
|
||||
this._pauseUpdatesPromise = BaseModule.newDeferredPromise();
|
||||
this._updateQueue.enqueue(() => this._pauseUpdatesPromise.then(() => {
|
||||
this._pauseUpdatesPromise = null;
|
||||
}), 'start');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* new gridItem element
|
||||
* @param area
|
||||
* @param label
|
||||
* @returns {HTMLPreElement}
|
||||
*/
|
||||
newGridItemEl(area, label){
|
||||
if(!this._gridItemEl){
|
||||
// create blank gridItem element for later cloning
|
||||
this._gridItemEl = Object.assign(document.createElement('pre'), {
|
||||
className: this._config.gridItemClassName,
|
||||
innerHTML: '<code></code>'
|
||||
});
|
||||
}
|
||||
|
||||
let iconClearEl = this.newIconElement([
|
||||
'fa-trash', 'fa-fw', 'pull-right',
|
||||
this._config.moduleHeadlineIconClass
|
||||
]);
|
||||
iconClearEl.setAttribute('title', 'clear output');
|
||||
iconClearEl.onclick = e => e.target.closest(`.${this._config.gridItemClassName}`).querySelector('code').innerHTML = '';
|
||||
|
||||
let toolbarEl = this.newHeadlineToolbarElement();
|
||||
toolbarEl.append(iconClearEl);
|
||||
|
||||
let gridItemEl = this._gridItemEl.cloneNode(true);
|
||||
gridItemEl.dataset.area = area;
|
||||
gridItemEl.prepend(toolbarEl, this.newHeadlineElement(label));
|
||||
return gridItemEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* get gridItem <pre> element from module body
|
||||
* @param {string} area
|
||||
* @returns {HTMLPreElement}
|
||||
*/
|
||||
queryGridItem(area){
|
||||
return this.moduleElement.querySelector(`.${this._config.bodyClassName} .${this._config.gridItemClassName}[data-area="${area}"]`);
|
||||
}
|
||||
};
|
||||
|
||||
DemoModule.isPlugin = true; // module is defined as 'plugin'
|
||||
DemoModule.scope = 'system'; // module scope controls how module gets updated and what type of data is injected
|
||||
DemoModule.sortArea = 'a'; // default sortable area
|
||||
DemoModule.position = 10; // default sort/order position within sortable area
|
||||
DemoModule.label = 'Demo'; // static module label (e.g. description)
|
||||
DemoModule.fullDataUpdate = true; // subscribe module for frequently updates see update() method
|
||||
|
||||
DemoModule.defaultConfig = {
|
||||
className: 'pf-system-demo-module', // class for module
|
||||
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
|
||||
headline: 'Demo Module',
|
||||
|
||||
// ... custom config for DemoModule
|
||||
gridItemClassName: 'pf-dynamic-area',
|
||||
highlightClassName: 'pf-animation-pulse-success',
|
||||
counter: 0,
|
||||
maxCodeSections: 8,
|
||||
gridItems: {
|
||||
info: {
|
||||
label: `handler/config`,
|
||||
jsonConf: {
|
||||
collapseDepth: 1,
|
||||
maxDepth: 3
|
||||
}
|
||||
},
|
||||
trigger: {
|
||||
label: `trigger`,
|
||||
jsonConf: {
|
||||
collapseDepth: 1,
|
||||
maxDepth: 5
|
||||
}
|
||||
},
|
||||
userData: {
|
||||
label: `user/char data`,
|
||||
jsonConf: {
|
||||
collapseDepth: 1,
|
||||
maxDepth: 8
|
||||
}
|
||||
},
|
||||
mapData: {
|
||||
label: `map data`,
|
||||
jsonConf: {
|
||||
collapseDepth: 2,
|
||||
maxDepth: 8,
|
||||
maxLinesFunctions: 2
|
||||
}
|
||||
},
|
||||
sortableJs: {
|
||||
label: `drag&drop events`,
|
||||
jsonConf: {
|
||||
collapseDepth: 0,
|
||||
maxDepth: 4,
|
||||
maxLinesFunctions: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return DemoModule;
|
||||
});
|
||||
define(["module/base","app/render"],(e,t)=>{"use strict";let s=class DemoModule extends e{constructor(e={}){super(Object.assign({},new.target.defaultConfig,e))}newHeaderElement(e){let t=super.newHeaderElement(e),s=this.newHeadlineToolbarElement(),a=this.newIconElement(["fa-play","fa-fw","txt-color","txt-color-success",this._config.moduleHeadlineIconClass]);return a.setAttribute("title","pause update()"),a.onclick=(e=>this.toggleUpdates(e.target)),s.append(a),t.append(s),t}logHandler(t,s=1){super.logHandler(t,s);let a=e=>{let t,s=this._config.logHandler[e]||0,a=!!s&&"success",n="fa-circle";this[`_${e}Queue`]&&(a=(t=this[`_${e}Queue`].filterQueue(e=>"upd"===e.data).length)?"warning":a,n=t?"fa-sync fa-spin":n);let r=Object.assign(document.createElement("span"),{className:"fa-li"});return r.append(this.newIconElement([n,"fa-fw","txt-color",a?`txt-color-${a}`:""])),[r,Object.assign(document.createElement("span"),{textContent:`${e} [${s}]${Number.isInteger(t)?`[${t}]`:""}`,className:a?`pf-animation-pulse-${a}`:""})]},n=this.queryGridItem("info").querySelector(".fa-ul");if(n)n.querySelector(`[data-handler="${t}"]`).innerHTML=a(t).map(e=>e.outerHTML).join("");else{n=Object.assign(document.createElement("ul"),{className:"fa-ul"});let t=e.handler.map(e=>{let t=document.createElement("li");return t.dataset.handler=e,t.prepend(...a(e)),t});n.append(...t),this.queryGridItem("info").querySelector("code").insertAdjacentElement("beforebegin",n)}}render(t,s){this._systemData=s;let a=Object.assign(document.createElement("div"),{className:[this._config.bodyClassName,"grid"].join(" ")}),n=[];for(let[e,t]of Object.entries(this._config.gridItems))n.push(this.newGridItemEl(e,t.label));a.append(...n),this.moduleElement.append(a),this.renderJson("_config",this._config,"info"),this.renderJson("render()",{mapId:t,systemData:s});let{config:r,data:i}=e.Util.getCurrentMapData(this._systemData.mapId);return this.renderJson("currentMapData",{config:r,data:i},"mapData"),this.moduleElement}update(e){return super.update(e).then(e=>new Promise(t=>{this.renderJson("update()",{systemData:e}),t({action:"update",data:{module:this}})}))}init(){super.init(),this.renderJson("init()",null),this.renderJson("currentUserData",e.Util.getCurrentUserData(),"userData")}beforeHide(){super.beforeHide(),this.renderJson("beforeHide()",null)}beforeDestroy(){super.beforeDestroy(),this.renderJson("beforeDestroy()",null)}onSortableEvent(e,t){super.onSortableEvent(e,t),this.renderJson(`${e}()`,t,"sortableJs")}renderJson(e,s,a="trigger"){let n=new Date,r=this.queryGridItem(a).querySelector("code");r.prepend(Object.assign(document.createElement("section"),{className:this._config.highlightClassName,innerHTML:`${++this._config.counter}. ${n.toLocaleTimeString("en-GB")}.${String(n.getMilliseconds()).padStart(3,"0")} ${e} \n`+`${t.highlightJson(s,this._config.gridItems[a].jsonConf)}`})),r.childElementCount>this._config.maxCodeSections&&r.removeChild(r.lastChild)}toggleUpdates(t){t.classList.toggle("fa-pause"),t.classList.toggle("txt-color-danger"),t.classList.toggle("fa-play"),t.classList.toggle("txt-color-success"),this._pauseUpdatesPromise?this._pauseUpdatesPromise.resolve():(this._pauseUpdatesPromise=e.newDeferredPromise(),this._updateQueue.enqueue(()=>this._pauseUpdatesPromise.then(()=>{this._pauseUpdatesPromise=null}),"start"))}newGridItemEl(e,t){this._gridItemEl||(this._gridItemEl=Object.assign(document.createElement("pre"),{className:this._config.gridItemClassName,innerHTML:"<code></code>"}));let s=this.newIconElement(["fa-trash","fa-fw","pull-right",this._config.moduleHeadlineIconClass]);s.setAttribute("title","clear output"),s.onclick=(e=>e.target.closest(`.${this._config.gridItemClassName}`).querySelector("code").innerHTML="");let a=this.newHeadlineToolbarElement();a.append(s);let n=this._gridItemEl.cloneNode(!0);return n.dataset.area=e,n.prepend(a,this.newHeadlineElement(t)),n}queryGridItem(e){return this.moduleElement.querySelector(`.${this._config.bodyClassName} .${this._config.gridItemClassName}[data-area="${e}"]`)}};return s.isPlugin=!0,s.scope="system",s.sortArea="a",s.position=10,s.label="Demo",s.fullDataUpdate=!0,s.defaultConfig={className:"pf-system-demo-module",sortTargetAreas:["a","b","c"],headline:"Demo Module",gridItemClassName:"pf-dynamic-area",highlightClassName:"pf-animation-pulse-success",counter:0,maxCodeSections:8,gridItems:{info:{label:"handler/config",jsonConf:{collapseDepth:1,maxDepth:3}},trigger:{label:"trigger",jsonConf:{collapseDepth:1,maxDepth:5}},userData:{label:"user/char data",jsonConf:{collapseDepth:1,maxDepth:8}},mapData:{label:"map data",jsonConf:{collapseDepth:2,maxDepth:8,maxLinesFunctions:2}},sortableJs:{label:"drag&drop events",jsonConf:{collapseDepth:0,maxDepth:4,maxLinesFunctions:2}}}},s});
|
||||
//# sourceMappingURL=demo.js.map
|
||||
|
||||
BIN
public/js/v2.0.0/app/ui/module/demo.js.br
Normal file
BIN
public/js/v2.0.0/app/ui/module/demo.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/ui/module/demo.js.map
Normal file
1
public/js/v2.0.0/app/ui/module/demo.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,71 +1,2 @@
|
||||
define([ // dependencies for this module
|
||||
'module/base', // abstract `parent` module class definition [required]
|
||||
'app/render' // ... for demo purpose, we load a Render helper object
|
||||
], (BaseModule, Render) => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* EmptyModule class
|
||||
* -> skeleton for custom module plugins
|
||||
* @type {EmptyModule}
|
||||
*/
|
||||
let EmptyModule = class EmptyModule extends BaseModule {
|
||||
constructor(config = {}) {
|
||||
super(Object.assign({}, new.target.defaultConfig, config));
|
||||
}
|
||||
|
||||
/**
|
||||
* initial module render method
|
||||
* -> implementation is enforced by BaseModule
|
||||
* -> must return a single node element
|
||||
* @param mapId
|
||||
* @param systemData
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
render(mapId, systemData){
|
||||
this._systemData = systemData;
|
||||
|
||||
// ... append your custom module body
|
||||
let bodyEl = Object.assign(document.createElement('div'), {
|
||||
className: this._config.bodyClassName
|
||||
});
|
||||
|
||||
this.moduleElement.append(bodyEl);
|
||||
|
||||
return this.moduleElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* init module
|
||||
*/
|
||||
init(){
|
||||
super.init();
|
||||
}
|
||||
|
||||
beforeHide(){
|
||||
super.beforeHide();
|
||||
}
|
||||
|
||||
beforeDestroy(){
|
||||
super.beforeDestroy();
|
||||
}
|
||||
|
||||
onSortableEvent(name, e){
|
||||
super.onSortableEvent(name, e);
|
||||
}
|
||||
};
|
||||
|
||||
EmptyModule.isPlugin = true; // module is defined as 'plugin'
|
||||
EmptyModule.scope = 'system'; // module scope controls how module gets updated and what type of data is injected
|
||||
EmptyModule.sortArea = 'a'; // default sortable area
|
||||
EmptyModule.position = 15; // default sort/order position within sortable area
|
||||
EmptyModule.label = 'Empty'; // static module label (e.g. description)
|
||||
|
||||
EmptyModule.defaultConfig = {
|
||||
className: 'pf-system-empty-module', // class for module
|
||||
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
|
||||
headline: 'Empty Module',
|
||||
};
|
||||
|
||||
return EmptyModule;
|
||||
});
|
||||
define(["module/base","app/render"],(e,t)=>{"use strict";let s=class EmptyModule extends e{constructor(e={}){super(Object.assign({},new.target.defaultConfig,e))}render(e,t){this._systemData=t;let s=Object.assign(document.createElement("div"),{className:this._config.bodyClassName});return this.moduleElement.append(s),this.moduleElement}init(){super.init()}beforeHide(){super.beforeHide()}beforeDestroy(){super.beforeDestroy()}onSortableEvent(e,t){super.onSortableEvent(e,t)}};return s.isPlugin=!0,s.scope="system",s.sortArea="a",s.position=15,s.label="Empty",s.defaultConfig={className:"pf-system-empty-module",sortTargetAreas:["a","b","c"],headline:"Empty Module"},s});
|
||||
//# sourceMappingURL=empty.js.map
|
||||
|
||||
BIN
public/js/v2.0.0/app/ui/module/empty.js.br
Normal file
BIN
public/js/v2.0.0/app/ui/module/empty.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/ui/module/empty.js.map
Normal file
1
public/js/v2.0.0/app/ui/module/empty.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["app/ui/module/empty.js"],"names":["define","BaseModule","Render","EmptyModule","[object Object]","config","super","Object","assign","defaultConfig","mapId","systemData","this","_systemData","bodyEl","document","createElement","className","_config","bodyClassName","moduleElement","append","init","beforeHide","beforeDestroy","name","e","onSortableEvent","isPlugin","scope","sortArea","position","label","sortTargetAreas","headline"],"mappings":"AAAAA,QACI,cACA,cACD,CAACC,EAAYC,KACZ,aAOA,IAAIC,QAAoBA,oBAAoBF,EACxCG,YAAYC,MACRC,MAAMC,OAAOC,qBAAsBC,cAAeJ,IAWtDD,OAAOM,EAAOC,GACVC,KAAKC,YAAcF,EAGnB,IAAIG,EAASP,OAAOC,OAAOO,SAASC,cAAc,QAC9CC,UAAWL,KAAKM,QAAQC,gBAK5B,OAFAP,KAAKQ,cAAcC,OAAOP,GAEnBF,KAAKQ,cAMhBhB,OACIE,MAAMgB,OAGVlB,aACIE,MAAMiB,aAGVnB,gBACIE,MAAMkB,gBAGVpB,gBAAgBqB,EAAMC,GAClBpB,MAAMqB,gBAAgBF,EAAMC,KAgBpC,OAZAvB,EAAYyB,UAAW,EACvBzB,EAAY0B,MAAQ,SACpB1B,EAAY2B,SAAW,IACvB3B,EAAY4B,SAAW,GACvB5B,EAAY6B,MAAQ,QAEpB7B,EAAYM,eACRQ,UAAW,yBACXgB,iBAAkB,IAAK,IAAK,KAC5BC,SAAU,gBAGP/B","file":"empty.js","sourceRoot":"/js"}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,392 +0,0 @@
|
||||
/**
|
||||
* System graph module
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/util',
|
||||
'module/base',
|
||||
'morris'
|
||||
], ($, Util, BaseModule, Morris) => {
|
||||
'use strict';
|
||||
|
||||
let SystemGraphModule = class SystemGraphModule extends BaseModule {
|
||||
constructor(config = {}) {
|
||||
super(Object.assign({}, new.target.defaultConfig, config));
|
||||
}
|
||||
|
||||
newHeaderElement(){
|
||||
return ''; // no default header for this module
|
||||
}
|
||||
|
||||
newHeadlineToolbarElement(){
|
||||
let toolbarEl = super.newHeadlineToolbarElement();
|
||||
|
||||
let infoEl = document.createElement('small');
|
||||
infoEl.innerHTML = '<i class="fas fa-fw fa-question-circle pf-help ' + Util.config.popoverTriggerClass + '"></i>';
|
||||
toolbarEl.append(infoEl);
|
||||
|
||||
return toolbarEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* render module
|
||||
* @param mapId
|
||||
* @param systemData
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
render(mapId, systemData){
|
||||
this._systemData = systemData;
|
||||
|
||||
// graph data is available for k-space systems
|
||||
if(systemData.type.id === 2){
|
||||
|
||||
let rowEl = document.createElement('div');
|
||||
rowEl.classList.add(this._config.bodyClassName, 'grid');
|
||||
|
||||
for(let graphKey of Object.keys(this._config.systemGraphs)){
|
||||
let colEl = document.createElement('div');
|
||||
colEl.dataset.graph = graphKey;
|
||||
|
||||
let headEl = this.newHeadElement();
|
||||
headEl.append(
|
||||
this.newHandlerElement(),
|
||||
this.newHeadlineElement(this.getInfoForGraph(graphKey, 'headline')),
|
||||
this.newHeadlineToolbarElement()
|
||||
);
|
||||
|
||||
let graphEl = document.createElement('div');
|
||||
graphEl.classList.add(this._config.systemGraphClass);
|
||||
|
||||
colEl.append(headEl, graphEl);
|
||||
rowEl.append(colEl);
|
||||
}
|
||||
this.moduleElement.append(rowEl);
|
||||
|
||||
this.setModuleObserver();
|
||||
|
||||
// request graph data and store result promise
|
||||
// -> module is not full rendered jet
|
||||
this._dataPromise = this.getGraphsData();
|
||||
|
||||
return this.moduleElement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* init module
|
||||
*/
|
||||
init(){
|
||||
super.init();
|
||||
|
||||
if(this._dataPromise instanceof Promise){
|
||||
this._dataPromise
|
||||
.then(payload => this.addGraphData(payload.data))
|
||||
.catch(payload => {
|
||||
let reason = payload.data.status + ' ' + payload.data.error;
|
||||
this.showNotify({title: payload.data.jqXHR.status + ': System graph data', text: reason, type: 'warning'});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get data for graphs
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getGraphsData(){
|
||||
$(this.moduleElement).find('.' + this._config.systemGraphClass).showLoadingAnimation();
|
||||
return this.request('GET', 'SystemGraph', this._systemData.id, {
|
||||
systemIds: [this._systemData.systemId]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* update graph elements with data
|
||||
* @param graphData
|
||||
*/
|
||||
addGraphData(graphData){
|
||||
|
||||
// calculate time offset until system updated -------------------------------------------------------------
|
||||
let serverData = Util.getServerTime();
|
||||
let timestampNow = Math.floor(serverData.getTime() / 1000);
|
||||
let timeSinceUpdate = timestampNow - this._systemData.updated.updated;
|
||||
|
||||
let timeInHours = Math.floor(timeSinceUpdate / 3600);
|
||||
let timeInMinutes = Math.floor((timeSinceUpdate % 3600) / 60);
|
||||
let timeInMinutesPercent = parseFloat((timeInMinutes / 60).toFixed(2));
|
||||
|
||||
// graph is from right to left -> convert event line
|
||||
let eventLine = Math.max(parseFloat((24 - timeInHours - timeInMinutesPercent).toFixed(2)), 0);
|
||||
|
||||
// update graph data --------------------------------------------------------------------------------------
|
||||
for(let [systemId, graphsData] of Object.entries(graphData)){
|
||||
for(let [graphKey, graphData] of Object.entries(graphsData)){
|
||||
let graphColElement = $(this.moduleElement).find('[data-graph="' + graphKey + '"]');
|
||||
let graphElement = graphColElement.find('.' + this._config.systemGraphClass);
|
||||
graphElement.hideLoadingAnimation();
|
||||
|
||||
graphColElement.data('infoData', this.initGraph(graphElement, graphKey, graphData, eventLine));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set module observer
|
||||
*/
|
||||
setModuleObserver(){
|
||||
$(this.moduleElement).hoverIntent({
|
||||
over: function(e){
|
||||
let element = $(this);
|
||||
let tooltipData = element.parents('[data-graph]').data('infoData');
|
||||
if(tooltipData){
|
||||
SystemGraphModule.addSystemGraphTooltip(element, tooltipData.rows, {
|
||||
trigger: 'manual',
|
||||
title: tooltipData.title
|
||||
}).popover('show');
|
||||
}
|
||||
},
|
||||
out: function(e){
|
||||
$(this).destroyPopover();
|
||||
},
|
||||
selector: '.' + Util.config.popoverTriggerClass
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get info for a given graph key
|
||||
* @param graphKey
|
||||
* @param option
|
||||
* @returns {*|string}
|
||||
*/
|
||||
getInfoForGraph(graphKey, option){
|
||||
return Util.getObjVal(this._config.systemGraphs, graphKey + '.' + option) || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* init Morris Graph
|
||||
* @param graphElement
|
||||
* @param graphKey
|
||||
* @param graphData
|
||||
* @param eventLine
|
||||
* @returns {null|Object}
|
||||
*/
|
||||
initGraph(graphElement, graphKey, graphData, eventLine){
|
||||
let tooltipData = null;
|
||||
|
||||
if(
|
||||
graphData.logExists &&
|
||||
graphData.data &&
|
||||
graphData.data.length
|
||||
){
|
||||
let dataLength = graphData.data.length;
|
||||
let xKey = 'x';
|
||||
let yKeys = this.getInfoForGraph(graphKey, 'ykeys');
|
||||
|
||||
// calc average (goal) ------------------------------------------------------------------------------------
|
||||
// ... init empty sum object ...
|
||||
let sum = yKeys.reduce((result, key) => {
|
||||
result[key] = 0;
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
// ... sum all values ...
|
||||
sum = graphData.data.reduce((sum, obj) => {
|
||||
for(let [key, value] of Object.entries(obj)){
|
||||
if(sum.hasOwnProperty(key)){
|
||||
sum[key] += value;
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}, sum);
|
||||
|
||||
// ... calc average
|
||||
let goals = Object.values(sum).map(value => Math.floor(value / dataLength));
|
||||
|
||||
// init Morris chart --------------------------------------------------------------------------------------
|
||||
let graphConfig = {
|
||||
element: graphElement,
|
||||
data: graphData.data,
|
||||
xkey: xKey,
|
||||
ykeys: yKeys,
|
||||
labels: this.getInfoForGraph(graphKey, 'labels'),
|
||||
xLabelAngle: 0,
|
||||
parseTime: false,
|
||||
ymin: 0,
|
||||
yLabelFormat: value => Math.round(value),
|
||||
padding: 8,
|
||||
hideHover: true,
|
||||
pointSize: 2.5,
|
||||
lineColors: this.getInfoForGraph(graphKey, 'lineColors'),
|
||||
pointFillColors: this.getInfoForGraph(graphKey, 'pointFillColors'),
|
||||
pointStrokeColors: ['#141519'],
|
||||
lineWidth: 1.5,
|
||||
grid: true,
|
||||
gridTextColor: '#63676a',
|
||||
gridTextSize: 10,
|
||||
gridTextFamily: 'Arial, "Oxygen Bold"',
|
||||
gridStrokeWidth: 0.3,
|
||||
behaveLikeLine: true,
|
||||
goals: goals,
|
||||
goalStrokeWidth: 1,
|
||||
goalLineColors: ['#c2760c'],
|
||||
smooth: true,
|
||||
fillOpacity: 0.5,
|
||||
//belowArea: true,
|
||||
areaColors: ['#c2760c', '#c2760c'],
|
||||
belowArea: true,
|
||||
//resize: true,
|
||||
//redraw: true,
|
||||
dataLabels: false,
|
||||
eventStrokeWidth: 1,
|
||||
eventLineColors: ['#63676a'],
|
||||
nbYkeys2: this.getInfoForGraph(graphKey, 'nbYkeys2')
|
||||
};
|
||||
|
||||
if(eventLine > 0){
|
||||
graphConfig.events = [eventLine];
|
||||
}
|
||||
|
||||
this['_aChart_' + graphKey] = Morris.Line(graphConfig);
|
||||
|
||||
// data for info "popover" --------------------------------------------------------------------------------
|
||||
tooltipData = {};
|
||||
let tooltipRows = [];
|
||||
let infoLabels = this.getInfoForGraph(graphKey, 'infoLabels');
|
||||
goals.forEach((goal, i) => {
|
||||
tooltipRows.push({
|
||||
label: infoLabels[i],
|
||||
value: goal,
|
||||
class: 'txt-color txt-color-orangeDark'
|
||||
});
|
||||
});
|
||||
tooltipData.rows = tooltipRows;
|
||||
|
||||
let serverDate = Util.getServerTime();
|
||||
let updatedDate = Util.convertTimestampToServerTime(graphData.updated);
|
||||
let updatedDiff = Util.getTimeDiffParts(updatedDate, serverDate);
|
||||
|
||||
tooltipData.title = '<i class="fas fa-download"></i><span class="pull-right ">' + Util.formatTimeParts(updatedDiff) + '</span>';
|
||||
}else{
|
||||
// make container a bit smaller -> no graph shown
|
||||
graphElement.css('height', '22px').text('No data');
|
||||
}
|
||||
|
||||
return tooltipData;
|
||||
}
|
||||
|
||||
beforeDestroy(){
|
||||
super.beforeDestroy();
|
||||
|
||||
for(let graphKey of Object.keys(this._config.systemGraphs)){
|
||||
if(typeof this['_aChart_' + graphKey] === 'object'){
|
||||
this['_aChart_' + graphKey].destroy();
|
||||
delete this['_aChart_' + graphKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* detect changed drop area, -> should trigger graph redraw
|
||||
* @param name
|
||||
* @param e
|
||||
*/
|
||||
onSortableEvent(name, e){
|
||||
super.onSortableEvent(name, e);
|
||||
if(e.type === 'add' && e.from !== e.to){
|
||||
for(let graphKey of Object.keys(this._config.systemGraphs)){
|
||||
if(typeof this['_aChart_' + graphKey] === 'object'){
|
||||
this['_aChart_' + graphKey].resizeHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add info tooltip for graphs
|
||||
* @param element
|
||||
* @param tooltipData
|
||||
* @param options
|
||||
* @returns {jQuery}
|
||||
*/
|
||||
static addSystemGraphTooltip(element, tooltipData, options = {}){
|
||||
let table = '<table>';
|
||||
for(let data of tooltipData){
|
||||
let css = data.class || '';
|
||||
|
||||
table += '<tr>';
|
||||
table += '<td>';
|
||||
table += data.label;
|
||||
table += '</td>';
|
||||
table += '<td class="text-right ' + css + '">';
|
||||
table += data.value;
|
||||
table += '</td>';
|
||||
table += '</tr>';
|
||||
}
|
||||
table += '</table>';
|
||||
|
||||
let defaultOptions = {
|
||||
placement: 'top',
|
||||
html: true,
|
||||
trigger: 'hover',
|
||||
container: 'body',
|
||||
title: 'Info',
|
||||
content: table,
|
||||
delay: {
|
||||
show: 0,
|
||||
hide: 0
|
||||
},
|
||||
};
|
||||
|
||||
options = Object.assign({}, defaultOptions, options);
|
||||
|
||||
return $(element).popover(options);
|
||||
}
|
||||
};
|
||||
|
||||
SystemGraphModule.isPlugin = false; // module is defined as 'plugin'
|
||||
SystemGraphModule.scope = 'system'; // module scope controls how module gets updated and what type of data is injected
|
||||
SystemGraphModule.sortArea = 'a'; // default sortable area
|
||||
SystemGraphModule.position = 3; // default sort/order position within sortable area
|
||||
SystemGraphModule.label = 'Graphs'; // static module label (e.g. description)
|
||||
|
||||
SystemGraphModule.defaultConfig = {
|
||||
className: 'pf-system-graph-module', // class for module
|
||||
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
|
||||
|
||||
systemGraphClass: 'pf-system-graph', // class for each graph
|
||||
systemGraphs: {
|
||||
jumps: {
|
||||
headline: 'Jumps',
|
||||
postUnits: 'jumps',
|
||||
ykeys: ['y'],
|
||||
nbYkeys2: 0,
|
||||
labels: ['Jumps'],
|
||||
lineColors: ['#375959'],
|
||||
pointFillColors: ['#477372'],
|
||||
infoLabels: ['Avg. jumps']
|
||||
},
|
||||
shipKills: {
|
||||
headline: 'Ship/POD Kills',
|
||||
postUnits: 'kills',
|
||||
ykeys: ['y', 'z'],
|
||||
nbYkeys2: 1,
|
||||
labels: ['Ships', 'PODs'],
|
||||
lineColors: ['#375959', '#477372'],
|
||||
pointFillColors: ['#477372', '#568a89'],
|
||||
infoLabels: ['Avg. ship kills', 'Avg. pod kills']
|
||||
},
|
||||
factionKills: {
|
||||
headline: 'NPC Kills',
|
||||
postUnits: 'kills',
|
||||
ykeys: ['y'],
|
||||
nbYkeys2: 0,
|
||||
labels: ['NPCs'],
|
||||
lineColors: ['#375959'],
|
||||
pointFillColors: ['#477372'],
|
||||
infoLabels: ['Avg. NPC kills']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return SystemGraphModule;
|
||||
});
|
||||
@@ -1,693 +0,0 @@
|
||||
/**
|
||||
* System info module
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'app/map/util',
|
||||
'module/base'
|
||||
], ($, Init, Util, MapUtil, BaseModule) => {
|
||||
'use strict';
|
||||
|
||||
let SystemInfoModule = class SystemInfoModule extends BaseModule {
|
||||
constructor(config = {}) {
|
||||
super(Object.assign({}, new.target.defaultConfig, config));
|
||||
}
|
||||
|
||||
/**
|
||||
* custom header for this module
|
||||
* @returns {*}
|
||||
*/
|
||||
newHeaderElement(){
|
||||
let headEl = this.newHeadElement();
|
||||
|
||||
let headAliasEl = this.newHeadlineElement(this._systemData.alias || this._systemData.name);
|
||||
headAliasEl.setAttribute('title', 'alias');
|
||||
headAliasEl.classList.add('pull-right');
|
||||
if(this._systemData.security === 'A'){
|
||||
headAliasEl.classList.add(this._config.fontTriglivianClass);
|
||||
}
|
||||
|
||||
let iconEl = this.newIconElement(['fa-fw', 'fa-angle-double-right']);
|
||||
|
||||
let headSysTypeEl = this.newHeadlineElement();
|
||||
let sysTypeEl = document.createElement('span');
|
||||
sysTypeEl.setAttribute('title', 'type');
|
||||
sysTypeEl.classList.add(this._config.typeLinkClass);
|
||||
sysTypeEl.textContent = MapUtil.getSystemTypeInfo(this._systemData.type.id, 'name');
|
||||
headSysTypeEl.append(sysTypeEl, iconEl);
|
||||
|
||||
let headSysRegionEl = this.newHeadlineElement();
|
||||
let sysRegionEl = document.createElement('span');
|
||||
sysRegionEl.setAttribute('title', 'region');
|
||||
sysRegionEl.classList.add(this._config.regionLinkClass);
|
||||
if(this._systemData.security === 'A'){
|
||||
sysRegionEl.classList.add(this._config.fontTriglivianClass);
|
||||
}
|
||||
sysRegionEl.textContent = this._systemData.region.name;
|
||||
headSysRegionEl.append(sysRegionEl, iconEl.cloneNode());
|
||||
|
||||
let headSysConstellationEl = this.newHeadlineElement();
|
||||
let sysConstellationEl = document.createElement('span');
|
||||
sysConstellationEl.classList.add(this._config.constellationLinkClass, this._config.linkClass, Util.config.popoverTriggerClass);
|
||||
if(this._systemData.security === 'A'){
|
||||
sysConstellationEl.classList.add(this._config.fontTriglivianClass);
|
||||
}
|
||||
sysConstellationEl.textContent = this._systemData.constellation.name;
|
||||
sysConstellationEl.setAttribute('popup-ajax', Init.path.getConstellationData + '/' + this._systemData.constellation.id);
|
||||
headSysConstellationEl.append(sysConstellationEl, iconEl.cloneNode());
|
||||
|
||||
let headSysNameEl = this.newHeadlineElement();
|
||||
let sysNameEl = document.createElement('span');
|
||||
sysNameEl.setAttribute('title', 'system');
|
||||
if(this._systemData.security === 'A'){
|
||||
sysNameEl.classList.add(this._config.fontTriglivianClass);
|
||||
}
|
||||
sysNameEl.textContent = this._systemData.name;
|
||||
let iconCopyEl = this.newIconElement(['fa-fw', 'fa-copy', this._config.moduleHeadlineIconClass, this._config.textActionIconCopyClass]);
|
||||
iconCopyEl.setAttribute('title', 'copy url');
|
||||
iconCopyEl.dataset.copy = MapUtil.getMapDeeplinkUrl(this._systemData.mapId, this._systemData.id);
|
||||
headSysNameEl.append(sysNameEl, iconCopyEl);
|
||||
if(this._systemData.locked){
|
||||
let iconLockedEl = this.newIconElement(['fa-fw', 'fa-lock', this._config.moduleHeadlineIconClass]);
|
||||
iconLockedEl.setAttribute('title', 'locked');
|
||||
headSysNameEl.append(iconLockedEl);
|
||||
}
|
||||
for(let linkData of this.getThirdPartySystemLinks(['dotlan', 'eveeye', 'anoik'])){
|
||||
if(linkData.showInModuleHead){
|
||||
let headSysLinkEl = document.createElement('a');
|
||||
headSysLinkEl.classList.add('pf-bg-icon-inline');
|
||||
headSysLinkEl.style.setProperty('--bg-image', `url("${Util.imgRoot()}icons/logo_${linkData.page}.png")`);
|
||||
headSysLinkEl.setAttribute('title', linkData.title);
|
||||
headSysLinkEl.setAttribute('href', linkData.url);
|
||||
headSysLinkEl.setAttribute('target', '_blank');
|
||||
headSysLinkEl.setAttribute('rel', 'noopener');
|
||||
headSysNameEl.append(headSysLinkEl);
|
||||
}
|
||||
}
|
||||
|
||||
headEl.append(
|
||||
this.newHandlerElement(),
|
||||
headAliasEl,
|
||||
headSysTypeEl,
|
||||
headSysRegionEl,
|
||||
headSysConstellationEl,
|
||||
headSysNameEl
|
||||
);
|
||||
return headEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* update module
|
||||
* @param systemData
|
||||
* @returns {Promise}
|
||||
*/
|
||||
update(systemData){
|
||||
return super.update(systemData).then(systemData => new Promise(resolve => {
|
||||
if(
|
||||
this._systemData.id === systemData.id &&
|
||||
this._updated !== systemData.updated.updated
|
||||
){
|
||||
let setUpdated = true;
|
||||
|
||||
// created/updated tooltip ------------------------------------------------------------------------
|
||||
let nameRowElement = $(this.moduleElement).find('.' + this._config.systemInfoNameClass);
|
||||
let tooltipData = {
|
||||
created: systemData.created,
|
||||
updated: systemData.updated
|
||||
};
|
||||
nameRowElement.addCharacterInfoTooltip(tooltipData);
|
||||
|
||||
// update system status ---------------------------------------------------------------------------
|
||||
let systemStatusLabelElement = $(this.moduleElement).find('.' + this._config.systemInfoStatusLabelClass);
|
||||
let systemStatusId = parseInt(systemStatusLabelElement.attr('data-status'));
|
||||
|
||||
if(systemStatusId !== systemData.status.id){
|
||||
// status changed
|
||||
let currentStatusClass = Util.getStatusInfoForSystem(systemStatusId, 'class');
|
||||
let newStatusClass = Util.getStatusInfoForSystem(systemData.status.id, 'class');
|
||||
let newStatusLabel = Util.getStatusInfoForSystem(systemData.status.id, 'label');
|
||||
systemStatusLabelElement.removeClass(currentStatusClass).addClass(newStatusClass).text(newStatusLabel);
|
||||
|
||||
// set new status attribute
|
||||
systemStatusLabelElement.attr('data-status', systemData.status.id);
|
||||
}
|
||||
|
||||
// update description textarea --------------------------------------------------------------------
|
||||
let descriptionTextareaElement = $(this.moduleElement).find('.' + this._config.descriptionTextareaElementClass);
|
||||
if(descriptionTextareaElement.length){
|
||||
let description = descriptionTextareaElement.html();
|
||||
if(description !== systemData.description){
|
||||
// description has changed
|
||||
if(typeof descriptionTextareaElement.data().summernote === 'object'){
|
||||
// "Summernote" editor is currently open
|
||||
setUpdated = false;
|
||||
}else{
|
||||
// not open
|
||||
let newDescription = systemData.description;
|
||||
if(!Util.isValidHtml(newDescription)){
|
||||
// try to convert raw text into valid html
|
||||
newDescription = newDescription.replace(/(\r\n|\n|\r)/g, '<br>');
|
||||
newDescription = '<p>' + newDescription + '</p>';
|
||||
}
|
||||
|
||||
descriptionTextareaElement.html(newDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(setUpdated){
|
||||
this._updated = systemData.updated.updated;
|
||||
}
|
||||
}
|
||||
|
||||
$(this.moduleElement).hideLoadingAnimation();
|
||||
|
||||
resolve({
|
||||
action: 'update',
|
||||
data: {
|
||||
module: this
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* render module
|
||||
* @param mapId
|
||||
* @param systemData
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
render(mapId, systemData){
|
||||
this._systemData = systemData;
|
||||
|
||||
let rowEl = document.createElement('div');
|
||||
rowEl.classList.add(this._config.bodyClassName, 'grid');
|
||||
|
||||
let colInfoEl, colSovEl, colDescEl;
|
||||
|
||||
colInfoEl = document.createElement('div');
|
||||
colInfoEl.classList.add(this._config.systemInfoSectionClass);
|
||||
rowEl.append(colInfoEl);
|
||||
|
||||
colSovEl = document.createElement('div');
|
||||
colSovEl.classList.add(this._config.systemSovSectionClass, 'pf-dynamic-area');
|
||||
rowEl.append(colSovEl);
|
||||
|
||||
colDescEl = document.createElement('div');
|
||||
colDescEl.classList.add(this._config.descriptionSectionClass);
|
||||
rowEl.append(colDescEl);
|
||||
|
||||
this.moduleElement.append(rowEl);
|
||||
|
||||
require(['text!templates/modules/system_info.html', 'mustache', 'summernote.loader'], (template, Mustache, Summernote) => {
|
||||
SystemInfoModule.Mustache = Mustache;
|
||||
SystemInfoModule.Summernote = Summernote;
|
||||
|
||||
template = new DOMParser().parseFromString(template, 'text/html');
|
||||
SystemInfoModule.tplInfoSection = template.getElementById('tplInfoSection').innerHTML;
|
||||
SystemInfoModule.tplSovSection = template.getElementById('tplSovSection').innerHTML;
|
||||
SystemInfoModule.tplDescSection = template.getElementById('tplDescSection').innerHTML;
|
||||
|
||||
// optional parse() for cache parsed templates
|
||||
SystemInfoModule.Mustache.parse(SystemInfoModule.tplInfoSection);
|
||||
SystemInfoModule.Mustache.parse(SystemInfoModule.tplSovSection);
|
||||
SystemInfoModule.Mustache.parse(SystemInfoModule.tplDescSection);
|
||||
|
||||
this.renderInfoSection(colInfoEl, SystemInfoModule.tplInfoSection);
|
||||
this.renderSovSection(colSovEl, SystemInfoModule.tplSovSection);
|
||||
this.renderDescSection(colDescEl, SystemInfoModule.tplDescSection);
|
||||
|
||||
this.setModuleObserver();
|
||||
});
|
||||
|
||||
return this.moduleElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* render 'sovereignty' section
|
||||
* @param parentEl
|
||||
* @param template
|
||||
*/
|
||||
renderInfoSection(parentEl, template){
|
||||
if(SystemInfoModule.Mustache && parentEl && template){
|
||||
let data = {
|
||||
config: this._config,
|
||||
system: this._systemData,
|
||||
systemStatusClass: Util.getStatusInfoForSystem(this._systemData.status.id, 'class'),
|
||||
systemStatusLabel: Util.getStatusInfoForSystem(this._systemData.status.id, 'label'),
|
||||
systemNameClass: this._systemData.security === 'A' ? this._config.fontTriglivianClass : '',
|
||||
systemSecurityClass: Util.getSecurityClassForSystem(this._systemData.security),
|
||||
trueSec: this._systemData.trueSec.toFixed(1),
|
||||
trueSecClass: Util.getTrueSecClassForSystem(this._systemData.trueSec),
|
||||
systemEffectName: MapUtil.getEffectInfoForSystem(this._systemData.effect, 'name'),
|
||||
systemEffectClass: MapUtil.getEffectInfoForSystem(this._systemData.effect, 'class'),
|
||||
systemPlanetCount: this._systemData.planets ? this._systemData.planets.length : 0,
|
||||
systemStaticData: (Util.getObjVal(this._systemData, 'statics') || []).reduce((acc, wormholeName) => {
|
||||
acc.push(Object.assign({}, Init.wormholes[wormholeName]));
|
||||
return acc;
|
||||
}, []),
|
||||
systemShatteredClass: Util.getSecurityClassForSystem('SH'),
|
||||
popoverTriggerClass: Util.config.popoverTriggerClass
|
||||
};
|
||||
parentEl.innerHTML = SystemInfoModule.Mustache.render(template, data);
|
||||
|
||||
this.setInfoSectionObserver(parentEl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* render 'sovereignty' section
|
||||
* @param parentEl
|
||||
* @param template
|
||||
*/
|
||||
renderSovSection(parentEl, template){
|
||||
if(SystemInfoModule.Mustache && parentEl && template){
|
||||
// system "sovereignty" data
|
||||
// "primary" data is either "alliance" -> 0.0 space
|
||||
// or "faction" -> Empire Regions (LS, HS)
|
||||
let sovereigntyDefault = {
|
||||
row1Label: 'Sov.',
|
||||
row1Val: '???',
|
||||
row1Img: undefined,
|
||||
row1ImgTitle: undefined,
|
||||
row2Label: undefined,
|
||||
row2Val: undefined,
|
||||
row3Label: undefined,
|
||||
row3Val: undefined
|
||||
};
|
||||
|
||||
let sovereigntyPrimary;
|
||||
let sovereigntySecondary;
|
||||
|
||||
if(this._systemData.sovereignty){
|
||||
let sovDataFact = Util.getObjVal(this._systemData.sovereignty, 'faction');
|
||||
let sovDataAlly = Util.getObjVal(this._systemData.sovereignty, 'alliance');
|
||||
let sovDataCorp = Util.getObjVal(this._systemData.sovereignty, 'corporation');
|
||||
|
||||
if(sovDataFact){
|
||||
sovereigntyPrimary = {
|
||||
row1Val: 'Faction',
|
||||
row1Img: Util.eveImageUrl('factions', sovDataFact.id, 64),
|
||||
row1ImgTitle: sovDataFact.name,
|
||||
row2Val: sovDataFact.name
|
||||
};
|
||||
}else{
|
||||
if(sovDataAlly){
|
||||
sovereigntyPrimary = {
|
||||
row1Val: 'Alliance',
|
||||
row1Img: Util.eveImageUrl('alliances', sovDataAlly.id, 64),
|
||||
row1ImgTitle: sovDataAlly.name,
|
||||
row2Val: '<' + sovDataAlly.ticker + '>',
|
||||
row3Label: 'Ally',
|
||||
row3Val: sovDataAlly.name
|
||||
};
|
||||
}
|
||||
if(sovDataCorp){
|
||||
sovereigntySecondary = {
|
||||
row1Label: 'Corp',
|
||||
row1Val: sovDataCorp.name,
|
||||
row1Img: Util.eveImageUrl('corporations', sovDataCorp.id, 64)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data = {
|
||||
config: this._config,
|
||||
sovereigntyPrimary: sovereigntyPrimary ? Object.assign({}, sovereigntyDefault, sovereigntyPrimary) : undefined,
|
||||
sovereigntySecondary: sovereigntySecondary ? Object.assign({}, sovereigntyDefault, sovereigntySecondary) : undefined,
|
||||
};
|
||||
|
||||
// show only if sov data exists
|
||||
if(data.sovereigntyPrimary){
|
||||
parentEl.innerHTML = SystemInfoModule.Mustache.render(template, data);
|
||||
|
||||
this.setSovSectionObserver(parentEl);
|
||||
}else{
|
||||
parentEl.parentNode.classList.add(this._config.bodyClassName + '-small');
|
||||
parentEl.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* render 'description' section
|
||||
* @param parentEl
|
||||
* @param template
|
||||
*/
|
||||
renderDescSection(parentEl, template){
|
||||
if(SystemInfoModule.Mustache && parentEl && template){
|
||||
let data = {
|
||||
config: this._config,
|
||||
summernoteClass: Util.config.summernoteClass
|
||||
};
|
||||
parentEl.innerHTML = SystemInfoModule.Mustache.render(template, data);
|
||||
|
||||
this.setDescSectionObserver(parentEl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* init module
|
||||
*/
|
||||
init(){
|
||||
super.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* set module observer
|
||||
*/
|
||||
setModuleObserver(){
|
||||
// init copy system deeplink URL
|
||||
$(this.moduleElement).find('.' + this._config.textActionIconCopyClass).on('click', function(){
|
||||
let mapUrl = $(this).attr('data-copy');
|
||||
Util.copyToClipboard(mapUrl).then(payload => {
|
||||
if(payload.data){
|
||||
Util.showNotify({title: 'Copied to clipboard', text: mapUrl, type: 'success'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// init constellation popover
|
||||
$(this.moduleElement).find('[popup-ajax]').popover({
|
||||
html: true,
|
||||
trigger: 'hover',
|
||||
placement: 'top',
|
||||
delay: 200,
|
||||
container: 'body',
|
||||
content: function(){
|
||||
//return details_in_popup(this);
|
||||
let popoverElement = $(this);
|
||||
let popover = popoverElement.data('bs.popover');
|
||||
|
||||
$.ajax({
|
||||
url: popoverElement.attr('popup-ajax'),
|
||||
success: function(data){
|
||||
popover.options.content = Util.getSystemsInfoTable(data.systemsData);
|
||||
// reopen popover (new content size)
|
||||
popover.show();
|
||||
}
|
||||
});
|
||||
return 'Loading...';
|
||||
}
|
||||
});
|
||||
|
||||
// init tooltips
|
||||
$(this.moduleElement).initTooltips({
|
||||
placement: 'top'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* observer 'info' section
|
||||
* @param parentEl
|
||||
*/
|
||||
setInfoSectionObserver(parentEl){
|
||||
// init system effect popover
|
||||
$(parentEl).find('.' + this._config.systemInfoEffectClass).addSystemEffectTooltip(
|
||||
this._systemData.security,
|
||||
this._systemData.effect, {
|
||||
placement: 'left'
|
||||
});
|
||||
|
||||
// init planets popover
|
||||
$(parentEl).find('.' + this._config.systemInfoPlanetsClass).addSystemPlanetsTooltip(
|
||||
this._systemData.planets, {
|
||||
placement: 'left'
|
||||
});
|
||||
|
||||
// init static wormhole popover
|
||||
MapUtil.initWormholeInfoTooltip($(parentEl), '[data-name]', {
|
||||
placement: 'left'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* observer 'sovereignty' section
|
||||
* @param parentEl
|
||||
*/
|
||||
setSovSectionObserver(parentEl){
|
||||
// "lock" area until first update
|
||||
$(parentEl).showLoadingAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* observer 'description' section
|
||||
* @param parentEl
|
||||
*/
|
||||
setDescSectionObserver(parentEl){
|
||||
let descriptionArea = $(parentEl).find('.' + this._config.descriptionAreaClass);
|
||||
let descriptionButton = $(parentEl).find('.' + this._config.addDescriptionButtonClass);
|
||||
let descriptionTextareaElement = $(parentEl).find('.' + this._config.descriptionTextareaElementClass);
|
||||
let maxDescriptionLength = this._config.maxDescriptionLength;
|
||||
let systemId = this._systemData.id;
|
||||
let saveCallback = this.update.bind(this);
|
||||
|
||||
// "lock" area until first update
|
||||
descriptionArea.showLoadingAnimation();
|
||||
|
||||
// WYSIWYG init on button click ---------------------------------------------------------------------------
|
||||
descriptionButton.on('click', function(e){
|
||||
e.stopPropagation();
|
||||
let descriptionButton = $(this);
|
||||
// hide edit button
|
||||
descriptionButton.hide();
|
||||
|
||||
// content has changed
|
||||
let descriptionChanged = false;
|
||||
|
||||
SystemInfoModule.Summernote.initSummernote(descriptionTextareaElement, {
|
||||
height: 75, // set editor height
|
||||
minHeight: 75, // set minimum height of editor
|
||||
maxHeight: 500, // set maximum height of editor
|
||||
focus: true,
|
||||
placeholder: false,
|
||||
maxTextLength: maxDescriptionLength,
|
||||
disableDragAndDrop: true,
|
||||
shortcuts: false,
|
||||
toolbar: [
|
||||
['style', ['style']],
|
||||
['font', ['underline', 'strikethrough', 'clear']],
|
||||
['color', ['color']],
|
||||
['para', ['ul', 'ol', 'paragraph']],
|
||||
['table', ['table']],
|
||||
['insert', ['link', 'hr']],
|
||||
//['view', ['codeview', 'help']],
|
||||
['misc', ['undo', 'redo']],
|
||||
['lengthField'],
|
||||
['customBtn', ['discardBtn', 'saveBtn']]
|
||||
],
|
||||
buttons: {
|
||||
saveBtn: context => {
|
||||
let ui = $.summernote.ui;
|
||||
let button = ui.button({
|
||||
contents: '<i class="fas fa-fw fa-check"/>',
|
||||
container: 'body',
|
||||
className: ['btn-success', 'btn-save'],
|
||||
click: e => {
|
||||
context.layoutInfo.editable.removeClass('has-error');
|
||||
|
||||
// save changes
|
||||
if(descriptionChanged){
|
||||
let validDescription = true;
|
||||
let description = '';
|
||||
|
||||
if(context.$note.summernote('isEmpty')){
|
||||
// ... isEmpty -> clear empty default tags as well
|
||||
context.$note.summernote('code', '');
|
||||
}else{
|
||||
description = context.$note.summernote('code');
|
||||
if(!Util.isValidHtml(description)){
|
||||
// ... not valid HTML
|
||||
validDescription = false;
|
||||
context.layoutInfo.editable.addClass('has-error');
|
||||
Util.showNotify({title: 'Validation failed', text: 'HTML not valid', type: 'error'});
|
||||
}
|
||||
}
|
||||
|
||||
if(validDescription){
|
||||
// ... valid -> save()
|
||||
descriptionArea.showLoadingAnimation();
|
||||
|
||||
Util.request('PATCH', 'System', systemId, {
|
||||
description: description
|
||||
}, {
|
||||
descriptionArea: descriptionArea
|
||||
}, context => {
|
||||
// always do
|
||||
context.descriptionArea.hideLoadingAnimation();
|
||||
}).then(
|
||||
payload => {
|
||||
context.$note.summernote('destroy');
|
||||
saveCallback(payload.data);
|
||||
},
|
||||
Util.handleAjaxErrorResponse
|
||||
);
|
||||
}
|
||||
}else{
|
||||
// ... no changes -> no save()
|
||||
context.$note.summernote('destroy');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return button.render();
|
||||
}
|
||||
},
|
||||
callbacks: {
|
||||
onInit: function(context){
|
||||
// make editable field a bit larger
|
||||
context.editable.css('height', '150px');
|
||||
|
||||
// set default background color
|
||||
// -> could not figure out how to set by API as default color
|
||||
let defaultBgColor = '#e2ce48';
|
||||
context.toolbar.find('.note-current-color-button').attr('data-backcolor', defaultBgColor)
|
||||
.find('.note-recent-color').css('background-color', defaultBgColor);
|
||||
},
|
||||
onChange: function(contents){
|
||||
descriptionChanged = true;
|
||||
},
|
||||
onPaste: function(e){
|
||||
let bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');
|
||||
e.preventDefault();
|
||||
|
||||
// Firefox fix
|
||||
setTimeout(() => {
|
||||
document.execCommand('insertText', false, bufferText);
|
||||
}, 10);
|
||||
},
|
||||
onDestroy: function(context){
|
||||
descriptionButton.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get 3rd party system link configuration
|
||||
* @param pages
|
||||
* @returns {[]}
|
||||
*/
|
||||
getThirdPartySystemLinks(pages){
|
||||
let links = [];
|
||||
let isWormhole = MapUtil.getSystemTypeInfo(Util.getObjVal(this._systemData, 'type.id'), 'name') === 'w-space';
|
||||
let systemName = Util.getObjVal(this._systemData, 'name') || '';
|
||||
let regionName = Util.getObjVal(this._systemData, 'region.name') || '';
|
||||
|
||||
for(let i = 0; i < pages.length; i++){
|
||||
let link = null;
|
||||
let showInModuleHead = true;
|
||||
let domain = Util.getObjVal(Init, 'url.' + pages[i]);
|
||||
if(domain){
|
||||
// linkOut url
|
||||
let url = false;
|
||||
switch(pages[i]){
|
||||
case 'dotlan':
|
||||
let systemNameTemp = systemName.replace(/ /g, '_');
|
||||
let regionNameTemp = regionName.replace(/ /g, '_');
|
||||
if(isWormhole){
|
||||
url = domain + '/system/' + systemNameTemp;
|
||||
}else{
|
||||
url = domain + '/map/' + regionNameTemp + '/' + systemNameTemp;
|
||||
}
|
||||
break;
|
||||
case 'eveeye':
|
||||
if(!isWormhole){
|
||||
url = domain + '/?m=' + encodeURIComponent(regionName) + '&s=' + encodeURIComponent(systemName);
|
||||
url += '&t=eswkc&o=thera,con_svc,node_sov,sub_sec,sector_fac,tag_mk';
|
||||
}
|
||||
break;
|
||||
case 'anoik':
|
||||
if(isWormhole){
|
||||
url = domain + '/systems/' + systemName;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(url){
|
||||
let urlObj = new URL(url);
|
||||
link = {
|
||||
title: urlObj.hostname,
|
||||
url: url
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if(link){
|
||||
links.push(Object.assign({}, link, {
|
||||
page: pages[i],
|
||||
showInModuleHead: showInModuleHead
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
beforeDestroy(){
|
||||
super.beforeDestroy();
|
||||
|
||||
let descriptionTextareaEl = this.moduleElement.querySelector(`.${this._config.descriptionTextareaElementClass}`);
|
||||
if(descriptionTextareaEl && $(descriptionTextareaEl).summernote){
|
||||
$(descriptionTextareaEl).summernote('destroy');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SystemInfoModule.isPlugin = false; // module is defined as 'plugin'
|
||||
SystemInfoModule.scope = 'system'; // module scope controls how module gets updated and what type of data is injected
|
||||
SystemInfoModule.sortArea = 'a'; // default sortable area
|
||||
SystemInfoModule.position = 2; // default sort/order position within sortable area
|
||||
SystemInfoModule.label = 'Information'; // static module label (e.g. description)
|
||||
SystemInfoModule.fullDataUpdate = true; // static module requires additional data (e.g. system description,...)
|
||||
|
||||
SystemInfoModule.defaultConfig = {
|
||||
className: 'pf-system-info-module', // class for module
|
||||
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
|
||||
|
||||
// headline toolbar
|
||||
textActionIconCopyClass: 'pf-module-icon-button-copy', // class for text action "copy"
|
||||
|
||||
// breadcrumb
|
||||
constellationLinkClass: 'pf-system-info-constellation', // class for "constellation" name
|
||||
regionLinkClass: 'pf-system-info-region', // class for "region" name
|
||||
typeLinkClass: 'pf-system-info-type', // class for "type" name
|
||||
|
||||
// info area
|
||||
systemInfoSectionClass: 'pf-system-info-section', // class for system info section
|
||||
systemInfoTableClass: 'pf-module-table', // class for system info table
|
||||
systemInfoNameClass: 'pf-system-info-name', // class for "name" information element
|
||||
systemInfoEffectClass: 'pf-system-info-effect', // class for "effect" information element
|
||||
systemInfoPlanetsClass: 'pf-system-info-planets', // class for "planets" information element
|
||||
systemInfoStatusLabelClass: 'pf-system-info-status-label', // class for "status" information element
|
||||
|
||||
// sovereignty area
|
||||
systemSovSectionClass: 'pf-system-sov-section', // class for system sov. section
|
||||
systemSovTableClass: 'pf-module-table', // class for system sov. table
|
||||
systemSovFwContestedRowClass: 'pf-system-sov-fw-contested-row', // class for "contested" sov. table row
|
||||
systemSovFwOccupationRowClass: 'pf-system-sov-fw-occupation-row', // class for "-occupation" sov. table row
|
||||
systemSovFwContestedClass: 'pf-system-sov-fw-contested',
|
||||
systemSovFwPercentageClass: 'pf-system-sov-fw-percentage',
|
||||
systemSovFwOccupationClass: 'pf-system-sov-fw-occupation',
|
||||
systemSovFwOccupationImageClass: 'pf-system-sov-fw-occupation-image',
|
||||
systemSovFwStatusIconClass: 'pf-system-sov-fw-status-icon',
|
||||
|
||||
// description area
|
||||
descriptionSectionClass: 'pf-system-description-section', // class for system description section
|
||||
descriptionAreaClass: 'pf-system-info-description-area', // class for description area
|
||||
addDescriptionButtonClass: 'pf-system-info-description-button', // class for "add description" button
|
||||
descriptionTextareaElementClass: 'pf-system-info-description', // class for description textarea element (Summernote)
|
||||
maxDescriptionLength: 9000, // max character length for system description
|
||||
|
||||
// fonts
|
||||
fontTriglivianClass: 'pf-triglivian', // class for "Triglivian" names (e.g. Abyssal systems)
|
||||
linkClass: 'pf-link'
|
||||
};
|
||||
|
||||
return SystemInfoModule;
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,825 +0,0 @@
|
||||
/**
|
||||
* System killboard module
|
||||
*/
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'app/init',
|
||||
'app/util',
|
||||
'module/base',
|
||||
'app/map/util'
|
||||
], ($, Init, Util, BaseModule, MapUtil) => {
|
||||
'use strict';
|
||||
|
||||
let SystemKillboardModule = class SystemKillboardModule extends BaseModule {
|
||||
constructor(config = {}) {
|
||||
super(Object.assign({}, new.target.defaultConfig, config));
|
||||
}
|
||||
|
||||
/**
|
||||
* module header
|
||||
* @param text
|
||||
* @returns {HTMLDivElement}
|
||||
*/
|
||||
newHeaderElement(text){
|
||||
let headEl = super.newHeaderElement(text);
|
||||
|
||||
// WebSocket status
|
||||
let wsStatusEl = Object.assign(document.createElement('h5'), {
|
||||
className: this._config.wsStatusWrapperClass
|
||||
});
|
||||
this._iconWsEl = this.newIconElement([
|
||||
'fa-circle', 'fa-fw', 'txt-color', this._config.wsStatusClass
|
||||
]);
|
||||
wsStatusEl.append(this._iconWsEl);
|
||||
|
||||
// toolbar
|
||||
let toolbarEl = this.newHeadlineToolbarElement();
|
||||
this._iconFilterEl = this.newIconElement([
|
||||
'fa-filter', 'fa-fw',
|
||||
this._config.moduleHeadlineIconClass
|
||||
]);
|
||||
|
||||
let iconKbEl = this.newIconElement([
|
||||
'fa-external-link-alt', 'fa-fw',
|
||||
this._config.moduleHeadlineIconClass
|
||||
]);
|
||||
iconKbEl.setAttribute('title', 'zkillboard.com');
|
||||
iconKbEl.onclick = e => this.openKillboardUrl(e);
|
||||
|
||||
toolbarEl.append(iconKbEl, this._iconFilterEl);
|
||||
headEl.append(wsStatusEl, toolbarEl);
|
||||
|
||||
return headEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP request
|
||||
* @param url
|
||||
* @returns {Promise}
|
||||
*/
|
||||
request(url){
|
||||
return new Promise((resolve, reject) => {
|
||||
let handleResponse = response => {
|
||||
return response.json()
|
||||
.then((json) => {
|
||||
if(!response.ok){
|
||||
return Promise.reject(Object.assign({}, json, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
}));
|
||||
}
|
||||
return json;
|
||||
});
|
||||
};
|
||||
|
||||
fetch(url)
|
||||
.then(handleResponse)
|
||||
.then((result) => {
|
||||
resolve(result);
|
||||
}).catch((e) => {
|
||||
console.error(url, e);
|
||||
|
||||
let title = e.status ? e.status : e;
|
||||
let text = e.error ? e.error : url;
|
||||
this.showNotify({title: title, text: text, type: 'error'});
|
||||
|
||||
reject(e);
|
||||
}).finally(() => {
|
||||
$(this.moduleElement).hideLoadingAnimation();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get zKillboard data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getSystemKillsData(){
|
||||
// check for cached responses "short term cache"
|
||||
let cacheKey = SystemKillboardModule.getCacheKey('systemId', this._systemData.systemId);
|
||||
let result = SystemKillboardModule.getCache('zkb').get(cacheKey);
|
||||
if(result){
|
||||
// could also be an empty array!
|
||||
return Promise.resolve(result);
|
||||
}else{
|
||||
return new Promise(resolve => {
|
||||
$(this.moduleElement).showLoadingAnimation();
|
||||
|
||||
// get kills within the last 24h
|
||||
let timeFrameInSeconds = 60 * 60 * 24;
|
||||
|
||||
// if system is w-space system -> add link modifier
|
||||
let wSpaceLinkModifier = '';
|
||||
if(this._systemData.type.id === 1){
|
||||
wSpaceLinkModifier = 'w-space/';
|
||||
}
|
||||
|
||||
let url = `${Init.url.zKillboard}/no-items/${wSpaceLinkModifier}no-attackers/npc/0/solarSystemID/${this._systemData.systemId}/pastSeconds/${timeFrameInSeconds}/`;
|
||||
|
||||
this.request(url).then(result => {
|
||||
if(result.error){
|
||||
// successful request with error response
|
||||
this.showNotify({title: 'Data fetch() from zKillboard failed', text: result.error, type: 'error'});
|
||||
return Promise.reject(result);
|
||||
}else{
|
||||
// zkb result needs to be cached and becomes reduced on "load more"
|
||||
SystemKillboardModule.getCache('zkb').set(cacheKey, result);
|
||||
return result;
|
||||
}
|
||||
}).then(result => resolve(result)).catch(e => {
|
||||
console.error(e);
|
||||
this.showNotify({title: 'Data fetch() from zKillboard failed', text: e, type: 'error'});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* render module
|
||||
* @param mapId
|
||||
* @param systemData
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
render(mapId, systemData){
|
||||
this._mapId = mapId;
|
||||
this._systemData = systemData;
|
||||
|
||||
// request graph data and store result promise
|
||||
// -> module is not full rendered jet
|
||||
this._dataPromise = this.getSystemKillsData();
|
||||
|
||||
this._bodyEl = Object.assign(document.createElement('div'), {
|
||||
className: this._config.bodyClassName
|
||||
});
|
||||
this.moduleElement.append(this._bodyEl);
|
||||
|
||||
this.setModuleObserver();
|
||||
|
||||
// init webSocket connection
|
||||
SystemKillboardModule.initWebSocket();
|
||||
|
||||
return this.moduleElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* init module
|
||||
*/
|
||||
init(){
|
||||
super.init();
|
||||
|
||||
// subscribe this module instance to the zKB WebSocket
|
||||
SystemKillboardModule.subscribeToWS(this);
|
||||
this.updateWsStatus();
|
||||
|
||||
// show "historic" killmails
|
||||
if(this._dataPromise instanceof Promise){
|
||||
this._dataPromise
|
||||
.then(result => {
|
||||
this._killboardLabelEl = this.newLabelElement('recent kills within the last hour', [
|
||||
'label-warning', this._config.labelRecentKillsClass, 'hidden'
|
||||
]);
|
||||
|
||||
if(result.length){
|
||||
// kills found
|
||||
this._killboardEl = document.createElement('ul');
|
||||
this._killboardEl.classList.add(this._config.systemKillboardListClass);
|
||||
|
||||
this._bodyEl.append(
|
||||
this._killboardLabelEl,
|
||||
this._killboardEl,
|
||||
this.newControlElement('load more', [this._config.moduleHeadlineIconClass])
|
||||
);
|
||||
|
||||
// set a "local" copy of all indexes from cached response
|
||||
// -> this "local" array gets reduces when KM gets loaded
|
||||
// -> manipulation does NOT affect cached data
|
||||
this._tempZkbKillmailIndexes = Object.keys(result);
|
||||
|
||||
this.showKills(this._config.chunkCountKills);
|
||||
}else{
|
||||
// no kills found
|
||||
this._bodyEl.append(
|
||||
this.newLabelElement('No kills found within the last 24h', ['label-success'])
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
beforeHide(){
|
||||
super.beforeHide();
|
||||
SystemKillboardModule.unsubscribeFromWS(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* load a chunk of killmails and render them
|
||||
* @param chunkSize
|
||||
*/
|
||||
showKills(chunkSize){
|
||||
if(chunkSize){
|
||||
let cacheKey = SystemKillboardModule.getCacheKey('systemId', this._systemData.systemId);
|
||||
let result = SystemKillboardModule.getCache('zkb').get(cacheKey);
|
||||
|
||||
if(
|
||||
this._killboardEl.children.length < this._config.maxCountKillHistoric &&
|
||||
(this._tempZkbKillmailIndexes || []).length &&
|
||||
(result || []).length
|
||||
){
|
||||
// next killmail to load -> reduce "local" index array
|
||||
let nextZkb = result[this._tempZkbKillmailIndexes.shift()];
|
||||
if(nextZkb){
|
||||
this.loadKillmailData({
|
||||
killId: parseInt(nextZkb.killmail_id) || 0,
|
||||
hash: nextZkb.zkb.hash
|
||||
}, {
|
||||
chunkSize: --chunkSize,
|
||||
zkb: nextZkb.zkb
|
||||
}, 'renderKillmail');
|
||||
}else{
|
||||
console.warn('Killmail not found in local zkb cache');
|
||||
}
|
||||
}else{
|
||||
// no more kills available OR max kills reached
|
||||
this.moduleElement.querySelector('.' + this._config.controlAreaClass).style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get killmail data from ESI
|
||||
* @param requestData
|
||||
* @param context
|
||||
* @param callback
|
||||
*/
|
||||
loadKillmailData(requestData, context, callback){
|
||||
let cacheKey = SystemKillboardModule.getCacheKey('killmail', requestData.killId);
|
||||
let cacheItem = SystemKillboardModule.getCache('killmail').get(cacheKey);
|
||||
if(cacheItem){
|
||||
// ... already cached -> show cache killmail
|
||||
this[callback](cacheItem.zkb, cacheItem.killmailData, cacheItem.systemData, context.chunkSize)
|
||||
.then(payload => this.showKills(payload.data.chunkSize))
|
||||
.catch(e => console.error(e));
|
||||
}else{
|
||||
// ...not cached -> request data
|
||||
let url = 'https://esi.evetech.net/latest/killmails/' + requestData.killId + '/' + requestData.hash + '/';
|
||||
|
||||
this.request(url).then(killmailData => {
|
||||
let systemData = SystemKillboardModule.getSystemDataForCache(this._systemData);
|
||||
SystemKillboardModule.getCache('killmail').set(cacheKey, {zkb: context.zkb, killmailData: killmailData, systemData: systemData});
|
||||
|
||||
this[callback](context.zkb, killmailData, systemData, context.chunkSize)
|
||||
.then(payload => this.showKills(payload.data.chunkSize))
|
||||
.catch(e => console.error(e));
|
||||
}).catch(e => {
|
||||
// request failed -> skip this and load next
|
||||
console.warn(e);
|
||||
this.showKills(context.chunkSize);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zkb
|
||||
* @param attackersCount
|
||||
* @returns {{color: string, label: string}}
|
||||
*/
|
||||
getAttackerBadgeData(zkb, attackersCount){
|
||||
let color, label;
|
||||
if(zkb.solo){
|
||||
color = '#5cb85c';
|
||||
label = 'solo';
|
||||
}else if(zkb.npc){
|
||||
color = '#d747d6';
|
||||
label = 'npc';
|
||||
}else if(attackersCount){
|
||||
color = '#adadad';
|
||||
label = attackersCount;
|
||||
}
|
||||
return {color, label};
|
||||
}
|
||||
|
||||
/**
|
||||
* render a single killmail
|
||||
* @param zkbData
|
||||
* @param killmailData
|
||||
* @param systemData
|
||||
* @param chunkSize
|
||||
* @param position
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
renderKillmail(zkbData, killmailData, systemData, chunkSize, position = 'bottom'){
|
||||
return new Promise((resolve, reject) => {
|
||||
if(!this._killboardEl){
|
||||
// killboard element might not be rendered at this point (e.g. if new kills pushed to WS)
|
||||
return reject(new ReferenceError('Could not render killmail. KB element not found.'));
|
||||
}
|
||||
|
||||
// calculate time diff in hours
|
||||
let serverDate = SystemKillboardModule.serverTime;
|
||||
let serverHours = serverDate.setHours(0,0,0,0);
|
||||
|
||||
let killDate = Util.convertDateToUTC(new Date(killmailData.killmail_time));
|
||||
|
||||
// get time diff
|
||||
let timeDiffMin = Math.round((serverDate - killDate) / 1000 / 60);
|
||||
let timeDiffHour = Math.floor(timeDiffMin / 60);
|
||||
|
||||
let attackers = BaseModule.Util.getObjVal(killmailData, 'attackers') || [];
|
||||
let attackersCount = attackers.length;
|
||||
let attackerFinal = attackers.find(attacker => attacker.final_blow);
|
||||
|
||||
|
||||
let data = {
|
||||
zkb: zkbData,
|
||||
killmail: killmailData,
|
||||
system: systemData,
|
||||
isPodKill: BaseModule.Util.getObjVal(killmailData, 'victim.ship_type_id') === 670, // POD shipId
|
||||
attackersCount: attackersCount,
|
||||
attackerFinal: attackerFinal,
|
||||
attackerBadge: this.getAttackerBadgeData(zkbData, attackersCount),
|
||||
config: this._config,
|
||||
zKillboardUrl: 'https://zkillboard.com',
|
||||
ccpImageServerUrl: Init.url.ccpImageServer,
|
||||
imgUrlType: () => {
|
||||
return (val, render) => this.getImageUrl('types', parseInt(render(val)) || 0, 64);
|
||||
},
|
||||
imgUrlChar: () => {
|
||||
return (val, render) => this.getImageUrl('characters', parseInt(render(val)) || 0);
|
||||
},
|
||||
imgUrlCorp: () => {
|
||||
return (val, render) => this.getImageUrl('corporations', parseInt(render(val)) || 0);
|
||||
},
|
||||
imgUrlAlly: () => {
|
||||
return (val, render) => this.getImageUrl('alliances', parseInt(render(val)) || 0);
|
||||
},
|
||||
dateFormat: () => {
|
||||
return (val, render) => {
|
||||
let killDate = Util.convertDateToUTC(new Date(render(val)));
|
||||
let value = Util.convertDateToString(killDate);
|
||||
|
||||
// check whether log is new (today) ->
|
||||
if(killDate.setHours(0,0,0,0) === serverHours){
|
||||
// replace dd/mm/YYYY
|
||||
value = 'today' + value.substring(10);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
},
|
||||
iskFormat: () => {
|
||||
return (val, render) => {
|
||||
return Util.formatPrice(render(val));
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
requirejs(['text!templates/modules/killmail.html', 'mustache'], (template, Mustache) => {
|
||||
// show hint for recent kills
|
||||
this._killboardLabelEl.classList.toggle('hidden', !(timeDiffHour === 0 && this._killboardLabelEl));
|
||||
|
||||
// render killmail entry
|
||||
let insertPos = ['append', 'beforeend'];
|
||||
let animationPos = 'lastChild';
|
||||
if(position === 'top'){
|
||||
insertPos = ['prepend', 'afterbegin'];
|
||||
animationPos = 'firstChild';
|
||||
}
|
||||
|
||||
this._killboardEl.insertAdjacentHTML(insertPos[1], Mustache.render(template, data));
|
||||
|
||||
// animate kill li-element
|
||||
$(this._killboardEl[animationPos]).velocity('transition.expandIn', {
|
||||
display: 'flex',
|
||||
complete: function(){
|
||||
$(this).initTooltips();
|
||||
}
|
||||
}, {
|
||||
display: 'flex'
|
||||
});
|
||||
|
||||
resolve({
|
||||
action: 'renderKillmail',
|
||||
data: {
|
||||
chunkSize: chunkSize
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* remove last killmail list entry
|
||||
*/
|
||||
removeKillmail(){
|
||||
if(this._killboardEl){
|
||||
let lastEl = this._nextToRemove || (this._listStreamHeadline ? this._listStreamHeadline.previousElementSibling : null) || this._killboardEl.lastChild;
|
||||
if(lastEl){
|
||||
this._nextToRemove = lastEl.previousElementSibling;
|
||||
this._killboardEl.removeChild(lastEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get image src URL
|
||||
* @param resourceType
|
||||
* @param resourceId
|
||||
* @param size
|
||||
* @returns {string}
|
||||
*/
|
||||
getImageUrl(resourceType, resourceId, size = 32){
|
||||
let url = '#';
|
||||
if(resourceId){
|
||||
url = BaseModule.Util.eveImageUrl(resourceType, resourceId, size);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Li headline (devider)
|
||||
* @param text
|
||||
* @returns {HTMLLIElement}
|
||||
*/
|
||||
newListItemHeadElement(text){
|
||||
let listHeadEl = Object.assign(document.createElement('li'), {
|
||||
className: ['flex-row', 'flex-between', 'media', this._config.systemKillboardListHeadClass].join(' ')
|
||||
});
|
||||
|
||||
let iconDownEl = this.newIconElement(['fa-angle-double-down']);
|
||||
let iconUpEl = this.newIconElement(['fa-angle-double-up']);
|
||||
let headlineIconLeftEl = Object.assign(this.newHeadlineElement(), {
|
||||
className: 'flex-col'
|
||||
});
|
||||
let headlineIconRightEl = Object.assign(this.newHeadlineElement(), {
|
||||
className: 'flex-col'
|
||||
});
|
||||
headlineIconLeftEl.append(iconDownEl);
|
||||
headlineIconRightEl.append(iconUpEl);
|
||||
|
||||
let headlineLeftEl = this.newHeadlineElement(text);
|
||||
headlineLeftEl.classList.add('flex-col', 'flex-grow', 'media-body');
|
||||
let headlineRightEl = this.newHeadlineElement('Killstream');
|
||||
headlineRightEl.classList.add('flex-col', 'flex-grow', 'media-body', 'text-right');
|
||||
|
||||
listHeadEl.append(headlineIconLeftEl, headlineLeftEl, headlineRightEl, headlineIconRightEl);
|
||||
return listHeadEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* set module observer
|
||||
*/
|
||||
setModuleObserver(){
|
||||
this.setFilterIconObserver();
|
||||
|
||||
$(this.moduleElement).initTooltips({
|
||||
placement: 'top'
|
||||
});
|
||||
|
||||
this.moduleElement.addEventListener('click', e => {
|
||||
if(e.target.closest('.' + this._config.controlAreaClass)){
|
||||
e.stopPropagation();
|
||||
this.showKills(this._config.chunkCountKills);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* init filter icon for WebSocket streams
|
||||
*/
|
||||
setFilterIconObserver(){
|
||||
let cacheKey = `map_${this._mapId}.streams`;
|
||||
this.getLocalStore().getItem(cacheKey).then(streams => {
|
||||
if(!streams){
|
||||
// not saved yet -> default streams
|
||||
streams = ['system', 'map'];
|
||||
this.getLocalStore().setItem(cacheKey, streams);
|
||||
}
|
||||
this._filterStreams = streams;
|
||||
|
||||
let sourceOptions = [{
|
||||
value: 'system',
|
||||
text: `System (${this._systemData.name})`
|
||||
},{
|
||||
value: 'map',
|
||||
text: `Map (${BaseModule.Util.getObjVal(BaseModule.Util.getCurrentMapData(this._mapId), 'config.name')})`
|
||||
},{
|
||||
value: 'all',
|
||||
text: `All (New Eden)`
|
||||
}];
|
||||
|
||||
$(this._iconFilterEl).editable({
|
||||
// toggle: 'manual',
|
||||
mode: 'popup',
|
||||
type: 'checklist',
|
||||
showbuttons: false,
|
||||
onblur: 'submit',
|
||||
highlight: false,
|
||||
title: 'Streams',
|
||||
placement: 'left',
|
||||
pk: this._mapId,
|
||||
value: this._filterStreams,
|
||||
source: sourceOptions,
|
||||
emptyclass: '',
|
||||
emptytext: '',
|
||||
display: function(value, sourceData){
|
||||
// update filter badge
|
||||
if(
|
||||
value && sourceData &&
|
||||
value.length < sourceData.length
|
||||
){
|
||||
this.dataset.badge = String(value.length);
|
||||
}else{
|
||||
delete this.dataset.badge;
|
||||
}
|
||||
},
|
||||
viewport: {
|
||||
selector: this.moduleElement,
|
||||
padding: 5
|
||||
}
|
||||
});
|
||||
|
||||
$(this._iconFilterEl).on('save', (e, params) => {
|
||||
this.getLocalStore().setItem(cacheKey, params.newValue).then(streams => this._filterStreams = streams);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* open external zKillboard URL
|
||||
* @param e
|
||||
*/
|
||||
openKillboardUrl(e){
|
||||
e.stopPropagation();
|
||||
window.open(`//zkillboard.com/system/${this._systemData.systemId}`, '_blank');
|
||||
}
|
||||
|
||||
/**
|
||||
* check if killmailData matches any killStream
|
||||
* @param killmailData
|
||||
* @returns {boolean}
|
||||
*/
|
||||
filterKillmailByStreams(killmailData){
|
||||
let streams = this._filterStreams || [];
|
||||
return !!(streams.includes('all') ||
|
||||
(streams.includes('system') && this._systemData.systemId === killmailData.solar_system_id) ||
|
||||
(streams.includes('map') && MapUtil.getSystemData(this._mapId, killmailData.solar_system_id, 'systemId')));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* callback for WebSocket responses
|
||||
* @param zkbData
|
||||
* @param killmailData
|
||||
*/
|
||||
onWsMessage(zkbData, killmailData){
|
||||
// check if killmail belongs to current filtered "streams"
|
||||
if(this.filterKillmailByStreams(killmailData)){
|
||||
// check max limit for WS kill entries
|
||||
this._countKillsWS = (this._countKillsWS || 0) + 1;
|
||||
if(this._countKillsWS > this._config.maxCountKillsWS){
|
||||
// remove oldest KM
|
||||
this.removeKillmail();
|
||||
}
|
||||
|
||||
// add headline before first WS killmail gets added to top
|
||||
if(this._killboardEl && this._countKillsWS === 1){
|
||||
this._listStreamHeadline = this.newListItemHeadElement(this._systemData.name);
|
||||
this._killboardEl.prepend(this._listStreamHeadline);
|
||||
}
|
||||
|
||||
// get systemData for killmailData
|
||||
// -> systemData should exist if KM belongs to any system on any map
|
||||
let systemData = MapUtil.getSystemData(this._mapId, killmailData.solar_system_id, 'systemId') || null;
|
||||
|
||||
this.renderKillmail(zkbData, killmailData, systemData, 0, 'top')
|
||||
.catch(e => console.warn(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update module with current WebSocket status
|
||||
*/
|
||||
updateWsStatus(){
|
||||
let isSuccess = false;
|
||||
let isWarning = false;
|
||||
let isError = false;
|
||||
let title = 'unknown';
|
||||
switch(SystemKillboardModule.wsStatus){
|
||||
case 1: // connecting
|
||||
title = 'connecting…';
|
||||
isWarning = true;
|
||||
break;
|
||||
case 2: // connected
|
||||
title = 'connected';
|
||||
isSuccess = true;
|
||||
break;
|
||||
case 3: // error
|
||||
title = 'error';
|
||||
isError = true;
|
||||
break;
|
||||
case 4: // close
|
||||
title = 'closed';
|
||||
isError = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
this._iconWsEl.classList.toggle('txt-color-green', isSuccess);
|
||||
this._iconWsEl.classList.toggle('txt-color-orange', isWarning);
|
||||
this._iconWsEl.classList.toggle('txt-color-red', isError);
|
||||
this._iconWsEl.setAttribute('title', title);
|
||||
|
||||
$(this._iconWsEl).destroyTooltips().initTooltips({
|
||||
placement: 'top'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* reduce full systemData to minimal data that should be cached with a KM
|
||||
* @param systemData
|
||||
* @returns {*}
|
||||
*/
|
||||
static getSystemDataForCache(systemData){
|
||||
return systemData ? {id: systemData.systemId, name: systemData.name} : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get all systems from all maps, that might be relevant for any cache stream
|
||||
* -> KMs belonging to any of these systems are cached
|
||||
* @returns {Object}
|
||||
*/
|
||||
static getWsRelevantSystemsFromMaps(){
|
||||
let cacheKey = SystemKillboardModule.getCacheKey('tempSystemsData', 1);
|
||||
let systemsData = SystemKillboardModule.getCache('zkb').get(cacheKey);
|
||||
|
||||
if(!systemsData){
|
||||
// KB cache ist for all maps (not just the current one)
|
||||
let mapsData = BaseModule.Util.getCurrentMapData() || [];
|
||||
systemsData = mapsData.reduce(
|
||||
(accAll, mapData) => Object.assign(
|
||||
accAll,
|
||||
(BaseModule.Util.getObjVal(mapData, 'data.systems') || []).reduce(
|
||||
(accMap, systemData) => Object.assign(
|
||||
accMap,
|
||||
{[systemData.systemId]: this.getSystemDataForCache(systemData)}
|
||||
), {})
|
||||
), {});
|
||||
|
||||
SystemKillboardModule.getCache('zkb').set(cacheKey, systemsData);
|
||||
}
|
||||
return systemsData;
|
||||
}
|
||||
|
||||
/**
|
||||
* cache WebSocket response if it is relevant for us
|
||||
* @param response
|
||||
* @returns {*[]}
|
||||
*/
|
||||
static cacheWsResponse(response){
|
||||
let zkbData = response.zkb;
|
||||
delete response.zkb;
|
||||
let killmailData = response;
|
||||
let systemData = null;
|
||||
|
||||
// check if killmailData is relevant (belongs to any system on any map
|
||||
let systemsData = this.getWsRelevantSystemsFromMaps();
|
||||
if(Object.keys(systemsData).map(systemId => parseInt(systemId)).includes(killmailData.solar_system_id)){
|
||||
// system is on map! -> cache
|
||||
systemData = BaseModule.Util.getObjVal(systemsData, String(killmailData.solar_system_id));
|
||||
let cacheKey = SystemKillboardModule.getCacheKey('killmail', killmailData.killmail_id);
|
||||
SystemKillboardModule.getCache('killmail').set(cacheKey, {
|
||||
zkb: zkbData,
|
||||
killmailData: killmailData,
|
||||
systemData: systemData
|
||||
});
|
||||
}
|
||||
|
||||
return [zkbData, killmailData];
|
||||
}
|
||||
|
||||
/**
|
||||
* subscribe instance of Killboard module to WebSocket
|
||||
* @param module
|
||||
*/
|
||||
static unsubscribeFromWS(module){
|
||||
SystemKillboardModule.wsSubscribtions = SystemKillboardModule.wsSubscribtions.filter(subscriber => subscriber !== module);
|
||||
}
|
||||
|
||||
/**
|
||||
* unsubscribe instance of Killboard module to WebSocket
|
||||
* @param module
|
||||
*/
|
||||
static subscribeToWS(module){
|
||||
if(
|
||||
!SystemKillboardModule.wsSubscribtions.includes(module) &&
|
||||
module instanceof SystemKillboardModule
|
||||
){
|
||||
SystemKillboardModule.wsSubscribtions.push(module);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* init/connect to WebSocket if not already done
|
||||
*/
|
||||
static initWebSocket(){
|
||||
if(!SystemKillboardModule.ws){
|
||||
SystemKillboardModule.ws = new WebSocket('wss://zkillboard.com:2096');
|
||||
SystemKillboardModule.wsStatus = 1;
|
||||
}
|
||||
|
||||
let sendMessage = req => {
|
||||
SystemKillboardModule.ws.send(JSON.stringify(req));
|
||||
};
|
||||
|
||||
SystemKillboardModule.ws.onopen = e => {
|
||||
SystemKillboardModule.wsStatus = 2;
|
||||
SystemKillboardModule.wsSubscribtions.forEach(subscriber => subscriber.updateWsStatus());
|
||||
|
||||
sendMessage({action:'sub', channel:'killstream'});
|
||||
};
|
||||
|
||||
SystemKillboardModule.ws.onmessage = e => {
|
||||
let response = JSON.parse(e.data);
|
||||
|
||||
let [zkbData, killmailData] = this.cacheWsResponse(response);
|
||||
SystemKillboardModule.wsSubscribtions.forEach(subscriber => subscriber.onWsMessage(zkbData, killmailData));
|
||||
};
|
||||
|
||||
SystemKillboardModule.ws.onerror = e => {
|
||||
SystemKillboardModule.wsStatus = 3;
|
||||
SystemKillboardModule.ws = null;
|
||||
SystemKillboardModule.wsSubscribtions.forEach(subscriber => subscriber.updateWsStatus());
|
||||
};
|
||||
|
||||
SystemKillboardModule.ws.onclose = e => {
|
||||
SystemKillboardModule.wsStatus = 4;
|
||||
SystemKillboardModule.ws = null;
|
||||
SystemKillboardModule.wsSubscribtions.forEach(subscriber => subscriber.updateWsStatus());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get cache key
|
||||
* @param type
|
||||
* @param objId
|
||||
* @returns {string}
|
||||
*/
|
||||
static getCacheKey(type, objId){
|
||||
if(
|
||||
typeof type === 'string' && type.length &&
|
||||
parseInt(objId)
|
||||
){
|
||||
return `${type}_${objId}`;
|
||||
}
|
||||
throw new TypeError('Invalid cache key types');
|
||||
}
|
||||
};
|
||||
|
||||
SystemKillboardModule.isPlugin = false; // module is defined as 'plugin'
|
||||
SystemKillboardModule.scope = 'system'; // module scope controls how module gets updated and what type of data is injected
|
||||
SystemKillboardModule.sortArea = 'c'; // default sortable area
|
||||
SystemKillboardModule.position = 1; // default sort/order position within sortable area
|
||||
SystemKillboardModule.label = 'Killboard'; // static module label (e.g. description)
|
||||
SystemKillboardModule.wsStatus = undefined;
|
||||
SystemKillboardModule.serverTime = BaseModule.Util.getServerTime(); // static Date() with current EVE server time
|
||||
SystemKillboardModule.wsSubscribtions = []; // static container for all KB module instances (from multiple maps) for WS responses
|
||||
SystemKillboardModule.cacheConfig = {
|
||||
zkb: { // cache for "zKillboard" responses -> short term cache
|
||||
ttl: 60 * 3,
|
||||
maxSize: 50
|
||||
},
|
||||
killmail: { // cache for "Killmail" data -> long term cache
|
||||
ttl: 60 * 30,
|
||||
maxSize: 500
|
||||
}
|
||||
};
|
||||
|
||||
SystemKillboardModule.defaultConfig = {
|
||||
className: 'pf-system-killboard-module', // class for module
|
||||
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
|
||||
headline: 'Killboard',
|
||||
|
||||
// system killboard list
|
||||
systemKillboardListClass: 'pf-system-killboard-list', // class for a list with kill entries
|
||||
systemKillboardListHeadClass: 'pf-system-killboard-list-head', // class for a list entry headline
|
||||
systemKillboardListImgL: 'pf-system-killboard-img-l', // class for all large centered img (ship images)
|
||||
systemKillboardListImgM: 'pf-system-killboard-img-m', // class for all medium centered img (character logos)
|
||||
systemKillboardListImgS: 'pf-system-killboard-img-s', // class for all small top/bottom img (alliance/corp logos)
|
||||
|
||||
wsStatusWrapperClass: 'pf-system-killboard-wsStatusWrapper', // class for WebSocket "status" wrapper
|
||||
wsStatusClass: 'pf-system-killboard-wsStatus', // class for WebSocket "status" headline
|
||||
labelRecentKillsClass: 'pf-system-killboard-label-recent', // class for "recent kills" label
|
||||
|
||||
minCountKills: 5,
|
||||
chunkCountKills: 5,
|
||||
maxCountKillHistoric: 43,
|
||||
|
||||
maxCountKillsWS: 150
|
||||
};
|
||||
|
||||
return SystemKillboardModule;
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,207 +1,2 @@
|
||||
'use strict';
|
||||
|
||||
// "fake" window object will contain "MsgWorker" after import
|
||||
let window = {}; // jshint ignore:line
|
||||
|
||||
// import "MsgWorker" class
|
||||
self.importScripts( self.name ); // jshint ignore:line
|
||||
|
||||
let MsgWorker = window.MsgWorker;
|
||||
let socket = null;
|
||||
let ports = [];
|
||||
let characterPorts = [];
|
||||
|
||||
// init "WebSocket" connection ========================================================================================
|
||||
let initSocket = uri => {
|
||||
let MsgWorkerOpen = new MsgWorker('ws:open');
|
||||
|
||||
if(socket === null){
|
||||
socket = new WebSocket(uri);
|
||||
|
||||
// "WebSocket" open -----------------------------------------------------------------------
|
||||
socket.onopen = e => {
|
||||
MsgWorkerOpen.meta({
|
||||
readyState: socket.readyState
|
||||
});
|
||||
|
||||
sendToCurrentPort(MsgWorkerOpen);
|
||||
};
|
||||
|
||||
// "WebSocket message ---------------------------------------------------------------------
|
||||
socket.onmessage = e => {
|
||||
let response = JSON.parse(e.data);
|
||||
|
||||
let MsgWorkerSend = new MsgWorker('ws:send');
|
||||
MsgWorkerSend.task( response.task );
|
||||
MsgWorkerSend.meta({
|
||||
readyState: this.readyState,
|
||||
characterIds: response.characterIds
|
||||
});
|
||||
MsgWorkerSend.data( response.load );
|
||||
|
||||
broadcastPorts(MsgWorkerSend);
|
||||
};
|
||||
|
||||
// "WebSocket" close ----------------------------------------------------------------------
|
||||
socket.onclose = closeEvent => {
|
||||
let MsgWorkerClosed = new MsgWorker('ws:closed');
|
||||
MsgWorkerClosed.meta({
|
||||
readyState: socket.readyState,
|
||||
code: closeEvent.code,
|
||||
reason: closeEvent.reason,
|
||||
wasClean: closeEvent.wasClean
|
||||
});
|
||||
|
||||
broadcastPorts(MsgWorkerClosed);
|
||||
socket = null; // reset WebSocket
|
||||
};
|
||||
|
||||
// "WebSocket" error ----------------------------------------------------------------------
|
||||
socket.onerror = e => {
|
||||
let MsgWorkerError = new MsgWorker('ws:error');
|
||||
MsgWorkerError.meta({
|
||||
readyState: socket.readyState
|
||||
});
|
||||
|
||||
sendToCurrentPort(MsgWorkerError);
|
||||
};
|
||||
}else{
|
||||
// socket still open
|
||||
MsgWorkerOpen.meta({
|
||||
readyState: socket.readyState
|
||||
});
|
||||
sendToCurrentPort(MsgWorkerOpen);
|
||||
}
|
||||
};
|
||||
|
||||
// send message to port(s) ============================================================================================
|
||||
let sendToCurrentPort = load => {
|
||||
ports[ports.length - 1].postMessage(load);
|
||||
};
|
||||
|
||||
let broadcastPorts = load => {
|
||||
// default: sent to all ports
|
||||
let sentToPorts = ports;
|
||||
|
||||
// check if send() is limited to some ports
|
||||
let meta = load.meta();
|
||||
if(
|
||||
meta &&
|
||||
meta.characterIds &&
|
||||
meta.characterIds !== 'undefined' &&
|
||||
meta.characterIds instanceof Array
|
||||
){
|
||||
// ... get ports for characterIds
|
||||
sentToPorts = getPortsByCharacterIds(meta.characterIds);
|
||||
}
|
||||
|
||||
for(let i = 0; i < sentToPorts.length; i++){
|
||||
sentToPorts[i].postMessage(load);
|
||||
}
|
||||
};
|
||||
|
||||
// port functions =====================================================================================================
|
||||
let addPort = (port, characterId) => {
|
||||
characterId = parseInt(characterId);
|
||||
|
||||
if(characterId > 0){
|
||||
characterPorts.push({
|
||||
characterId: characterId,
|
||||
port: port
|
||||
});
|
||||
}else{
|
||||
ports.push(port);
|
||||
}
|
||||
};
|
||||
|
||||
let getPortsByCharacterIds = characterIds => {
|
||||
let ports = [];
|
||||
|
||||
for(let i = 0; i < characterPorts.length; i++){
|
||||
for(let j = 0; j < characterIds.length; j++){
|
||||
if(characterPorts[i].characterId === characterIds[j]){
|
||||
ports.push(characterPorts[i].port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ports;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param port
|
||||
* @returns {int[]}
|
||||
*/
|
||||
let removePort = port => {
|
||||
let characterIds = [];
|
||||
|
||||
// reverse loop required because of array index reset after splice()
|
||||
let i = characterPorts.length;
|
||||
while(i--){
|
||||
if(characterPorts[i].port === port){
|
||||
// collectt all character Ids mapped to the removed port
|
||||
characterIds.push(characterPorts[i].characterId);
|
||||
characterPorts.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
let j = ports.length;
|
||||
while(j--){
|
||||
if(ports[j] === port){
|
||||
ports.splice(j, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// return unique characterIds
|
||||
return [...new Set(characterIds)];
|
||||
};
|
||||
|
||||
// "SharedWorker" connection ==========================================================================================
|
||||
self.addEventListener('connect', event => { // jshint ignore:line
|
||||
let port = event.ports[0];
|
||||
addPort(port);
|
||||
|
||||
port.addEventListener('message', (e) => {
|
||||
let MsgWorkerMessage = e.data;
|
||||
Object.setPrototypeOf(MsgWorkerMessage, MsgWorker.prototype);
|
||||
|
||||
switch(MsgWorkerMessage.command){
|
||||
case 'ws:init':
|
||||
let data = MsgWorkerMessage.data();
|
||||
// add character specific port (for broadcast) to individual ports (tabs)
|
||||
addPort(port, data.characterId);
|
||||
initSocket(data.uri);
|
||||
break;
|
||||
case 'ws:send':
|
||||
socket.send(JSON.stringify({
|
||||
task: MsgWorkerMessage.task(),
|
||||
load: MsgWorkerMessage.data()
|
||||
}));
|
||||
break;
|
||||
case 'sw:closePort':
|
||||
port.close();
|
||||
|
||||
// remove port from store
|
||||
// -> charIds managed by closed port
|
||||
let characterIds = removePort(port);
|
||||
|
||||
// check if there are still other ports active that manage removed ports
|
||||
// .. if not -> send "unsubscribe" event to WebSocket server
|
||||
let portsLeft = getPortsByCharacterIds(characterIds);
|
||||
|
||||
if(!portsLeft.length){
|
||||
socket.send(JSON.stringify({
|
||||
task: MsgWorkerMessage.task(),
|
||||
load: characterIds
|
||||
}));
|
||||
}
|
||||
break;
|
||||
case 'ws:close':
|
||||
// closeSocket();
|
||||
break;
|
||||
}
|
||||
}, false);
|
||||
|
||||
port.start();
|
||||
}, false);
|
||||
"use strict";let window={};self.importScripts(self.name);let MsgWorker=window.MsgWorker,socket=null,ports=[],characterPorts=[],initSocket=t=>{let e=new MsgWorker("ws:open");null===socket?((socket=new WebSocket(t)).onopen=(t=>{e.meta({readyState:socket.readyState}),sendToCurrentPort(e)}),socket.onmessage=(t=>{let e=JSON.parse(t.data),r=new MsgWorker("ws:send");r.task(e.task),r.meta({readyState:this.readyState,characterIds:e.characterIds}),r.data(e.load),broadcastPorts(r)}),socket.onclose=(t=>{let e=new MsgWorker("ws:closed");e.meta({readyState:socket.readyState,code:t.code,reason:t.reason,wasClean:t.wasClean}),broadcastPorts(e),socket=null}),socket.onerror=(t=>{let e=new MsgWorker("ws:error");e.meta({readyState:socket.readyState}),sendToCurrentPort(e)})):(e.meta({readyState:socket.readyState}),sendToCurrentPort(e))},sendToCurrentPort=t=>{ports[ports.length-1].postMessage(t)},broadcastPorts=t=>{let e=ports,r=t.meta();r&&r.characterIds&&"undefined"!==r.characterIds&&r.characterIds instanceof Array&&(e=getPortsByCharacterIds(r.characterIds));for(let r=0;r<e.length;r++)e[r].postMessage(t)},addPort=(t,e)=>{(e=parseInt(e))>0?characterPorts.push({characterId:e,port:t}):ports.push(t)},getPortsByCharacterIds=t=>{let e=[];for(let r=0;r<characterPorts.length;r++)for(let a=0;a<t.length;a++)characterPorts[r].characterId===t[a]&&e.push(characterPorts[r].port);return e},removePort=t=>{let e=[],r=characterPorts.length;for(;r--;)characterPorts[r].port===t&&(e.push(characterPorts[r].characterId),characterPorts.splice(r,1));let a=ports.length;for(;a--;)ports[a]===t&&ports.splice(a,1);return[...new Set(e)]};self.addEventListener("connect",t=>{let e=t.ports[0];addPort(e),e.addEventListener("message",t=>{let r=t.data;switch(Object.setPrototypeOf(r,MsgWorker.prototype),r.command){case"ws:init":let t=r.data();addPort(e,t.characterId),initSocket(t.uri);break;case"ws:send":socket.send(JSON.stringify({task:r.task(),load:r.data()}));break;case"sw:closePort":e.close();let a=removePort(e);getPortsByCharacterIds(a).length||socket.send(JSON.stringify({task:r.task(),load:a}))}},!1),e.start()},!1);
|
||||
//# sourceMappingURL=map.js.map
|
||||
|
||||
BIN
public/js/v2.0.0/app/worker/map.js.br
Normal file
BIN
public/js/v2.0.0/app/worker/map.js.br
Normal file
Binary file not shown.
1
public/js/v2.0.0/app/worker/map.js.map
Normal file
1
public/js/v2.0.0/app/worker/map.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["app/worker/map.js"],"names":["window","self","importScripts","name","MsgWorker","socket","ports","characterPorts","initSocket","uri","MsgWorkerOpen","WebSocket","onopen","e","meta","readyState","sendToCurrentPort","onmessage","response","JSON","parse","data","MsgWorkerSend","task","this","characterIds","load","broadcastPorts","onclose","closeEvent","MsgWorkerClosed","code","reason","wasClean","onerror","MsgWorkerError","length","postMessage","sentToPorts","Array","getPortsByCharacterIds","i","addPort","port","characterId","parseInt","push","j","removePort","splice","Set","addEventListener","event","MsgWorkerMessage","Object","setPrototypeOf","prototype","command","send","stringify","close","start"],"mappings":"AAAA,aAGA,IAAIA,UAGJC,KAAKC,cAAeD,KAAKE,MAEzB,IAAIC,UAAYJ,OAAOI,UACnBC,OAAS,KACTC,SACAC,kBAGAC,WAAaC,IACb,IAAIC,EAAgB,IAAIN,UAAU,WAEpB,OAAXC,SACCA,OAAS,IAAIM,UAAUF,IAGhBG,OAASC,CAAAA,IACZH,EAAcI,MACVC,WAAYV,OAAOU,aAGvBC,kBAAkBN,KAItBL,OAAOY,UAAYJ,CAAAA,IACf,IAAIK,EAAWC,KAAKC,MAAMP,EAAEQ,MAExBC,EAAgB,IAAIlB,UAAU,WAClCkB,EAAcC,KAAML,EAASK,MAC7BD,EAAcR,MACVC,WAAYS,KAAKT,WACjBU,aAAcP,EAASO,eAE3BH,EAAcD,KAAMH,EAASQ,MAE7BC,eAAeL,KAInBjB,OAAOuB,QAAUC,CAAAA,IACb,IAAIC,EAAkB,IAAI1B,UAAU,aACpC0B,EAAgBhB,MACZC,WAAYV,OAAOU,WACnBgB,KAAMF,EAAWE,KACjBC,OAAQH,EAAWG,OACnBC,SAAUJ,EAAWI,WAGzBN,eAAeG,GACfzB,OAAS,OAIbA,OAAO6B,QAAUrB,CAAAA,IACb,IAAIsB,EAAiB,IAAI/B,UAAU,YACnC+B,EAAerB,MACXC,WAAYV,OAAOU,aAGvBC,kBAAkBmB,OAItBzB,EAAcI,MACVC,WAAYV,OAAOU,aAEvBC,kBAAkBN,KAKtBM,kBAAoBU,IACpBpB,MAAMA,MAAM8B,OAAS,GAAGC,YAAYX,IAGpCC,eAAiBD,IAEjB,IAAIY,EAAchC,MAGdQ,EAAOY,EAAKZ,OAEZA,GACAA,EAAKW,cACiB,cAAtBX,EAAKW,cACLX,EAAKW,wBAAwBc,QAG7BD,EAAcE,uBAAuB1B,EAAKW,eAG9C,IAAI,IAAIgB,EAAI,EAAGA,EAAIH,EAAYF,OAAQK,IACnCH,EAAYG,GAAGJ,YAAYX,IAK/BgB,QAAU,CAACC,EAAMC,MACjBA,EAAcC,SAASD,IAEN,EACbrC,eAAeuC,MACXF,YAAaA,EACbD,KAAMA,IAGVrC,MAAMwC,KAAKH,IAIfH,uBAAyBf,IACzB,IAAInB,KAEJ,IAAI,IAAImC,EAAI,EAAGA,EAAIlC,eAAe6B,OAAQK,IACtC,IAAI,IAAIM,EAAI,EAAGA,EAAItB,EAAaW,OAAQW,IACjCxC,eAAekC,GAAGG,cAAgBnB,EAAasB,IAC9CzC,EAAMwC,KAAKvC,eAAekC,GAAGE,MAKzC,OAAOrC,GAQP0C,WAAaL,IACb,IAAIlB,KAGAgB,EAAIlC,eAAe6B,OACvB,KAAMK,KACClC,eAAekC,GAAGE,OAASA,IAE1BlB,EAAaqB,KAAKvC,eAAekC,GAAGG,aACpCrC,eAAe0C,OAAOR,EAAG,IAIjC,IAAIM,EAAIzC,MAAM8B,OACd,KAAMW,KACCzC,MAAMyC,KAAOJ,GACZrC,MAAM2C,OAAOF,EAAG,GAKxB,UAAW,IAAIG,IAAIzB,KAIvBxB,KAAKkD,iBAAiB,UAAWC,IAC7B,IAAIT,EAAOS,EAAM9C,MAAM,GACvBoC,QAAQC,GAERA,EAAKQ,iBAAiB,UAAYtC,IAC9B,IAAIwC,EAAmBxC,EAAEQ,KAGzB,OAFAiC,OAAOC,eAAeF,EAAkBjD,UAAUoD,WAE3CH,EAAiBI,SACpB,IAAK,UACD,IAAIpC,EAAOgC,EAAiBhC,OAE5BqB,QAAQC,EAAMtB,EAAKuB,aACnBpC,WAAWa,EAAKZ,KAChB,MACJ,IAAK,UACDJ,OAAOqD,KAAKvC,KAAKwC,WACbpC,KAAM8B,EAAiB9B,OACvBG,KAAM2B,EAAiBhC,UAE3B,MACJ,IAAK,eACDsB,EAAKiB,QAIL,IAAInC,EAAeuB,WAAWL,GAIdH,uBAAuBf,GAEzBW,QACV/B,OAAOqD,KAAKvC,KAAKwC,WACbpC,KAAM8B,EAAiB9B,OACvBG,KAAMD,QAQvB,GAEHkB,EAAKkB,UACN","file":"map.js","sourceRoot":"/js"}
|
||||
@@ -1,55 +1,2 @@
|
||||
window.MsgWorker = class MessageWorker {
|
||||
|
||||
constructor(cmd){
|
||||
/**
|
||||
* "command" type (identifies this message)
|
||||
*/
|
||||
this.cmd = cmd;
|
||||
|
||||
/**
|
||||
* "task" what should be done with this message
|
||||
* @type {string}
|
||||
*/
|
||||
this.msgTask = '';
|
||||
|
||||
/**
|
||||
* "message" meta data (e.g. error/close data from WebSocket
|
||||
* @type {null}
|
||||
*/
|
||||
this.msgMeta = null;
|
||||
|
||||
/**
|
||||
* "message" body (load)
|
||||
* @type {null}
|
||||
*/
|
||||
this.msgBody = null;
|
||||
}
|
||||
|
||||
get command(){
|
||||
return this.cmd;
|
||||
}
|
||||
|
||||
task(task){
|
||||
if(task){
|
||||
this.msgTask = task;
|
||||
}
|
||||
|
||||
return this.msgTask;
|
||||
}
|
||||
|
||||
meta(metaData){
|
||||
if(metaData){
|
||||
this.msgMeta = metaData;
|
||||
}
|
||||
|
||||
return this.msgMeta;
|
||||
}
|
||||
|
||||
data(data){
|
||||
if(data){
|
||||
this.msgBody = data;
|
||||
}
|
||||
|
||||
return this.msgBody;
|
||||
}
|
||||
};
|
||||
window.MsgWorker=class MessageWorker{constructor(s){this.cmd=s,this.msgTask="",this.msgMeta=null,this.msgBody=null}get command(){return this.cmd}task(s){return s&&(this.msgTask=s),this.msgTask}meta(s){return s&&(this.msgMeta=s),this.msgMeta}data(s){return s&&(this.msgBody=s),this.msgBody}};
|
||||
//# sourceMappingURL=message.js.map
|
||||
|
||||
1
public/js/v2.0.0/app/worker/message.js.br
Normal file
1
public/js/v2.0.0/app/worker/message.js.br
Normal file
@@ -0,0 +1 @@
|
||||
G`Ĵ9<C4B4>F<EFBFBD>0~y<><18>|uK<75>v0․<30><EFBFBD>K<17><>u<EFBFBD>p<1C><><EFBFBD>C<>(<28>}$<24>(<28>=<3D>\4<>O<EFBFBD>'<27><><EFBFBD><EFBFBD>3/ݟ<05><>U<EFBFBD><55>A<EFBFBD>S?1<><31><EFBFBD>)<29>LFj%A<>k<EFBFBD><6B><04>#<23>e<>l.<2E>3<EFBFBD><33>-<0C>0<0C><> <09><><0F>#<23>J2R<32><52><EFBFBD><EFBFBD>Vt<56>m+s$<24>P=@<40>k<EFBFBD><6B>)c<19>l<><6C>>
|
||||
1
public/js/v2.0.0/app/worker/message.js.map
Normal file
1
public/js/v2.0.0/app/worker/message.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["app/worker/message.js"],"names":["window","MsgWorker","MessageWorker","[object Object]","cmd","this","msgTask","msgMeta","msgBody","command","task","metaData","data"],"mappings":"AAAAA,OAAOC,gBAAkBC,cAErBC,YAAYC,GAIRC,KAAKD,IAAMA,EAMXC,KAAKC,QAAU,GAMfD,KAAKE,QAAU,KAMfF,KAAKG,QAAU,KAGnBC,cACI,OAAOJ,KAAKD,IAGhBD,KAAKO,GAKD,OAJGA,IACCL,KAAKC,QAAUI,GAGZL,KAAKC,QAGhBH,KAAKQ,GAKD,OAJGA,IACCN,KAAKE,QAAUI,GAGZN,KAAKE,QAGhBJ,KAAKS,GAKD,OAJGA,IACCP,KAAKG,QAAUI,GAGZP,KAAKG","file":"message.js","sourceRoot":"/js"}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,190 +0,0 @@
|
||||
/*
|
||||
* blueimp helper JS
|
||||
* https://github.com/blueimp/Gallery
|
||||
*
|
||||
* Copyright 2013, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* global define, window, document */
|
||||
|
||||
;(function () {
|
||||
'use strict'
|
||||
|
||||
function extend (obj1, obj2) {
|
||||
var prop
|
||||
for (prop in obj2) {
|
||||
if (obj2.hasOwnProperty(prop)) {
|
||||
obj1[prop] = obj2[prop]
|
||||
}
|
||||
}
|
||||
return obj1
|
||||
}
|
||||
|
||||
function Helper (query) {
|
||||
if (!this || this.find !== Helper.prototype.find) {
|
||||
// Called as function instead of as constructor,
|
||||
// so we simply return a new instance:
|
||||
return new Helper(query)
|
||||
}
|
||||
this.length = 0
|
||||
if (query) {
|
||||
if (typeof query === 'string') {
|
||||
query = this.find(query)
|
||||
}
|
||||
if (query.nodeType || query === query.window) {
|
||||
// Single HTML element
|
||||
this.length = 1
|
||||
this[0] = query
|
||||
} else {
|
||||
// HTML element collection
|
||||
var i = query.length
|
||||
this.length = i
|
||||
while (i) {
|
||||
i -= 1
|
||||
this[i] = query[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Helper.extend = extend
|
||||
|
||||
Helper.contains = function (container, element) {
|
||||
do {
|
||||
element = element.parentNode
|
||||
if (element === container) {
|
||||
return true
|
||||
}
|
||||
} while (element)
|
||||
return false
|
||||
}
|
||||
|
||||
Helper.parseJSON = function (string) {
|
||||
return window.JSON && JSON.parse(string)
|
||||
}
|
||||
|
||||
extend(Helper.prototype, {
|
||||
find: function (query) {
|
||||
var container = this[0] || document
|
||||
if (typeof query === 'string') {
|
||||
if (container.querySelectorAll) {
|
||||
query = container.querySelectorAll(query)
|
||||
} else if (query.charAt(0) === '#') {
|
||||
query = container.getElementById(query.slice(1))
|
||||
} else {
|
||||
query = container.getElementsByTagName(query)
|
||||
}
|
||||
}
|
||||
return new Helper(query)
|
||||
},
|
||||
|
||||
hasClass: function (className) {
|
||||
if (!this[0]) {
|
||||
return false
|
||||
}
|
||||
return new RegExp('(^|\\s+)' + className +
|
||||
'(\\s+|$)').test(this[0].className)
|
||||
},
|
||||
|
||||
addClass: function (className) {
|
||||
var i = this.length
|
||||
var element
|
||||
while (i) {
|
||||
i -= 1
|
||||
element = this[i]
|
||||
if (!element.className) {
|
||||
element.className = className
|
||||
return this
|
||||
}
|
||||
if (this.hasClass(className)) {
|
||||
return this
|
||||
}
|
||||
element.className += ' ' + className
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
removeClass: function (className) {
|
||||
var regexp = new RegExp('(^|\\s+)' + className + '(\\s+|$)')
|
||||
var i = this.length
|
||||
var element
|
||||
while (i) {
|
||||
i -= 1
|
||||
element = this[i]
|
||||
element.className = element.className.replace(regexp, ' ')
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
on: function (eventName, handler) {
|
||||
var eventNames = eventName.split(/\s+/)
|
||||
var i
|
||||
var element
|
||||
while (eventNames.length) {
|
||||
eventName = eventNames.shift()
|
||||
i = this.length
|
||||
while (i) {
|
||||
i -= 1
|
||||
element = this[i]
|
||||
if (element.addEventListener) {
|
||||
element.addEventListener(eventName, handler, false)
|
||||
} else if (element.attachEvent) {
|
||||
element.attachEvent('on' + eventName, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
off: function (eventName, handler) {
|
||||
var eventNames = eventName.split(/\s+/)
|
||||
var i
|
||||
var element
|
||||
while (eventNames.length) {
|
||||
eventName = eventNames.shift()
|
||||
i = this.length
|
||||
while (i) {
|
||||
i -= 1
|
||||
element = this[i]
|
||||
if (element.removeEventListener) {
|
||||
element.removeEventListener(eventName, handler, false)
|
||||
} else if (element.detachEvent) {
|
||||
element.detachEvent('on' + eventName, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
empty: function () {
|
||||
var i = this.length
|
||||
var element
|
||||
while (i) {
|
||||
i -= 1
|
||||
element = this[i]
|
||||
while (element.hasChildNodes()) {
|
||||
element.removeChild(element.lastChild)
|
||||
}
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
first: function () {
|
||||
return new Helper(this[0])
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(function () {
|
||||
return Helper
|
||||
})
|
||||
} else {
|
||||
window.blueimp = window.blueimp || {}
|
||||
window.blueimp.helper = Helper
|
||||
}
|
||||
}())
|
||||
6
public/js/v2.0.0/lib/bootbox.min.js
vendored
6
public/js/v2.0.0/lib/bootbox.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
89
public/js/v2.0.0/lib/bootstrap-image-gallery.js
vendored
89
public/js/v2.0.0/lib/bootstrap-image-gallery.js
vendored
@@ -1,87 +1,2 @@
|
||||
/*
|
||||
* Bootstrap Image Gallery
|
||||
* https://github.com/blueimp/Bootstrap-Image-Gallery
|
||||
*
|
||||
* Copyright 2013, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/*global define, window */
|
||||
|
||||
;(function (factory) {
|
||||
'use strict'
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([
|
||||
'jquery',
|
||||
'blueImpGallery'
|
||||
], factory)
|
||||
} else {
|
||||
factory(
|
||||
window.jQuery,
|
||||
window.blueimp.Gallery
|
||||
)
|
||||
}
|
||||
}(function ($, Gallery) {
|
||||
'use strict'
|
||||
|
||||
$.extend(Gallery.prototype.options, {
|
||||
useBootstrapModal: true
|
||||
})
|
||||
|
||||
var close = Gallery.prototype.close
|
||||
var imageFactory = Gallery.prototype.imageFactory
|
||||
var videoFactory = Gallery.prototype.videoFactory
|
||||
var textFactory = Gallery.prototype.textFactory
|
||||
|
||||
$.extend(Gallery.prototype, {
|
||||
modalFactory: function (obj, callback, factoryInterface, factory) {
|
||||
if (!this.options.useBootstrapModal || factoryInterface) {
|
||||
return factory.call(this, obj, callback, factoryInterface)
|
||||
}
|
||||
var that = this
|
||||
var modalTemplate = $(this.container).children('.modal')
|
||||
var modal = modalTemplate.clone().css('display', 'block').on('click', function (event) {
|
||||
//var modal = modalTemplate.clone().show().on('click', function (event) {
|
||||
// Close modal if click is outside of modal-content:
|
||||
if (event.target === modal[0] ||
|
||||
event.target === modal.children()[0]) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
that.close()
|
||||
}
|
||||
})
|
||||
var element = factory.call(this, obj, function (event) {
|
||||
callback({
|
||||
type: event.type,
|
||||
target: modal[0]
|
||||
})
|
||||
modal.addClass('in')
|
||||
}, factoryInterface)
|
||||
modal.find('.modal-title').text(element.title || String.fromCharCode(160))
|
||||
modal.find('.modal-body').append(element)
|
||||
|
||||
return modal[0]
|
||||
},
|
||||
|
||||
imageFactory: function (obj, callback, factoryInterface) {
|
||||
return this.modalFactory(obj, callback, factoryInterface, imageFactory)
|
||||
},
|
||||
|
||||
videoFactory: function (obj, callback, factoryInterface) {
|
||||
return this.modalFactory(obj, callback, factoryInterface, videoFactory)
|
||||
},
|
||||
|
||||
textFactory: function (obj, callback, factoryInterface) {
|
||||
return this.modalFactory(obj, callback, factoryInterface, textFactory)
|
||||
},
|
||||
|
||||
close: function () {
|
||||
this.container.find('.modal').removeClass('in')
|
||||
close.call(this)
|
||||
}
|
||||
|
||||
})
|
||||
}))
|
||||
!function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery","blueImpGallery"],t):t(window.jQuery,window.blueimp.Gallery)}(function(t,o){"use strict";t.extend(o.prototype.options,{useBootstrapModal:!0});var e=o.prototype.close,n=o.prototype.imageFactory,i=o.prototype.videoFactory,r=o.prototype.textFactory;t.extend(o.prototype,{modalFactory:function(o,e,n,i){if(!this.options.useBootstrapModal||n)return i.call(this,o,e,n);var r=this,a=t(this.container).children(".modal").clone().css("display","block").on("click",function(t){t.target!==a[0]&&t.target!==a.children()[0]||(t.preventDefault(),t.stopPropagation(),r.close())}),c=i.call(this,o,function(t){e({type:t.type,target:a[0]}),a.addClass("in")},n);return a.find(".modal-title").text(c.title||String.fromCharCode(160)),a.find(".modal-body").append(c),a[0]},imageFactory:function(t,o,e){return this.modalFactory(t,o,e,n)},videoFactory:function(t,o,e){return this.modalFactory(t,o,e,i)},textFactory:function(t,o,e){return this.modalFactory(t,o,e,r)},close:function(){this.container.find(".modal").removeClass("in"),e.call(this)}})});
|
||||
//# sourceMappingURL=bootstrap-image-gallery.js.map
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user