- v1.5.4 JS/CSS build files

- upgraded [_jQuery_](https://jquery.com) js lib `v3.3.1` → `v3.4.1`
This commit is contained in:
Mark Friedrich
2019-07-28 14:53:45 +02:00
parent dc319a4461
commit c27fd899b2
156 changed files with 55729 additions and 57 deletions

View File

@@ -13,8 +13,8 @@ NAME = Pathfinder
; Version is used for CSS/JS cache busting and is part of the URL for static resources:
; e.g. public/js/vX.X.X/app.js
; Syntax: String (current version)
; Default: v1.5.0
VERSION = v1.5.3
; Default: v1.5.4
VERSION = v1.5.4
; Contact information [optional]
; Shown on 'licence', 'contact' page.

13
composer.lock generated
View File

@@ -689,16 +689,16 @@
},
{
"name": "egulias/email-validator",
"version": "2.1.9",
"version": "2.1.10",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "128cc721d771ec2c46ce59698f4ca42b73f71b25"
"reference": "a6c8d7101b19a451c1707b1b79bbbc56e4bdb7ec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/128cc721d771ec2c46ce59698f4ca42b73f71b25",
"reference": "128cc721d771ec2c46ce59698f4ca42b73f71b25",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/a6c8d7101b19a451c1707b1b79bbbc56e4bdb7ec",
"reference": "a6c8d7101b19a451c1707b1b79bbbc56e4bdb7ec",
"shasum": ""
},
"require": {
@@ -708,7 +708,8 @@
"require-dev": {
"dominicsayers/isemail": "dev-master",
"phpunit/phpunit": "^4.8.35||^5.7||^6.0",
"satooshi/php-coveralls": "^1.0.1"
"satooshi/php-coveralls": "^1.0.1",
"symfony/phpunit-bridge": "^4.4@dev"
},
"suggest": {
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
@@ -742,7 +743,7 @@
"validation",
"validator"
],
"time": "2019-06-23T10:14:27+00:00"
"time": "2019-07-19T20:52:08+00:00"
},
{
"name": "evenement/evenement",

View File

@@ -27,7 +27,7 @@ requirejs.config({
admin: './app/admin', // initial start "admin page" view
notification: './app/notification', // "notification" view
jquery: 'lib/jquery-3.3.1.min', // v3.3.1 jQuery
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

File diff suppressed because one or more lines are too long

2
js/lib/jquery-3.4.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -1,2 +0,0 @@
"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",notification:"./app/notification",jquery:"lib/jquery-3.3.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",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",dragToSelect:"lib/jquery.dragToSelect",hoverIntent:"lib/jquery.hoverIntent.min",select2:"lib/select2.min",validator:"lib/validator.min",lazylinepainter:"lib/jquery.lazylinepainter-1.5.1.min",blueImpGallery:"lib/blueimp-gallery",blueImpGalleryHelper:"lib/blueimp-helper",blueImpGalleryBootstrap:"lib/bootstrap-image-gallery",bootstrapConfirmation:"lib/bootstrap-confirmation",bootstrapToggle:"lib/bootstrap-toggle.min",lazyload:"lib/jquery.lazyload.min",sortable:"lib/sortable.min","summernote.loader":"./app/summernote.loader",summernote:"lib/summernote/summernote.min",easePack:"lib/EasePack.min",tweenLite:"lib/TweenLite.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-select":"lib/datatables/Select-1.3.0/js/dataTables.select.min","datatables.plugins.render.ellipsis":"lib/datatables/plugins/render/ellipsis",pnotify:"lib/pnotify/pnotify","pnotify.buttons":"lib/pnotify/pnotify.buttons","pnotify.confirm":"lib/pnotify/pnotify.confirm","pnotify.nonblock":"lib/pnotify/pnotify.nonblock","pnotify.desktop":"lib/pnotify/pnotify.desktop","pnotify.history":"lib/pnotify/pnotify.history","pnotify.callbacks":"lib/pnotify/pnotify.callbacks","pnotify.reference":"lib/pnotify/pnotify.reference"},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-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}},pnotify:{deps:["jquery"]},easyPieChart:{deps:["jquery"]},peityInlineChart:{deps:["jquery"]},dragToSelect:{deps:["jquery"]},hoverIntent:{deps:["jquery"]},select2:{deps:["jquery","mousewheel"],exports:"Select2"},validator:{deps:["jquery","bootstrap"]},lazylinepainter:{deps:["jquery","bootstrap"]},blueImpGallery:{deps:["jquery"]},bootstrapConfirmation:{deps:["bootstrap"]},bootstrapToggle:{deps:["jquery"]},lazyload:{deps:["jquery"]},summernote:{deps:["jquery"]}}}),require.config({baseUrl:jsBaseUrl}),requirejs([mainScriptPath]);
//# sourceMappingURL=app.js.map

Binary file not shown.

View File

@@ -1 +0,0 @@
{"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","notification","jquery","bootstrap","text","mustache","localForage","velocity","velocityUI","slidebars","jsPlumb","farahey","customScrollbar","mousewheel","xEditable","morris","raphael","bootbox","easyPieChart","peityInlineChart","dragToSelect","hoverIntent","select2","validator","lazylinepainter","blueImpGallery","blueImpGalleryHelper","blueImpGalleryBootstrap","bootstrapConfirmation","bootstrapToggle","lazyload","sortable","summernote.loader","summernote","easePack","tweenLite","datatables.loader","datatables.net","datatables.net-buttons","datatables.net-buttons-html","datatables.net-responsive","datatables.net-select","datatables.plugins.render.ellipsis","pnotify","pnotify.buttons","pnotify.confirm","pnotify.nonblock","pnotify.desktop","pnotify.history","pnotify.callbacks","pnotify.reference","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,cACPC,aAAc,qBAEdC,OAAQ,uBACRC,UAAW,oBACXC,KAAM,qBACNC,SAAU,mBACVC,YAAa,sBACbC,SAAU,mBACVC,WAAY,sBACZC,UAAW,gBACXC,QAAS,cACTC,QAAS,cACTC,gBAAiB,kCACjBC,WAAY,4BACZC,UAAW,6BACXC,OAAQ,iBACRC,QAAS,kBACTC,QAAS,kBACTC,aAAc,8BACdC,iBAAkB,uBAClBC,aAAc,0BACdC,YAAa,6BACbC,QAAS,kBACTC,UAAW,oBACXC,gBAAiB,uCACjBC,eAAgB,sBAChBC,qBAAsB,qBACtBC,wBAAyB,8BACzBC,sBAAuB,6BACvBC,gBAAiB,2BACjBC,SAAU,0BACVC,SAAU,mBAEVC,oBAAqB,0BACrBC,WAAc,gCAGdC,SAAU,mBACVC,UAAW,oBAGXC,oBAAqB,0BACrBC,iBAAkB,6DAClBC,yBAA0B,yDAC1BC,8BAA+B,oDAC/BC,4BAA6B,+DAC7BC,wBAAyB,uDACzBC,qCAAsC,yCAGtCC,QAAS,sBACTC,kBAAmB,8BACnBC,kBAAmB,8BACnBC,mBAAoB,+BACpBC,kBAAmB,8BACnBC,kBAAmB,8BACnBC,oBAAqB,gCACrBC,oBAAqB,iCAEzBC,MACIhD,WACIiD,MAAO,WAEXzC,SACIyC,MAAO,YAEX7C,UACI6C,MAAO,WAEX5C,YACI4C,MAAO,aAEX3C,WACI2C,MAAO,WAEXxC,iBACIwC,MAAO,SAAU,eAErBhB,qBACIgB,MAAO,WAEXf,kBACIe,MAAO,WAEXd,0BACIc,MAAO,mBAEXb,+BACIa,MAAO,2BAEXZ,6BACIY,MAAO,mBAEXX,yBACIW,MAAO,mBAEXV,sCACIU,MAAO,mBAEXtC,WACIsC,MAAO,cAEXnC,SACImC,MAAO,SAAU,aACjBC,QAAS,WAEbtC,QACIqC,MAAO,SAAU,WACjBC,QAAS,SACTC,KAAM,SAAUC,EAAGC,GACfC,OAAOD,QAAUA,IAGzBb,SACIS,MAAO,WAEXlC,cACIkC,MAAO,WAEXjC,kBACIiC,MAAO,WAEXhC,cACIgC,MAAO,WAEX/B,aACI+B,MAAO,WAEX9B,SACI8B,MAAO,SAAU,cACjBC,QAAS,WAEb9B,WACI6B,MAAO,SAAU,cAErB5B,iBACI4B,MAAO,SAAU,cAErB3B,gBACI2B,MAAO,WAEXxB,uBACIwB,MAAO,cAEXvB,iBACIuB,MAAO,WAEXtB,UACIsB,MAAO,WAEXnB,YACImB,MAAO,cAQnBM,QAAQtE,QACJC,QAASH,YAIbC,WAAYL","file":"app.js","sourceRoot":"/js"}

File diff suppressed because one or more lines are too long

Binary file not shown.

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

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

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

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
define(["jquery","app/init","summernote"],(e,a)=>{"use strict";return e.extend(e.summernote.plugins,{lengthField:function(a){e.summernote.ui,a.memo("button.lengthField",()=>e("<kbd>",{class:["text-right","txt-color"].join(" ")}));let t=e=>{let t=a.options.maxTextLength,n=e.length,s=a.layoutInfo.toolbar.find("kbd"),f=t-n;s.text(f).data("charCount",f),s.toggleClass("txt-color-red",t<=n),a.layoutInfo.toolbar.find(".btn-save").prop("disabled",t<n)};this.events={"summernote.init":function(e,n){t(a.$note.summernote("code"))},"summernote.change":function(e,a){t(a)}}}}),e.extend(e.summernote.plugins,{discardBtn:function(a){let t=e.summernote.ui;a.memo("button.discardBtn",()=>{let n=t.button({contents:'<i class="fas fa-fw fa-ban"/>',container:"body",click:function(){e(this).confirmation("show")}}).render(),s={container:"body",placement:"top",btnCancelClass:"btn btn-sm btn-default",btnCancelLabel:"cancel",title:"discard changes",btnOkClass:"btn btn-sm btn-warning",btnOkLabel:"discard",btnOkIcon:"fas fa-fw fa-ban",onConfirm:(e,t)=>{a.$note.summernote("reset"),a.$note.summernote("destroy")}};return n.confirmation(s),n})}}),{initSummernote:(a,t)=>{t=e.extend({},{dialogsInBody:!0,dialogsFade:!0,styleTags:["p","h2","h3","blockquote"],linkTargetBlank:!0,tableClassName:"table table-condensed table-bordered",insertTableMaxSize:{col:5,row:5},icons:{alignCenter:"fas fa-align-center",alignJustify:"fas fa-align-justify",alignLeft:"fas fa-align-left",alignRight:"fas fa-align-right",indent:"fas fa-indent",outdent:"fas fa-outdent",arrowsAlt:"fas fa-expand-arrows-alt",bold:"fas fa-bold",caret:"fas fa-caret-down",circle:"fas fa-circle",close:"fas fa-time",code:"fas fa-code",eraser:"fas fa-eraser",font:"fas fa-font",italic:"fas fa-italic",link:"fas fa-link",unlink:"fas fa-unlink",magic:"fas fa-magic",menuCheck:"fas fa-check",minus:"fas fa-minus",orderedlist:"fas fa-list-ol",pencil:"fa-pen",picture:"fas fa-image",question:"fas fa-question",redo:"fas fa-redo",square:"fas fa-square",strikethrough:"fas fa-strikethrough",subscript:"fas fa-subscript",superscript:"fas fa-superscript",table:"fas fa-table",textHeight:"fas fa-text-height",trash:"fas fa-trash",underline:"fas fa-underline",undo:"fas fa-undo",unorderedlist:"fas fa-list-ul",video:"fab fa-youtube"},colors:[["#5cb85c","#e28a0d","#d9534f","#e06fdf","#9fa8da","#e2ce48","#428bca"]],colorsName:[["Green","Orange","Red","Pink","Indigo","Yellow","Blue"]]},t),a.summernote(t)}}});
//# sourceMappingURL=summernote.loader.js.map

View File

@@ -1 +0,0 @@
{"version":3,"sources":["app/summernote.loader.js"],"names":["define","$","Init","extend","summernote","plugins","lengthField","context","ui","memo","class","join","updateCounter","contents","maxTextLength","options","textLength","length","counter","layoutInfo","toolbar","find","counterLeft","text","data","toggleClass","prop","this","events","summernote.init","we","e","$note","summernote.change","discardBtn","$button","button","container","click","confirmation","render","confirmationSettings","placement","btnCancelClass","btnCancelLabel","title","btnOkClass","btnOkLabel","btnOkIcon","onConfirm","target","initSummernote","element","dialogsInBody","dialogsFade","styleTags","linkTargetBlank","tableClassName","insertTableMaxSize","col","row","icons","alignCenter","alignJustify","alignLeft","alignRight","indent","outdent","arrowsAlt","bold","caret","circle","close","code","eraser","font","italic","link","unlink","magic","menuCheck","minus","orderedlist","pencil","picture","question","redo","square","strikethrough","subscript","superscript","table","textHeight","trash","underline","undo","unorderedlist","video","colors","colorsName"],"mappings":"AAAAA,QACI,SACA,WACA,cACD,CAACC,EAAGC,KACH,aAkLA,OA7KID,EAAEE,OAAOF,EAAEG,WAAWC,SAIlBC,YAAa,SAAUC,GAEVN,EAAEG,WAAWI,GAGtBD,EAAQE,KAAK,qBAAsB,IACxBR,EAAE,SACLS,OAAQ,aAAc,aAAaC,KAAK,QAQhD,IAAIC,EAAiBC,IACjB,IAAIC,EAAgBP,EAAQQ,QAAQD,cAChCE,EAAaH,EAASI,OACtBC,EAAUX,EAAQY,WAAWC,QAAQC,KAAK,OAC1CC,EAAcR,EAAgBE,EAElCE,EAAQK,KAAKD,GAAaE,KAAK,YAAaF,GAC5CJ,EAAQO,YAAY,gBAAiBX,GAAiBE,GAGxCT,EAAQY,WAAWC,QAAQC,KAAK,aACtCK,KAAK,WAAYZ,EAAgBE,IAI7CW,KAAKC,QACDC,kBAAmB,SAAUC,EAAIC,GAC7BnB,EAAcL,EAAQyB,MAAM5B,WAAW,UAE3C6B,oBAAqB,SAASH,EAAIjB,GAC9BD,EAAcC,QAQ9BZ,EAAEE,OAAOF,EAAEG,WAAWC,SAIlB6B,WAAY,SAAU3B,GAClB,IACIC,EAAKP,EAAEG,WAAWI,GAGtBD,EAAQE,KAAK,oBAAqB,KAC9B,IAQI0B,EARS3B,EAAG4B,QACZvB,SAAU,gCACVwB,UAAW,OACXC,MAAO,WAEHrC,EAAE0B,MAAMY,aAAa,WAGRC,SAGjBC,GACAJ,UAAW,OACXK,UAAW,MACXC,eAAgB,yBAChBC,eAAgB,SAChBC,MAAO,kBACPC,WAAY,yBACZC,WAAY,UACZC,UAAW,mBACXC,UAAW,CAAClB,EAAGmB,KAEX3C,EAAQyB,MAAM5B,WAAW,SACzBG,EAAQyB,MAAM5B,WAAW,aAKjC,OAFA+B,EAAQI,aAAaE,GAEdN,QAyFnBgB,eA9EiB,CAACC,EAASrC,KAsE3BA,EAAUd,EAAEE,WAnERkD,eAAe,EACfC,aAAa,EAKbC,WAAY,IAAK,KAAM,KAAM,cAC7BC,iBAAiB,EACjBC,eAAgB,uCAChBC,oBACIC,IAAK,EACLC,IAAK,GAETC,OAEIC,YAAe,sBACfC,aAAgB,uBAChBC,UAAa,oBACbC,WAAc,qBAOdC,OAAU,gBACVC,QAAW,iBACXC,UAAa,2BACbC,KAAQ,cACRC,MAAS,oBACTC,OAAU,gBACVC,MAAS,cACTC,KAAQ,cACRC,OAAU,gBACVC,KAAQ,cAERC,OAAU,gBACVC,KAAQ,cACRC,OAAU,gBACVC,MAAS,eACTC,UAAa,eACbC,MAAS,eACTC,YAAe,iBACfC,OAAU,SACVC,QAAW,eACXC,SAAY,kBACZC,KAAQ,cACRC,OAAU,gBACVC,cAAiB,uBACjBC,UAAa,mBACbC,YAAe,qBACfC,MAAS,eACTC,WAAc,qBACdC,MAAS,eACTC,UAAa,mBACbC,KAAQ,cACRC,cAAiB,iBACjBC,MAAS,kBAEbC,SACK,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,YAEvEC,aACK,QAAS,SAAU,MAAO,OAAQ,SAAU,SAAU,UAIxBpF,GAEvCqC,EAAQhD,WAAWW","file":"summernote.loader.js","sourceRoot":"/js"}

View File

@@ -1,2 +0,0 @@
"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

View File

@@ -1 +0,0 @@
{"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"}

View File

@@ -1,2 +0,0 @@
window.MsgWorker=class{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

View File

@@ -1 +0,0 @@
9<01>Ĵ9<C4B4>F<EFBFBD>0~y<><18>|uK<75>v0<76>Iw<49><77>.<2E>x<14><><EFBFBD><EFBFBD>(O<><4F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><12><>zʘL0<4C><05><><EFBFBD><EFBFBD>ů<EFBFBD>r<16>_D<5F>? <1<><31>P(<28>LFj<46><1C><><EFBFBD>H<EFBFBD><48><EFBFBD><EFBFBD><EFBFBD>26hL<68>3<EFBFBD><33>- <0C>0 +<2B> ;<3B><0F>#<23>dMFj=<3D><><EFBFBD><EFBFBD><EFBFBD>`+s$<24>P=.a<>veS<65>r<EFBFBD>z<EFBFBD><7A><EFBFBD>>

View File

@@ -1 +0,0 @@
{"version":3,"sources":["app/worker/message.js"],"names":["window","MsgWorker","[object Object]","cmd","this","msgTask","msgMeta","msgBody","command","task","metaData","data"],"mappings":"AAAAA,OAAOC,gBAEHC,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"}

View File

@@ -1,2 +0,0 @@
!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

View File

@@ -1 +0,0 @@
{"version":3,"sources":["lib/bootstrap-image-gallery.js"],"names":["factory","define","amd","window","jQuery","blueimp","Gallery","$","extend","prototype","options","useBootstrapModal","close","imageFactory","videoFactory","textFactory","modalFactory","obj","callback","factoryInterface","this","call","that","modal","container","children","clone","css","on","event","target","preventDefault","stopPropagation","element","type","addClass","find","text","title","String","fromCharCode","append","removeClass"],"mappings":"CAaE,SAAUA,GACR,aACsB,mBAAXC,QAAyBA,OAAOC,IACvCD,QACI,SACA,kBACDD,GAEHA,EACIG,OAAOC,OACPD,OAAOE,QAAQC,SAV1B,CAaC,SAAUC,EAAGD,GACX,aAEAC,EAAEC,OAAOF,EAAQG,UAAUC,SACvBC,mBAAmB,IAGvB,IAAIC,EAAQN,EAAQG,UAAUG,MAC1BC,EAAeP,EAAQG,UAAUI,aACjCC,EAAeR,EAAQG,UAAUK,aACjCC,EAAcT,EAAQG,UAAUM,YAEpCR,EAAEC,OAAOF,EAAQG,WACbO,aAAc,SAAUC,EAAKC,EAAUC,EAAkBnB,GACrD,IAAKoB,KAAKV,QAAQC,mBAAqBQ,EACnC,OAAOnB,EAAQqB,KAAKD,KAAMH,EAAKC,EAAUC,GAE7C,IAAIG,EAAOF,KAEPG,EADgBhB,EAAEa,KAAKI,WAAWC,SAAS,UACrBC,QAAQC,IAAI,UAAW,SAASC,GAAG,QAAS,SAAUC,GAGxEA,EAAMC,SAAWP,EAAM,IACvBM,EAAMC,SAAWP,EAAME,WAAW,KAClCI,EAAME,iBACNF,EAAMG,kBACNV,EAAKV,WAGTqB,EAAUjC,EAAQqB,KAAKD,KAAMH,EAAK,SAAUY,GAC5CX,GACIgB,KAAML,EAAMK,KACZJ,OAAQP,EAAM,KAElBA,EAAMY,SAAS,OAChBhB,GAIH,OAHAI,EAAMa,KAAK,gBAAgBC,KAAKJ,EAAQK,OAASC,OAAOC,aAAa,MACrEjB,EAAMa,KAAK,eAAeK,OAAOR,GAE1BV,EAAM,IAGjBV,aAAc,SAAUI,EAAKC,EAAUC,GACnC,OAAOC,KAAKJ,aAAaC,EAAKC,EAAUC,EAAkBN,IAG9DC,aAAc,SAAUG,EAAKC,EAAUC,GACnC,OAAOC,KAAKJ,aAAaC,EAAKC,EAAUC,EAAkBL,IAG9DC,YAAa,SAAUE,EAAKC,EAAUC,GAClC,OAAOC,KAAKJ,aAAaC,EAAKC,EAAUC,EAAkBJ,IAG9DH,MAAO,WACHQ,KAAKI,UAAUY,KAAK,UAAUM,YAAY,MAC1C9B,EAAMS,KAAKD","file":"bootstrap-image-gallery.js","sourceRoot":"/js"}

View File

@@ -1,2 +0,0 @@
!function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery"],e):jQuery&&!jQuery.fn.hoverIntent&&e(jQuery)}(function(e){"use strict";var t,n,i={interval:100,sensitivity:6,timeout:0},o=0,u=function(e){t=e.pageX,n=e.pageY},r=function(e,i,o,v){if(Math.sqrt((o.pX-t)*(o.pX-t)+(o.pY-n)*(o.pY-n))<v.sensitivity)return i.off(o.event,u),delete o.timeoutId,o.isActive=!0,e.pageX=t,e.pageY=n,delete o.pX,delete o.pY,v.over.apply(i[0],[e]);o.pX=t,o.pY=n,o.timeoutId=setTimeout(function(){r(e,i,o,v)},v.interval)};e.fn.hoverIntent=function(t,n,v){var a=o++,s=e.extend({},i);e.isPlainObject(t)?(s=e.extend(s,t),e.isFunction(s.out)||(s.out=s.over)):s=e.isFunction(n)?e.extend(s,{over:t,out:n,selector:v}):e.extend(s,{over:t,out:t,selector:n});var f=function(t){var n=e.extend({},t),i=e(this),o=i.data("hoverIntent");o||i.data("hoverIntent",o={});var v=o[a];v||(o[a]=v={id:a}),v.timeoutId&&(v.timeoutId=clearTimeout(v.timeoutId));var f=v.event="mousemove.hoverIntent.hoverIntent"+a;if("mouseenter"===t.type){if(v.isActive)return;v.pX=n.pageX,v.pY=n.pageY,i.off(f,u).on(f,u),v.timeoutId=setTimeout(function(){r(n,i,v,s)},s.interval)}else{if(!v.isActive)return;i.off(f,u),v.timeoutId=setTimeout(function(){!function(e,t,n,i){delete t.data("hoverIntent")[n.id],i.apply(t[0],[e])}(n,i,v,s.out)},s.timeout)}};return this.on({"mouseenter.hoverIntent":f,"mouseleave.hoverIntent":f},s.selector)}});
//# sourceMappingURL=jquery.hoverIntent.minified.js.map

View File

@@ -1 +0,0 @@
{"version":3,"sources":["lib/jquery.hoverIntent.minified.js"],"names":["factory","define","amd","jQuery","fn","hoverIntent","$","cX","cY","_cfg","interval","sensitivity","timeout","INSTANCE_COUNT","track","ev","pageX","pageY","compare","$el","s","cfg","Math","sqrt","pX","pY","off","event","timeoutId","isActive","over","apply","setTimeout","handlerIn","handlerOut","selector","instanceId","extend","isPlainObject","isFunction","out","handleHover","e","this","hoverIntentData","data","state","id","clearTimeout","mousemove","type","on","delay","mouseenter.hoverIntent","mouseleave.hoverIntent"],"mappings":"CAQC,SAASA,GAAS,aAAa,mBAAmBC,QAAQA,OAAOC,IAAID,QAAQ,UAAUD,GAASG,SAASA,OAAOC,GAAGC,aAAaL,EAAQG,QAAxI,CAAiJ,SAASG,GAAG,aAAa,IAAIC,EAAGC,EAAGC,GAAMC,SAAS,IAAIC,YAAY,EAAEC,QAAQ,GAAGC,EAAe,EAAEC,EAAM,SAASC,GAAIR,EAAGQ,EAAGC,MAAMR,EAAGO,EAAGE,OAAOC,EAAQ,SAASH,EAAGI,EAAIC,EAAEC,GAAK,GAAGC,KAAKC,MAAMH,EAAEI,GAAGjB,IAAKa,EAAEI,GAAGjB,IAAKa,EAAEK,GAAGjB,IAAKY,EAAEK,GAAGjB,IAAKa,EAAIV,YAAY,OAAOQ,EAAIO,IAAIN,EAAEO,MAAMb,UAAcM,EAAEQ,UAAUR,EAAES,UAAS,EAAGd,EAAGC,MAAMT,EAAGQ,EAAGE,MAAMT,SAAUY,EAAEI,UAAUJ,EAAEK,GAAGJ,EAAIS,KAAKC,MAAMZ,EAAI,IAAIJ,IAAKK,EAAEI,GAAGjB,EAAGa,EAAEK,GAAGjB,EAAGY,EAAEQ,UAAUI,WAAW,WAAWd,EAAQH,EAAGI,EAAIC,EAAEC,IAAMA,EAAIX,WAA4GJ,EAAEF,GAAGC,YAAY,SAAS4B,EAAUC,EAAWC,GAAU,IAAIC,EAAWvB,IAAiBQ,EAAIf,EAAE+B,UAAU5B,GAAMH,EAAEgC,cAAcL,IAAYZ,EAAIf,EAAE+B,OAAOhB,EAAIY,GAAW3B,EAAEiC,WAAWlB,EAAImB,OAAOnB,EAAImB,IAAInB,EAAIS,OAAOT,EAAIf,EAAEiC,WAAWL,GAAY5B,EAAE+B,OAAOhB,GAAKS,KAAKG,EAAUO,IAAIN,EAAWC,SAASA,IAAW7B,EAAE+B,OAAOhB,GAAKS,KAAKG,EAAUO,IAAIP,EAAUE,SAASD,IAAa,IAAIO,EAAY,SAASC,GAAG,IAAI3B,EAAGT,EAAE+B,UAAUK,GAAGvB,EAAIb,EAAEqC,MAAMC,EAAgBzB,EAAI0B,KAAK,eAAeD,GAAiBzB,EAAI0B,KAAK,cAAcD,MAAoB,IAAIE,EAAMF,EAAgBR,GAAYU,IAAQF,EAAgBR,GAAYU,GAAOC,GAAGX,IAAaU,EAAMlB,YAAYkB,EAAMlB,UAAUoB,aAAaF,EAAMlB,YAAY,IAAIqB,EAAUH,EAAMnB,MAAM,oCAAoCS,EAAW,GAAG,eAAeM,EAAEQ,KAAK,CAAC,GAAGJ,EAAMjB,SAAS,OAAOiB,EAAMtB,GAAGT,EAAGC,MAAM8B,EAAMrB,GAAGV,EAAGE,MAAME,EAAIO,IAAIuB,EAAUnC,GAAOqC,GAAGF,EAAUnC,GAAOgC,EAAMlB,UAAUI,WAAW,WAAWd,EAAQH,EAAGI,EAAI2B,EAAMzB,IAAMA,EAAIX,cAAc,CAAC,IAAIoC,EAAMjB,SAAS,OAAOV,EAAIO,IAAIuB,EAAUnC,GAAOgC,EAAMlB,UAAUI,WAAW,YAA9nC,SAASjB,EAAGI,EAAIC,EAAEoB,UAAmBrB,EAAI0B,KAAK,eAAezB,EAAE2B,IAAIP,EAAIT,MAAMZ,EAAI,IAAIJ,IAAojCqC,CAAMrC,EAAGI,EAAI2B,EAAMzB,EAAImB,MAAMnB,EAAIT,WAAW,OAAO+B,KAAKQ,IAAIE,yBAAyBZ,EAAYa,yBAAyBb,GAAapB,EAAIc","file":"jquery.hoverIntent.minified.js","sourceRoot":"/js"}

View File

@@ -1,2 +0,0 @@
!function(o,s){"function"==typeof define&&define.amd?define("pnotify.buttons",["jquery","pnotify"],s):"object"==typeof exports&&"undefined"!=typeof module?module.exports=s(require("jquery"),require("./pnotify")):s(o.jQuery,o.PNotify)}("undefined"!=typeof window?window:this,function(o,s){return s.prototype.options.buttons={closer:!0,closer_hover:!0,sticker:!0,sticker_hover:!0,show_on_nonblock:!1,labels:{close:"Close",stick:"Stick",unstick:"Unstick"},classes:{closer:null,pin_up:null,pin_down:null}},s.prototype.modules.buttons={init:function(s,i){var n=this;s.elem.on({mouseenter:function(o){!n.options.sticker||s.options.nonblock&&s.options.nonblock.nonblock&&!n.options.show_on_nonblock||n.sticker.trigger("pnotify:buttons:toggleStick").css("visibility","visible"),!n.options.closer||s.options.nonblock&&s.options.nonblock.nonblock&&!n.options.show_on_nonblock||n.closer.css("visibility","visible")},mouseleave:function(o){n.options.sticker_hover&&n.sticker.css("visibility","hidden"),n.options.closer_hover&&n.closer.css("visibility","hidden")}}),this.sticker=o("<div />",{class:"ui-pnotify-sticker","aria-role":"button","aria-pressed":s.options.hide?"false":"true",tabindex:"0",title:s.options.hide?i.labels.stick:i.labels.unstick,css:{cursor:"pointer",visibility:i.sticker_hover?"hidden":"visible"},click:function(){s.options.hide=!s.options.hide,s.options.hide?s.queueRemove():s.cancelRemove(),o(this).trigger("pnotify:buttons:toggleStick")}}).bind("pnotify:buttons:toggleStick",function(){var i=null===n.options.classes.pin_up?s.styles.pin_up:n.options.classes.pin_up,e=null===n.options.classes.pin_down?s.styles.pin_down:n.options.classes.pin_down;o(this).attr("title",s.options.hide?n.options.labels.stick:n.options.labels.unstick).children().attr("class","").addClass(s.options.hide?i:e).attr("aria-pressed",s.options.hide?"false":"true")}).append("<span />").trigger("pnotify:buttons:toggleStick").prependTo(s.container),(!i.sticker||s.options.nonblock&&s.options.nonblock.nonblock&&!i.show_on_nonblock)&&this.sticker.css("display","none"),this.closer=o("<div />",{class:"ui-pnotify-closer","aria-role":"button",tabindex:"0",title:i.labels.close,css:{cursor:"pointer",visibility:i.closer_hover?"hidden":"visible"},click:function(){s.remove(!1),n.sticker.css("visibility","hidden"),n.closer.css("visibility","hidden")}}).append(o("<span />",{class:null===i.classes.closer?s.styles.closer:i.classes.closer})).prependTo(s.container),(!i.closer||s.options.nonblock&&s.options.nonblock.nonblock&&!i.show_on_nonblock)&&this.closer.css("display","none")},update:function(o,s){!s.closer||o.options.nonblock&&o.options.nonblock.nonblock&&!s.show_on_nonblock?this.closer.css("display","none"):s.closer&&this.closer.css("display","block"),!s.sticker||o.options.nonblock&&o.options.nonblock.nonblock&&!s.show_on_nonblock?this.sticker.css("display","none"):s.sticker&&this.sticker.css("display","block"),this.sticker.trigger("pnotify:buttons:toggleStick"),this.closer.find("span").attr("class","").addClass(null===s.classes.closer?o.styles.closer:s.classes.closer),s.sticker_hover?this.sticker.css("visibility","hidden"):o.options.nonblock&&o.options.nonblock.nonblock&&!s.show_on_nonblock||this.sticker.css("visibility","visible"),s.closer_hover?this.closer.css("visibility","hidden"):o.options.nonblock&&o.options.nonblock.nonblock&&!s.show_on_nonblock||this.closer.css("visibility","visible")}},o.extend(s.styling.brighttheme,{closer:"brighttheme-icon-closer",pin_up:"brighttheme-icon-sticker",pin_down:"brighttheme-icon-sticker brighttheme-icon-stuck"}),o.extend(s.styling.bootstrap3,{closer:"glyphicon glyphicon-remove",pin_up:"glyphicon glyphicon-pause",pin_down:"glyphicon glyphicon-play"}),o.extend(s.styling.fontawesome,{closer:"fa fa-times",pin_up:"fa fa-pause",pin_down:"fa fa-play"}),s});
//# sourceMappingURL=pnotify.buttons.js.map

View File

@@ -1 +0,0 @@
{"version":3,"sources":["lib/pnotify/pnotify.buttons.js"],"names":["root","factory","define","amd","exports","module","require","jQuery","PNotify","window","this","$","prototype","options","buttons","closer","closer_hover","sticker","sticker_hover","show_on_nonblock","labels","close","stick","unstick","classes","pin_up","pin_down","modules","init","notice","that","elem","on","mouseenter","e","nonblock","trigger","css","mouseleave","class","aria-role","aria-pressed","hide","tabindex","title","cursor","visibility","click","queueRemove","cancelRemove","bind","styles","attr","children","addClass","append","prependTo","container","remove","update","find","extend","styling","brighttheme","bootstrap3","fontawesome"],"mappings":"CACC,SAAUA,EAAMC,GACO,mBAAXC,QAAyBA,OAAOC,IAEzCD,OAAO,mBAAoB,SAAU,WAAYD,GACrB,iBAAZG,SAA0C,oBAAXC,OAE/CA,OAAOD,QAAUH,EAAQK,QAAQ,UAAWA,QAAQ,cAGpDL,EAAQD,EAAKO,OAAQP,EAAKQ,SAT9B,CAWoB,oBAAXC,OAAyBA,OAASC,KAAM,SAASC,EAAGH,GAsJ3D,OArJAA,EAAQI,UAAUC,QAAQC,SAExBC,QAAQ,EAERC,cAAc,EAEdC,SAAS,EAETC,eAAe,EAEfC,kBAAkB,EAElBC,QACEC,MAAO,QACPC,MAAO,QACPC,QAAS,WAGXC,SACET,OAAQ,KACRU,OAAQ,KACRC,SAAU,OAGdlB,EAAQI,UAAUe,QAAQb,SACxBc,KAAM,SAASC,EAAQhB,GACrB,IAAIiB,EAAOpB,KACXmB,EAAOE,KAAKC,IACVC,WAAc,SAASC,IAEjBJ,EAAKjB,QAAQI,SAAcY,EAAOhB,QAAQsB,UAAYN,EAAOhB,QAAQsB,SAASA,WAAaL,EAAKjB,QAAQM,kBAC1GW,EAAKb,QAAQmB,QAAQ,+BAA+BC,IAAI,aAAc,YAEpEP,EAAKjB,QAAQE,QAAac,EAAOhB,QAAQsB,UAAYN,EAAOhB,QAAQsB,SAASA,WAAaL,EAAKjB,QAAQM,kBACzGW,EAAKf,OAAOsB,IAAI,aAAc,YAGlCC,WAAc,SAASJ,GAEjBJ,EAAKjB,QAAQK,eACfY,EAAKb,QAAQoB,IAAI,aAAc,UAE7BP,EAAKjB,QAAQG,cACfc,EAAKf,OAAOsB,IAAI,aAAc,aAMpC3B,KAAKO,QAAUN,EAAE,WACf4B,MAAS,qBACTC,YAAa,SACbC,eAAgBZ,EAAOhB,QAAQ6B,KAAO,QAAU,OAChDC,SAAY,IACZC,MAASf,EAAOhB,QAAQ6B,KAAO7B,EAAQO,OAAOE,MAAQT,EAAQO,OAAOG,QACrEc,KACEQ,OAAU,UACVC,WAAcjC,EAAQK,cAAgB,SAAW,WAEnD6B,MAAS,WACPlB,EAAOhB,QAAQ6B,MAAQb,EAAOhB,QAAQ6B,KAClCb,EAAOhB,QAAQ6B,KACjBb,EAAOmB,cAEPnB,EAAOoB,eAETtC,EAAED,MAAM0B,QAAQ,kCAGnBc,KAAK,8BAA+B,WACnC,IAAIzB,EAAyC,OAAhCK,EAAKjB,QAAQW,QAAQC,OAAkBI,EAAOsB,OAAO1B,OAASK,EAAKjB,QAAQW,QAAQC,OAC5FC,EAA6C,OAAlCI,EAAKjB,QAAQW,QAAQE,SAAoBG,EAAOsB,OAAOzB,SAAWI,EAAKjB,QAAQW,QAAQE,SACtGf,EAAED,MACD0C,KAAK,QAASvB,EAAOhB,QAAQ6B,KAAOZ,EAAKjB,QAAQO,OAAOE,MAAQQ,EAAKjB,QAAQO,OAAOG,SACpF8B,WACAD,KAAK,QAAS,IACdE,SAASzB,EAAOhB,QAAQ6B,KAAOjB,EAASC,GACxC0B,KAAK,eAAgBvB,EAAOhB,QAAQ6B,KAAO,QAAU,UAEvDa,OAAO,YACPnB,QAAQ,+BACRoB,UAAU3B,EAAO4B,aACb5C,EAAQI,SAAYY,EAAOhB,QAAQsB,UAAYN,EAAOhB,QAAQsB,SAASA,WAAatB,EAAQM,mBAC/FT,KAAKO,QAAQoB,IAAI,UAAW,QAI9B3B,KAAKK,OAASJ,EAAE,WACd4B,MAAS,oBACTC,YAAa,SACbG,SAAY,IACZC,MAAS/B,EAAQO,OAAOC,MACxBgB,KAAQQ,OAAU,UAAWC,WAAcjC,EAAQG,aAAe,SAAW,WAC7E+B,MAAS,WACPlB,EAAO6B,QAAO,GACd5B,EAAKb,QAAQoB,IAAI,aAAc,UAC/BP,EAAKf,OAAOsB,IAAI,aAAc,aAGjCkB,OAAO5C,EAAE,YAAa4B,MAAoC,OAA3B1B,EAAQW,QAAQT,OAAkBc,EAAOsB,OAAOpC,OAASF,EAAQW,QAAQT,UACxGyC,UAAU3B,EAAO4B,aACb5C,EAAQE,QAAWc,EAAOhB,QAAQsB,UAAYN,EAAOhB,QAAQsB,SAASA,WAAatB,EAAQM,mBAC9FT,KAAKK,OAAOsB,IAAI,UAAW,SAG/BsB,OAAQ,SAAS9B,EAAQhB,IAElBA,EAAQE,QAAWc,EAAOhB,QAAQsB,UAAYN,EAAOhB,QAAQsB,SAASA,WAAatB,EAAQM,iBAC9FT,KAAKK,OAAOsB,IAAI,UAAW,QAClBxB,EAAQE,QACjBL,KAAKK,OAAOsB,IAAI,UAAW,UAExBxB,EAAQI,SAAYY,EAAOhB,QAAQsB,UAAYN,EAAOhB,QAAQsB,SAASA,WAAatB,EAAQM,iBAC/FT,KAAKO,QAAQoB,IAAI,UAAW,QACnBxB,EAAQI,SACjBP,KAAKO,QAAQoB,IAAI,UAAW,SAG9B3B,KAAKO,QAAQmB,QAAQ,+BAErB1B,KAAKK,OAAO6C,KAAK,QAAQR,KAAK,QAAS,IAAIE,SAAoC,OAA3BzC,EAAQW,QAAQT,OAAkBc,EAAOsB,OAAOpC,OAASF,EAAQW,QAAQT,QAEzHF,EAAQK,cACVR,KAAKO,QAAQoB,IAAI,aAAc,UACpBR,EAAOhB,QAAQsB,UAAYN,EAAOhB,QAAQsB,SAASA,WAAatB,EAAQM,kBACnFT,KAAKO,QAAQoB,IAAI,aAAc,WAE7BxB,EAAQG,aACVN,KAAKK,OAAOsB,IAAI,aAAc,UACnBR,EAAOhB,QAAQsB,UAAYN,EAAOhB,QAAQsB,SAASA,WAAatB,EAAQM,kBACnFT,KAAKK,OAAOsB,IAAI,aAAc,aAIpC1B,EAAEkD,OAAOrD,EAAQsD,QAAQC,aACvBhD,OAAQ,0BACRU,OAAQ,2BACRC,SAAU,oDAEZf,EAAEkD,OAAOrD,EAAQsD,QAAQE,YACvBjD,OAAQ,6BACRU,OAAQ,4BACRC,SAAU,6BAEZf,EAAEkD,OAAOrD,EAAQsD,QAAQG,aACvBlD,OAAQ,cACRU,OAAQ,cACRC,SAAU,eAELlB","file":"pnotify.buttons.js","sourceRoot":"/js"}

File diff suppressed because one or more lines are too long

Binary file not shown.

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

192
public/js/v1.5.4/app.js Normal file
View File

@@ -0,0 +1,192 @@
'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
notification: './app/notification', // "notification" 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
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.5.1 Morris.js - graphs and charts
raphael: 'lib/raphael.min', // v2.2.8 Raphaël - required for morris - https://dmitrybaranovskiy.github.io/raphael
bootbox: 'lib/bootbox.min', // v4.4.0 Bootbox.js - custom dialogs - http://bootboxjs.com
easyPieChart: 'lib/jquery.easypiechart.min', // v2.1.6 Easy Pie Chart - HTML 5 pie charts - http://rendro.github.io/easy-pie-chart
peityInlineChart: 'lib/jquery.peity.min', // v3.2.1 Inline Chart - http://benpickles.github.io/peity/
dragToSelect: 'lib/jquery.dragToSelect', // v1.1 Drag to Select - http://andreaslagerkvist.com/jquery/drag-to-select
hoverIntent: 'lib/jquery.hoverIntent.min', // v1.9.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
select2: 'lib/select2.min', // v4.0.3 Drop Down customization - https://select2.github.io
validator: 'lib/validator.min', // v0.10.1 Validator for Bootstrap 3 - https://github.com/1000hz/bootstrap-validator
lazylinepainter: 'lib/jquery.lazylinepainter-1.5.1.min', // v1.5.1 SVG line animation plugin - http://lazylinepainter.info
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', // v1.0.5 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/jquery.lazyload.min', // v1.9.7 LazyLoader images - https://appelsiini.net/projects/lazyload/
sortable: 'lib/sortable.min', // v1.6.0 Sortable - drag&drop reorder - https://github.com/rubaxa/Sortable
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org
'summernote': 'lib/summernote/summernote.min',
// header animation
easePack: 'lib/EasePack.min',
tweenLite: 'lib/TweenLite.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-select': 'lib/datatables/Select-1.3.0/js/dataTables.select.min',
'datatables.plugins.render.ellipsis': 'lib/datatables/plugins/render/ellipsis',
// notification plugin
pnotify: 'lib/pnotify/pnotify', // v3.2.1 PNotify - notification core file - https://sciactive.com/pnotify/
'pnotify.buttons': 'lib/pnotify/pnotify.buttons', // PNotify - buttons notification extension
'pnotify.confirm': 'lib/pnotify/pnotify.confirm', // PNotify - confirmation notification extension
'pnotify.nonblock': 'lib/pnotify/pnotify.nonblock', // PNotify - notification non-block extension (hover effect)
'pnotify.desktop': 'lib/pnotify/pnotify.desktop', // PNotify - desktop push notification extension
'pnotify.history': 'lib/pnotify/pnotify.history', // PNotify - history push notification history extension
'pnotify.callbacks': 'lib/pnotify/pnotify.callbacks', // PNotify - callbacks push notification extension
'pnotify.reference': 'lib/pnotify/pnotify.reference' // PNotify - reference push notification extension
},
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-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;
}
},
pnotify: {
deps: ['jquery']
},
easyPieChart: {
deps: ['jquery']
},
peityInlineChart: {
deps: ['jquery']
},
dragToSelect: {
deps: ['jquery']
},
hoverIntent: {
deps: ['jquery']
},
select2: {
deps: ['jquery', 'mousewheel'],
exports: 'Select2'
},
validator: {
deps: ['jquery', 'bootstrap']
},
lazylinepainter: {
deps: ['jquery', 'bootstrap']
},
blueImpGallery: {
deps: ['jquery']
},
bootstrapConfirmation: {
deps: ['bootstrap']
},
bootstrapToggle: {
deps: ['jquery']
},
lazyload: {
deps: ['jquery']
},
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] );

View File

@@ -0,0 +1,93 @@
/**
* Main "admin" page
*/
define([
'jquery',
'app/init',
'app/util',
'datatables.loader'
], ($, Init, Util) => {
'use strict';
let config = {
splashOverlayClass: 'pf-splash', // class for "splash" overlay
triggerOverlayClass: 'pf-overlay-trigger' // class for
};
/**
* set page observer
*/
let setPageObserver = () => {
$('.' + config.triggerOverlayClass).on('click', function(e){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
$('body').initTooltips();
// set fieldset toggled by checkbox ---------------------------------------------------------------------------
$('input[type="checkbox"][data-target]').on('change', function(){
let targetId = $(this).attr('data-target');
if(targetId){
let targetElement = $('[data-id="' + targetId + '"]');
let targetFormFields = targetElement.find('input[type="radio"]');
let checkFormFields = [];
for(let formField of targetFormFields){
if(this.checked){
if(formField.hasAttribute('data-default') || formField.getAttribute('data-default-value')){
checkFormFields.push(formField);
}
}else{
formField.setAttribute('data-default-value', formField.checked ? 'checked' : '');
if(formField.hasAttribute('data-default')){
checkFormFields.push(formField);
}
}
}
for(let checkFormField of checkFormFields){
checkFormField.checked = true;
}
}
});
$('input[type="radio"]').on('change', function(){
if(this.checked){
let targetId = $(this).parents('fieldset').attr('data-id');
$('input[type="checkbox"][data-target="' + targetId + '"]').prop('checked', true);
}
});
};
/**
* main init "admin" page
*/
$(() => {
// set Dialog default config
Util.initDefaultBootboxConfig();
// hide splash loading animation
$('.' + config.splashOverlayClass + '[data-status="ok"]').hideSplashOverlay();
setPageObserver();
let temp = $('.dataTable').dataTable({
pageLength: 100,
paging: true,
ordering: true,
autoWidth: false,
hover: false,
language: {
emptyTable: 'No entries',
zeroRecords: 'No entries found',
lengthMenu: 'Show _MENU_ entries',
info: 'Showing _START_ to _END_ of _TOTAL_ entries'
},
data: null // use DOM data overwrites [] default -> data.loader.js
});
});
});

View File

@@ -0,0 +1,435 @@
/**
* Created by exodus4d on 06.07.2015.
* static signature types
*
* (*) marked fields are in-game verified and
* proofed, signature names (copy & paste from scanning window)
*/
define(['jquery'], ($) => {
'use strict';
// signature sources
// http://de.sistersprobe.wikia.com/wiki/EVE_Sister_Core_Scanner_Probe_Wiki
// NullSec Relic sites, which can also spawn in C1, C2, C3 wormholes
let nullSecRelicSites = {
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'
};
// NulSec Data sites, which can also spawn in C1, C2, C3 wormholes
let nullSecDataSites = {
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 Center',
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'
};
// signature types
let signatureTypes = {
1: { // system type (wh)
1: { // C1 (area id)
1: { // Combat
1: 'Perimeter Ambush Point',
2: 'Perimeter Camp',
3: 'Phase Catalyst Node',
4: 'The Line'
},
2: $.extend({}, nullSecRelicSites, { // Relic
1: 'Forgotten Perimeter Coronation Platform', //*
2: 'Forgotten Perimeter Power Array' //*
}),
3: $.extend({}, nullSecDataSites, { // Data
1: 'Unsecured Perimeter Amplifier', //*
2: 'Unsecured Perimeter Information Center' //*
}),
4: { // Gas
1: 'Barren Perimeter Reservoir', //*
2: 'Token Perimeter Reservoir', //*
3: 'Minor Perimeter Reservoir', //*
4: 'Sizeable Perimeter Reservoir', //*
5: 'Ordinary Perimeter Reservoir' //*
},
5: { // Wormhole
// all k-space exits are static or K162
1: 'H121 - C1',
2: 'C125 - C2',
3: 'O883 - C3',
4: 'M609 - C4',
5: 'L614 - C5',
6: 'S804 - C6',
7: 'F353 - C12 Thera'
},
6: { // ORE
1: 'Ordinary Perimeter Deposit', //*
2: 'Common Perimeter Deposit', //*
3: 'Unexceptional Frontier Deposit', //*
4: 'Average Frontier Deposit', //*
5: 'Isolated Core Deposit', //*
6: 'Uncommon Core Deposit' //*
},
7: { // Ghost
}
},
2: { // C2
1: { // Combat
1: 'Perimeter Checkpoint',
2: 'Perimeter Hangar',
3: 'The Ruins of Enclave Cohort 27',
4: 'Sleeper Data Sanctuary'
},
2: $.extend({}, nullSecRelicSites, { // Relic
1: 'Forgotten Perimeter Gateway', //*
2: 'Forgotten Perimeter Habitation Coils' //*
}),
3: $.extend({}, nullSecDataSites, { // Data
1: 'Unsecured Perimeter Comms Relay', //*
2: 'Unsecured Perimeter Transponder Farm' //*
}),
4: { // Gas
1: 'Barren Perimeter Reservoir', //*
2: 'Token Perimeter Reservoir', //*
3: 'Minor Perimeter Reservoir', //*
4: 'Sizeable Perimeter Reservoir', //*
5: 'Ordinary Perimeter Reservoir' //*
},
5: { // Wormhole
// all w-space -> w-space are statics or K162
1: 'Z647 - C1',
2: 'D382 - C2',
3: 'O477 - C3',
4: 'Y683 - C4',
5: 'N062 - C5',
6: 'R474 - C6',
7: 'F135 - C12 Thera'
},
6: { // ORE
1: 'Ordinary Perimeter Deposit', //*
2: 'Common Perimeter Deposit', //*
3: 'Unexceptional Frontier Deposit', //*
4: 'Average Frontier Deposit', //*
5: 'Isolated Core Deposit', //*
6: 'Uncommon Core Deposit' //*
},
7: { // Ghost
}
},
3: { // C3
1: { // Combat
1: 'Fortification Frontier Stronghold',
2: 'Outpost Frontier Stronghold',
3: 'Solar Cell',
4: 'The Oruze Construct'
},
2: $.extend({}, nullSecRelicSites, { // Relic
1: 'Forgotten Frontier Quarantine Outpost', //*
2: 'Forgotten Frontier Recursive Depot' //*
}),
3: $.extend({}, nullSecDataSites, { // Data
1: 'Unsecured Frontier Database', //*
2: 'Unsecured Frontier Receiver' //*
}),
4: { // Gas
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' //*
},
5: { // Wormhole
// all k-space exits are static or K162
1: 'V301 - C1',
2: 'I182 - C2',
3: 'N968 - C3',
4: 'T405 - C4',
5: 'N770 - C5',
6: 'A982 - C6',
7: 'F135 - C12 Thera'
},
6: { // ORE
1: 'Ordinary Perimeter Deposit', //*
2: 'Common Perimeter Deposit', //*
3: 'Unexceptional Frontier Deposit', //*
4: 'Average Frontier Deposit', //*
5: 'Infrequent Core Deposit', //*
6: 'Unusual Core Deposit' //*
},
7: { // Ghost
}
},
4: { // C4
1: { // Combat
1: 'Frontier Barracks',
2: 'Frontier Command Post',
3: 'Integrated Terminus',
4: 'Sleeper Information Sanctum'
},
2: { // Relic
1: 'Forgotten Frontier Conversion Module',
2: 'Forgotten Frontier Evacuation Center'
},
3: { // Data
1: 'Unsecured Frontier Digital Nexus',
2: 'Unsecured Frontier Trinary Hub'
},
4: { // Gas
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' //*
},
5: { // Wormhole
// no *wandering* w-space -> w-space
// all holes are statics or K162
1: 'S047 - H',
2: 'N290 - L',
3: 'K329 - 0.0'
},
6: { // ORE
1: 'Ordinary Perimeter Deposit', //*
2: 'Common Perimeter Deposit', //*
3: 'Unexceptional Frontier Deposit', //*
4: 'Average Frontier Deposit', //*
5: 'Unusual Core Deposit', //*
6: 'Infrequent Core Deposit' //*
},
7: { // Ghost
}
},
5: { // C5
1: { // Combat
1: 'Core Garrison', //*
2: 'Core Stronghold', //*
3: 'Oruze Osobnyk', //*
4: 'Quarantine Area'
},
2: { // Relic
1: 'Forgotten Core Data Field',
2: 'Forgotten Core Information Pen'
},
3: { // Data
1: 'Unsecured Frontier Enclave Relay',
2: 'Unsecured Frontier Server Bank'
},
4: { // Gas
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' //*
},
5: { // Wormhole
1: 'D792 - H',
2: 'C140 - L',
3: 'Z142 - 0.0'
},
6: { // ORE
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' //*
},
7: { // Ghost
}
},
6: { // C6
1: { // Combat
1: 'Core Citadel', //*
2: 'Core Bastion', //*
3: 'Strange Energy Readings', //*
4: 'The Mirror' //*
},
2: { // Relic
1: 'Forgotten Core Assembly Hall', //*
2: 'Forgotten Core Circuitry Disassembler' //*
},
3: { // Data
1: 'Unsecured Core Backup Array', //*
2: 'Unsecured Core Emergence' //*
},
4: { // Gas
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' //*
},
5: { // Wormhole
1: 'B520 - H',
2: 'D792 - H',
3: 'C140 - L',
4: 'C391 - L',
5: 'C248 - 0.0',
6: 'Z142 - 0.0'
},
6: { // ORE
1: 'Ordinary Perimeter Deposit', //*
2: 'Common Perimeter Deposit', //*
3: 'Unexceptional Frontier Deposit', //*
4: 'Average Frontier Deposit', //*
5: 'Rarified Core Deposit' //*
},
7: { // Ghost
1: 'Superior Blood Raider Covert Research Facility' //*
}
},
12: { // Thera wormhole
1: { // Combat
1: 'Epicenter',
2: 'Expedition Command Outpost Wreck',
3: 'Planetary Colonization Office Wreck',
4: 'Testing Facilities'
}
},
13: { // Shattered Wormholes
5: { // Wormhole (some of them are static)
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'
}
}
}, // system type (k-space)
2: {
30: { // High Sec
5: { // Wormhole
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'
}
},
31: { // Low Sec
5: { // Wormhole
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'
}
},
32: { // 0.0
5: { // Wormhole
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'
}
}
}
};
return signatureTypes;
});

View File

@@ -0,0 +1,733 @@
/**
* Created by exodus4d on 06.07.2015.
* static system effects
*/
define([], function(){
'use strict';
// system effects
let systemEffects = {
wh: {
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%'
}
]
},
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%'
}
]
},
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%'
}
]
},
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%'
}
]
},
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%'
}
]
},
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%'
}
]
}
}
};
return systemEffects;
});

