- BC Break: Required _PHP_ version changed >=7.1>=7.2

- NEW "plugin API" for custom UI modules, closed #913
- NEW live "Killstream" for killboard module, closed #909
- NEW "custom layout" UI settings, closed #470
This commit is contained in:
Mark Friedrich
2020-02-01 12:40:17 +01:00
parent c91bcbe246
commit 0c3d57e833
151 changed files with 33387 additions and 22779 deletions

View File

@@ -35,7 +35,7 @@
"latedef": false,
// Enforce line length to 100 characters
"maxlen": 200,
"maxlen": 220,
// Require capitalized names for constructor functions.
"newcap": true,

View File

@@ -170,7 +170,7 @@ class Map extends Controller\AccessController {
// get program routes -------------------------------------------------------------------------------------
$return->routes = [
'ssoLogin' => $this->getF3()->alias( 'sso', ['action' => 'requestAuthorization'] )
'ssoLogin' => $this->getF3()->alias('sso', ['action' => 'requestAuthorization'])
];
// get third party APIs -----------------------------------------------------------------------------------
@@ -182,6 +182,11 @@ class Map extends Controller\AccessController {
'anoik' => Config::getPathfinderData('api.anoik')
];
// get Plugin config --------------------------------------------------------------------------------------
$return->plugin = [
'modules' => Config::getPluginConfig('modules')
];
// Character default config -------------------------------------------------------------------------------
$return->character = [
'autoLocationSelect' => (bool)Config::getPathfinderData('character.auto_location_select')
@@ -1065,7 +1070,7 @@ class Map extends Controller\AccessController {
){
// check distance between systems (in jumps)
// -> if > 1 it is !very likely! a wormhole
$route = (new Route())->searchRoute($sourceSystem->systemId, $targetSystem->systemId, 1);
$route = (new Controller\Api\Rest\Route())->searchRoute($sourceSystem->systemId, $targetSystem->systemId, 1);
if(!$route['routePossible']){
$addSourceSystem = true;
@@ -1165,7 +1170,7 @@ class Map extends Controller\AccessController {
// .. now we need to check jump distance between systems
// -> if > 1 it is !very likely! podded jump
if(empty($route)){
$route = (new Route())->searchRoute($sourceSystem->systemId, $targetSystem->systemId, 1);
$route = (new Controller\Api\Rest\Route())->searchRoute($sourceSystem->systemId, $targetSystem->systemId, 1);
}
if(!$route['routePossible']){

View File

@@ -13,6 +13,51 @@ use Exodus4D\Pathfinder\Model\Pathfinder;
class Connection extends AbstractRestController {
/**
* @param \Base $f3
* @param $params
* @throws \Exception
*/
public function get(\Base $f3, $params){
$requestData = $this->getRequestData($f3);
$connectionIds = array_map('intval', explode(',', (string)$params['id']));
$addData = (array)$requestData['addData'];
$filterData = (array)$requestData['filterData'];
$connectionData = [];
if($mapId = (int)$requestData['mapId']){
$activeCharacter = $this->getCharacter();
/**
* @var $map Pathfinder\MapModel
*/
$map = Pathfinder\AbstractPathfinderModel::getNew('MapModel');
$map->getById($mapId);
if($map->hasAccess($activeCharacter)){
$connections = $map->getConnections($connectionIds, 'wh');
foreach($connections as $connection){
$check = true;
$data = $connection->getData(in_array('signatures', $addData), in_array('logs', $addData));
// filter result
if(in_array('signatures', $filterData) && !$data->signatures){
$check = false;
}
if(in_array('logs', $filterData) && !$data->logs){
$check = false;
}
if($check){
$connectionData[] = $data;
}
}
}
}
$this->out($connectionData);
}
/**
* save a new connection or updates an existing (drag/drop) between two systems
* if a connection is changed (drag&drop) to another system. -> this function is called for update

View File

@@ -1,24 +1,15 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 06.06.15
* Time: 03:34
*/
namespace Exodus4D\Pathfinder\Controller\Api;
namespace Exodus4D\Pathfinder\Controller\Api\Rest;
use Exodus4D\Pathfinder\Lib\Config;
use Exodus4D\Pathfinder\Controller;
use Exodus4D\Pathfinder\Controller\Ccp\Universe;
use Exodus4D\Pathfinder\Model\Pathfinder;
/**
* Routes controller
* Class Route
* @package Controller\Api
*/
class Route extends Controller\AccessController {
class Route extends AbstractRestController {
/**
* cache key for current Thera connections from eve-scout.com
@@ -739,8 +730,8 @@ class Route extends Controller\AccessController {
* @param \Base $f3
* @throws \Exception
*/
public function search($f3){
$requestData = (array)$f3->get('POST');
public function post(\Base $f3){
$requestData = $this->getRequestData($f3);
$activeCharacter = $this->getCharacter();
@@ -861,20 +852,6 @@ class Route extends Controller\AccessController {
}
}
echo json_encode($return);
$this->out($return);
}
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Exodus4D\Pathfinder\Controller\Api\Rest;
use Exodus4D\Pathfinder\Lib\Config;
use Exodus4D\Pathfinder\Model\Pathfinder;
class SystemGraph extends AbstractRestController {
/**
* cache key for HTTP response
*/
const CACHE_KEY_GRAPH = 'CACHED_SYSTEM_GRAPH_%s';
/**
* get graphs data for system(s)
* @param \Base $f3
* @param $params
* @throws \Exception
*/
public function get(\Base $f3, $params){
$requestData = $this->getRequestData($f3);
$systemIds = (array)$requestData['systemIds'];
$graphsData = [];
// valid response (data found) should be cached by server + client
$cacheResponse = false;
// number of log entries in each table per system (24 = 24h)
$logEntryCount = Pathfinder\AbstractSystemApiBasicModel::DATA_COLUMN_COUNT;
$ttl = 60 * 10;
// table names with system data
$logTables = [
'jumps' => 'SystemJumpModel',
'shipKills' => 'SystemShipKillModel',
'podKills' => 'SystemPodKillModel',
'factionKills' => 'SystemFactionKillModel'
];
$exists = false;
foreach($systemIds as $systemId){
$cacheKey = $this->getSystemGraphCacheKey($systemId);
if(!$exists = $f3->exists($cacheKey, $graphData)){
$graphData = [];
$cacheSystem = false;
foreach($logTables as $label => $className){
$systemLogModel = Pathfinder\AbstractSystemApiBasicModel::getNew($className);
$systemLogExists = false;
// 10min cache (could be up to 1h cache time)
$systemLogModel->getByForeignKey('systemId', $systemId);
if($systemLogModel->valid()){
$systemLogExists = true;
$cacheSystem = true;
$cacheResponse = true;
}
$systemLogData = $systemLogModel->getData();
// podKills share graph with shipKills -> skip
if($label != 'podKills'){
$graphData[$label]['logExists'] = $systemLogExists;
$graphData[$label]['updated'] = $systemLogData->updated;
}
$logValueCount = range(0, $logEntryCount - 1);
foreach($logValueCount as $i){
if($label == 'podKills'){
$graphData['shipKills']['data'][$i]['z'] = $systemLogData->values[$i];
}else{
$graphData[$label]['data'][] = [
'x' => ($logEntryCount - $i - 1) . 'h',
'y' => $systemLogData->values[$i]
];
}
}
}
if($cacheSystem){
$f3->set($cacheKey, $graphData, $ttl);
}
}else{
// server cache data exists -> client should cache as well
$cacheResponse = true;
}
$graphsData[$systemId] = $graphData;
}
if($cacheResponse){
// send client cache header
$f3->expire(Config::ttlLeft($exists, $ttl));
}
$this->out($graphsData);
}
// ----------------------------------------------------------------------------------------------------------------
/**
* get system graph cache key
* @param int $systemId
* @return string
*/
protected function getSystemGraphCacheKey(int $systemId): string {
return sprintf(self::CACHE_KEY_GRAPH, 'SYSTEM_' . $systemId);
}
}

View File

@@ -14,103 +14,6 @@ use Exodus4D\Pathfinder\Model\Pathfinder;
class System extends Controller\AccessController {
// cache keys
const CACHE_KEY_GRAPH = 'CACHED_SYSTEM_GRAPH_%s';
/**
* get system graph cache key
* @param int $systemId
* @return string
*/
protected function getSystemGraphCacheKey(int $systemId): string {
return sprintf(self::CACHE_KEY_GRAPH, 'SYSTEM_' . $systemId);
}
/**
* get system log data from CCP API import
* system Kills, Jumps,....
* @param \Base $f3
* @throws \Exception
*/
public function graphData(\Base $f3){
$graphsData = [];
$systemIds = (array)$f3->get('GET.systemIds');
// valid response (data found) should be cached by server + client
$cacheResponse = false;
// number of log entries in each table per system (24 = 24h)
$logEntryCount = Pathfinder\AbstractSystemApiBasicModel::DATA_COLUMN_COUNT;
$ttl = 60 * 10;
// table names with system data
$logTables = [
'jumps' => 'SystemJumpModel',
'shipKills' => 'SystemShipKillModel',
'podKills' => 'SystemPodKillModel',
'factionKills' => 'SystemFactionKillModel'
];
$exists = false;
foreach($systemIds as $systemId){
$cacheKey = $this->getSystemGraphCacheKey($systemId);
if(!$exists = $f3->exists($cacheKey, $graphData)){
$graphData = [];
$cacheSystem = false;
foreach($logTables as $label => $className){
$systemLogModel = Pathfinder\AbstractSystemApiBasicModel::getNew($className);
$systemLogExists = false;
// 10min cache (could be up to 1h cache time)
$systemLogModel->getByForeignKey('systemId', $systemId);
if($systemLogModel->valid()){
$systemLogExists = true;
$cacheSystem = true;
$cacheResponse = true;
}
$systemLogData = $systemLogModel->getData();
// podKills share graph with shipKills -> skip
if($label != 'podKills'){
$graphData[$label]['logExists'] = $systemLogExists;
$graphData[$label]['updated'] = $systemLogData->updated;
}
$logValueCount = range(0, $logEntryCount - 1);
foreach($logValueCount as $i){
if($label == 'podKills'){
$graphData['shipKills']['data'][$i]['z'] = $systemLogData->values[$i];
}else{
$graphData[$label]['data'][] = [
'x' => ($logEntryCount - $i - 1) . 'h',
'y' => $systemLogData->values[$i]
];
}
}
}
if($cacheSystem){
$f3->set($cacheKey, $graphData, $ttl);
}
}else{
// server cache data exists -> client should cache as well
$cacheResponse = true;
}
$graphsData[$systemId] = $graphData;
}
if($cacheResponse){
// send client cache header
$f3->expire(Config::ttlLeft($exists, $ttl));
}
echo json_encode($graphsData);
}
/**
* set destination for system, station or structure
* @param \Base $f3

View File

@@ -93,7 +93,7 @@ class Controller {
header('Pf-Maintenance: ' . $modeMaintenance);
}
}else{
$this->initResource($f3);
$f3->set('tplResource', $this->initResource($f3));
$this->setTemplate(Config::getPathfinderData('view.index'));
@@ -161,15 +161,18 @@ class Controller {
/**
* init new Resource handler
* @param \Base $f3
* @return Resource
*/
protected function initResource(\Base $f3){
$resource = Resource::instance();
$resource->setOption('basePath', $f3->get('BASE'));
$resource->setOption('filePath', [
'style' => $f3->get('BASE') . '/public/css/' . Config::getPathfinderData('version'),
'script' => $f3->get('BASE') . '/public/js/' . Config::getPathfinderData('version'),
'font' => $f3->get('BASE') . '/public/fonts',
'document' => $f3->get('BASE') . '/public/templates',
'image' => $f3->get('BASE') . '/public/img'
'style' => sprintf('/%scss/%s', $f3->get('UI'), Config::getPathfinderData('version')),
'script' => sprintf('/%sjs/%s', $f3->get('UI'), Config::getPathfinderData('version')),
'font' => sprintf('/%sfonts', $f3->get('UI')),
'document' => sprintf('/%stemplates', $f3->get('UI')),
'image' => sprintf('/%simg', $f3->get('UI')),
'favicon' => $f3->get('FAVICON')
], true);
$resource->register('style', 'pathfinder');
@@ -187,7 +190,7 @@ class Controller {
$resource->register('url', Config::getPathfinderData('api.ccp_image_server'), 'dns-prefetch');
$resource->register('url', '//i.ytimg.com', 'dns-prefetch'); // YouTube tiny embed domain
$f3->set('tplResource', $resource);
return $resource;
}
/**

View File

@@ -148,7 +148,7 @@ class Setup extends Controller {
* @return bool
*/
function beforeroute(\Base $f3, $params): bool {
$this->initResource($f3);
$f3->set('tplResource', $this->initResource($f3));
// page title
$f3->set('tplPageTitle', 'Setup | ' . Config::getPathfinderData('name'));

View File

@@ -39,6 +39,11 @@ class Config extends \Prefab {
*/
const HIVE_KEY_ENVIRONMENT = 'ENVIRONMENT';
/**
* Hive key for custom plugins (js map modules)
*/
const HIVE_KEY_PLUGIN = 'PLUGIN';
/**
* Hive key for Socket validation check
*/
@@ -374,7 +379,7 @@ class Config extends \Prefab {
* get SMTP config values
* @return \stdClass
*/
static function getSMTPConfig(): \stdClass{
static function getSMTPConfig() : \stdClass{
$config = new \stdClass();
$config->host = self::getEnvironmentData('SMTP_HOST');
$config->port = self::getEnvironmentData('SMTP_PORT');
@@ -392,9 +397,9 @@ class Config extends \Prefab {
* @param \stdClass $config
* @return bool
*/
static function isValidSMTPConfig(\stdClass $config): bool {
static function isValidSMTPConfig(\stdClass $config) : bool {
// validate email from either an configured array or plain string
$validateMailConfig = function($mailConf = null): bool {
$validateMailConfig = function($mailConf = null) : bool {
$email = null;
if(is_array($mailConf)){
reset($mailConf);
@@ -436,13 +441,35 @@ class Config extends \Prefab {
return $mapConfig;
}
/**
* get Plugin config from `plugin.ini`
* @param string|null $key
* @param bool $checkEnabled
* @return array|null
*/
static function getPluginConfig(?string $key, bool $checkEnabled = true) : ?array {
$isEnabled = $checkEnabled ?
filter_var(\Base::instance()->get(
self::HIVE_KEY_PLUGIN . '.' . strtoupper($key) . '_ENABLED'),
FILTER_VALIDATE_BOOLEAN
) :
true;
$data = null;
if($isEnabled){
$hiveKey = self::HIVE_KEY_PLUGIN . '.' . strtoupper($key);
$data = (array)\Base::instance()->get($hiveKey);
}
return $data;
}
/**
* get custom $message for a a HTTP $status
* -> use this in addition to the very general Base::HTTP_XXX labels
* @param int $status
* @return string
*/
static function getMessageFromHTTPStatus(int $status): string {
static function getMessageFromHTTPStatus(int $status) : string {
switch($status){
case 403:
$message = 'Access denied: User not found'; break;

View File

@@ -15,45 +15,52 @@ class Resource extends \Prefab {
* default link "rel" attribute
* @link https://w3c.github.io/preload/#x2.link-type-preload
*/
const ATTR_REL = 'preload';
const ATTR_REL = 'preload';
/**
* default link "as" attributes
*/
const ATTR_AS = [
'style' => 'style',
'script' => 'script',
'font' => 'font',
'document' => 'document',
'image' => 'image',
'url' => ''
'style' => 'style',
'script' => 'script',
'font' => 'font',
'document' => 'document',
'image' => 'image',
'url' => ''
];
/**
* default link "type" attributes
*/
const ATTR_TYPE = [
'font' => 'font/woff2'
'font' => 'font/woff2'
];
/**
* default additional attributes by $group
*/
const ATTR_ADD = [
'font' => ['crossorigin' => 'anonymous']
'font' => ['crossorigin' => 'anonymous']
];
/**
* BASE path
* @var string
*/
private $basePath = '';
/**
* absolute file path -> use setOption() for update
* @var array
*/
private $filePath = [
'style' => '',
'script' => '',
'font' => '',
'document' => '',
'image' => '',
'url' => ''
'style' => '',
'script' => '',
'font' => '',
'document' => '',
'image' => '',
'favicon' => '',
'url' => ''
];
/**
@@ -62,10 +69,10 @@ class Resource extends \Prefab {
* @var array
*/
private $fileExt = [
'style' => 'css',
'script' => 'js',
'document' => 'html',
'font' => 'woff2'
'style' => 'css',
'script' => 'js',
'document' => 'html',
'font' => 'woff2'
];
/**
@@ -76,7 +83,7 @@ class Resource extends \Prefab {
* @see buildHeader()
* @var string
*/
private $output = 'inline';
private $output = 'inline';
/**
* resource file cache
@@ -134,7 +141,7 @@ class Resource extends \Prefab {
* @return string
*/
public function getPath(string $group) : string {
return $this->filePath[$group];
return rtrim($this->basePath, '/\\') . $this->filePath[$group];
}
/**

View File

@@ -9,7 +9,7 @@
namespace Exodus4D\Pathfinder\Model\Pathfinder;
use DB\SQL\Schema;
use Exodus4D\Pathfinder\Controller\Api\Route;
use Exodus4D\Pathfinder\Controller\Api\Rest\Route;
use Exodus4D\Pathfinder\Lib\Logging;
use Exodus4D\Pathfinder\Exception;
@@ -234,9 +234,7 @@ class ConnectionModel extends AbstractMapTrackingModel {
$this->scope = 'abyssal';
$this->type = ['abyssal'];
}else{
$routeController = new Route();
$route = $routeController->searchRoute($this->source->systemId, $this->target->systemId, 1);
$route = (new Route())->searchRoute($this->source->systemId, $this->target->systemId, 1);
if($route['routePossible']){
$this->scope = 'stargate';
$this->type = ['stargate'];

View File

@@ -137,6 +137,8 @@ CONF.DEFAULT = app/
[configs]
{{@CONF.DEFAULT}}routes.ini = true
{{@CONF.DEFAULT}}pathfinder.ini = true
{{@CONF.DEFAULT}}plugin.ini = true
{{@CONF.CUSTOM}}pathfinder.ini = true
{{@CONF.CUSTOM}}plugin.ini = true
{{@CONF.DEFAULT}}requirements.ini = true
{{@CONF.DEFAULT}}cron.ini = true

6
app/plugin.ini Normal file
View File

@@ -0,0 +1,6 @@
[PLUGIN]
MODULES_ENABLED = 1
[PLUGIN.MODULES]
DEMO = ./app/ui/module/demo
EMPTY = ./app/ui/module/empty

View File

@@ -27,7 +27,7 @@
}
],
"require": {
"php-64bit": ">=7.1",
"php-64bit": ">=7.2",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-curl": "*",
@@ -40,7 +40,7 @@
"ikkez/f3-sheet": "0.4.*",
"xfra35/f3-cron": "1.2.*",
"monolog/monolog": "2.*",
"swiftmailer/swiftmailer": "6.2.x",
"swiftmailer/swiftmailer": "6.2.*",
"league/html-to-markdown": "4.9.*",
"cache/redis-adapter": "1.0.*",
"cache/filesystem-adapter": "1.0.*",

View File

@@ -32,3 +32,11 @@ cache_path = '.sass-cache'
# preferred_syntax = :sass
# and then run:
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
# custom SASS functions
module Sass::Script::Functions
def currentYear()
# return Sass::Script::String.new(Time.now.to_s)
return Sass::Script::String.new(Time.now.year.to_s)
end
end

View File

@@ -514,7 +514,7 @@ gulp.task('task:hintJS', () => {
* concat/build JS files by modules
*/
gulp.task('task:concatJS', () => {
let modules = ['login', 'mappage', 'setup', 'admin', 'notification', 'datatables.loader'];
let modules = ['login', 'mappage', 'setup', 'admin', 'PNotify.loader', 'datatables.loader'];
let srcModules = ['./js/app/*(' + modules.join('|') + ').js'];
return gulp.src(srcModules, {base: 'js'})

View File

@@ -25,7 +25,6 @@ requirejs.config({
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
@@ -56,7 +55,7 @@ requirejs.config({
bootstrapConfirmation: 'lib/bootstrap-confirmation.min', // v1.0.7 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
lazyload: 'lib/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
sortable: 'lib/sortable.min', // v1.10.1 Sortable - drag&drop reorder - https://github.com/SortableJS/Sortable
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org
'summernote': 'lib/summernote/summernote.min',
@@ -65,7 +64,7 @@ requirejs.config({
easePack: 'lib/EasePack.min',
tweenLite: 'lib/TweenLite.min',
// datatables // v1.10.18 DataTables - https://datatables.net
// 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',
@@ -74,15 +73,14 @@ requirejs.config({
'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
// PNotify // v4.0.0 PNotify - notification core file - https://sciactive.com/pnotify
'PNotify.loader': './app/pnotify.loader',
'PNotify': 'lib/pnotify/PNotify',
'PNotifyButtons': 'lib/pnotify/PNotifyButtons',
'PNotifyNonBlock': 'lib/pnotify/PNotifyNonBlock',
'PNotifyDesktop': 'lib/pnotify/PNotifyDesktop',
'PNotifyCallbacks': 'lib/pnotify/PNotifyCallbacks',
'NonBlock': 'lib/pnotify/NonBlock' // v1.0.8 NonBlock.js - for PNotify "nonblock" feature
},
shim: {
bootstrap: {
@@ -138,9 +136,6 @@ requirejs.config({
window.Raphael = Raphael;
}
},
pnotify: {
deps: ['jquery']
},
easyPieChart: {
deps: ['jquery']
},

View File

@@ -5,11 +5,12 @@ define([
'app/promises/promise.deferred',
'app/promises/promise.timeout',
'datatables.net',
'datatables.net-select',
'datatables.net-buttons',
'datatables.net-buttons-html',
'datatables.net-responsive',
'datatables.net-select'
'datatables.net-responsive'
], ($, Init, Counter, DeferredPromise, TimeoutPromise) => {
'use strict';
// all Datatables stuff is available...

View File

@@ -44,11 +44,8 @@ define([], () => {
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

View File

@@ -127,7 +127,7 @@ define([], () => {
constructor(config){
this.config = Object.assign({},{
name: 'Default', // custom name for identification
name: 'Default', // custom unique name for identification
ttl: 3600, // default ttl for cache entries
maxSize: 600, // max cache entries
bufferSize: 10, // cache entry count in percent to be removed if maxSize reached

View File

@@ -107,7 +107,7 @@ define([
delete(){
let isDeleted = false;
if(this._manager){
isDeleted = this._manager.delete(this.name);
isDeleted = this._manager.delete(this._name);
}
return isDeleted;
}

55
js/app/lib/dataStore.js Normal file
View File

@@ -0,0 +1,55 @@
define([], () => {
'use strict';
/*
// Example usage --------------------------------------------------------------------------------------------------
// global accessible DataStore instance
window.dataStore = new DataStore();
// extend HTMLElement class with an interface to set/get data to it
HTMLElement.prototype.setData = function(key, value){
window.dataStore.set(this, key, value);
};
HTMLElement.prototype.getData = function(key){
return window.dataStore.get(this, key);
};
*/
/**
* Stores data to an object
* -> can be used as a replacement for jQuery $.data()
*/
return class DataStore {
constructor() {
this._store = new WeakMap();
}
set(obj, key, value) {
if (!this._store.has(obj)) {
this._store.set(obj, new Map());
}
this._store.get(obj).set(key, value);
return obj;
}
get(obj, key) {
return this._store.has(obj) && this._store.get(obj).get(key);
}
has(obj, key) {
return this._store.has(obj) && this._store.get(obj).has(key);
}
remove(obj, key) {
let ret = false;
if (this._store.has(obj)) {
ret = this._store.get(obj).delete(key);
if (!this._store.get(obj).size) {
this._store.delete(obj);
}
}
return ret;
}
};
});

394
js/app/lib/localStore.js Normal file
View File

@@ -0,0 +1,394 @@
define([
'localForage',
'app/promises/promise.queue',
'app/promises/promise.deferred',
], (LocalForage, PromiseQueue, DeferredPromise) => {
'use strict';
/**
* Instances of LocalStore handle its own LocalForage instance
*/
class LocalStore {
constructor(config, LocalForageConfig){
this._config = Object.assign({}, this.constructor.defaultConfig, config);
let initPromise = new DeferredPromise();
this._processQueue = new PromiseQueue();
this._processQueue.enqueue(() => initPromise);
this._localforage = LocalForage.createInstance(Object.assign({}, LocalStore.LocalForageConfig, LocalForageConfig));
this._localforage.ready().then(() => initPromise.resolve());
this._manager = null; // reference to LocalStoreManager() that manages this LocalStore instance
this.debug = (msg,...data) => {
if(this._config.debug){
data = (data || []);
data.unshift(this.constructor.name, this._config.name);
console.debug('debug: %s %o | ' + msg, ...data);
}
};
}
/**
* set scope for this instance
* -> all read/write actions are scoped
* this is a prefix for all keys!
* @param scope
*/
set scope(scope){
if(LocalStore.isString(scope)){
this._config.scope = scope;
}else{
throw new TypeError('Scope must be instance of "String", Type of "' + typeof scope + '" given');
}
}
get scope(){
return this._config.scope;
}
/**
* get item
* @param key
* @param successCallback
* @returns {Promise}
*/
getItem(key, successCallback = undefined){
key = this.fixKey(key);
let propArray = LocalStore.keyToArray(key);
let rootKey = propArray.shift();
let getItem = () => this._localforage.getItem(key, successCallback);
if(propArray.length){
getItem = () => {
return this._localforage.getItem(rootKey)
.then(data => {
if(LocalStore.isObject(data)){
// find nested property
return LocalStore.findObjProp(data, propArray);
}else{
// rootKey not found -> propArray path not exists
return Promise.resolve(null);
}
});
};
}
return this._processQueue.enqueue(() => getItem());
}
/**
* set/update existing value
* @param key e.g. nested object key' first.a.b.test'
* @param value
* @param successCallback
* @returns {Promise}
*/
setItem(key, value, successCallback = undefined){
key = this.fixKey(key);
let propArray = LocalStore.keyToArray(key);
let rootKey = propArray.shift();
let getItem = () => Promise.resolve(value);
if(propArray.length){
getItem = () => {
return this._localforage.getItem(rootKey)
.then(rootVal => {
rootVal = (rootVal === null) ? {} : rootVal;
// update data with new value (merge obj)
LocalStore.updateObjProp(rootVal, value, propArray);
return rootVal;
});
};
}
return this._processQueue.enqueue(() =>
getItem()
.then(rootVal => this._localforage.setItem(rootKey, rootVal, successCallback))
.then(() => Promise.resolve(value))
);
}
/**
* remove item by key
* -> allows deep obj delete if key points to a nested obj prop
* @param key
* @param successCallback
* @returns {Promise}
*/
removeItem(key, successCallback = undefined){
key = this.fixKey(key);
let propArray = LocalStore.keyToArray(key);
let rootKey = propArray.shift();
let removeItem = () => this._localforage.removeItem(rootKey, successCallback);
if(propArray.length){
removeItem = () => {
return this._localforage.getItem(rootKey)
.then(data => {
if(LocalStore.isObject(data)){
// update data -> delete nested prop
LocalStore.deleteObjProp(data, propArray);
return data;
}else{
// rootKey not found -> nothing to delete
return Promise.reject(new RangeError('No data found for key: ' + rootKey));
}
})
.then(value => this._localforage.setItem(rootKey, value, successCallback))
.catch(e => this.debug('removeItem() error',e));
};
}
return this._processQueue.enqueue(() => removeItem());
}
/**
* clear all items in store
* @param successCallback
* @returns {Promise}
*/
clear(successCallback = undefined){
return this._processQueue.enqueue(() => this._localforage.clear(successCallback));
}
/**
* get number of keys in store
* @param successCallback
* @returns {Promise}
*/
length(successCallback = undefined){
return this._processQueue.enqueue(() => this._localforage.length(successCallback));
}
/**
* Get the name of a key based on its index
* @param keyIndex
* @param successCallback
* @returns {Promise|void}
*/
key(keyIndex, successCallback = undefined){
return this._processQueue.enqueue(() => this._localforage.key(keyIndex, successCallback));
}
/**
* get list of all keys in store
* @param successCallback
* @returns {Promise|void}
*/
keys(successCallback = undefined){
return this._processQueue.enqueue(() => this._localforage.keys(successCallback));
}
/**
* drop current LocalForage instance
* -> removes this from LocalStoreManager
* @returns {Promise|void}
*/
dropInstance(){
return this._processQueue.enqueue(() =>
this._localforage.dropInstance().then(() => this._manager.deleteStore(this._config.name))
);
}
/**
* set LocalStoreManager for this instance
* @param {LocalStoreManager} manager
*/
setManager(manager){
if(manager instanceof LocalStoreManager){
this._manager = manager;
}else{
throw new TypeError('Parameter must be instance of LocalStoreManager. Type of "' + typeof manager + '" given');
}
}
/**
* check if key is Int or String with Int at pos 0
* -> prefix key
* @param key
* @returns {string}
*/
fixKey(key){
if(LocalStore.isString(this.scope) && this.scope.length){
key = [this.scope, key].join('.');
}
if(
Number.isInteger(key) ||
(LocalStore.isString(key) && parseInt(key.charAt(0), 10))
){
key = [this._config.name, key].join('_');
}
return key;
}
/**
* find data from obj prop
* -> deep object search
* @param obj
* @param propArray
* @returns {null|*}
*/
static findObjProp(obj, propArray){
let [head, ...rest] = propArray;
if(!rest.length){
return obj[head];
}else{
if(LocalStore.isObject(obj[head])){
return LocalStore.findObjProp(obj[head], rest);
}else{
return null;
}
}
}
/**
* update/extend obj with new value
* -> deep object manipulation
* @param obj
* @param value
* @param propArray
*/
static updateObjProp(obj, value, propArray){
let [head, ...rest] = propArray;
if(!rest.length){
obj[head] = value;
}else{
if(!LocalStore.isObject(obj[head])) obj[head] = {};
LocalStore.updateObjProp(obj[head], value, rest);
}
}
/**
* delete object prop by propArray path
* -> deep object delete
* @param obj
* @param propArray
*/
static deleteObjProp(obj, propArray){
let [head, ...rest] = propArray;
if(!rest.length){
delete obj[head];
}else{
if(LocalStore.isObject(obj[head])){
LocalStore.deleteObjProp(obj[head], rest);
}
}
}
/**
* converts string key to array
* @param propPath
* @returns {*|string[]}
*/
static keyToArray(propPath){
return propPath.split('.');
}
/**
* build DB name
* @param name
* @returns {string}
*/
static buildDbName(name){
return [LocalStore.dbNamePrefix, name].join(' ');
}
/**
* check var for Object
* @param obj
* @returns {boolean|boolean}
*/
static isObject(obj){
return (!!obj) && (obj.constructor === Object);
}
/**
* check var for Array
* @param arr
* @returns {boolean}
*/
static isArray(arr){
return (!!arr) && (arr.constructor === Array);
}
/**
* check var for String
* @param str
* @returns {boolean}
*/
static isString(str){
return typeof str === 'string';
}
}
LocalStore.defaultConfig = {
name: 'default', // custom unique name for identification
debug: false
};
LocalStore.dbNamePrefix = 'PathfinderDB';
LocalStore.LocalForageConfig = {
driver: [LocalForage.INDEXEDDB, LocalForage.WEBSQL, LocalForage.LOCALSTORAGE],
name: LocalStore.dbNamePrefix
};
/**
* An instance of LocalStoreManager() handles multiple LocalStore()´s
* -> LocalStore()´s can be set()/delete() from LocalStore() instance
*/
class LocalStoreManager {
constructor(){
if(!this.constructor.instance){
this._store = new Map();
this.constructor.instance = this;
}
return this.constructor.instance;
}
/**
* get LocalStore instance by name
* @param name
* @returns {LocalStore}
*/
getStore(name){
return this.newStore(name);
}
/**
* get either existing LocalStore instance
* -> or create new instance
* @param name
* @returns {LocalStore|undefined}
*/
newStore(name){
if(!this._store.has(name)){
let store = new LocalStore({
name: name
}, {
name: LocalStore.buildDbName(name)
});
store.setManager(this);
this._store.set(name, store);
}
return this._store.get(name);
}
/**
* removes LocalStore instance from Manager
* -> this will not drop LocalForage instance!
* check LocalStore.dropInstance() for graceful delete
* @param name
* @returns {boolean}
*/
deleteStore(name){
return this._store.delete(name);
}
}
return new LocalStoreManager();
});

View File

@@ -1,6 +1,44 @@
define([], () => {
define([
'app/lib/dataStore'
], (DataStore) => {
'use strict';
// DOM node data store ============================================================================================
window.dataStore = new DataStore();
/**
* @param key
* @param value
* @returns {HTMLElement}
*/
HTMLElement.prototype.setData = function(key, value){
return window.dataStore.set(this, key, value);
};
/**
* @param key
* @returns {*}
*/
HTMLElement.prototype.getData = function(key){
return window.dataStore.get(this, key);
};
/**
* @param key
* @returns {*}
*/
HTMLElement.prototype.hasData = function(key){
return window.dataStore.has(this, key);
};
/**
* @param key
* @returns {*}
*/
HTMLElement.prototype.removeData = function(key){
return window.dataStore.remove(this, key);
};
/**
* Array diff
* [1,2,3,4,5].diff([4,5,6]) => [1,2,3]

View File

@@ -37,6 +37,7 @@ define([
if(logDialog.length){
// dialog is open
let statusArea = logDialog.find('.' + config.taskDialogStatusAreaClass);
statusArea.destroyTooltips(true);
requirejs(['text!templates/modules/sync_status.html', 'mustache'], (templateSyncStatus, Mustache) => {
let data = {
timestampCounterClass: config.timestampCounterClass,
@@ -57,7 +58,7 @@ define([
let counterElements = syncStatusElement.find('.' + config.timestampCounterClass);
Counter.initTimestampCounter(counterElements);
syncStatusElement.initTooltips({
statusArea.initTooltips({
placement: 'right'
});
});
@@ -92,9 +93,9 @@ define([
// 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>>',
dom: '<"flex-row flex-between"<"flex-col"l><"flex-col"B><"flex-col"fS>>' +
'<"flex-row"<"flex-col flex-grow"tr>>' +
'<"flex-row flex-between"<"flex-col"i><"flex-col"p>>',
buttons: {
name: 'tableTools',
buttons: [
@@ -286,23 +287,24 @@ define([
parseTime: false,
ymin: 0,
yLabelFormat: labelYFormat,
padding: 10,
padding: 8,
hideHover: true,
pointSize: 3,
pointSize: 2.5,
lineColors: ['#375959'],
pointFillColors: ['#477372'],
pointStrokeColors: ['#313335'],
lineWidth: 2,
grid: false,
lineWidth: 1.5,
grid: true,
gridStrokeWidth: 0.3,
gridTextSize: 9,
gridTextFamily: 'Oxygen Bold',
gridTextColor: '#63676a',
behaveLikeLine: true,
behaveLikeLine: false,
goals: [],
goalStrokeWidth: 1,
goalLineColors: ['#66c84f'],
smooth: false,
fillOpacity: 0.3,
fillOpacity: 0.2,
resize: true
});

View File

@@ -529,16 +529,16 @@ define([
setVersionLinkObserver();
// mark panel as "shown"
Util.getLocalStorage().setItem(storageKey, currentVersion);
Util.getLocalStore('default').setItem(storageKey, currentVersion);
}
});
});
};
Util.getLocalStorage().getItem(storageKey).then(function(data){
Util.getLocalStore('default').getItem(storageKey).then(data => {
// check if panel was shown before
if(data){
if(data !== this.version){
if(data !== currentVersion){
// show current panel
showNotificationPanel();
}
@@ -546,9 +546,7 @@ define([
// show current panel
showNotificationPanel();
}
}.bind({
version: currentVersion
}));
});
};
/**

View File

@@ -10,6 +10,7 @@ define([
'use strict';
let config = {
contextMenuContainerId: 'pf-contextmenu-container', // id for container element that holds (hidden) context menus
mapContextMenuId: 'pf-map-contextmenu', // id for "maps" context menu
connectionContextMenuId: 'pf-map-connection-contextmenu', // id for "connections" context menu
endpointContextMenuId: 'pf-map-endpoint-contextmenu', // id for "endpoints" context menu

View File

@@ -100,7 +100,7 @@ define([
let isOpenStatus = isOpen(overlayMain);
// store current state in indexDB (client)
MapUtil.storeLocalData('map', mapId, 'showLocal', !isOpenStatus );
Util.getLocalStore('map').setItem(`${mapId}.showLocal`, !isOpenStatus);
// trigger open/close
if( isOpenStatus ){
@@ -111,8 +111,8 @@ define([
});
// trigger table re-draw() ------------------------------------------------------------------------------------
let mapWrapper = overlay.parents('.' + MapUtil.config.mapWrapperClass);
mapWrapper.on('pf:mapResize', function(e){
let areaMap = overlay.closest('.' + Util.getMapTabContentAreaClass('map'));
areaMap.on('pf:mapResize', function(e){
let tableElement = overlay.find('.' + config.overlayLocalTableClass);
let tableApi = tableElement.DataTable();
tableApi.draw('full-hold');
@@ -120,7 +120,6 @@ define([
// tooltips ---------------------------------------------------------------------------------------------------
overlayMain.initTooltips({
container: 'body',
placement: 'bottom'
});
};
@@ -247,8 +246,7 @@ define([
// open Overlay -------------------------------------------------------------------------------------------
if( !isOpen(overlay) ){
let promiseStore = MapUtil.getLocaleData('map', mapId);
promiseStore.then(dataStore => {
Util.getLocalStore('map').getItem(mapId).then(dataStore => {
if(
dataStore &&
dataStore.showLocal
@@ -358,12 +356,12 @@ define([
// init local table ---------------------------------------------------------------------------------------
table.on('preDraw.dt', function(e, settings){
let table = $(this);
let mapWrapper = table.parents('.' + MapUtil.config.mapWrapperClass);
let areaMap = table.closest('.' + Util.getMapTabContentAreaClass('map'));
// mapWrapper should always exist
if(mapWrapper && mapWrapper.length) {
// areaMap should always exist
if(areaMap && areaMap.length) {
// check available maxHeight for "locale" table based on current map height (resizable)
let mapHeight = mapWrapper[0].offsetHeight;
let mapHeight = areaMap[0].offsetHeight;
let localOverlay = MapOverlayUtil.getMapOverlay(table, 'local');
let paginationElement = localOverlay.find('.dataTables_paginate');
@@ -401,7 +399,6 @@ define([
table.on('draw.dt', function(e, settings){
// init table tooltips
$(this).find('td').initTooltips({
container: 'body',
placement: 'left'
});
});
@@ -410,7 +407,6 @@ define([
table.on('init.dt', function(){
// init table head tooltips
$(this).initTooltips({
container: 'body',
placement: 'top'
});
});

View File

@@ -27,9 +27,6 @@ define([
zIndexCounter: 110,
maxActiveConnections: 8,
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapClass: 'pf-map', // class for all maps
mapIdPrefix: 'pf-map-', // id prefix for all maps
systemClass: 'pf-system', // class for all systems
systemActiveClass: 'pf-system-active', // class for an active system on a map
@@ -45,7 +42,6 @@ define([
systemBodyItemStatusClass: 'pf-user-status', // class for player status in system body
systemBodyItemNameClass: 'pf-system-body-item-name', // class for player name in system body
systemBodyRightClass: 'pf-system-body-right', // class for player ship name in system body
dynamicElementWrapperId: 'pf-dialog-wrapper', // wrapper div for dynamic content (dialogs, context-menus,...)
// endpoint classes
endpointSourceClass: 'pf-map-endpoint-source',
@@ -578,7 +574,7 @@ define([
* @param system
*/
let systemActions = (action, system) => {
let mapContainer = system.closest('.' + config.mapClass);
let mapContainer = system.closest('.' + Util.config.mapClass);
let map = MapUtil.getMapInstance(system.attr('data-mapid'));
let systemData = {};
@@ -685,8 +681,7 @@ define([
let filterScope = action.split('_')[1];
let filterScopeLabel = MapUtil.getScopeInfoForConnection( filterScope, 'label');
let promiseStore = MapUtil.getLocaleData('map', mapId);
promiseStore.then(data => {
Util.getLocalStore('map').getItem(mapId).then(data => {
let filterScopes = [];
if(data && data.filterScopes){
filterScopes = data.filterScopes;
@@ -704,7 +699,7 @@ define([
}
// store filterScopes in IndexDB
MapUtil.storeLocalData('map', mapId, 'filterScopes', filterScopes);
Util.getLocalStore('map').setItem(`${mapId}.filterScopes`, filterScopes);
MapUtil.filterMapByScopes(map, filterScopes);
Util.showNotify({title: 'Scope filter changed', text: filterScopeLabel, type: 'success'});
@@ -1085,17 +1080,17 @@ define([
};
/**
* set map wrapper observer
* @param mapWrapper
* set map area observer
* @param areaMap
* @param mapConfig
*/
let setMapWrapperObserver = (mapWrapper, mapConfig) => {
let setMapAreaObserver = (areaMap, mapConfig) => {
/**
* save current map dimension to local storage
* @param entry
*/
let saveMapSize = (entry) => {
let saveMapSize = entry => {
let width = '';
let height = '';
if(entry.constructor.name === 'HTMLDivElement'){
@@ -1109,10 +1104,9 @@ define([
width = parseInt(width.substring(0, width.length - 2)) || 0;
height = parseInt(height.substring(0, height.length - 2)) || 0;
mapWrapper.trigger('pf:mapResize');
areaMap.trigger('pf:mapResize');
let promiseStore = MapUtil.getLocaleData('map', mapConfig.config.id );
promiseStore.then((data) => {
Util.getLocalStore('map').getItem(mapConfig.config.id).then((data) => {
let storeData = true;
if(
@@ -1125,7 +1119,7 @@ define([
}
if(storeData){
MapUtil.storeLocalData('map', mapConfig.config.id, 'style', {
Util.getLocalStore('map').setItem(`${mapConfig.config.id}.style`, {
width: width,
height: height
});
@@ -1148,7 +1142,7 @@ define([
}
});
wrapperResize.observe(mapWrapper[0]);
wrapperResize.observe(areaMap[0]);
}else if(requestAnimationFrame){
// ResizeObserver() not supported
let checkMapSize = (entry) => {
@@ -1156,17 +1150,18 @@ define([
return setTimeout(checkMapSize, 500, entry);
};
checkMapSize(mapWrapper[0]);
checkMapSize(areaMap[0]);
}
};
/**
* get a mapMapElement
* @param parentElement
* @param areaMap
* @param mapConfig
* @returns {Promise<any>}
*/
let newMapElement = (parentElement, mapConfig) => {
let newMapElement = (areaMap, mapConfig) => {
areaMap = $(areaMap);
/**
* new map element promise
@@ -1175,45 +1170,37 @@ define([
*/
let newMapElementExecutor = (resolve, reject) => {
// get map dimension from local storage
let promiseStore = MapUtil.getLocaleData('map', mapConfig.config.id );
promiseStore.then((data) => {
Util.getLocalStore('map').getItem(mapConfig.config.id).then(data => {
let height = 0;
if(data && data.style){
height = data.style.height;
}
// create map wrapper
let mapWrapper = $('<div>', {
class: config.mapWrapperClass,
height: height
});
areaMap.css('height', height);
setMapWrapperObserver(mapWrapper, mapConfig);
setMapAreaObserver(areaMap, mapConfig);
let mapId = mapConfig.config.id;
// create new map container
let mapContainer = $('<div>', {
id: config.mapIdPrefix + mapId,
class: config.mapClass
class: Util.config.mapClass
}).data('id', mapId);
mapWrapper.append(mapContainer);
// append mapWrapper to parent element (at the top)
parentElement.prepend(mapWrapper);
areaMap.append(mapContainer);
// set main Container for current map -> the container exists now in DOM !! very important
mapConfig.map.setContainer(mapContainer);
// init custom scrollbars and add overlay
initMapScrollbar(mapWrapper);
initMapScrollbar(areaMap);
// set map observer
setMapObserver(mapConfig.map);
// set shortcuts
mapWrapper.setMapShortcuts();
areaMap.setMapShortcuts();
// show static overlay actions
let mapOverlay = MapOverlayUtil.getMapOverlay(mapContainer, 'info');
@@ -1417,8 +1404,7 @@ define([
*/
let filterMapByScopes = payload => {
let filterMapByScopesExecutor = resolve => {
let promiseStore = MapUtil.getLocaleData('map', payload.data.mapConfig.config.id);
promiseStore.then(dataStore => {
Util.getLocalStore('map').getItem(payload.data.mapConfig.config.id).then(dataStore => {
let scopes = [];
if(dataStore && dataStore.filterScopes){
scopes = dataStore.filterScopes;
@@ -1439,8 +1425,7 @@ define([
*/
let showInfoSignatureOverlays = payload => {
let showInfoSignatureOverlaysExecutor = resolve => {
let promiseStore = MapUtil.getLocaleData('map', payload.data.mapConfig.config.id);
promiseStore.then(dataStore => {
Util.getLocalStore('map').getItem(payload.data.mapConfig.config.id).then(dataStore => {
if(dataStore && dataStore.mapSignatureOverlays){
MapOverlay.showInfoSignatureOverlays($(payload.data.mapConfig.map.getContainer()));
}
@@ -1625,7 +1610,6 @@ define([
title: 'System alias',
placement: 'top',
onblur: 'submit',
container: 'body',
toggle: 'manual', // is triggered manually on dblClick
showbuttons: false
});
@@ -1751,7 +1735,7 @@ define([
options.id = MapContextMenu.config.systemContextMenuId;
options.selectCallback = systemActions;
let mapContainer = system.closest('.' + config.mapClass);
let mapContainer = system.closest('.' + Util.config.mapClass);
// hidden menu actions
if(system.data('locked') === true){
@@ -1795,8 +1779,7 @@ define([
let mapContainer = $(map.getContainer());
// active menu actions
let promiseStore = MapUtil.getLocaleData('map', mapContainer.data('id'));
promiseStore.then(dataStore => {
Util.getLocalStore('map').getItem(mapContainer.data('id')).then(dataStore => {
if(dataStore && dataStore.filterScopes){
options.active = dataStore.filterScopes.map(scope => 'filter_' + scope);
}
@@ -2005,10 +1988,9 @@ define([
let systemTooltipOptions = {
toggle: 'tooltip',
placement: 'right',
container: 'body',
viewport: system.id
};
system.find('.fas').tooltip(systemTooltipOptions);
//system.find('.fas').tooltip(systemTooltipOptions);
// system click events ========================================================================================
let double = function(e){
@@ -2316,9 +2298,9 @@ define([
// store new zoom level in IndexDB
if(zoom === 1){
MapUtil.deleteLocalData('map', mapId, 'mapZoom');
Util.getLocalStore('map').removeItem(`${mapId}.mapZoom`);
}else{
MapUtil.storeLocalData('map', mapId, 'mapZoom', zoom);
Util.getLocalStore('map').setItem(`${mapId}.mapZoom`, zoom);
}
});
@@ -2406,7 +2388,7 @@ define([
e.stopPropagation();
// make sure map is clicked and NOT a connection
if($(e.target).hasClass(config.mapClass)){
if($(e.target).hasClass(Util.config.mapClass)){
getContextMenuConfig(map).then(payload => {
let context = {
component: map
@@ -2532,6 +2514,20 @@ define([
selector: '.' + config.systemClass + ' .' + config.systemHeadExpandClass
});
mapContainer.hoverIntent({
over: function(e){
$(this).tooltip({
trigger: 'manual',
placement: 'right',
viewport: this.closest(`.${config.systemClass}`)
}).tooltip('show');
},
out: function(e){
$(this).tooltip('destroy');
},
selector: `.${config.systemClass} .fas[title]`
});
// system "active users" popover ------------------------------------------------------------------------------
mapContainer.hoverIntent({
over: function(e){
@@ -2611,8 +2607,7 @@ define([
// get map menu config options
let mapOption = mapOptions[data.option];
let promiseStore = MapUtil.getLocaleData('map', mapContainer.data('id'));
promiseStore.then(function(dataStore){
Util.getLocalStore('map').getItem(mapContainer.data('id')).then(function(dataStore){
let notificationText = 'disabled';
let button = $('#' + this.mapOption.buttonId);
let dataExists = false;
@@ -2643,7 +2638,7 @@ define([
MapOverlayUtil.getMapOverlay(this.mapContainer, 'info').updateOverlayIcon(this.data.option, 'hide');
// delete map option
MapUtil.deleteLocalData('map', this.mapContainer.data('id'), this.data.option);
Util.getLocalStore('map').removeItem(`${this.mapContainer.data('id')}.${this.data.option}`);
}else{
// toggle button class
button.addClass('active');
@@ -2662,7 +2657,7 @@ define([
MapOverlayUtil.getMapOverlay(this.mapContainer, 'info').updateOverlayIcon(this.data.option, 'show');
// store map option
MapUtil.storeLocalData('map', this.mapContainer.data('id'), this.data.option, 1);
Util.getLocalStore('map').setItem(`${this.mapContainer.data('id')}.${this.data.option}`, 1);
notificationText = 'enabled';
}
@@ -2701,8 +2696,8 @@ define([
}
if(select){
let mapWrapper = mapContainer.closest('.' + config.mapWrapperClass);
Scrollbar.scrollToCenter(mapWrapper, system);
let areaMap = mapContainer.closest('.' + Util.getMapTabContentAreaClass('map'));
Scrollbar.scrollToCenter(areaMap, system);
// select system
MapUtil.showSystemInfo(map, system);
}
@@ -3135,12 +3130,12 @@ define([
/**
* load OR updates system map
* @param tabContentElement parent element where the map will be loaded
* @param areaMap parent element where the map will be loaded
* @param mapConfig
* @param options
* @returns {Promise<any>}
*/
let loadMap = (tabContentElement, mapConfig, options) => {
let loadMap = (areaMap, mapConfig, options) => {
/**
* load map promise
@@ -3155,7 +3150,7 @@ define([
if(mapConfig.map.getContainer() === undefined){
// map not loaded -> create & update
newMapElement(tabContentElement, mapConfig)
newMapElement(areaMap, mapConfig)
.then(payload => updateMap(payload.data.mapConfig))
.then(payload => resolve(payload));
}else{
@@ -3172,14 +3167,14 @@ define([
/**
* init scrollbar for Map element
* @param mapWrapper
* @param areaMap
*/
let initMapScrollbar = mapWrapper => {
let mapElement = mapWrapper.find('.' + config.mapClass);
let initMapScrollbar = areaMap => {
let mapElement = areaMap.find('.' + Util.config.mapClass);
let mapId = mapElement.data('id');
let dragSelect;
Scrollbar.initScrollbar(mapWrapper, {
Scrollbar.initScrollbar(areaMap, {
callbacks: {
onInit: function(){
let scrollWrapper = this;
@@ -3215,7 +3210,7 @@ define([
let animationFrameId = 0;
let toggleDragScroll = active => {
mapElement.toggleClass('disabled', active).toggleClass(' pf-map-move', active);
mapElement.toggleClass('disabled', active).toggleClass('pf-map-move', active);
};
let stopDragScroll = () => {
@@ -3303,7 +3298,7 @@ define([
mapElement.attr('data-scroll-top', this.mcs.top);
// store new map scrollOffset -> localDB
MapUtil.storeLocalData('map', mapId, 'scrollOffset', {
Util.getLocalStore('map').setItem(`${mapId}.scrollOffset`, {
x: Math.abs(this.mcs.left),
y: Math.abs(this.mcs.top)
});
@@ -3326,8 +3321,8 @@ define([
// ------------------------------------------------------------------------------------------------------------
// add map overlays after scrollbar is initialized
// because of its absolute position
mapWrapper.initMapOverlays();
mapWrapper.initLocalOverlay(mapId);
areaMap.initMapOverlays();
areaMap.initLocalOverlay(mapId);
};
return {

View File

@@ -17,7 +17,7 @@ define([
* @returns {*}
*/
let getMapObjectFromOverlayIcon = overlayIcon => {
return MapUtil.getMapInstance(Util.getMapElementFromOverlay(overlayIcon).data('id'));
return MapUtil.getMapInstance(MapOverlayUtil.getMapElementFromOverlay(overlayIcon).data('id'));
};
/**
@@ -323,7 +323,7 @@ define([
iconClass: ['fas', 'fa-fw', 'fa-filter'],
onClick: function(e){
// clear all filter
let mapElement = Util.getMapElementFromOverlay(this);
let mapElement = MapOverlayUtil.getMapElementFromOverlay(this);
let map = getMapObjectFromOverlayIcon(this);
MapUtil.storeLocalData('map', mapElement.data('id'), 'filterScopes', []);
@@ -349,7 +349,7 @@ define([
iconClass: ['fas', 'fa-fw', 'fa-tags'],
hoverIntent: {
over: function(e){
let mapElement = Util.getMapElementFromOverlay(this);
let mapElement = MapOverlayUtil.getMapElementFromOverlay(this);
mapElement.find('.' + MapOverlayUtil.config.systemHeadClass).each(function(){
let systemHead = $(this);
// init popover if not already exists
@@ -373,7 +373,7 @@ define([
});
},
out: function(e){
let mapElement = Util.getMapElementFromOverlay(this);
let mapElement = MapOverlayUtil.getMapElementFromOverlay(this);
mapElement.find('.' + MapOverlayUtil.config.systemHeadClass).popover('hide');
}
}
@@ -556,7 +556,7 @@ define([
duration: Init.animationSpeed.mapOverlay,
complete: function(){
counterChart.data('interval', false);
Util.getMapElementFromOverlay(mapOverlayTimer).trigger('pf:unlocked');
MapOverlayUtil.getMapElementFromOverlay(mapOverlayTimer).trigger('pf:unlocked');
}
});
}

View File

@@ -13,8 +13,6 @@ define([
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
@@ -53,27 +51,36 @@ define([
* @returns {null}
*/
let getMapOverlay = (element, overlayType) => {
let mapWrapperElement = $(element).parents('.' + config.mapWrapperClass);
let areaMap = $(element).closest('.' + Util.getMapTabContentAreaClass('map'));
let mapOverlay = null;
switch(overlayType){
case 'timer':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayTimerClass);
mapOverlay = areaMap.find('.' + config.mapOverlayTimerClass);
break;
case 'info':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayInfoClass);
mapOverlay = areaMap.find('.' + config.mapOverlayInfoClass);
break;
case 'zoom':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayZoomClass);
mapOverlay = areaMap.find('.' + config.mapOverlayZoomClass);
break;
case 'local':
mapOverlay = mapWrapperElement.find('.' + config.overlayLocalClass);
mapOverlay = areaMap.find('.' + config.overlayLocalClass);
break;
}
return mapOverlay;
};
/**
* get mapElement from overlay or any child of that
* @param mapOverlay
* @returns {jQuery}
*/
let getMapElementFromOverlay = mapOverlay => {
return $(mapOverlay).closest('.' + Util.getMapTabContentAreaClass('map')).find('.' + Util.config.mapClass);
};
/**
* get the map counter chart from overlay
* @param element
@@ -91,6 +98,7 @@ define([
return {
config: config,
getMapOverlay: getMapOverlay,
getMapElementFromOverlay: getMapElementFromOverlay,
getMapCounter: getMapCounter,
getMapOverlayInterval: getMapOverlayInterval
};

View File

@@ -226,25 +226,25 @@ define([
/**
* scroll to a specific position on map
* demo: http://manos.malihu.gr/repository/custom-scrollbar/demo/examples/scrollTo_demo.html
* @param scrollWrapper
* @param scrollArea
* @param position
* @param options
*/
let scrollToPosition = (scrollWrapper, position, options) => {
$(scrollWrapper).mCustomScrollbar('scrollTo', position, options);
let scrollToPosition = (scrollArea, position, options) => {
$(scrollArea).mCustomScrollbar('scrollTo', position, options);
};
/**
* scroll to center an element
* -> subtract some offset for tooltips/connections
* @param scrollWrapper
* @param scrollArea
* @param element
*/
let scrollToCenter = (scrollWrapper, element) => {
let scrollToCenter = (scrollArea, element) => {
// no scroll if element is already FULL visible in scrollable viewport
if(!isInView(element)){
// get scrollTo position for centered element
scrollToPosition(scrollWrapper, getCenterScrollPosition(element));
scrollToPosition(scrollArea, getCenterScrollPosition(element));
}
};

View File

@@ -19,8 +19,6 @@ define([
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
@@ -618,7 +616,7 @@ define([
html: true,
animation: true,
template: template,
viewport: system.closest('.' + config.mapClass)
viewport: system.closest('.' + Util.config.mapClass)
};
// init new tooltip -> Do not show automatic maybe system is currently dragged

View File

@@ -18,14 +18,6 @@ define([
zoomMax: 1.5,
zoomMin: 0.5,
// local storage
characterLocalStoragePrefix: 'character_', // prefix for character data local storage key
mapLocalStoragePrefix: 'map_', // prefix for map data local storage key
mapTabContentClass: 'pf-map-tab-content', // Tab-Content element (parent element)
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapClass: 'pf-map', // class for all maps
mapGridClass: 'pf-grid-small', // class for map grid snapping
mapCompactClass: 'pf-compact', // class for map compact system UI
@@ -500,7 +492,7 @@ define([
connectionData &&
connectionData.signatures // signature data is required...
){
let SystemSignatures = require('module/system_signature');
let SystemSignatureModule = require('module/system_signature');
let sourceEndpoint = connection.endpoints[0];
let targetEndpoint = connection.endpoints[1];
@@ -531,7 +523,7 @@ define([
// ... get endpoint label for source || target system
if(tmpSystem && tmpSystem){
// ... get all available signature type (wormholes) names
let availableSigTypeNames = SystemSignatures.getSignatureTypeOptionsBySystem(tmpSystem, 5);
let availableSigTypeNames = SystemSignatureModule.getSignatureTypeOptionsBySystem(tmpSystem, 5);
let flattenSigTypeNames = Util.flattenXEditableSelectArray(availableSigTypeNames);
if(flattenSigTypeNames.hasOwnProperty(signatureData.typeId)){
@@ -609,7 +601,7 @@ define([
* @param element
* @returns {*}
*/
let getTabContentElementByMapElement = element => $(element).closest('.' + config.mapTabContentClass);
let getTabContentElementByMapElement = element => $(element).closest('.' + Util.config.mapTabContentClass);
/**
* checks if there is an "active" connection on a map
@@ -753,11 +745,11 @@ define([
'height': scrollableHeight ? scaledHeight + 'px' : (wrapperHeight) + 'px',
});
let mapWrapperElement = mapContainer.closest('.mCustomScrollbar');
let areaMap = mapContainer.closest('.mCustomScrollbar');
if(scrollableWidth && scrollableHeight){
mapWrapperElement.mCustomScrollbar('update');
areaMap.mCustomScrollbar('update');
}else{
mapWrapperElement.mCustomScrollbar('scrollTo', '#' + mapContainer.attr('id'), {
areaMap.mCustomScrollbar('scrollTo', '#' + mapContainer.attr('id'), {
scrollInertia: 0,
scrollEasing: 'linear',
timeout: 0,
@@ -1001,7 +993,12 @@ define([
setSystemActive(map, system);
// get parent Tab Content and fire update event
getTabContentElementByMapElement(system).trigger('pf:drawSystemModules');
let mapContainer = $(map.getContainer());
getTabContentElementByMapElement(mapContainer).trigger('pf:renderSystemModules', {
mapId: parseInt(mapContainer.data('id')),
payload: Util.getCurrentSystemData()
});
};
/**
@@ -1015,9 +1012,9 @@ define([
// get parent Tab Content and fire update event
let mapContainer = $(map.getContainer());
getTabContentElementByMapElement(mapContainer).trigger('pf:drawConnectionModules', {
connections: connections,
mapId: parseInt(mapContainer.data('id'))
getTabContentElementByMapElement(mapContainer).trigger('pf:renderConnectionModules', {
mapId: parseInt(mapContainer.data('id')),
payload: connections
});
};
@@ -1046,9 +1043,11 @@ define([
let showFindRouteDialog = (mapContainer, systemToData) => {
// get parent Tab Content and fire update event
getTabContentElementByMapElement(mapContainer).trigger('pf:updateRouteModules', {
task: 'showFindRouteDialog',
systemToData: systemToData,
mapId: parseInt(mapContainer.data('id'))
mapId: parseInt(mapContainer.data('id')),
payload: {
task: 'showFindRouteDialog',
systemToData: systemToData
}
});
};
@@ -1059,9 +1058,11 @@ define([
*/
let findRoute = (mapContainer, systemToData) => {
getTabContentElementByMapElement(mapContainer).trigger('pf:updateRouteModules', {
task: 'findRoute',
systemToData: systemToData,
mapId: parseInt(mapContainer.data('id'))
mapId: parseInt(mapContainer.data('id')),
payload: {
task: 'findRoute',
systemToData: systemToData
}
});
};
@@ -1267,84 +1268,6 @@ define([
return scopeInfo;
};
/**
* store local data for current user (IndexDB)
* @param key
* @param value
*/
let storeLocaleCharacterData = (key, value) => {
if(key.length && value){
let userData = Util.getCurrentUserData();
if(
userData &&
userData.character
){
storeLocalData('character', userData.character.id, key, value);
}
}
};
/**
* get key prefix for local storage data
* @param type
* @returns {boolean}
*/
let getLocalStoragePrefixByType = (type) => {
let prefix = false;
switch(type){
case 'character': prefix = config.characterLocalStoragePrefix; break;
case 'map': prefix = config.mapLocalStoragePrefix; break;
default: prefix = config.mapLocalStoragePrefix;
}
return prefix;
};
/**
* get stored local data from client cache (IndexedDB)
* @param type
* @param objectId
* @returns {*}
*/
let getLocaleData = (type, objectId) => {
if(objectId > 0){
let storageKey = getLocalStoragePrefixByType(type) + objectId;
return Util.getLocalStorage().getItem(storageKey);
}else{
console.warn('Local storage requires object id > 0');
}
};
/**
* store local config data to client cache (IndexedDB)
* @param type
* @param objectId
* @param key
* @param value
*/
let storeLocalData = (type, objectId, key, value) => {
if(objectId > 0){
// get current map config
let storageKey = getLocalStoragePrefixByType(type) + objectId;
Util.getLocalStorage().getItem(storageKey).then(function(data){
// This code runs once the value has been loaded
// from the offline store.
data = (data === null) ? {} : data;
// set/update value
data[this.key] = this.value;
Util.getLocalStorage().setItem(this.storageKey, data);
}.bind({
key: key,
value: value,
storageKey: storageKey
})).catch(function(err){
// This code runs if there were any errors
console.error('Map local storage can not be accessed!');
});
}else{
console.warn('storeLocalData(): Local storage requires object id > 0');
}
};
/**
* show map animations when a new map gets visual
* @param mapElement
@@ -1531,11 +1454,10 @@ define([
// -> implementation would be difficult...
if(map.getZoom() === 1){
let mapElement = $(map.getContainer());
let promiseStore = getLocaleData('map', mapElement.data('id'));
promiseStore.then(data => {
Util.getLocalStore('map').getItem(mapElement.data('id')).then(data => {
if(data && data.scrollOffset){
let mapWrapper = mapElement.parents('.' + config.mapWrapperClass);
Scrollbar.scrollToPosition(mapWrapper, [data.scrollOffset.y, data.scrollOffset.x]);
let areaMap = mapElement.closest('.' + Util.getMapTabContentAreaClass('map'));
Scrollbar.scrollToPosition(areaMap, [data.scrollOffset.y, data.scrollOffset.x]);
}
resolve(payload);
@@ -1557,8 +1479,7 @@ define([
let zoomToDefaultScaleExecutor = resolve => {
let mapElement = $(map.getContainer());
let promiseStore = getLocaleData('map', mapElement.data('id'));
promiseStore.then(data => {
Util.getLocalStore('map').getItem(mapElement.data('id')).then(data => {
if(data && data.mapZoom){
setZoom(map, data.mapZoom);
}
@@ -1573,32 +1494,6 @@ define([
return new Promise(zoomToDefaultScaleExecutor);
};
/**
* delete local map configuration by key (IndexedDB)
* @param type
* @param objectId
* @param key
*/
let deleteLocalData = (type, objectId, key) => {
if(objectId > 0){
// get current map config
let storageKey = getLocalStoragePrefixByType(type) + objectId;
Util.getLocalStorage().getItem(storageKey).then(function(data){
if(
data &&
data.hasOwnProperty(key)
){
delete data[key];
Util.getLocalStorage().setItem(this.storageKey, data);
}
}.bind({
storageKey: storageKey
}));
}else{
console.warn('deleteLocalData(): Local storage requires object id > 0');
}
};
/**
* set or change rallyPoint for systems
* @param rallyUpdated
@@ -1650,8 +1545,7 @@ define([
// check if desktop notification was already send
let mapId = system.data('mapid');
let systemId = system.data('id');
let promiseStore = getLocaleData('map', mapId);
promiseStore.then(function(data){
Util.getLocalStore('map').getItem(mapId).then(function(data){
// This code runs once the value has been loaded
// from the offline store.
let rallyPokeData = {};
@@ -1669,7 +1563,7 @@ define([
rallyPokeData[this.systemId] !== rallyUpdated // already send to that system but in the past
){
rallyPokeData[this.systemId] = rallyUpdated;
storeLocalData('map', this.mapId, 'rallyPoke', rallyPokeData);
Util.getLocalStore('map').setItem(`${this.mapId}.rallyPoke`, rallyPokeData);
notificationOptions.type = 'info';
Util.showNotify(notificationOptions, {
@@ -1686,7 +1580,7 @@ define([
}
// update active "route" module -> add rally point row --------------------------------------------
let mapContainer = system.parents('.' + config.mapClass);
let mapContainer = system.parents('.' + Util.config.mapClass);
findRoute(mapContainer, {
systemId: system.data('systemId'),
name: system.data('name'),
@@ -1711,28 +1605,27 @@ define([
* set map "shortcut" events
*/
$.fn.setMapShortcuts = function(){
return this.each((i, mapWrapper) => {
mapWrapper = $(mapWrapper);
let mapElement = mapWrapper.find('.' + config.mapClass);
return this.each((i, areaMap) => {
areaMap = $(areaMap);
let mapElement = areaMap.find('.' + Util.config.mapClass);
// dynamic require Map module -> otherwise there is a require(), loop
let Map = require('app/map/map');
let System = require('app/map/system');
let map = Map.getMapInstance( mapElement.data('id'));
let map = Map.getMapInstance(mapElement.data('id'));
mapWrapper.watchKey('mapSystemAdd', (mapWrapper) => {
areaMap.watchKey('mapSystemAdd', areaMap => {
System.showNewSystemDialog(map, {position: {x: 0, y: 0}}, Map.saveSystemCallback);
},{focus: true});
mapWrapper.watchKey('mapSystemsSelect', (mapWrapper) => {
areaMap.watchKey('mapSystemsSelect', areaMap => {
mapElement.selectAllSystems();
},{focus: true});
mapWrapper.watchKey('mapSystemsDelete', (mapWrapper) => {
areaMap.watchKey('mapSystemsDelete', areaMap => {
let selectedSystems = mapElement.getSelectedSystems();
$.fn.showDeleteSystemDialog(map, selectedSystems);
},{focus: true});
});
};
@@ -2309,10 +2202,6 @@ define([
changeZoom: changeZoom,
setZoom: setZoom,
toggleSystemAliasEditable: toggleSystemAliasEditable,
storeLocaleCharacterData: storeLocaleCharacterData,
getLocaleData: getLocaleData,
storeLocalData: storeLocalData,
deleteLocalData: deleteLocalData,
visualizeMap: visualizeMap,
setMapDefaultOptions: setMapDefaultOptions,
getSystemPosition: getSystemPosition,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,198 +0,0 @@
define([
'jquery',
'app/init',
'pnotify',
//'pnotify.buttons',
//'pnotify.confirm',
'pnotify.nonblock',
'pnotify.desktop',
//'pnotify.history',
'pnotify.callbacks'
], ($, 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 = (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 = 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 = () => {
if(blinkTimer){
clearInterval(blinkTimer);
document.title = initialPageTitle;
blinkTimer = null;
}
};
return {
showNotify: showNotify,
startTabBlink: startTabBlink,
stopTabBlink: stopTabBlink
};
});

View File

@@ -60,9 +60,6 @@ define([
// menu
menuHeadMenuLogoClass: 'pf-head-menu-logo', // class for main menu logo
// helper element
dynamicElementWrapperId: 'pf-dialog-wrapper', // class for container element that holds hidden "context menus"
// system signature module
systemSignatureModuleClass: 'pf-system-signature-module', // module wrapper (signatures)
systemIntelModuleClass: 'pf-system-intel-module', // module wrapper (intel)
@@ -110,7 +107,7 @@ define([
* @param element
*/
let initHeaderTooltips = element => {
element.find('[title]').tooltip({
element.initTooltips({
placement: 'bottom',
delay: {
show: 500,
@@ -120,54 +117,62 @@ define([
};
/**
* load main page structure elements and navigation container into body
* @returns {*|w.fn.init|jQuery|HTMLElement}
* render page content + left/right menu
* @param rootEl
* @returns {Promise<{pageEl: HTMLDivElement, pageMenuRightEl: HTMLDivElement, pageMenuLeftEl: HTMLDivElement}>}
*/
let loadPageStructure = () => {
let renderPage = rootEl => {
let pageEl = Object.assign(document.createElement('div'), {
className: config.pageClass
});
pageEl.setAttribute('canvas', 'container');
let executor = resolve => {
let body = $('body');
let contextMenuContainerEl = Object.assign(document.createElement('div'), {
id: MapContextMenu.config.contextMenuContainerId
});
pageEl.append(Util.getMapModule()[0], contextMenuContainerEl);
let pageElement = $('<div>', {
class: config.pageClass
}).attr('canvas', 'container').append(
Util.getMapModule(),
$('<div>', {
id: config.dynamicElementWrapperId
})
);
let pageMenuLeftEl = Object.assign(document.createElement('div'), {
className: [config.pageMenuClass, config.pageMenuLeftClass].join(' ')
});
pageMenuLeftEl.setAttribute('off-canvas', [config.pageMenuLeftClass, 'left', 'push'].join(' '));
let pageMenuLeftElement = $('<div>', {
class: [config.pageMenuClass, config.pageMenuLeftClass].join(' ')
}).attr('off-canvas', [config.pageMenuLeftClass, 'left', 'push'].join(' '));
let pageMenuRightEl = Object.assign(document.createElement('div'), {
className: [config.pageMenuClass, config.pageMenuRightClass].join(' ')
});
pageMenuRightEl.setAttribute('off-canvas', [config.pageMenuRightClass, 'right', 'push'].join(' '));
let pageMenuRightElement = $('<div>', {
class: [config.pageMenuClass, config.pageMenuRightClass].join(' ')
}).attr('off-canvas', [config.pageMenuRightClass, 'right', 'push'].join(' '));
rootEl.prepend(pageEl, pageMenuLeftEl, pageMenuRightEl);
body.prepend(pageElement, pageMenuLeftElement, pageMenuRightElement);
Promise.all([
loadHeader(pageElement),
loadFooter(pageElement),
loadLeftMenu(pageMenuLeftElement),
loadRightMenu(pageMenuRightElement),
loadSVGs()
]).then(payload => Promise.all([
setMenuObserver(payload[2].data),
setMenuObserver(payload[3].data),
setDocumentObserver(),
setBodyObserver(),
setGlobalShortcuts()
])).then(() => resolve({
action: 'loadPageStructure',
data: {}
}));
};
return new Promise(executor);
return Promise.resolve({pageEl, pageMenuLeftEl, pageMenuRightEl});
};
/**
* load main page structure elements and navigation container into body
* @param pageEl
* @param pageMenuLeftEl
* @param pageMenuRightEl
* @returns {Promise}
*/
let loadPageStructure = ({pageEl, pageMenuLeftEl, pageMenuRightEl}) => new Promise(resolve => {
Promise.all([
loadHeader(pageEl),
loadFooter(pageEl),
loadLeftMenu(pageMenuLeftEl),
loadRightMenu(pageMenuRightEl),
loadSVGs()
]).then(payload => Promise.all([
setMenuObserver(payload[2].data),
setMenuObserver(payload[3].data),
setDocumentObserver(),
setBodyObserver(),
setGlobalShortcuts()
])).then(() => resolve({
action: 'loadPageStructure',
data: pageEl
}));
});
/**
* build main menu from mapConfig array
* @param menuConfig
@@ -230,13 +235,13 @@ define([
/**
* load left menu content options
* @param pageMenuLeftElement
* @param pageMenuLeftEl
* @returns {Promise<any>}
*/
let loadLeftMenu = pageMenuLeftElement => {
let loadLeftMenu = pageMenuLeftEl => {
let executor = resolve => {
pageMenuLeftElement.append(getMenu([
$(pageMenuLeftEl).append(getMenu([
{
type: 'button',
label: 'Home',
@@ -303,7 +308,7 @@ define([
resolve({
action: 'loadLeftMenu',
data: pageMenuLeftElement
data: pageMenuLeftEl
});
};
@@ -312,13 +317,13 @@ define([
/**
* load right menu content options
* @param pageMenuRightElement
* @param pageMenuRightEl
* @returns {Promise<any>}
*/
let loadRightMenu = pageMenuRightElement => {
let loadRightMenu = pageMenuRightEl => {
let executor = resolve => {
pageMenuRightElement.append(getMenu([
$(pageMenuRightEl).append(getMenu([
{
type: 'button',
label: 'Information',
@@ -407,7 +412,7 @@ define([
resolve({
action: 'loadRightMenu',
data: pageMenuRightElement
data: pageMenuRightEl
});
};
@@ -445,10 +450,10 @@ define([
/**
* load page header
* @param pageElement
* @param pageEl
* @returns {Promise<any>}
*/
let loadHeader = pageElement => {
let loadHeader = pageEl => {
let executor = resolve => {
let moduleData = {
@@ -462,7 +467,7 @@ define([
mapTrackingId: Util.config.headMapTrackingId
};
pageElement.prepend(Mustache.render(TplHead, moduleData));
pageEl.insertAdjacentHTML('afterbegin', Mustache.render(TplHead, moduleData));
// init header --------------------------------------------------------------------------------------------
@@ -571,7 +576,7 @@ define([
resolve({
action: 'loadHeader',
data: {
pageElement: pageElement
pageEl: pageEl
}
});
};
@@ -581,10 +586,10 @@ define([
/**
* load page footer
* @param pageElement
* @param pageEl
* @returns {Promise<any>}
*/
let loadFooter = pageElement => {
let loadFooter = pageEl => {
let executor = resolve => {
let moduleData = {
@@ -594,11 +599,11 @@ define([
currentYear: new Date().getFullYear()
};
pageElement.prepend(Mustache.render(TplFooter, moduleData));
pageEl.insertAdjacentHTML('afterbegin', Mustache.render(TplFooter, moduleData));
// init footer --------------------------------------------------------------------------------------------
pageElement.find('.' + config.footerLicenceLinkClass).on('click', e => {
$(pageEl.querySelector(`.${config.footerLicenceLinkClass}`)).on('click', e => {
e.stopPropagation();
//show credits info dialog
$.fn.showCreditsDialog();
@@ -609,7 +614,7 @@ define([
resolve({
action: 'loadFooter',
data: {
pageElement: pageElement
pageEl: pageEl
}
});
};
@@ -619,10 +624,10 @@ define([
/**
* set page menu observer
* @param menuElement
* @param menuEl
* @returns {Promise<any>}
*/
let setMenuObserver = menuElement => {
let setMenuObserver = menuEl => {
let executor = resolve => {
@@ -634,7 +639,7 @@ define([
}
};
menuElement.on('click', '.list-group-item[data-action]', e => {
$(menuEl).on('click', '.list-group-item[data-action]', e => {
e.preventDefault();
e.stopPropagation();
@@ -707,7 +712,10 @@ define([
title: 'Test Notification',
text: 'Accept browser security question'
},{
desktop: true,
desktop: {
title: 'Test OK',
text: 'Desktop notifications active'
},
stack: 'barBottom'
});
break;
@@ -1272,11 +1280,11 @@ define([
};
/**
* set event listener if the program tab is active or not
* this is used to lower the update ping cycle to reduce server load
* set event listener if the program tab is active or not
* this is used to lower the update ping cycle to reduce server load
* @returns {Promise}
*/
let initTabChangeObserver = () => {
let initTabChangeObserver = () => new Promise(resolve => {
// increase the timer if a user is inactive
let increaseTimer = 5000;
@@ -1335,21 +1343,24 @@ define([
document.addEventListener(visibilityChange, handleVisibilityChange, false);
}
};
resolve({
action: 'initTabChangeObserver',
data: false
});
});
/**
* add "hidden" context menu elements to page
* @returns {Promise[]}
*/
let renderMapContextMenus = () => {
Promise.all([
MapContextMenu.renderMapContextMenu(),
MapContextMenu.renderConnectionContextMenu(),
MapContextMenu.renderEndpointContextMenu(),
MapContextMenu.renderSystemContextMenu(Init.systemStatus)
]).then(payloads => {
$('#' + config.dynamicElementWrapperId).append(payloads.join(''));
});
};
let renderMapContextMenus = () => Promise.all([
MapContextMenu.renderMapContextMenu(),
MapContextMenu.renderConnectionContextMenu(),
MapContextMenu.renderEndpointContextMenu(),
MapContextMenu.renderSystemContextMenu(Init.systemStatus)
]).then(payloads => {
document.getElementById(MapContextMenu.config.contextMenuContainerId).innerHTML = payloads.join('');
});
/**
* trigger "program status" in head
@@ -1476,6 +1487,7 @@ define([
};
return {
renderPage: renderPage,
loadPageStructure: loadPageStructure,
initTabChangeObserver: initTabChangeObserver,
renderMapContextMenus: renderMapContextMenus

158
js/app/pnotify.loader.js Normal file
View File

@@ -0,0 +1,158 @@
define([
'PNotify',
'PNotifyDesktop',
'PNotifyCallbacks',
'NonBlock'
], (PNotify) => {
'use strict';
let stackConfig = {
bottomRight: {
stack: {
dir1: 'up',
dir2: 'left',
firstpos1: 32,
firstpos2: 10,
spacing1: 5,
spacing2: 5,
push: 'bottom',
context: document.body
}
},
barBottom: {
stack: {
dir1: 'up',
firstpos1: 32,
spacing1: 0,
context: document.querySelector(`.pf-site`)
},
addclass: 'stack-bar-bottom'
}
};
let initDefaultPNotifyConfig = () => {
PNotify.defaults.styling = 'bootstrap3';
PNotify.defaults.icons = 'fontawesome5';
PNotify.defaults.addClass = 'nonblock';
PNotify.defaults.delay = 5000;
PNotify.defaults.width = '250px';
PNotify.defaults.animateSpeed = 'fast';
PNotify.defaults.stack = stackConfig.bottomRight.stack;
PNotify.modules.Desktop.defaults.icon = '/public/img/notifications/logo.png';
};
/**
* show browser/desktop notification
* @param config
* @param options
*/
let showNotify = (config = {}, options = {}) => {
if(options.desktop){
config.modules = {
Desktop: Object.assign({}, {desktop: true}, options.desktop)
};
startTabBlink(config.title); // make browser tab blink
}
switch(config.type){
case 'info':
config.icon = 'fas fa-info fa-fw fa-lg';
break;
case 'success':
config.icon = 'fas fa-check fa-fw fa-lg';
break;
case 'notice':
case 'warning':
config.icon = 'fas fa-exclamation-triangle fa-fw fa-lg';
config.type = 'notice';
break;
case 'error':
config.icon = 'fas fa-times fa-fw fa-lg';
break;
case 'lock':
config.icon = 'fas fa-lock fa-fw fa-lg';
config.type = 'success';
break;
case 'unlock':
config.icon = 'fas fa-unlock fa-fw fa-lg';
config.type = 'info';
break;
default:
config.icon = false;
}
if(options.stack){
config.stack = stackConfig[options.stack].stack;
config.addClass = stackConfig[options.stack].addclass;
}
let notice = PNotify.alert(config);
if(typeof options.click === 'function'){
notice.refs.elem.style.cursor = 'pointer';
notice.on('click', options.click);
}
};
initDefaultPNotifyConfig();
// browser tab blink ==============================================================================================
// initial page title (cached)
let initialPageTitle = document.title;
// global blink timeout cache
let blinkTimer;
/**
* change document.title and make the browsers tab blink
* @param blinkTitle
*/
let startTabBlink = blinkTitle => {
let initBlink = (function(){
// count blinks if tab is currently active
let activeTabBlinkCount = 0;
let blink = (blinkTitle) => {
// 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 () => {
if(!blinkTimer){
blinkTimer = setInterval(blink, 1000, blinkTitle);
}
};
}(blinkTitle));
initBlink();
};
/**
* stop blinking document.title
*/
let stopTabBlink = () => {
if(blinkTimer){
clearInterval(blinkTimer);
document.title = initialPageTitle;
blinkTimer = null;
}
};
return {
showNotify: showNotify,
startTabBlink: startTabBlink,
stopTabBlink: stopTabBlink
};
});

View File

@@ -0,0 +1,82 @@
define([], () => {
'use strict';
/**
* sequential promise queue
* @see https://medium.com/@karenmarkosyan/how-to-manage-promises-into-dynamic-queue-with-vanilla-javascript-9d0d1f8d4df5
* @see https://codepen.io/exodus4d/pen/QWwgKay
*/
return class Queue {
constructor() {
this._queue = [];
this._pendingPromise = false;
this._stop = false;
}
/**
* wraps a promise that needs to be sequentially resolved
* -> dequeue() process starts immediately (if not already pending)
* @param promise
* @param {'end'|'start'} position
* @param data
* @returns {Promise}
*/
enqueue(promise, position = 'end', data = null) {
return new Promise((resolve, reject) => {
this._queue[position === 'end' ? 'push' : 'unshift']({
promise,
resolve,
reject,
data,
});
this.dequeue();
});
}
/**
* resolve promise queue recursive until queue is empty
* @returns {boolean}
*/
dequeue() {
if (this._pendingPromise) {
return false;
}
if (this._stop) {
this._queue = [];
this._stop = false;
return false;
}
let item = this._queue.shift();
if (!item) {
return false;
}
try {
this._pendingPromise = true;
item.promise()
.then((value) => {
this._pendingPromise = false;
item.resolve(value);
this.dequeue();
})
.catch(err => {
this._pendingPromise = false;
item.reject(err);
this.dequeue();
});
} catch (err) {
this._pendingPromise = false;
item.reject(err);
this.dequeue();
}
return true;
}
filterQueue(callback) {
return this._queue.filter(callback);
}
};
});

View File

@@ -24,8 +24,11 @@ define(['jquery', 'mustache'], ($, Mustache) => {
/**
* convert JSON object into HTML highlighted string
* @param obj
* @param options
*/
let highlightJson = (obj) => {
let highlightJson = (obj, options = {}) => {
let maxLinesFunctions = options.maxLinesFunctions || 5;
let multiplyString = (num, str) => {
let sb = [];
for(let i = 0; i < num; i++){
@@ -39,7 +42,10 @@ define(['jquery', 'mustache'], ($, Mustache) => {
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 expImageClicked = '(() => {this.classList.toggle(\'fa-minus-square\'); ' +
'this.classList.toggle(\'fa-plus-square\'); ' +
'let container=this.parentNode.nextSibling; ' +
'container.style.display=container.style.display===\'none\'?\'inline\':\'none\'})();';
let checkForArray = function(obj){
return obj &&
@@ -67,18 +73,27 @@ define(['jquery', 'mustache'], ($, Mustache) => {
let formatFunction = function(indent, obj){
let tabs = '';
for(let i = 0; i < indent; i++) tabs += tab;
let funcStrArray = obj.toString().split('\n');
let funcStrArray = obj.toString().split('\n', maxLinesFunctions);
let str = '';
for(let i = 0; i < funcStrArray.length; i++){
str += ((i === 0) ? '' : tabs) + funcStrArray[i] + '\n';
str += ((i === 0) ? '' : '') + funcStrArray[i] + '\n';
}
return str;
return str + tabs;
};
let highlight = (obj, indent, addComma, isArray, isPropertyContent) => {
// check if recursive call depth leads to collapsed data
let startCollapseIcon = options.collapseDepth <= indent ? 'fa-plus-square' : 'fa-minus-square';
let startCollapseStyle = options.collapseDepth <= indent ? 'none' : 'inline';
let html = '';
// check max recursion depth
if(indent > (options.maxDepth || 8)){
return html;
}
let comma = (addComma) ? '<span class="pf-code-Comma">,</span> ' : '';
let type = typeof obj;
let clpsHtml = '';
@@ -86,7 +101,8 @@ define(['jquery', 'mustache'], ($, Mustache) => {
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">' : '';
clpsHtml = isCollapsible ? '<span><i class="pf-module-icon-button fas fa-fw ' + startCollapseIcon + '" onClick="' + expImageClicked + '"></i></span>' +
'<span class="collapsible" style="display:'+ startCollapseStyle +'">' : '';
html += getRow(indent, '<span class="pf-code-ArrayBrace">[</span>' + clpsHtml, isPropertyContent);
for(let i = 0; i < obj.length; i++){
html += highlight(obj[i], indent + 1, i < (obj.length - 1), true, false);
@@ -98,7 +114,7 @@ define(['jquery', 'mustache'], ($, Mustache) => {
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');
html += formatLiteral('new Date(' + obj.getTime() + ') &lt;' + obj.toLocaleString('en-GB') + '&gt;', '', comma, indent, isArray, 'pf-code-Date');
}else if(obj.constructor === regexpObj.constructor){
html += formatLiteral('new RegExp(' + obj + ')', '', comma, indent, isArray, 'RegExp');
}else{
@@ -107,7 +123,8 @@ define(['jquery', 'mustache'], ($, Mustache) => {
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">' : '';
clpsHtml = isCollapsible ? '<span><i class="pf-module-icon-button fas fa-fw ' + startCollapseIcon + '" onClick="' + expImageClicked + '"></i></span>' +
'<span class="collapsible" style="display:'+ startCollapseStyle +'">' : '';
html += getRow(indent, '<span class="pf-code-ObjectBrace">{</span>' + clpsHtml, isPropertyContent);
let j = 0;
for(let prop in obj){

View File

@@ -1033,7 +1033,7 @@ define([
// get to last page (pageIndex starts at zero) -> check if last page > 0
context.tableApi.page(newPageIndex).draw(false);
}else{
Util.showNotify({title: 'No logs found', text: 'No more entries', type: 'danger'});
Util.showNotify({title: 'No logs found', text: 'No more entries', type: 'warning'});
}
};

View File

@@ -432,10 +432,9 @@ define([
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);
let tabLinkEls = Util.getMapTabLinkElements(Util.getMapModule()[0], responseData.mapData.mapData.id);
if(tabLinkEls.length === 1){
ModuleMap.updateTabData(tabLinkEls[0], responseData.mapData.mapData);
}
$(mapInfoDialog).modal('hide');
@@ -803,6 +802,10 @@ define([
},
callback: function(result){
if(result){
// lock dialog
let dialogContent = mapDeleteDialog.find('.modal-content');
dialogContent.showLoadingAnimation();
let data = {mapData: mapData.config};
$.ajax({

View File

@@ -75,10 +75,10 @@ define([
// 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>>',
let statsTable = table.DataTable({
dom: '<"flex-row flex-between"<"flex-col"l><"flex-col"B><"flex-col"fS>>' +
'<"flex-row"<"flex-col flex-grow"tr>>' +
'<"flex-row flex-between"<"flex-col"i><"flex-col"p>>',
buttons: {
name: 'tableTools',
buttons: [

284
js/app/ui/module/base.js Normal file
View File

@@ -0,0 +1,284 @@
define([
'jquery',
'app/init',
'app/util',
'app/promises/promise.deferred',
'app/promises/promise.queue'
], ($, Init, Util, DeferredPromise, PromiseQueue) => {
'use strict';
/**
* abstract BaseModel class
* -> custom/plugin modules must extend from it
* @type {BaseModule}
*/
let BaseModule = class BaseModule {
constructor(config= {}){
if(new.target === BaseModule){
throw new TypeError('Cannot construct ' + this.constructor.name + ' instances directly');
}
// check for abstract methods to be implemented in child
if(this.render === undefined){
throw new TypeError('Abstract method render() missing in ' + new.target.name + ' class');
}
this._config = Object.assign({}, BaseModule.defaultConfig, config);
this._updateQueue = new PromiseQueue();
}
/**
* get current module configuration
* @returns {*}
*/
get config(){
return this._config;
}
/**
* get root node for this module
* -> parent container for custom body HTML
* @returns {HTMLElement}
*/
get moduleElement(){
if(!this._moduleEl){
// init new moduleElement
this._moduleEl = Object.assign(document.createElement('div'), {
className: `${BaseModule.className} ${this._config.className}`,
style: {
opacity: '0'
}
}).setData('module', this);
this._moduleEl.dataset.position = this._config.position;
this._moduleEl.dataset.module = this.constructor.name;
// module header
this._moduleEl.append(this.newHeaderElement());
}
return this._moduleEl;
}
/**
* module header element
* -> dragHandler + headline
* @param text
* @returns {HTMLDivElement}
*/
newHeaderElement(text){
let headEl = this.newHeadElement();
headEl.append(
this.newHandlerElement(),
this.newHeadlineElement(text || this._config.headline)
);
return headEl;
}
/**
* module head element
* @returns {HTMLDivElement}
*/
newHeadElement(){
return Object.assign(document.createElement('div'), {
className: this._config.headClassName
});
}
/**
* module dragHandler element
* @returns {HTMLHeadingElement}
*/
newHandlerElement(){
return Object.assign(document.createElement('h5'), {
className: this._config.handlerClassName
});
}
/**
* module headline element
* @param text
* @returns {HTMLHeadingElement}
*/
newHeadlineElement(text){
return Object.assign(document.createElement('h5'), {
textContent: typeof text === 'string' ? text : ''
});
}
/**
* module toolbar element (wrapper)
* @returns {HTMLHeadingElement}
*/
newHeadlineToolbarElement(){
return Object.assign(document.createElement('h5'), {
className: 'pull-right'
});
}
/**
* icon element
* @param cls
* @returns {HTMLElement}
*/
newIconElement(cls = []){
return Object.assign(document.createElement('i'), {
className: ['fas', ...cls].join(' ')
});
}
/**
* HTTP request handler for internal (Pathfinder) ajax calls
* @param args
* @returns {Promise}
*/
request(...args){
return BaseModule.Util.request(...args);
}
/**
* scoped instance for LocalStore for current module
* @returns {LocalStore}
*/
getLocalStore(){
if(!this._localStore){
// make accessible -> scope Store keys!
this._localStore = BaseModule.Util.getLocalStore('module');
this._localStore.scope = this.constructor.name;
}
return this._localStore;
}
/**
* visual notification handler (UI popover)
* -> can be used for info/error on-screen messages
* @param args
*/
showNotify(...args){
return BaseModule.Util.showNotify(...args);
}
/**
* responsible for dispatching all incoming method calls
* @param handler
* @param data
* @returns {*}
*/
handle(handler, ...data){
try{
if(BaseModule.handler.includes(handler)){
// .. run module handler
let returnData = this[handler].apply(this, data);
if(returnData instanceof Promise){
// log returned Promise from handler call resolved
returnData.then(() => { this.logHandler(handler, 0);});
}
// log handler call
this.logHandler(handler);
return returnData;
}else{
console.error('Error in module %o. Invalid handler %o', this.constructor.name, handler);
}
}catch(e){
console.error('Error in module %o in handler %s() %o', this.constructor.name, handler, e);
}
}
/**
* log handler calls for this instance
* -> can be helpful for debugging
* @param handler
* @param increment
*/
logHandler(handler, increment = 1){
if(increment){
if(!this._config.logHandler){
this._config.logHandler = {};
}
this._config.logHandler[handler] = (this._config.logHandler[handler] || 0) + increment;
}
}
/**
* init module
*/
init(){}
/**
* update module
* @param data
* @returns {Promise}
*/
update(data){
return this._updateQueue.enqueue(() => Promise.resolve(data), 'end', 'upd');
}
beforeHide(){}
beforeDestroy(){
$(this.moduleElement).destroyPopover(true);
// destroy DataTable instances
for(let table of $(this.moduleElement).find('table.dataTable')){
$(table).DataTable().destroy(true);
}
}
/**
* events from 'Sortable' lib
* @see https://github.com/SortableJS/Sortable
* @param name
* @param e
*/
onSortableEvent(name, e){
if(name === 'onUnchoose' && this._sortableChoosePromise){
this._sortableChoosePromise.resolve();
}
if(name === 'onChoose' && !this._sortableChoosePromise){
this._sortableChoosePromise = BaseModule.newDeferredPromise();
this._updateQueue.enqueue(() => this._sortableChoosePromise.then(() => {
this._sortableChoosePromise = null;
}), 'start');
}
}
static newDeferredPromise(){
return new DeferredPromise();
}
};
BaseModule.isPlugin = true; // module is defined as 'plugin'
BaseModule.scope = 'system'; // static module scope controls how module gets updated and what type of data is injected
BaseModule.sortArea = 'a'; // static default sortable area
BaseModule.position = 0; // static default sort/order position within sortable area
BaseModule.label = '???'; // static module label (e.g. description)
BaseModule.className = 'pf-module'; // static CSS class name
BaseModule.fullDataUpdate = false; // static module requires additional data (e.g. system description,...)
BaseModule.Util = Util; // static access to Pathfinders Util object
BaseModule.handler = [
'render',
'init',
'update',
'beforeHide',
'beforeDestroy',
'onSortableEvent'
];
BaseModule.defaultConfig = {
position: 1,
className: 'pf-base-module', // class for module
headClassName: 'pf-module-head', // class for module header
handlerClassName: 'pf-sortable-handle', // class for "drag" handler
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
headline: 'Base headline', // module headline
bodyClassName: 'pf-module-body', // class for module body [optional: can be used]
moduleHeadlineIconClass: 'pf-module-icon-button' // class for toolbar icons in the head
};
return BaseModule;
});

File diff suppressed because it is too large Load Diff

311
js/app/ui/module/demo.js Normal file
View File

@@ -0,0 +1,311 @@
define([ // dependencies for this module
'module/base', // abstract `parent` module class definition [required]
'app/render' // ... for demo purpose, we load a Render helper object
], (BaseModule, Render) => {
'use strict';
/**
* DemoModule class
* -> skeleton for custom module plugins
* @type {DemoModule}
*/
let DemoModule = class DemoModule extends BaseModule {
constructor(config = {}) {
super(Object.assign({}, new.target.defaultConfig, config));
}
/**
* module header
* @param text
* @returns {HTMLDivElement}
*/
newHeaderElement(text){
let headEl = super.newHeaderElement(text);
let toolbarEl = this.newHeadlineToolbarElement();
let iconPlayEl = this.newIconElement([
'fa-play', 'fa-fw',
'txt-color', 'txt-color-success',
this._config.moduleHeadlineIconClass
]);
iconPlayEl.setAttribute('title', 'pause update()');
iconPlayEl.onclick = e => this.toggleUpdates(e.target);
toolbarEl.append(iconPlayEl);
headEl.append(toolbarEl);
return headEl;
}
/**
* extends logHandler() from BaseModule
* -> updates moduleBody (demo)
* @param handler
* @param increment
*/
logHandler(handler, increment = 1){
super.logHandler(handler, increment);
/**
* @param handler
* @returns {[HTMLSpanElement, HTMLSpanElement]}
*/
let newLiContent = handler => {
let count = this._config.logHandler[handler] || 0;
let label = count ? 'success' : false;
let icon = 'fa-circle';
let handlerQueueLength;
if(this[`_${handler}Queue`]){
handlerQueueLength = this[`_${handler}Queue`].filterQueue(item => item.data === 'upd').length;
label = handlerQueueLength ? 'warning' : label;
icon = handlerQueueLength ? 'fa-sync fa-spin' : icon;
}
let iconLiEl = Object.assign(document.createElement('span'), {
className: 'fa-li'
});
iconLiEl.append(this.newIconElement([icon, 'fa-fw', 'txt-color', label ? `txt-color-${label}` : ``]));
let textLiEl = Object.assign(document.createElement('span'), {
textContent: `${handler} [${count}]${Number.isInteger(handlerQueueLength) ? `[${handlerQueueLength}]`: ``}`,
className: label ? `pf-animation-pulse-${label}` : ``
});
return [iconLiEl, textLiEl];
};
let ulEl = this.queryGridItem('info').querySelector(`.fa-ul`);
if(!ulEl){
ulEl = Object.assign(document.createElement('ul'), {
className: 'fa-ul'
});
let liEls = BaseModule.handler.map(handler => {
let liEl = document.createElement('li');
liEl.dataset.handler = handler;
liEl.prepend(...newLiContent(handler));
return liEl;
});
ulEl.append(...liEls);
this.queryGridItem('info').querySelector(`code`).insertAdjacentElement('beforebegin', ulEl);
}else{
ulEl.querySelector(`[data-handler="${handler}"]`).innerHTML = newLiContent(handler).map(el => el.outerHTML).join('');
}
}
/**
* initial module render method
* -> implementation is enforced by BaseModule
* -> must return a single node element
* @param mapId
* @param systemData
* @returns {HTMLElement}
*/
render(mapId, systemData){
this._systemData = systemData;
// ... append your custom module body
let bodyEl = Object.assign(document.createElement('div'), {
className: [this._config.bodyClassName, 'grid'].join(' ')
});
let gridItems = [];
for(let [area, conf] of Object.entries(this._config.gridItems)){
gridItems.push(this.newGridItemEl(area, conf.label));
}
bodyEl.append(...gridItems);
this.moduleElement.append(bodyEl);
this.renderJson('_config', this._config, 'info');
this.renderJson('render()', {mapId, systemData});
let {config, data} = BaseModule.Util.getCurrentMapData(this._systemData.mapId);
this.renderJson('currentMapData', {config, data}, 'mapData');
return this.moduleElement;
}
/**
* update module
* @param systemData
* @returns {Promise}
*/
update(systemData){
return super.update(systemData).then(systemData => new Promise(resolve => {
this.renderJson('update()', {systemData});
// ... custom (async) code e.g. request external API and update module
// -> resolve() Promise when module is updated.
resolve({
action: 'update',
data: {
module: this
}
});
}));
}
/**
* init module
*/
init(){
super.init();
this.renderJson('init()', null);
this.renderJson('currentUserData', BaseModule.Util.getCurrentUserData(), 'userData');
}
beforeHide(){
super.beforeHide();
this.renderJson('beforeHide()', null);
}
beforeDestroy(){
super.beforeDestroy();
this.renderJson('beforeDestroy()', null);
}
/**
* @param name
* @param e
*/
onSortableEvent(name, e){
super.onSortableEvent(name, e);
this.renderJson(`${name}()`, e, 'sortableJs');
}
/**
* @param label
* @param data
* @param area
*/
renderJson(label, data, area = 'trigger'){
let now = new Date();
let codeEl = this.queryGridItem(area).querySelector(`code`);
codeEl.prepend(Object.assign(document.createElement('section'), {
className: this._config.highlightClassName,
innerHTML: `${++this._config.counter}. ${now.toLocaleTimeString('en-GB')}.${String(now.getMilliseconds()).padStart(3, '0')} ${label} \n` +
`${Render.highlightJson(data, this._config.gridItems[area].jsonConf)}`
}));
// limit code blocks
if(codeEl.childElementCount > this._config.maxCodeSections){
codeEl.removeChild(codeEl.lastChild);
}
}
/**
* @param iconEl
*/
toggleUpdates(iconEl){
iconEl.classList.toggle('fa-pause');
iconEl.classList.toggle('txt-color-danger');
iconEl.classList.toggle('fa-play');
iconEl.classList.toggle('txt-color-success');
if(this._pauseUpdatesPromise){
this._pauseUpdatesPromise.resolve();
}else{
this._pauseUpdatesPromise = BaseModule.newDeferredPromise();
this._updateQueue.enqueue(() => this._pauseUpdatesPromise.then(() => {
this._pauseUpdatesPromise = null;
}), 'start');
}
}
/**
* new gridItem element
* @param area
* @param label
* @returns {HTMLPreElement}
*/
newGridItemEl(area, label){
if(!this._gridItemEl){
// create blank gridItem element for later cloning
this._gridItemEl = Object.assign(document.createElement('pre'), {
className: this._config.gridItemClassName,
innerHTML: '<code></code>'
});
}
let iconClearEl = this.newIconElement([
'fa-trash', 'fa-fw', 'pull-right',
this._config.moduleHeadlineIconClass
]);
iconClearEl.setAttribute('title', 'clear output');
iconClearEl.onclick = e => e.target.closest(`.${this._config.gridItemClassName}`).querySelector('code').innerHTML = '';
let toolbarEl = this.newHeadlineToolbarElement();
toolbarEl.append(iconClearEl);
let gridItemEl = this._gridItemEl.cloneNode(true);
gridItemEl.dataset.area = area;
gridItemEl.prepend(toolbarEl, this.newHeadlineElement(label));
return gridItemEl;
}
/**
* get gridItem <pre> element from module body
* @param {string} area
* @returns {HTMLPreElement}
*/
queryGridItem(area){
return this.moduleElement.querySelector(`.${this._config.bodyClassName} .${this._config.gridItemClassName}[data-area="${area}"]`);
}
};
DemoModule.isPlugin = true; // module is defined as 'plugin'
DemoModule.scope = 'system'; // module scope controls how module gets updated and what type of data is injected
DemoModule.sortArea = 'a'; // default sortable area
DemoModule.position = 10; // default sort/order position within sortable area
DemoModule.label = 'Demo'; // static module label (e.g. description)
DemoModule.fullDataUpdate = true; // subscribe module for frequently updates see update() method
DemoModule.defaultConfig = {
className: 'pf-system-demo-module', // class for module
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
headline: 'Demo Module',
// ... custom config for DemoModule
gridItemClassName: 'pf-dynamic-area',
highlightClassName: 'pf-animation-pulse-success',
counter: 0,
maxCodeSections: 8,
gridItems: {
info: {
label: `handler/config`,
jsonConf: {
collapseDepth: 1,
maxDepth: 3
}
},
trigger: {
label: `trigger`,
jsonConf: {
collapseDepth: 1,
maxDepth: 5
}
},
userData: {
label: `user/char data`,
jsonConf: {
collapseDepth: 1,
maxDepth: 8
}
},
mapData: {
label: `map data`,
jsonConf: {
collapseDepth: 2,
maxDepth: 8,
maxLinesFunctions: 2
}
},
sortableJs: {
label: `drag&drop events`,
jsonConf: {
collapseDepth: 0,
maxDepth: 4,
maxLinesFunctions: 2
}
}
}
};
return DemoModule;
});

71
js/app/ui/module/empty.js Normal file
View File

@@ -0,0 +1,71 @@
define([ // dependencies for this module
'module/base', // abstract `parent` module class definition [required]
'app/render' // ... for demo purpose, we load a Render helper object
], (BaseModule, Render) => {
'use strict';
/**
* EmptyModule class
* -> skeleton for custom module plugins
* @type {EmptyModule}
*/
let EmptyModule = class EmptyModule extends BaseModule {
constructor(config = {}) {
super(Object.assign({}, new.target.defaultConfig, config));
}
/**
* initial module render method
* -> implementation is enforced by BaseModule
* -> must return a single node element
* @param mapId
* @param systemData
* @returns {HTMLElement}
*/
render(mapId, systemData){
this._systemData = systemData;
// ... append your custom module body
let bodyEl = Object.assign(document.createElement('div'), {
className: this._config.bodyClassName
});
this.moduleElement.append(bodyEl);
return this.moduleElement;
}
/**
* init module
*/
init(){
super.init();
}
beforeHide(){
super.beforeHide();
}
beforeDestroy(){
super.beforeDestroy();
}
onSortableEvent(name, e){
super.onSortableEvent(name, e);
}
};
EmptyModule.isPlugin = true; // module is defined as 'plugin'
EmptyModule.scope = 'system'; // module scope controls how module gets updated and what type of data is injected
EmptyModule.sortArea = 'a'; // default sortable area
EmptyModule.position = 15; // default sort/order position within sortable area
EmptyModule.label = 'Empty'; // static module label (e.g. description)
EmptyModule.defaultConfig = {
className: 'pf-system-empty-module', // class for module
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
headline: 'Empty Module',
};
return EmptyModule;
});

View File

@@ -4,24 +4,339 @@
define([
'jquery',
'app/init',
'app/util',
'module/base',
'morris'
], ($, Init, Util, Morris) => {
], ($, Util, BaseModule, Morris) => {
'use strict';
let config = {
// module info
modulePosition: 3,
moduleName: 'systemGraph',
moduleHeadClass: 'pf-module-head', // class for module header
moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler
let SystemGraphModule = class SystemGraphModule extends BaseModule {
constructor(config = {}) {
super(Object.assign({}, new.target.defaultConfig, config));
}
// system graph module
moduleTypeClass: 'pf-system-graph-module', // class for this module
systemGraphClass: 'pf-system-graph', // class for each graph
newHeaderElement(){
return ''; // no default header for this module
}
// system graph labels
newHeadlineToolbarElement(){
let toolbarEl = super.newHeadlineToolbarElement();
let infoEl = document.createElement('small');
infoEl.innerHTML = '<i class="fas fa-fw fa-question-circle pf-help ' + Util.config.popoverTriggerClass + '"></i>';
toolbarEl.append(infoEl);
return toolbarEl;
}
/**
* render module
* @param mapId
* @param systemData
* @returns {HTMLElement}
*/
render(mapId, systemData){
this._systemData = systemData;
// graph data is available for k-space systems
if(systemData.type.id === 2){
let rowEl = document.createElement('div');
rowEl.classList.add(this._config.bodyClassName, 'grid');
for(let graphKey of Object.keys(this._config.systemGraphs)){
let colEl = document.createElement('div');
colEl.dataset.graph = graphKey;
let headEl = this.newHeadElement();
headEl.append(
this.newHandlerElement(),
this.newHeadlineElement(this.getInfoForGraph(graphKey, 'headline')),
this.newHeadlineToolbarElement()
);
let graphEl = document.createElement('div');
graphEl.classList.add(this._config.systemGraphClass);
colEl.append(headEl, graphEl);
rowEl.append(colEl);
}
this.moduleElement.append(rowEl);
this.setModuleObserver();
// request graph data and store result promise
// -> module is not full rendered jet
this._dataPromise = this.getGraphsData();
return this.moduleElement;
}
}
/**
* init module
*/
init(){
super.init();
if(this._dataPromise instanceof Promise){
this._dataPromise
.then(payload => this.addGraphData(payload.data))
.catch(payload => {
let reason = payload.data.status + ' ' + payload.data.error;
this.showNotify({title: payload.data.jqXHR.status + ': System graph data', text: reason, type: 'warning'});
});
}
}
/**
* get data for graphs
* @returns {Promise}
*/
getGraphsData(){
$(this.moduleElement).find('.' + this._config.systemGraphClass).showLoadingAnimation();
return this.request('GET', 'systemgraph', this._systemData.id, {
systemIds: [this._systemData.systemId]
});
}
/**
* update graph elements with data
* @param graphData
*/
addGraphData(graphData){
// calculate time offset until system updated -------------------------------------------------------------
let serverData = Util.getServerTime();
let timestampNow = Math.floor(serverData.getTime() / 1000);
let timeSinceUpdate = timestampNow - this._systemData.updated.updated;
let timeInHours = Math.floor(timeSinceUpdate / 3600);
let timeInMinutes = Math.floor((timeSinceUpdate % 3600) / 60);
let timeInMinutesPercent = parseFloat((timeInMinutes / 60).toFixed(2));
// graph is from right to left -> convert event line
let eventLine = Math.max(parseFloat((24 - timeInHours - timeInMinutesPercent).toFixed(2)), 0);
// update graph data --------------------------------------------------------------------------------------
for(let [systemId, graphsData] of Object.entries(graphData)){
for(let [graphKey, graphData] of Object.entries(graphsData)){
let graphColElement = $(this.moduleElement).find('[data-graph="' + graphKey + '"]');
let graphElement = graphColElement.find('.' + this._config.systemGraphClass);
graphElement.hideLoadingAnimation();
graphColElement.data('infoData', this.initGraph(graphElement, graphKey, graphData, eventLine));
}
}
}
/**
* set module observer
*/
setModuleObserver(){
$(this.moduleElement).hoverIntent({
over: function(e){
let element = $(this);
let tooltipData = element.parents('[data-graph]').data('infoData');
if(tooltipData){
SystemGraphModule.addSystemGraphTooltip(element, tooltipData.rows, {
trigger: 'manual',
title: tooltipData.title
}).popover('show');
}
},
out: function(e){
$(this).destroyPopover();
},
selector: '.' + Util.config.popoverTriggerClass
});
}
/**
* get info for a given graph key
* @param graphKey
* @param option
* @returns {*|string}
*/
getInfoForGraph(graphKey, option){
return Util.getObjVal(this._config.systemGraphs, graphKey + '.' + option) || '';
}
/**
* init Morris Graph
* @param graphElement
* @param graphKey
* @param graphData
* @param eventLine
* @returns {null|Object}
*/
initGraph(graphElement, graphKey, graphData, eventLine){
let tooltipData = null;
if(
graphData.logExists &&
graphData.data &&
graphData.data.length
){
let dataLength = graphData.data.length;
let xKey = 'x';
let yKeys = this.getInfoForGraph(graphKey, 'ykeys');
// calc average (goal) ------------------------------------------------------------------------------------
// ... init empty sum object ...
let sum = yKeys.reduce((result, key) => {
result[key] = 0;
return result;
}, {});
// ... sum all values ...
sum = graphData.data.reduce((sum, obj) => {
for(let [key, value] of Object.entries(obj)){
if(sum.hasOwnProperty(key)){
sum[key] += value;
}
}
return sum;
}, sum);
// ... calc average
let goals = Object.values(sum).map(value => Math.floor(value / dataLength));
// init Morris chart --------------------------------------------------------------------------------------
let graphConfig = {
element: graphElement,
data: graphData.data,
xkey: xKey,
ykeys: yKeys,
labels: this.getInfoForGraph(graphKey, 'labels'),
parseTime: false,
ymin: 0,
yLabelFormat: value => Math.round(value),
padding: 8,
hideHover: true,
pointSize: 2.5,
lineColors: this.getInfoForGraph(graphKey, 'lineColors'),
pointFillColors: this.getInfoForGraph(graphKey, 'pointFillColors'),
pointStrokeColors: ['#141519'],
lineWidth: 1.5,
grid: true,
gridStrokeWidth: 0.3,
gridTextSize: 9,
gridTextFamily: 'Oxygen Bold',
gridTextColor: '#63676a',
behaveLikeLine: false,
goals: goals,
goalStrokeWidth: 1,
goalLineColors: ['#c2760c'],
smooth: false,
fillOpacity: 0.2,
resize: true,
redraw: true,
eventStrokeWidth: 1,
eventLineColors: ['#63676a']
};
if(eventLine > 0){
graphConfig.events = [eventLine];
}
this['_aChart_' + graphKey] = Morris.Area(graphConfig);
// data for info "popover" --------------------------------------------------------------------------------
tooltipData = {};
let tooltipRows = [];
let infoLabels = this.getInfoForGraph(graphKey, 'infoLabels');
goals.forEach((goal, i) => {
tooltipRows.push({
label: infoLabels[i],
value: goal,
class: 'txt-color txt-color-orangeDark'
});
});
tooltipData.rows = tooltipRows;
let serverDate = Util.getServerTime();
let updatedDate = Util.convertTimestampToServerTime(graphData.updated);
let updatedDiff = Util.getTimeDiffParts(updatedDate, serverDate);
tooltipData.title = '<i class="fas fa-download"></i><span class="pull-right ">' + Util.formatTimeParts(updatedDiff) + '</span>';
}else{
// make container a bit smaller -> no graph shown
graphElement.css('height', '22px').text('No data');
}
return tooltipData;
}
/**
* detect changed drop area, -> should trigger graph redraw
* @param name
* @param e
*/
onSortableEvent(name, e){
super.onSortableEvent(name, e);
if(e.type === 'add' && e.from !== e.to){
for(let graphKey of Object.keys(this._config.systemGraphs)){
if(typeof this['_aChart_' + graphKey] === 'object'){
this['_aChart_' + graphKey].resizeHandler();
}
}
}
}
/**
* add info tooltip for graphs
* @param element
* @param tooltipData
* @param options
* @returns {jQuery}
*/
static addSystemGraphTooltip(element, tooltipData, options = {}){
let table = '<table>';
for(let data of tooltipData){
let css = data.class || '';
table += '<tr>';
table += '<td>';
table += data.label;
table += '</td>';
table += '<td class="text-right ' + css + '">';
table += data.value;
table += '</td>';
table += '</tr>';
}
table += '</table>';
let defaultOptions = {
placement: 'top',
html: true,
trigger: 'hover',
container: 'body',
title: 'Info',
content: table,
delay: {
show: 0,
hide: 0
},
};
options = Object.assign({}, defaultOptions, options);
return $(element).popover(options);
}
};
SystemGraphModule.isPlugin = false; // module is defined as 'plugin'
SystemGraphModule.scope = 'system'; // module scope controls how module gets updated and what type of data is injected
SystemGraphModule.sortArea = 'a'; // default sortable area
SystemGraphModule.position = 3; // default sort/order position within sortable area
SystemGraphModule.label = 'Graphs'; // static module label (e.g. description)
SystemGraphModule.defaultConfig = {
className: 'pf-system-graph-module', // class for module
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
systemGraphClass: 'pf-system-graph', // class for each graph
systemGraphs: {
jumps: {
headline: 'Jumps',
@@ -53,353 +368,5 @@ define([
}
};
// temp storage for graphsData response Promise
// -> stored until module is fully rendered (attached to DOM)
// otherwise graph can not be rendered
let graphDataPromise = null;
/**
* get info for a given graph key
* @param graphKey
* @param option
* @returns {string}
*/
let getInfoForGraph = (graphKey, option) => Util.getObjVal(config.systemGraphs, graphKey + '.' + option) || '';
/**
* init Morris Graph
* @param graphElement
* @param graphKey
* @param graphData
* @param eventLine
* @returns {Array|null}
*/
let initGraph = (graphElement, graphKey, graphData, eventLine) => {
let tooltipData = null;
if(
graphData.logExists &&
graphData.data &&
graphData.data.length
){
let dataLength = graphData.data.length;
let xKey = 'x';
let yKeys = getInfoForGraph(graphKey, 'ykeys');
// calc average (goal) ------------------------------------------------------------------------------------
// ... init empty sum object ...
let sum = yKeys.reduce((result, key) => {
result[key] = 0;
return result;
}, {});
// ... sum all values ...
sum = graphData.data.reduce((sum, obj) => {
for(let [key, value] of Object.entries(obj)){
if(sum.hasOwnProperty(key)){
sum[key] += value;
}
}
return sum;
}, sum);
// ... calc average
let goals = Object.values(sum).map(value => Math.floor(value / dataLength));
// init Morris chart --------------------------------------------------------------------------------------
let graphConfig = {
element: graphElement,
data: graphData.data,
xkey: xKey,
ykeys: yKeys,
labels: getInfoForGraph(graphKey, 'labels'),
parseTime: false,
ymin: 0,
yLabelFormat: value => Math.round(value),
padding: 8,
hideHover: true,
pointSize: 2.5,
lineColors: getInfoForGraph(graphKey, 'lineColors'),
pointFillColors: getInfoForGraph(graphKey, 'pointFillColors'),
pointStrokeColors: ['#141519'],
lineWidth: 1.5,
grid: true,
gridStrokeWidth: 0.3,
gridTextSize: 9,
gridTextFamily: 'Oxygen Bold',
gridTextColor: '#63676a',
behaveLikeLine: false,
goals: goals,
goalStrokeWidth: 1,
goalLineColors: ['#c2760c'],
smooth: false,
fillOpacity: 0.2,
resize: true,
redraw: true,
eventStrokeWidth: 1,
eventLineColors: ['#63676a']
};
if(eventLine > 0){
graphConfig.events = [eventLine];
}
Morris.Area(graphConfig);
// data for info "popover" --------------------------------------------------------------------------------
tooltipData = {};
let tooltipRows = [];
let infoLabels = getInfoForGraph(graphKey, 'infoLabels');
goals.forEach((goal, i) => {
tooltipRows.push({
label: infoLabels[i],
value: goal,
class: 'txt-color txt-color-orangeDark'
});
});
tooltipData.rows = tooltipRows;
let serverDate = Util.getServerTime();
let updatedDate = Util.convertTimestampToServerTime(graphData.updated);
let updatedDiff = Util.getTimeDiffParts(updatedDate, serverDate);
tooltipData.title = '<i class="fas fa-download"></i><span class="pull-right ">' + Util.formatTimeParts(updatedDiff) + '</span>';
}else{
// make container a bit smaller -> no graph shown
graphElement.css('height', '22px').text('No data');
}
return tooltipData;
};
/**
* request graphs data
* @param requestData
* @param context
* @returns {Promise<any>}
*/
let requestGraphData = (requestData, context) => {
let requestGraphDataExecutor = (resolve, reject) => {
// show loading animation
context.moduleElement.find('.' + config.systemGraphClass).showLoadingAnimation();
$.ajax({
type: 'GET',
url: Init.path.getSystemGraphData,
data: requestData,
dataType: 'json',
context: context
}).done(function(graphData){
resolve({
action: 'requestGraphData',
data: {
context: this,
graphData: graphData
}
});
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': System graph data', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
this.moduleElement.hide();
reject();
});
};
return new Promise(requestGraphDataExecutor);
};
/**
* update graph elements with data
* @param context
* @param graphData
*/
let addGraphData = (context, graphData) => {
// calculate time offset until system updated -----------------------------------------------------------------
let serverData = Util.getServerTime();
let timestampNow = Math.floor(serverData.getTime() / 1000);
let timeSinceUpdate = timestampNow - context.systemData.updated.updated;
let timeInHours = Math.floor(timeSinceUpdate / 3600);
let timeInMinutes = Math.floor((timeSinceUpdate % 3600) / 60);
let timeInMinutesPercent = parseFloat((timeInMinutes / 60).toFixed(2));
// graph is from right to left -> convert event line
let eventLine = Math.max(parseFloat((24 - timeInHours - timeInMinutesPercent).toFixed(2)), 0);
// update graph data ------------------------------------------------------------------------------------------
for(let [systemId, graphsData] of Object.entries(graphData)){
for(let [graphKey, graphData] of Object.entries(graphsData)){
let graphColElement = context.moduleElement.find('[data-graph="' + graphKey + '"]');
let graphElement = graphColElement.find('.' + config.systemGraphClass);
graphElement.hideLoadingAnimation();
graphColElement.data('infoData', initGraph(graphElement, graphKey, graphData, eventLine));
}
}
setModuleObserver(context.moduleElement);
};
/**
* @param moduleElement
*/
let setModuleObserver = moduleElement => {
moduleElement.hoverIntent({
over: function(e){
let element = $(this);
let tooltipData = element.parents('[data-graph]').data('infoData');
if(tooltipData){
element.addSystemGraphTooltip(tooltipData.rows, {
trigger: 'manual',
title: tooltipData.title
}).popover('show');
}
},
out: function(e){
$(this).destroyPopover();
},
selector: '.' + Util.config.popoverTriggerClass
});
};
/**
* add info tooltip for graphs
* @param tooltipData
* @param options
* @returns {void|*|undefined}
*/
$.fn.addSystemGraphTooltip = function(tooltipData, options = {}){
let table = '<table>';
for(let data of tooltipData){
let css = data.class || '';
table += '<tr>';
table += '<td>';
table += data.label;
table += '</td>';
table += '<td class="text-right ' + css + '">';
table += data.value;
table += '</td>';
table += '</tr>';
}
table += '</table>';
let defaultOptions = {
placement: 'top',
html: true,
trigger: 'hover',
container: 'body',
title: 'Info',
content: table,
delay: {
show: 0,
hide: 0
},
};
options = $.extend({}, defaultOptions, options);
return this.each(function(){
$(this).popover(options);
});
};
/**
* @see requestGraphData
* @param moduleElement
* @param systemData
* @returns {Promise<any>}
*/
let getGraphsData = (moduleElement, systemData) => {
let requestData = {
systemIds: [systemData.systemId]
};
let contextData = {
moduleElement: moduleElement,
systemData: systemData
};
return requestGraphData(requestData, contextData);
};
/**
* init callback
* @param moduleElement
* @param mapId
* @param systemData
*/
let initModule = (moduleElement, mapId, systemData) => {
if(graphDataPromise instanceof Promise){
graphDataPromise
.then(payload => addGraphData(payload.data.context, payload.data.graphData))
.catch(payload => {});
}
};
/**
* get module element
* @param parentElement
* @param mapId
* @param systemData
* @returns {*}
*/
let getModule = (parentElement, mapId, systemData) => {
// graph data is available for k-space systems
let moduleElement = null;
if(systemData.type.id === 2){
moduleElement = $('<div>');
let rowElement = $('<div>', {
class: 'row'
});
for(let graphKey of Object.keys(config.systemGraphs)){
rowElement.append(
$('<div>', {
class: ['col-xs-12', 'col-sm-4'].join(' ')
}).attr('data-graph', graphKey).append(
$('<div>', {
class: config.moduleHeadClass
}).append(
$('<h5>', {
class: config.moduleHandlerClass
}),
$('<h5>', {
text: getInfoForGraph(graphKey, 'headline')
}),
$('<h5>', {
class: 'pull-right'
}).append(
$('<small>', {
html: '<i class="fas fa-fw fa-question-circle pf-help ' + Util.config.popoverTriggerClass + '"></i>'
})
)
),
$('<div>', {
class: config.systemGraphClass
})
)
);
}
moduleElement.append(rowElement);
// request graph data and store result promise globally
// -> moduleElement is not full rendered at this point
graphDataPromise = getGraphsData(moduleElement, systemData);
}
return moduleElement;
};
return {
config: config,
getModule: getModule,
initModule: initModule
};
return SystemGraphModule;
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,10 @@ define([
'app/init',
'app/lib/prototypes',
'app/lib/console',
'app/lib/localStore',
'conf/system_effect',
'conf/signature_type',
'bootbox',
'localForage',
'lazyload',
'velocity',
'velocityUI',
@@ -20,7 +20,7 @@ define([
'bootstrapConfirmation',
'bootstrapToggle',
'select2'
], ($, Init, Proto, Con, SystemEffect, SignatureType, bootbox, localforage) => {
], ($, Init, Proto, Con, LocalStoreManager, SystemEffect, SignatureType, bootbox) => {
'use strict';
@@ -63,8 +63,11 @@ define([
// map module
mapModuleId: 'pf-map-module', // id for main map module
mapTabBarId: 'pf-map-tabs', // id for map tab bar
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapTabBarIdPrefix: 'pf-map-tab-bar-', // id prefix map tab bar lists <ul>
mapTabBarClass: 'pf-map-tab-bar', // class for map tab bar lists <ul>
mapTabContentClass: 'pf-map-tab-content', // class for map tab content
mapTabContentAreaClass: 'pf-map-tab-content-area', // class for map tab content grid areas
mapTabContentAreaAliases: ['map', 'a', 'b', 'c'],
mapClass: 'pf-map' , // class for all maps
// util
@@ -93,15 +96,16 @@ define([
helpClass: 'pf-help', // class for "help" tooltip elements
// fonts
fontTriglivianClass: 'pf-triglivian' // class for "Triglivian" names (e.g. Abyssal systems)
fontTriglivianClass: 'pf-triglivian', // class for "Triglivian" names (e.g. Abyssal systems)
// LocalStore
localStoreNames: ['default', 'character', 'map', 'module'] // Allowed name identifiers for DB names
};
let stopTimerCache = {}; // cache for stopwatch timer
let animationTimerCache = {}; // cache for table row animation timeout
let localStorage; // cache for "localForage" singleton
/*
* ===============================================================================================================
* Global jQuery plugins for some common and frequently used functions
@@ -114,22 +118,14 @@ define([
$.fn.showLoadingAnimation = function(options){
return this.each(function(){
let loadingElement = $(this);
let iconSize = 'fa-lg';
let iconSize = getObjVal(options, 'icon.size') || 'fa-lg';
// disable all events
loadingElement.css('pointer-events', 'none');
if(options){
if(options.icon){
if(options.icon.size){
iconSize = options.icon.size;
}
}
}
//loadingElement.css('pointer-events', 'none');
let overlay = $('<div>', {
class: config.ajaxOverlayClass
}).append(
}).css('pointer-events', 'none').append(
$('<div>', {
class: [config.ajaxOverlayWrapperClass].join(' ')
}).append(
@@ -151,20 +147,21 @@ define([
};
/**
* removes a loading indicator
* removes loading overlay(s)
*/
$.fn.hideLoadingAnimation = function(){
return this.each(function(){
let loadingElement = $(this);
let overlay = loadingElement.find('.' + config.ajaxOverlayClass );
if(overlay.length){
let parentEl = $(this);
let overlays = parentEl.find('.' + config.ajaxOverlayClass);
if(overlays.length){
overlays.css('pointer-events', 'auto');
// important: "stop" is required to stop "show" animation
// -> otherwise "complete" callback is not fired!
overlay.velocity('stop').velocity('reverse', {
overlays.velocity('stop').velocity('reverse', {
complete: function(){
$(this).remove();
// enable all events
loadingElement.css('pointer-events', 'auto');
this.forEach(overlay => {
overlay.remove();
});
}
});
}
@@ -429,23 +426,35 @@ define([
* @param {object} options
* @returns {*}
*/
$.fn.initTooltips = function(options){
options = (typeof options === 'object') ? options : {};
let defaultOptions = {
container: this,
delay: 100
$.fn.initTooltips = function(options= {}){
let containerSelectors = ['.modal', '.popover'];
let getContainer = (el, selectors = containerSelectors) => {
for(let i = 0; i < selectors.length; i++){
let checkContainer = el.closest(containerSelectors[i]);
if(checkContainer){
return checkContainer;
}
}
};
options = $.extend(defaultOptions, options);
return this.each(function(){
let tooltipElements = $(this).find('[title]');
tooltipElements.tooltip('destroy').tooltip(options);
if(tooltipElements.length){
let tooltipOptions = Object.assign({}, options);
if(!options.hasOwnProperty('container')){
// check if current this is a modal/(child of modal) element
let container = getContainer(this);
if(container){
tooltipOptions.container = container;
}
}
tooltipElements.tooltip('destroy').tooltip(tooltipOptions);
}
});
};
/**
* destroy popover elements
* destroy tooltips from element
* @param recursive
* @returns {*}
*/
@@ -606,7 +615,6 @@ define([
title: 'select character',
trigger: 'manual',
placement: 'bottom',
container: 'body',
content: content,
animation: false
}).data('bs.popover').tip().addClass(config.popoverClass);
@@ -816,26 +824,6 @@ define([
});
};
/**
* get all mapTabElements (<a> tags)
* or search for a specific tabElement within mapModule
* @param mapId
* @returns {JQuery|*|{}|T}
*/
$.fn.getMapTabElements = function(mapId){
let mapModule = $(this);
let mapTabElements = mapModule.find('#' + config.mapTabBarId).find('a');
if(mapId){
// search for a specific tab element
mapTabElements = mapTabElements.filter(function(i, el){
return ( $(el).data('mapId') === mapId );
});
}
return mapTabElements;
};
/*
* ===============================================================================================================
* Util functions that are global available for all modules
@@ -858,16 +846,16 @@ define([
/**
* get CCP image URLs for
* @param resourceType 'alliances'|'corporations'|'characters'|'types'
* @param $resourceId
* @param resourceId
* @param size
* @param resourceVariant
* @returns {boolean}
*/
let eveImageUrl = (resourceType, $resourceId, size = 32, resourceVariant = undefined) => {
let eveImageUrl = (resourceType, resourceId, size = 32, resourceVariant = undefined) => {
let url = false;
if(
typeof resourceType === 'string' &&
typeof $resourceId === 'number' &&
typeof resourceId === 'number' &&
typeof size === 'number'
){
resourceType = resourceType.toLowerCase();
@@ -885,7 +873,7 @@ define([
}
}
url = [Init.url.ccpImageServer, resourceType, $resourceId, resourceVariant].join('/');
url = [Init.url.ccpImageServer, resourceType, resourceId, resourceVariant].join('/');
let params = {size: size};
let searchParams = new URLSearchParams(params); // jshint ignore:line
@@ -969,15 +957,6 @@ define([
}
};
/**
* init utility prototype functions
*/
let initPrototypes = () => {
initPassiveEvents();
};
/**
* filter elements from elements array that are not within viewport
* @param elements
@@ -1282,6 +1261,23 @@ define([
});
};
/**
* set default configuration for "Tooltip" plugin
* @param containerEl
*/
let initDefaultTooltipConfig = containerEl => {
$.fn.tooltip.Constructor.DEFAULTS.container = containerEl;
$.fn.tooltip.Constructor.DEFAULTS.delay = 100;
};
/**
* set default configuration for "Popover" plugin
* @param containerEl
*/
let initDefaultPopoverConfig = containerEl => {
$.fn.popover.Constructor.DEFAULTS.container = containerEl;
};
/**
* set default configuration for "Confirmation" popover plugin
*/
@@ -1424,7 +1420,9 @@ define([
/**
* set default configuration for "xEditable" plugin
*/
let initDefaultEditableConfig = () => {
let initDefaultEditableConfig = containerEl => {
$.fn.editable.defaults.container = containerEl;
// use fontAwesome buttons template
$.fn.editableform.buttons =
'<div class="btn-group">'+
@@ -1516,11 +1514,10 @@ define([
* @returns {Date}
*/
let getServerTime = () => {
// Server is running with GMT/UTC (EVE Time)
let localDate = new Date();
let serverDate = new Date(
return new Date(
localDate.getUTCFullYear(),
localDate.getUTCMonth(),
localDate.getUTCDate(),
@@ -1528,8 +1525,6 @@ define([
localDate.getUTCMinutes(),
localDate.getUTCSeconds()
);
return serverDate;
};
/**
@@ -1669,12 +1664,11 @@ define([
/**
* trigger a notification (on screen or desktop)
* @param customConfig
* @param settings
* @param args
*/
let showNotify = (customConfig, settings) => {
requirejs(['notification'], Notification => {
Notification.showNotify(customConfig, settings);
let showNotify = (...args) => {
requirejs(['PNotify.loader'], Notification => {
Notification.showNotify(...args);
});
};
@@ -1682,7 +1676,7 @@ define([
* stop browser tab title "blinking"
*/
let stopTabBlink = () => {
requirejs(['notification'], Notification => {
requirejs(['PNotify.loader'], Notification => {
Notification.stopTabBlink();
});
};
@@ -2091,15 +2085,6 @@ define([
});
};
/**
* get mapElement from overlay or any child of that
* @param mapOverlay
* @returns {jQuery}
*/
let getMapElementFromOverlay = mapOverlay => {
return $(mapOverlay).parents('.' + config.mapWrapperClass).find('.' + config.mapClass);
};
/**
* get the map module object or create a new module
* @returns {*|HTMLElement}
@@ -2115,6 +2100,32 @@ define([
return mapModule;
};
/**
* get all map tab link elements (<a> tags)
* or search for a specific tabElement within mapModule
* @param mapModule
* @param mapId
* @returns {NodeListOf<HTMLElementTagNameMap[string]>}
*/
let getMapTabLinkElements = (mapModule, mapId) => {
let selector = `.${config.mapTabBarClass} > li > a`;
if(mapId){
selector += `[data-map-id="${mapId}"]`;
}
return mapModule.querySelectorAll(selector);
};
/**
* get clas for tab content areas (drapable sections)
* @param alias
* @returns {string}
*/
let getMapTabContentAreaClass = alias => [
config.mapTabContentAreaClass,
config.mapTabContentAreaAliases.includes(alias) ? alias : undefined
].filter(Boolean).join('-');
/**
*
* @param ariaId
@@ -2737,6 +2748,7 @@ define([
return userInfo;
};
/**
* get "nearBy" systemData based on a jump radius around a currentSystem
* @param currentSystemData
@@ -3112,17 +3124,16 @@ define([
};
/**
* get localForage instance (singleton) for offline client site storage
* @returns {localforage}
* get LocalStore instance for offline client data store
* @param name
* @returns {LocalStore}
*/
let getLocalStorage = function(){
if(localStorage === undefined){
localStorage = localforage.createInstance({
driver: [localforage.INDEXEDDB, localforage.WEBSQL, localforage.LOCALSTORAGE],
name: 'Pathfinder local storage'
});
let getLocalStore = name => {
if(config.localStoreNames.includes(name)){
return LocalStoreManager.getStore(name);
}else{
throw new RangeError('Invalid LocalStore name. Allowed names: ' + config.localStoreNames.join('|'));
}
return localStorage;
};
/**
@@ -3454,8 +3465,10 @@ define([
getVersion: getVersion,
showVersionInfo: showVersionInfo,
eveImageUrl: eveImageUrl,
initPrototypes: initPrototypes,
initPassiveEvents: initPassiveEvents,
initDefaultBootboxConfig: initDefaultBootboxConfig,
initDefaultTooltipConfig: initDefaultTooltipConfig,
initDefaultPopoverConfig: initDefaultPopoverConfig,
initDefaultConfirmationConfig: initDefaultConfirmationConfig,
initDefaultSelect2Config: initDefaultSelect2Config,
initDefaultEditableConfig: initDefaultEditableConfig,
@@ -3480,8 +3493,9 @@ define([
isXHRAborted: isXHRAborted,
triggerMenuAction: triggerMenuAction,
getLabelByRole: getLabelByRole,
getMapElementFromOverlay: getMapElementFromOverlay,
getMapModule: getMapModule,
getMapTabLinkElements: getMapTabLinkElements,
getMapTabContentAreaClass: getMapTabContentAreaClass,
getSystemEffectMultiplierByAreaId: getSystemEffectMultiplierByAreaId,
getSystemEffectData: getSystemEffectData,
getSystemEffectTable: getSystemEffectTable,
@@ -3529,7 +3543,7 @@ define([
openIngameWindow: openIngameWindow,
formatPrice: formatPrice,
formatMassValue: formatMassValue,
getLocalStorage: getLocalStorage,
getLocalStore: getLocalStore,
clearSessionStorage: clearSessionStorage,
getBrowserTabId: getBrowserTabId,
singleDoubleClick: singleDoubleClick,

493
js/lib/pnotify/NonBlock.js Normal file
View File

@@ -0,0 +1,493 @@
/**
* NonBlock.js
*
* Copyright (c) 2017-2018 Hunter Perrin
*
* @author Hunter Perrin <hperrin@gmail.com>
*/
'use strict';
((NonBlock) => {
window.NonBlockJs = {
NonBlock
};
if (document.body) {
window.NonBlockJs.nonBlock = new NonBlock(document.body);
} else {
document.addEventListener('DOMContentLoaded', () => {
window.NonBlockJs.nonBlock = new NonBlock(document.body);
});
}
})((() => {
class NonBlock {
constructor(root, mode) {
this.root = root;
// Detect if we can use "pointer-events".
// Can't use document.documentElement.style because IE9/IE10 report true,
// but only support it on SVG elements, not HTML elements.
const windowStyle = window.getComputedStyle(document.body);
this.pointerEventsSupport = (windowStyle.pointerEvents && windowStyle.pointerEvents === 'auto');
// Some useful regexes.
this.regexOn = /^on/;
this.regexMouseEvents = /^(dbl)?click$|^mouse(move|down|up|over|out|enter|leave)$|^contextmenu$/;
this.regexUiEvents = /^(focus|blur|select|change|reset)$|^key(press|down|up)$/;
this.regexHtmlEvents = /^(scroll|resize|(un)?load|abort|error)$/;
// Whether to use event constructors.
this.useEventConstructors = true;
try {
const e = new MouseEvent('click');
} catch (e) {
this.useEventConstructors = false;
}
// If mode is not provided, use PointerEvents, if it's supported.
if (typeof mode === 'undefined') {
this.mode = this.pointerEventsSupport ? 'PointerEvents' : 'EventForwarding';
} else {
this.mode = mode;
}
// Init the current mode.
if (this['init'+this.mode]) {
this['init'+this.mode]();
}
}
initPointerEvents() {
// Using pointer-events, we can just detect whether an element is being
// hovered over. No event forwarding necessary.
this.addCSS(`.nonblock{transition:opacity .1s ease; pointer-events: none;}.nonblock:hover,.nonblock-hover{opacity:.1 !important;}`);
this.onmousemove = (ev) => {
const nonblocks = document.querySelectorAll('.nonblock');
for (let nonblock of nonblocks) {
const rect = nonblock.getBoundingClientRect();
if (ev.clientX >= rect.left && ev.clientX <= rect.right && ev.clientY >= rect.top && ev.clientY <= rect.bottom) {
if (!nonblock.classList.contains('nonblock-hover')) {
nonblock.classList.add('nonblock-hover');
if (this.isSimulateMouse(nonblock) && ev.isTrusted) {
this.domEvent(nonblock, 'onmouseenter', ev, false);
this.domEvent(nonblock, 'onmouseover', ev, true);
}
} else if (this.isSimulateMouse(nonblock) && ev.isTrusted) {
this.domEvent(nonblock, 'onmousemove', ev, true);
}
} else {
if (nonblock.classList.contains('nonblock-hover')) {
if (this.isSimulateMouse(nonblock) && ev.isTrusted) {
this.domEvent(nonblock, 'onmouseout', ev, true);
this.domEvent(nonblock, 'onmouseleave', ev, false);
}
nonblock.classList.remove('nonblock-hover');
}
}
}
};
this.root.addEventListener('mousemove', this.onmousemove);
}
initEventForwarding() {
// No pointer-events means we have to fall back to using event forwarding.
this.addCSS(`.nonblock{transition:opacity .1s ease;}
.nonblock:hover{opacity:.1 !important;}
.nonblock-hide{position:absolute !important;left:-10000000px !important;right:10000000px !important;}
.nonblock-cursor-auto{cursor:auto !important;}
.nonblock-cursor-default{cursor:default !important;}
.nonblock-cursor-none{cursor:none !important;}
.nonblock-cursor-context-menu{cursor:context-menu !important;}
.nonblock-cursor-help{cursor:help !important;}
.nonblock-cursor-pointer{cursor:pointer !important;}
.nonblock-cursor-progress{cursor:progress !important;}
.nonblock-cursor-wait{cursor:wait !important;}
.nonblock-cursor-cell{cursor:cell !important;}
.nonblock-cursor-crosshair{cursor:crosshair !important;}
.nonblock-cursor-text{cursor:text !important;}
.nonblock-cursor-vertical-text{cursor:vertical-text !important;}
.nonblock-cursor-alias{cursor:alias !important;}
.nonblock-cursor-copy{cursor:copy !important;}
.nonblock-cursor-move{cursor:move !important;}
.nonblock-cursor-no-drop{cursor:no-drop !important;}
.nonblock-cursor-not-allowed{cursor:not-allowed !important;}
.nonblock-cursor-all-scroll{cursor:all-scroll !important;}
.nonblock-cursor-col-resize{cursor:col-resize !important;}
.nonblock-cursor-row-resize{cursor:row-resize !important;}
.nonblock-cursor-n-resize{cursor:n-resize !important;}
.nonblock-cursor-e-resize{cursor:e-resize !important;}
.nonblock-cursor-s-resize{cursor:s-resize !important;}
.nonblock-cursor-w-resize{cursor:w-resize !important;}
.nonblock-cursor-ne-resize{cursor:ne-resize !important;}
.nonblock-cursor-nw-resize{cursor:nw-resize !important;}
.nonblock-cursor-se-resize{cursor:se-resize !important;}
.nonblock-cursor-sw-resize{cursor:sw-resize !important;}
.nonblock-cursor-ew-resize{cursor:ew-resize !important;}
.nonblock-cursor-ns-resize{cursor:ns-resize !important;}
.nonblock-cursor-nesw-resize{cursor:nesw-resize !important;}
.nonblock-cursor-nwse-resize{cursor:nwse-resize !important;}
.nonblock-cursor-zoom-in{cursor:zoom-in !important;}
.nonblock-cursor-zoom-out{cursor:zoom-out !important;}
.nonblock-cursor-grab{cursor:grab !important;}
.nonblock-cursor-grabbing{cursor:grabbing !important;}`);
// This keeps track of the last element the mouse was over, so
// mouseleave, mouseenter, etc can be called.
this.nonBlockLastElem = null;
// These are used for selecting text under a nonblock element.
this.isOverTextNode = false;
this.selectingText = false;
this.onmouseenter = (ev) => {
let nonblock;
if (ev.isTrusted && (nonblock = this.getNonBlocking(ev.target))) {
this.nonBlockLastElem = false;
if (!this.isPropagating(nonblock)) {
ev.stopPropagation();
}
}
};
this.onmouseleave = (ev) => {
let nonblock;
if (ev.isTrusted && (nonblock = this.getNonBlocking(ev.target))) {
this.remCursor(nonblock);
this.nonBlockLastElem = null;
this.selectingText = false;
if (!this.isPropagating(nonblock)) {
ev.stopPropagation();
}
}
};
this.onmouseover = (ev) => {
let nonblock;
if (ev.isTrusted && (nonblock = this.getNonBlocking(ev.target)) && !this.isPropagating(nonblock)) {
ev.stopPropagation();
}
};
this.onmouseout = (ev) => {
let nonblock;
if (ev.isTrusted && (nonblock = this.getNonBlocking(ev.target)) && !this.isPropagating(nonblock)) {
ev.stopPropagation();
}
};
this.onmousemove = (ev) => {
let nonblock;
if (ev.isTrusted && (nonblock = this.getNonBlocking(ev.target))) {
this.nonblockPass(nonblock, ev, 'onmousemove');
// If the user just clicks somewhere, we don't want to select text, so this
// detects that the user moved their mouse.
if (this.selectingText === null) {
window.getSelection().removeAllRanges();
this.selectingText = true;
} else if (this.selectingText) {
// Stop the default action, which would be selecting text.
ev.preventDefault();
}
if (!this.isPropagating(nonblock)) {
ev.stopPropagation();
}
}
};
this.onmousedown = (ev) => {
let nonblock;
if (ev.isTrusted && (nonblock = this.getNonBlocking(ev.target))) {
this.nonblockPass(nonblock, ev, 'onmousedown');
this.selectingText = null;
if (!this.isFocusable(nonblock)) {
// Stop the default action, which would focus the element.
ev.preventDefault();
}
if (!this.isPropagating(nonblock) || !this.isActionPropagating(nonblock)) {
ev.stopPropagation();
}
}
};
this.onmouseup = (ev) => {
let nonblock;
if (ev.isTrusted && (nonblock = this.getNonBlocking(ev.target))) {
this.nonblockPass(nonblock, ev, 'onmouseup');
if (this.selectingText === null) {
window.getSelection().removeAllRanges();
}
this.selectingText = false;
if (!this.isPropagating(nonblock) || !this.isActionPropagating(nonblock)) {
ev.stopPropagation();
}
}
};
this.onclick = (ev) => {
let nonblock;
if (ev.isTrusted && (nonblock = this.getNonBlocking(ev.target))) {
this.nonblockPass(nonblock, ev, 'onclick');
if (!this.isPropagating(nonblock) || !this.isActionPropagating(nonblock)) {
ev.stopPropagation();
}
}
};
this.ondblclick = (ev) => {
let nonblock;
if (ev.isTrusted && (nonblock = this.getNonBlocking(ev.target))) {
this.nonblockPass(nonblock, ev, 'ondblclick');
if (!this.isPropagating(nonblock) || !this.isActionPropagating(nonblock)) {
ev.stopPropagation();
}
}
};
this.root.addEventListener('mouseenter', this.onmouseenter, true);
this.root.addEventListener('mouseleave', this.onmouseleave, true);
this.root.addEventListener('mouseover', this.onmouseover, true);
this.root.addEventListener('mouseout', this.onmouseout, true);
this.root.addEventListener('mousemove', this.onmousemove, true);
this.root.addEventListener('mousedown', this.onmousedown, true);
this.root.addEventListener('mouseup', this.onmouseup, true);
this.root.addEventListener('click', this.onclick, true);
this.root.addEventListener('dblclick', this.ondblclick, true);
}
destroy() {
for (let event of ['mouseenter', 'mouseleave', 'mouseover', 'mouseout', 'mousemove', 'mousedown', 'mouseup', 'click', 'dblclick']) {
if (this['on'+event]) {
this.root.removeEventListener(event, this['on'+event], true);
delete this['on'+event];
}
}
this.styling.parentNode.removeChild(this.styling);
delete this.styling;
}
addCSS(css) {
this.styling = document.createElement('style');
this.styling.setAttribute('type', 'text/css');
if (this.styling.styleSheet) {
this.styling.styleSheet.cssText = css; // IE
} else {
this.styling.appendChild(document.createTextNode(css));
}
document.getElementsByTagName('head')[0].appendChild(this.styling);
}
// Fire a DOM event.
domEvent(elem, event, origEvent, bubbles) {
let eventObject;
event = event.toLowerCase();
if (this.useEventConstructors) {
// New browsers
event = event.replace(this.regexOn, '');
if (event.match(this.regexMouseEvents)) {
eventObject = new MouseEvent(event, {
screenX: origEvent.screenX,
screenY: origEvent.screenY,
clientX: origEvent.clientX,
clientY: origEvent.clientY,
ctrlKey: origEvent.ctrlKey,
shiftKey: origEvent.shiftKey,
altKey: origEvent.altKey,
metaKey: origEvent.metaKey,
button: origEvent.button,
buttons: origEvent.buttons,
relatedTarget: origEvent.relatedTarget,
region: origEvent.region,
detail: origEvent.detail,
view: origEvent.view,
bubbles: bubbles === undefined ? origEvent.bubbles : bubbles,
cancelable: origEvent.cancelable,
composed: origEvent.composed
});
} else if (event.match(this.regexUiEvents)) {
eventObject = new UIEvent(event, {
detail: origEvent.detail,
view: origEvent.view,
bubbles: bubbles === undefined ? origEvent.bubbles : bubbles,
cancelable: origEvent.cancelable,
composed: origEvent.composed
});
} else if (event.match(this.regexHtmlEvents)) {
eventObject = new Event(event, {
bubbles: bubbles === undefined ? origEvent.bubbles : bubbles,
cancelable: origEvent.cancelable,
composed: origEvent.composed
});
}
if (!eventObject) {
return;
}
elem.dispatchEvent(eventObject);
} else if (document.createEvent && elem.dispatchEvent) {
// Old method for FireFox, Opera, Safari, Chrome
event = event.replace(this.regexOn, '');
if (event.match(this.regexMouseEvents)) {
// This allows the click event to fire on the notice. There is
// probably a much better way to do it.
elem.getBoundingClientRect();
eventObject = document.createEvent("MouseEvents");
eventObject.initMouseEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable, origEvent.view, origEvent.detail, origEvent.screenX, origEvent.screenY, origEvent.clientX, origEvent.clientY, origEvent.ctrlKey, origEvent.altKey, origEvent.shiftKey, origEvent.metaKey, origEvent.button, origEvent.relatedTarget);
} else if (event.match(this.regexUiEvents)) {
eventObject = document.createEvent("UIEvents");
eventObject.initUIEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable, origEvent.view, origEvent.detail);
} else if (event.match(this.regexHtmlEvents)) {
eventObject = document.createEvent("HTMLEvents");
eventObject.initEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable);
}
if (!eventObject) {
return;
}
elem.dispatchEvent(eventObject);
} else {
// Internet Explorer
if (!event.match(this.regexOn)) {
event = "on"+event
};
eventObject = document.createEventObject(origEvent);
elem.fireEvent(event, eventObject);
}
}
// This is used to pass events through the el if it is nonblocking.
nonblockPass(elem, event, eventName) {
elem.classList.add('nonblock-hide');
const elBelow = document.elementFromPoint(event.clientX, event.clientY);
if (this.nonBlockLastElem === false) {
this.nonBlockLastElem = elBelow;
}
let range, textNode, whitespaceBefore, text, offset;
if (document.caretPositionFromPoint) {
range = document.caretPositionFromPoint(event.clientX, event.clientY);
textNode = range ? range.offsetNode : null;
offset = range ? range.offset : null;
} else if (document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(event.clientX, event.clientY);
textNode = range ? range.endContainer : null;
offset = range ? range.endOffset : null;
}
if (range) {
whitespaceBefore = range.startContainer.textContent.match(/^[\s\n]*/)[0];
text = range.startContainer.textContent.replace(/[\s\n]+$/g, '');
}
elem.classList.remove('nonblock-hide');
let cursorStyle = this.getCursor(elBelow);
this.isOverTextNode = false;
if (cursorStyle === 'auto' && elBelow.tagName === 'A') {
cursorStyle = 'pointer';
} else if (range && (!whitespaceBefore.length || offset > whitespaceBefore.length) && offset < text.length) {
if (cursorStyle === 'auto') {
cursorStyle = 'text';
}
this.isOverTextNode = true;
}
if (range && this.selectingText && offset > 0) {
const selection = window.getSelection();
let selectionRange;
if (selection.rangeCount === 0) {
this.selectingText = {
originContainer: range.startContainer ? range.startContainer : textNode,
originOffset: offset - 1
};
selectionRange = document.createRange();
selection.addRange(selectionRange);
} else {
selectionRange = selection.getRangeAt(0);
}
if (
(textNode === this.selectingText.originContainer && offset < this.selectingText.originOffset)
|| (textNode.compareDocumentPosition(this.selectingText.originContainer) & Node.DOCUMENT_POSITION_FOLLOWING)
) {
selectionRange.setEnd(this.selectingText.originContainer, this.selectingText.originOffset);
selectionRange.setStart(textNode, offset);
} else {
selectionRange.setStart(this.selectingText.originContainer, this.selectingText.originOffset);
selectionRange.setEnd(textNode, offset);
}
}
this.setCursor(elem, cursorStyle !== 'auto' ? cursorStyle : 'default');
// If the element changed, call mouseenter, mouseleave, etc.
if (!this.nonBlockLastElem || this.nonBlockLastElem !== elBelow) {
if (this.nonBlockLastElem) {
const lastElem = this.nonBlockLastElem;
if (!lastElem.contains(elBelow)) {
this.domEvent(lastElem, 'mouseleave', event, false);
}
this.domEvent(lastElem, 'mouseout', event, true);
if (!elBelow.contains(lastElem)) {
this.domEvent(elBelow, 'mouseenter', event, false);
}
} else if (!elBelow.contains(elem)) {
this.domEvent(elBelow, 'mouseenter', event, false);
}
this.domEvent(elBelow, 'mouseover', event, true);
}
// If the event is mousedown, then we need to focus the element.
if (eventName === 'onmousedown') {
document.activeElement && document.activeElement.blur();
elBelow.focus({preventScroll: true});
}
// Forward the event.
this.domEvent(elBelow, eventName, event);
// Remember the latest element the mouse was over.
this.nonBlockLastElem = elBelow;
}
getNonBlocking(el) {
let nonblock = el;
while (nonblock) {
if (nonblock.classList && nonblock.classList.contains('nonblock')) {
return nonblock;
}
nonblock = nonblock.parentNode;
}
return false;
}
isPropagating(el) {
return !el.classList.contains('nonblock-stop-propagation');
}
isActionPropagating(el) {
return el.classList.contains('nonblock-allow-action-propagation');
}
isFocusable(el) {
return el.classList.contains('nonblock-allow-focus');
}
isSimulateMouse(el) {
return !el.classList.contains('nonblock-stop-mouse-simulation');
}
getCursor(el) {
const style = window.getComputedStyle(el);
return style.getPropertyValue('cursor');
}
setCursor(el, value) {
if (el.classList.contains('nonblock-cursor-' + value)) {
return;
}
this.remCursor(el);
el.classList.add('nonblock-cursor-' + value);
}
remCursor(el) {
const values = Object.keys(el.classList).map(e => el.classList[e]);
[...values].forEach((className) => {
if (className.indexOf('nonblock-cursor-') === 0) {
el.classList.remove(className);
}
});
}
}
return NonBlock;
})());

2040
js/lib/pnotify/PNotify.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,561 @@
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
/* src/PNotifyButtons.html generated by Svelte v2.16.1 */
(function (global, factory) {
(typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object" && typeof module !== "undefined" ? module.exports = factory(require('./PNotify')) : typeof define === "function" && define.amd ? define('PNotifyButtons', ["./PNotify"], factory) : global.PNotifyButtons = factory(PNotify);
})(this, function (PNotify) {
"use strict";
PNotify = PNotify && PNotify.__esModule ? PNotify["default"] : PNotify;
function _showSticker(_ref) {
var sticker = _ref.sticker,
_notice = _ref._notice;
return sticker && !(_notice && _notice.refs.elem.classList.contains('nonblock'));
}
function _showCloser(_ref2) {
var closer = _ref2.closer,
_notice = _ref2._notice;
return closer && !(_notice && _notice.refs.elem.classList.contains('nonblock'));
}
function _pinUpClass(_ref3) {
var classes = _ref3.classes,
_notice = _ref3._notice;
return _notice ? classes.pinUp === null ? _notice.get()._icons.pinUp : classes.pinUp : '';
}
function _pinDownClass(_ref4) {
var classes = _ref4.classes,
_notice = _ref4._notice;
return _notice ? classes.pinDown === null ? _notice.get()._icons.pinDown : classes.pinDown : '';
}
function _closerClass(_ref5) {
var classes = _ref5.classes,
_notice = _ref5._notice;
return _notice ? classes.closer === null ? _notice.get()._icons.closer : classes.closer : '';
}
function data() {
return _extends({
'_notice': null, // The PNotify notice.
'_options': {}, // The options for the notice.
'_mouseIsIn': false
}, PNotify.modules.Buttons.defaults);
};
var methods = {
initModule: function initModule(options) {
var _this = this;
this.set(options);
var _get = this.get(),
_notice = _get._notice;
_notice.on('mouseenter', function () {
return _this.set({ '_mouseIsIn': true });
});
_notice.on('mouseleave', function () {
return _this.set({ '_mouseIsIn': false });
});
_notice.on('state', function (_ref6) {
var changed = _ref6.changed,
current = _ref6.current;
if (!changed.hide) {
return;
}
var _get2 = _this.get(),
sticker = _get2.sticker;
if (!sticker) {
return;
}
// Font Awesome 5 replaces our lovely element with a gross SVG. In
// order to make it play nice with Svelte, we have to clear the
// element and make it again.
var icon = current.hide ? _this.get().classes.pinUp : _this.get().classes.pinDown;
if (_this.get()._notice.get().icons === 'fontawesome5' || typeof icon === 'string' && icon.match(/(^| )fa[srlb]($| )/)) {
_this.set({ 'sticker': false });
_this.set({ 'sticker': true });
}
});
},
handleStickerClick: function handleStickerClick() {
var _get3 = this.get(),
_notice = _get3._notice;
_notice.update({ hide: !_notice.get().hide });
},
handleCloserClick: function handleCloserClick() {
this.get()._notice.close(false);
this.set({ '_mouseIsIn': false });
}
};
function oncreate() {
this.fire('init', { module: this });
};
function setup(Component) {
Component.key = 'Buttons';
Component.defaults = {
// Provide a button for the user to manually close the notice.
closer: true,
// Only show the closer button on hover.
closerHover: true,
// Provide a button for the user to manually stick the notice.
sticker: true,
// Only show the sticker button on hover.
stickerHover: true,
// The various displayed text, helps facilitating internationalization.
labels: {
close: 'Close',
stick: 'Stick',
unstick: 'Unstick'
},
// The classes to use for button icons. Leave them null to use the classes from the styling you're using.
classes: {
closer: null,
pinUp: null,
pinDown: null
}
};
// Register the module with PNotify.
PNotify.modules.Buttons = Component;
// Prepend this module to the container.
PNotify.modulesPrependContainer.push(Component);
// Add button icons to icons objects.
_extends(PNotify.icons.brighttheme, {
closer: 'brighttheme-icon-closer',
pinUp: 'brighttheme-icon-sticker',
pinDown: 'brighttheme-icon-sticker brighttheme-icon-stuck'
});
_extends(PNotify.icons.bootstrap3, {
closer: 'glyphicon glyphicon-remove',
pinUp: 'glyphicon glyphicon-pause',
pinDown: 'glyphicon glyphicon-play'
});
_extends(PNotify.icons.fontawesome4, {
closer: 'fa fa-times',
pinUp: 'fa fa-pause',
pinDown: 'fa fa-play'
});
_extends(PNotify.icons.fontawesome5, {
closer: 'fas fa-times',
pinUp: 'fas fa-pause',
pinDown: 'fas fa-play'
});
};
function add_css() {
var style = createElement("style");
style.id = 'svelte-1yjle82-style';
style.textContent = ".ui-pnotify-closer.svelte-1yjle82,.ui-pnotify-sticker.svelte-1yjle82{float:right;margin-left:.5em;cursor:pointer}[dir=rtl] .ui-pnotify-closer.svelte-1yjle82,[dir=rtl] .ui-pnotify-sticker.svelte-1yjle82{float:left;margin-right:.5em;margin-left:0}.ui-pnotify-buttons-hidden.svelte-1yjle82{visibility:hidden}";
append(document.head, style);
}
function create_main_fragment(component, ctx) {
var text, if_block1_anchor;
var if_block0 = ctx._showCloser && create_if_block_1(component, ctx);
var if_block1 = ctx._showSticker && create_if_block(component, ctx);
return {
c: function c() {
if (if_block0) if_block0.c();
text = createText("\n");
if (if_block1) if_block1.c();
if_block1_anchor = createComment();
},
m: function m(target, anchor) {
if (if_block0) if_block0.m(target, anchor);
insert(target, text, anchor);
if (if_block1) if_block1.m(target, anchor);
insert(target, if_block1_anchor, anchor);
},
p: function p(changed, ctx) {
if (ctx._showCloser) {
if (if_block0) {
if_block0.p(changed, ctx);
} else {
if_block0 = create_if_block_1(component, ctx);
if_block0.c();
if_block0.m(text.parentNode, text);
}
} else if (if_block0) {
if_block0.d(1);
if_block0 = null;
}
if (ctx._showSticker) {
if (if_block1) {
if_block1.p(changed, ctx);
} else {
if_block1 = create_if_block(component, ctx);
if_block1.c();
if_block1.m(if_block1_anchor.parentNode, if_block1_anchor);
}
} else if (if_block1) {
if_block1.d(1);
if_block1 = null;
}
},
d: function d(detach) {
if (if_block0) if_block0.d(detach);
if (detach) {
detachNode(text);
}
if (if_block1) if_block1.d(detach);
if (detach) {
detachNode(if_block1_anchor);
}
}
};
}
// (1:0) {#if _showCloser}
function create_if_block_1(component, ctx) {
var div, span, div_class_value, div_title_value;
function click_handler(event) {
component.handleCloserClick();
}
return {
c: function c() {
div = createElement("div");
span = createElement("span");
span.className = "" + ctx._closerClass + " svelte-1yjle82";
addListener(div, "click", click_handler);
div.className = div_class_value = "ui-pnotify-closer " + (!ctx.closerHover || ctx._mouseIsIn ? '' : 'ui-pnotify-buttons-hidden') + " svelte-1yjle82";
setAttribute(div, "role", "button");
div.tabIndex = "0";
div.title = div_title_value = ctx.labels.close;
},
m: function m(target, anchor) {
insert(target, div, anchor);
append(div, span);
},
p: function p(changed, ctx) {
if (changed._closerClass) {
span.className = "" + ctx._closerClass + " svelte-1yjle82";
}
if ((changed.closerHover || changed._mouseIsIn) && div_class_value !== (div_class_value = "ui-pnotify-closer " + (!ctx.closerHover || ctx._mouseIsIn ? '' : 'ui-pnotify-buttons-hidden') + " svelte-1yjle82")) {
div.className = div_class_value;
}
if (changed.labels && div_title_value !== (div_title_value = ctx.labels.close)) {
div.title = div_title_value;
}
},
d: function d(detach) {
if (detach) {
detachNode(div);
}
removeListener(div, "click", click_handler);
}
};
}
// (11:0) {#if _showSticker}
function create_if_block(component, ctx) {
var div, span, span_class_value, div_class_value, div_aria_pressed_value, div_title_value;
function click_handler(event) {
component.handleStickerClick();
}
return {
c: function c() {
div = createElement("div");
span = createElement("span");
span.className = span_class_value = "" + (ctx._options.hide ? ctx._pinUpClass : ctx._pinDownClass) + " svelte-1yjle82";
addListener(div, "click", click_handler);
div.className = div_class_value = "ui-pnotify-sticker " + (!ctx.stickerHover || ctx._mouseIsIn ? '' : 'ui-pnotify-buttons-hidden') + " svelte-1yjle82";
setAttribute(div, "role", "button");
setAttribute(div, "aria-pressed", div_aria_pressed_value = ctx._options.hide);
div.tabIndex = "0";
div.title = div_title_value = ctx._options.hide ? ctx.labels.stick : ctx.labels.unstick;
},
m: function m(target, anchor) {
insert(target, div, anchor);
append(div, span);
},
p: function p(changed, ctx) {
if ((changed._options || changed._pinUpClass || changed._pinDownClass) && span_class_value !== (span_class_value = "" + (ctx._options.hide ? ctx._pinUpClass : ctx._pinDownClass) + " svelte-1yjle82")) {
span.className = span_class_value;
}
if ((changed.stickerHover || changed._mouseIsIn) && div_class_value !== (div_class_value = "ui-pnotify-sticker " + (!ctx.stickerHover || ctx._mouseIsIn ? '' : 'ui-pnotify-buttons-hidden') + " svelte-1yjle82")) {
div.className = div_class_value;
}
if (changed._options && div_aria_pressed_value !== (div_aria_pressed_value = ctx._options.hide)) {
setAttribute(div, "aria-pressed", div_aria_pressed_value);
}
if ((changed._options || changed.labels) && div_title_value !== (div_title_value = ctx._options.hide ? ctx.labels.stick : ctx.labels.unstick)) {
div.title = div_title_value;
}
},
d: function d(detach) {
if (detach) {
detachNode(div);
}
removeListener(div, "click", click_handler);
}
};
}
function PNotifyButtons(options) {
var _this2 = this;
init(this, options);
this._state = assign(data(), options.data);
this._recompute({ sticker: 1, _notice: 1, closer: 1, classes: 1 }, this._state);
this._intro = true;
if (!document.getElementById("svelte-1yjle82-style")) add_css();
this._fragment = create_main_fragment(this, this._state);
this.root._oncreate.push(function () {
oncreate.call(_this2);
_this2.fire("update", { changed: assignTrue({}, _this2._state), current: _this2._state });
});
if (options.target) {
this._fragment.c();
this._mount(options.target, options.anchor);
flush(this);
}
}
assign(PNotifyButtons.prototype, {
destroy: destroy,
get: get,
fire: fire,
on: on,
set: set,
_set: _set,
_stage: _stage,
_mount: _mount,
_differs: _differs
});
assign(PNotifyButtons.prototype, methods);
PNotifyButtons.prototype._recompute = function _recompute(changed, state) {
if (changed.sticker || changed._notice) {
if (this._differs(state._showSticker, state._showSticker = _showSticker(state))) changed._showSticker = true;
}
if (changed.closer || changed._notice) {
if (this._differs(state._showCloser, state._showCloser = _showCloser(state))) changed._showCloser = true;
}
if (changed.classes || changed._notice) {
if (this._differs(state._pinUpClass, state._pinUpClass = _pinUpClass(state))) changed._pinUpClass = true;
if (this._differs(state._pinDownClass, state._pinDownClass = _pinDownClass(state))) changed._pinDownClass = true;
if (this._differs(state._closerClass, state._closerClass = _closerClass(state))) changed._closerClass = true;
}
};
setup(PNotifyButtons);
function createElement(name) {
return document.createElement(name);
}
function append(target, node) {
target.appendChild(node);
}
function createText(data) {
return document.createTextNode(data);
}
function createComment() {
return document.createComment('');
}
function insert(target, node, anchor) {
target.insertBefore(node, anchor);
}
function detachNode(node) {
node.parentNode.removeChild(node);
}
function addListener(node, event, handler, options) {
node.addEventListener(event, handler, options);
}
function setAttribute(node, attribute, value) {
if (value == null) node.removeAttribute(attribute);else node.setAttribute(attribute, value);
}
function removeListener(node, event, handler, options) {
node.removeEventListener(event, handler, options);
}
function init(component, options) {
component._handlers = blankObject();
component._slots = blankObject();
component._bind = options._bind;
component._staged = {};
component.options = options;
component.root = options.root || component;
component.store = options.store || component.root.store;
if (!options.root) {
component._beforecreate = [];
component._oncreate = [];
component._aftercreate = [];
}
}
function assign(tar, src) {
for (var k in src) {
tar[k] = src[k];
}return tar;
}
function assignTrue(tar, src) {
for (var k in src) {
tar[k] = 1;
}return tar;
}
function flush(component) {
component._lock = true;
callAll(component._beforecreate);
callAll(component._oncreate);
callAll(component._aftercreate);
component._lock = false;
}
function destroy(detach) {
this.destroy = noop;
this.fire('destroy');
this.set = noop;
this._fragment.d(detach !== false);
this._fragment = null;
this._state = {};
}
function get() {
return this._state;
}
function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) {
var handler = handlers[i];
if (!handler.__calling) {
try {
handler.__calling = true;
handler.call(this, data);
} finally {
handler.__calling = false;
}
}
}
}
function on(eventName, handler) {
var handlers = this._handlers[eventName] || (this._handlers[eventName] = []);
handlers.push(handler);
return {
cancel: function cancel() {
var index = handlers.indexOf(handler);
if (~index) handlers.splice(index, 1);
}
};
}
function set(newState) {
this._set(assign({}, newState));
if (this.root._lock) return;
flush(this.root);
}
function _set(newState) {
var oldState = this._state,
changed = {},
dirty = false;
newState = assign(this._staged, newState);
this._staged = {};
for (var key in newState) {
if (this._differs(newState[key], oldState[key])) changed[key] = dirty = true;
}
if (!dirty) return;
this._state = assign(assign({}, oldState), newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
this.fire("state", { changed: changed, current: this._state, previous: oldState });
this._fragment.p(changed, this._state);
this.fire("update", { changed: changed, current: this._state, previous: oldState });
}
}
function _stage(newState) {
assign(this._staged, newState);
}
function _mount(target, anchor) {
this._fragment[this._fragment.i ? 'i' : 'm'](target, anchor || null);
}
function _differs(a, b) {
return a != a ? b == b : a !== b || a && (typeof a === "undefined" ? "undefined" : _typeof(a)) === 'object' || typeof a === 'function';
}
function blankObject() {
return Object.create(null);
}
function callAll(fns) {
while (fns && fns.length) {
fns.shift()();
}
}
function noop() {}
return PNotifyButtons;
});
//# sourceMappingURL=PNotifyButtons.js.map

View File

@@ -0,0 +1,257 @@
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
/* src/PNotifyCallbacks.html generated by Svelte v2.16.1 */
(function (global, factory) {
(typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object" && typeof module !== "undefined" ? module.exports = factory(require('./PNotify')) : typeof define === "function" && define.amd ? define('PNotifyCallbacks', ["./PNotify"], factory) : global.PNotifyCallbacks = factory(PNotify);
})(this, function (PNotify) {
"use strict";
PNotify = PNotify && PNotify.__esModule ? PNotify["default"] : PNotify;
var _open = PNotify.prototype.open;
var _close = PNotify.prototype.close;
var callbacks = function callbacks(notice, options, name) {
var modules = notice ? notice.get().modules : options.modules;
var cbs = modules && modules.Callbacks ? modules.Callbacks : {};
return cbs[name] ? cbs[name] : function () {
return true;
};
};
PNotify.prototype.open = function () {
var ret = callbacks(this, null, 'beforeOpen')(this);
if (ret !== false) {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_open.apply(this, args);
callbacks(this, null, 'afterOpen')(this);
}
};
PNotify.prototype.close = function (timerHide) {
var ret = callbacks(this, null, 'beforeClose')(this, timerHide);
if (ret !== false) {
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
_close.apply(this, [timerHide].concat(args));
callbacks(this, null, 'afterClose')(this, timerHide);
}
};
function setup(Component) {
Component.key = 'Callbacks';
Component.getCallbacks = callbacks;
var _alert = PNotify.alert;
var _notice = PNotify.notice;
var _info = PNotify.info;
var _success = PNotify.success;
var _error = PNotify.error;
var init = function init(original, options) {
callbacks(null, options, 'beforeInit')(options);
var notice = original(options);
callbacks(notice, null, 'afterInit')(notice);
return notice;
};
PNotify.alert = function (options) {
return init(_alert, options);
};
PNotify.notice = function (options) {
return init(_notice, options);
};
PNotify.info = function (options) {
return init(_info, options);
};
PNotify.success = function (options) {
return init(_success, options);
};
PNotify.error = function (options) {
return init(_error, options);
};
// Register the module with PNotify.
PNotify.modules.Callbacks = Component;
};
function create_main_fragment(component, ctx) {
return {
c: noop,
m: noop,
p: noop,
d: noop
};
}
function PNotifyCallbacks(options) {
init(this, options);
this._state = assign({}, options.data);
this._intro = true;
this._fragment = create_main_fragment(this, this._state);
if (options.target) {
this._fragment.c();
this._mount(options.target, options.anchor);
}
}
assign(PNotifyCallbacks.prototype, {
destroy: destroy,
get: get,
fire: fire,
on: on,
set: set,
_set: _set,
_stage: _stage,
_mount: _mount,
_differs: _differs
});
PNotifyCallbacks.prototype._recompute = noop;
setup(PNotifyCallbacks);
function noop() {}
function init(component, options) {
component._handlers = blankObject();
component._slots = blankObject();
component._bind = options._bind;
component._staged = {};
component.options = options;
component.root = options.root || component;
component.store = options.store || component.root.store;
if (!options.root) {
component._beforecreate = [];
component._oncreate = [];
component._aftercreate = [];
}
}
function assign(tar, src) {
for (var k in src) {
tar[k] = src[k];
}return tar;
}
function destroy(detach) {
this.destroy = noop;
this.fire('destroy');
this.set = noop;
this._fragment.d(detach !== false);
this._fragment = null;
this._state = {};
}
function get() {
return this._state;
}
function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) {
var handler = handlers[i];
if (!handler.__calling) {
try {
handler.__calling = true;
handler.call(this, data);
} finally {
handler.__calling = false;
}
}
}
}
function on(eventName, handler) {
var handlers = this._handlers[eventName] || (this._handlers[eventName] = []);
handlers.push(handler);
return {
cancel: function cancel() {
var index = handlers.indexOf(handler);
if (~index) handlers.splice(index, 1);
}
};
}
function set(newState) {
this._set(assign({}, newState));
if (this.root._lock) return;
flush(this.root);
}
function _set(newState) {
var oldState = this._state,
changed = {},
dirty = false;
newState = assign(this._staged, newState);
this._staged = {};
for (var key in newState) {
if (this._differs(newState[key], oldState[key])) changed[key] = dirty = true;
}
if (!dirty) return;
this._state = assign(assign({}, oldState), newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
this.fire("state", { changed: changed, current: this._state, previous: oldState });
this._fragment.p(changed, this._state);
this.fire("update", { changed: changed, current: this._state, previous: oldState });
}
}
function _stage(newState) {
assign(this._staged, newState);
}
function _mount(target, anchor) {
this._fragment[this._fragment.i ? 'i' : 'm'](target, anchor || null);
}
function _differs(a, b) {
return a != a ? b == b : a !== b || a && (typeof a === "undefined" ? "undefined" : _typeof(a)) === 'object' || typeof a === 'function';
}
function blankObject() {
return Object.create(null);
}
function flush(component) {
component._lock = true;
callAll(component._beforecreate);
callAll(component._oncreate);
callAll(component._aftercreate);
component._lock = false;
}
function callAll(fns) {
while (fns && fns.length) {
fns.shift()();
}
}
return PNotifyCallbacks;
});
//# sourceMappingURL=PNotifyCallbacks.js.map

View File

@@ -0,0 +1,464 @@
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
/* src/PNotifyDesktop.html generated by Svelte v2.16.1 */
(function (global, factory) {
(typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object" && typeof module !== "undefined" ? module.exports = factory(require('./PNotify')) : typeof define === "function" && define.amd ? define('PNotifyDesktop', ["./PNotify"], factory) : global.PNotifyDesktop = factory(PNotify);
})(this, function (PNotify) {
"use strict";
PNotify = PNotify && PNotify.__esModule ? PNotify["default"] : PNotify;
var permission = void 0;
var Notification = window.Notification;
var _notify = function notify(title, options, onclick, onclose) {
// Memoize based on feature detection.
if ('Notification' in window) {
_notify = function notify(title, options, onclick, onclose) {
var notice = new Notification(title, options);
if ('NotificationEvent' in window) {
notice.addEventListener('notificationclick', onclick);
notice.addEventListener('close', onclose);
} else if ('addEventListener' in notice) {
notice.addEventListener('click', onclick);
notice.addEventListener('close', onclose);
} else {
notice.onclick = onclick;
notice.onclose = onclose;
}
return notice;
};
} else if ('mozNotification' in navigator) {
_notify = function notify(title, options, onclick, onclose) {
// Gecko < 22
var notice = navigator.mozNotification.createNotification(title, options.body, options.icon).show();
notice.onclick = onclick;
notice.onclose = onclose;
return notice;
};
} else if ('webkitNotifications' in window) {
_notify = function notify(title, options, onclick, onclose) {
var notice = window.webkitNotifications.createNotification(options.icon, title, options.body);
notice.onclick = onclick;
notice.onclose = onclose;
return notice;
};
} else {
_notify = function notify(title, options, onclick, onclose) {
return null;
};
}
return _notify(title, options, onclick, onclose);
};
function data() {
return _extends({
'_notice': null, // The PNotify notice.
'_options': {} // The options for the notice.
}, PNotify.modules.Desktop.defaults);
};
var methods = {
initModule: function initModule(options) {
var _this = this;
this.set(options);
var _get = this.get(),
_notice = _get._notice;
// Animation should always be 'none' for desktop notices, but remember
// the old animation so it can be recovered.
this.set({ '_oldAnimation': _notice.get().animation });
_notice.on('state', function (_ref) {
var changed = _ref.changed,
current = _ref.current,
previous = _ref.previous;
if (changed.animation) {
if (previous.animation === undefined || current.animation !== 'none' || previous.animation === 'none' && current.animation !== _this.get()._oldAnimation) {
_this.set({ '_oldAnimation': current.animation });
}
}
// This is necessary so desktop notices don't cause spacing problems
// when positioning.
if (changed._animatingClass) {
if (!(current._animatingClass === '' || permission !== 0 && _this.get().fallback || !_this.get().desktop)) {
_notice.set({ '_animatingClass': '' });
}
}
});
if (!this.get().desktop) {
return;
}
permission = PNotify.modules.Desktop.checkPermission();
if (permission !== 0) {
// Keep the notice from opening if fallback is false.
if (!this.get().fallback) {
_notice.set({ 'autoDisplay': false });
}
return;
}
_notice.set({ 'animation': 'none' });
_notice.addModuleClass('ui-pnotify-desktop-hide');
this.genNotice();
},
update: function update() {
var _get2 = this.get(),
_notice = _get2._notice;
if (permission !== 0 && this.get().fallback || !this.get().desktop) {
_notice.set({ 'animation': this.get()._oldAnimation });
_notice.removeModuleClass('ui-pnotify-desktop-hide');
return;
} else {
_notice.set({ 'animation': 'none' });
_notice.addModuleClass('ui-pnotify-desktop-hide');
}
this.genNotice();
},
beforeOpen: function beforeOpen() {
if (this.get().desktop && permission !== 0) {
PNotify.modules.Desktop.permission();
}
if (permission !== 0 && this.get().fallback || !this.get().desktop) {
return;
}
var _get3 = this.get(),
_desktop = _get3._desktop;
if (_desktop && 'show' in _desktop) {
this.get()._notice.set({ '_moduleIsNoticeOpen': true });
_desktop.show();
}
},
beforeClose: function beforeClose() {
if (permission !== 0 && this.get().fallback || !this.get().desktop) {
return;
}
var _get4 = this.get(),
_desktop = _get4._desktop;
if (_desktop && 'close' in _desktop) {
_desktop.close();
this.get()._notice.set({ '_moduleIsNoticeOpen': false });
}
},
genNotice: function genNotice() {
var _get5 = this.get(),
_notice = _get5._notice,
icon = _get5.icon;
if (icon === null) {
switch (_notice.get().type) {
case 'error':
this.set({ '_icon': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gQJATQg7e6HvQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABr0lEQVRYw8WXu0oDQRSGv7hRSFYrLTTWKihaqUgUJO+gphBLL1jYpPSCVcAggpWthYhC7Ows9An0IbSPkMRCw8ZmFuI6yczs9cAPuzNz5v92brtrESxGARtokkCcAg2hk7jNl4G2R/m4zFPAiwTgWdRFHnmJuaulOAAaPQDqUZvv9DB3tR0lwIcGwHtU5uca5q4qYZvngJbHpAZ8CtU8dS1gLEyAisegBGTFKWiL65KnzVlY5uOSId6VtNuTtMupOu/TAHiQlNmSskHNXCOAGWBeUp7VhFoApoMAXAOWJoCszBJ9+ALY6vL0JiPgjsKmKUAaOOoBZwIAcNxlJLsCrAOTIQJMAWu62y4LOIqT7lGS96TIcYCMDkBZ46h1gB+PHI28ssq8X/G6DaqG8Piz2DrjVjGXbtSBy46F5QAHwJAizwZugKKscs7gSaqS/KpB/qxsFxwafhf6Odb/eblJi8BGwJdW26BtURxQpMU83hmaDQsNiPtvYMSwj3tgAqDgYzU7wJdHjo9+CgBvEW47lV5Tgj5DMtG0xIfESkIAF+522gdWxTzGEX3i9+6KpOMXF5UBt0NKJCAAAAAASUVORK5CYII=' });
break;
case 'success':
this.set({ '_icon': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gQJATQPRj+65AAAAdBJREFUWMPtlzsvRFEQx3+7HmEjoiYKolVJJDRqnS8ggvVIVEQhCIUsEYJGCEH2E4h4FPREaLTbEo1IEJXHrmY2GTf33nPuY7ud5OTenTMz//89Z86ZWShLWf5LB3AOfACFiOMF2AkC3qOc88BXxFEAxlX8ftGdaNCEen8H6oFHYBR4FocwkpTngzzHgF01fwL0aYcp9fVtMW/rsMcWXWijK1Hexgye9smRT6CxaHgjytMYwccNSXqoja9FeVbiZS+OVaeDiUBLAPAJA/i2m5MXgRSQk7llC/DBMOBeBGqAe0eAjQhfvurH3EmgQk6EW6CVEHt+ZFo6J4EU8OoTcF35jhnAl2wSx20LFgyB1yyOWtY2c72ScMAAkPeZy6g4zUBdGAIAcyEq4Z7y7xbdTFgCACMBwPVJqVDHeNqvaplkH5i0sNuUwmaNkQxww20ZSOy7gFvX7SAk0i76jPQQlJoAwAEwq35ngfmwVatSdUMArZZ+K9JQ1Bp6iGqgSt7f/AIOqSzujLEn6AV+JG6zm4HuCZ+AJuAbWAQu5aIJu7JDck0ngDugC/j1c2qPqR13jpxuvWyS8liY/kQcean/lX6ACQ99DdAQYe+Lf0zylMUgf7qDKgzv284QAAAAAElFTkSuQmCC' });
break;
case 'info':
this.set({ '_icon': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gQJATQ09zRTwAAAAdxJREFUWMPtl88rRFEUxz8zBolRCgsrpOym8TMSO2WplLKwUrKi/B0W7JSFmhVLNlhSlLKx8CtRGpEsJpofpZk3Nkc9b968e++8mdlw6vTeu/edc773nl/3wl+ngOH/zUAf0AN0AmEgB7wCD8AtcFMJoM3ADpAHLHk62RIwL8B0uQwHgXVRnDfkS2DSj/EW4K0Ew05eLMV4O/CuUJwEUvJUgdgwMd4IpBUKl13kVG6aL+ZjJ20DDQqQXy5jKYVMDBhVrb5f069LLrKfGnInqh040HRTvsTAHgei9oGQ7X0YaNNUNCdFKChgQvKtQ1vAkNvEahlSToez9oXad2BCA30ceHZxRxMQMShuvZLmv+hOA32/h+KUwS7MugVhqwb6Go+5nEEwht0ABDUEzyXdFsrQYwqMJjTbdxio9Qkg6QbgvkpnkLw0uQIAZ1UCYNkXawdw4qPCmVBcuADAMZCpAoCVYr3AKtYyHZSWauakjMx50TWwrzJw6lFARjQOt3se8jM6W9TloSCqIb9bRHbN5Fg+KkEZcow/Ak+KFBsD6h3jR8CUabAMlqn7xfxEbAdwWKLhhO3sGPCbOsNSvSyF0Z/5TaCuEleziLhmAOiWG1NWrmZXwIVU1A/+SZO+AcgLC4wt0zD3AAAAAElFTkSuQmCC' });
break;
case 'notice':
default:
this.set({ '_icon': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gQJATM4scOJLAAAAcxJREFUWMPtljtLA0EQx3+J0QRfnYqCiCA+MERBrIwgFtoFbMTOR61i5QcQBdEihZWNoEWwsNAvkMJeBLHRQtHC0iIP4utOmw2cx97d7l2SRgcGbufmv/Pf2dmdhb8uIR+YJqAPaBff30AeeAHuxLgqMgRkgS/AAEybGuLfEdBcycCTwKVYmY5mgO6gwdd8BLaqAST9Bs8EDG7VTd3gex4TbgEjwKjQOHDugZlRDb7sMZEJpCS4bYVMJOygsG1cB+wqHN0Gib1RYXFpLwL74nx7Sb3EFlXATQNjTgRagA3FbZIRiCliT5wITGgUaRACA0CPjMC4xtUcDUAgDAzLCCQ0MhALQCAE9MoIdGkQCJIBgE4ZgWiNMvDL10qgUMMMFGQEnjQmkLXbVg38s8y4qtFcTCAnHiJ5oKiJnSoHjVgIXAmHkGIl5yy+YcWruIy9dvqpupIDCfZWEXvh1gsWFVfxIbG9a3RbRwJnYiuqJYfAqxsBgBWFiQyJzfTAlIB1uzEicbwBFoBTl8lSwINoSuXKjrv4F4FBh61zlKUKvgn7/e5ZEngMEDgLdFSieHaAT42LpgTMVbqC24B54Bi4twV9E6cnDcw6PFj+RSo/l6rlSlldhx4AAAAASUVORK5CYII=' });
break;
}
} else if (icon === false) {
this.set({ '_icon': null });
} else {
this.set({ '_icon': icon });
}
var _get6 = this.get(),
tag = _get6.tag;
if (!this.get()._tag || tag !== null) {
this.set({
'_tag': tag === null ? 'PNotify-' + Math.round(Math.random() * 1000000) : tag
});
}
var options = {
body: this.get().text || _notice.get().text,
tag: this.get()._tag
};
if (!_notice.get().hide) {
options.requireInteraction = true;
}
if (this.get()._icon !== null) {
options.icon = this.get()._icon;
}
Object.apply(options, this.get().options);
var _desktop = _notify(this.get().title || _notice.get().title, options, function () {
_notice.fire('click', { target: _desktop });
}, function () {
_notice.close();
});
_notice.set({ '_moduleIsNoticeOpen': true });
this.set({ _desktop: _desktop });
if (!('close' in _desktop) && 'cancel' in _desktop) {
_desktop.close = function () {
_desktop.cancel();
};
}
}
};
function setup(Component) {
Component.key = 'Desktop';
Component.defaults = {
// Display the notification as a desktop notification.
desktop: false,
// If desktop notifications are not supported or allowed, fall back to a regular notice.
fallback: true,
// The URL of the icon to display. If false, no icon will show. If null, a default icon will show.
icon: null,
// Using a tag lets you update an existing notice, or keep from duplicating notices between tabs.
// If you leave tag null, one will be generated, facilitating the 'update' function.
// see: http://www.w3.org/TR/notifications/#tags-example
tag: null,
// Optionally display a different title for the desktop.
title: null,
// Optionally display different text for the desktop.
text: null,
// Any additional options to be passed to the Notification constructor.
options: {}
};
Component.init = function (notice) {
return new Component({ target: document.body });
};
Component.permission = function () {
if (typeof Notification !== 'undefined' && 'requestPermission' in Notification) {
Notification.requestPermission();
} else if ('webkitNotifications' in window) {
window.webkitNotifications.requestPermission();
}
};
Component.checkPermission = function () {
if (typeof Notification !== 'undefined' && 'permission' in Notification) {
return Notification.permission === 'granted' ? 0 : 1;
} else if ('webkitNotifications' in window) {
return window.webkitNotifications.checkPermission() == 0 ? 0 : 1; // eslint-disable-line eqeqeq
} else {
return 1;
}
};
permission = Component.checkPermission();
// Register the module with PNotify.
PNotify.modules.Desktop = Component;
};
function add_css() {
var style = createElement("style");
style.id = 'svelte-xbgnx4-style';
style.textContent = "[ui-pnotify].ui-pnotify-desktop-hide.ui-pnotify{left:-10000px !important;display:none !important}";
append(document.head, style);
}
function create_main_fragment(component, ctx) {
return {
c: noop,
m: noop,
p: noop,
d: noop
};
}
function PNotifyDesktop(options) {
init(this, options);
this._state = assign(data(), options.data);
this._intro = true;
if (!document.getElementById("svelte-xbgnx4-style")) add_css();
this._fragment = create_main_fragment(this, this._state);
if (options.target) {
this._fragment.c();
this._mount(options.target, options.anchor);
}
}
assign(PNotifyDesktop.prototype, {
destroy: destroy,
get: get,
fire: fire,
on: on,
set: set,
_set: _set,
_stage: _stage,
_mount: _mount,
_differs: _differs
});
assign(PNotifyDesktop.prototype, methods);
PNotifyDesktop.prototype._recompute = noop;
setup(PNotifyDesktop);
function createElement(name) {
return document.createElement(name);
}
function append(target, node) {
target.appendChild(node);
}
function noop() {}
function init(component, options) {
component._handlers = blankObject();
component._slots = blankObject();
component._bind = options._bind;
component._staged = {};
component.options = options;
component.root = options.root || component;
component.store = options.store || component.root.store;
if (!options.root) {
component._beforecreate = [];
component._oncreate = [];
component._aftercreate = [];
}
}
function assign(tar, src) {
for (var k in src) {
tar[k] = src[k];
}return tar;
}
function destroy(detach) {
this.destroy = noop;
this.fire('destroy');
this.set = noop;
this._fragment.d(detach !== false);
this._fragment = null;
this._state = {};
}
function get() {
return this._state;
}
function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) {
var handler = handlers[i];
if (!handler.__calling) {
try {
handler.__calling = true;
handler.call(this, data);
} finally {
handler.__calling = false;
}
}
}
}
function on(eventName, handler) {
var handlers = this._handlers[eventName] || (this._handlers[eventName] = []);
handlers.push(handler);
return {
cancel: function cancel() {
var index = handlers.indexOf(handler);
if (~index) handlers.splice(index, 1);
}
};
}
function set(newState) {
this._set(assign({}, newState));
if (this.root._lock) return;
flush(this.root);
}
function _set(newState) {
var oldState = this._state,
changed = {},
dirty = false;
newState = assign(this._staged, newState);
this._staged = {};
for (var key in newState) {
if (this._differs(newState[key], oldState[key])) changed[key] = dirty = true;
}
if (!dirty) return;
this._state = assign(assign({}, oldState), newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
this.fire("state", { changed: changed, current: this._state, previous: oldState });
this._fragment.p(changed, this._state);
this.fire("update", { changed: changed, current: this._state, previous: oldState });
}
}
function _stage(newState) {
assign(this._staged, newState);
}
function _mount(target, anchor) {
this._fragment[this._fragment.i ? 'i' : 'm'](target, anchor || null);
}
function _differs(a, b) {
return a != a ? b == b : a !== b || a && (typeof a === "undefined" ? "undefined" : _typeof(a)) === 'object' || typeof a === 'function';
}
function blankObject() {
return Object.create(null);
}
function flush(component) {
component._lock = true;
callAll(component._beforecreate);
callAll(component._oncreate);
callAll(component._aftercreate);
component._lock = false;
}
function callAll(fns) {
while (fns && fns.length) {
fns.shift()();
}
}
return PNotifyDesktop;
});
//# sourceMappingURL=PNotifyDesktop.js.map

View File

@@ -1,164 +0,0 @@
// Buttons
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as a module.
define('pnotify.buttons', ['jquery', 'pnotify'], factory);
} else if (typeof exports === 'object' && typeof module !== 'undefined') {
// CommonJS
module.exports = factory(require('jquery'), require('./pnotify'));
} else {
// Browser globals
factory(root.jQuery, root.PNotify);
}
}(typeof window !== "undefined" ? window : this, function($, PNotify){
PNotify.prototype.options.buttons = {
// Provide a button for the user to manually close the notice.
closer: true,
// Only show the closer button on hover.
closer_hover: true,
// Provide a button for the user to manually stick the notice.
sticker: true,
// Only show the sticker button on hover.
sticker_hover: true,
// Show the buttons even when the nonblock module is in use.
show_on_nonblock: false,
// The various displayed text, helps facilitating internationalization.
labels: {
close: "Close",
stick: "Stick",
unstick: "Unstick"
},
// The classes to use for button icons. Leave them null to use the classes from the styling you're using.
classes: {
closer: null,
pin_up: null,
pin_down: null
}
};
PNotify.prototype.modules.buttons = {
init: function(notice, options){
var that = this;
notice.elem.on({
"mouseenter": function(e){
// Show the buttons.
if (that.options.sticker && (!(notice.options.nonblock && notice.options.nonblock.nonblock) || that.options.show_on_nonblock)) {
that.sticker.trigger("pnotify:buttons:toggleStick").css("visibility", "visible");
}
if (that.options.closer && (!(notice.options.nonblock && notice.options.nonblock.nonblock) || that.options.show_on_nonblock)) {
that.closer.css("visibility", "visible");
}
},
"mouseleave": function(e){
// Hide the buttons.
if (that.options.sticker_hover) {
that.sticker.css("visibility", "hidden");
}
if (that.options.closer_hover) {
that.closer.css("visibility", "hidden");
}
}
});
// Provide a button to stick the notice.
this.sticker = $("<div />", {
"class": "ui-pnotify-sticker",
"aria-role": "button",
"aria-pressed": notice.options.hide ? "false" : "true",
"tabindex": "0",
"title": notice.options.hide ? options.labels.stick : options.labels.unstick,
"css": {
"cursor": "pointer",
"visibility": options.sticker_hover ? "hidden" : "visible"
},
"click": function(){
notice.options.hide = !notice.options.hide;
if (notice.options.hide) {
notice.queueRemove();
} else {
notice.cancelRemove();
}
$(this).trigger("pnotify:buttons:toggleStick");
}
})
.bind("pnotify:buttons:toggleStick", function(){
var pin_up = that.options.classes.pin_up === null ? notice.styles.pin_up : that.options.classes.pin_up;
var pin_down = that.options.classes.pin_down === null ? notice.styles.pin_down : that.options.classes.pin_down;
$(this)
.attr("title", notice.options.hide ? that.options.labels.stick : that.options.labels.unstick)
.children()
.attr("class", "")
.addClass(notice.options.hide ? pin_up : pin_down)
.attr("aria-pressed", notice.options.hide ? "false" : "true");
})
.append("<span />")
.trigger("pnotify:buttons:toggleStick")
.prependTo(notice.container);
if (!options.sticker || (notice.options.nonblock && notice.options.nonblock.nonblock && !options.show_on_nonblock)) {
this.sticker.css("display", "none");
}
// Provide a button to close the notice.
this.closer = $("<div />", {
"class": "ui-pnotify-closer",
"aria-role": "button",
"tabindex": "0",
"title": options.labels.close,
"css": {"cursor": "pointer", "visibility": options.closer_hover ? "hidden" : "visible"},
"click": function(){
notice.remove(false);
that.sticker.css("visibility", "hidden");
that.closer.css("visibility", "hidden");
}
})
.append($("<span />", {"class": options.classes.closer === null ? notice.styles.closer : options.classes.closer}))
.prependTo(notice.container);
if (!options.closer || (notice.options.nonblock && notice.options.nonblock.nonblock && !options.show_on_nonblock)) {
this.closer.css("display", "none");
}
},
update: function(notice, options){
// Update the sticker and closer buttons.
if (!options.closer || (notice.options.nonblock && notice.options.nonblock.nonblock && !options.show_on_nonblock)) {
this.closer.css("display", "none");
} else if (options.closer) {
this.closer.css("display", "block");
}
if (!options.sticker || (notice.options.nonblock && notice.options.nonblock.nonblock && !options.show_on_nonblock)) {
this.sticker.css("display", "none");
} else if (options.sticker) {
this.sticker.css("display", "block");
}
// Update the sticker icon.
this.sticker.trigger("pnotify:buttons:toggleStick");
// Update the close icon.
this.closer.find("span").attr("class", "").addClass(options.classes.closer === null ? notice.styles.closer : options.classes.closer);
// Update the hover status of the buttons.
if (options.sticker_hover) {
this.sticker.css("visibility", "hidden");
} else if (!(notice.options.nonblock && notice.options.nonblock.nonblock && !options.show_on_nonblock)) {
this.sticker.css("visibility", "visible");
}
if (options.closer_hover) {
this.closer.css("visibility", "hidden");
} else if (!(notice.options.nonblock && notice.options.nonblock.nonblock && !options.show_on_nonblock)) {
this.closer.css("visibility", "visible");
}
}
};
$.extend(PNotify.styling.brighttheme, {
closer: "brighttheme-icon-closer",
pin_up: "brighttheme-icon-sticker",
pin_down: "brighttheme-icon-sticker brighttheme-icon-stuck"
});
$.extend(PNotify.styling.bootstrap3, {
closer: "glyphicon glyphicon-remove",
pin_up: "glyphicon glyphicon-pause",
pin_down: "glyphicon glyphicon-play"
});
$.extend(PNotify.styling.fontawesome, {
closer: "fa fa-times",
pin_up: "fa fa-pause",
pin_down: "fa fa-play"
});
return PNotify;
}));

View File

@@ -1,51 +0,0 @@
// Callbacks
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as a module.
define('pnotify.callbacks', ['jquery', 'pnotify'], factory);
} else if (typeof exports === 'object' && typeof module !== 'undefined') {
// CommonJS
module.exports = factory(require('jquery'), require('./pnotify'));
} else {
// Browser globals
factory(root.jQuery, root.PNotify);
}
}(typeof window !== "undefined" ? window : this, function($, PNotify){
var _init = PNotify.prototype.init,
_open = PNotify.prototype.open,
_remove = PNotify.prototype.remove;
PNotify.prototype.init = function(){
if (this.options.before_init) {
this.options.before_init(this.options);
}
_init.apply(this, arguments);
if (this.options.after_init) {
this.options.after_init(this);
}
};
PNotify.prototype.open = function(){
var ret;
if (this.options.before_open) {
ret = this.options.before_open(this);
}
if (ret !== false) {
_open.apply(this, arguments);
if (this.options.after_open) {
this.options.after_open(this);
}
}
};
PNotify.prototype.remove = function(timer_hide){
var ret;
if (this.options.before_close) {
ret = this.options.before_close(this, timer_hide);
}
if (ret !== false) {
_remove.apply(this, arguments);
if (this.options.after_close) {
this.options.after_close(this, timer_hide);
}
}
};
return PNotify;
}));

View File

@@ -1,157 +0,0 @@
// Desktop
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as a module.
define('pnotify.desktop', ['jquery', 'pnotify'], factory);
} else if (typeof exports === 'object' && typeof module !== 'undefined') {
// CommonJS
module.exports = factory(require('jquery'), require('./pnotify'));
} else {
// Browser globals
factory(root.jQuery, root.PNotify);
}
}(typeof window !== "undefined" ? window : this, function($, PNotify){
var permission;
var notify = function(title, options){
// Memoize based on feature detection.
if ("Notification" in window) {
notify = function (title, options) {
return new Notification(title, options);
};
} else if ("mozNotification" in navigator) {
notify = function (title, options) {
// Gecko < 22
return navigator.mozNotification
.createNotification(title, options.body, options.icon)
.show();
};
} else if ("webkitNotifications" in window) {
notify = function (title, options) {
return window.webkitNotifications.createNotification(
options.icon,
title,
options.body
);
};
} else {
notify = function (title, options) {
return null;
};
}
return notify(title, options);
};
PNotify.prototype.options.desktop = {
// Display the notification as a desktop notification.
desktop: false,
// If desktop notifications are not supported or allowed, fall back to a regular notice.
fallback: true,
// The URL of the icon to display. If false, no icon will show. If null, a default icon will show.
icon: null,
// Using a tag lets you update an existing notice, or keep from duplicating notices between tabs.
// If you leave tag null, one will be generated, facilitating the "update" function.
// see: http://www.w3.org/TR/notifications/#tags-example
tag: null,
// Optionally display a different title for the desktop.
title: null,
// Optionally display different text for the desktop.
text: null
};
PNotify.prototype.modules.desktop = {
genNotice: function(notice, options){
if (options.icon === null) {
this.icon = "http://sciactive.com/pnotify/includes/desktop/"+notice.options.type+".png";
} else if (options.icon === false) {
this.icon = null;
} else {
this.icon = options.icon;
}
if (this.tag === null || options.tag !== null) {
this.tag = options.tag === null ? "PNotify-"+Math.round(Math.random() * 1000000) : options.tag;
}
notice.desktop = notify(options.title || notice.options.title, {
icon: this.icon,
body: options.text || notice.options.text,
tag: this.tag
});
if (!("close" in notice.desktop) && ("cancel" in notice.desktop)) {
notice.desktop.close = function(){
notice.desktop.cancel();
};
}
notice.desktop.onclick = function(){
notice.elem.trigger("click");
};
notice.desktop.onclose = function(){
if (notice.state !== "closing" && notice.state !== "closed") {
notice.remove();
}
};
},
init: function(notice, options){
if (!options.desktop)
return;
permission = PNotify.desktop.checkPermission();
if (permission !== 0) {
// Keep the notice from opening if fallback is false.
if (!options.fallback) {
notice.options.auto_display = false;
}
return;
}
this.genNotice(notice, options);
},
update: function(notice, options, oldOpts){
if ((permission !== 0 && options.fallback) || !options.desktop)
return;
this.genNotice(notice, options);
},
beforeOpen: function(notice, options){
if ((permission !== 0 && options.fallback) || !options.desktop)
return;
notice.elem.css({'left': '-10000px'}).removeClass('ui-pnotify-in');
},
afterOpen: function(notice, options){
if ((permission !== 0 && options.fallback) || !options.desktop)
return;
notice.elem.css({'left': '-10000px'}).removeClass('ui-pnotify-in');
if ("show" in notice.desktop) {
notice.desktop.show();
}
},
beforeClose: function(notice, options){
if ((permission !== 0 && options.fallback) || !options.desktop)
return;
notice.elem.css({'left': '-10000px'}).removeClass('ui-pnotify-in');
},
afterClose: function(notice, options){
if ((permission !== 0 && options.fallback) || !options.desktop)
return;
notice.elem.css({'left': '-10000px'}).removeClass('ui-pnotify-in');
if ("close" in notice.desktop) {
notice.desktop.close();
}
}
};
PNotify.desktop = {
permission: function(){
if (typeof Notification !== "undefined" && "requestPermission" in Notification) {
Notification.requestPermission();
} else if ("webkitNotifications" in window) {
window.webkitNotifications.requestPermission();
}
},
checkPermission: function(){
if (typeof Notification !== "undefined" && "permission" in Notification) {
return (Notification.permission === "granted" ? 0 : 1);
} else if ("webkitNotifications" in window) {
return window.webkitNotifications.checkPermission() == 0 ? 0 : 1;
} else {
return 1;
}
}
};
permission = PNotify.desktop.checkPermission();
return PNotify;
}));

View File

@@ -1,875 +0,0 @@
/*
PNotify 3.2.0 sciactive.com/pnotify/
(C) 2015 Hunter Perrin; Google, Inc.
license Apache-2.0
*/
/*
* ====== PNotify ======
*
* http://sciactive.com/pnotify/
*
* Copyright 2009-2015 Hunter Perrin
* Copyright 2015 Google, Inc.
*
* Licensed under Apache License, Version 2.0.
* http://www.apache.org/licenses/LICENSE-2.0
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as a module.
define('pnotify', ['jquery'], function($){
return factory($, root);
});
} else if (typeof exports === 'object' && typeof module !== 'undefined') {
// CommonJS
module.exports = factory(require('jquery'), global || root);
} else {
// Browser globals
root.PNotify = factory(root.jQuery, root);
}
}(typeof window !== "undefined" ? window : this, function($, root){
var init = function(root){
var default_stack = {
dir1: "down",
dir2: "left",
push: "bottom",
spacing1: 36,
spacing2: 36,
context: $("body"),
modal: false
};
var posTimer, // Position all timer.
body,
jwindow = $(root);
// Set global variables.
var do_when_ready = function(){
body = $("body");
PNotify.prototype.options.stack.context = body;
jwindow = $(root);
// Reposition the notices when the window resizes.
jwindow.bind('resize', function(){
if (posTimer) {
clearTimeout(posTimer);
}
posTimer = setTimeout(function(){
PNotify.positionAll(true);
}, 10);
});
};
var createStackOverlay = function(stack) {
var overlay = $("<div />", {"class": "ui-pnotify-modal-overlay"});
overlay.prependTo(stack.context);
if (stack.overlay_close) {
// Close the notices on overlay click.
overlay.click(function(){
PNotify.removeStack(stack);
});
}
return overlay;
};
var PNotify = function(options){
// === Class Variables ===
this.state = "initializing"; // The state can be "initializing", "opening", "open", "closing", and "closed".
this.timer = null; // Auto close timer.
this.animTimer = null; // Animation timer.
this.styles = null;
this.elem = null;
this.container = null;
this.title_container = null;
this.text_container = null;
this.animating = false; // Stores what is currently being animated (in or out).
this.timerHide = false; // Stores whether the notice was hidden by a timer.
this.parseOptions(options);
this.init();
};
$.extend(PNotify.prototype, {
// The current version of PNotify.
version: "3.2.0",
// === Options ===
// Options defaults.
options: {
// The notice's title.
title: false,
// Whether to escape the content of the title. (Not allow HTML.)
title_escape: false,
// The notice's text.
text: false,
// Whether to escape the content of the text. (Not allow HTML.)
text_escape: false,
// What styling classes to use. (Can be either "brighttheme", "bootstrap3", or "fontawesome".)
styling: "brighttheme",
// Additional classes to be added to the notice. (For custom styling.)
addclass: "",
// Class to be added to the notice for corner styling.
cornerclass: "",
// Display the notice when it is created.
auto_display: true,
// Width of the notice.
width: "300px",
// Minimum height of the notice. It will expand to fit content.
min_height: "16px",
// Type of the notice. "notice", "info", "success", or "error".
type: "notice",
// Set icon to true to use the default icon for the selected
// style/type, false for no icon, or a string for your own icon class.
icon: true,
// The animation to use when displaying and hiding the notice. "none"
// and "fade" are supported through CSS. Others are supported
// through the Animate module and Animate.css.
animation: "fade",
// Speed at which the notice animates in and out. "slow", "normal",
// or "fast". Respectively, 400ms, 250ms, 100ms.
animate_speed: "normal",
// Display a drop shadow.
shadow: true,
// After a delay, remove the notice.
hide: true,
// Delay in milliseconds before the notice is removed.
delay: 8000,
// Reset the hide timer if the mouse moves over the notice.
mouse_reset: true,
// Remove the notice's elements from the DOM after it is removed.
remove: true,
// Change new lines to br tags.
insert_brs: true,
// Whether to remove the notice from the global array when it is closed.
destroy: true,
// The stack on which the notices will be placed. Also controls the
// direction the notices stack.
stack: default_stack
},
// === Modules ===
// This object holds all the PNotify modules. They are used to provide
// additional functionality.
modules: {},
// This runs an event on all the modules.
runModules: function(event, arg){
var curArg;
for (var module in this.modules) {
curArg = ((typeof arg === "object" && module in arg) ? arg[module] : arg);
if (typeof this.modules[module][event] === 'function') {
this.modules[module].notice = this;
this.modules[module].options = typeof this.options[module] === 'object' ? this.options[module] : {};
this.modules[module][event](this, typeof this.options[module] === 'object' ? this.options[module] : {}, curArg);
}
}
},
// === Events ===
init: function(){
var that = this;
// First and foremost, we don't want our module objects all referencing the prototype.
this.modules = {};
$.extend(true, this.modules, PNotify.prototype.modules);
// Get our styling object.
if (typeof this.options.styling === "object") {
this.styles = this.options.styling;
} else {
this.styles = PNotify.styling[this.options.styling];
}
// Create our widget.
// Stop animation, reset the removal timer when the user mouses over.
this.elem = $("<div />", {
"class": "ui-pnotify "+this.options.addclass,
"css": {"display": "none"},
"aria-live": "assertive",
"aria-role": "alertdialog",
"mouseenter": function(e){
if (that.options.mouse_reset && that.animating === "out") {
if (!that.timerHide) {
return;
}
that.cancelRemove();
}
// Stop the close timer.
if (that.options.hide && that.options.mouse_reset) {
that.cancelRemove();
}
},
"mouseleave": function(e){
// Start the close timer.
if (that.options.hide && that.options.mouse_reset && that.animating !== "out") {
that.queueRemove();
}
PNotify.positionAll();
}
});
// Maybe we need to fade in/out.
if (this.options.animation === "fade") {
this.elem.addClass("ui-pnotify-fade-"+this.options.animate_speed);
}
// Create a container for the notice contents.
this.container = $("<div />", {
"class": this.styles.container+" ui-pnotify-container "+(this.options.type === "error" ? this.styles.error : (this.options.type === "info" ? this.styles.info : (this.options.type === "success" ? this.styles.success : this.styles.notice))),
"role": "alert"
}).appendTo(this.elem);
if (this.options.cornerclass !== "") {
this.container.removeClass("ui-corner-all").addClass(this.options.cornerclass);
}
// Create a drop shadow.
if (this.options.shadow) {
this.container.addClass("ui-pnotify-shadow");
}
// Add the appropriate icon.
if (this.options.icon !== false) {
$("<div />", {"class": "ui-pnotify-icon"})
.append($("<span />", {"class": this.options.icon === true ? (this.options.type === "error" ? this.styles.error_icon : (this.options.type === "info" ? this.styles.info_icon : (this.options.type === "success" ? this.styles.success_icon : this.styles.notice_icon))) : this.options.icon}))
.prependTo(this.container);
}
// Add a title.
this.title_container = $("<h4 />", {
"class": "ui-pnotify-title"
})
.appendTo(this.container);
if (this.options.title === false) {
this.title_container.hide();
} else if (this.options.title_escape) {
this.title_container.text(this.options.title);
} else {
this.title_container.html(this.options.title);
}
// Add text.
this.text_container = $("<div />", {
"class": "ui-pnotify-text",
"aria-role": "alert"
})
.appendTo(this.container);
if (this.options.text === false) {
this.text_container.hide();
} else if (this.options.text_escape) {
this.text_container.text(this.options.text);
} else {
this.text_container.html(this.options.insert_brs ? String(this.options.text).replace(/\n/g, "<br />") : this.options.text);
}
// Set width and min height.
if (typeof this.options.width === "string") {
this.elem.css("width", this.options.width);
}
if (typeof this.options.min_height === "string") {
this.container.css("min-height", this.options.min_height);
}
// Add the notice to the notice array.
if (this.options.stack.push === "top") {
PNotify.notices = $.merge([this], PNotify.notices);
} else {
PNotify.notices = $.merge(PNotify.notices, [this]);
}
// Now position all the notices if they are to push to the top.
if (this.options.stack.push === "top") {
this.queuePosition(false, 1);
}
// Mark the stack so it won't animate the new notice.
this.options.stack.animation = false;
// Run the modules.
this.runModules('init');
// We're now initialized, but haven't been opened yet.
this.state = "closed";
// Display the notice.
if (this.options.auto_display) {
this.open();
}
return this;
},
// This function is for updating the notice.
update: function(options){
// Save old options.
var oldOpts = this.options;
// Then update to the new options.
this.parseOptions(oldOpts, options);
// Maybe we need to fade in/out.
this.elem.removeClass("ui-pnotify-fade-slow ui-pnotify-fade-normal ui-pnotify-fade-fast");
if (this.options.animation === "fade") {
this.elem.addClass("ui-pnotify-fade-"+this.options.animate_speed);
}
// Update the corner class.
if (this.options.cornerclass !== oldOpts.cornerclass) {
this.container.removeClass("ui-corner-all "+oldOpts.cornerclass).addClass(this.options.cornerclass);
}
// Update the shadow.
if (this.options.shadow !== oldOpts.shadow) {
if (this.options.shadow) {
this.container.addClass("ui-pnotify-shadow");
} else {
this.container.removeClass("ui-pnotify-shadow");
}
}
// Update the additional classes.
if (this.options.addclass === false) {
this.elem.removeClass(oldOpts.addclass);
} else if (this.options.addclass !== oldOpts.addclass) {
this.elem.removeClass(oldOpts.addclass).addClass(this.options.addclass);
}
// Update the title.
if (this.options.title === false) {
this.title_container.slideUp("fast");
} else if (this.options.title !== oldOpts.title) {
if (this.options.title_escape) {
this.title_container.text(this.options.title);
} else {
this.title_container.html(this.options.title);
}
if (oldOpts.title === false) {
this.title_container.slideDown(200);
}
}
// Update the text.
if (this.options.text === false) {
this.text_container.slideUp("fast");
} else if (this.options.text !== oldOpts.text) {
if (this.options.text_escape) {
this.text_container.text(this.options.text);
} else {
this.text_container.html(this.options.insert_brs ? String(this.options.text).replace(/\n/g, "<br />") : this.options.text);
}
if (oldOpts.text === false) {
this.text_container.slideDown(200);
}
}
// Change the notice type.
if (this.options.type !== oldOpts.type) {
this.container.removeClass(
this.styles.error+" "+this.styles.notice+" "+this.styles.success+" "+this.styles.info
).addClass(this.options.type === "error" ?
this.styles.error :
(this.options.type === "info" ?
this.styles.info :
(this.options.type === "success" ?
this.styles.success :
this.styles.notice
)
)
);
}
if (this.options.icon !== oldOpts.icon || (this.options.icon === true && this.options.type !== oldOpts.type)) {
// Remove any old icon.
this.container.find("div.ui-pnotify-icon").remove();
if (this.options.icon !== false) {
// Build the new icon.
$("<div />", {"class": "ui-pnotify-icon"})
.append($("<span />", {"class": this.options.icon === true ? (this.options.type === "error" ? this.styles.error_icon : (this.options.type === "info" ? this.styles.info_icon : (this.options.type === "success" ? this.styles.success_icon : this.styles.notice_icon))) : this.options.icon}))
.prependTo(this.container);
}
}
// Update the width.
if (this.options.width !== oldOpts.width) {
this.elem.animate({width: this.options.width});
}
// Update the minimum height.
if (this.options.min_height !== oldOpts.min_height) {
this.container.animate({minHeight: this.options.min_height});
}
// Update the timed hiding.
if (!this.options.hide) {
this.cancelRemove();
} else if (!oldOpts.hide) {
this.queueRemove();
}
this.queuePosition(true);
// Run the modules.
this.runModules('update', oldOpts);
return this;
},
// Display the notice.
open: function(){
this.state = "opening";
// Run the modules.
this.runModules('beforeOpen');
var that = this;
// If the notice is not in the DOM, append it.
if (!this.elem.parent().length) {
this.elem.appendTo(this.options.stack.context ? this.options.stack.context : body);
}
// Try to put it in the right position.
if (this.options.stack.push !== "top") {
this.position(true);
}
this.animateIn(function(){
that.queuePosition(true);
// Now set it to hide.
if (that.options.hide) {
that.queueRemove();
}
that.state = "open";
// Run the modules.
that.runModules('afterOpen');
});
return this;
},
// Remove the notice.
remove: function(timer_hide) {
this.state = "closing";
this.timerHide = !!timer_hide; // Make sure it's a boolean.
// Run the modules.
this.runModules('beforeClose');
var that = this;
if (this.timer) {
root.clearTimeout(this.timer);
this.timer = null;
}
this.animateOut(function(){
that.state = "closed";
// Run the modules.
that.runModules('afterClose');
that.queuePosition(true);
// If we're supposed to remove the notice from the DOM, do it.
if (that.options.remove) {
that.elem.detach();
}
// Run the modules.
that.runModules('beforeDestroy');
// Remove object from PNotify.notices to prevent memory leak (issue #49)
// unless destroy is off
if (that.options.destroy) {
if (PNotify.notices !== null) {
var idx = $.inArray(that, PNotify.notices);
if (idx !== -1) {
PNotify.notices.splice(idx,1);
}
}
}
// Run the modules.
that.runModules('afterDestroy');
});
return this;
},
// === Class Methods ===
// Get the DOM element.
get: function(){
return this.elem;
},
// Put all the options in the right places.
parseOptions: function(options, moreOptions){
this.options = $.extend(true, {}, PNotify.prototype.options);
// This is the only thing that *should* be copied by reference.
this.options.stack = PNotify.prototype.options.stack;
var optArray = [options, moreOptions], curOpts;
for (var curIndex=0; curIndex < optArray.length; curIndex++) {
curOpts = optArray[curIndex];
if (typeof curOpts === "undefined") {
break;
}
if (typeof curOpts !== 'object') {
this.options.text = curOpts;
} else {
for (var option in curOpts) {
if (this.modules[option]) {
// Avoid overwriting module defaults.
$.extend(true, this.options[option], curOpts[option]);
} else {
this.options[option] = curOpts[option];
}
}
}
}
},
// Animate the notice in.
animateIn: function(callback){
// Declare that the notice is animating in.
this.animating = "in";
var that = this;
var finished = function(){
if (that.animTimer) {
clearTimeout(that.animTimer);
}
if (that.animating !== "in") {
return;
}
if (that.elem.is(":visible")) {
if (callback) {
callback.call();
}
// Declare that the notice has completed animating.
that.animating = false;
} else {
that.animTimer = setTimeout(finished, 40);
}
};
if (this.options.animation === "fade") {
this.elem.one('webkitTransitionEnd mozTransitionEnd MSTransitionEnd oTransitionEnd transitionend', finished).addClass("ui-pnotify-in");
this.elem.css("opacity"); // This line is necessary for some reason. Some notices don't fade without it.
this.elem.addClass("ui-pnotify-fade-in");
// Just in case the event doesn't fire, call it after 650 ms.
this.animTimer = setTimeout(finished, 650);
} else {
this.elem.addClass("ui-pnotify-in");
finished();
}
},
// Animate the notice out.
animateOut: function(callback){
// Declare that the notice is animating out.
this.animating = "out";
var that = this;
var finished = function(){
if (that.animTimer) {
clearTimeout(that.animTimer);
}
if (that.animating !== "out") {
return;
}
if (that.elem.css("opacity") == "0" || !that.elem.is(":visible")) {
that.elem.removeClass("ui-pnotify-in");
if (that.options.stack.overlay) {
// Go through the modal stack to see if any are left open.
// TODO: Rewrite this cause it sucks.
var stillOpen = false;
$.each(PNotify.notices, function(i, notice){
if (notice != that && notice.options.stack === that.options.stack && notice.state != "closed") {
stillOpen = true;
}
});
if (!stillOpen) {
that.options.stack.overlay.hide();
}
}
if (callback) {
callback.call();
}
// Declare that the notice has completed animating.
that.animating = false;
} else {
// In case this was called before the notice finished animating.
that.animTimer = setTimeout(finished, 40);
}
};
if (this.options.animation === "fade") {
this.elem.one('webkitTransitionEnd mozTransitionEnd MSTransitionEnd oTransitionEnd transitionend', finished).removeClass("ui-pnotify-fade-in");
// Just in case the event doesn't fire, call it after 650 ms.
this.animTimer = setTimeout(finished, 650);
} else {
this.elem.removeClass("ui-pnotify-in");
finished();
}
},
// Position the notice. dont_skip_hidden causes the notice to
// position even if it's not visible.
position: function(dontSkipHidden){
// Get the notice's stack.
var stack = this.options.stack,
elem = this.elem;
if (typeof stack.context === "undefined") {
stack.context = body;
}
if (!stack) {
return;
}
if (typeof stack.nextpos1 !== "number") {
stack.nextpos1 = stack.firstpos1;
}
if (typeof stack.nextpos2 !== "number") {
stack.nextpos2 = stack.firstpos2;
}
if (typeof stack.addpos2 !== "number") {
stack.addpos2 = 0;
}
var hidden = !elem.hasClass("ui-pnotify-in");
// Skip this notice if it's not shown.
if (!hidden || dontSkipHidden) {
if (stack.modal) {
if (stack.overlay) {
stack.overlay.show();
} else {
stack.overlay = createStackOverlay(stack);
}
}
// Add animate class by default.
elem.addClass("ui-pnotify-move");
var curpos1, curpos2;
// Calculate the current pos1 value.
var csspos1;
switch (stack.dir1) {
case "down":
csspos1 = "top";
break;
case "up":
csspos1 = "bottom";
break;
case "left":
csspos1 = "right";
break;
case "right":
csspos1 = "left";
break;
}
curpos1 = parseInt(elem.css(csspos1).replace(/(?:\..*|[^0-9.])/g, ''));
if (isNaN(curpos1)) {
curpos1 = 0;
}
// Remember the first pos1, so the first visible notice goes there.
if (typeof stack.firstpos1 === "undefined" && !hidden) {
stack.firstpos1 = curpos1;
stack.nextpos1 = stack.firstpos1;
}
// Calculate the current pos2 value.
var csspos2;
switch (stack.dir2) {
case "down":
csspos2 = "top";
break;
case "up":
csspos2 = "bottom";
break;
case "left":
csspos2 = "right";
break;
case "right":
csspos2 = "left";
break;
}
curpos2 = parseInt(elem.css(csspos2).replace(/(?:\..*|[^0-9.])/g, ''));
if (isNaN(curpos2)) {
curpos2 = 0;
}
// Remember the first pos2, so the first visible notice goes there.
if (typeof stack.firstpos2 === "undefined" && !hidden) {
stack.firstpos2 = curpos2;
stack.nextpos2 = stack.firstpos2;
}
// Check that it's not beyond the viewport edge.
if (
(stack.dir1 === "down" && stack.nextpos1 + elem.height() > (stack.context.is(body) ? jwindow.height() : stack.context.prop('scrollHeight')) ) ||
(stack.dir1 === "up" && stack.nextpos1 + elem.height() > (stack.context.is(body) ? jwindow.height() : stack.context.prop('scrollHeight')) ) ||
(stack.dir1 === "left" && stack.nextpos1 + elem.width() > (stack.context.is(body) ? jwindow.width() : stack.context.prop('scrollWidth')) ) ||
(stack.dir1 === "right" && stack.nextpos1 + elem.width() > (stack.context.is(body) ? jwindow.width() : stack.context.prop('scrollWidth')) )
) {
// If it is, it needs to go back to the first pos1, and over on pos2.
stack.nextpos1 = stack.firstpos1;
stack.nextpos2 += stack.addpos2 + (typeof stack.spacing2 === "undefined" ? 25 : stack.spacing2);
stack.addpos2 = 0;
}
if (typeof stack.nextpos2 === "number") {
if (!stack.animation) {
elem.removeClass("ui-pnotify-move");
elem.css(csspos2, stack.nextpos2+"px");
elem.css(csspos2);
elem.addClass("ui-pnotify-move");
} else {
elem.css(csspos2, stack.nextpos2+"px");
}
}
// Keep track of the widest/tallest notice in the column/row, so we can push the next column/row.
switch (stack.dir2) {
case "down":
case "up":
if (elem.outerHeight(true) > stack.addpos2) {
stack.addpos2 = elem.height();
}
break;
case "left":
case "right":
if (elem.outerWidth(true) > stack.addpos2) {
stack.addpos2 = elem.width();
}
break;
}
// Move the notice on dir1.
if (typeof stack.nextpos1 === "number") {
if (!stack.animation) {
elem.removeClass("ui-pnotify-move");
elem.css(csspos1, stack.nextpos1+"px");
elem.css(csspos1);
elem.addClass("ui-pnotify-move");
} else {
elem.css(csspos1, stack.nextpos1+"px");
}
}
// Calculate the next dir1 position.
switch (stack.dir1) {
case "down":
case "up":
stack.nextpos1 += elem.height() + (typeof stack.spacing1 === "undefined" ? 25 : stack.spacing1);
break;
case "left":
case "right":
stack.nextpos1 += elem.width() + (typeof stack.spacing1 === "undefined" ? 25 : stack.spacing1);
break;
}
}
return this;
},
// Queue the position all function so it doesn't run repeatedly and
// use up resources.
queuePosition: function(animate, milliseconds){
if (posTimer) {
clearTimeout(posTimer);
}
if (!milliseconds) {
milliseconds = 10;
}
posTimer = setTimeout(function(){
PNotify.positionAll(animate);
}, milliseconds);
return this;
},
// Cancel any pending removal timer.
cancelRemove: function(){
if (this.timer) {
root.clearTimeout(this.timer);
}
if (this.animTimer) {
root.clearTimeout(this.animTimer);
}
if (this.state === "closing") {
// If it's animating out, stop it.
this.state = "open";
this.animating = false;
this.elem.addClass("ui-pnotify-in");
if (this.options.animation === "fade") {
this.elem.addClass("ui-pnotify-fade-in");
}
}
return this;
},
// Queue a removal timer.
queueRemove: function(){
var that = this;
// Cancel any current removal timer.
this.cancelRemove();
this.timer = root.setTimeout(function(){
that.remove(true);
}, (isNaN(this.options.delay) ? 0 : this.options.delay));
return this;
}
});
// These functions affect all notices.
$.extend(PNotify, {
// This holds all the notices.
notices: [],
reload: init,
removeAll: function(){
$.each(PNotify.notices, function(i, notice){
if (notice.remove) {
notice.remove(false);
}
});
},
removeStack: function(stack){
$.each(PNotify.notices, function(i, notice){
if (notice.remove && notice.options.stack === stack) {
notice.remove(false);
}
});
},
positionAll: function(animate){
// This timer is used for queueing this function so it doesn't run
// repeatedly.
if (posTimer) {
clearTimeout(posTimer);
}
posTimer = null;
// Reset the next position data.
if (PNotify.notices && PNotify.notices.length) {
$.each(PNotify.notices, function(i, notice){
var s = notice.options.stack;
if (!s) {
return;
}
if (s.overlay) {
s.overlay.hide();
}
s.nextpos1 = s.firstpos1;
s.nextpos2 = s.firstpos2;
s.addpos2 = 0;
s.animation = animate;
});
$.each(PNotify.notices, function(i, notice){
notice.position();
});
} else {
var s = PNotify.prototype.options.stack;
if (s) {
delete s.nextpos1;
delete s.nextpos2;
}
}
},
styling: {
brighttheme: {
// Bright Theme doesn't require any UI libraries.
container: "brighttheme",
notice: "brighttheme-notice",
notice_icon: "brighttheme-icon-notice",
info: "brighttheme-info",
info_icon: "brighttheme-icon-info",
success: "brighttheme-success",
success_icon: "brighttheme-icon-success",
error: "brighttheme-error",
error_icon: "brighttheme-icon-error"
},
bootstrap3: {
container: "alert",
notice: "alert-warning",
notice_icon: "glyphicon glyphicon-exclamation-sign",
info: "alert-info",
info_icon: "glyphicon glyphicon-info-sign",
success: "alert-success",
success_icon: "glyphicon glyphicon-ok-sign",
error: "alert-danger",
error_icon: "glyphicon glyphicon-warning-sign"
}
}
});
/*
* uses icons from http://fontawesome.io/
* version 4.0.3
*/
PNotify.styling.fontawesome = $.extend({}, PNotify.styling.bootstrap3);
$.extend(PNotify.styling.fontawesome, {
notice_icon: "fa fa-exclamation-circle",
info_icon: "fa fa-info",
success_icon: "fa fa-check",
error_icon: "fa fa-warning"
});
if (root.document.body) {
do_when_ready();
} else {
$(do_when_ready);
}
return PNotify;
};
return init(root);
}));

View File

@@ -1,157 +0,0 @@
// Nonblock
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as a module.
define('pnotify.nonblock', ['jquery', 'pnotify'], factory);
} else if (typeof exports === 'object' && typeof module !== 'undefined') {
// CommonJS
module.exports = factory(require('jquery'), require('./pnotify'));
} else {
// Browser globals
factory(root.jQuery, root.PNotify);
}
}(typeof window !== "undefined" ? window : this, function($, PNotify){
// Some useful regexes.
var re_on = /^on/,
re_mouse_events = /^(dbl)?click$|^mouse(move|down|up|over|out|enter|leave)$|^contextmenu$/,
re_ui_events = /^(focus|blur|select|change|reset)$|^key(press|down|up)$/,
re_html_events = /^(scroll|resize|(un)?load|abort|error)$/;
// Fire a DOM event.
var dom_event = function(e, orig_e){
var event_object;
e = e.toLowerCase();
if (document.createEvent && this.dispatchEvent) {
// FireFox, Opera, Safari, Chrome
e = e.replace(re_on, '');
if (e.match(re_mouse_events)) {
// This allows the click event to fire on the notice. There is
// probably a much better way to do it.
$(this).offset();
event_object = document.createEvent("MouseEvents");
event_object.initMouseEvent(
e, orig_e.bubbles, orig_e.cancelable, orig_e.view, orig_e.detail,
orig_e.screenX, orig_e.screenY, orig_e.clientX, orig_e.clientY,
orig_e.ctrlKey, orig_e.altKey, orig_e.shiftKey, orig_e.metaKey, orig_e.button, orig_e.relatedTarget
);
} else if (e.match(re_ui_events)) {
event_object = document.createEvent("UIEvents");
event_object.initUIEvent(e, orig_e.bubbles, orig_e.cancelable, orig_e.view, orig_e.detail);
} else if (e.match(re_html_events)) {
event_object = document.createEvent("HTMLEvents");
event_object.initEvent(e, orig_e.bubbles, orig_e.cancelable);
}
if (!event_object) return;
this.dispatchEvent(event_object);
} else {
// Internet Explorer
if (!e.match(re_on)) e = "on"+e;
event_object = document.createEventObject(orig_e);
this.fireEvent(e, event_object);
}
};
// This keeps track of the last element the mouse was over, so
// mouseleave, mouseenter, etc can be called.
var nonblock_last_elem;
// This is used to pass events through the notice if it is non-blocking.
var nonblock_pass = function(notice, e, e_name){
notice.elem.addClass("ui-pnotify-nonblock-hide");
var element_below = document.elementFromPoint(e.clientX, e.clientY);
notice.elem.removeClass("ui-pnotify-nonblock-hide");
var jelement_below = $(element_below);
var cursor_style = jelement_below.css("cursor");
if (cursor_style === "auto" && element_below.tagName === "A") {
cursor_style = "pointer";
}
notice.elem.css("cursor", cursor_style !== "auto" ? cursor_style : "default");
// If the element changed, call mouseenter, mouseleave, etc.
if (!nonblock_last_elem || nonblock_last_elem.get(0) != element_below) {
if (nonblock_last_elem) {
dom_event.call(nonblock_last_elem.get(0), "mouseleave", e.originalEvent);
dom_event.call(nonblock_last_elem.get(0), "mouseout", e.originalEvent);
}
dom_event.call(element_below, "mouseenter", e.originalEvent);
dom_event.call(element_below, "mouseover", e.originalEvent);
}
dom_event.call(element_below, e_name, e.originalEvent);
// Remember the latest element the mouse was over.
nonblock_last_elem = jelement_below;
};
PNotify.prototype.options.nonblock = {
// Create a non-blocking notice. It lets the user click elements underneath it.
nonblock: false
};
PNotify.prototype.modules.nonblock = {
init: function(notice, options){
var that = this;
notice.elem.on({
"mouseenter": function(e){
if (that.options.nonblock) {
e.stopPropagation();
}
if (that.options.nonblock) {
// If it's non-blocking, animate to the other opacity.
notice.elem.addClass("ui-pnotify-nonblock-fade");
}
},
"mouseleave": function(e){
if (that.options.nonblock) {
e.stopPropagation();
}
nonblock_last_elem = null;
notice.elem.css("cursor", "auto");
// Animate back to the normal opacity.
if (that.options.nonblock && notice.animating !== "out") {
notice.elem.removeClass("ui-pnotify-nonblock-fade");
}
},
"mouseover": function(e){
if (that.options.nonblock) {
e.stopPropagation();
}
},
"mouseout": function(e){
if (that.options.nonblock) {
e.stopPropagation();
}
},
"mousemove": function(e){
if (that.options.nonblock) {
e.stopPropagation();
nonblock_pass(notice, e, "onmousemove");
}
},
"mousedown": function(e){
if (that.options.nonblock) {
e.stopPropagation();
e.preventDefault();
nonblock_pass(notice, e, "onmousedown");
}
},
"mouseup": function(e){
if (that.options.nonblock) {
e.stopPropagation();
e.preventDefault();
nonblock_pass(notice, e, "onmouseup");
}
},
"click": function(e){
if (that.options.nonblock) {
e.stopPropagation();
nonblock_pass(notice, e, "onclick");
}
},
"dblclick": function(e){
if (that.options.nonblock) {
e.stopPropagation();
nonblock_pass(notice, e, "ondblclick");
}
}
});
}
};
return PNotify;
}));

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.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

View File

@@ -25,7 +25,6 @@ requirejs.config({
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
@@ -56,7 +55,7 @@ requirejs.config({
bootstrapConfirmation: 'lib/bootstrap-confirmation.min', // v1.0.7 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
lazyload: 'lib/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
sortable: 'lib/sortable.min', // v1.10.1 Sortable - drag&drop reorder - https://github.com/SortableJS/Sortable
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org
'summernote': 'lib/summernote/summernote.min',
@@ -65,7 +64,7 @@ requirejs.config({
easePack: 'lib/EasePack.min',
tweenLite: 'lib/TweenLite.min',
// datatables // v1.10.18 DataTables - https://datatables.net
// 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',
@@ -74,15 +73,14 @@ requirejs.config({
'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
// PNotify // v4.0.0 PNotify - notification core file - https://sciactive.com/pnotify
'PNotify.loader': './app/pnotify.loader',
'PNotify': 'lib/pnotify/PNotify',
'PNotifyButtons': 'lib/pnotify/PNotifyButtons',
'PNotifyNonBlock': 'lib/pnotify/PNotifyNonBlock',
'PNotifyDesktop': 'lib/pnotify/PNotifyDesktop',
'PNotifyCallbacks': 'lib/pnotify/PNotifyCallbacks',
'NonBlock': 'lib/pnotify/NonBlock' // v1.0.8 NonBlock.js - for PNotify "nonblock" feature
},
shim: {
bootstrap: {
@@ -138,9 +136,6 @@ requirejs.config({
window.Raphael = Raphael;
}
},
pnotify: {
deps: ['jquery']
},
easyPieChart: {
deps: ['jquery']
},

View File

@@ -5,11 +5,12 @@ define([
'app/promises/promise.deferred',
'app/promises/promise.timeout',
'datatables.net',
'datatables.net-select',
'datatables.net-buttons',
'datatables.net-buttons-html',
'datatables.net-responsive',
'datatables.net-select'
'datatables.net-responsive'
], ($, Init, Counter, DeferredPromise, TimeoutPromise) => {
'use strict';
// all Datatables stuff is available...

View File

@@ -44,11 +44,8 @@ define([], () => {
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

View File

@@ -127,7 +127,7 @@ define([], () => {
constructor(config){
this.config = Object.assign({},{
name: 'Default', // custom name for identification
name: 'Default', // custom unique name for identification
ttl: 3600, // default ttl for cache entries
maxSize: 600, // max cache entries
bufferSize: 10, // cache entry count in percent to be removed if maxSize reached

View File

@@ -107,7 +107,7 @@ define([
delete(){
let isDeleted = false;
if(this._manager){
isDeleted = this._manager.delete(this.name);
isDeleted = this._manager.delete(this._name);
}
return isDeleted;
}

View File

@@ -0,0 +1,55 @@
define([], () => {
'use strict';
/*
// Example usage --------------------------------------------------------------------------------------------------
// global accessible DataStore instance
window.dataStore = new DataStore();
// extend HTMLElement class with an interface to set/get data to it
HTMLElement.prototype.setData = function(key, value){
window.dataStore.set(this, key, value);
};
HTMLElement.prototype.getData = function(key){
return window.dataStore.get(this, key);
};
*/
/**
* Stores data to an object
* -> can be used as a replacement for jQuery $.data()
*/
return class DataStore {
constructor() {
this._store = new WeakMap();
}
set(obj, key, value) {
if (!this._store.has(obj)) {
this._store.set(obj, new Map());
}
this._store.get(obj).set(key, value);
return obj;
}
get(obj, key) {
return this._store.has(obj) && this._store.get(obj).get(key);
}
has(obj, key) {
return this._store.has(obj) && this._store.get(obj).has(key);
}
remove(obj, key) {
let ret = false;
if (this._store.has(obj)) {
ret = this._store.get(obj).delete(key);
if (!this._store.get(obj).size) {
this._store.delete(obj);
}
}
return ret;
}
};
});

View File

@@ -0,0 +1,394 @@
define([
'localForage',
'app/promises/promise.queue',
'app/promises/promise.deferred',
], (LocalForage, PromiseQueue, DeferredPromise) => {
'use strict';
/**
* Instances of LocalStore handle its own LocalForage instance
*/
class LocalStore {
constructor(config, LocalForageConfig){
this._config = Object.assign({}, this.constructor.defaultConfig, config);
let initPromise = new DeferredPromise();
this._processQueue = new PromiseQueue();
this._processQueue.enqueue(() => initPromise);
this._localforage = LocalForage.createInstance(Object.assign({}, LocalStore.LocalForageConfig, LocalForageConfig));
this._localforage.ready().then(() => initPromise.resolve());
this._manager = null; // reference to LocalStoreManager() that manages this LocalStore instance
this.debug = (msg,...data) => {
if(this._config.debug){
data = (data || []);
data.unshift(this.constructor.name, this._config.name);
console.debug('debug: %s %o | ' + msg, ...data);
}
};
}
/**
* set scope for this instance
* -> all read/write actions are scoped
* this is a prefix for all keys!
* @param scope
*/
set scope(scope){
if(LocalStore.isString(scope)){
this._config.scope = scope;
}else{
throw new TypeError('Scope must be instance of "String", Type of "' + typeof scope + '" given');
}
}
get scope(){
return this._config.scope;
}
/**
* get item
* @param key
* @param successCallback
* @returns {Promise}
*/
getItem(key, successCallback = undefined){
key = this.fixKey(key);
let propArray = LocalStore.keyToArray(key);
let rootKey = propArray.shift();
let getItem = () => this._localforage.getItem(key, successCallback);
if(propArray.length){
getItem = () => {
return this._localforage.getItem(rootKey)
.then(data => {
if(LocalStore.isObject(data)){
// find nested property
return LocalStore.findObjProp(data, propArray);
}else{
// rootKey not found -> propArray path not exists
return Promise.resolve(null);
}
});
};
}
return this._processQueue.enqueue(() => getItem());
}
/**
* set/update existing value
* @param key e.g. nested object key' first.a.b.test'
* @param value
* @param successCallback
* @returns {Promise}
*/
setItem(key, value, successCallback = undefined){
key = this.fixKey(key);
let propArray = LocalStore.keyToArray(key);
let rootKey = propArray.shift();
let getItem = () => Promise.resolve(value);
if(propArray.length){
getItem = () => {
return this._localforage.getItem(rootKey)
.then(rootVal => {
rootVal = (rootVal === null) ? {} : rootVal;
// update data with new value (merge obj)
LocalStore.updateObjProp(rootVal, value, propArray);
return rootVal;
});
};
}
return this._processQueue.enqueue(() =>
getItem()
.then(rootVal => this._localforage.setItem(rootKey, rootVal, successCallback))
.then(() => Promise.resolve(value))
);
}
/**
* remove item by key
* -> allows deep obj delete if key points to a nested obj prop
* @param key
* @param successCallback
* @returns {Promise}
*/
removeItem(key, successCallback = undefined){
key = this.fixKey(key);
let propArray = LocalStore.keyToArray(key);
let rootKey = propArray.shift();
let removeItem = () => this._localforage.removeItem(rootKey, successCallback);
if(propArray.length){
removeItem = () => {
return this._localforage.getItem(rootKey)
.then(data => {
if(LocalStore.isObject(data)){
// update data -> delete nested prop
LocalStore.deleteObjProp(data, propArray);
return data;
}else{
// rootKey not found -> nothing to delete
return Promise.reject(new RangeError('No data found for key: ' + rootKey));
}
})
.then(value => this._localforage.setItem(rootKey, value, successCallback))
.catch(e => this.debug('removeItem() error',e));
};
}
return this._processQueue.enqueue(() => removeItem());
}
/**
* clear all items in store
* @param successCallback
* @returns {Promise}
*/
clear(successCallback = undefined){
return this._processQueue.enqueue(() => this._localforage.clear(successCallback));
}
/**
* get number of keys in store
* @param successCallback
* @returns {Promise}
*/
length(successCallback = undefined){
return this._processQueue.enqueue(() => this._localforage.length(successCallback));
}
/**
* Get the name of a key based on its index
* @param keyIndex
* @param successCallback
* @returns {Promise|void}
*/
key(keyIndex, successCallback = undefined){
return this._processQueue.enqueue(() => this._localforage.key(keyIndex, successCallback));
}
/**
* get list of all keys in store
* @param successCallback
* @returns {Promise|void}
*/
keys(successCallback = undefined){
return this._processQueue.enqueue(() => this._localforage.keys(successCallback));
}
/**
* drop current LocalForage instance
* -> removes this from LocalStoreManager
* @returns {Promise|void}
*/
dropInstance(){
return this._processQueue.enqueue(() =>
this._localforage.dropInstance().then(() => this._manager.deleteStore(this._config.name))
);
}
/**
* set LocalStoreManager for this instance
* @param {LocalStoreManager} manager
*/
setManager(manager){
if(manager instanceof LocalStoreManager){
this._manager = manager;
}else{
throw new TypeError('Parameter must be instance of LocalStoreManager. Type of "' + typeof manager + '" given');
}
}
/**
* check if key is Int or String with Int at pos 0
* -> prefix key
* @param key
* @returns {string}
*/
fixKey(key){
if(LocalStore.isString(this.scope) && this.scope.length){
key = [this.scope, key].join('.');
}
if(
Number.isInteger(key) ||
(LocalStore.isString(key) && parseInt(key.charAt(0), 10))
){
key = [this._config.name, key].join('_');
}
return key;
}
/**
* find data from obj prop
* -> deep object search
* @param obj
* @param propArray
* @returns {null|*}
*/
static findObjProp(obj, propArray){
let [head, ...rest] = propArray;
if(!rest.length){
return obj[head];
}else{
if(LocalStore.isObject(obj[head])){
return LocalStore.findObjProp(obj[head], rest);
}else{
return null;
}
}
}
/**
* update/extend obj with new value
* -> deep object manipulation
* @param obj
* @param value
* @param propArray
*/
static updateObjProp(obj, value, propArray){
let [head, ...rest] = propArray;
if(!rest.length){
obj[head] = value;
}else{
if(!LocalStore.isObject(obj[head])) obj[head] = {};
LocalStore.updateObjProp(obj[head], value, rest);
}
}
/**
* delete object prop by propArray path
* -> deep object delete
* @param obj
* @param propArray
*/
static deleteObjProp(obj, propArray){
let [head, ...rest] = propArray;
if(!rest.length){
delete obj[head];
}else{
if(LocalStore.isObject(obj[head])){
LocalStore.deleteObjProp(obj[head], rest);
}
}
}
/**
* converts string key to array
* @param propPath
* @returns {*|string[]}
*/
static keyToArray(propPath){
return propPath.split('.');
}
/**
* build DB name
* @param name
* @returns {string}
*/
static buildDbName(name){
return [LocalStore.dbNamePrefix, name].join(' ');
}
/**
* check var for Object
* @param obj
* @returns {boolean|boolean}
*/
static isObject(obj){
return (!!obj) && (obj.constructor === Object);
}
/**
* check var for Array
* @param arr
* @returns {boolean}
*/
static isArray(arr){
return (!!arr) && (arr.constructor === Array);
}
/**
* check var for String
* @param str
* @returns {boolean}
*/
static isString(str){
return typeof str === 'string';
}
}
LocalStore.defaultConfig = {
name: 'default', // custom unique name for identification
debug: false
};
LocalStore.dbNamePrefix = 'PathfinderDB';
LocalStore.LocalForageConfig = {
driver: [LocalForage.INDEXEDDB, LocalForage.WEBSQL, LocalForage.LOCALSTORAGE],
name: LocalStore.dbNamePrefix
};
/**
* An instance of LocalStoreManager() handles multiple LocalStore()´s
* -> LocalStore()´s can be set()/delete() from LocalStore() instance
*/
class LocalStoreManager {
constructor(){
if(!this.constructor.instance){
this._store = new Map();
this.constructor.instance = this;
}
return this.constructor.instance;
}
/**
* get LocalStore instance by name
* @param name
* @returns {LocalStore}
*/
getStore(name){
return this.newStore(name);
}
/**
* get either existing LocalStore instance
* -> or create new instance
* @param name
* @returns {LocalStore|undefined}
*/
newStore(name){
if(!this._store.has(name)){
let store = new LocalStore({
name: name
}, {
name: LocalStore.buildDbName(name)
});
store.setManager(this);
this._store.set(name, store);
}
return this._store.get(name);
}
/**
* removes LocalStore instance from Manager
* -> this will not drop LocalForage instance!
* check LocalStore.dropInstance() for graceful delete
* @param name
* @returns {boolean}
*/
deleteStore(name){
return this._store.delete(name);
}
}
return new LocalStoreManager();
});

View File

@@ -1,6 +1,44 @@
define([], () => {
define([
'app/lib/dataStore'
], (DataStore) => {
'use strict';
// DOM node data store ============================================================================================
window.dataStore = new DataStore();
/**
* @param key
* @param value
* @returns {HTMLElement}
*/
HTMLElement.prototype.setData = function(key, value){
return window.dataStore.set(this, key, value);
};
/**
* @param key
* @returns {*}
*/
HTMLElement.prototype.getData = function(key){
return window.dataStore.get(this, key);
};
/**
* @param key
* @returns {*}
*/
HTMLElement.prototype.hasData = function(key){
return window.dataStore.has(this, key);
};
/**
* @param key
* @returns {*}
*/
HTMLElement.prototype.removeData = function(key){
return window.dataStore.remove(this, key);
};
/**
* Array diff
* [1,2,3,4,5].diff([4,5,6]) => [1,2,3]

View File

@@ -37,6 +37,7 @@ define([
if(logDialog.length){
// dialog is open
let statusArea = logDialog.find('.' + config.taskDialogStatusAreaClass);
statusArea.destroyTooltips(true);
requirejs(['text!templates/modules/sync_status.html', 'mustache'], (templateSyncStatus, Mustache) => {
let data = {
timestampCounterClass: config.timestampCounterClass,
@@ -57,7 +58,7 @@ define([
let counterElements = syncStatusElement.find('.' + config.timestampCounterClass);
Counter.initTimestampCounter(counterElements);
syncStatusElement.initTooltips({
statusArea.initTooltips({
placement: 'right'
});
});
@@ -92,9 +93,9 @@ define([
// 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>>',
dom: '<"flex-row flex-between"<"flex-col"l><"flex-col"B><"flex-col"fS>>' +
'<"flex-row"<"flex-col flex-grow"tr>>' +
'<"flex-row flex-between"<"flex-col"i><"flex-col"p>>',
buttons: {
name: 'tableTools',
buttons: [
@@ -286,23 +287,24 @@ define([
parseTime: false,
ymin: 0,
yLabelFormat: labelYFormat,
padding: 10,
padding: 8,
hideHover: true,
pointSize: 3,
pointSize: 2.5,
lineColors: ['#375959'],
pointFillColors: ['#477372'],
pointStrokeColors: ['#313335'],
lineWidth: 2,
grid: false,
lineWidth: 1.5,
grid: true,
gridStrokeWidth: 0.3,
gridTextSize: 9,
gridTextFamily: 'Oxygen Bold',
gridTextColor: '#63676a',
behaveLikeLine: true,
behaveLikeLine: false,
goals: [],
goalStrokeWidth: 1,
goalLineColors: ['#66c84f'],
smooth: false,
fillOpacity: 0.3,
fillOpacity: 0.2,
resize: true
});

View File

@@ -529,16 +529,16 @@ define([
setVersionLinkObserver();
// mark panel as "shown"
Util.getLocalStorage().setItem(storageKey, currentVersion);
Util.getLocalStore('default').setItem(storageKey, currentVersion);
}
});
});
};
Util.getLocalStorage().getItem(storageKey).then(function(data){
Util.getLocalStore('default').getItem(storageKey).then(data => {
// check if panel was shown before
if(data){
if(data !== this.version){
if(data !== currentVersion){
// show current panel
showNotificationPanel();
}
@@ -546,9 +546,7 @@ define([
// show current panel
showNotificationPanel();
}
}.bind({
version: currentVersion
}));
});
};
/**

View File

@@ -10,6 +10,7 @@ define([
'use strict';
let config = {
contextMenuContainerId: 'pf-contextmenu-container', // id for container element that holds (hidden) context menus
mapContextMenuId: 'pf-map-contextmenu', // id for "maps" context menu
connectionContextMenuId: 'pf-map-connection-contextmenu', // id for "connections" context menu
endpointContextMenuId: 'pf-map-endpoint-contextmenu', // id for "endpoints" context menu

View File

@@ -100,7 +100,7 @@ define([
let isOpenStatus = isOpen(overlayMain);
// store current state in indexDB (client)
MapUtil.storeLocalData('map', mapId, 'showLocal', !isOpenStatus );
Util.getLocalStore('map').setItem(`${mapId}.showLocal`, !isOpenStatus);
// trigger open/close
if( isOpenStatus ){
@@ -111,8 +111,8 @@ define([
});
// trigger table re-draw() ------------------------------------------------------------------------------------
let mapWrapper = overlay.parents('.' + MapUtil.config.mapWrapperClass);
mapWrapper.on('pf:mapResize', function(e){
let areaMap = overlay.closest('.' + Util.getMapTabContentAreaClass('map'));
areaMap.on('pf:mapResize', function(e){
let tableElement = overlay.find('.' + config.overlayLocalTableClass);
let tableApi = tableElement.DataTable();
tableApi.draw('full-hold');
@@ -120,7 +120,6 @@ define([
// tooltips ---------------------------------------------------------------------------------------------------
overlayMain.initTooltips({
container: 'body',
placement: 'bottom'
});
};
@@ -247,8 +246,7 @@ define([
// open Overlay -------------------------------------------------------------------------------------------
if( !isOpen(overlay) ){
let promiseStore = MapUtil.getLocaleData('map', mapId);
promiseStore.then(dataStore => {
Util.getLocalStore('map').getItem(mapId).then(dataStore => {
if(
dataStore &&
dataStore.showLocal
@@ -358,12 +356,12 @@ define([
// init local table ---------------------------------------------------------------------------------------
table.on('preDraw.dt', function(e, settings){
let table = $(this);
let mapWrapper = table.parents('.' + MapUtil.config.mapWrapperClass);
let areaMap = table.closest('.' + Util.getMapTabContentAreaClass('map'));
// mapWrapper should always exist
if(mapWrapper && mapWrapper.length) {
// areaMap should always exist
if(areaMap && areaMap.length) {
// check available maxHeight for "locale" table based on current map height (resizable)
let mapHeight = mapWrapper[0].offsetHeight;
let mapHeight = areaMap[0].offsetHeight;
let localOverlay = MapOverlayUtil.getMapOverlay(table, 'local');
let paginationElement = localOverlay.find('.dataTables_paginate');
@@ -401,7 +399,6 @@ define([
table.on('draw.dt', function(e, settings){
// init table tooltips
$(this).find('td').initTooltips({
container: 'body',
placement: 'left'
});
});
@@ -410,7 +407,6 @@ define([
table.on('init.dt', function(){
// init table head tooltips
$(this).initTooltips({
container: 'body',
placement: 'top'
});
});

View File

@@ -27,9 +27,6 @@ define([
zIndexCounter: 110,
maxActiveConnections: 8,
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapClass: 'pf-map', // class for all maps
mapIdPrefix: 'pf-map-', // id prefix for all maps
systemClass: 'pf-system', // class for all systems
systemActiveClass: 'pf-system-active', // class for an active system on a map
@@ -45,7 +42,6 @@ define([
systemBodyItemStatusClass: 'pf-user-status', // class for player status in system body
systemBodyItemNameClass: 'pf-system-body-item-name', // class for player name in system body
systemBodyRightClass: 'pf-system-body-right', // class for player ship name in system body
dynamicElementWrapperId: 'pf-dialog-wrapper', // wrapper div for dynamic content (dialogs, context-menus,...)
// endpoint classes
endpointSourceClass: 'pf-map-endpoint-source',
@@ -578,7 +574,7 @@ define([
* @param system
*/
let systemActions = (action, system) => {
let mapContainer = system.closest('.' + config.mapClass);
let mapContainer = system.closest('.' + Util.config.mapClass);
let map = MapUtil.getMapInstance(system.attr('data-mapid'));
let systemData = {};
@@ -685,8 +681,7 @@ define([
let filterScope = action.split('_')[1];
let filterScopeLabel = MapUtil.getScopeInfoForConnection( filterScope, 'label');
let promiseStore = MapUtil.getLocaleData('map', mapId);
promiseStore.then(data => {
Util.getLocalStore('map').getItem(mapId).then(data => {
let filterScopes = [];
if(data && data.filterScopes){
filterScopes = data.filterScopes;
@@ -704,7 +699,7 @@ define([
}
// store filterScopes in IndexDB
MapUtil.storeLocalData('map', mapId, 'filterScopes', filterScopes);
Util.getLocalStore('map').setItem(`${mapId}.filterScopes`, filterScopes);
MapUtil.filterMapByScopes(map, filterScopes);
Util.showNotify({title: 'Scope filter changed', text: filterScopeLabel, type: 'success'});
@@ -1085,17 +1080,17 @@ define([
};
/**
* set map wrapper observer
* @param mapWrapper
* set map area observer
* @param areaMap
* @param mapConfig
*/
let setMapWrapperObserver = (mapWrapper, mapConfig) => {
let setMapAreaObserver = (areaMap, mapConfig) => {
/**
* save current map dimension to local storage
* @param entry
*/
let saveMapSize = (entry) => {
let saveMapSize = entry => {
let width = '';
let height = '';
if(entry.constructor.name === 'HTMLDivElement'){
@@ -1109,10 +1104,9 @@ define([
width = parseInt(width.substring(0, width.length - 2)) || 0;
height = parseInt(height.substring(0, height.length - 2)) || 0;
mapWrapper.trigger('pf:mapResize');
areaMap.trigger('pf:mapResize');
let promiseStore = MapUtil.getLocaleData('map', mapConfig.config.id );
promiseStore.then((data) => {
Util.getLocalStore('map').getItem(mapConfig.config.id).then((data) => {
let storeData = true;
if(
@@ -1125,7 +1119,7 @@ define([
}
if(storeData){
MapUtil.storeLocalData('map', mapConfig.config.id, 'style', {
Util.getLocalStore('map').setItem(`${mapConfig.config.id}.style`, {
width: width,
height: height
});
@@ -1148,7 +1142,7 @@ define([
}
});
wrapperResize.observe(mapWrapper[0]);
wrapperResize.observe(areaMap[0]);
}else if(requestAnimationFrame){
// ResizeObserver() not supported
let checkMapSize = (entry) => {
@@ -1156,17 +1150,18 @@ define([
return setTimeout(checkMapSize, 500, entry);
};
checkMapSize(mapWrapper[0]);
checkMapSize(areaMap[0]);
}
};
/**
* get a mapMapElement
* @param parentElement
* @param areaMap
* @param mapConfig
* @returns {Promise<any>}
*/
let newMapElement = (parentElement, mapConfig) => {
let newMapElement = (areaMap, mapConfig) => {
areaMap = $(areaMap);
/**
* new map element promise
@@ -1175,45 +1170,37 @@ define([
*/
let newMapElementExecutor = (resolve, reject) => {
// get map dimension from local storage
let promiseStore = MapUtil.getLocaleData('map', mapConfig.config.id );
promiseStore.then((data) => {
Util.getLocalStore('map').getItem(mapConfig.config.id).then(data => {
let height = 0;
if(data && data.style){
height = data.style.height;
}
// create map wrapper
let mapWrapper = $('<div>', {
class: config.mapWrapperClass,
height: height
});
areaMap.css('height', height);
setMapWrapperObserver(mapWrapper, mapConfig);
setMapAreaObserver(areaMap, mapConfig);
let mapId = mapConfig.config.id;
// create new map container
let mapContainer = $('<div>', {
id: config.mapIdPrefix + mapId,
class: config.mapClass
class: Util.config.mapClass
}).data('id', mapId);
mapWrapper.append(mapContainer);
// append mapWrapper to parent element (at the top)
parentElement.prepend(mapWrapper);
areaMap.append(mapContainer);
// set main Container for current map -> the container exists now in DOM !! very important
mapConfig.map.setContainer(mapContainer);
// init custom scrollbars and add overlay
initMapScrollbar(mapWrapper);
initMapScrollbar(areaMap);
// set map observer
setMapObserver(mapConfig.map);
// set shortcuts
mapWrapper.setMapShortcuts();
areaMap.setMapShortcuts();
// show static overlay actions
let mapOverlay = MapOverlayUtil.getMapOverlay(mapContainer, 'info');
@@ -1417,8 +1404,7 @@ define([
*/
let filterMapByScopes = payload => {
let filterMapByScopesExecutor = resolve => {
let promiseStore = MapUtil.getLocaleData('map', payload.data.mapConfig.config.id);
promiseStore.then(dataStore => {
Util.getLocalStore('map').getItem(payload.data.mapConfig.config.id).then(dataStore => {
let scopes = [];
if(dataStore && dataStore.filterScopes){
scopes = dataStore.filterScopes;
@@ -1439,8 +1425,7 @@ define([
*/
let showInfoSignatureOverlays = payload => {
let showInfoSignatureOverlaysExecutor = resolve => {
let promiseStore = MapUtil.getLocaleData('map', payload.data.mapConfig.config.id);
promiseStore.then(dataStore => {
Util.getLocalStore('map').getItem(payload.data.mapConfig.config.id).then(dataStore => {
if(dataStore && dataStore.mapSignatureOverlays){
MapOverlay.showInfoSignatureOverlays($(payload.data.mapConfig.map.getContainer()));
}
@@ -1625,7 +1610,6 @@ define([
title: 'System alias',
placement: 'top',
onblur: 'submit',
container: 'body',
toggle: 'manual', // is triggered manually on dblClick
showbuttons: false
});
@@ -1751,7 +1735,7 @@ define([
options.id = MapContextMenu.config.systemContextMenuId;
options.selectCallback = systemActions;
let mapContainer = system.closest('.' + config.mapClass);
let mapContainer = system.closest('.' + Util.config.mapClass);
// hidden menu actions
if(system.data('locked') === true){
@@ -1795,8 +1779,7 @@ define([
let mapContainer = $(map.getContainer());
// active menu actions
let promiseStore = MapUtil.getLocaleData('map', mapContainer.data('id'));
promiseStore.then(dataStore => {
Util.getLocalStore('map').getItem(mapContainer.data('id')).then(dataStore => {
if(dataStore && dataStore.filterScopes){
options.active = dataStore.filterScopes.map(scope => 'filter_' + scope);
}
@@ -2005,10 +1988,9 @@ define([
let systemTooltipOptions = {
toggle: 'tooltip',
placement: 'right',
container: 'body',
viewport: system.id
};
system.find('.fas').tooltip(systemTooltipOptions);
//system.find('.fas').tooltip(systemTooltipOptions);
// system click events ========================================================================================
let double = function(e){
@@ -2316,9 +2298,9 @@ define([
// store new zoom level in IndexDB
if(zoom === 1){
MapUtil.deleteLocalData('map', mapId, 'mapZoom');
Util.getLocalStore('map').removeItem(`${mapId}.mapZoom`);
}else{
MapUtil.storeLocalData('map', mapId, 'mapZoom', zoom);
Util.getLocalStore('map').setItem(`${mapId}.mapZoom`, zoom);
}
});
@@ -2406,7 +2388,7 @@ define([
e.stopPropagation();
// make sure map is clicked and NOT a connection
if($(e.target).hasClass(config.mapClass)){
if($(e.target).hasClass(Util.config.mapClass)){
getContextMenuConfig(map).then(payload => {
let context = {
component: map
@@ -2532,6 +2514,20 @@ define([
selector: '.' + config.systemClass + ' .' + config.systemHeadExpandClass
});
mapContainer.hoverIntent({
over: function(e){
$(this).tooltip({
trigger: 'manual',
placement: 'right',
viewport: this.closest(`.${config.systemClass}`)
}).tooltip('show');
},
out: function(e){
$(this).tooltip('destroy');
},
selector: `.${config.systemClass} .fas[title]`
});
// system "active users" popover ------------------------------------------------------------------------------
mapContainer.hoverIntent({
over: function(e){
@@ -2611,8 +2607,7 @@ define([
// get map menu config options
let mapOption = mapOptions[data.option];
let promiseStore = MapUtil.getLocaleData('map', mapContainer.data('id'));
promiseStore.then(function(dataStore){
Util.getLocalStore('map').getItem(mapContainer.data('id')).then(function(dataStore){
let notificationText = 'disabled';
let button = $('#' + this.mapOption.buttonId);
let dataExists = false;
@@ -2643,7 +2638,7 @@ define([
MapOverlayUtil.getMapOverlay(this.mapContainer, 'info').updateOverlayIcon(this.data.option, 'hide');
// delete map option
MapUtil.deleteLocalData('map', this.mapContainer.data('id'), this.data.option);
Util.getLocalStore('map').removeItem(`${this.mapContainer.data('id')}.${this.data.option}`);
}else{
// toggle button class
button.addClass('active');
@@ -2662,7 +2657,7 @@ define([
MapOverlayUtil.getMapOverlay(this.mapContainer, 'info').updateOverlayIcon(this.data.option, 'show');
// store map option
MapUtil.storeLocalData('map', this.mapContainer.data('id'), this.data.option, 1);
Util.getLocalStore('map').setItem(`${this.mapContainer.data('id')}.${this.data.option}`, 1);
notificationText = 'enabled';
}
@@ -2701,8 +2696,8 @@ define([
}
if(select){
let mapWrapper = mapContainer.closest('.' + config.mapWrapperClass);
Scrollbar.scrollToCenter(mapWrapper, system);
let areaMap = mapContainer.closest('.' + Util.getMapTabContentAreaClass('map'));
Scrollbar.scrollToCenter(areaMap, system);
// select system
MapUtil.showSystemInfo(map, system);
}
@@ -3135,12 +3130,12 @@ define([
/**
* load OR updates system map
* @param tabContentElement parent element where the map will be loaded
* @param areaMap parent element where the map will be loaded
* @param mapConfig
* @param options
* @returns {Promise<any>}
*/
let loadMap = (tabContentElement, mapConfig, options) => {
let loadMap = (areaMap, mapConfig, options) => {
/**
* load map promise
@@ -3155,7 +3150,7 @@ define([
if(mapConfig.map.getContainer() === undefined){
// map not loaded -> create & update
newMapElement(tabContentElement, mapConfig)
newMapElement(areaMap, mapConfig)
.then(payload => updateMap(payload.data.mapConfig))
.then(payload => resolve(payload));
}else{
@@ -3172,14 +3167,14 @@ define([
/**
* init scrollbar for Map element
* @param mapWrapper
* @param areaMap
*/
let initMapScrollbar = mapWrapper => {
let mapElement = mapWrapper.find('.' + config.mapClass);
let initMapScrollbar = areaMap => {
let mapElement = areaMap.find('.' + Util.config.mapClass);
let mapId = mapElement.data('id');
let dragSelect;
Scrollbar.initScrollbar(mapWrapper, {
Scrollbar.initScrollbar(areaMap, {
callbacks: {
onInit: function(){
let scrollWrapper = this;
@@ -3215,7 +3210,7 @@ define([
let animationFrameId = 0;
let toggleDragScroll = active => {
mapElement.toggleClass('disabled', active).toggleClass(' pf-map-move', active);
mapElement.toggleClass('disabled', active).toggleClass('pf-map-move', active);
};
let stopDragScroll = () => {
@@ -3303,7 +3298,7 @@ define([
mapElement.attr('data-scroll-top', this.mcs.top);
// store new map scrollOffset -> localDB
MapUtil.storeLocalData('map', mapId, 'scrollOffset', {
Util.getLocalStore('map').setItem(`${mapId}.scrollOffset`, {
x: Math.abs(this.mcs.left),
y: Math.abs(this.mcs.top)
});
@@ -3326,8 +3321,8 @@ define([
// ------------------------------------------------------------------------------------------------------------
// add map overlays after scrollbar is initialized
// because of its absolute position
mapWrapper.initMapOverlays();
mapWrapper.initLocalOverlay(mapId);
areaMap.initMapOverlays();
areaMap.initLocalOverlay(mapId);
};
return {

View File

@@ -17,7 +17,7 @@ define([
* @returns {*}
*/
let getMapObjectFromOverlayIcon = overlayIcon => {
return MapUtil.getMapInstance(Util.getMapElementFromOverlay(overlayIcon).data('id'));
return MapUtil.getMapInstance(MapOverlayUtil.getMapElementFromOverlay(overlayIcon).data('id'));
};
/**
@@ -323,7 +323,7 @@ define([
iconClass: ['fas', 'fa-fw', 'fa-filter'],
onClick: function(e){
// clear all filter
let mapElement = Util.getMapElementFromOverlay(this);
let mapElement = MapOverlayUtil.getMapElementFromOverlay(this);
let map = getMapObjectFromOverlayIcon(this);
MapUtil.storeLocalData('map', mapElement.data('id'), 'filterScopes', []);
@@ -349,7 +349,7 @@ define([
iconClass: ['fas', 'fa-fw', 'fa-tags'],
hoverIntent: {
over: function(e){
let mapElement = Util.getMapElementFromOverlay(this);
let mapElement = MapOverlayUtil.getMapElementFromOverlay(this);
mapElement.find('.' + MapOverlayUtil.config.systemHeadClass).each(function(){
let systemHead = $(this);
// init popover if not already exists
@@ -373,7 +373,7 @@ define([
});
},
out: function(e){
let mapElement = Util.getMapElementFromOverlay(this);
let mapElement = MapOverlayUtil.getMapElementFromOverlay(this);
mapElement.find('.' + MapOverlayUtil.config.systemHeadClass).popover('hide');
}
}
@@ -556,7 +556,7 @@ define([
duration: Init.animationSpeed.mapOverlay,
complete: function(){
counterChart.data('interval', false);
Util.getMapElementFromOverlay(mapOverlayTimer).trigger('pf:unlocked');
MapOverlayUtil.getMapElementFromOverlay(mapOverlayTimer).trigger('pf:unlocked');
}
});
}

View File

@@ -13,8 +13,6 @@ define([
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
@@ -53,27 +51,36 @@ define([
* @returns {null}
*/
let getMapOverlay = (element, overlayType) => {
let mapWrapperElement = $(element).parents('.' + config.mapWrapperClass);
let areaMap = $(element).closest('.' + Util.getMapTabContentAreaClass('map'));
let mapOverlay = null;
switch(overlayType){
case 'timer':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayTimerClass);
mapOverlay = areaMap.find('.' + config.mapOverlayTimerClass);
break;
case 'info':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayInfoClass);
mapOverlay = areaMap.find('.' + config.mapOverlayInfoClass);
break;
case 'zoom':
mapOverlay = mapWrapperElement.find('.' + config.mapOverlayZoomClass);
mapOverlay = areaMap.find('.' + config.mapOverlayZoomClass);
break;
case 'local':
mapOverlay = mapWrapperElement.find('.' + config.overlayLocalClass);
mapOverlay = areaMap.find('.' + config.overlayLocalClass);
break;
}
return mapOverlay;
};
/**
* get mapElement from overlay or any child of that
* @param mapOverlay
* @returns {jQuery}
*/
let getMapElementFromOverlay = mapOverlay => {
return $(mapOverlay).closest('.' + Util.getMapTabContentAreaClass('map')).find('.' + Util.config.mapClass);
};
/**
* get the map counter chart from overlay
* @param element
@@ -91,6 +98,7 @@ define([
return {
config: config,
getMapOverlay: getMapOverlay,
getMapElementFromOverlay: getMapElementFromOverlay,
getMapCounter: getMapCounter,
getMapOverlayInterval: getMapOverlayInterval
};

View File

@@ -226,25 +226,25 @@ define([
/**
* scroll to a specific position on map
* demo: http://manos.malihu.gr/repository/custom-scrollbar/demo/examples/scrollTo_demo.html
* @param scrollWrapper
* @param scrollArea
* @param position
* @param options
*/
let scrollToPosition = (scrollWrapper, position, options) => {
$(scrollWrapper).mCustomScrollbar('scrollTo', position, options);
let scrollToPosition = (scrollArea, position, options) => {
$(scrollArea).mCustomScrollbar('scrollTo', position, options);
};
/**
* scroll to center an element
* -> subtract some offset for tooltips/connections
* @param scrollWrapper
* @param scrollArea
* @param element
*/
let scrollToCenter = (scrollWrapper, element) => {
let scrollToCenter = (scrollArea, element) => {
// no scroll if element is already FULL visible in scrollable viewport
if(!isInView(element)){
// get scrollTo position for centered element
scrollToPosition(scrollWrapper, getCenterScrollPosition(element));
scrollToPosition(scrollArea, getCenterScrollPosition(element));
}
};

View File

@@ -19,8 +19,6 @@ define([
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
@@ -618,7 +616,7 @@ define([
html: true,
animation: true,
template: template,
viewport: system.closest('.' + config.mapClass)
viewport: system.closest('.' + Util.config.mapClass)
};
// init new tooltip -> Do not show automatic maybe system is currently dragged

View File

@@ -18,14 +18,6 @@ define([
zoomMax: 1.5,
zoomMin: 0.5,
// local storage
characterLocalStoragePrefix: 'character_', // prefix for character data local storage key
mapLocalStoragePrefix: 'map_', // prefix for map data local storage key
mapTabContentClass: 'pf-map-tab-content', // Tab-Content element (parent element)
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapClass: 'pf-map', // class for all maps
mapGridClass: 'pf-grid-small', // class for map grid snapping
mapCompactClass: 'pf-compact', // class for map compact system UI
@@ -500,7 +492,7 @@ define([
connectionData &&
connectionData.signatures // signature data is required...
){
let SystemSignatures = require('module/system_signature');
let SystemSignatureModule = require('module/system_signature');
let sourceEndpoint = connection.endpoints[0];
let targetEndpoint = connection.endpoints[1];
@@ -531,7 +523,7 @@ define([
// ... get endpoint label for source || target system
if(tmpSystem && tmpSystem){
// ... get all available signature type (wormholes) names
let availableSigTypeNames = SystemSignatures.getSignatureTypeOptionsBySystem(tmpSystem, 5);
let availableSigTypeNames = SystemSignatureModule.getSignatureTypeOptionsBySystem(tmpSystem, 5);
let flattenSigTypeNames = Util.flattenXEditableSelectArray(availableSigTypeNames);
if(flattenSigTypeNames.hasOwnProperty(signatureData.typeId)){
@@ -609,7 +601,7 @@ define([
* @param element
* @returns {*}
*/
let getTabContentElementByMapElement = element => $(element).closest('.' + config.mapTabContentClass);
let getTabContentElementByMapElement = element => $(element).closest('.' + Util.config.mapTabContentClass);
/**
* checks if there is an "active" connection on a map
@@ -753,11 +745,11 @@ define([
'height': scrollableHeight ? scaledHeight + 'px' : (wrapperHeight) + 'px',
});
let mapWrapperElement = mapContainer.closest('.mCustomScrollbar');
let areaMap = mapContainer.closest('.mCustomScrollbar');
if(scrollableWidth && scrollableHeight){
mapWrapperElement.mCustomScrollbar('update');
areaMap.mCustomScrollbar('update');
}else{
mapWrapperElement.mCustomScrollbar('scrollTo', '#' + mapContainer.attr('id'), {
areaMap.mCustomScrollbar('scrollTo', '#' + mapContainer.attr('id'), {
scrollInertia: 0,
scrollEasing: 'linear',
timeout: 0,
@@ -1001,7 +993,12 @@ define([
setSystemActive(map, system);
// get parent Tab Content and fire update event
getTabContentElementByMapElement(system).trigger('pf:drawSystemModules');
let mapContainer = $(map.getContainer());
getTabContentElementByMapElement(mapContainer).trigger('pf:renderSystemModules', {
mapId: parseInt(mapContainer.data('id')),
payload: Util.getCurrentSystemData()
});
};
/**
@@ -1015,9 +1012,9 @@ define([
// get parent Tab Content and fire update event
let mapContainer = $(map.getContainer());
getTabContentElementByMapElement(mapContainer).trigger('pf:drawConnectionModules', {
connections: connections,
mapId: parseInt(mapContainer.data('id'))
getTabContentElementByMapElement(mapContainer).trigger('pf:renderConnectionModules', {
mapId: parseInt(mapContainer.data('id')),
payload: connections
});
};
@@ -1046,9 +1043,11 @@ define([
let showFindRouteDialog = (mapContainer, systemToData) => {
// get parent Tab Content and fire update event
getTabContentElementByMapElement(mapContainer).trigger('pf:updateRouteModules', {
task: 'showFindRouteDialog',
systemToData: systemToData,
mapId: parseInt(mapContainer.data('id'))
mapId: parseInt(mapContainer.data('id')),
payload: {
task: 'showFindRouteDialog',
systemToData: systemToData
}
});
};
@@ -1059,9 +1058,11 @@ define([
*/
let findRoute = (mapContainer, systemToData) => {
getTabContentElementByMapElement(mapContainer).trigger('pf:updateRouteModules', {
task: 'findRoute',
systemToData: systemToData,
mapId: parseInt(mapContainer.data('id'))
mapId: parseInt(mapContainer.data('id')),
payload: {
task: 'findRoute',
systemToData: systemToData
}
});
};
@@ -1267,84 +1268,6 @@ define([
return scopeInfo;
};
/**
* store local data for current user (IndexDB)
* @param key
* @param value
*/
let storeLocaleCharacterData = (key, value) => {
if(key.length && value){
let userData = Util.getCurrentUserData();
if(
userData &&
userData.character
){
storeLocalData('character', userData.character.id, key, value);
}
}
};
/**
* get key prefix for local storage data
* @param type
* @returns {boolean}
*/
let getLocalStoragePrefixByType = (type) => {
let prefix = false;
switch(type){
case 'character': prefix = config.characterLocalStoragePrefix; break;
case 'map': prefix = config.mapLocalStoragePrefix; break;
default: prefix = config.mapLocalStoragePrefix;
}
return prefix;
};
/**
* get stored local data from client cache (IndexedDB)
* @param type
* @param objectId
* @returns {*}
*/
let getLocaleData = (type, objectId) => {
if(objectId > 0){
let storageKey = getLocalStoragePrefixByType(type) + objectId;
return Util.getLocalStorage().getItem(storageKey);
}else{
console.warn('Local storage requires object id > 0');
}
};
/**
* store local config data to client cache (IndexedDB)
* @param type
* @param objectId
* @param key
* @param value
*/
let storeLocalData = (type, objectId, key, value) => {
if(objectId > 0){
// get current map config
let storageKey = getLocalStoragePrefixByType(type) + objectId;
Util.getLocalStorage().getItem(storageKey).then(function(data){
// This code runs once the value has been loaded
// from the offline store.
data = (data === null) ? {} : data;
// set/update value
data[this.key] = this.value;
Util.getLocalStorage().setItem(this.storageKey, data);
}.bind({
key: key,
value: value,
storageKey: storageKey
})).catch(function(err){
// This code runs if there were any errors
console.error('Map local storage can not be accessed!');
});
}else{
console.warn('storeLocalData(): Local storage requires object id > 0');
}
};
/**
* show map animations when a new map gets visual
* @param mapElement
@@ -1531,11 +1454,10 @@ define([
// -> implementation would be difficult...
if(map.getZoom() === 1){
let mapElement = $(map.getContainer());
let promiseStore = getLocaleData('map', mapElement.data('id'));
promiseStore.then(data => {
Util.getLocalStore('map').getItem(mapElement.data('id')).then(data => {
if(data && data.scrollOffset){
let mapWrapper = mapElement.parents('.' + config.mapWrapperClass);
Scrollbar.scrollToPosition(mapWrapper, [data.scrollOffset.y, data.scrollOffset.x]);
let areaMap = mapElement.closest('.' + Util.getMapTabContentAreaClass('map'));
Scrollbar.scrollToPosition(areaMap, [data.scrollOffset.y, data.scrollOffset.x]);
}
resolve(payload);
@@ -1557,8 +1479,7 @@ define([
let zoomToDefaultScaleExecutor = resolve => {
let mapElement = $(map.getContainer());
let promiseStore = getLocaleData('map', mapElement.data('id'));
promiseStore.then(data => {
Util.getLocalStore('map').getItem(mapElement.data('id')).then(data => {
if(data && data.mapZoom){
setZoom(map, data.mapZoom);
}
@@ -1573,32 +1494,6 @@ define([
return new Promise(zoomToDefaultScaleExecutor);
};
/**
* delete local map configuration by key (IndexedDB)
* @param type
* @param objectId
* @param key
*/
let deleteLocalData = (type, objectId, key) => {
if(objectId > 0){
// get current map config
let storageKey = getLocalStoragePrefixByType(type) + objectId;
Util.getLocalStorage().getItem(storageKey).then(function(data){
if(
data &&
data.hasOwnProperty(key)
){
delete data[key];
Util.getLocalStorage().setItem(this.storageKey, data);
}
}.bind({
storageKey: storageKey
}));
}else{
console.warn('deleteLocalData(): Local storage requires object id > 0');
}
};
/**
* set or change rallyPoint for systems
* @param rallyUpdated
@@ -1650,8 +1545,7 @@ define([
// check if desktop notification was already send
let mapId = system.data('mapid');
let systemId = system.data('id');
let promiseStore = getLocaleData('map', mapId);
promiseStore.then(function(data){
Util.getLocalStore('map').getItem(mapId).then(function(data){
// This code runs once the value has been loaded
// from the offline store.
let rallyPokeData = {};
@@ -1669,7 +1563,7 @@ define([
rallyPokeData[this.systemId] !== rallyUpdated // already send to that system but in the past
){
rallyPokeData[this.systemId] = rallyUpdated;
storeLocalData('map', this.mapId, 'rallyPoke', rallyPokeData);
Util.getLocalStore('map').setItem(`${this.mapId}.rallyPoke`, rallyPokeData);
notificationOptions.type = 'info';
Util.showNotify(notificationOptions, {
@@ -1686,7 +1580,7 @@ define([
}
// update active "route" module -> add rally point row --------------------------------------------
let mapContainer = system.parents('.' + config.mapClass);
let mapContainer = system.parents('.' + Util.config.mapClass);
findRoute(mapContainer, {
systemId: system.data('systemId'),
name: system.data('name'),
@@ -1711,28 +1605,27 @@ define([
* set map "shortcut" events
*/
$.fn.setMapShortcuts = function(){
return this.each((i, mapWrapper) => {
mapWrapper = $(mapWrapper);
let mapElement = mapWrapper.find('.' + config.mapClass);
return this.each((i, areaMap) => {
areaMap = $(areaMap);
let mapElement = areaMap.find('.' + Util.config.mapClass);
// dynamic require Map module -> otherwise there is a require(), loop
let Map = require('app/map/map');
let System = require('app/map/system');
let map = Map.getMapInstance( mapElement.data('id'));
let map = Map.getMapInstance(mapElement.data('id'));
mapWrapper.watchKey('mapSystemAdd', (mapWrapper) => {
areaMap.watchKey('mapSystemAdd', areaMap => {
System.showNewSystemDialog(map, {position: {x: 0, y: 0}}, Map.saveSystemCallback);
},{focus: true});
mapWrapper.watchKey('mapSystemsSelect', (mapWrapper) => {
areaMap.watchKey('mapSystemsSelect', areaMap => {
mapElement.selectAllSystems();
},{focus: true});
mapWrapper.watchKey('mapSystemsDelete', (mapWrapper) => {
areaMap.watchKey('mapSystemsDelete', areaMap => {
let selectedSystems = mapElement.getSelectedSystems();
$.fn.showDeleteSystemDialog(map, selectedSystems);
},{focus: true});
});
};
@@ -2309,10 +2202,6 @@ define([
changeZoom: changeZoom,
setZoom: setZoom,
toggleSystemAliasEditable: toggleSystemAliasEditable,
storeLocaleCharacterData: storeLocaleCharacterData,
getLocaleData: getLocaleData,
storeLocalData: storeLocalData,
deleteLocalData: deleteLocalData,
visualizeMap: visualizeMap,
setMapDefaultOptions: setMapDefaultOptions,
getSystemPosition: getSystemPosition,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
define([
'jquery',
'app/init',
'pnotify',
'PNotify',
//'pnotify.buttons',
//'pnotify.confirm',
'pnotify.nonblock',
'pnotify.desktop',
'PNotifyNonBlock',
'PNotifyDesktop',
//'pnotify.history',
'pnotify.callbacks'
'PNotifyCallbacks'
], ($, Init, PNotify) => {
'use strict';
@@ -142,6 +142,17 @@ define([
notify.get().on('click', settings.click);
}
PNotify.defaults.styling = 'bootstrap3';
PNotify.defaults.icons = 'fontawesome5';
PNotify.defaults.delay = 5000;
PNotify.defaults.width = '250px';
PNotify.notice({
text: "I'm a notice."
});
};
/**

View File

@@ -60,9 +60,6 @@ define([
// menu
menuHeadMenuLogoClass: 'pf-head-menu-logo', // class for main menu logo
// helper element
dynamicElementWrapperId: 'pf-dialog-wrapper', // class for container element that holds hidden "context menus"
// system signature module
systemSignatureModuleClass: 'pf-system-signature-module', // module wrapper (signatures)
systemIntelModuleClass: 'pf-system-intel-module', // module wrapper (intel)
@@ -110,7 +107,7 @@ define([
* @param element
*/
let initHeaderTooltips = element => {
element.find('[title]').tooltip({
element.initTooltips({
placement: 'bottom',
delay: {
show: 500,
@@ -120,54 +117,62 @@ define([
};
/**
* load main page structure elements and navigation container into body
* @returns {*|w.fn.init|jQuery|HTMLElement}
* render page content + left/right menu
* @param rootEl
* @returns {Promise<{pageEl: HTMLDivElement, pageMenuRightEl: HTMLDivElement, pageMenuLeftEl: HTMLDivElement}>}
*/
let loadPageStructure = () => {
let renderPage = rootEl => {
let pageEl = Object.assign(document.createElement('div'), {
className: config.pageClass
});
pageEl.setAttribute('canvas', 'container');
let executor = resolve => {
let body = $('body');
let contextMenuContainerEl = Object.assign(document.createElement('div'), {
id: MapContextMenu.config.contextMenuContainerId
});
pageEl.append(Util.getMapModule()[0], contextMenuContainerEl);
let pageElement = $('<div>', {
class: config.pageClass
}).attr('canvas', 'container').append(
Util.getMapModule(),
$('<div>', {
id: config.dynamicElementWrapperId
})
);
let pageMenuLeftEl = Object.assign(document.createElement('div'), {
className: [config.pageMenuClass, config.pageMenuLeftClass].join(' ')
});
pageMenuLeftEl.setAttribute('off-canvas', [config.pageMenuLeftClass, 'left', 'push'].join(' '));
let pageMenuLeftElement = $('<div>', {
class: [config.pageMenuClass, config.pageMenuLeftClass].join(' ')
}).attr('off-canvas', [config.pageMenuLeftClass, 'left', 'push'].join(' '));
let pageMenuRightEl = Object.assign(document.createElement('div'), {
className: [config.pageMenuClass, config.pageMenuRightClass].join(' ')
});
pageMenuRightEl.setAttribute('off-canvas', [config.pageMenuRightClass, 'right', 'push'].join(' '));
let pageMenuRightElement = $('<div>', {
class: [config.pageMenuClass, config.pageMenuRightClass].join(' ')
}).attr('off-canvas', [config.pageMenuRightClass, 'right', 'push'].join(' '));
rootEl.prepend(pageEl, pageMenuLeftEl, pageMenuRightEl);
body.prepend(pageElement, pageMenuLeftElement, pageMenuRightElement);
Promise.all([
loadHeader(pageElement),
loadFooter(pageElement),
loadLeftMenu(pageMenuLeftElement),
loadRightMenu(pageMenuRightElement),
loadSVGs()
]).then(payload => Promise.all([
setMenuObserver(payload[2].data),
setMenuObserver(payload[3].data),
setDocumentObserver(),
setBodyObserver(),
setGlobalShortcuts()
])).then(() => resolve({
action: 'loadPageStructure',
data: {}
}));
};
return new Promise(executor);
return Promise.resolve({pageEl, pageMenuLeftEl, pageMenuRightEl});
};
/**
* load main page structure elements and navigation container into body
* @param pageEl
* @param pageMenuLeftEl
* @param pageMenuRightEl
* @returns {Promise}
*/
let loadPageStructure = ({pageEl, pageMenuLeftEl, pageMenuRightEl}) => new Promise(resolve => {
Promise.all([
loadHeader(pageEl),
loadFooter(pageEl),
loadLeftMenu(pageMenuLeftEl),
loadRightMenu(pageMenuRightEl),
loadSVGs()
]).then(payload => Promise.all([
setMenuObserver(payload[2].data),
setMenuObserver(payload[3].data),
setDocumentObserver(),
setBodyObserver(),
setGlobalShortcuts()
])).then(() => resolve({
action: 'loadPageStructure',
data: pageEl
}));
});
/**
* build main menu from mapConfig array
* @param menuConfig
@@ -230,13 +235,13 @@ define([
/**
* load left menu content options
* @param pageMenuLeftElement
* @param pageMenuLeftEl
* @returns {Promise<any>}
*/
let loadLeftMenu = pageMenuLeftElement => {
let loadLeftMenu = pageMenuLeftEl => {
let executor = resolve => {
pageMenuLeftElement.append(getMenu([
$(pageMenuLeftEl).append(getMenu([
{
type: 'button',
label: 'Home',
@@ -303,7 +308,7 @@ define([
resolve({
action: 'loadLeftMenu',
data: pageMenuLeftElement
data: pageMenuLeftEl
});
};
@@ -312,13 +317,13 @@ define([
/**
* load right menu content options
* @param pageMenuRightElement
* @param pageMenuRightEl
* @returns {Promise<any>}
*/
let loadRightMenu = pageMenuRightElement => {
let loadRightMenu = pageMenuRightEl => {
let executor = resolve => {
pageMenuRightElement.append(getMenu([
$(pageMenuRightEl).append(getMenu([
{
type: 'button',
label: 'Information',
@@ -407,7 +412,7 @@ define([
resolve({
action: 'loadRightMenu',
data: pageMenuRightElement
data: pageMenuRightEl
});
};
@@ -445,10 +450,10 @@ define([
/**
* load page header
* @param pageElement
* @param pageEl
* @returns {Promise<any>}
*/
let loadHeader = pageElement => {
let loadHeader = pageEl => {
let executor = resolve => {
let moduleData = {
@@ -462,7 +467,7 @@ define([
mapTrackingId: Util.config.headMapTrackingId
};
pageElement.prepend(Mustache.render(TplHead, moduleData));
pageEl.insertAdjacentHTML('afterbegin', Mustache.render(TplHead, moduleData));
// init header --------------------------------------------------------------------------------------------
@@ -571,7 +576,7 @@ define([
resolve({
action: 'loadHeader',
data: {
pageElement: pageElement
pageEl: pageEl
}
});
};
@@ -581,10 +586,10 @@ define([
/**
* load page footer
* @param pageElement
* @param pageEl
* @returns {Promise<any>}
*/
let loadFooter = pageElement => {
let loadFooter = pageEl => {
let executor = resolve => {
let moduleData = {
@@ -594,11 +599,11 @@ define([
currentYear: new Date().getFullYear()
};
pageElement.prepend(Mustache.render(TplFooter, moduleData));
pageEl.insertAdjacentHTML('afterbegin', Mustache.render(TplFooter, moduleData));
// init footer --------------------------------------------------------------------------------------------
pageElement.find('.' + config.footerLicenceLinkClass).on('click', e => {
$(pageEl.querySelector(`.${config.footerLicenceLinkClass}`)).on('click', e => {
e.stopPropagation();
//show credits info dialog
$.fn.showCreditsDialog();
@@ -609,7 +614,7 @@ define([
resolve({
action: 'loadFooter',
data: {
pageElement: pageElement
pageEl: pageEl
}
});
};
@@ -619,10 +624,10 @@ define([
/**
* set page menu observer
* @param menuElement
* @param menuEl
* @returns {Promise<any>}
*/
let setMenuObserver = menuElement => {
let setMenuObserver = menuEl => {
let executor = resolve => {
@@ -634,7 +639,7 @@ define([
}
};
menuElement.on('click', '.list-group-item[data-action]', e => {
$(menuEl).on('click', '.list-group-item[data-action]', e => {
e.preventDefault();
e.stopPropagation();
@@ -707,7 +712,10 @@ define([
title: 'Test Notification',
text: 'Accept browser security question'
},{
desktop: true,
desktop: {
title: 'Test OK',
text: 'Desktop notifications active'
},
stack: 'barBottom'
});
break;
@@ -1272,11 +1280,11 @@ define([
};
/**
* set event listener if the program tab is active or not
* this is used to lower the update ping cycle to reduce server load
* set event listener if the program tab is active or not
* this is used to lower the update ping cycle to reduce server load
* @returns {Promise}
*/
let initTabChangeObserver = () => {
let initTabChangeObserver = () => new Promise(resolve => {
// increase the timer if a user is inactive
let increaseTimer = 5000;
@@ -1335,21 +1343,24 @@ define([
document.addEventListener(visibilityChange, handleVisibilityChange, false);
}
};
resolve({
action: 'initTabChangeObserver',
data: false
});
});
/**
* add "hidden" context menu elements to page
* @returns {Promise[]}
*/
let renderMapContextMenus = () => {
Promise.all([
MapContextMenu.renderMapContextMenu(),
MapContextMenu.renderConnectionContextMenu(),
MapContextMenu.renderEndpointContextMenu(),
MapContextMenu.renderSystemContextMenu(Init.systemStatus)
]).then(payloads => {
$('#' + config.dynamicElementWrapperId).append(payloads.join(''));
});
};
let renderMapContextMenus = () => Promise.all([
MapContextMenu.renderMapContextMenu(),
MapContextMenu.renderConnectionContextMenu(),
MapContextMenu.renderEndpointContextMenu(),
MapContextMenu.renderSystemContextMenu(Init.systemStatus)
]).then(payloads => {
document.getElementById(MapContextMenu.config.contextMenuContainerId).innerHTML = payloads.join('');
});
/**
* trigger "program status" in head
@@ -1476,6 +1487,7 @@ define([
};
return {
renderPage: renderPage,
loadPageStructure: loadPageStructure,
initTabChangeObserver: initTabChangeObserver,
renderMapContextMenus: renderMapContextMenus

View File

@@ -0,0 +1,158 @@
define([
'PNotify',
'PNotifyDesktop',
'PNotifyCallbacks',
'NonBlock'
], (PNotify) => {
'use strict';
let stackConfig = {
bottomRight: {
stack: {
dir1: 'up',
dir2: 'left',
firstpos1: 32,
firstpos2: 10,
spacing1: 5,
spacing2: 5,
push: 'bottom',
context: document.body
}
},
barBottom: {
stack: {
dir1: 'up',
firstpos1: 32,
spacing1: 0,
context: document.querySelector(`.pf-site`)
},
addclass: 'stack-bar-bottom'
}
};
let initDefaultPNotifyConfig = () => {
PNotify.defaults.styling = 'bootstrap3';
PNotify.defaults.icons = 'fontawesome5';
PNotify.defaults.addClass = 'nonblock';
PNotify.defaults.delay = 5000;
PNotify.defaults.width = '250px';
PNotify.defaults.animateSpeed = 'fast';
PNotify.defaults.stack = stackConfig.bottomRight.stack;
PNotify.modules.Desktop.defaults.icon = '/public/img/notifications/logo.png';
};
/**
* show browser/desktop notification
* @param config
* @param options
*/
let showNotify = (config = {}, options = {}) => {
if(options.desktop){
config.modules = {
Desktop: Object.assign({}, {desktop: true}, options.desktop)
};
startTabBlink(config.title); // make browser tab blink
}
switch(config.type){
case 'info':
config.icon = 'fas fa-info fa-fw fa-lg';
break;
case 'success':
config.icon = 'fas fa-check fa-fw fa-lg';
break;
case 'notice':
case 'warning':
config.icon = 'fas fa-exclamation-triangle fa-fw fa-lg';
config.type = 'notice';
break;
case 'error':
config.icon = 'fas fa-times fa-fw fa-lg';
break;
case 'lock':
config.icon = 'fas fa-lock fa-fw fa-lg';
config.type = 'success';
break;
case 'unlock':
config.icon = 'fas fa-unlock fa-fw fa-lg';
config.type = 'info';
break;
default:
config.icon = false;
}
if(options.stack){
config.stack = stackConfig[options.stack].stack;
config.addClass = stackConfig[options.stack].addclass;
}
let notice = PNotify.alert(config);
if(typeof options.click === 'function'){
notice.refs.elem.style.cursor = 'pointer';
notice.on('click', options.click);
}
};
initDefaultPNotifyConfig();
// browser tab blink ==============================================================================================
// initial page title (cached)
let initialPageTitle = document.title;
// global blink timeout cache
let blinkTimer;
/**
* change document.title and make the browsers tab blink
* @param blinkTitle
*/
let startTabBlink = blinkTitle => {
let initBlink = (function(){
// count blinks if tab is currently active
let activeTabBlinkCount = 0;
let blink = (blinkTitle) => {
// 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 () => {
if(!blinkTimer){
blinkTimer = setInterval(blink, 1000, blinkTitle);
}
};
}(blinkTitle));
initBlink();
};
/**
* stop blinking document.title
*/
let stopTabBlink = () => {
if(blinkTimer){
clearInterval(blinkTimer);
document.title = initialPageTitle;
blinkTimer = null;
}
};
return {
showNotify: showNotify,
startTabBlink: startTabBlink,
stopTabBlink: stopTabBlink
};
});

View File

@@ -0,0 +1,82 @@
define([], () => {
'use strict';
/**
* sequential promise queue
* @see https://medium.com/@karenmarkosyan/how-to-manage-promises-into-dynamic-queue-with-vanilla-javascript-9d0d1f8d4df5
* @see https://codepen.io/exodus4d/pen/QWwgKay
*/
return class Queue {
constructor() {
this._queue = [];
this._pendingPromise = false;
this._stop = false;
}
/**
* wraps a promise that needs to be sequentially resolved
* -> dequeue() process starts immediately (if not already pending)
* @param promise
* @param {'end'|'start'} position
* @param data
* @returns {Promise}
*/
enqueue(promise, position = 'end', data = null) {
return new Promise((resolve, reject) => {
this._queue[position === 'end' ? 'push' : 'unshift']({
promise,
resolve,
reject,
data,
});
this.dequeue();
});
}
/**
* resolve promise queue recursive until queue is empty
* @returns {boolean}
*/
dequeue() {
if (this._pendingPromise) {
return false;
}
if (this._stop) {
this._queue = [];
this._stop = false;
return false;
}
let item = this._queue.shift();
if (!item) {
return false;
}
try {
this._pendingPromise = true;
item.promise()
.then((value) => {
this._pendingPromise = false;
item.resolve(value);
this.dequeue();
})
.catch(err => {
this._pendingPromise = false;
item.reject(err);
this.dequeue();
});
} catch (err) {
this._pendingPromise = false;
item.reject(err);
this.dequeue();
}
return true;
}
filterQueue(callback) {
return this._queue.filter(callback);
}
};
});

View File

@@ -24,8 +24,11 @@ define(['jquery', 'mustache'], ($, Mustache) => {
/**
* convert JSON object into HTML highlighted string
* @param obj
* @param options
*/
let highlightJson = (obj) => {
let highlightJson = (obj, options = {}) => {
let maxLinesFunctions = options.maxLinesFunctions || 5;
let multiplyString = (num, str) => {
let sb = [];
for(let i = 0; i < num; i++){
@@ -39,7 +42,10 @@ define(['jquery', 'mustache'], ($, Mustache) => {
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 expImageClicked = '(() => {this.classList.toggle(\'fa-minus-square\'); ' +
'this.classList.toggle(\'fa-plus-square\'); ' +
'let container=this.parentNode.nextSibling; ' +
'container.style.display=container.style.display===\'none\'?\'inline\':\'none\'})();';
let checkForArray = function(obj){
return obj &&
@@ -67,18 +73,27 @@ define(['jquery', 'mustache'], ($, Mustache) => {
let formatFunction = function(indent, obj){
let tabs = '';
for(let i = 0; i < indent; i++) tabs += tab;
let funcStrArray = obj.toString().split('\n');
let funcStrArray = obj.toString().split('\n', maxLinesFunctions);
let str = '';
for(let i = 0; i < funcStrArray.length; i++){
str += ((i === 0) ? '' : tabs) + funcStrArray[i] + '\n';
str += ((i === 0) ? '' : '') + funcStrArray[i] + '\n';
}
return str;
return str + tabs;
};
let highlight = (obj, indent, addComma, isArray, isPropertyContent) => {
// check if recursive call depth leads to collapsed data
let startCollapseIcon = options.collapseDepth <= indent ? 'fa-plus-square' : 'fa-minus-square';
let startCollapseStyle = options.collapseDepth <= indent ? 'none' : 'inline';
let html = '';
// check max recursion depth
if(indent > (options.maxDepth || 8)){
return html;
}
let comma = (addComma) ? '<span class="pf-code-Comma">,</span> ' : '';
let type = typeof obj;
let clpsHtml = '';
@@ -86,7 +101,8 @@ define(['jquery', 'mustache'], ($, Mustache) => {
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">' : '';
clpsHtml = isCollapsible ? '<span><i class="pf-module-icon-button fas fa-fw ' + startCollapseIcon + '" onClick="' + expImageClicked + '"></i></span>' +
'<span class="collapsible" style="display:'+ startCollapseStyle +'">' : '';
html += getRow(indent, '<span class="pf-code-ArrayBrace">[</span>' + clpsHtml, isPropertyContent);
for(let i = 0; i < obj.length; i++){
html += highlight(obj[i], indent + 1, i < (obj.length - 1), true, false);
@@ -98,7 +114,7 @@ define(['jquery', 'mustache'], ($, Mustache) => {
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');
html += formatLiteral('new Date(' + obj.getTime() + ') &lt;' + obj.toLocaleString('en-GB') + '&gt;', '', comma, indent, isArray, 'pf-code-Date');
}else if(obj.constructor === regexpObj.constructor){
html += formatLiteral('new RegExp(' + obj + ')', '', comma, indent, isArray, 'RegExp');
}else{
@@ -107,7 +123,8 @@ define(['jquery', 'mustache'], ($, Mustache) => {
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">' : '';
clpsHtml = isCollapsible ? '<span><i class="pf-module-icon-button fas fa-fw ' + startCollapseIcon + '" onClick="' + expImageClicked + '"></i></span>' +
'<span class="collapsible" style="display:'+ startCollapseStyle +'">' : '';
html += getRow(indent, '<span class="pf-code-ObjectBrace">{</span>' + clpsHtml, isPropertyContent);
let j = 0;
for(let prop in obj){

View File

@@ -1033,7 +1033,7 @@ define([
// get to last page (pageIndex starts at zero) -> check if last page > 0
context.tableApi.page(newPageIndex).draw(false);
}else{
Util.showNotify({title: 'No logs found', text: 'No more entries', type: 'danger'});
Util.showNotify({title: 'No logs found', text: 'No more entries', type: 'warning'});
}
};

View File

@@ -432,10 +432,9 @@ define([
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);
let tabLinkEls = Util.getMapTabLinkElements(Util.getMapModule()[0], responseData.mapData.mapData.id);
if(tabLinkEls.length === 1){
ModuleMap.updateTabData(tabLinkEls[0], responseData.mapData.mapData);
}
$(mapInfoDialog).modal('hide');
@@ -803,6 +802,10 @@ define([
},
callback: function(result){
if(result){
// lock dialog
let dialogContent = mapDeleteDialog.find('.modal-content');
dialogContent.showLoadingAnimation();
let data = {mapData: mapData.config};
$.ajax({

View File

@@ -75,10 +75,10 @@ define([
// 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>>',
let statsTable = table.DataTable({
dom: '<"flex-row flex-between"<"flex-col"l><"flex-col"B><"flex-col"fS>>' +
'<"flex-row"<"flex-col flex-grow"tr>>' +
'<"flex-row flex-between"<"flex-col"i><"flex-col"p>>',
buttons: {
name: 'tableTools',
buttons: [

View File

@@ -0,0 +1,284 @@
define([
'jquery',
'app/init',
'app/util',
'app/promises/promise.deferred',
'app/promises/promise.queue'
], ($, Init, Util, DeferredPromise, PromiseQueue) => {
'use strict';
/**
* abstract BaseModel class
* -> custom/plugin modules must extend from it
* @type {BaseModule}
*/
let BaseModule = class BaseModule {
constructor(config= {}){
if(new.target === BaseModule){
throw new TypeError('Cannot construct ' + this.constructor.name + ' instances directly');
}
// check for abstract methods to be implemented in child
if(this.render === undefined){
throw new TypeError('Abstract method render() missing in ' + new.target.name + ' class');
}
this._config = Object.assign({}, BaseModule.defaultConfig, config);
this._updateQueue = new PromiseQueue();
}
/**
* get current module configuration
* @returns {*}
*/
get config(){
return this._config;
}
/**
* get root node for this module
* -> parent container for custom body HTML
* @returns {HTMLElement}
*/
get moduleElement(){
if(!this._moduleEl){
// init new moduleElement
this._moduleEl = Object.assign(document.createElement('div'), {
className: `${BaseModule.className} ${this._config.className}`,
style: {
opacity: '0'
}
}).setData('module', this);
this._moduleEl.dataset.position = this._config.position;
this._moduleEl.dataset.module = this.constructor.name;
// module header
this._moduleEl.append(this.newHeaderElement());
}
return this._moduleEl;
}
/**
* module header element
* -> dragHandler + headline
* @param text
* @returns {HTMLDivElement}
*/
newHeaderElement(text){
let headEl = this.newHeadElement();
headEl.append(
this.newHandlerElement(),
this.newHeadlineElement(text || this._config.headline)
);
return headEl;
}
/**
* module head element
* @returns {HTMLDivElement}
*/
newHeadElement(){
return Object.assign(document.createElement('div'), {
className: this._config.headClassName
});
}
/**
* module dragHandler element
* @returns {HTMLHeadingElement}
*/
newHandlerElement(){
return Object.assign(document.createElement('h5'), {
className: this._config.handlerClassName
});
}
/**
* module headline element
* @param text
* @returns {HTMLHeadingElement}
*/
newHeadlineElement(text){
return Object.assign(document.createElement('h5'), {
textContent: typeof text === 'string' ? text : ''
});
}
/**
* module toolbar element (wrapper)
* @returns {HTMLHeadingElement}
*/
newHeadlineToolbarElement(){
return Object.assign(document.createElement('h5'), {
className: 'pull-right'
});
}
/**
* icon element
* @param cls
* @returns {HTMLElement}
*/
newIconElement(cls = []){
return Object.assign(document.createElement('i'), {
className: ['fas', ...cls].join(' ')
});
}
/**
* HTTP request handler for internal (Pathfinder) ajax calls
* @param args
* @returns {Promise}
*/
request(...args){
return BaseModule.Util.request(...args);
}
/**
* scoped instance for LocalStore for current module
* @returns {LocalStore}
*/
getLocalStore(){
if(!this._localStore){
// make accessible -> scope Store keys!
this._localStore = BaseModule.Util.getLocalStore('module');
this._localStore.scope = this.constructor.name;
}
return this._localStore;
}
/**
* visual notification handler (UI popover)
* -> can be used for info/error on-screen messages
* @param args
*/
showNotify(...args){
return BaseModule.Util.showNotify(...args);
}
/**
* responsible for dispatching all incoming method calls
* @param handler
* @param data
* @returns {*}
*/
handle(handler, ...data){
try{
if(BaseModule.handler.includes(handler)){
// .. run module handler
let returnData = this[handler].apply(this, data);
if(returnData instanceof Promise){
// log returned Promise from handler call resolved
returnData.then(() => { this.logHandler(handler, 0);});
}
// log handler call
this.logHandler(handler);
return returnData;
}else{
console.error('Error in module %o. Invalid handler %o', this.constructor.name, handler);
}
}catch(e){
console.error('Error in module %o in handler %s() %o', this.constructor.name, handler, e);
}
}
/**
* log handler calls for this instance
* -> can be helpful for debugging
* @param handler
* @param increment
*/
logHandler(handler, increment = 1){
if(increment){
if(!this._config.logHandler){
this._config.logHandler = {};
}
this._config.logHandler[handler] = (this._config.logHandler[handler] || 0) + increment;
}
}
/**
* init module
*/
init(){}
/**
* update module
* @param data
* @returns {Promise}
*/
update(data){
return this._updateQueue.enqueue(() => Promise.resolve(data), 'end', 'upd');
}
beforeHide(){}
beforeDestroy(){
$(this.moduleElement).destroyPopover(true);
// destroy DataTable instances
for(let table of $(this.moduleElement).find('table.dataTable')){
$(table).DataTable().destroy(true);
}
}
/**
* events from 'Sortable' lib
* @see https://github.com/SortableJS/Sortable
* @param name
* @param e
*/
onSortableEvent(name, e){
if(name === 'onUnchoose' && this._sortableChoosePromise){
this._sortableChoosePromise.resolve();
}
if(name === 'onChoose' && !this._sortableChoosePromise){
this._sortableChoosePromise = BaseModule.newDeferredPromise();
this._updateQueue.enqueue(() => this._sortableChoosePromise.then(() => {
this._sortableChoosePromise = null;
}), 'start');
}
}
static newDeferredPromise(){
return new DeferredPromise();
}
};
BaseModule.isPlugin = true; // module is defined as 'plugin'
BaseModule.scope = 'system'; // static module scope controls how module gets updated and what type of data is injected
BaseModule.sortArea = 'a'; // static default sortable area
BaseModule.position = 0; // static default sort/order position within sortable area
BaseModule.label = '???'; // static module label (e.g. description)
BaseModule.className = 'pf-module'; // static CSS class name
BaseModule.fullDataUpdate = false; // static module requires additional data (e.g. system description,...)
BaseModule.Util = Util; // static access to Pathfinders Util object
BaseModule.handler = [
'render',
'init',
'update',
'beforeHide',
'beforeDestroy',
'onSortableEvent'
];
BaseModule.defaultConfig = {
position: 1,
className: 'pf-base-module', // class for module
headClassName: 'pf-module-head', // class for module header
handlerClassName: 'pf-sortable-handle', // class for "drag" handler
sortTargetAreas: ['a', 'b', 'c'], // sortable areas where module can be dragged into
headline: 'Base headline', // module headline
bodyClassName: 'pf-module-body', // class for module body [optional: can be used]
moduleHeadlineIconClass: 'pf-module-icon-button' // class for toolbar icons in the head
};
return BaseModule;
});

File diff suppressed because it is too large Load Diff

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