View File

@@ -0,0 +1,215 @@
/**
* 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"',
},
'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 += ['ok', 'log', 'info', 'pf'].includes(logType) ? getStyleByLogType('indentDefault') : '';
let bullet = ['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.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
};
});

View File

@@ -0,0 +1,160 @@
define([
'jquery',
'app/init',
'app/util'
], ($, Init, Util) => {
'use strict';
let config = {
counterDigitSmallClass: 'pf-digit-counter-small',
counterDigitLargeClass: 'pf-digit-counter-large'
};
/**
* 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 + '">' + '&gt;&nbsp;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
*/
$.fn.destroyTimestampCounter = function(recursive){
return this.each(function(){
let element = $(this);
let counterSelector = '[data-counter="init"]';
let counterElements = element.filter(counterSelector);
if(recursive){
counterElements = counterElements.add(element.find(counterSelector));
}
counterElements.each(function(){
let element = $(this);
let interval = element.data('interval');
if(interval){
clearInterval(interval);
element.removeAttr('data-counter').removeData('interval').removeClass('stopCounter');
}
});
});
};
/**
* init a live counter based on a unix timestamp
* @param round string e.g. 'd' => round days
*/
$.fn.initTimestampCounter = function(round){
return this.each(function(){
let element = $(this);
let timestamp = parseInt( element.text() );
// do not init twice
if(timestamp > 0){
// mark as init
element.attr('data-counter', 'init');
let date = new Date( timestamp * 1000);
updateDateDiff(element, date, round);
// show element (if invisible) after first update
element.css({'visibility': 'initial'});
// calc ms until next second
// -> makes sure all counter update in sync no matter when init
let msUntilSecond = 1500 - new Date().getMilliseconds();
setTimeout(function(){
let refreshIntervalId = window.setInterval(function(){
// update element with current time
if( !element.hasClass('stopCounter')){
updateDateDiff(element, date, round);
}else{
clearInterval( element.data('interval') );
}
}, 500);
element.data('interval', refreshIntervalId);
}, msUntilSecond);
}
});
};
/**
* init global timestamp counter or DataTable for specific columns
* @param tableElement
* @param columnSelector
* @param round
*/
let initTableCounter = (tableElement, columnSelector, round) => {
let tableApi = tableElement.api();
// mark as init
tableElement.attr('data-counter', 'init');
let refreshIntervalId = window.setInterval(() => {
tableApi.cells(null, columnSelector).every(function(rowIndex, colIndex, tableLoopCount, cellLoopCount){
let cell = this;
let node = cell.node();
let data = cell.data();
if(data && Number.isInteger(data) && !node.classList.contains('stopCounter')){
// timestamp expected int > 0
let date = new Date(data * 1000);
updateDateDiff( cell.nodes().to$(), date, round);
}
});
}, 500);
tableElement.data('interval', refreshIntervalId);
};
return {
updateDateDiff: updateDateDiff,
initTableCounter: initTableCounter
};
});

View File

@@ -0,0 +1,161 @@
define([
'jquery',
'app/init',
'app/promises/promise.deferred',
'app/promises/promise.timeout',
'datatables.net',
'datatables.net-buttons',
'datatables.net-buttons-html',
'datatables.net-responsive',
'datatables.net-select'
], ($, Init, DeferredPromise, TimeoutPromise) => {
'use strict';
// all Datatables stuff is available...
let initDefaultDatatablesConfig = () => {
$.extend(true, $.fn.dataTable.defaults, {
pageLength: -1,
pagingType: 'simple_numbers',
lengthMenu: [[5, 10, 25, 50, -1], [5, 10, 25, 50, 'All']],
order: [], // no default order because columnDefs is empty
autoWidth: false,
language: {
info: '_START_ - _END_ of _TOTAL_ entries'
},
responsive: {
breakpoints: Init.breakpoints,
details: false
},
columnDefs: [],
data: []
});
// global open event
$(document).on('destroy.dt', '.dataTable ', function(e, settings){
let table = $(this);
let tableApi = new $.fn.dataTable.Api(settings);
// end all active processes (e.g. table lock)
// -> this custom extension is only available if "StatusTable" feature is enabled with it
if(typeof tableApi.endProcesses === 'function'){
tableApi.endProcesses();
}
// remove all active counters in table
table.destroyTimestampCounter(true);
});
let StatusTable = function(settings){
let me = this;
me.statusContainer = $('<div>', {
class: 'dt-stats pull-right'
});
me.statusLock = $('<span>', {
class: ['dt-stat', 'disabled', 'fade'].join(' ')
}).append($('<i>', {class: ['fas', 'fa-fw', 'fa-lock'].join(' ')}));
me.statusRequest = $('<span>', {
class: ['dt-stat', 'disabled', 'fade'].join(' ')
}).append($('<i>', {class: ['fas', 'fa-fw', 'fa-sync', 'fa-spin'].join(' ')}));
me.statusContainer.append(me.statusLock, me.statusRequest);
// processStore holds "unfulfilled" promises
me.processStore = {};
me.defaultProcessName = 'default';
me.lock = () => me.statusLock.addClass('in');
me.unlock = () => me.statusLock.removeClass('in');
me.request = () => me.statusRequest.addClass('in');
me.unrequest = () => me.statusRequest.removeClass('in');
me.endProcess = deferredPromise => {
me.processStore[deferredPromise.data.name].delete(deferredPromise);
// update table processStatus
me.updateProcessStatus(deferredPromise.data.name);
};
me.hasProcesses = name => {
let hasProcesses = false;
if(me.processStore[name] instanceof Map){
hasProcesses = me.processStore[name].size > 0;
}
return hasProcesses;
};
me.updateProcessStatus = name => {
let method = me.hasProcesses(name) ? name : 'un' + name;
me[method]();
};
$.fn.dataTable.Api.register('newProcess()', function(name = me.defaultProcessName){
// new DeferredPromise for process
let deferredPromise = new DeferredPromise();
deferredPromise.data = {name: name};
// reject DeferredPromise after timeout (if not resolved
let timeoutPromise = new TimeoutPromise(deferredPromise);
timeoutPromise
.then(function(name, payload){
// resolved within timeout -> wait for finally() block
}.bind(me, name))
.catch(error => {
if(error instanceof Error){
// either timeout error or error from rejected deferredPromise
console.warn(error);
}
})
.finally(function(deferredPromise){
// no matter if TimeoutPromise is resolved or rejected
// -> remove from processStore
this.endProcess(deferredPromise);
}.bind(me, deferredPromise));
// store TimeoutPromise -------------------------------------------------------------------------------
if(!(me.processStore[name] instanceof Map)){
me.processStore[name] = new Map();
}
me.processStore[name].set(deferredPromise);
// update table processStatus
me.updateProcessStatus(name);
return deferredPromise;
});
$.fn.dataTable.Api.register('hasProcesses()', function(name = me.defaultProcessName){
return me.hasProcesses(name);
});
$.fn.dataTable.Api.register('endProcess()', function(deferredPromise){
me.endProcess(deferredPromise);
deferredPromise.resolve();
});
$.fn.dataTable.Api.register('endProcesses()', function(){
for(let [name, store] of Object.entries(me.processStore)){
for(let [deferredPromise, value] of store.entries()){
me.endProcess(deferredPromise);
deferredPromise.resolve();
}
}
});
};
StatusTable.prototype.getContainer = function(){
return this.statusContainer;
};
$.fn.dataTable.ext.feature.push({
fnInit: settings => {
let oStatusTable = new StatusTable(settings);
return oStatusTable.getContainer();
},
cFeature: 'S',
sFeature: 'StatusTable'
});
};
initDefaultDatatablesConfig();
});

View File

@@ -0,0 +1,644 @@
/**
* Init
*/
define([], () => {
'use strict';
let Config = {
path: {
img: '/public/img/', // path for images
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
saveMap: '/api/map/save', // ajax URL - save/update map
deleteMap: '/api/map/delete', // ajax URL - delete map
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
getSystemGraphData: '/api/system/graphData', // ajax URL - get all system graph data
setDestination: '/api/system/setDestination', // ajax URL - set destination
pokeRally: '/api/system/pokeRally', // ajax URL - send rally point pokes
// route API
searchRoute: '/api/route/search', // ajax URL - search system routes
// 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: '&#xf108;'
},{
class: 'fa-space-shuttle',
label: 'space shuttle',
unicode: '&#xf197;'
},{
class: 'fa-anchor',
label: 'anchor',
unicode: '&#xf13d;'
},{
class: 'fa-satellite',
label: 'satellite',
unicode: '&#xf7bf;'
},{
class: 'fa-skull-crossbones',
label: 'skull crossbones',
unicode: '&#xf714;'
},{
class: 'fa-fire',
label: 'fire',
unicode: '&#xf06d;'
},{
class: 'fa-bookmark',
label: 'bookmark',
unicode: '&#xf02e;'
},{
class: 'fa-cube',
label: 'cube',
unicode: '&#xf1b2;'
},{
class: 'fa-star',
label: 'star',
unicode: '&#xf005;'
},{
class: 'fa-hat-wizard',
label: 'hat wizard',
unicode: '&#xf6e8;'
},{
class: 'fa-plane',
label: 'plane',
unicode: '&#xf072;'
},{
class: 'fa-globe',
label: 'globe',
unicode: '&#xf0ac;'
},{
class: 'fa-rocket',
label: 'rocket',
unicode: '&#xf135;'
},{
class: 'fa-life-ring',
label: 'life ring',
unicode: '&#xf1cd;'
},{
class: 'fa-heart',
label: 'heart',
unicode: '&#xf004;'
},{
class: 'fa-poop',
label: 'poop',
unicode: '&#xf619;'
}
],
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'
}
},
// 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>&nbsp;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: { // C1
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'
},
2: { // C2
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'
},
3: { // C3
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'
},
4: { // C4
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'
},
5: { // C5
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'
},
6: { // C6
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'
},
13: { // Shattered Wormholes (some of them are static)
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'
},
30: { // High Sec
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'
},
31: { // Low Sec
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'
},
32: { // 0.0
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'
}
},
// 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'
}
};
return Config;
});

478
public/js/v1.5.4/app/key.js Normal file
View File

@@ -0,0 +1,478 @@
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
};
});

View File

@@ -0,0 +1,544 @@
/**
* logging
*/
define([
'jquery',
'app/init',
'app/util',
'bootbox'
], ($, Init, Util, 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
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 ));
logDialog.find('.' + config.taskDialogStatusAreaClass).html( syncStatusElement );
logDialog.find('.' + config.timestampCounterClass).initTimestampCounter();
syncStatusElement.initTooltips({
placement: 'right'
});
});
}
};
/**
* shows the logging dialog
*/
let showDialog = () => {
// dialog content
requirejs(['text!templates/dialog/task_manager.html', 'mustache', 'datatables.loader'], 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: '<"row"<"col-xs-3"l><"col-xs-5"B><"col-xs-4"fS>>' +
'<"row"<"col-xs-12"tr>>' +
'<"row"<"col-xs-5"i><"col-xs-7"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],
units: 'ms',
parseTime: false,
ymin: 0,
yLabelFormat: labelYFormat,
padding: 10,
hideHover: true,
pointSize: 3,
lineColors: ['#375959'],
pointFillColors: ['#477372'],
pointStrokeColors: ['#313335'],
lineWidth: 2,
grid: false,
gridStrokeWidth: 0.3,
gridTextSize: 9,
gridTextFamily: 'Oxygen Bold',
gridTextColor: '#63676a',
behaveLikeLine: true,
goals: [],
goalLineColors: ['#66c84f'],
smooth: false,
fillOpacity: 0.3,
resize: true
});
updateLogGraph(key);
graphArea.hideLoadingAnimation();
}
}
});
// modal dialog is closed
logDialog.on('hidden.bs.modal', function(e){
// clear memory -> destroy all charts
for(let key in chartData){
if(chartData.hasOwnProperty(key)){
chartData[key].graph = null;
}
}
});
// modal dialog before hide
logDialog.on('hide.bs.modal', function(e){
// 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] = {};
chartData[key].data = [];
chartData[key].graph = null;
chartData[key].averageElement = null;
chartData[key].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 !== null){
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 = function(){
let maxEntries = 150;
$(window).on('pf:syncStatus', function(){
updateSyncStatus();
});
// set global logging listener
$(window).on('pf:log', function(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 ---------------------------------
let rowCount = logData.length;
if(rowCount >= 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
};
});

View File

@@ -0,0 +1,835 @@
/**
* Main loginPage application
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'blueImpGallery',
'bootbox',
'lazyload',
'layout/header_login',
'layout/logo',
'layout/demo_map',
'dialog/account_settings',
'dialog/notification',
'dialog/manual',
'dialog/changelog',
'dialog/credit',
'dialog/api_status',
], ($, Init, Util, Render, Gallery, bootbox) => {
'use strict';
let config = {
splashOverlayClass: 'pf-splash', // class for "splash" overlay
// header
headerId: 'pf-landing-top', // id for header
headerContainerId: 'pf-header-container', // id for header container
logoContainerId: 'pf-logo-container', // id for main header logo container
headHeaderMapId: 'pf-header-map', // id for header image (svg animation)
// map bg
headMapBgId: 'pf-header-map-bg', // id for header background container
mapNeocomId: 'pf-map-neocom', // id for map "neocom" image
mapBrowserId: 'pf-map-browser', // id for "browser" image
mapBgImageId: 'pf-map-bg-image', // id for "background" map image
// navigation
navigationElementId: 'pf-navbar', // id for navbar element
navigationLinkManualClass: 'pf-navbar-manual', // class for "manual" trigger link
navigationLinkLicenseClass: 'pf-navbar-license', // class for "license" trigger link
navigationVersionLinkClass: 'pf-navbar-version-info', // class for "version information"
// cookie hint
cookieHintId: 'pf-cookie-hint', // id for "cookie hint" element
// login
ssoButtonClass: 'pf-sso-login-button', // class for SSO login button
// character select
characterSelectionClass: 'pf-character-selection', // class for character panel wrapper
characterRowAnimateClass: 'pf-character-row-animate', // class for character panel row during animation
characterImageWrapperClass: 'pf-character-image-wrapper', // class for image wrapper (animated)
characterImageInfoClass: 'pf-character-info', // class for character info layer (visible on hover)
dynamicMessageContainerClass: 'pf-dynamic-message-container', // class for "dynamic" (JS) message container
// gallery
galleryId: 'pf-gallery', // id for gallery container
galleryThumbImageClass: 'pf-landing-image-preview', // class for gallery thumb images
galleryThumbContainerId: 'pf-landing-gallery-thumb-container', // id for gallery thumb images
galleryCarouselId: 'pf-landing-gallery-carousel', // id for "carousel" element
// notification panel
notificationPanelId: 'pf-notification-panel', // id for "notification panel" (e.g. last update information)
// sticky panel
stickyPanelClass: 'pf-landing-sticky-panel', // class for sticky panels
stickyPanelServerId: 'pf-landing-server-panel', // id for EVE Online server status panel
stickyPanelAdminId: 'pf-landing-admin-panel', // id for admin login panel
apiStatusTriggerClass: 'pf-api-status-trigger', // class for "api status" dialog trigger elements
// animation
animateElementClass: 'pf-animate-on-visible', // class for elements that will be animated to show
defaultAcceptCookieExpire: 365 // default expire for "accept coolies" cookie
};
/**
* set link observer for "version info" dialog
*/
let setVersionLinkObserver = () => {
$('.' + config.navigationVersionLinkClass).off('click').on('click', function(e){
$.fn.changelogsDialog();
});
};
/**
* move panel out of "cookie" accept hint
* @param direction
*/
let moveAdminPanel = (direction) => {
let adminPanel = $('#' + config.stickyPanelAdminId);
adminPanel.css({bottom: ((direction === 'up') ? '+' : '-') + '=35px'});
};
let setAcceptCookie = () => {
Util.setCookie('cookie', 1, config.defaultAcceptCookieExpire, 'd');
};
/**
* set page observer
*/
let setPageObserver = () => {
let ssoButtonElement = $('.' + config.ssoButtonClass);
let cookieHintElement = $('#' + config.cookieHintId);
$(document).on('click', '.' + config.characterSelectionClass + ' a', function(){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
$(document).on('click', '.' + config.ssoButtonClass , function(){
if(Util.getCookie('cookie') === '1'){
// ... cookies accepted no "confirm" shown
$('.' + config.splashOverlayClass).showSplashOverlay();
}
});
// cookie hint --------------------------------------------------------
cookieHintElement.find('.btn-success').on('click', function(){
setAcceptCookie();
// confirmation no longer needed on SSO login button
ssoButtonElement.confirmation('destroy');
});
cookieHintElement.on('show.bs.collapse', function(){
// move admin panel upwards (prevents overlapping with cookie notice)
moveAdminPanel('up');
});
cookieHintElement.on('hidden.bs.collapse', function(){
moveAdminPanel('down');
});
if(Util.getCookie('cookie') !== '1'){
// hint not excepted
cookieHintElement.collapse('show');
// show Cookie accept hint on SSO login button
let confirmationSettings = {
container: 'body',
placement: 'bottom',
btnOkClass: 'btn btn-sm btn-default',
btnOkLabel: 'dismiss',
btnOkIcon: 'fas fa-fw fa-sign-in-alt',
title: 'Accept cookies',
btnCancelClass: 'btn btn-sm btn-success',
btnCancelLabel: 'accept',
btnCancelIcon: 'fas fa-fw fa-check',
onCancel: function(e, target){
// "Accept cookies"
setAcceptCookie();
// set "default" href
let href = $(target).data('bs.confirmation').getHref();
$(e.target).attr('href', href);
},
onConfirm : function(e, target){
// "NO cookies" => trigger "default" href link action
},
href: function(target){
return $(target).attr('href');
}
};
ssoButtonElement.confirmation(confirmationSettings);
}
// manual -------------------------------------------------------------
$('.' + config.navigationLinkManualClass).on('click', function(e){
e.preventDefault();
$.fn.showMapManual();
});
// license ------------------------------------------------------------
$('.' + config.navigationLinkLicenseClass).on('click', function(e){
e.preventDefault();
$.fn.showCreditsDialog(false, true);
});
// releases -----------------------------------------------------------
setVersionLinkObserver();
// tooltips -----------------------------------------------------------
let mapTooltipOptions = {
toggle: 'tooltip',
delay: 150
};
let tooltipElements = $('[title]').not('.slide img');
tooltipElements.tooltip(mapTooltipOptions);
// initial show some tooltips
tooltipElements.filter('[data-show="1"]').tooltip('show');
};
/**
* init image carousel
*/
let initCarousel = () => {
// check if carousel exists
if($('#' + config.galleryCarouselId).length === 0){
return;
}
// extent "blueimp" gallery for a textFactory method to show HTML templates
Gallery.prototype.textFactory = function(obj, callback){
let newSlideContent = $('<div>')
.addClass('text-content')
.attr('imgTitle', obj.title);
// render HTML file (template)
let moduleData = {
id: config.headHeaderMapId,
bgId: config.headMapBgId,
neocomId: config.mapNeocomId,
browserId: config.mapBrowserId,
mapBgImageId: config.mapBgImageId
};
Render.render(obj.href, moduleData)
.then(payload => newSlideContent.append(payload))
.then(payload => callback({type: 'complete', target: payload[0]}));
return newSlideContent[0];
};
// initialize carousel ------------------------------------------------
let carousel = new Gallery([
{
imgTitle: 'Browser',
href: 'ui/map',
type: 'text/html'
},
{
href: 'public/img/landing/responsive.jpg',
imgTitle: 'Responsive layout',
type: 'image/jpg',
thumbnail: ''
},
{
href: 'public/img/landing/pathfinder_1.jpg',
imgTitle: 'Map view',
type: 'image/jpg',
thumbnail: ''
},
{
href: 'public/img/landing/pathfinder_3.jpg',
imgTitle: 'Map information',
type: 'image/jpg',
thumbnail: ''
},
{
href: 'public/img/landing/pathfinder_2.jpg',
imgTitle: 'System information',
type: 'image/jpg',
thumbnail: ''
}
], {
container: '#' + config.galleryCarouselId,
carousel: true,
startSlideshow: false,
titleProperty: 'imgTitle',
transitionSpeed: 600,
slideshowInterval: 5000,
preloadRange: 1,
onopened: function(){
// Callback function executed when the Gallery has been initialized
// and the initialization transition has been completed.
// -> show "demo" map
// set title for first slide
$(this.options.container).find(this.options.titleElement).text('Browser view');
$('#' + config.headHeaderMapId).drawDemoMap(function(){
// zoom map SVGs
$('#' + config.headHeaderMapId + ' svg').velocity({
scaleX: 0.66,
scaleY: 0.66
}, {
duration: 360
});
// position map container
$('#' + config.headHeaderMapId).velocity({
marginTop: '130px',
marginLeft: '-50px'
}, {
duration: 360,
complete: function(){
// show browser
$('#' + config.mapBrowserId).velocity('transition.slideUpBigIn', {
duration: 360,
complete: function(){
// show neocom
$('#' + config.mapNeocomId).velocity('transition.slideLeftIn', {
duration: 180
});
// show background
$('#' + config.mapBgImageId).velocity('transition.shrinkIn', {
duration: 360
});
// when map is shown -> start carousel looping
carousel.play();
}
});
}
});
});
}
});
};
/**
* get all thumbnail elements
* @returns {*|jQuery|HTMLElement}
*/
let getThumbnailElements = () => {
return $('a[data-gallery="#' + config.galleryId + '"]').not('.disabled');
};
/**
* init gallery for thumbnail elements
* @param newElements
*/
let initGallery = (newElements) => {
if( newElements.length > 0){
// We have to add ALL thumbnail elements to the gallery!
// -> even those which are invisible (not lazyLoaded) now!
// -> This is required for "swipe" through all images
let allThumbLinks = getThumbnailElements();
requirejs(['blueImpGalleryBootstrap'], () => {
$(newElements).each(function(){
let borderless = false;
let galleryElement = $('#' + config.galleryId);
galleryElement.data('useBootstrapModal', !borderless);
galleryElement.toggleClass('blueimp-gallery-controls', borderless);
$(this).on('click', function(e){
e.preventDefault();
e = e || window.event;
let target = e.target || e.srcElement;
let link = target.src ? target.parentNode : target;
let options = {
index: link,
event: e,
container: '#' + config.galleryId,
titleProperty: 'description'
};
new Gallery(allThumbLinks, options);
});
});
});
}
};
/**
* init "YouTube" video preview
*/
let initYoutube = () => {
$('.youtube').each(function(){
// Based on the YouTube ID, we can easily find the thumbnail image
$(this).css('background-image', 'url(//i.ytimg.com/vi/' + this.id + '/sddefault.jpg)');
// Overlay the Play icon to make it look like a video player
$(this).append($('<div/>', {'class': 'play'}));
$(document).delegate('#' + this.id, 'click', function(){
// Create an iFrame with autoplay set to true
let iFrameUrl = '//www.youtube.com/embed/' + this.id + '?autoplay=1&autohide=1';
if( $(this).data('params') ){
iFrameUrl += '&'+$(this).data('params');
}
// The height and width of the iFrame should be the same as parent
let iFrame = $('<iframe/>', {
frameborder: '0',
src: iFrameUrl,
width: $(this).width(),
height: $(this).height(),
class: 'pricing-big'
});
// Replace the YouTube thumbnail with YouTube HTML5 Player
$(this).replaceWith(iFrame);
});
});
};
/**
* init scrollSpy for navigation bar
*/
let initScrollSpy = () => {
// init scrollspy
// show elements that are currently in the viewport
let showVisibleElements = () => {
// find all elements that should be animated
let visibleElements = $('.' + config.animateElementClass).isInViewport();
$(visibleElements).removeClass( config.animateElementClass );
$(visibleElements).velocity('transition.flipXIn', {
duration: 600,
stagger: 60,
delay: 500,
complete: function(element){
// show "fade" modules (e.g. ribbons)
$(element).find('.fade').addClass('in');
// init gallery for "now" visible elements
let newGalleryElements = $(element).filter('[data-gallery="#' + config.galleryId + '"]');
initGallery(newGalleryElements);
},
visibility: 'visible'
});
};
$( window ).scroll(() => {
// check for new visible elements
showVisibleElements();
});
// initial check for visible elements
showVisibleElements();
// event listener for navigation links
Util.initPageScroll('#' + config.navigationElementId);
};
/**
* get current EVE-Online server status
* -> show "server panel"
*/
let initServerStatus = () => {
$.ajax({
type: 'POST',
url: Init.path.getServerStatus,
dataType: 'json'
}).done(function(responseData, textStatus, request){
let data = {
stickyPanelServerId: config.stickyPanelServerId,
stickyPanelClass: config.stickyPanelClass,
apiStatusTriggerClass: config.apiStatusTriggerClass,
server: responseData.server,
api: responseData.api,
statusFormat: () => {
return (val, render) => {
switch(render(val)){
case 'online':
case 'green': return 'txt-color-green';
case 'vip':
case 'yellow': return 'txt-color-orange';
case 'offline':
case 'red': return 'txt-color-red';
default: return '';
}
};
}
};
requirejs(['text!templates/ui/server_panel.html', 'mustache'], function(template, Mustache){
let content = Mustache.render(template, data);
$('#' + config.headerId).prepend(content);
let stickyPanelServer = $('#' + config.stickyPanelServerId);
stickyPanelServer.velocity('transition.slideLeftBigIn', {
duration: 240
});
// set observer for api status dialog
stickyPanelServer.on('click', '.' + config.apiStatusTriggerClass, function(){
$.fn.apiStatusDialog(data.api);
});
});
}).fail(handleAjaxErrorResponse);
};
/**
* show "notification panel" to user
* -> checks if panel not already shown
*/
let initNotificationPanel = () => {
let storageKey = 'notification_panel';
let currentVersion = Util.getVersion();
let showNotificationPanel = () => {
let data = {
version: Util.getVersion()
};
requirejs(['text!templates/ui/notice.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
let notificationPanel = $('#' + config.notificationPanelId);
notificationPanel.html(content);
notificationPanel.velocity('transition.slideUpIn', {
duration: 300,
complete: function(){
setVersionLinkObserver();
// mark panel as "shown"
Util.getLocalStorage().setItem(storageKey, currentVersion);
}
});
});
};
Util.getLocalStorage().getItem(storageKey).then(function(data){
// check if panel was shown before
if(data){
if(data !== this.version){
// show current panel
showNotificationPanel();
}
}else{
// show current panel
showNotificationPanel();
}
}.bind({
version: currentVersion
}));
};
/**
* load character data from cookie information
* -> all validation is done server side!
*/
let initCharacterSelect = function(){
/**
* init panel animation for an element
* @param imageWrapperElement
*/
let initCharacterAnimation = function(imageWrapperElement){
imageWrapperElement.velocity('stop').velocity('transition.flipBounceXIn', {
display: 'inline-block',
drag: true,
duration: 500
});
// Hover effect for character info layer
imageWrapperElement.hoverIntent(function(e){
let characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
characterInfoElement.velocity('finish').velocity({
width: ['100%', [ 400, 15 ] ]
},{
easing: 'easeOutSine'
});
}, function(e){
let characterInfoElement = $(this).find('.' + config.characterImageInfoClass);
characterInfoElement.velocity('finish').velocity({
width: 0
},{
duration: 150,
easing: 'easeOutSine'
});
});
};
// --------------------------------------------------------------------
/**
* update all character panels -> set CSS class (e.g. after some panels were added/removed,..)
*/
let updateCharacterPanels = function(){
let characterRows = $('.' + config.characterSelectionClass + ' .' + Util.config.dynamicAreaClass).parent();
let rowClassIdentifier = ((12 / characterRows.length ) <= 3) ? 3 : (12 / characterRows.length);
$(characterRows).removeClass().addClass('col-sm-' + rowClassIdentifier);
};
// --------------------------------------------------------------------
let removeCharacterPanel = function(panelElement){
$(panelElement).velocity('transition.expandOut', {
duration: 250,
complete: function(){
// lock row for CSS animations while removing...
$(this).parent().addClass(config.characterRowAnimateClass);
$(this).parent().velocity({
width: 0
},{
easing: 'ease',
duration: 300,
complete: function(){
$(this).remove();
// reset column CSS classes for all existing panels
updateCharacterPanels();
}
});
}
});
};
// --------------------------------------------------------------------
let getCharacterAuthLabel = (authStatus) => {
let label = '';
switch(authStatus){
case 'UNKNOWN':
label = 'ERROR';
break;
case 'CHARACTER':
case 'CORPORATION':
case 'ALLIANCE':
label = 'INVALID';
break;
default:
label = authStatus;
break;
}
return label;
};
// --------------------------------------------------------------------
// request character data for each character panel
requirejs(['text!templates/ui/character_panel.html', 'mustache'], function(template, Mustache){
$('.' + config.characterSelectionClass + ' .' + Util.config.dynamicAreaClass).each(function(){
let characterElement = $(this);
characterElement.showLoadingAnimation();
let requestData = {
cookie: characterElement.data('cookie')
};
$.ajax({
type: 'POST',
url: Init.path.getCookieCharacterData,
data: requestData,
dataType: 'json',
context: {
cookieName: requestData.cookie,
characterElement: characterElement,
browserTabId: Util.getBrowserTabId()
}
}).done(function(responseData, textStatus, request){
this.characterElement.hideLoadingAnimation();
if(
responseData.error &&
responseData.error.length > 0
){
$('.' + config.dynamicMessageContainerClass).showMessage({
dismissible: false,
type: responseData.error[0].type,
title: 'Character verification failed',
text: responseData.error[0].message
});
}
if(responseData.hasOwnProperty('character')){
let data = {
link: this.characterElement.data('href'),
cookieName: this.cookieName,
browserTabId: this.browserTabId,
character: responseData.character,
isManager: Util.getObjVal(responseData, 'character.role.name') === 'CORPORATION',
isAdmin: Util.getObjVal(responseData, 'character.role.name') === 'SUPER',
authLabel: getCharacterAuthLabel(responseData.character.authStatus),
authOK: responseData.character.authStatus === 'OK',
hasActiveSession: responseData.character.hasActiveSession === true
};
let content = Mustache.render(template, data);
this.characterElement.html(content);
// show character panel (animation settings)
initCharacterAnimation(this.characterElement.find('.' + config.characterImageWrapperClass));
}else{
// character data not available -> remove panel
removeCharacterPanel(this.characterElement);
}
}).fail(function(jqXHR, status, error){
let characterElement = this.characterElement;
characterElement.hideLoadingAnimation();
// character data not available -> remove panel
removeCharacterPanel(this.characterElement);
});
});
});
};
/**
* default ajax error handler
* -> show user notifications
* @param jqXHR
* @param status
* @param error
*/
let handleAjaxErrorResponse = (jqXHR, status, error) => {
let type = status;
let title = 'Status ' + jqXHR.status + ': ' + error;
let message = '';
if(jqXHR.responseText){
let errorObj = $.parseJSON(jqXHR.responseText);
if(
errorObj.error &&
errorObj.error.length > 0
){
for(let i = 0; i < errorObj.error.length; i++){
let errorData = errorObj.error[i];
type = errorData.type;
title = 'Status ' + errorData.code + ': ' + errorData.status;
message = errorData.message;
Util.showNotify({title: title, text: message, type: type});
}
}
}else{
Util.showNotify({title: title, text: message, type: type});
}
};
/**
* main init "landing" page
*/
$(() => {
// clear sessionStorage
Util.clearSessionStorage();
// set default AJAX config
Util.ajaxSetup();
// set Dialog default config
Util.initDefaultBootboxConfig();
// show app information in browser console
Util.showVersionInfo();
// show log off message
let isLogOut = location.search.split('logout')[1];
if(isLogOut !== undefined){
// show logout dialog
let options = {
buttons: {
close: {
label: 'close',
className: ['btn-default'].join(' ')
}
},
content: {
icon: 'fa-sign-out-alt',
class: 'txt-color-warning',
title: 'Logout',
headline: 'Logout',
text: [
'For security reasons, you were logged out automatically',
'Please log in again'
]
}
};
$.fn.showNotificationDialog(options);
// change url (remove logout parameter)
if(history.pushState){
history.pushState({}, '', location.protocol + '//' + location.host + location.pathname);
}
}
// "Lock" default link action (=> open in new tab)!
// -> until "gallery" is initialized (=> wait animation complete!)
getThumbnailElements().on('click', function(e){
e.preventDefault();
});
// init "lazy loading" for images
$('.' + config.galleryThumbImageClass).lazyload({
threshold : 300
});
// hide splash loading animation
$('.' + config.splashOverlayClass + '[data-status="ok"]').hideSplashOverlay();
// init server status information
initServerStatus();
// init notification panel
initNotificationPanel();
// init character select
initCharacterSelect();
// init page observer
setPageObserver();
// init carousel
initCarousel();
// init scrollSpy
// -> after "Carousel"! required for correct "viewport" calculation (Gallery)!
initScrollSpy();
// init youtube videos
initYoutube();
// draw header logo
$('#' + config.logoContainerId).drawLogo(() => {
// init header animation
$('#' + config.headerContainerId).initHeader(() => {
});
}, false);
});
});

View File

@@ -0,0 +1,304 @@
/**
* context menu
*/
define([
'jquery',
'app/render'
], ($, Render) => {
'use strict';
let config = {
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
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 pageWidth = $(window).width();
// opening menu would pass the side of the page
if(mouseWidth + menuWidth > pageWidth &&
menuWidth < mouseWidth){
return mouseWidth - menuWidth;
}
return mouseWidth;
};
/**
* calc menu Y coordinate
* @param e
* @param menuHeight
* @returns {number|*}
*/
let getMenuTopCoordinate = (e, menuHeight) => {
let mouseHeight = e.pageY;
let pageHeight = $(window).height();
// opening menu would pass the bottom of the page
if(mouseHeight + menuHeight > pageHeight &&
menuHeight < mouseHeight){
return mouseHeight - menuHeight;
}
return 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-redDarker', 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-checkered', 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 = $('.dropdown-menu[role="menu"]');
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);
menuElement.css({
position: 'absolute',
left: getMenuLeftCoordinate(e, menuElement.width()),
top: getMenuTopCoordinate(e, menuElement.height())
}).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: Array, active: Array, disabled: Array, id: string, selectCallback: null}}
*/
let defaultMenuOptionConfig = () => {
return {
'id': '',
'selectCallback': null,
'hidden': [],
'active': [],
'disabled': []
};
};
return {
config: config,
defaultMenuOptionConfig: defaultMenuOptionConfig,
renderMapContextMenu: renderMapContextMenu,
renderConnectionContextMenu: renderConnectionContextMenu,
renderEndpointContextMenu: renderEndpointContextMenu,
renderSystemContextMenu: renderSystemContextMenu,
openMenu: openMenu,
closeMenus: closeMenus
};
});

View File

@@ -0,0 +1,423 @@
define(() => {
'use strict';
class Position {
constructor(config){
this._defaultConfig = {
container: null, // parent DOM container element
center: null, // DOM elements that works as center
elementClass: 'pf-system', // class for all elements
defaultGapX: 50,
defaultGapY: 50,
gapX: 50, // leave gap between elements (x-axis)
gapY: 50, // leave gap between elements (y-axis)
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
debug: false, // render debug elements
debugElementClass: 'pf-system-debug' // class for debug elements
};
this._config = Object.assign({}, this._defaultConfig, config);
/**
* convert degree into radial unit
* @param deg
* @returns {number}
* @private
*/
this._degToRad = (deg) => {
return deg * Math.PI / 180;
};
/**
* get element dimension/position of a DOM element
* @param element
* @returns {*}
* @private
*/
this._getElementDimension = element => {
let dim = null;
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)){
// xy 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){
// DOM element
left = element.style.left ? parseInt(element.style.left, 10) : 0;
top = element.style.top ? parseInt(element.style.top, 10) : 0;
a = parseInt((element.offsetWidth / 2).toString(), 10) + this._config.gapX;
b = parseInt((element.offsetHeight / 2).toString(), 10) + this._config.gapY;
width = element.offsetWidth;
height = element.offsetHeight;
}
// 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));
}
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 dim1 is partially overlapped by any other element
* @param dim1
* @param dimensionContainer
* @param allDimensions
* @returns {boolean}
* @private
*/
this._isOverlapping = (dim1, dimensionContainer, allDimensions) => {
let isOverlapping = false;
if(dim1){
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;
// render debug element
this._showDebugElement(dim1, percentCovered);
break;
}
}
}else{
isOverlapping = true;
this._showDebugElement(dim1, 100);
}
}else{
isOverlapping = true;
}
return isOverlapping;
};
/**
* find all dimensions around a centerDimension that are not overlapped by other elements
* @param maxResults
* @param steps
* @param allDimensions
* @param loops
* @returns {Array}
* @private
*/
this._findDimensions = (maxResults, steps, allDimensions, loops) => {
let dimensions = [];
let start = 0;
let end = 360;
let angle = end / steps;
let dimensionContainer = this._getElementDimension(this._getContainer());
steps = steps || 1;
loops = loops || 1;
if(loops === 1){
// check center element
let centerDimension = this._getElementDimension(this._config.center);
if(!this._isOverlapping(centerDimension, dimensionContainer, allDimensions)){
dimensions.push({
left: centerDimension.left,
top: centerDimension.top,
width: centerDimension.width,
height: centerDimension.height
});
// render debug element
this._showDebugElement(centerDimension, 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);
// transform relative x/y coordinate into a absolute 2D area
let checkDimension = this._transformCoordinate(centerDimension, coordinate);
if(!this._isOverlapping(checkDimension, dimensionContainer, allDimensions)){
dimensions.push({
left: checkDimension.left,
top: checkDimension.top,
width: checkDimension.width,
height: checkDimension.height
});
// render debug element
this._showDebugElement(checkDimension, 0);
maxResults--;
}
end -= angle;
}
if(maxResults > 0 && loops < this._config.loops){
loops++;
steps *= 2;
dimensions = dimensions.concat(this._findDimensions(maxResults, steps, allDimensions, 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
* @param dimension
* @param percentCovered
* @private
*/
this._showDebugElement = (dimension, percentCovered) => {
if(this._config.debug){
let element = document.createElement('div');
element.style.left = dimension.left + 'px';
element.style.top = dimension.top + 'px';
element.style.width = dimension.width + 'px';
element.style.height = dimension.height + 'px';
element.style.backgroundColor = Boolean(percentCovered) ? 'rgba(255,0,0,0.1)' : 'rgba(0,255,0,0.1)';
element.innerHTML = Math.round(percentCovered * 100) / 100 + '%';
element.classList.add(this._config.debugElementClass);
this._getContainer().appendChild(element);
}
};
/**
* 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 steps
* @returns {Array}
*/
this.findNonOverlappingDimensions = (maxResults, steps) => {
this._hideDebugElements();
// element dimensions that exist and should be checked for overlapping
let allDimensions = this._getAllElementDimensions();
return this._findDimensions(maxResults, steps, allDimensions);
};
}
}
/**
* 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
};
});

View File

@@ -0,0 +1,583 @@
/**
* map overlay functions for "Nearby" table
* Created by Exodus on 13.04.2017.
*/
define([
'jquery',
'app/init',
'app/util',
'app/map/util'
], function($, Init, Util, MapUtil){
'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);
overlayMain.on('click', function(){
let overlayMain = $(this).parent('.' + config.overlayLocalClass);
let isOpenStatus = isOpen(overlayMain);
// store current state in indexDB (client)
MapUtil.storeLocalData('map', mapId, 'showLocal', !isOpenStatus );
// trigger open/close
if( isOpenStatus ){
closeOverlay(overlay);
}else{
openOverlay(overlay);
}
});
overlayMain.initTooltips({
container: 'body',
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();
// update system relevant data in overlay -----------------------------------------------------------------
updateLocaleHeadline(overlay, systemData, characterAllIds.length, characterLocalIds.length);
// open Overlay -------------------------------------------------------------------------------------------
if( !isOpen(overlay) ){
let promiseStore = MapUtil.getLocaleData('map', mapId);
promiseStore.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);
require(['datatables.loader'], () => {
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&nbsp;&nbsp;&nbsp;',
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);
// set observer
setOverlayObserver(overlay, mapId);
parentElement.append(overlay);
// init local table ---------------------------------------------------------------------------------------
table.on('draw.dt', function(e, settings){
// init table tooltips
$(this).find('td').initTooltips({
container: 'body',
placement: 'left'
});
// hide pagination in case of only one page
let paginationElement = overlay.find('.dataTables_paginate');
let pageElements = paginationElement.find('span .paginate_button');
if(pageElements.length <= 1){
paginationElement.hide();
}else{
paginationElement.show();
}
});
// table init complete
table.on('init.dt', function(){
// init table head tooltips
$(this).initTooltips({
container: 'body',
placement: 'top'
});
});
let localTable = table.DataTable( {
pageLength: 13, // hint: if pagination visible => we need space to show it
paging: true,
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&nbsp;are&nbsp;alone</span>'
},
columnDefs: [
{
targets: 0,
orderable: true,
title: '<span title="jumps" data-toggle="tooltip">&nbsp;</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="' + Init.url.ccpImageServer + '/Render/' + data.typeId + '_32.png"/>';
}
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&nbsp;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();
});
};
});

View File

@@ -0,0 +1,224 @@
/**
* 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', 'pf-system-locked'];
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

View File

@@ -0,0 +1,892 @@
/**
* map overlay functions
*/
define([
'jquery',
'app/init',
'app/util',
'app/map/overlay/util',
'app/map/util'
], ($, Init, Util, MapOverlayUtil, MapUtil) => {
'use strict';
/**
* get map object (jsPlumb) from iconElement
* @param overlayIcon
* @returns {*}
*/
let getMapObjectFromOverlayIcon = overlayIcon => {
return MapUtil.getMapInstance(Util.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.batch(() => {
map.getAllConnections().forEach(connection => {
let connectionId = connection.getParameter('connectionId');
let sourceEndpoint = connection.endpoints[0];
let targetEndpoint = connection.endpoints[1];
let connectionData = connectionsData.hasOwnProperty(connectionId) ? connectionsData[connectionId] : undefined;
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 primLabel;
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;
primLabel = targetLabel.find(label => label !== 'K162');
}else{
// default arrow direction is fine
overlayType = 'arrow';
primLabel = 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(primLabel)){
// connection size from mapped signature
sizeLockedBySignature = true;
let wormholeData = Object.assign({}, Init.wormholes[primLabel]);
// get 'connection mass type' from wormholeData
let massType = Util.getObjVal(wormholeData, 'size.type');
if(massType && !connection.hasType(massType)){
MapOverlayUtil.getMapOverlay(connection.canvas, 'timer').startMapUpdateCounter();
MapUtil.setConnectionJumpMassType(connection, wormholeData.size.type);
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);
});
});
};
/**
* format json object with "time parts" into string
* @param parts
* @returns {string}
*/
let formatTimeParts = parts => {
let label = '';
if(parts.days){
label += parts.days + 'd ';
}
label += ('00' + parts.hours).slice(-2);
label += ':' + ('00' + parts.min).slice(-2);
return label;
};
/**
* 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.batch(() => {
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);
});
};
/**
* 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 = Util.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 = Util.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();
systemHead.popover({
placement: 'right',
html: true,
trigger: 'manual',
container: mapElement,
title: false,
content: Util.getSystemRegionTable(
system.data('region'),
system.data('faction') || null
)
});
}
systemHead.setPopoverSmall();
systemHead.popover('show');
});
},
out: function(e){
let mapElement = Util.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 = [
formatTimeParts(createdDiff) + '&nbsp;<i class="fas fa-fw fa-plus-square"></i>',
formatTimeParts(updatedDiff) + '&nbsp;<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>&nbsp;' + 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
* @returns {*}
*/
let setMapUpdateCounter = (mapOverlayTimer, percent, value) => {
// check if counter already exists
let counterChart = MapOverlayUtil.getMapCounter(mapOverlayTimer);
if(counterChart.length === 0){
// create new counter
counterChart = $('<div>', {
class: [Init.classes.pieChart.class, Init.classes.pieChart.pieChartMapCounterClass].join(' ')
}).attr('data-percent', percent).append(
$('<span>', {
text: value
})
);
mapOverlayTimer.append(counterChart);
// init counter
counterChart.initMapUpdateCounter();
// set tooltip
mapOverlayTimer.attr('data-placement', 'left');
mapOverlayTimer.attr('title', 'update counter');
mapOverlayTimer.tooltip();
}
return counterChart;
};
/**
* start the map update counter or reset
*/
$.fn.startMapUpdateCounter = function(){
let mapOverlayTimer = $(this);
let counterChart = MapOverlayUtil.getMapCounter(mapOverlayTimer);
let maxSeconds = MapOverlayUtil.config.logTimerCount;
let counterChartLabel = counterChart.find('span');
let percentPerCount = 100 / maxSeconds;
// update counter
let updateChart = tempSeconds => {
let pieChart = counterChart.data('easyPieChart');
if(pieChart !== undefined){
counterChart.data('easyPieChart').update( percentPerCount * tempSeconds);
}
counterChartLabel.text(tempSeconds);
};
// main timer function is called on any counter update
let timer = mapUpdateCounter => {
// decrease timer
let currentSeconds = counterChart.data('currentSeconds');
currentSeconds--;
counterChart.data('currentSeconds', currentSeconds);
if(currentSeconds >= 0){
// update counter
updateChart(currentSeconds);
}else{
// hide counter and reset
clearInterval(mapUpdateCounter);
mapOverlayTimer.velocity('transition.whirlOut', {
duration: Init.animationSpeed.mapOverlay,
complete: function(){
counterChart.data('interval', false);
Util.getMapElementFromOverlay(mapOverlayTimer).trigger('pf:unlocked');
}
});
}
};
// get current seconds (in case the timer is already running)
let currentSeconds = counterChart.data('currentSeconds');
// start values for timer and chart
counterChart.data('currentSeconds', maxSeconds);
updateChart(maxSeconds);
if(
currentSeconds === undefined ||
currentSeconds < 0
){
// start timer
let mapUpdateCounter = setInterval(() => {
timer(mapUpdateCounter);
}, 1000);
// store counter interval
counterChart.data('interval', mapUpdateCounter);
// show overlay
if(mapOverlayTimer.is(':hidden')){
mapOverlayTimer.velocity('stop').velocity('transition.whirlIn', { duration: Init.animationSpeed.mapOverlay });
}
}
};
/**
* 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
for(let prop in options){
if(options.hasOwnProperty(prop)){
let icon = $('<i>', {
class: options[prop].iconClass.concat( ['pull-right', options[prop].class] ).join(' ')
}).attr('title', options[prop].title).tooltip({
placement: 'bottom',
container: 'body',
delay: 150
});
// add "hover" action for some icons
if(
options[prop].trigger === 'hover' ||
options[prop].trigger === 'refresh'
){
icon.hoverIntent(options[prop].hoverIntent);
}
// add "click" handler for some icons
if(options[prop].hasOwnProperty('onClick')){
icon.on('click', options[prop].onClick);
}
mapOverlayInfo.append(icon);
}
}
parentElement.append(mapOverlayInfo);
// reset map update timer
setMapUpdateCounter(mapOverlayTimer, 100, MapOverlayUtil.config.logTimerCount);
});
};
return {
showInfoSignatureOverlays: showInfoSignatureOverlays,
hideInfoSignatureOverlays: hideInfoSignatureOverlays,
updateZoomOverlay: updateZoomOverlay,
initMapDebugOverlays: initMapDebugOverlays
};
});

View File

@@ -0,0 +1,97 @@
/**
* 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
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
// 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 mapWrapperElement = $(element).parents('.' + config.mapWrapperClass);
let mapOverlay = null;
switch(overlayType){
case 'timer':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayTimerClass);
break;
case 'info':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayInfoClass);
break;
case 'zoom':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayZoomClass);
break;
case 'local':
mapOverlay = mapWrapperElement.find('.' + config.overlayLocalClass);
break;
}
return mapOverlay;
};
/**
* get the map counter chart from overlay
* @param element
* @returns {jQuery}
*/
let getMapCounter = element => $(element).find('.' + Init.classes.pieChart.pieChartMapCounterClass);
/**
* get interval value from map timer overlay
* @param element
* @returns {*}
*/
let getMapOverlayInterval = element => getMapCounter(getMapOverlay(element, 'timer')).data('interval');
return {
config: config,
getMapOverlay: getMapOverlay,
getMapCounter: getMapCounter,
getMapOverlayInterval: getMapOverlayInterval
};
});

View File

@@ -0,0 +1,114 @@
/**
* Created by Exodus on 26.06.2016.
*/
define([
'jquery',
'app/init',
'app/util',
'mousewheel',
'customScrollbar'
], ($, Init, Util) => {
'use strict';
/**
* init map scrollbar
* @param config
*/
$.fn.initCustomScrollbar = function(config){
// default config -------------------------------------------------------------------------
let defaultConfig = {
axis: 'yx',
theme: 'light-3' ,
scrollInertia: 300,
autoExpandScrollbar: false,
scrollButtons:{
enable: true,
scrollAmount: 30,
scrollType: 'stepless'
},
callbacks: {
onTotalScrollOffset: 0,
onTotalScrollBackOffset: 0,
alwaysTriggerOffsets: true
},
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
};
// init -----------------------------------------------------------------------------------
config = $.extend(true, {}, defaultConfig, config);
return this.each(function(){
let mapWrapperElement = $(this);
// prevent multiple initialization
mapWrapperElement.mCustomScrollbar('destroy');
// init custom scrollbars
mapWrapperElement.mCustomScrollbar(config);
});
};
/**
* scroll to a specific position on map
* demo: http://manos.malihu.gr/repository/custom-scrollbar/demo/examples/scrollTo_demo.html
* @param scrollWrapper
* @param position
* @param options
*/
let scrollToPosition = (scrollWrapper, position, options) => {
$(scrollWrapper).mCustomScrollbar('scrollTo', position, options);
};
/**
* scroll to a specific system on map
* -> subtract some offset for tooltips/connections
* @param scrollWrapper
* @param position
* @param options
*/
let scrollToSystem = (scrollWrapper, position, options) => {
position = getOffsetPosition(position, {x: -15, y: -35});
scrollToPosition(scrollWrapper, position, options);
};
/**
* add/subtract offset coordinates from position
* -> no negative values returned
* @param position
* @param offset
* @returns {{x: number, y: number}}
*/
let getOffsetPosition = (position, offset) => {
return {
x: Math.max(0, position.x + offset.x),
y: Math.max(0, position.y + offset.y)
};
};
return {
scrollToPosition: scrollToPosition,
scrollToSystem: scrollToSystem
};
});

View File

@@ -0,0 +1,825 @@
/**
* map system functions
*/
define([
'jquery',
'app/init',
'app/util',
'bootbox',
'app/map/util',
'app/map/layout',
'app/map/magnetizing'
], ($, Init, Util, bootbox, MapUtil, Layout, Magnetizer) => {
'use strict';
let config = {
newSystemOffset: {
x: 130,
y: 0
},
mapClass: 'pf-map', // class for all maps
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
systemActiveClass: 'pf-system-active', // class for an active system on a map
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>&nbsp' + createdTime);
dialogElement.find('#' + config.dialogSystemUpdatedId).html('<i class="fas fa-fw fa-pen"></i>&nbsp' + 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.getCurrentCharacterLog();
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 = {
x: 0,
y: 0
};
// add new position
let sourceSystem = null;
if(options.sourceSystem !== undefined){
sourceSystem = options.sourceSystem;
// get new position
newPosition = calculateNewSystemPosition(sourceSystem);
}else if(options.position){
// check mouse cursor position (add system to map)
newPosition = {
x: options.position.x,
y: options.position.y
};
}
formData.position = newPosition;
formData.mapId = mapId;
// ----------------------------------------------------------------------------------------
this.find('.modal-content').showLoadingAnimation();
Util.request('PUT', 'system', [], formData, {
systemDialog: systemDialog,
formElement: form,
map: map,
sourceSystem: sourceSystem
}, 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);
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>&nbsp;' + 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,
viewport: system.closest('.' + 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);
// check if system is "active"
if(system.hasClass(config.systemActiveClass)){
delete Init.currentSystemData;
// 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(system.data('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 - relativ to a source system
* @param sourceSystem
* @returns {{x: *, y: *}}
*/
let calculateNewSystemPosition = sourceSystem => {
let mapContainer = sourceSystem.parent();
let grid = [MapUtil.config.mapSnapToGridDimension, MapUtil.config.mapSnapToGridDimension];
let x = 0;
let y = 0;
let positionFinder = new Layout.Position({
container: mapContainer[0],
center: sourceSystem[0],
loops: 4,
grid: mapContainer.hasClass(MapUtil.config.mapGridClass) ? grid : false,
debug: false
});
let dimensions = positionFinder.findNonOverlappingDimensions(1, 16);
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.shattered){
headInfoLeft.push('<i class="fas fa-fw fa-skull ' + 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('&nbsp;&nbsp;')
}),
$('<div>', {
class: config.systemHeadInfoRightClass,
html: headInfoRight.join('&nbsp;&nbsp;')
})
);
}
return headInfo;
};
return {
showNewSystemDialog: showNewSystemDialog,
deleteSystems: deleteSystems,
removeSystems: removeSystems,
getHeadInfoElement: getHeadInfoElement
};
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,154 @@
/**
* 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
};
});

View File

@@ -0,0 +1,605 @@
/**
* Main map application
*/
define([
'jquery',
'app/init',
'app/util',
'app/logging',
'app/page',
'app/map/worker',
'app/module_map',
'app/key',
'app/ui/form_element'
], ($, Init, Util, Logging, Page, MapWorker, ModuleMap) => {
'use strict';
/**
* main init "map" page
*/
$(() => {
Util.initPrototypes();
// clear sessionStorage
//Util.clearSessionStorage();
// set default AJAX config
Util.ajaxSetup();
// set default dialog config
Util.initDefaultBootboxConfig();
// set default select2 config
Util.initDefaultSelect2Config();
// set default xEditable config
Util.initDefaultEditableConfig();
// load page
Page.loadPageStructure();
// show app information in browser console
Util.showVersionInfo();
// init logging
Logging.init();
let mapModule = $('#' + Util.config.mapModuleId);
// main update intervals/trigger (heartbeat)
let updateTimeouts = {
mapUpdate: 0,
userUpdate: 0
};
/**
* clear both main update timeouts
* -> stop program from working -> shutdown
*/
let clearUpdateTimeouts = () => {
for(let intervalKey in updateTimeouts){
if(updateTimeouts.hasOwnProperty(intervalKey)){
clearTimeout(updateTimeouts[intervalKey]);
}
}
};
/**
* Ajax error response handler function for main-ping functions
* @param jqXHR
* @param status
* @param error
*/
let handleAjaxErrorResponse = (jqXHR, status, error) => {
// clear both main update request trigger timer
clearUpdateTimeouts();
let reason = status + ' ' + jqXHR.status + ': ' + error;
let errorData = [];
let redirect = false; // redirect user to other page e.g. login
let reload = true; // reload current page (default: true)
if(jqXHR.responseJSON){
// handle JSON
let responseObj = jqXHR.responseJSON;
if(
responseObj.error &&
responseObj.error.length > 0
){
errorData = responseObj.error;
}
if(responseObj.reroute){
redirect = responseObj.reroute;
}
}else{
// handle HTML
errorData.push({
type: 'error',
message: 'Please restart and reload this page'
});
}
console.error(' ↪ %s Error response: %o', jqXHR.url, errorData);
$(document).trigger('pf:shutdown', {
status: jqXHR.status,
reason: reason,
error: errorData,
redirect: redirect,
reload: reload
});
};
// map init functions =========================================================================================
/**
* get static init data and store response
* @returns {Promise<any>}
*/
let initData = () => {
/**
* add wormhole size data for each wormhole
* @param wormholes
* @returns {*}
*/
let addWormholeSizeData = wormholes => {
for(let [wormholeName, wormholeData] of Object.entries(wormholes)){
wormholeData.class = Util.getSecurityClassForSystem(wormholeData.security);
for(let [sizeName, sizeData] of Object.entries(Init.wormholeSizes)){
if(wormholeData.massIndividual >= sizeData.jumpMassMin){
wormholeData.size = sizeData;
break;
}
}
}
return wormholes;
};
let initDataExecutor = (resolve, reject) => {
$.getJSON(Init.path.initData).done(response => {
if( response.error.length > 0 ){
for(let i = 0; i < response.error.length; i++){
Util.showNotify({
title: response.error[i].title,
text: response.error[i].message,
type: response.error[i].type
});
}
}
Init.timer = response.timer;
Init.mapTypes = response.mapTypes;
Init.mapScopes = response.mapScopes;
Init.connectionScopes = response.connectionScopes;
Init.systemStatus = response.systemStatus;
Init.systemType = response.systemType;
Init.wormholes = addWormholeSizeData(response.wormholes);
Init.characterStatus = response.characterStatus;
Init.routes = response.routes;
Init.url = response.url;
Init.character = response.character;
Init.slack = response.slack;
Init.discord = response.discord;
Init.structureStatus = response.structureStatus;
Init.universeCategories = response.universeCategories;
Init.routeSearch = response.routeSearch;
resolve({
action: 'initData',
data: false
});
}).fail((jqXHR, status, error) => {
reject({
action: 'shutdown',
data: {
jqXHR: jqXHR,
status: status,
error: error
}
});
});
};
return new Promise(initDataExecutor);
};
/**
* get mapAccess Data for WebSocket subscription
* @returns {Promise<any>}
*/
let getMapAccessData = () => {
let getMapAccessDataExecutor = (resolve, reject) => {
$.getJSON(Init.path.getAccessData).done(response => {
resolve({
action: 'mapAccessData',
data: response
});
}).fail((jqXHR, status, error) => {
reject({
action: 'shutdown',
data: {
jqXHR: jqXHR,
status: status,
error: error
}
});
});
};
return new Promise(getMapAccessDataExecutor);
};
/**
* init main mapModule
* -> initData() needs to be resolved first!
* @param payload
* @returns {Promise<any>}
*/
let initMapModule = payload => {
let initMapModuleExecutor = (resolve, reject) => {
// init browser tab change observer, Once the timers are available
Page.initTabChangeObserver();
// init hidden context menu elements
Page.renderMapContextMenus();
// init map module
mapModule.initMapModule();
resolve({
action: 'initMapModule',
data: false
});
};
return new Promise(initMapModuleExecutor);
};
/**
* request all map access data (tokens) -> required wor WebSocket subscription
* -> initData() needs to be resolved first!
* @param payloadMapAccessData
* @returns {Promise<any>}
*/
let initMapWorker = payloadMapAccessData => {
let initMapWorkerExecutor = (resolve, reject) => {
let getPayload = command => {
return {
action: 'initMapWorker',
data: {
syncStatus: Init.syncStatus.type,
command: command
}
};
};
let validMapAccessData = false;
if(payloadMapAccessData && payloadMapAccessData.action === 'mapAccessData'){
let response = payloadMapAccessData.data;
if(response.status === 'OK'){
validMapAccessData = true;
// init SharedWorker for maps
MapWorker.init({
characterId: response.data.id,
callbacks: {
onInit: (MsgWorkerMessage) => {
Util.setSyncStatus(MsgWorkerMessage.command);
},
onOpen: (MsgWorkerMessage) => {
Util.setSyncStatus(MsgWorkerMessage.command, MsgWorkerMessage.meta());
MapWorker.send('subscribe', response.data);
resolve(getPayload(MsgWorkerMessage.command));
},
onGet: (MsgWorkerMessage) => {
switch(MsgWorkerMessage.task()){
case 'mapUpdate':
Util.updateCurrentMapData(MsgWorkerMessage.data());
ModuleMap.updateMapModule(mapModule);
break;
case 'mapAccess':
case 'mapDeleted':
Util.deleteCurrentMapData(MsgWorkerMessage.data());
ModuleMap.updateMapModule(mapModule);
break;
case 'mapSubscriptions':
Util.updateCurrentMapUserData(MsgWorkerMessage.data());
ModuleMap.updateActiveMapUserData(mapModule);
break;
}
Util.setSyncStatus('ws:get');
},
onClosed: (MsgWorkerMessage) => {
Util.setSyncStatus(MsgWorkerMessage.command, MsgWorkerMessage.meta());
reject(getPayload(MsgWorkerMessage.command));
},
onError: (MsgWorkerMessage) => {
Util.setSyncStatus(MsgWorkerMessage.command, MsgWorkerMessage.meta());
reject(getPayload(MsgWorkerMessage.command));
}
}
});
}
}
if( !validMapAccessData ){
reject(getPayload('Invalid mapAccessData'));
}
};
return new Promise(initMapWorkerExecutor);
};
// run all init functions for mainModule and WebSocket configuration async
Promise.all([initData(), getMapAccessData()])
.then(payload => Promise.all([initMapModule(payload[0]), initMapWorker(payload[1])]))
.then(payload => {
// mapModule initialized and WebSocket configuration working
console.ok('Client syncStatus: %s. %O resolved by command: %s!',
payload[1].data.syncStatus,
payload[1].action + '()',
payload[1].data.command
);
})
.catch(payload => {
switch(payload.action){
case 'shutdown':
// ajax error
handleAjaxErrorResponse(payload.data.jqXHR, payload.data.status, payload.data.error);
break;
case 'initMapWorker':
// WebSocket not working -> no error here -> fallback to Ajax
console.info('Client syncStatus: %s. %O rejected by command: %s! payload: %o',
payload.data.syncStatus,
payload.action + '()',
payload.data.command,
payload.data
);
break;
default:
console.error('Unhandled error thrown while initialization: %o ', payload);
}
});
/**
* main function for init all map relevant trigger calls
*/
$.fn.initMapModule = function(){
let mapModule = $(this);
// log keys -----------------------------------------------------------------------------------------------
let logKeyServerMapData = Init.performanceLogging.keyServerMapData;
let logKeyServerUserData = Init.performanceLogging.keyServerUserData;
let locationToggle = $('#' + Util.config.headMapTrackingId);
// ping for main map update ===============================================================================
/**
* @param forceUpdateMapData // force request to be send
*/
let triggerMapUpdatePing = (forceUpdateMapData) => {
// check each interval if map module is still available
let check = $('#' + mapModule.attr('id')).length;
if(check === 0){
// program crash stop any update
return;
}
// get updated map data
let updatedMapData = {
mapData: ModuleMap.getMapModuleDataForUpdate(mapModule),
getUserData: Util.getCurrentUserData() ? 0 : 1
};
// check if mapUpdate trigger should be send
// -> if "syncType" === "ajax" -> send always
// -> if "syncType" === "webSocket" -> send initial AND on map changes
if(
forceUpdateMapData ||
Util.getSyncType() === 'ajax' ||
(
Util.getSyncType() === 'webSocket' &&
updatedMapData.mapData.length
)
){
// start log
Util.timeStart(logKeyServerMapData);
// store updatedMapData
$.ajax({
type: 'POST',
url: Init.path.updateMapData,
data: updatedMapData,
dataType: 'json'
}).done((data) => {
// log request time
let duration = Util.timeStop(logKeyServerMapData);
Util.log(logKeyServerMapData, {duration: duration, type: 'server', description: 'request map data'});
Util.setSyncStatus('ajax:get');
if(
data.error &&
data.error.length > 0
){
// any error in the main trigger functions result in a user log-off
Util.triggerMenuAction(document, 'Logout');
}else{
$(document).setProgramStatus('online');
if(data.userData !== undefined){
// store current user data global (cache)
Util.setCurrentUserData(data.userData);
}
// map data found
Util.setCurrentMapData(data.mapData);
// load/update main map module
ModuleMap.updateMapModule(mapModule).then(() => {
// map update done, init new trigger
// get the current update delay (this can change if a user is inactive)
let mapUpdateDelay = Util.getCurrentTriggerDelay(logKeyServerMapData, 0);
// init new trigger
initMapUpdatePing(false);
// initial start for the userUpdate trigger
// this should only be called at the first time!
if(updateTimeouts.userUpdate === 0){
// start user update trigger after map loaded
updateTimeouts.userUpdate = setTimeout(() => {
triggerUserUpdatePing();
}, 1000);
}
});
}
}).fail(handleAjaxErrorResponse);
}else{
// skip this mapUpdate trigger and init next one
initMapUpdatePing(false);
}
};
// ping for user data update ==============================================================================
let triggerUserUpdatePing = () => {
// IMPORTANT: Get user data for ONE map that is currently visible
// On later releases this can be easy changed to "full update" all maps for a user
//
let mapIds = [];
let activeMap = Util.getMapModule().getActiveMap();
if(activeMap){
mapIds = [ activeMap.data('id') ];
}
let updatedUserData = {
mapIds: mapIds,
getMapUserData: Util.getSyncType() === 'webSocket' ? 0 : 1,
mapTracking: locationToggle.is(':checked') ? 1 : 0, // location tracking
systemData: Util.getCurrentSystemData()
};
Util.timeStart(logKeyServerUserData);
$.ajax({
type: 'POST',
url: Init.path.updateUserData,
data: updatedUserData,
dataType: 'json'
}).done((data) => {
// log request time
let duration = Util.timeStop(logKeyServerUserData);
Util.log(logKeyServerUserData, {duration: duration, type: 'server', description:'request user data'});
if(
data.error &&
data.error.length > 0
){
// any error in the main trigger functions result in a user log-off
Util.triggerMenuAction(document, 'Logout');
}else{
$(document).setProgramStatus('online');
if(data.userData !== undefined){
// store current user data global (cache)
Util.setCurrentUserData(data.userData);
// update system info panels
if(data.system){
ModuleMap.updateSystemModulesData(mapModule, data.system);
}
// store current map user data (cache)
if(data.mapUserData !== undefined){
Util.setCurrentMapUserData(data.mapUserData);
}
// update map module character data
ModuleMap.updateActiveMapUserData(mapModule).then(() => {
// map module update done, init new trigger
initMapUserUpdatePing();
});
}
}
}).fail(handleAjaxErrorResponse);
};
/**
* init (schedule) next MapUpdate Ping
*/
let initMapUpdatePing = (forceUpdateMapData) => {
// get the current update delay (this can change if a user is inactive)
let delay = Util.getCurrentTriggerDelay(logKeyServerMapData, 0);
updateTimeouts.mapUpdate = setTimeout((forceUpdateMapData) => {
triggerMapUpdatePing(forceUpdateMapData);
}, delay, forceUpdateMapData);
};
/**
* init (schedule) next MapUserUpdate Ping
*/
let initMapUserUpdatePing = () => {
// get the current update delay (this can change if a user is inactive)
let delay = Util.getCurrentTriggerDelay(logKeyServerUserData, 0);
updateTimeouts.userUpdate = setTimeout(() => {
triggerUserUpdatePing();
}, delay);
};
// initial start of the map update function
triggerMapUpdatePing(true);
/**
* handles "final" map update request before window.unload event
* -> navigator.sendBeacon browser support required
* ajax would not work here, because browsers might cancel the request!
* @param mapModule
*/
let mapUpdateUnload = mapModule => {
// get updated map data
let mapData = ModuleMap.getMapModuleDataForUpdate(mapModule);
if(mapData.length){
let fd = new FormData();
fd.set('mapData', JSON.stringify(mapData));
navigator.sendBeacon(Init.path.updateUnloadData, fd);
console.info('Map update request send by: %O', navigator.sendBeacon);
}
};
// Send map update request on tab close/reload, in order to save map changes that
// haven´t been saved through default update trigger
window.addEventListener('beforeunload', function(e){
// close "SharedWorker" connection
MapWorker.close();
// clear periodic update timeouts
// -> this function will handle the final map update request
clearUpdateTimeouts();
// save unsaved map changes ...
if(navigator.sendBeacon){
mapUpdateUnload(mapModule);
}else{
// fallback if sendBeacon() is not supported by browser
triggerMapUpdatePing();
}
// check if character should be switched on reload or current character should be loaded afterwards
let characterSwitch = Boolean( $('body').data('characterSwitch') );
if(!characterSwitch){
let characterId = Util.getCurrentCharacterId();
if(characterId){
Util.setCookie('old_char_id', characterId, 3, 's');
}
}
// IMPORTANT, return false in order to not "abort" ajax request in background!
return false;
}, false);
};
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,199 @@
define([
'jquery',
'app/init',
'pnotify',
//'pnotify.buttons',
//'pnotify.confirm',
'pnotify.nonblock',
'pnotify.desktop',
//'pnotify.history',
'pnotify.callbacks'
], function($, Init, PNotify){
'use strict';
let config = {
title: '',
text: '',
type: '', // 'info', 'success', error, 'warning'
icon: false,
styling: 'fontawesome', // 'fontawesome', 'bootstrap3', 'jqueryui'
animate_speed: 'fast', // animation speed for notifications moving up/down
hide: true, // close after few seconds
delay: 5000, // visible time for notification in browser
mouse_reset: true, // Reset the hide timer if the mouse moves over the notice.
shadow: true,
addclass: 'stack-bottomright', // class for display, must changed on stack different stacks
width: '250px',
// nonblock extension parameter (click through notifications)
nonblock: {
nonblock: true, // change for enable
nonblock_opacity: 0.9
},
// desktop extension "Web Notifications"
desktop: {
desktop: false, // change for enable
icon: Init.path.img + 'notifications/logo.png' // default image for desktop notifications
}
};
// initial page title (cached)
let initialPageTitle = document.title;
// global blink timeout cache
let blinkTimer;
// stack container for all notifications
let stack = {
bottomRight: {
stack: {
dir1: 'up',
dir2: 'left',
firstpos1: 30,
firstpos2: 10,
spacing1: 5,
spacing2: 5,
push: 'bottom'
},
addclass: 'stack-bottomright',
width: '250px',
},
barBottom: {
stack: {
dir1: 'up',
dir2: 'right',
// context: $('body'),
spacing1: 0,
spacing2: 0
},
addclass: 'stack-bar-bottom',
width: '70%',
}
};
/**
* show a notification in browser and/or "Web Notifications" in OS
* @param customConfig
* @param settings
*/
let showNotify = function(customConfig, settings){
customConfig = $.extend(true, {}, config, customConfig );
// desktop notification
if(
settings &&
settings.desktop === true
){
// ask for Web Notifications permission
PNotify.desktop.permission();
customConfig.delay = 10000;
customConfig.nonblock.nonblock = false; // true results in error on "click" desktop notification
customConfig.desktop.desktop = true;
// make browser tab blink
startTabBlink(customConfig.title);
}
// set notification stack
if(
settings &&
settings.stack
){
customConfig.stack = stack[settings.stack].stack;
customConfig.addclass = stack[settings.stack].addclass;
customConfig.width = stack[settings.stack].width;
}else{
customConfig.stack = stack.bottomRight.stack;
customConfig.addclass = stack.bottomRight.addclass;
}
switch(customConfig.type){
case 'info':
customConfig.icon = 'fas fa-info fa-fw fa-lg';
break;
case 'success':
customConfig.icon = 'fas fa-check fa-fw fa-lg';
break;
case 'warning':
customConfig.icon = 'fas fa-exclamation-triangle fa-fw fa-lg';
break;
case 'error':
customConfig.icon = 'fas fa-times fa-fw fa-lg';
break;
case 'lock':
customConfig.icon = 'fas fa-lock fa-fw fa-lg';
customConfig.type = 'success';
break;
case 'unlock':
customConfig.icon = 'fas fa-unlock fa-fw fa-lg';
customConfig.type = 'info';
break;
default:
customConfig.icon = false;
}
let notify = new PNotify(customConfig);
if(
settings &&
settings.click
){
// set onclick for notification
notify.get().on('click', settings.click);
}
};
/**
* change document.title and make the browsers tab blink
* @param blinkTitle
*/
let startTabBlink = function(blinkTitle){
let initBlink = (function(blinkTitle){
// count blinks if tab is currently active
let activeTabBlinkCount = 0;
let blink = function(){
// number of "blinks" should be limited if tab is currently active
if(window.isVisible){
activeTabBlinkCount++;
}
// toggle page title
document.title = (document.title === blinkTitle) ? initialPageTitle : blinkTitle;
if(activeTabBlinkCount > 10){
stopTabBlink();
}
};
return function(){
if(!blinkTimer){
blinkTimer = setInterval(blink, 1000);
}
};
}( blinkTitle ));
initBlink();
};
/**
* stop blinking document.title
*/
let stopTabBlink = function(){
if(blinkTimer){
clearInterval(blinkTimer);
document.title = initialPageTitle;
blinkTimer = null;
}
};
return {
showNotify: showNotify,
startTabBlink: startTabBlink,
stopTabBlink: stopTabBlink
};
});

1422
public/js/v1.5.4/app/page.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
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';
}
};
});

View File

@@ -0,0 +1,69 @@
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 promise = 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);
});
});
}
};
});

View File

@@ -0,0 +1,150 @@
/**
* 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
*/
let highlightJson = (obj) => {
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 = '(() => {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('&lt;').split('>').join('&gt;');
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');
let str = '';
for(let i = 0; i < funcStrArray.length; i++){
str += ((i === 0) ? '' : tabs) + funcStrArray[i] + '\n';
}
return str;
};
let highlight = (obj, indent, addComma, isArray, isPropertyContent) => {
let 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="fas fa-fw fa-plus-square" onClick="' + expImageClicked + '"></i></span><span class="collapsible">' : '';
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() + '*/', '', comma, indent, isArray, '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="fas fa-fw fa-plus-square" onClick="' + expImageClicked + '"></i></span><span class="collapsible">' : '';
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
};
});

View File

@@ -0,0 +1,359 @@
/**
* Main setupPage application
*/
define([
'jquery',
'app/init',
'app/util',
'app/map/worker',
'mustache',
], function($, Init, Util, MapWorker, Mustache){
'use strict';
let config = {
splashOverlayClass: 'pf-splash', // class for "splash" overlay
webSocketStatsId: 'pf-setup-webSocket-stats', // id for webSocket "stats" panel
webSocketRefreshStatsId: 'pf-setup-webSocket-stats-refresh' // class for "reload stats" button
};
/**
* send ajax request for index build
* @param url
* @param requestData
* @param context
* @param callback
*/
let sendRequest = (url, requestData, context, callback) => {
if(requestData.count === 0){
// first iteration
context.target.button('loading');
}
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data: requestData,
context: context
}).done(function(data){
callback(this, data);
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': Failed. Please retry', text: reason, type: 'warning'});
this.target.button('reset');
});
};
/**
*
* @param container
* @param selector
*/
let setCollapseObserver = (container, selector) => {
container.find(selector).css({cursor: 'pointer'});
container.on('click', selector, function(){
$(this).find('.pf-animate-rotate').toggleClass('right');
});
};
/**
* set page observer
*/
let setPageObserver = () => {
let body = $('body');
// navigation (scroll) ----------------------------------------------------------------------------------------
Util.initPageScroll(body);
// collapse ---------------------------------------------------------------------------------------------------
setCollapseObserver(body, '[data-toggle="collapse"]');
// buttons ----------------------------------------------------------------------------------------------------
// exclude "download" && "navigation" buttons
body.find('.btn')
.not('.navbar-fixed-bottom .btn')
.not('[data-action="clearIndex"]')
.not('[data-action="buildIndex"]')
.not('[href^="?export"]').on('click', function(e){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
// build/clear index buttons ----------------------------------------------------------------------------------
// clear index buttons ----------------------------------------------------------------------------------------
body.find('.btn[data-action="buildIndex"], .btn[data-action="clearIndex"]').on('click', function(e){
e.preventDefault();
let element = $(this);
let url = '/api/setup/' + element.attr('data-action');
sendRequest(url, {
type: element.attr('data-type'),
count: 0,
offset: 0
}, {
target: element,
url: url
}, updateIndexCount);
});
// tooltips ---------------------------------------------------------------------------------------------------
body.find('[title]').tooltip();
// change url (remove logout parameter)
if(history.pushState){
history.pushState({}, '', location.protocol + '//' + location.host + location.pathname);
}
};
/**
* update data count label for "indexed data"
* @param context
* @param responseData
*/
let updateIndexCount = (context, responseData) => {
let countElement = context.target.closest('.row').children().eq(1).find('kbd');
countElement.text(responseData.countBuildAll + '/' + responseData.countAll);
countElement.removeClass('txt-color-success txt-color-danger txt-color-warning');
if(responseData.countBuildAll >=responseData.countAll){
countElement.addClass('txt-color-success');
}else if(responseData.countBuildAll > 0){
countElement.addClass('txt-color-warning');
}else{
countElement.addClass('txt-color-danger');
}
context.target.find('.btn-progress').html('&nbsp;&nbsp;' + responseData.progress + '%').css('width', responseData.progress + '%');
// send next chunk of rows -> import only
if(
context.target.attr('data-action') === 'buildIndex' &&
responseData.countBuildAll < responseData.countAll
){
sendRequest(context.url, {
type: responseData.type,
count: responseData.count,
offset: responseData.offset
}, {
target: context.target,
url: context.url
}, updateIndexCount);
}else{
context.target.button('reset');
}
};
/**
* get WebSockets "subscriptions" <table> HTML
* @param subscriptionStats
* @returns {Promise<any>}
*/
let getWebSocketSubscriptionTable = subscriptionStats => {
let executor = resolve => {
requirejs(['text!templates/modules/subscriptions_table.html', 'mustache'], (template, Mustache) => {
let data = {
panelId: config.webSocketStatsId,
refreshButtonId: config.webSocketRefreshStatsId,
subStats: subscriptionStats,
channelCount: (Util.getObjVal(subscriptionStats, 'channels') || []).length
};
resolve(Mustache.render(template, data));
});
};
return new Promise(executor);
};
/**
* perform a basic check if Clients (browser) can connect to the webSocket server
*/
let testWebSocket = () => {
let tcpSocketPanel = $('#pf-setup-tcpSocket');
let webSocketPanel = $('#pf-setup-webSocket');
let webSocketURI = MapWorker.getWebSocketURL();
let sslIcon = webSocketURI.startsWith('wss:') ?
'<i class="fas fa-fw fa-lock txt-color txt-color-success"></i>' :
'<i class="fas fa-fw fa-unlock txt-color txt-color-warning"></i>';
webSocketPanel.showLoadingAnimation();
let removeColorClasses = (el) => {
el.removeClass (function(index, css){
return (css.match (/\btxt-color-\S+/g) || []).join(' ');
});
};
/**
* updates the WebSocket panel with new data
* @param data
*/
let updateWebSocketPanel = (data) => {
let badgeSocketWarning = $('.navbar a[data-anchor="#pf-setup-socket"] .txt-color-warning');
let badgeSocketDanger = $('.navbar a[data-anchor="#pf-setup-socket"] .txt-color-danger');
let socketWarningCount = parseInt(badgeSocketWarning.text()) || 0;
let socketDangerCount = parseInt(badgeSocketDanger.text()) || 0;
if(data.uri){
let uriRow = webSocketPanel.find('.panel-body').filter(':first').find('table tr');
uriRow.find('td:nth-child(2) kbd').html(data.uri.value);
if(data.uri.status){
let statusIcon = uriRow.find('td:nth-child(3) i');
removeColorClasses(statusIcon);
statusIcon.toggleClass('fa-exclamation-triangle', false).toggleClass('fa-check', true).addClass('txt-color-success');
// update head badge. "Decrease" warning count, default for "URI" connection is "warn + 1"
socketWarningCount--;
}
}
if(data.status){
let footer = webSocketPanel.find('.panel-footer h3');
removeColorClasses(footer);
footer.text(data.status.label).addClass(data.status.class);
// update head badge
switch(data.status.type){
case 'success':
socketWarningCount = '';
socketDangerCount = '';
break;
case 'warning':
break;
case 'danger':
socketDangerCount = 1;
break;
}
}
badgeSocketWarning.text(socketWarningCount ? socketWarningCount : '');
badgeSocketDanger.text(socketDangerCount ? socketDangerCount : '');
};
// update initial
updateWebSocketPanel({
uri: {
value: sslIcon + '&nbsp;' + webSocketURI,
status: true
},
status: {
type: 'warning',
label: 'CONNECTING…',
class: 'txt-color-warning'
}
});
/**
* @param socket
* @param task
* @param load
*/
let sendMessage = (socket, task, load) => {
socket.send(JSON.stringify({
task: task,
load: load
}));
};
// try to connect to WebSocket server
let socket = new WebSocket(webSocketURI);
socket.onopen = (e) => {
updateWebSocketPanel({
status: {
type: 'warning',
label: 'OPEN wait for response…',
class: 'txt-color-warning'
}
});
// sent token and check response
sendMessage(socket, 'healthCheck', tcpSocketPanel.attr('data-token'));
webSocketPanel.hideLoadingAnimation();
};
socket.onmessage = (e) => {
let response = JSON.parse(e.data);
if(Util.getObjVal(response, 'load.isValid') === true){
// SUCCESS
updateWebSocketPanel({
status: {
type: 'success',
label: 'CONNECTED',
class: 'txt-color-success'
}
});
// show subscription stats table
getWebSocketSubscriptionTable(Util.getObjVal(response, 'load.subStats')).then(payload => {
// remove existing table -> then insert new
$('#' + config.webSocketStatsId).remove();
$(payload).insertAfter(webSocketPanel).initTooltips();
let token = Util.getObjVal(response, 'load.token');
tcpSocketPanel.attr('data-token', token);
// set "reload stats" observer
$('#' + config.webSocketRefreshStatsId).on('click', function(){
$('#' + config.webSocketStatsId).showLoadingAnimation();
sendMessage(socket, 'healthCheck', token);
});
});
}else{
// Got response but INVALID
updateWebSocketPanel({
status: {
type: 'warning',
label: 'INVALID RESPONSE',
class: 'txt-color-warning'
}
});
}
};
socket.onerror = (e) => {
updateWebSocketPanel({
status: {
type: 'danger',
label: 'CONNECTION ERROR',
class: 'txt-color-danger'
}
});
webSocketPanel.hideLoadingAnimation();
};
socket.onclose = (closeEvent) => {
updateWebSocketPanel({
status: {
type: 'danger',
label: 'CONNECTION FAILED',
class: 'txt-color-danger'
}
});
webSocketPanel.hideLoadingAnimation();
$('#' + config.webSocketStatsId).remove();
};
};
/**
* main init "setup" page
*/
$(function(){
// show app information in browser console --------------------------------------------------------------------
Util.showVersionInfo();
// hide splash loading animation ------------------------------------------------------------------------------
$('.' + config.splashOverlayClass + '[data-status="ok"]').hideSplashOverlay();
setPageObserver();
testWebSocket();
});
});

View File

@@ -0,0 +1,187 @@
define([
'jquery',
'app/init',
'summernote'
], ($, Init) => {
'use strict';
// all Summernote stuff is available...
let initDefaultSummernoteConfig = () => {
// "length" hint plugin ---------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
lengthField: function (context){
let self = this;
let ui = $.summernote.ui;
// add counter
context.memo('button.lengthField', () => {
return $('<kbd>', {
class: ['text-right', 'txt-color'].join(' ')
});
});
/**
* update counter element with left chars
* @param contents
*/
let updateCounter = (contents) => {
let maxTextLength = context.options.maxTextLength;
let textLength = contents.length;
let counter = context.layoutInfo.toolbar.find('kbd');
let counterLeft = maxTextLength - textLength;
counter.text(counterLeft).data('charCount', counterLeft);
counter.toggleClass('txt-color-red', maxTextLength <= textLength);
// disable "save" button
let saveBtn = context.layoutInfo.toolbar.find('.btn-save');
saveBtn.prop('disabled', maxTextLength < textLength);
};
// events
this.events = {
'summernote.init': function (we, e) {
updateCounter(context.$note.summernote('code'));
},
'summernote.change': function(we, contents){
updateCounter(contents);
}
};
}
});
// "discard" button plugin ------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
discardBtn: function (context){
let self = this;
let ui = $.summernote.ui;
// add button
context.memo('button.discardBtn', () => {
let button = ui.button({
contents: '<i class="fas fa-fw fa-ban"/>',
container: 'body',
click: function(){
// show confirmation dialog
$(this).confirmation('show');
}
});
let $button = button.render();
// show "discard" changes confirmation
let confirmationSettings = {
container: 'body',
placement: 'top',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
title: 'discard changes',
btnOkClass: 'btn btn-sm btn-warning',
btnOkLabel: 'discard',
btnOkIcon: 'fas fa-fw fa-ban',
onConfirm: (e, target) => {
// discard all changes
context.$note.summernote('reset');
context.$note.summernote('destroy');
}
};
$button.confirmation(confirmationSettings);
return $button;
});
}
});
};
/**
* init new Summernote editor
* @param element
* @param options
*/
let initSummernote = (element, options) => {
let defaultOptions = {
dialogsInBody: true,
dialogsFade: true,
//textareaAutoSync: false,
//hintDirection: 'right',
//tooltip: 'right',
//container: 'body',
styleTags: ['p', 'h2', 'h3', 'blockquote'],
linkTargetBlank: true,
tableClassName: 'table table-condensed table-bordered',
insertTableMaxSize: {
col: 5,
row: 5
},
icons: {
//'align': 'note-icon-align',
'alignCenter': 'fas fa-align-center',
'alignJustify': 'fas fa-align-justify',
'alignLeft': 'fas fa-align-left',
'alignRight': 'fas fa-align-right',
//'rowBelow': 'note-icon-row-below',
//'colBefore': 'note-icon-col-before',
//'colAfter': 'note-icon-col-after',
//'rowAbove': 'note-icon-row-above',
//'rowRemove': 'note-icon-row-remove',
//'colRemove': 'note-icon-col-remove',
'indent': 'fas fa-indent',
'outdent': 'fas fa-outdent',
'arrowsAlt': 'fas fa-expand-arrows-alt',
'bold': 'fas fa-bold',
'caret': 'fas fa-caret-down',
'circle': 'fas fa-circle',
'close': 'fas fa-time',
'code': 'fas fa-code',
'eraser': 'fas fa-eraser',
'font': 'fas fa-font',
//'frame': 'note-icon-frame',
'italic': 'fas fa-italic',
'link': 'fas fa-link',
'unlink': 'fas fa-unlink',
'magic': 'fas fa-magic',
'menuCheck': 'fas fa-check',
'minus': 'fas fa-minus',
'orderedlist': 'fas fa-list-ol',
'pencil': 'fa-pen',
'picture': 'fas fa-image',
'question': 'fas fa-question',
'redo': 'fas fa-redo',
'square': 'fas fa-square',
'strikethrough': 'fas fa-strikethrough',
'subscript': 'fas fa-subscript',
'superscript': 'fas fa-superscript',
'table': 'fas fa-table',
'textHeight': 'fas fa-text-height',
'trash': 'fas fa-trash',
'underline': 'fas fa-underline',
'undo': 'fas fa-undo',
'unorderedlist': 'fas fa-list-ul',
'video': 'fab fa-youtube'
},
colors: [
['#5cb85c', '#e28a0d', '#d9534f', '#e06fdf', '#9fa8da', '#e2ce48', '#428bca']
],
colorsName: [
['Green', 'Orange', 'Red', 'Pink', 'Indigo', 'Yellow', 'Blue']
],
};
options = $.extend({}, defaultOptions, options);
element.summernote(options);
};
initDefaultSummernoteConfig();
return {
initSummernote: initSummernote
};
});

View File

@@ -0,0 +1,214 @@
/**
* 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.getObjVal(Util.getCurrentUserData(), 'character.role')).prop('outerHTML'),
characterAutoLocationSelectEnabled: Boolean(Util.getObjVal(Init, 'character.autoLocationSelect'))
};
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>&nbsp;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();
});
// show dialog
accountSettingsDialog.modal('show');
// events for tab change
accountSettingsDialog.find('.navbar a').on('shown.bs.tab', function(e){
// init "toggle" switches on current active tab
accountSettingsDialog.find( $(this).attr('href') ).find('input[data-toggle="toggle"][type="checkbox"]').bootstrapToggle({
on: '<i class="fas fa-fw fa-check"></i>&nbsp;Enable',
off: 'Disable&nbsp;<i class="fas fa-fw fa-ban"></i>',
onstyle: 'success',
offstyle: 'warning',
width: 100,
height: 30
});
});
});
};
});

View File

@@ -0,0 +1,75 @@
/**
* 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 'red': return 'bad: Most requests are not succeeding and/or are very slow (5s+) on average';
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');
});
};
});

View File

@@ -0,0 +1,139 @@
/**
* 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);
});
};
});

View File

@@ -0,0 +1,50 @@
/**
* credits dialog
*/
define([
'jquery',
'app/init',
'app/util',
'bootbox',
'layout/logo'
], ($, Init, Util, bootbox) => {
'use strict';
let config = {
// jump info dialog
creditsDialogClass: 'pf-credits-dialog', // class for credits dialog
creditsDialogLogoContainerId: 'pf-logo-container' // id for logo element
};
/**
* show jump info dialog
*/
$.fn.showCreditsDialog = function(callback, enableHover){
requirejs(['text!templates/dialog/credit.html', 'mustache'], (template, Mustache) => {
let data = {
logoContainerId: config.creditsDialogLogoContainerId,
version: Util.getVersion()
};
let content = Mustache.render(template, data);
let creditDialog = bootbox.dialog({
className: config.creditsDialogClass,
title: 'Licence',
message: content
});
// after modal is shown =======================================================================
creditDialog.on('shown.bs.modal', function(e){
// load Logo svg
creditDialog.find('#' + config.creditsDialogLogoContainerId).drawLogo(callback, enableHover);
});
});
};
});

View File

@@ -0,0 +1,130 @@
/**
* 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>&nbsp;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');
});
};
});

View File

@@ -0,0 +1,172 @@
/**
* 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', 'datatables.loader'], (template, Mustache) => {
let staticsMatrixHead = [
['From╲To', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'H', 'L', '0.0', 'C12', 'C13']
];
let staticsMatrixBody = [
['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 data = {
config: config,
popoverTriggerClass: Util.config.popoverTriggerClass,
wormholes: Object.keys(Init.wormholes).map(function(k){ return Init.wormholes[k]; }), // convert Json to array
staticsMatrixHead: staticsMatrixHead,
staticsMatrixBody: staticsMatrixBody.map((row, rowIndex) => {
return row.map((label, colIndex) => {
// get security name from "matrix Head" data if NOT first column
let secName = colIndex ? staticsMatrixHead[0][colIndex] : label;
return {
label: label,
class: Util.getSecurityClassForSystem(secName),
hasPopover: colIndex && label.length
};
});
}),
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 + '&nbsp;h' : 'unknown';
};
},
sigStrengthValue: function(){
return function(value, render){
let float = render(value);
return float.length ? parseFloat(float).toLocaleString() + '&nbsp;&#37;' : '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');
});
};
});

View File

@@ -0,0 +1,164 @@
/**
* 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.pieChartMapCounterClass,
mapCounterClass : Init.classes.pieChart.pieChartMapCounterClass
};
let content = Mustache.render(template, data);
// show dialog
let mapManualDialog = bootbox.dialog({
title: 'Manual',
message: content,
size: 'large',
buttons: {
success: {
label: 'close',
className: 'btn-default',
callback: function(){
$(mapManualDialog).modal('hide');
}
}
},
show: true
});
// modal offset top
let modalOffsetTop = 200;
// disable on scroll event
let disableOnScrollEvent = false;
// scroll breakpoints
let scrollBreakpointElements = $('.pf-manual-scroll-break');
// scroll navigation links
let scrollNavLiElements = $('.' + config.dialogNavigationListItemClass);
let scrollspyElement = $('#' + config.mapManualScrollspyId);
let whileScrolling = function(){
if(disableOnScrollEvent === false){
for(let i = 0; i < scrollBreakpointElements.length; i++){
let offset = $(scrollBreakpointElements[i]).offset().top;
if( (offset - modalOffsetTop) > 0){
if(! $( scrollNavLiElements[i]).hasClass('active')){
// remove all active classes
scrollNavLiElements.removeClass('active');
// remove focus on links
scrollNavLiElements.find('a').blur();
$( scrollNavLiElements[i]).addClass('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
scrollspyElement.find('.' + data.mapCounterClass).initMapUpdateCounter();
// set navigation button observer
let mainNavigationLinks = $('.' + config.dialogNavigationClass).find('a');
// text anchor links
let subNavigationLinks = scrollspyElement.find('a[data-target]');
let navigationLinks = mainNavigationLinks.add(subNavigationLinks);
navigationLinks.on('click', function(e){
e.preventDefault();
disableOnScrollEvent = true;
// scroll to anchor
scrollspyElement.mCustomScrollbar('scrollTo', $(this).attr('data-target'));
let mainNavigationLiElement = $(this).parent('.' + config.dialogNavigationListItemClass);
whileScrolling();
// if link is a main navigation link (not an anchor link)
if(mainNavigationLiElement.length > 0){
// remove all active classes
scrollNavLiElements.removeClass('active');
// set new active class
$(this).parent().addClass('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
});
});
};
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,819 @@
/**
* map settings dialogs
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox',
'app/map/util',
'app/module_map'
], ($, Init, Util, Render, bootbox, MapUtil, ModuleMap) => {
'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
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
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
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 dialogTitle = 'Map settings';
// if there are no maps -> hide settings tab
let hideSettingsTab = false;
let hideEditTab = false;
let hideDownloadTab = false;
let hasRightMapCreate = true;
let hasRightMapUpdate = true;
let hasRightMapExport = true;
let hasRightMapImport = true;
if(mapData === false){
hideSettingsTab = true;
hideEditTab = true;
hideDownloadTab = true;
}else{
hasRightMapUpdate = MapUtil.checkRight('map_update', mapData.config);
hasRightMapExport = MapUtil.checkRight('map_export', mapData.config);
hasRightMapImport = MapUtil.checkRight('map_import', mapData.config);
}
// available map "types" for a new or existing map
let mapTypes = MapUtil.getMapTypes(true);
let mapFormData = {
select2Class: Util.config.select2Class,
scope: MapUtil.getMapScopes(),
type: mapTypes,
icon: MapUtil.getMapIcons(),
formErrorContainerClass: Util.config.formErrorContainerClass,
formWarningContainerClass: Util.config.formWarningContainerClass,
formInfoContainerClass: Util.config.formInfoContainerClass
};
// render "new map" tab content -----------------------------------------------------------------------
let mapFormDataNew = $.extend({}, mapFormData, {
hasRightMapForm: hasRightMapCreate,
iconSelectId: config.newIconSelectId,
scopeSelectId: config.newScopeSelectId,
typeSelectId: config.newTypeSelectId
});
let contentNewMap = Mustache.render(templateMapForm, mapFormDataNew);
// render "edit map" tab content ----------------------------------------------------------------------
let mapFormDataEdit = $.extend({}, mapFormData, {
hasRightMapForm: hasRightMapUpdate,
iconSelectId: config.editIconSelectId,
scopeSelectId: config.editScopeSelectId,
typeSelectId: config.editTypeSelectId
});
let contentEditMap = Mustache.render(templateMapForm, mapFormDataEdit);
contentEditMap = $(contentEditMap);
// current map access info
let accessCharacter = [];
let accessCorporation = [];
let accessAlliance = [];
let deleteExpiredConnections = true;
let deleteEolConnections = true;
let persistentAliases = true;
let persistentSignatures = true;
let logActivity = true;
let logHistory = true;
let slackWebHookURL = '';
let slackUsername = '';
let slackIcon = '';
let slackChannelHistory = '';
let slackChannelRally = '';
let slackEnabled = false;
let slackHistoryEnabled = false;
let slackRallyEnabled = false;
let slackSectionShow = false;
let discordUsername = '';
let discordWebHookURLRally = '';
let discordWebHookURLHistory = '';
let discordEnabled = false;
let discordRallyEnabled = false;
let discordHistoryEnabled = false;
let discordSectionShow = false;
if(mapData !== false){
// set current map information
contentEditMap.find('input[name="id"]').val( mapData.config.id );
contentEditMap.find('select[name="icon"]').val( mapData.config.icon );
contentEditMap.find('input[name="name"]').val( mapData.config.name );
contentEditMap.find('select[name="scopeId"]').val( mapData.config.scope.id );
contentEditMap.find('select[name="typeId"]').val( mapData.config.type.id );
accessCharacter = mapData.config.access.character;
accessCorporation = mapData.config.access.corporation;
accessAlliance = mapData.config.access.alliance;
deleteExpiredConnections = mapData.config.deleteExpiredConnections;
deleteEolConnections = mapData.config.deleteEolConnections;
persistentAliases = mapData.config.persistentAliases;
persistentSignatures = mapData.config.persistentSignatures;
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'));
slackHistoryEnabled = slackEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_history_slack_enabled'));
slackRallyEnabled = slackEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_rally_slack_enabled'));
slackSectionShow = (slackEnabled && slackWebHookURL.length > 0);
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'));
discordRallyEnabled = discordEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_rally_discord_enabled'));
discordHistoryEnabled = discordEnabled && Boolean(Util.getObjVal(Init.mapTypes, mapData.config.type.name + '.defaultConfig.send_history_discord_enabled'));
discordSectionShow = (discordEnabled && (discordWebHookURLRally.length > 0 || discordWebHookURLHistory.length > 0));
// remove "#" from Slack channels
slackChannelHistory = slackChannelHistory.indexOf('#') === 0 ? slackChannelHistory.substr(1) : slackChannelHistory;
slackChannelRally = slackChannelRally.indexOf('#') === 0 ? slackChannelRally.substr(1) : slackChannelRally;
}
// render main dialog ---------------------------------------------------------------------------------
let mapDialogData = {
id: config.newMapDialogId,
mapData: mapData,
type: mapTypes,
// 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: hideEditTab,
hideSettingsTab: hideSettingsTab,
hideDownloadTab: hideDownloadTab,
// settings tab --------------
deleteExpiredConnectionsId : config.deleteExpiredConnectionsId,
deleteEolConnectionsId : config.deleteEolConnectionsId,
persistentAliasesId : config.persistentAliasesId,
persistentSignaturesId : config.persistentSignaturesId,
deleteExpiredConnections: deleteExpiredConnections,
deleteEolConnections: deleteEolConnections,
persistentAliases: persistentAliases,
persistentSignatures: persistentSignatures,
logHistoryId: config.logHistoryId,
logActivityId: config.logActivityId,
logActivity: logActivity,
logHistory: logHistory,
slackWebHookURLId: config.slackWebHookURLId,
slackUsernameId: config.slackUsernameId,
slackIconId: config.slackIconId,
slackChannelHistoryId: config.slackChannelHistoryId,
slackChannelRallyId: config.slackChannelRallyId,
slackWebHookURL: slackWebHookURL,
slackUsername: slackUsername,
slackIcon: slackIcon,
slackChannelHistory: slackChannelHistory,
slackChannelRally: slackChannelRally,
slackEnabled: slackEnabled,
slackHistoryEnabled: slackHistoryEnabled,
slackRallyEnabled: slackRallyEnabled,
slackSectionShow: slackSectionShow,
discordUsernameId: config.discordUsernameId,
discordWebHookURLRallyId: config.discordWebHookURLRallyId,
discordWebHookURLHistoryId: config.discordWebHookURLHistoryId,
discordUsername: discordUsername,
discordWebHookURLRally: discordWebHookURLRally,
discordWebHookURLHistory: discordWebHookURLHistory,
discordEnabled: discordEnabled,
discordRallyEnabled: discordRallyEnabled,
discordHistoryEnabled: discordHistoryEnabled,
discordSectionShow: discordSectionShow,
characterSelectId: config.characterSelectId,
corporationSelectId: config.corporationSelectId,
allianceSelectId: config.allianceSelectId,
// map access objects --------
accessCharacter: accessCharacter,
accessCorporation: accessCorporation,
accessAlliance: accessAlliance,
// access limitations --------
maxCharacter: Init.mapTypes.private.defaultConfig.max_shared,
maxCorporation: Init.mapTypes.corporation.defaultConfig.max_shared,
maxAlliance: Init.mapTypes.alliance.defaultConfig.max_shared,
// download tab --------------
dialogMapExportFormId: config.dialogMapExportFormId,
dialogMapImportFormId: config.dialogMapImportFormId,
buttonExportId: config.buttonExportId,
buttonImportId: config.buttonImportId,
fieldExportId: config.fieldExportId,
fieldImportId: config.fieldImportId,
dialogMapImportInfoId: config.dialogMapImportInfoId,
hasRightMapUpdate: hasRightMapUpdate,
hasRightMapExport: hasRightMapExport,
hasRightMapImport: hasRightMapImport,
formatFilename: function(){
// format filename from "map name" (initial)
return function(mapName, render){
let filename = render(mapName);
return formatFilename(filename);
};
}
};
let contentDialog = Mustache.render(templateMapDialog, mapDialogData);
contentDialog = $(contentDialog);
// set tab content
$('#' + config.dialogMapNewContainerId, contentDialog).html(contentNewMap);
$('#' + 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>&nbsp;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
let formValid = form.isValidForm();
if(formValid === true){
// lock dialog
let dialogContent = mapInfoDialog.find('.modal-content');
dialogContent.showLoadingAnimation();
// get form data
let formData = form.getFormValues();
// add value prefixes (Slack channels)
let tmpVal;
if(typeof (tmpVal = Util.getObjVal(formData, 'slackChannelHistory')) === 'string' && tmpVal.length){
formData.slackChannelHistory = '#' + tmpVal;
}
if(typeof (tmpVal = Util.getObjVal(formData, 'slackChannelRally')) === 'string' && tmpVal.length){
formData.slackChannelRally = '#' + tmpVal;
}
// checkbox fix -> settings tab
if( form.find('#' + config.deleteExpiredConnectionsId).length ){
formData.deleteExpiredConnections = formData.hasOwnProperty('deleteExpiredConnections') ? parseInt( formData.deleteExpiredConnections ) : 0;
}
if( form.find('#' + config.deleteEolConnectionsId).length ){
formData.deleteEolConnections = formData.hasOwnProperty('deleteEolConnections') ? parseInt( formData.deleteEolConnections ) : 0;
}
if( form.find('#' + config.persistentAliasesId).length ){
formData.persistentAliases = formData.hasOwnProperty('persistentAliases') ? parseInt( formData.persistentAliases ) : 0;
}
if( form.find('#' + config.persistentSignaturesId).length ){
formData.persistentSignatures = formData.hasOwnProperty('persistentSignatures') ? parseInt( formData.persistentSignatures ) : 0;
}
if( form.find('#' + config.logHistoryId).length ){
formData.logHistory = formData.hasOwnProperty('logHistory') ? parseInt( formData.logHistory ) : 0;
}
if( form.find('#' + config.logActivityId).length ){
formData.logActivity = formData.hasOwnProperty('logActivity') ? parseInt( formData.logActivity ) : 0;
}
let requestData = {formData: formData};
$.ajax({
type: 'POST',
url: Init.path.saveMap,
data: requestData,
dataType: 'json'
}).done(function(responseData){
if(responseData.error.length){
form.showFormMessage(responseData.error);
}else{
// success
Util.showNotify({title: dialogTitle, text: 'Map: ' + responseData.mapData.mapData.name, type: 'success'});
// update map-tab Element
let tabLinkElement = Util.getMapModule().getMapTabElements(responseData.mapData.mapData.id);
if(tabLinkElement.length === 1){
ModuleMap.updateTabData(tabLinkElement, responseData.mapData.mapData);
}
$(mapInfoDialog).modal('hide');
Util.triggerMenuAction(document, 'Close');
}
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveMap', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
dialogContent.hideLoadingAnimation();
});
}
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
).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', message: 'Creating new maps or change settings may take a few seconds'}]);
if(mapData === false){
// no map data found (probably new user
form.showFormMessage([{type: 'warning', message: '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: 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', message: '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', message: '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 mapName = mapData.config.name;
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>&nbsp;delete map',
className: 'btn-danger'
}
},
callback: function(result){
if(result){
let data = {mapData: mapData.config};
$.ajax({
type: 'POST',
url: Init.path.deleteMap,
data: data,
dataType: 'json'
}).done(function(data){
Util.showNotify({title: 'Map deleted', text: 'Map: ' + mapName, type: 'success'});
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': deleteMap', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
$(mapDeleteDialog).modal('hide');
});
return false;
}
}
});
};
});

View File

@@ -0,0 +1,87 @@
/**
* 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(800);
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);
});
});
}
};
});

View File

@@ -0,0 +1,49 @@
/**
* 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
});
});
};
});

View File

@@ -0,0 +1,854 @@
/**
* activity stats dialog
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox',
'peityInlineChart'
], ($, Init, Util, Render, 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 = function(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: '<"row"<"col-xs-3"l><"col-xs-5"B><"col-xs-4"f>>' +
'<"row"<"col-xs-12"tr>>' +
'<"row"<"col-xs-5"i><"col-xs-7"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,
title: '<i class="fas fa-hashtag"></i>',
orderable: false,
searchable: false,
width: 10,
class: 'text-right',
data: 'character.id'
},{
targets: 1,
title: '',
orderable: false,
searchable: false,
width: 26,
className: ['text-center', config.tableCellImageClass].join(' '),
data: 'character',
render: {
_: function(data, type, row, meta){
return '<img src="' + Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg" />';
}
}
},{
targets: 2,
title: 'name',
width: 200,
data: 'character',
render: {
_: 'name',
sort: 'name'
}
},{
targets: 3,
title: 'last login',
searchable: false,
width: 70,
className: ['text-right', 'separator-right'].join(' '),
data: 'character',
render: {
_: 'lastLogin',
sort: 'lastLogin'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter();
}
},{
targets: 4,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'mapCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 5,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'mapUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 6,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'mapDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 7,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'separator-right'].join(' ') ,
data: 'mapSum',
render: {
_: renderNumericColumn
}
},{
targets: 8,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 9,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 10,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 11,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'separator-right'].join(' ') ,
data: 'systemSum',
render: {
_: renderNumericColumn
}
},{
targets: 12,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 13,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 14,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 15,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'separator-right'].join(' '),
data: 'connectionSum',
render: {
_: renderNumericColumn
}
},{
targets: 16,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 17,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 18,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 19,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'separator-right'].join(' '),
data: 'signatureSum',
render: {
_: renderNumericColumn
}
},{
targets: 20,
title: 'Σ&nbsp;&nbsp;',
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});
},
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 + '.&nbsp;&nbsp;';
}
$(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 = function(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);
}).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 = function(responseData){
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);
this.tableApi.clear();
this.tableApi.rows.add(formattedData).draw();
};
/**
* format statistics data for dataTable
* -> e.g. format inline-chart data
* @param statsData
* @returns {Array}
*/
let formatStatisticsData = function(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 = function(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', 'datatables.loader'], (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');
});
};
});

View File

@@ -0,0 +1,188 @@
/**
* 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(){
requirejs(['datatables.loader'], () => {
let rowElement = $('<div>', {
class: 'row'
});
let systemEffectData = Util.getSystemEffectData();
// last active (hover) table columnIndex
let lastActiveColIndex = 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>') );
thead.append( rows[0] );
rows[0].append(
$('<th>').html('&nbsp;&nbsp;' + systemEffectName).prepend(
$('<i>', {
class: ['fas', 'fa-square', systemEffectClass].join(' ')
})
)
);
}
rows[0].append( $('<th>', {
class: ['text-right', 'col-xs-1', securityClass].join(' ')
}).text( systemType ));
for(let [i, data] of Object.entries(areaData)){
i = parseInt(i);
if(areaId === '1'){
rows.push( $('<tr>') );
tbody.append(rows[i + 1]);
// add label
rows[i + 1].append( $('<td>').text( data.effect ));
}
rows[i + 1].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;
if(colIndex !== lastActiveColIndex){
removeColumnHighlight();
lastActiveColIndex = colIndex;
if(colIndex > 0){
// active column changed -> highlight same colIndex on other tables
let tableApis = $.fn.dataTable.tables({ visible: false, api: true })
.tables('.' + config.systemEffectTableClass);
let columns = tableApis.columns(colIndex);
columns.header().flatten().to$().addClass('colHighlight');
columns.nodes().flatten().to$().addClass('colHighlight');
}
}
}).on('mouseleave', function(){
// no longer inside table
lastActiveColIndex = 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');
});
};
});

View File

@@ -0,0 +1,893 @@
/**
* 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 = Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg';
break;
case 'corporation':
imagePath = Init.url.ccpImageServer + '/Corporation/' + data.id + '_32.png';
break;
case 'alliance':
imagePath = Init.url.ccpImageServer + '/Alliance/' + data.id + '_32.png';
break;
case 'inventoryType':
imagePath = Init.url.ccpImageServer + '/Type/' + data.id + '_32.png';
break;
case 'render':
imagePath = Init.url.ccpImageServer + '/Render/' + data.id + '_32.png';
break;
case 'station':
iconName = 'fa-home';
break;
case 'system':
iconName = 'fa-sun';
break;
}
if(imagePath){
thumb = '<img class="' + config.select2ImageLazyLoadClass + '" data-original="' + 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">' + data.text + '</div>';
}
markup += '</div>';
return markup;
};
/**
* format results data for signature type select
* @param state
* @returns {*|jQuery|HTMLElement}
*/
let formatSignatureTypeSelectionData = state => {
let parts = state.text.split(' - ');
let markup = '';
if(parts.length === 2){
// wormhole data -> 2 columns
let securityClass = Util.getSecurityClassForSystem(getSystemSecurityFromLabel(parts[1]));
markup += '<span>' + parts[0] + '</span>&nbsp;&nbsp;';
markup += '<i class="fas fa-long-arrow-alt-right txt-color txt-color-grayLight"></i>';
markup += '<span class="' + securityClass + ' ' + Util.config.popoverTriggerClass + ' ' + Util.config.helpDefaultClass +
'" data-name="' + parts[0] + '">&nbsp;&nbsp;' + parts[1] + '&nbsp;</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':
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"></i></div>';
markup += '<div class="col-xs-7 ' + securityClass + '">' + parts[1] + '</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>&nbsp;&nbsp;';
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: -1,
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: -1,
width: '100%',
iconClass: 'fa-circle'
};
options = $.extend({}, defaultConfig, options);
let formatStatusSelectionData = state => {
return '<i class="fas ' + options.iconClass + ' ' + state.class + '"></i>&nbsp;&nbsp;&nbsp;' + 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-sm-4 pf-select-item-anchor ' + systemNameClass + '">' + data.text + '</div>';
markup += '<div class="col-sm-2 text-right ' + data.effectClass + '">';
markup += '<i class="fas fa-fw fa-square ' + hideEffectClass + '"></i>';
markup += '</div>';
markup += '<div class="col-sm-2 text-right ' + data.secClass + '">' + data.security + '</div>';
markup += '<div class="col-sm-2 text-right ' + shatteredClass + '">';
markup += '<i class="fas fa-fw fa-skull ' + hideShatteredClass + '"></i>';
markup += '</div>';
markup += '<div class="col-sm-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: -1,
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: -1,
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
};
});

View File

@@ -0,0 +1,314 @@
/**
* Demo SVG map
*/
define([
'jquery',
'lazylinepainter'
], ($) => {
'use strict';
let config = {
headerSystemsContainerId: 'pf-header-systems', // id for systems layer
headerSystemConnectorsId: 'pf-header-connectors', // id for connectors layer
headerConnectionsContainerId: 'pf-header-connections', // id for connections layer
headerBackgroundContainerId: 'pf-header-background', // id for background layer
headerSystemClass: 'pf-header-system', // class for all header background systems
// map dimensions
mapWidth: 600, // map width (px)
mapHeight: 380 // map height (px)
};
/**
* draw systems layer
* @param callback
*/
let drawSystems = function(callback){
let pathObj = {
systems: {
strokepath: [
// systems =======================================================================
// 1
{
path: 'm 155 30 90 0 c 2.8 0 5 2.2 5 5 l 0 10 c 0 2.8 -2.2 5 -5 5 l -90 0 c -2.8 0 -5 -2.2 -5 -5 l 0 -10 c 0 -2.8 2.2 -5 5 -5 z',
duration: 500,
strokeColor: '#568A89' //teal
},
// 2
{
path: 'm 374 91 110 0 c 2.8 0 5 2.2 5 5 l 0 10 c 0 2.8 -2.2 5 -5 5 l -110 0 c -2.8 0 -5 -2.2 -5 -5 l 0 -10 c 0 -2.8 2.2 -5 5 -5 z',
duration: 500,
strokeColor: '#63676A' //gray
},
// 3
{
path: 'm 15 149 90 0 c 2.8 0 5 2.2 5 5 l 0 30 c 0 2.8 -2.2 5 -5 5 l -90 0 c -2.8 0 -5 -2.2 -5 -5 l 0 -30 c 0 -2.8 2.2 -5 5 -5 z',
duration: 500,
strokeColor: '#D9534F ' //red
},
// 4
{
path: 'm 235 230 90 0 c 2.8 0 5 2.2 5 5 l 0 10 c 0 2.8 -2.2 5 -5 5 l -90 0 c -2.8 0 -5 -2.2 -5 -5 l 0 -10 c 0 -2.8 2.2 -5 5 -5 z',
duration: 500,
strokeColor: '#63676A' //gray
},
// 5
{
path: 'm 175 330 90 0 c 2.8 0 5 2.2 5 5 l 0 30 c 0 2.8 -2.2 5 -5 5 l -90 0 c -2.8 0 -5 -2.2 -5 -5 l 0 -30 c 0 -2.8 2.2 -5 5 -5 z',
duration: 500,
strokeColor: '#E28A0D ' //orange
},
// 6
{
path: 'm 436 312 90 0 c 2.8 0 5 2.2 5 5 l 0 10 c 0 2.8 -2.2 5 -5 5 l -90 0 c -2.8 0 -5 -2.2 -5 -5 l 0 -10 c 0 -2.8 2.2 -5 5 -5 z',
duration: 500,
strokeColor: '#5CB85C ' //green
}
],
dimensions: {
width: config.mapWidth,
height: config.mapHeight
}
}
};
// draw systems into header
$('#' + config.headerSystemsContainerId).lazylinepainter(
{
svgData: pathObj,
strokeWidth: 2,
strokeOpacity: 1,
overrideKey: 'systems',
strokeJoin: 'miter',
strokeCap: 'butt',
delay: 1000,
onComplete: function(){
drawConnectors(callback);
}
}).lazylinepainter('paint');
};
/**
* draw connectors layer
* @param callback
*/
let drawConnectors = function(callback){
let connectorDuration = 150;
let pathObj = {
connectors: {
strokepath: [
// connectors ====================================================================
// 1
{
path: 'm 250.4 34.8 c 1.7 0 3.1 1.3 3.9 2.9 0.8 1.7 0.8 3.7 0 5.3 -0.8 1.7 -2.4 2.7 -4 2.7',
duration: connectorDuration
},
{
path: 'm 150 34.7 c -1.7 0 -3.1 1.3 -3.9 2.9 -0.8 1.7 -0.8 3.7 0 5.3 0.8 1.7 2.4 2.7 4 2.7',
duration: connectorDuration
},
// 2
{
path: 'm 369 96 c -1.7 0 -3.1 1.3 -3.9 2.9 -0.8 1.7 -0.8 3.7 0 5.3 0.8 1.7 2.4 2.7 4 2.7',
duration: connectorDuration
},
// 3
{
path: 'm 110.4 165 c 1.7 0 3.1 1.3 3.9 2.9 0.8 1.7 0.8 3.7 0 5.3 -0.8 1.7 -2.4 2.7 -4 2.7',
duration: connectorDuration
},
{
path: 'm 56 148 c 0 -1.7 1.3 -3.1 2.9 -3.9 1.7 -0.8 3.7 -0.8 5.3 0 1.7 0.8 2.7 2.4 2.7 4',
duration: connectorDuration
},
// 4
{
path: 'm 229 236 c -1.7 0 -3.1 1.3 -3.9 2.9 -0.8 1.7 -0.8 3.7 0 5.3 0.8 1.7 2.4 2.7 4 2.7',
duration: connectorDuration
},
{
path: 'm 331 234.7 c 1.7 0 3.1 1.3 3.9 2.9 0.8 1.7 0.8 3.7 0 5.3 -0.8 1.7 -2.4 2.7 -4 2.7',
duration: connectorDuration
},
{
path: 'm 285 251 c 0 1.7 -1.3 3.1 -2.9 3.9 -1.7 0.8 -3.7 0.8 -5.3 0 -1.7 -0.8 -2.7 -2.4 -2.7 -4',
duration: connectorDuration
},
// 5
{
path: 'm 213 329.5 c 0 -1.7 1.3 -3.1 2.9 -3.9 1.7 -0.8 3.7 -0.8 5.3 0 1.7 0.8 2.7 2.4 2.7 4',
duration: connectorDuration
},
// 6
{
path: 'm 430 316 c -1.7 0 -3.1 1.3 -3.9 2.9 -0.8 1.7 -0.8 3.7 0 5.3 0.8 1.7 2.4 2.7 4 2.7',
duration: connectorDuration
}
],
dimensions: {
width: config.mapWidth,
height: config.mapHeight
}
}
};
// draw systems into header
$('#' + config.headerConnectionsContainerId).lazylinepainter(
{
svgData: pathObj,
strokeWidth: 2,
duration: 600,
drawSequential: false,
strokeOpacity: 1,
overrideKey: 'connectors',
strokeJoin: 'miter',
strokeCap: 'butt',
strokeColor: '#63676A', //gray
onComplete: function(){
drawConnections(callback);
}
}).lazylinepainter('paint');
};
/**
* draw connections layer
* @param callback
*/
let drawConnections = function(callback){
let connectionDuration = 250;
let connectionWidth = 8;
let connectionInnerWidth = 4;
let connectionBorderColor = '#63676A'; //gray
let pathObj = {
connections: {
strokepath: [
// connections ====================================================================
// 1 - 2
{
path: 'm 255,40 c 44.5,0 64.2,61.1 109.0,61.15',
duration: connectionDuration,
strokeWidth: connectionWidth
},
{
path: 'm 255,40 c 44.5,0 64.2,61.1 109.0,61.15',
duration: connectionDuration,
strokeWidth: connectionInnerWidth,
strokeColor: '#3C3F41' // gray
},
// 2 - 3
{
path: 'm 146,40.0 c -51.7,0 -83.8,58.8 -83.8,104.5',
duration: connectionDuration,
strokeWidth: connectionWidth
},
{
path: 'm 146,40.0 c -51.7,0 -83.8,58.8 -83.8,104.5',
duration: connectionDuration,
strokeWidth: connectionInnerWidth,
strokeColor: '#E28A0D' // orange
},
// 3 - 4
{
path: 'm 115,171 c 45.7,0 64.1,71.2 109.6,70.8',
duration: connectionDuration,
strokeWidth: connectionWidth
},
{
path: 'm 115,171 c 45.7,0 64.1,71.2 109.6,70.8',
duration: connectionDuration,
strokeWidth: connectionInnerWidth,
strokeColor: '#A52521' // red
},
// 4 - 5
{
path: 'm 279,256 c 0.5,35.9 -60.1,35.1 -60.1,70.0',
duration: connectionDuration,
strokeWidth: connectionWidth
},
{
path: 'm 279,256 c 0.5,35.9 -60.1,35.1 -60.1,70.0',
duration: connectionDuration,
strokeWidth: connectionInnerWidth,
strokeColor: '#3C3F41' // gray
},
// 4 - 6
{
path: 'm 335,240 c 44.2,0 43.7,81.6 89.9,81.6',
duration: connectionDuration,
strokeWidth: connectionWidth
},
{
path: 'm 335,240 c 44.2,0 43.7,81.6 89.9,81.6',
duration: connectionDuration,
strokeWidth: connectionInnerWidth,
strokeColor: '#3C3F41' // gray
}
],
dimensions: {
width: config.mapWidth,
height: config.mapHeight
}
}
};
// draw systems into header
$('#' + config.headerSystemConnectorsId).lazylinepainter(
{
svgData: pathObj,
strokeWidth: 2,
duration: 600,
// drawSequential: false,
strokeOpacity: 1,
overrideKey: 'connections',
strokeJoin: 'miter',
strokeCap: 'butt',
strokeColor: connectionBorderColor,
onComplete: function(){
drawBackground(callback);
}
}).lazylinepainter('paint');
};
/**
* draw background layer
* @param callback
*/
let drawBackground = function(callback){
$('#' + config.headerBackgroundContainerId + ' .' + config.headerSystemClass).velocity('transition.bounceUpIn', {
stagger: 150,
complete: function(){
if(typeof callback === 'function'){
callback();
}
}
});
};
/**
* draws the demo map
* @param callback
*/
$.fn.drawDemoMap = function(callback){
let canvasElement = $(this);
// draw systems
drawSystems(callback);
};
});

View File

@@ -0,0 +1,239 @@
/**
* Header animation
*/
define([
'jquery',
'easePack',
'tweenLite'
], ($) => {
'use strict';
let config = {
previewElementClass: 'pf-header-preview-element' // class for "preview" elements
};
let width, height, largeHeader, canvas, ctx, points, target, animateHeader = true;
let canvasHeight = 355;
let colorRGB = '108, 174, 173';
let connectionCount = 4;
let initHeader = function(){
width = window.innerWidth;
height = canvasHeight;
target = {x: width * 1, y: 230};
largeHeader.style.height = height+'px';
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext('2d');
// create points
points = [];
for(let x = 0; x < width; x = x + width/20){
for(let y = 0; y < height; y = y + height/15){
let px = x + Math.random()*width/15;
let py = y + Math.random()*height/15;
let p = {x: px, originX: px, y: py, originY: py };
points.push(p);
}
}
// for each point find the 5 closest points
for(let i = 0; i < points.length; i++){
let closest = [];
let p1 = points[i];
for(let j = 0; j < points.length; j++){
let p2 = points[j];
if(p1 !== p2){
let placed = false;
for(let k = 0; k < connectionCount; k++){
if(!placed){
if(closest[k] === undefined){
closest[k] = p2;
placed = true;
}
}
}
for(let m = 0; m < connectionCount; m++){
if(!placed){
if(getDistance(p1, p2) < getDistance(p1, closest[m])){
closest[m] = p2;
placed = true;
}
}
}
}
}
p1.closest = closest;
}
// assign a circle to each point
for(let n in points){
let c = new Circle(points[n], 2+Math.random()*2, 'rgba(255,255,255,0.3)');
points[n].circle = c;
}
};
// Event handling
let addListeners = function(){
if(!('ontouchstart' in window)){
window.addEventListener('mousemove', mouseMove);
}
window.addEventListener('scroll', scrollCheck);
window.addEventListener('resize', resize);
};
let mouseMove = function(e){
let posx = 0;
let posy = 0;
if(e.pageX || e.pageY){
posx = e.pageX;
posy = e.pageY;
}else if(e.clientX || e.clientY){
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
target.x = posx;
target.y = posy;
};
let scrollCheck = function(){
if(document.body.scrollTop > height){
animateHeader = false;
}else{
animateHeader = true;
}
};
let resize = function(){
width = window.innerWidth;
height = canvasHeight;
largeHeader.style.height = height+'px';
canvas.width = width;
canvas.height = height;
};
// animation
let initAnimation = function(){
animate();
for(let i in points){
shiftPoint(points[i]);
}
};
let animate = function animate(){
if(animateHeader){
ctx.clearRect(0,0,width,height);
for(let i in points){
// detect points in range
if(Math.abs(getDistance(target, points[i])) < 4000){
points[i].active = 0.25;
points[i].circle.active = 0.45;
}else if(Math.abs(getDistance(target, points[i])) < 20000){
points[i].active = 0.1;
points[i].circle.active = 0.3;
}else if(Math.abs(getDistance(target, points[i])) < 40000){
points[i].active = 0.02;
points[i].circle.active = 0.1;
}else{
points[i].active = 0;
points[i].circle.active = 0;
}
drawLines(points[i]);
points[i].circle.draw();
}
}
requestAnimationFrame(animate);
};
let shiftPoint = function(p){
TweenLite.to(p, 1 + 1 * Math.random(), {x: p.originX - 50 + Math.random() * 100,
y: p.originY - 50 + Math.random() * 100, ease: Circ.easeInOut,
onComplete: function(){
shiftPoint(p);
}});
};
// Canvas manipulation
let drawLines = function(p){
if(!p.active) return;
for(let i in p.closest){
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p.closest[i].x, p.closest[i].y);
ctx.strokeStyle = 'rgba(' + colorRGB +','+ p.active+')';
ctx.stroke();
}
};
let Circle = function(pos,rad,color){
let _this = this;
// constructor
(function(){
_this.pos = pos || null;
_this.radius = rad || null;
_this.color = color || null;
})();
this.draw = function(){
if(!_this.active) return;
ctx.beginPath();
ctx.arc(_this.pos.x, _this.pos.y, _this.radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'rgba(' + colorRGB + ','+ _this.active+')';
ctx.fill();
};
};
// Util
let getDistance = function(p1, p2){
return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
};
/**
* init header animation
* @param callback
*/
$.fn.initHeader = function(callback){
largeHeader = $(this)[0];
canvas = $(this).find('canvas:visible')[0];
// header preview elements
$('.' + config.previewElementClass).velocity('transition.bounceIn', {
duration: 600,
stagger: 60,
delay: 120,
complete: function(){
// show header canvas animation
if(canvas){
// header animation
initHeader();
initAnimation();
addListeners();
$(canvas).velocity('fadeIn', {
duration: 900,
visibility: 'visible',
complete: function(){
if(callback !== undefined){
callback();
}
}
});
}
}
});
};
});

Some files were not shown because too many files have changed in this diff Show More