- new role management for Corporation maps, closed #164

- new role management section for corporations admins
- added column "nullable" detection within /setup page for DB diff
- added new map icon options options to the map add/edit dialog
- refactored setup() method for all tables with static data
- fixed broken map icons
- fixed broken "drag/select" for systems on map
- fixed new "map resize" event for non Chrome browsers
- multiple minor improvements and fixes...
This commit is contained in:
Mark Friedrich
2018-02-16 17:02:10 +01:00
parent 21df484457
commit ac36d5e074
61 changed files with 1947 additions and 857 deletions

View File

@@ -14,6 +14,7 @@ use Model\CharacterModel;
use Model\CorporationModel;
use lib\Config;
use Model\MapModel;
use Model\RoleModel;
class Admin extends Controller{
@@ -91,9 +92,7 @@ class Admin extends Controller{
$adminCharacter = null;
if( !$f3->exists(Sso::SESSION_KEY_SSO_ERROR) ){
if( $character = $this->getCharacter() ){
$this->setCharacterRole($character);
if($character->role != 'MEMBER'){
if(in_array($character->roleId->name, ['SUPER', 'CORPORATION'], true)){
// current character is admin
$adminCharacter = $character;
}elseif( !$character->hasAdminScopes() ){
@@ -118,41 +117,12 @@ class Admin extends Controller{
return $adminCharacter;
}
/**
* set temp "virtual" field with current admin role name for a $characterModel
* @param CharacterModel $character
*/
protected function setCharacterRole(CharacterModel $character){
$character->virtual('role', function ($character){
// default role based on roleId (auto-detected)
if(($role = array_search($character->roleId, CharacterModel::ROLES)) === false){
$role = 'MEMBER';
}
/**
* check config files for hardcoded character roles
* -> overwrites default role (e.g. auto detected by corp in-game roles)
*/
if($this->getF3()->exists('PATHFINDER.ADMIN.CHARACTER', $globalAdminData)){
foreach((array)$globalAdminData as $adminData){
if($adminData[ 'ID' ] === $character->_id){
if(CharacterModel::ROLES[ $adminData[ 'ROLE' ] ]){
$role = $adminData[ 'ROLE' ];
}
break;
}
}
}
return $role;
});
}
/**
* dispatch page events by URL $params
* @param \Base $f3
* @param array $params
* @param $params
* @param null $character
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function dispatch(\Base $f3, $params, $character = null){
@@ -163,6 +133,18 @@ class Admin extends Controller{
switch($parts[0]){
case 'settings':
switch($parts[1]){
case 'save':
$objectId = (int)$parts[2];
$values = (array)$f3->get('GET');
$this->saveSettings($character, $objectId, $values);
$f3->reroute('@admin(@*=/' . $parts[0] . ')');
break;
}
$f3->set('tplDefaultRole', RoleModel::getDefaultRole());
$f3->set('tplRoles', RoleModel::getAll());
$this->initSettings($f3, $character);
break;
case 'members':
switch($parts[1]){
@@ -179,9 +161,7 @@ class Admin extends Controller{
$this->banCharacter($character, $objectId, $value);
break;
}
$f3->set('tplPage', 'members');
$f3->set('tplKickOptions', self::KICK_OPTIONS);
$this->initMembers($f3, $character);
break;
case 'maps':
@@ -209,6 +189,44 @@ class Admin extends Controller{
}
}
/**
* save or delete settings (e.g. corporation rights)
* @param CharacterModel $character
* @param int $corporationId
* @param array $settings
* @throws \Exception
*/
protected function saveSettings(CharacterModel $character, int $corporationId, array $settings){
$defaultRole = RoleModel::getDefaultRole();
if($corporationId && $defaultRole){
$corporations = $this->getAccessibleCorporations($character);
foreach($corporations as $corporation){
if($corporation->_id === $corporationId){
// character has access to that corporation -> create/update/delete rights...
if($corporationRightsData = (array)$settings['rights']){
// get existing corp rights
foreach($corporation->getRights(['addInactive' => true]) as $corporationRight){
$corporationRightData = $corporationRightsData[$corporationRight->rightId->_id];
if(
$corporationRightData &&
$corporationRightData['roleId'] != $defaultRole->_id // default roles should not be saved
){
$corporationRight->setData($corporationRightData);
$corporationRight->setActive(true);
$corporationRight->save();
}else{
// right not send by user -> delete existing right
$corporationRight->erase();
}
}
}
break;
}
}
}
}
/**
* kick or revoke a character
* @param CharacterModel $character
@@ -276,7 +294,7 @@ class Admin extends Controller{
// check if kickCharacters belong to same Corp as admin character
// -> remove admin char from valid characters...
if( !empty($characterIds = array_diff( [$characterId], [$character->_id])) ){
if($character->role === 'SUPERADMIN'){
if($character->roleId->name === 'SUPER'){
if($filterCharacters = CharacterModel::getAll($characterIds)){
$characters = $filterCharacters;
}
@@ -317,13 +335,13 @@ class Admin extends Controller{
* checks whether a $character has admin access rights for $mapId
* @param CharacterModel $character
* @param int $mapId
* @return array
* @return \DB\CortexCollection[]|MapModel[]
* @throws \Exception\PathfinderException
*/
protected function filterValidMaps(CharacterModel $character, int $mapId) : array {
protected function filterValidMaps(CharacterModel $character, int $mapId) {
$maps = [];
if($character->role === 'SUPERADMIN'){
if($filterMaps = MapModel::getAll([$mapId])){
if($character->roleId->name === 'SUPER'){
if($filterMaps = MapModel::getAll([$mapId], ['addInactive' => true])){
$maps = $filterMaps;
}
}else{
@@ -343,6 +361,22 @@ class Admin extends Controller{
return parent::getLogger('ADMIN');
}
/**
* init /settings page data
* @param \Base $f3
* @param CharacterModel $character
*/
protected function initSettings(\Base $f3, CharacterModel $character){
$data = (object) [];
$corporations = $this->getAccessibleCorporations($character);
foreach($corporations as $corporation){
$data->corporations[$corporation->name] = $corporation;
}
$f3->set('tplSettings', $data);
}
/**
* init /member page data
* @param \Base $f3
@@ -351,17 +385,7 @@ class Admin extends Controller{
protected function initMembers(\Base $f3, CharacterModel $character){
$data = (object) [];
if($characterCorporation = $character->getCorporation()){
$corporations = [];
switch($character->role){
case 'SUPERADMIN':
if($accessCorporations = CorporationModel::getAll(['addNPC' => true])){
$corporations = $accessCorporations;
}
break;
case 'CORPORATION':
$corporations[] = $characterCorporation;
break;
}
$corporations = $this->getAccessibleCorporations($character);
foreach($corporations as $corporation){
if($characters = $corporation->getCharacters()){
@@ -382,21 +406,12 @@ class Admin extends Controller{
* init /maps page data
* @param \Base $f3
* @param CharacterModel $character
* @throws \Exception\PathfinderException
*/
protected function initMaps(\Base $f3, CharacterModel $character){
$data = (object) [];
if($characterCorporation = $character->getCorporation()){
$corporations = [];
switch($character->role){
case 'SUPERADMIN':
if($accessCorporations = CorporationModel::getAll(['addNPC' => true])){
$corporations = $accessCorporations;
}
break;
case 'CORPORATION':
$corporations[] = $characterCorporation;
break;
}
$corporations = $this->getAccessibleCorporations($character);
foreach($corporations as $corporation){
if($maps = $corporation->getMaps([], ['addInactive' => true, 'ignoreMapCount' => true])){
@@ -408,4 +423,27 @@ class Admin extends Controller{
$f3->set('tplMaps', $data);
}
/**
* get all corporations a characters has admin access for
* @param CharacterModel $character
* @return CorporationModel[]
*/
protected function getAccessibleCorporations(CharacterModel $character) {
$corporations = [];
if($characterCorporation = $character->getCorporation()){
switch($character->roleId->name){
case 'SUPER':
if($accessCorporations = CorporationModel::getAll(['addNPC' => true])){
$corporations = $accessCorporations;
}
break;
case 'CORPORATION':
$corporations[] = $characterCorporation;
break;
}
}
return $corporations;
}
}

View File

@@ -80,7 +80,7 @@ class User extends Controller\Controller{
$this->getF3()->set(self::SESSION_KEY_CHARACTERS, $sessionCharacters);
// save user login information --------------------------------------------------------
$characterModel->roleId = $characterModel->requestRoleId();
$characterModel->roleId = $characterModel->requestRole();
$characterModel->touch('lastLogin');
$characterModel->save();

View File

@@ -67,6 +67,8 @@ class Setup extends Controller {
'Model\SystemStatusModel',
'Model\SystemNeighbourModel',
'Model\WormholeModel',
'Model\RightModel',
'Model\RoleModel',
'Model\CharacterStatusModel',
'Model\ConnectionScopeModel',
@@ -75,6 +77,8 @@ class Setup extends Controller {
'Model\AllianceMapModel',
'Model\CorporationMapModel',
'Model\CorporationRightModel',
'Model\UserCharacterModel',
'Model\CharacterModel',
'Model\CharacterAuthenticationModel',
@@ -944,10 +948,13 @@ class Setup extends Controller {
}
foreach((array)$data['fieldConf'] as $columnName => $fieldConf){
// if 'nullable' key not set in $fieldConf, Column was created with 'nullable' = true (Cortex default)
$fieldConf['nullable'] = isset($fieldConf['nullable']) ? (bool)$fieldConf['nullable'] : true;
$columnStatusCheck = true;
$foreignKeyStatusCheck = true;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['requiredType'] = $fieldConf['type'];
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['requiredNullable'] = ($fieldConf['nullable']) ? '1' : '0';
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['requiredIndex'] = ($fieldConf['index']) ? '1' : '0';
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['requiredUnique'] = ($fieldConf['unique']) ? '1' : '0';
@@ -962,11 +969,14 @@ class Setup extends Controller {
$col->copyfrom($currentColumns[$columnName]);
$currentColType = $currentColumns[$columnName]['type'];
$currentNullable = $currentColumns[$columnName]['nullable'];
$hasNullable = $currentNullable ? '1' : '0';
$currentColIndexData = call_user_func($data['model'] . '::indexExists', [$columnName]);
$currentColIndex = is_array($currentColIndexData);
$hasIndex = ($currentColIndex) ? '1' : '0';
$hasUnique = ($currentColIndexData['unique']) ? '1' : '0';
$changedType = false;
$changedNullable = false;
$changedUnique = false;
$changedIndex = false;
$addConstraints = [];
@@ -974,6 +984,7 @@ class Setup extends Controller {
// set (new) column information -------------------------------------------------------
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['exists'] = true;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentType'] = $currentColType;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentNullable'] = $hasNullable;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentIndex'] = $hasIndex;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentUnique'] = $hasUnique;
@@ -1014,6 +1025,13 @@ class Setup extends Controller {
$tableStatusCheckCount++;
}
// check if column nullable changed ---------------------------------------------------
if( $currentNullable != $fieldConf['nullable']){
$changedNullable = true;
$columnStatusCheck = false;
$tableStatusCheckCount++;
}
// check if column index changed ------------------------------------------------------
$indexUpdate = false;
$indexKey = (bool)$hasIndex;
@@ -1025,7 +1043,7 @@ class Setup extends Controller {
$tableStatusCheckCount++;
$indexUpdate = true;
$indexKey = (bool) $fieldConf['index'];
$indexKey = (bool)$fieldConf['index'];
}
// check if column unique changed -----------------------------------------------------
@@ -1035,7 +1053,7 @@ class Setup extends Controller {
$tableStatusCheckCount++;
$indexUpdate = true;
$indexUnique =(bool)$fieldConf['unique'];
$indexUnique = (bool)$fieldConf['unique'];
}
// build table with changed columns ---------------------------------------------------
@@ -1045,6 +1063,11 @@ class Setup extends Controller {
// IMPORTANT: setType is always required! Even if type has not changed
$col->type($fieldConf['type']);
// update "nullable"
if($changedNullable){
$col->nullable($fieldConf['nullable']);
}
// update/change/delete index/unique keys
if($indexUpdate){
if($hasIndex){
@@ -1077,6 +1100,7 @@ class Setup extends Controller {
// set (new) column information -------------------------------------------------------
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedType'] = $changedType;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedNullable'] = $changedNullable;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedUnique'] = $changedUnique;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedIndex'] = $changedIndex;

View File

@@ -881,4 +881,24 @@ abstract class BasicModel extends \DB\Cortex {
return $status;
}
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
// set static default data
if($status === true && property_exists(static::class, 'tableData')){
$model = self::getNew(self::getClassName(), 0);
$model->importStaticData(static::$tableData);
}
return $status;
}
}

View File

@@ -59,6 +59,7 @@ class CharacterMapModel extends BasicModel {
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);

View File

@@ -36,15 +36,6 @@ class CharacterModel extends BasicModel {
'KICKED' => 'character is kicked',
'BANNED' => 'character is banned'
];
/**
* all admin roles and related roleId for a character
*/
const ROLES = [
'MEMBER' => 0,
'SUPERADMIN' => 1,
'CORPORATION' => 2
];
/**
* enables change for "kicked" column
@@ -119,10 +110,17 @@ class CharacterModel extends BasicModel {
]
],
'roleId' => [
'type' => Schema::DT_TINYINT,
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0,
'index' => true
'default' => 1,
'index' => true,
'belongs-to-one' => 'Model\RoleModel',
'constraint' => [
[
'table' => 'role',
'on-delete' => 'CASCADE'
]
],
],
'kicked' => [
'type' => Schema::DT_TIMESTAMP,
@@ -164,7 +162,8 @@ class CharacterModel extends BasicModel {
/**
* get character data
* @param bool|false $addCharacterLogData
* @return \stdClass
* @return null|object|\stdClass
* @throws \Exception
*/
public function getData($addCharacterLogData = false){
$cacheKeyModifier = '';
@@ -182,11 +181,11 @@ class CharacterModel extends BasicModel {
$characterData = (object) [];
$characterData->id = $this->id;
$characterData->name = $this->name;
$characterData->roleId = $this->roleId;
$characterData->role = $this->roleId->getData();
$characterData->shared = $this->shared;
$characterData->logLocation = $this->logLocation;
if( $this->authStatus ){
if($this->authStatus){
$characterData->authStatus = $this->authStatus;
}
@@ -270,8 +269,9 @@ class CharacterModel extends BasicModel {
/**
* setter for "kicked" until time
* @param bool|int $minutes
* @return mixed
* @param $minutes
* @return mixed|null|string
* @throws \Exception
*/
public function set_kicked($minutes){
if($this->allowKickChange){
@@ -470,7 +470,8 @@ class CharacterModel extends BasicModel {
/**
* get ESI API "access_token" from OAuth
* @return bool|string
* @return bool|mixed
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function getAccessToken(){
@@ -550,7 +551,6 @@ class CharacterModel extends BasicModel {
// check whether character is banned or temp kicked
if(is_null($this->banned)){
if( !$this->isKicked() ){
$f3 = self::getF3();
$whitelistCorporations = array_filter( array_map('trim', (array)Config::getPathfinderData('login.corporation') ) );
$whitelistAlliance = array_filter( array_map('trim', (array)Config::getPathfinderData('login.alliance') ) );
@@ -596,27 +596,55 @@ class CharacterModel extends BasicModel {
}
/**
* get pathfinder roleId
* @return int
* get Pathfinder role for character
* @return RoleModel
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function requestRoleId(){
$roleId = self::ROLES['MEMBER'];
public function requestRole() : RoleModel{
$role = null;
if( !empty($rolesData = $this->requestRoles()) ){
// roles that grant admin access for this character
$adminRoles = array_intersect(CorporationModel::ADMIN_ROLES, $rolesData);
if( !empty($adminRoles) ){
$roleId = self::ROLES['CORPORATION'];
// check config files for hardcoded character roles
if(self::getF3()->exists('PATHFINDER.ROLES.CHARACTER', $globalAdminData)){
foreach((array)$globalAdminData as $adminData){
if($adminData['ID'] === $this->_id){
switch($adminData['ROLE']){
case 'SUPER':
$role = RoleModel::getAdminRole();
break;
case 'CORPORATION':
$role = RoleModel::getCorporationManagerRole();
break;
}
break;
}
}
}
return $roleId;
// check in-game roles
if(
is_null($role) &&
!empty($rolesData = $this->requestRoles())
){
// roles that grant admin access for this character
$adminRoles = array_intersect(CorporationModel::ADMIN_ROLES, $rolesData);
if( !empty($adminRoles) ){
$role = RoleModel::getCorporationManagerRole();
}
}
// default role
if(is_null($role)){
$role = RoleModel::getDefaultRole();
}
return $role;
}
/**
* request all corporation roles granted to this character
* @return array
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function requestRoles(){

View File

@@ -50,24 +50,4 @@ class CharacterStatusModel extends BasicModel {
'class' => 'pf-user-status-own'
]
];
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
// set static default values
if($status === true){
$model = self::getNew(self::getClassName(), 0);
$model->importStaticData(self::$tableData);
}
return $status;
}
}

View File

@@ -60,23 +60,4 @@ class ConnectionScopeModel extends BasicModel{
];
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
// set static default values
if($status === true){
$model = self::getNew(self::getClassName(), 0);
$model->importStaticData(self::$tableData);
}
return $status;
}
}

View File

@@ -59,6 +59,7 @@ class CorporationMapModel extends BasicModel {
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);

View File

@@ -81,6 +81,16 @@ class CorporationModel extends BasicModel {
'security_officer'
];
/**
* corp rights that can be stored to a corp
*/
const RIGHTS = [
'map_update',
'map_delete',
'map_import',
'map_export'
];
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
@@ -108,12 +118,16 @@ class CorporationModel extends BasicModel {
],
'mapCorporations' => [
'has-many' => ['Model\CorporationMapModel', 'corporationId']
],
'corporationRights' => [
'has-many' => ['Model\CorporationRightModel', 'corporationId']
]
];
/**
* get all cooperation data
* @return \stdClass
* get cooperation data
* @return object
* @throws \Exception
*/
public function getData(){
$cooperationData = (object) [];
@@ -122,6 +136,12 @@ class CorporationModel extends BasicModel {
$cooperationData->name = $this->name;
$cooperationData->shared = $this->shared;
if($corporationRights = $this->getRights()){
foreach($corporationRights as $corporationRight){
$cooperationData->rights[] = $corporationRight->getData();
}
}
return $cooperationData;
}
@@ -208,6 +228,50 @@ class CorporationModel extends BasicModel {
return $characterRolesData;
}
/**
* get all corporation rights
* @param array $options
* @return CorporationRightModel[]
* @throws \Exception
*/
public function getRights($options = []) : array {
$corporationRights = [];
// get available rights
$right = self::getNew('RightModel');
if($rights = $right->find(['active = ? AND name IN (?)', 1, self::RIGHTS])){
// get already stored rights
if( !$options['addInactive'] ){
$this->filter('corporationRights', ['active = ?', 1]);
}
foreach($rights as $i => $tempRight){
$corporationRight = false;
if($this->corporationRights){
foreach($this->corporationRights as $tempCorporationRight){
/**
* @var $tempCorporationRight CorporationRightModel
*/
if($tempCorporationRight->get('rightId', true) === $tempRight->_id){
$corporationRight = $tempCorporationRight;
break;
}
}
}
if(!$corporationRight){
$corporationRight = self::getNew('CorporationRightModel');
$corporationRight->corporationId = $this;
$corporationRight->rightId = $tempRight;
$corporationRight->roleId = RoleModel::getDefaultRole();
}
$corporationRights[] = $corporationRight;
}
}
return $corporationRights;
}
/**
* get all corporations
* @param array $options

View File

@@ -0,0 +1,107 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 28.01.2018
* Time: 15:37
*/
namespace Model;
use DB\SQL\Schema;
class CorporationRightModel extends BasicModel {
protected $table = 'corporation_right';
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'corporationId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CorporationModel',
'constraint' => [
[
'table' => 'corporation',
'on-delete' => 'CASCADE'
]
]
],
'rightId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\RightModel',
'constraint' => [
[
'table' => 'right',
'on-delete' => 'CASCADE'
]
]
],
'roleId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\RoleModel',
'constraint' => [
[
'table' => 'role',
'on-delete' => 'CASCADE'
]
]
]
];
/**
* set map data by an associative array
* @param array $data
*/
public function setData($data){
unset($data['id']);
unset($data['created']);
unset($data['updated']);
foreach((array)$data as $key => $value){
if(!is_array($value)){
if($this->exists($key)){
$this->$key = $value;
}
}
}
}
/**
* get cooperation right data
* @return \stdClass
*/
public function getData(){
$cooperationRightData = (object) [];
$cooperationRightData->right = $this->rightId->getData();
$cooperationRightData->role = $this->roleId->getData();
return $cooperationRightData;
}
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
if($status === true){
$status = parent::setMultiColumnIndex(['corporationId', 'rightId'], true);
}
return $status;
}
}

View File

@@ -1359,14 +1359,18 @@ class MapModel extends AbstractMapTrackingModel {
/**
* get all maps
* @param array $mapIds
* @param array $options
* @return \DB\CortexCollection
*/
public static function getAll($mapIds = []){
public static function getAll($mapIds = [], $options = []){
$query = [
'active = :active AND id IN :mapIds',
':active' => 1,
'id IN :mapIds',
':mapIds' => $mapIds
];
if( !$options['addInactive'] ){
$query[0] .= ' AND active = :active';
$query[':active'] = 1;
}
return (new self())->find($query);
}

View File

@@ -53,23 +53,4 @@ class MapScopeModel extends BasicModel{
]
];
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
// set static default values
if($status === true){
$model = self::getNew(self::getClassName(), 0);
$model->importStaticData(self::$tableData);
}
return $status;
}
}

View File

@@ -81,24 +81,4 @@ class MapTypeModel extends BasicModel{
]
];
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
// set static default values
if($status === true){
$model = self::getNew(self::getClassName(), 0);
$model->importStaticData(self::$tableData);
}
return $status;
}
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 28.01.2018
* Time: 14:38
*/
namespace Model;
use DB\SQL\Schema;
class RightModel extends BasicModel {
protected $table = 'right';
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'label' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'description' => [
'type' => Schema::DT_VARCHAR512,
'nullable' => false,
'default' => ''
],
'corporationRights' => [
'has-many' => ['Model\CorporationRightModel', 'rightId']
]
];
protected static $tableData = [
[
'id' => 1,
'name' => 'map_update',
'label' => 'update',
'description' => 'Map settings update right'
],
[
'id' => 2,
'name' => 'map_delete',
'label' => 'delete',
'description' => 'Map delete right'
],
[
'id' => 3,
'name' => 'map_import',
'label' => 'import',
'description' => 'Map import right'
],
[
'id' => 4,
'name' => 'map_export',
'label' => 'export',
'description' => 'Map export right'
]
];
/**
* get right data
* @return \stdClass
*/
public function getData(){
$rightData = (object) [];
$rightData->name = $this->name;
return $rightData;
}
}

View File

@@ -0,0 +1,125 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 28.01.2018
* Time: 12:42
*/
namespace Model;
use DB\SQL\Schema;
class RoleModel extends BasicModel {
protected $table = 'role';
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'label' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'style' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'corporationRights' => [
'has-many' => ['Model\CorporationRightModel', 'roleId']
]
];
protected static $tableData = [
[
'id' => 1,
'name' => 'MEMBER',
'label' => 'member',
'style' => 'default'
],
[
'id' => 2,
'name' => 'SUPER',
'label' => 'admin',
'style' => 'danger'
],
[
'id' => 3,
'name' => 'CORPORATION',
'label' => 'manager',
'style' => 'info'
]
];
/**
* get role data
* @return \stdClass
*/
public function getData(){
$roleData = (object) [];
$roleData->name = $this->name;
$roleData->label = $this->label;
return $roleData;
}
/**
* get default role
* @return self|null
*/
public static function getDefaultRole(){
return self::getRoleById(1);
}
/**
* get admin role
* @return self|null
*/
public static function getAdminRole(){
return self::getRoleById(2);
}
/**
* get corporation admin role
* @return self|null
*/
public static function getCorporationManagerRole(){
return self::getRoleById(3);
}
/**
* get role by id
* @param int $roleId
* @return self|null
*/
public static function getRoleById(int $roleId = 1){
$role = (new self())->getById($roleId);
return $role->dry() ? null : $role;
}
/**
* get all corporations
* @return \DB\CortexCollection
*/
public static function getAll(){
$query = [
'active = :active',
':active' => 1
];
return (new self())->find($query);
}
}

View File

@@ -77,23 +77,4 @@ class SystemStatusModel extends BasicModel {
]
];
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
// set static default values
if($status === true){
$model = self::getNew(self::getClassName(), 0);
$model->importStaticData(self::$tableData);
}
return $status;
}
}

View File

@@ -39,23 +39,4 @@ class SystemTypeModel extends BasicModel {
]
];
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
// set static default values
if($status === true){
$model = self::getNew(self::getClassName(), 0);
$model->importStaticData(self::$tableData);
}
return $status;
}
}

View File

@@ -7,36 +7,48 @@ define([
'app/init',
'app/util',
'datatables.loader'
], function($, Init, Util) {
], ($, Init, Util) => {
'use strict';
let config = {
splashOverlayClass: 'pf-splash' // class for "splash" overlay
splashOverlayClass: 'pf-splash', // class for "splash" overlay
triggerOverlayClass: 'pf-overlay-trigger' // class for
};
/**
* set page observer
*/
let setPageObserver = () => {
$('.' + config.triggerOverlayClass).on('click', function(){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
};
/**
* main init "admin" page
*/
$(function(){
$(() => {
// set Dialog default config
Util.initDefaultBootboxConfig();
// hide splash loading animation
$('.' + config.splashOverlayClass).hideSplashOverlay();
setPageObserver();
let systemsDataTable = $('.dataTable').dataTable( {
let temp = $('.dataTable').dataTable( {
pageLength: 100,
paging: true,
ordering: true,
autoWidth: false,
hover: false,
language: {
emptyTable: 'No members',
zeroRecords: 'No members found',
lengthMenu: 'Show _MENU_ members',
info: 'Showing _START_ to _END_ of _TOTAL_ members'
emptyTable: 'No entries',
zeroRecords: 'No entries found',
lengthMenu: 'Show _MENU_ entries',
info: 'Showing _START_ to _END_ of _TOTAL_ entries'
}
});

View File

@@ -2,7 +2,7 @@
* Init
*/
define(['jquery'], function($) {
define(['jquery'], ($) => {
'use strict';
@@ -99,6 +99,14 @@ define(['jquery'], function($) {
class: 'fa-desktop',
label: 'desktop',
unicode: '&#xf108;'
},{
class: 'fa-space-shuttle',
label: 'space shuttle',
unicode: '&#xf197;'
},{
class: 'fa-anchor',
label: 'anchor',
unicode: '&#xf13d;'
},{
class: 'fa-bookmark',
label: 'bookmark',
@@ -107,6 +115,10 @@ define(['jquery'], function($) {
class: 'fa-cube',
label: 'cube',
unicode: '&#xf1b2;'
},{
class: 'fa-star',
label: 'star',
unicode: '&#xf005;'
},{
class: 'fa-plane',
label: 'plane',
@@ -148,7 +160,6 @@ define(['jquery'], function($) {
},
// system effects
systemEffects: {
effect: {
class: 'pf-system-effect',
name: 'no effect'

View File

@@ -18,7 +18,7 @@ define([
'app/map/contextmenu',
'app/map/overlay',
'app/map/local'
], function($, Init, Util, Render, bootbox, MapUtil, System, Layout, MagnetizerWrapper) {
], ($, Init, Util, Render, bootbox, MapUtil, System, Layout, MagnetizerWrapper) => {
'use strict';
@@ -74,10 +74,26 @@ define([
// -> those maps queue their updates until "pf:unlocked" event
let mapUpdateQueue = [];
/**
* checks mouse events on system head elements
* -> prevents drag/drop system AND drag/drop connections on some child elements
* @param e
* @param system
* @returns {boolean | *}
*/
let filterSystemHeadEvent = (e, system) => {
let target = $(e.target);
let effectClass = MapUtil.getEffectInfoForSystem('effect', 'class');
return (
target.hasClass(config.systemHeadNameClass) ||
target.hasClass(effectClass)
);
};
// jsPlumb config
let globalMapConfig = {
source: {
filter: '.' + config.systemHeadNameClass,
filter: filterSystemHeadEvent,
//isSource:true,
isTarget:true, // add target Endpoint to each system (e.g. for drag&drop)
allowLoopback: false, // loopBack connections are not allowed
@@ -91,7 +107,7 @@ define([
anchor: 'Continuous'
},
target: {
filter: '.' + config.systemHeadNameClass,
filter: filterSystemHeadEvent,
isSource:true,
//isTarget:true,
//allowLoopBack: false, // loopBack connections are not allowed
@@ -107,6 +123,8 @@ define([
connectionTypes: Init.connectionTypes
};
/**
* updates a system with current information
* @param map
@@ -499,8 +517,7 @@ define([
class: config.systemHeadClass
}).append(
// System name is editable
$('<a>', {
href: '#',
$('<span>', {
class: config.systemHeadNameClass
}).attr('data-value', systemName)
).append(
@@ -931,33 +948,36 @@ define([
let setMapWrapperObserver = (mapWrapper, mapConfig) => {
// map resize observer ----------------------------------------------------------------------------------------
let resizeTimer;
let wrapperResize = new ResizeObserver(entries => { // jshint ignore:line
/**
* save current map dimension to local storage
* @param entry
*/
let saveMapSize = (entry) => {
return setTimeout(() => {
let width = entry.target.style.width;
let height = entry.target.style.height;
width = parseInt( width.substring(0, width.length - 2) ) || 0;
height = parseInt( height.substring(0, height.length - 2) ) || 0;
if(window.ResizeObserver) {
let resizeTimer;
let wrapperResize = new ResizeObserver(entries => { // jshint ignore:line
/**
* save current map dimension to local storage
* @param entry
*/
let saveMapSize = (entry) => {
return setTimeout(() => {
let width = entry.target.style.width;
let height = entry.target.style.height;
width = parseInt( width.substring(0, width.length - 2) ) || 0;
height = parseInt( height.substring(0, height.length - 2) ) || 0;
MapUtil.storeLocalData('map', mapConfig.config.id, 'style', {
width: width,
height: height
});
}, 100);
};
for (let entry of entries){
// use timeout to "throttle" save actions
clearTimeout(resizeTimer);
resizeTimer = saveMapSize(entry);
}
});
MapUtil.storeLocalData('map', mapConfig.config.id, 'style', {
width: width,
height: height
});
}, 100);
};
for (let entry of entries){
// use timeout to "throttle" save actions
clearTimeout(resizeTimer);
resizeTimer = saveMapSize(entry);
}
});
wrapperResize.observe(mapWrapper[0]);
}
wrapperResize.observe(mapWrapper[0]);
};
/**
@@ -2155,7 +2175,7 @@ define([
containment: 'parent',
constrain: true,
//scroll: true, // not working because of customized scrollbar
filter: '.' + config.systemHeadNameClass, // disable drag on "system name"
filter: filterSystemHeadEvent,
snapThreshold: MapUtil.config.mapSnapToGridDimension, // distance for grid snapping "magnet" effect (optional)
start: function(params){
let dragSystem = $(params.el);
@@ -3323,7 +3343,7 @@ define([
* removes a map instance from local cache
* @param mapId
*/
let clearMapInstance = function(mapId){
let clearMapInstance = (mapId) => {
if(typeof activeInstances[mapId] === 'object'){
delete activeInstances[mapId];
}
@@ -3356,8 +3376,8 @@ define([
* @param mapName
* @param mapContainer
*/
let switchTabCallback = (mapName, mapContainer) => {
Util.showNotify({title: 'Map initialized', text: mapName + ' - loaded', type: 'success'});
let switchTabCallback = (mapContainer, mapConfig) => {
Util.showNotify({title: 'Map initialized', text: mapConfig.name + ' - loaded', type: 'success'});
let mapWrapper = mapContainer.parents('.' + config.mapWrapperClass);
@@ -3370,6 +3390,11 @@ define([
}
});
// update main menu options based on the active map -----------------------------------------------
$(document).trigger('pf:updateMenuOptions', {
mapConfig: mapConfig
});
// init magnetizer --------------------------------------------------------------------------------
mapContainer.triggerMenuEvent('MapOption', {
option: 'mapMagnetizer',
@@ -3392,7 +3417,7 @@ define([
// show nice visualization effect ---------------------------------------------------------------------
let mapContainer = $(mapConfig.map.getContainer());
mapContainer.visualizeMap('show', function(){
switchTabCallback( mapConfig.config.name, mapContainer );
switchTabCallback(mapContainer, mapConfig.config);
});
}

View File

@@ -7,7 +7,7 @@ define([
'app/init',
'app/util',
'bootbox'
], function($, Init, Util, bootbox) {
], ($, Init, Util, bootbox) => {
'use strict';
let config = {
@@ -891,7 +891,7 @@ define([
$.fn.setMapShortcuts = function(){
return this.each((i, mapWrapper) => {
mapWrapper = $(mapWrapper);
let mapElement = mapWrapper.findMapElement();
let mapElement = mapWrapper.find('.' + config.mapClass);
// dynamic require Map module -> otherwise there is a require(), loop
let Map = require('app/map/map');
@@ -963,10 +963,6 @@ define([
});
};
$.fn.findMapElement = function(){
return $(this).find('.' + config.mapClass);
};
/**
* get systemId string (selector
* @param mapId
@@ -977,6 +973,72 @@ define([
return config.systemIdPrefix + mapId + '-' + systemId;
};
/**
* check whether the current User has access for a given right
* based on a given mapConfig
* @param right
* @param mapConfig
* @returns {boolean}
*/
let checkRight = (right, mapConfig) => {
let hasAccess = false;
let currentUserData = Util.getCurrentUserData();
if(currentUserData){
// ...there is an active user
let currentCharacterData = Util.getObjVal(currentUserData, 'character');
if(currentCharacterData){
// ... there is an active character
let currentCharacterRole = Util.getObjVal(currentCharacterData, 'role');
if(currentCharacterRole){
// ... active character has a role assigned
let mapType = Util.getObjVal(mapConfig, 'type.name');
let mapAccess = Util.getObjVal(mapConfig, 'access.' + (mapType === 'private' ? 'character' : mapType)) || [];
// this is either Ally/Corp or Character Id
let accessObjectId = Util.getCurrentUserInfo(mapType + 'Id');
// check whether character has map access
let hasMapAccess = mapAccess.some((accessObj) => {
return (accessObj.id === accessObjectId);
});
if(hasMapAccess){
// ... this should ALWAYS be be true!
switch(mapType){
case 'private':
hasAccess = true;
break;
case 'corporation':
let objectRights = Util.getObjVal(currentCharacterData, mapType + '.rights') || [];
let objectRight = objectRights.find((objectRight) => {
return objectRight.right.name === right;
});
if(objectRight){
// ... Ally/Corp has the right we are looking for assigned with a required role
if(
currentCharacterRole.name === 'SUPER' ||
objectRight.role.name === 'MEMBER' ||
objectRight.role.name === currentCharacterRole.name
){
hasAccess = true;
}
}
break;
case 'alliance':
hasAccess = true;
break;
}
}
}
}
}
return hasAccess;
};
return {
config: config,
mapOptions: mapOptions,
@@ -1010,6 +1072,7 @@ define([
getLocaleData: getLocaleData,
storeLocalData: storeLocalData,
deleteLocalData: deleteLocalData,
getSystemId: getSystemId
getSystemId: getSystemId,
checkRight: checkRight
};
});

View File

@@ -12,7 +12,7 @@ define([
'app/ui/system_killboard',
'app/ui/connection_info',
'app/counter'
], function(
], (
$,
Init,
Util,
@@ -25,7 +25,7 @@ define([
SystemRouteModule,
SystemKillboardModule,
ConnectionInfoModule
){
) => {
'use strict';
let config = {
@@ -565,12 +565,12 @@ define([
e.preventDefault();
// callback function after tab switch
function switchTabCallback(mapElement, tabLinkElement){
let switchTabCallback = (mapElement, tabLinkElement) => {
tabLinkElement.tab('show');
// unfreeze map
mapElement.data('frozen', false);
return false;
}
};
if(mapTabChangeBlocked === false){
let tabLinkElement = $(this);
@@ -611,7 +611,6 @@ define([
$(document).trigger('pf:menuShowMapSettings', {tab: 'new'});
e.preventDefault();
}
});
$(tabListElement).on('shown.bs.tab', 'a', function(e){

View File

@@ -26,7 +26,7 @@ define([
'xEditable',
'slidebars',
'app/module_map'
], function($, Init, Util, Logging, Mustache, MapUtil, TplLogo, TplHead, TplFooter) {
], ($, Init, Util, Logging, Mustache, MapUtil, TplLogo, TplHead, TplFooter) => {
'use strict';
@@ -166,7 +166,7 @@ define([
* @param title
* @returns {JQuery|*|jQuery}
*/
let getMenuHeadline = function(title){
let getMenuHeadline = (title) => {
return $('<div>', {
class: 'panel-heading'
}).prepend(
@@ -388,7 +388,7 @@ define([
class: 'list-group-item list-group-item-info'
}).html('&nbsp;&nbsp;Shortcuts').prepend(
$('<i>',{
class: 'far fa-keyboard fa-fw'
class: 'fas fa-keyboard fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('Shortcuts');
@@ -407,7 +407,8 @@ define([
getMenuHeadline('Danger zone')
).append(
$('<a>', {
class: 'list-group-item list-group-item-danger'
class: 'list-group-item list-group-item-danger',
id: Util.config.menuButtonMapDeleteId
}).html('&nbsp;&nbsp;Delete map').prepend(
$('<i>',{
class: 'fas fa-trash fa-fw'
@@ -569,9 +570,10 @@ define([
* catch all global document events
*/
let setPageObserver = function(){
let documentElement = $(document);
// on "full-screen" change event
$(document).on('fscreenchange', function(e, state, elem){
documentElement.on('fscreenchange', function(e, state, elem){
let menuButton = $('#' + Util.config.menuButtonFullScreenId);
@@ -587,67 +589,67 @@ define([
}
});
$(document).on('pf:menuShowStatsDialog', function(e){
documentElement.on('pf:menuShowStatsDialog', function(e){
// show user activity stats dialog
$.fn.showStatsDialog();
return false;
});
$(document).on('pf:menuShowSystemEffectInfo', function(e){
documentElement.on('pf:menuShowSystemEffectInfo', function(e){
// show system effects dialog
$.fn.showSystemEffectInfoDialog();
return false;
});
$(document).on('pf:menuShowJumpInfo', function(e){
documentElement.on('pf:menuShowJumpInfo', function(e){
// show system effects info box
$.fn.showJumpInfoDialog();
return false;
});
$(document).on('pf:menuNotificationTest', function(e){
documentElement.on('pf:menuNotificationTest', function(e){
// show system effects info box
notificationTest();
return false;
});
$(document).on('pf:menuDeleteAccount', function(e){
documentElement.on('pf:menuDeleteAccount', function(e){
// show "delete account" dialog
$.fn.showDeleteAccountDialog();
return false;
});
$(document).on('pf:menuManual', function(e){
documentElement.on('pf:menuManual', function(e){
// show map manual
$.fn.showMapManual();
return false;
});
$(document).on('pf:menuShowTaskManager', function(e, data){
documentElement.on('pf:menuShowTaskManager', function(e, data){
// show log dialog
Logging.showDialog();
return false;
});
$(document).on('pf:menuShortcuts', function(e, data){
documentElement.on('pf:menuShortcuts', function(e, data){
// show shortcuts dialog
$.fn.showShortcutsDialog();
return false;
});
$(document).on('pf:menuShowSettingsDialog', function(e){
documentElement.on('pf:menuShowSettingsDialog', function(e){
// show character select dialog
$.fn.showSettingsDialog();
return false;
});
$(document).on('pf:menuShowMapInfo', function(e, data){
documentElement.on('pf:menuShowMapInfo', function(e, data){
// show map information dialog
$.fn.showMapInfoDialog(data);
return false;
});
$(document).on('pf:menuShowMapSettings', function(e, data){
documentElement.on('pf:menuShowMapSettings', function(e, data){
// show map edit dialog or edit map
let mapData = false;
@@ -661,7 +663,7 @@ define([
return false;
});
$(document).on('pf:menuDeleteMap', function(e){
documentElement.on('pf:menuDeleteMap', function(e){
// delete current active map
let mapData = false;
@@ -675,7 +677,7 @@ define([
return false;
});
$(document).on('pf:menuLogout', function(e, data){
documentElement.on('pf:menuLogout', function(e, data){
let clearCookies = false;
if(
@@ -701,8 +703,14 @@ define([
$(this).destroyTimestampCounter();
});
// disable memue links based on current map config
documentElement.on('pf:updateMenuOptions', function(e, data){
let hasRightMapDelete = MapUtil.checkRight('map_delete', data.mapConfig);
$('#' + Util.config.menuButtonMapDeleteId).toggleClass('disabled', !hasRightMapDelete);
});
// update header links with current map data
$(document).on('pf:updateHeaderMapData', function(e, data){
documentElement.on('pf:updateHeaderMapData', function(e, data){
let activeMap = Util.getMapModule().getActiveMap();
let userCount = 0;
@@ -721,7 +729,7 @@ define([
});
// shutdown the program -> show dialog
$(document).on('pf:shutdown', function(e, data){
documentElement.on('pf:shutdown', function(e, data){
// show shutdown dialog
let options = {
buttons: {
@@ -735,7 +743,7 @@ define([
// redirect to login
window.location = '../';
}else{
$(document).trigger('pf:menuLogout');
documentElement.trigger('pf:menuLogout');
}
}
}
@@ -764,7 +772,7 @@ define([
$.fn.showNotificationDialog(options);
$(document).setProgramStatus('offline');
documentElement.setProgramStatus('offline');
Util.showNotify({title: 'Logged out', text: data.reason, type: 'error'}, false);

View File

@@ -44,6 +44,8 @@ define([
requirejs(['text!templates/dialog/settings.html', 'mustache'], function(template, Mustache) {
let characterRoleName = Util.getCurrentUserInfo('roleName');
let data = {
id: config.settingsDialogId,
settingsAccountContainerId: config.settingsAccountContainerId,
@@ -53,7 +55,15 @@ define([
captchaImageWrapperId: config.captchaImageWrapperId,
captchaImageId: config.captchaImageId,
formErrorContainerClass: Util.config.formErrorContainerClass,
ccpImageServer: Init.url.ccpImageServer
ccpImageServer: Init.url.ccpImageServer,
roleLabelClass: () => {
let label = 'label-default';
switch(characterRoleName){
case 'SUPER': label = 'label-danger'; break;
case 'CORPORATION': label = 'label-info'; break;
}
return label;
}
};
let content = Mustache.render(template, data);

View File

@@ -10,7 +10,7 @@ define([
'bootbox',
'app/map/util',
'app/module_map'
], function($, Init, Util, Render, bootbox, MapUtil, ModuleMap) {
], ($, Init, Util, Render, bootbox, MapUtil, ModuleMap) => {
'use strict';
let config = {
@@ -83,7 +83,7 @@ define([
'text!templates/dialog/map.html',
'text!templates/form/map.html',
'mustache'
], function(templateMapDialog, templateMapSettings, Mustache) {
], (templateMapDialog, templateMapForm, Mustache) => {
let dialogTitle = 'Map settings';
@@ -92,16 +92,25 @@ define([
let hideEditTab = false;
let hideDownloadTab = false;
let hasRightMapCreate = true;
let hasRightMapUpdate = true;
let hasRightMapExport = true;
let hasRightMapImport = true;
if(mapData === false){
hideSettingsTab = true;
hideEditTab = true;
hideDownloadTab = true;
}else{
hasRightMapUpdate = MapUtil.checkRight('map_update', mapData.config);
hasRightMapExport = MapUtil.checkRight('map_export', mapData.config);
hasRightMapImport = MapUtil.checkRight('map_import', mapData.config);
}
// available map "types" for a new or existing map
let mapTypes = MapUtil.getMapTypes(true);
let data = {
let mapFormData = {
scope: MapUtil.getMapScopes(),
type: mapTypes,
icon: MapUtil.getMapIcons(),
@@ -111,10 +120,16 @@ define([
};
// render "new map" tab content -----------------------------------------------------------------------
let contentNewMap = Mustache.render(templateMapSettings, data);
let mapFormDataNew = $.extend({}, mapFormData, {
hasRightMapForm: hasRightMapCreate
});
let contentNewMap = Mustache.render(templateMapForm, mapFormDataNew);
// render "edit map" tab content ----------------------------------------------------------------------
let contentEditMap = Mustache.render(templateMapSettings, data);
let mapFormDataEdit = $.extend({}, mapFormData, {
hasRightMapForm: hasRightMapUpdate
});
let contentEditMap = Mustache.render(templateMapForm, mapFormDataEdit);
contentEditMap = $(contentEditMap);
// current map access info
@@ -189,7 +204,7 @@ define([
}
// render main dialog ---------------------------------------------------------------------------------
data = {
let mapDialogData = {
id: config.newMapDialogId,
mapData: mapData,
type: mapTypes,
@@ -276,6 +291,10 @@ define([
fieldImportId: config.fieldImportId,
dialogMapImportInfoId: config.dialogMapImportInfoId,
hasRightMapUpdate: hasRightMapUpdate,
hasRightMapExport: hasRightMapExport,
hasRightMapImport: hasRightMapImport,
formatFilename: function(){
// format filename from "map name" (initial)
return function (mapName, render) {
@@ -285,7 +304,7 @@ define([
}
};
let contentDialog = Mustache.render(templateMapDialog, data);
let contentDialog = Mustache.render(templateMapDialog, mapDialogData);
contentDialog = $(contentDialog);
// set tab content
@@ -553,8 +572,10 @@ define([
};
let dropZone = downloadTabElement.find('.' + config.dragDropElementClass);
dropZone[0].addEventListener('dragover', handleDragOver, false);
dropZone[0].addEventListener('drop', handleFileSelect, false);
if(dropZone.length){
dropZone[0].addEventListener('dragover', handleDragOver, false);
dropZone[0].addEventListener('drop', handleFileSelect, false);
}
// import "button"
downloadTabElement.find('#' + config.buttonImportId).on('click', function(e) {
@@ -587,12 +608,13 @@ define([
// events for tab change ------------------------------------------------------------------------------
mapInfoDialog.find('.navbar a').on('shown.bs.tab', function(e){
let modalDialog = mapInfoDialog.find('div.modal-dialog');
let tabContentId = $(e.target).attr('href');
let tabContentForms = $(tabContentId).find('form.form-horizontal');
let selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
let selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
let selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
if($(e.target).attr('href') === '#' + config.dialogMapSettingsContainerId){
if(tabContentId === '#' + config.dialogMapSettingsContainerId){
// "settings" tab -> resize modal
modalDialog.toggleClass('modal-lg', true);
initSettingsSelectFields(mapInfoDialog);
@@ -614,7 +636,10 @@ define([
}
// no "save" dialog button on "in/export" tab
if($(e.target).attr('href') === '#' + config.dialogMapDownloadContainerId){
if(
tabContentId === '#' + config.dialogMapDownloadContainerId || // no "save" dialog button on "in/export" tab
!tabContentForms.length // no <form> in tab (e.g. restricted by missing right)
){
mapInfoDialog.find('button.btn-success').hide();
}else{
mapInfoDialog.find('button.btn-success').show();

View File

@@ -16,7 +16,7 @@ define([
'hoverIntent',
'bootstrapConfirmation',
'bootstrapToggle'
], function($, Init, SystemEffect, SignatureType, bootbox, localforage) {
], ($, Init, SystemEffect, SignatureType, bootbox, localforage) => {
'use strict';
@@ -36,11 +36,11 @@ define([
headCurrentLocationId: 'pf-head-current-location', // id for "show current location" element
// menu
menuButtonFullScreenId: 'pf-menu-button-fullscreen', // id for menu button "fullscreen"
menuButtonFullScreenId: 'pf-menu-button-fullscreen', // id for menu button "fullScreen"
menuButtonMagnetizerId: 'pf-menu-button-magnetizer', // id for menu button "magnetizer"
menuButtonGridId: 'pf-menu-button-grid', // id for menu button "grid snap"
menuButtonEndpointId: 'pf-menu-button-endpoint', // id for menu button "endpoint" overlays
menuButtonMapDeleteId: 'pf-menu-button-map-delete', // id for menu button "delete map"
settingsMessageVelocityOptions: {
duration: 180
@@ -565,10 +565,18 @@ define([
browserTabId: getBrowserTabId(),
routes: Init.routes,
userData: userData,
otherCharacters: $.grep( userData.characters, function( character ) {
// exclude current active character
return character.id !== userData.character.id;
})
otherCharacters: () => {
return userData.characters.filter((character, i) => {
let characterImage = Init.url.ccpImageServer + '/Character/' + character.id + '_32.jpg';
// preload image (prevent UI flicker
let img= new Image();
img.src = characterImage;
userData.characters[i].image = characterImage;
return character.id !== userData.character.id;
});
}
};
let content = Mustache.render(template, data);
@@ -832,10 +840,85 @@ define([
console.info('PATHFINDER ' + getVersion());
};
/**
* polyfill for "passive" events
* -> see https://github.com/zzarcon/default-passive-events
*/
let initPassiveEvents = () => {
const defaultOptions = {
passive: true,
capture: false
};
const supportedPassiveTypes = [
'scroll', 'wheel',
'touchstart', 'touchmove', 'touchenter', 'touchend', 'touchleave',
'mouseout', 'mouseleave', 'mouseup', 'mousedown', 'mousemove', 'mouseenter', 'mousewheel', 'mouseover'
];
const getDefaultPassiveOption = (passive, eventName) => {
if (passive !== undefined) return passive;
return supportedPassiveTypes.indexOf(eventName) === -1 ? false : defaultOptions.passive;
};
const getWritableOptions = (options) => {
const passiveDescriptor = Object.getOwnPropertyDescriptor(options, 'passive');
return passiveDescriptor &&
passiveDescriptor.writable !== true &&
passiveDescriptor.set === undefined ? Object.assign({}, options) : options;
};
const prepareSafeListener = (listener, passive) => {
if (!passive) return listener;
return function (e) {
e.preventDefault = () => {};
return listener.call(this, e);
};
};
const overwriteAddEvent = (superMethod) => {
EventTarget.prototype.addEventListener = function (type, listener, options) { // jshint ignore:line
const usesListenerOptions = typeof options === 'object';
const useCapture = usesListenerOptions ? options.capture : options;
options = usesListenerOptions ? getWritableOptions(options) : {};
options.passive = getDefaultPassiveOption(options.passive, type);
options.capture = useCapture === undefined ? defaultOptions.capture : useCapture;
listener = prepareSafeListener(listener, options.passive);
superMethod.call(this, type, listener, options);
};
};
let eventListenerOptionsSupported = () => {
let supported = false;
try {
const opts = Object.defineProperty({}, 'passive', {
get() {
supported = true;
}
});
window.addEventListener('test', null, opts);
window.removeEventListener('test', null, opts);
} catch (e) {}
return supported;
};
let supportsPassive = eventListenerOptionsSupported ();
if (supportsPassive) {
const addEvent = EventTarget.prototype.addEventListener; // jshint ignore:line
overwriteAddEvent(addEvent);
}
};
/**
* init utility prototype functions
*/
let initPrototypes = function(){
let initPrototypes = () => {
// Array diff
// [1,2,3,4,5,6].diff( [3,4,5] );
// => [1, 2, 6]
@@ -853,6 +936,8 @@ define([
return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
});
};
initPassiveEvents();
};
/**
@@ -1807,6 +1892,10 @@ define([
if(option === 'corporationId' && characterData.corporation){
userInfo = characterData.corporation.id;
}
if(option === 'roleName' && characterData.role){
userInfo = characterData.role.name;
}
}
}

View File

@@ -155,6 +155,7 @@ $.fn.dragToSelect = function (conf) {
}
// get scroll position
/*
var leftScroll = 0;
var topScroll = 0;
@@ -165,14 +166,15 @@ $.fn.dragToSelect = function (conf) {
if(realParent.attr('data-scroll-top')){
topScroll = parseInt(realParent.attr('data-scroll-top'));
}
*/
var left = lastMousePosition.x - parentDim.left + parent[0].scrollLeft;
var top = lastMousePosition.y - parentDim.top + parent[0].scrollTop;
var tempWidth = selectBoxOrigin.left - left;
var tempHeight = selectBoxOrigin.top - top;
let newLeft = selectBoxOrigin.left - leftScroll;
let newTop = selectBoxOrigin.top - topScroll;
let newLeft = selectBoxOrigin.left;// - leftScroll;
let newTop = selectBoxOrigin.top;// - topScroll;
var newWidth = left - selectBoxOrigin.left;
var newHeight = top - selectBoxOrigin.top;

File diff suppressed because one or more lines are too long

View File

@@ -7,36 +7,48 @@ define([
'app/init',
'app/util',
'datatables.loader'
], function($, Init, Util) {
], ($, Init, Util) => {
'use strict';
let config = {
splashOverlayClass: 'pf-splash' // class for "splash" overlay
splashOverlayClass: 'pf-splash', // class for "splash" overlay
triggerOverlayClass: 'pf-overlay-trigger' // class for
};
/**
* set page observer
*/
let setPageObserver = () => {
$('.' + config.triggerOverlayClass).on('click', function(){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
};
/**
* main init "admin" page
*/
$(function(){
$(() => {
// set Dialog default config
Util.initDefaultBootboxConfig();
// hide splash loading animation
$('.' + config.splashOverlayClass).hideSplashOverlay();
setPageObserver();
let systemsDataTable = $('.dataTable').dataTable( {
let temp = $('.dataTable').dataTable( {
pageLength: 100,
paging: true,
ordering: true,
autoWidth: false,
hover: false,
language: {
emptyTable: 'No members',
zeroRecords: 'No members found',
lengthMenu: 'Show _MENU_ members',
info: 'Showing _START_ to _END_ of _TOTAL_ members'
emptyTable: 'No entries',
zeroRecords: 'No entries found',
lengthMenu: 'Show _MENU_ entries',
info: 'Showing _START_ to _END_ of _TOTAL_ entries'
}
});

View File

@@ -2,7 +2,7 @@
* Init
*/
define(['jquery'], function($) {
define(['jquery'], ($) => {
'use strict';
@@ -99,6 +99,14 @@ define(['jquery'], function($) {
class: 'fa-desktop',
label: 'desktop',
unicode: '&#xf108;'
},{
class: 'fa-space-shuttle',
label: 'space shuttle',
unicode: '&#xf197;'
},{
class: 'fa-anchor',
label: 'anchor',
unicode: '&#xf13d;'
},{
class: 'fa-bookmark',
label: 'bookmark',
@@ -107,6 +115,10 @@ define(['jquery'], function($) {
class: 'fa-cube',
label: 'cube',
unicode: '&#xf1b2;'
},{
class: 'fa-star',
label: 'star',
unicode: '&#xf005;'
},{
class: 'fa-plane',
label: 'plane',
@@ -148,7 +160,6 @@ define(['jquery'], function($) {
},
// system effects
systemEffects: {
effect: {
class: 'pf-system-effect',
name: 'no effect'

View File

@@ -18,7 +18,7 @@ define([
'app/map/contextmenu',
'app/map/overlay',
'app/map/local'
], function($, Init, Util, Render, bootbox, MapUtil, System, Layout, MagnetizerWrapper) {
], ($, Init, Util, Render, bootbox, MapUtil, System, Layout, MagnetizerWrapper) => {
'use strict';
@@ -74,10 +74,26 @@ define([
// -> those maps queue their updates until "pf:unlocked" event
let mapUpdateQueue = [];
/**
* checks mouse events on system head elements
* -> prevents drag/drop system AND drag/drop connections on some child elements
* @param e
* @param system
* @returns {boolean | *}
*/
let filterSystemHeadEvent = (e, system) => {
let target = $(e.target);
let effectClass = MapUtil.getEffectInfoForSystem('effect', 'class');
return (
target.hasClass(config.systemHeadNameClass) ||
target.hasClass(effectClass)
);
};
// jsPlumb config
let globalMapConfig = {
source: {
filter: '.' + config.systemHeadNameClass,
filter: filterSystemHeadEvent,
//isSource:true,
isTarget:true, // add target Endpoint to each system (e.g. for drag&drop)
allowLoopback: false, // loopBack connections are not allowed
@@ -91,7 +107,7 @@ define([
anchor: 'Continuous'
},
target: {
filter: '.' + config.systemHeadNameClass,
filter: filterSystemHeadEvent,
isSource:true,
//isTarget:true,
//allowLoopBack: false, // loopBack connections are not allowed
@@ -107,6 +123,8 @@ define([
connectionTypes: Init.connectionTypes
};
/**
* updates a system with current information
* @param map
@@ -499,8 +517,7 @@ define([
class: config.systemHeadClass
}).append(
// System name is editable
$('<a>', {
href: '#',
$('<span>', {
class: config.systemHeadNameClass
}).attr('data-value', systemName)
).append(
@@ -931,33 +948,36 @@ define([
let setMapWrapperObserver = (mapWrapper, mapConfig) => {
// map resize observer ----------------------------------------------------------------------------------------
let resizeTimer;
let wrapperResize = new ResizeObserver(entries => { // jshint ignore:line
/**
* save current map dimension to local storage
* @param entry
*/
let saveMapSize = (entry) => {
return setTimeout(() => {
let width = entry.target.style.width;
let height = entry.target.style.height;
width = parseInt( width.substring(0, width.length - 2) ) || 0;
height = parseInt( height.substring(0, height.length - 2) ) || 0;
if(window.ResizeObserver) {
let resizeTimer;
let wrapperResize = new ResizeObserver(entries => { // jshint ignore:line
/**
* save current map dimension to local storage
* @param entry
*/
let saveMapSize = (entry) => {
return setTimeout(() => {
let width = entry.target.style.width;
let height = entry.target.style.height;
width = parseInt( width.substring(0, width.length - 2) ) || 0;
height = parseInt( height.substring(0, height.length - 2) ) || 0;
MapUtil.storeLocalData('map', mapConfig.config.id, 'style', {
width: width,
height: height
});
}, 100);
};
for (let entry of entries){
// use timeout to "throttle" save actions
clearTimeout(resizeTimer);
resizeTimer = saveMapSize(entry);
}
});
MapUtil.storeLocalData('map', mapConfig.config.id, 'style', {
width: width,
height: height
});
}, 100);
};
for (let entry of entries){
// use timeout to "throttle" save actions
clearTimeout(resizeTimer);
resizeTimer = saveMapSize(entry);
}
});
wrapperResize.observe(mapWrapper[0]);
}
wrapperResize.observe(mapWrapper[0]);
};
/**
@@ -2155,7 +2175,7 @@ define([
containment: 'parent',
constrain: true,
//scroll: true, // not working because of customized scrollbar
filter: '.' + config.systemHeadNameClass, // disable drag on "system name"
filter: filterSystemHeadEvent,
snapThreshold: MapUtil.config.mapSnapToGridDimension, // distance for grid snapping "magnet" effect (optional)
start: function(params){
let dragSystem = $(params.el);
@@ -3323,7 +3343,7 @@ define([
* removes a map instance from local cache
* @param mapId
*/
let clearMapInstance = function(mapId){
let clearMapInstance = (mapId) => {
if(typeof activeInstances[mapId] === 'object'){
delete activeInstances[mapId];
}
@@ -3356,8 +3376,8 @@ define([
* @param mapName
* @param mapContainer
*/
let switchTabCallback = (mapName, mapContainer) => {
Util.showNotify({title: 'Map initialized', text: mapName + ' - loaded', type: 'success'});
let switchTabCallback = (mapContainer, mapConfig) => {
Util.showNotify({title: 'Map initialized', text: mapConfig.name + ' - loaded', type: 'success'});
let mapWrapper = mapContainer.parents('.' + config.mapWrapperClass);
@@ -3370,6 +3390,11 @@ define([
}
});
// update main menu options based on the active map -----------------------------------------------
$(document).trigger('pf:updateMenuOptions', {
mapConfig: mapConfig
});
// init magnetizer --------------------------------------------------------------------------------
mapContainer.triggerMenuEvent('MapOption', {
option: 'mapMagnetizer',
@@ -3392,7 +3417,7 @@ define([
// show nice visualization effect ---------------------------------------------------------------------
let mapContainer = $(mapConfig.map.getContainer());
mapContainer.visualizeMap('show', function(){
switchTabCallback( mapConfig.config.name, mapContainer );
switchTabCallback(mapContainer, mapConfig.config);
});
}

View File

@@ -7,7 +7,7 @@ define([
'app/init',
'app/util',
'bootbox'
], function($, Init, Util, bootbox) {
], ($, Init, Util, bootbox) => {
'use strict';
let config = {
@@ -891,7 +891,7 @@ define([
$.fn.setMapShortcuts = function(){
return this.each((i, mapWrapper) => {
mapWrapper = $(mapWrapper);
let mapElement = mapWrapper.findMapElement();
let mapElement = mapWrapper.find('.' + config.mapClass);
// dynamic require Map module -> otherwise there is a require(), loop
let Map = require('app/map/map');
@@ -963,10 +963,6 @@ define([
});
};
$.fn.findMapElement = function(){
return $(this).find('.' + config.mapClass);
};
/**
* get systemId string (selector
* @param mapId
@@ -977,6 +973,72 @@ define([
return config.systemIdPrefix + mapId + '-' + systemId;
};
/**
* check whether the current User has access for a given right
* based on a given mapConfig
* @param right
* @param mapConfig
* @returns {boolean}
*/
let checkRight = (right, mapConfig) => {
let hasAccess = false;
let currentUserData = Util.getCurrentUserData();
if(currentUserData){
// ...there is an active user
let currentCharacterData = Util.getObjVal(currentUserData, 'character');
if(currentCharacterData){
// ... there is an active character
let currentCharacterRole = Util.getObjVal(currentCharacterData, 'role');
if(currentCharacterRole){
// ... active character has a role assigned
let mapType = Util.getObjVal(mapConfig, 'type.name');
let mapAccess = Util.getObjVal(mapConfig, 'access.' + (mapType === 'private' ? 'character' : mapType)) || [];
// this is either Ally/Corp or Character Id
let accessObjectId = Util.getCurrentUserInfo(mapType + 'Id');
// check whether character has map access
let hasMapAccess = mapAccess.some((accessObj) => {
return (accessObj.id === accessObjectId);
});
if(hasMapAccess){
// ... this should ALWAYS be be true!
switch(mapType){
case 'private':
hasAccess = true;
break;
case 'corporation':
let objectRights = Util.getObjVal(currentCharacterData, mapType + '.rights') || [];
let objectRight = objectRights.find((objectRight) => {
return objectRight.right.name === right;
});
if(objectRight){
// ... Ally/Corp has the right we are looking for assigned with a required role
if(
currentCharacterRole.name === 'SUPER' ||
objectRight.role.name === 'MEMBER' ||
objectRight.role.name === currentCharacterRole.name
){
hasAccess = true;
}
}
break;
case 'alliance':
hasAccess = true;
break;
}
}
}
}
}
return hasAccess;
};
return {
config: config,
mapOptions: mapOptions,
@@ -1010,6 +1072,7 @@ define([
getLocaleData: getLocaleData,
storeLocalData: storeLocalData,
deleteLocalData: deleteLocalData,
getSystemId: getSystemId
getSystemId: getSystemId,
checkRight: checkRight
};
});

View File

@@ -12,7 +12,7 @@ define([
'app/ui/system_killboard',
'app/ui/connection_info',
'app/counter'
], function(
], (
$,
Init,
Util,
@@ -25,7 +25,7 @@ define([
SystemRouteModule,
SystemKillboardModule,
ConnectionInfoModule
){
) => {
'use strict';
let config = {
@@ -565,12 +565,12 @@ define([
e.preventDefault();
// callback function after tab switch
function switchTabCallback(mapElement, tabLinkElement){
let switchTabCallback = (mapElement, tabLinkElement) => {
tabLinkElement.tab('show');
// unfreeze map
mapElement.data('frozen', false);
return false;
}
};
if(mapTabChangeBlocked === false){
let tabLinkElement = $(this);
@@ -611,7 +611,6 @@ define([
$(document).trigger('pf:menuShowMapSettings', {tab: 'new'});
e.preventDefault();
}
});
$(tabListElement).on('shown.bs.tab', 'a', function(e){

View File

@@ -26,7 +26,7 @@ define([
'xEditable',
'slidebars',
'app/module_map'
], function($, Init, Util, Logging, Mustache, MapUtil, TplLogo, TplHead, TplFooter) {
], ($, Init, Util, Logging, Mustache, MapUtil, TplLogo, TplHead, TplFooter) => {
'use strict';
@@ -166,7 +166,7 @@ define([
* @param title
* @returns {JQuery|*|jQuery}
*/
let getMenuHeadline = function(title){
let getMenuHeadline = (title) => {
return $('<div>', {
class: 'panel-heading'
}).prepend(
@@ -388,7 +388,7 @@ define([
class: 'list-group-item list-group-item-info'
}).html('&nbsp;&nbsp;Shortcuts').prepend(
$('<i>',{
class: 'far fa-keyboard fa-fw'
class: 'fas fa-keyboard fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('Shortcuts');
@@ -407,7 +407,8 @@ define([
getMenuHeadline('Danger zone')
).append(
$('<a>', {
class: 'list-group-item list-group-item-danger'
class: 'list-group-item list-group-item-danger',
id: Util.config.menuButtonMapDeleteId
}).html('&nbsp;&nbsp;Delete map').prepend(
$('<i>',{
class: 'fas fa-trash fa-fw'
@@ -569,9 +570,10 @@ define([
* catch all global document events
*/
let setPageObserver = function(){
let documentElement = $(document);
// on "full-screen" change event
$(document).on('fscreenchange', function(e, state, elem){
documentElement.on('fscreenchange', function(e, state, elem){
let menuButton = $('#' + Util.config.menuButtonFullScreenId);
@@ -587,67 +589,67 @@ define([
}
});
$(document).on('pf:menuShowStatsDialog', function(e){
documentElement.on('pf:menuShowStatsDialog', function(e){
// show user activity stats dialog
$.fn.showStatsDialog();
return false;
});
$(document).on('pf:menuShowSystemEffectInfo', function(e){
documentElement.on('pf:menuShowSystemEffectInfo', function(e){
// show system effects dialog
$.fn.showSystemEffectInfoDialog();
return false;
});
$(document).on('pf:menuShowJumpInfo', function(e){
documentElement.on('pf:menuShowJumpInfo', function(e){
// show system effects info box
$.fn.showJumpInfoDialog();
return false;
});
$(document).on('pf:menuNotificationTest', function(e){
documentElement.on('pf:menuNotificationTest', function(e){
// show system effects info box
notificationTest();
return false;
});
$(document).on('pf:menuDeleteAccount', function(e){
documentElement.on('pf:menuDeleteAccount', function(e){
// show "delete account" dialog
$.fn.showDeleteAccountDialog();
return false;
});
$(document).on('pf:menuManual', function(e){
documentElement.on('pf:menuManual', function(e){
// show map manual
$.fn.showMapManual();
return false;
});
$(document).on('pf:menuShowTaskManager', function(e, data){
documentElement.on('pf:menuShowTaskManager', function(e, data){
// show log dialog
Logging.showDialog();
return false;
});
$(document).on('pf:menuShortcuts', function(e, data){
documentElement.on('pf:menuShortcuts', function(e, data){
// show shortcuts dialog
$.fn.showShortcutsDialog();
return false;
});
$(document).on('pf:menuShowSettingsDialog', function(e){
documentElement.on('pf:menuShowSettingsDialog', function(e){
// show character select dialog
$.fn.showSettingsDialog();
return false;
});
$(document).on('pf:menuShowMapInfo', function(e, data){
documentElement.on('pf:menuShowMapInfo', function(e, data){
// show map information dialog
$.fn.showMapInfoDialog(data);
return false;
});
$(document).on('pf:menuShowMapSettings', function(e, data){
documentElement.on('pf:menuShowMapSettings', function(e, data){
// show map edit dialog or edit map
let mapData = false;
@@ -661,7 +663,7 @@ define([
return false;
});
$(document).on('pf:menuDeleteMap', function(e){
documentElement.on('pf:menuDeleteMap', function(e){
// delete current active map
let mapData = false;
@@ -675,7 +677,7 @@ define([
return false;
});
$(document).on('pf:menuLogout', function(e, data){
documentElement.on('pf:menuLogout', function(e, data){
let clearCookies = false;
if(
@@ -701,8 +703,14 @@ define([
$(this).destroyTimestampCounter();
});
// disable memue links based on current map config
documentElement.on('pf:updateMenuOptions', function(e, data){
let hasRightMapDelete = MapUtil.checkRight('map_delete', data.mapConfig);
$('#' + Util.config.menuButtonMapDeleteId).toggleClass('disabled', !hasRightMapDelete);
});
// update header links with current map data
$(document).on('pf:updateHeaderMapData', function(e, data){
documentElement.on('pf:updateHeaderMapData', function(e, data){
let activeMap = Util.getMapModule().getActiveMap();
let userCount = 0;
@@ -721,7 +729,7 @@ define([
});
// shutdown the program -> show dialog
$(document).on('pf:shutdown', function(e, data){
documentElement.on('pf:shutdown', function(e, data){
// show shutdown dialog
let options = {
buttons: {
@@ -735,7 +743,7 @@ define([
// redirect to login
window.location = '../';
}else{
$(document).trigger('pf:menuLogout');
documentElement.trigger('pf:menuLogout');
}
}
}
@@ -764,7 +772,7 @@ define([
$.fn.showNotificationDialog(options);
$(document).setProgramStatus('offline');
documentElement.setProgramStatus('offline');
Util.showNotify({title: 'Logged out', text: data.reason, type: 'error'}, false);

View File

@@ -44,6 +44,8 @@ define([
requirejs(['text!templates/dialog/settings.html', 'mustache'], function(template, Mustache) {
let characterRoleName = Util.getCurrentUserInfo('roleName');
let data = {
id: config.settingsDialogId,
settingsAccountContainerId: config.settingsAccountContainerId,
@@ -53,7 +55,15 @@ define([
captchaImageWrapperId: config.captchaImageWrapperId,
captchaImageId: config.captchaImageId,
formErrorContainerClass: Util.config.formErrorContainerClass,
ccpImageServer: Init.url.ccpImageServer
ccpImageServer: Init.url.ccpImageServer,
roleLabelClass: () => {
let label = 'label-default';
switch(characterRoleName){
case 'SUPER': label = 'label-danger'; break;
case 'CORPORATION': label = 'label-info'; break;
}
return label;
}
};
let content = Mustache.render(template, data);

View File

@@ -10,7 +10,7 @@ define([
'bootbox',
'app/map/util',
'app/module_map'
], function($, Init, Util, Render, bootbox, MapUtil, ModuleMap) {
], ($, Init, Util, Render, bootbox, MapUtil, ModuleMap) => {
'use strict';
let config = {
@@ -83,7 +83,7 @@ define([
'text!templates/dialog/map.html',
'text!templates/form/map.html',
'mustache'
], function(templateMapDialog, templateMapSettings, Mustache) {
], (templateMapDialog, templateMapForm, Mustache) => {
let dialogTitle = 'Map settings';
@@ -92,16 +92,25 @@ define([
let hideEditTab = false;
let hideDownloadTab = false;
let hasRightMapCreate = true;
let hasRightMapUpdate = true;
let hasRightMapExport = true;
let hasRightMapImport = true;
if(mapData === false){
hideSettingsTab = true;
hideEditTab = true;
hideDownloadTab = true;
}else{
hasRightMapUpdate = MapUtil.checkRight('map_update', mapData.config);
hasRightMapExport = MapUtil.checkRight('map_export', mapData.config);
hasRightMapImport = MapUtil.checkRight('map_import', mapData.config);
}
// available map "types" for a new or existing map
let mapTypes = MapUtil.getMapTypes(true);
let data = {
let mapFormData = {
scope: MapUtil.getMapScopes(),
type: mapTypes,
icon: MapUtil.getMapIcons(),
@@ -111,10 +120,16 @@ define([
};
// render "new map" tab content -----------------------------------------------------------------------
let contentNewMap = Mustache.render(templateMapSettings, data);
let mapFormDataNew = $.extend({}, mapFormData, {
hasRightMapForm: hasRightMapCreate
});
let contentNewMap = Mustache.render(templateMapForm, mapFormDataNew);
// render "edit map" tab content ----------------------------------------------------------------------
let contentEditMap = Mustache.render(templateMapSettings, data);
let mapFormDataEdit = $.extend({}, mapFormData, {
hasRightMapForm: hasRightMapUpdate
});
let contentEditMap = Mustache.render(templateMapForm, mapFormDataEdit);
contentEditMap = $(contentEditMap);
// current map access info
@@ -189,7 +204,7 @@ define([
}
// render main dialog ---------------------------------------------------------------------------------
data = {
let mapDialogData = {
id: config.newMapDialogId,
mapData: mapData,
type: mapTypes,
@@ -276,6 +291,10 @@ define([
fieldImportId: config.fieldImportId,
dialogMapImportInfoId: config.dialogMapImportInfoId,
hasRightMapUpdate: hasRightMapUpdate,
hasRightMapExport: hasRightMapExport,
hasRightMapImport: hasRightMapImport,
formatFilename: function(){
// format filename from "map name" (initial)
return function (mapName, render) {
@@ -285,7 +304,7 @@ define([
}
};
let contentDialog = Mustache.render(templateMapDialog, data);
let contentDialog = Mustache.render(templateMapDialog, mapDialogData);
contentDialog = $(contentDialog);
// set tab content
@@ -553,8 +572,10 @@ define([
};
let dropZone = downloadTabElement.find('.' + config.dragDropElementClass);
dropZone[0].addEventListener('dragover', handleDragOver, false);
dropZone[0].addEventListener('drop', handleFileSelect, false);
if(dropZone.length){
dropZone[0].addEventListener('dragover', handleDragOver, false);
dropZone[0].addEventListener('drop', handleFileSelect, false);
}
// import "button"
downloadTabElement.find('#' + config.buttonImportId).on('click', function(e) {
@@ -587,12 +608,13 @@ define([
// events for tab change ------------------------------------------------------------------------------
mapInfoDialog.find('.navbar a').on('shown.bs.tab', function(e){
let modalDialog = mapInfoDialog.find('div.modal-dialog');
let tabContentId = $(e.target).attr('href');
let tabContentForms = $(tabContentId).find('form.form-horizontal');
let selectElementCharacter = mapInfoDialog.find('#' + config.characterSelectId);
let selectElementCorporation = mapInfoDialog.find('#' + config.corporationSelectId);
let selectElementAlliance = mapInfoDialog.find('#' + config.allianceSelectId);
if($(e.target).attr('href') === '#' + config.dialogMapSettingsContainerId){
if(tabContentId === '#' + config.dialogMapSettingsContainerId){
// "settings" tab -> resize modal
modalDialog.toggleClass('modal-lg', true);
initSettingsSelectFields(mapInfoDialog);
@@ -614,7 +636,10 @@ define([
}
// no "save" dialog button on "in/export" tab
if($(e.target).attr('href') === '#' + config.dialogMapDownloadContainerId){
if(
tabContentId === '#' + config.dialogMapDownloadContainerId || // no "save" dialog button on "in/export" tab
!tabContentForms.length // no <form> in tab (e.g. restricted by missing right)
){
mapInfoDialog.find('button.btn-success').hide();
}else{
mapInfoDialog.find('button.btn-success').show();

View File

@@ -16,7 +16,7 @@ define([
'hoverIntent',
'bootstrapConfirmation',
'bootstrapToggle'
], function($, Init, SystemEffect, SignatureType, bootbox, localforage) {
], ($, Init, SystemEffect, SignatureType, bootbox, localforage) => {
'use strict';
@@ -36,11 +36,11 @@ define([
headCurrentLocationId: 'pf-head-current-location', // id for "show current location" element
// menu
menuButtonFullScreenId: 'pf-menu-button-fullscreen', // id for menu button "fullscreen"
menuButtonFullScreenId: 'pf-menu-button-fullscreen', // id for menu button "fullScreen"
menuButtonMagnetizerId: 'pf-menu-button-magnetizer', // id for menu button "magnetizer"
menuButtonGridId: 'pf-menu-button-grid', // id for menu button "grid snap"
menuButtonEndpointId: 'pf-menu-button-endpoint', // id for menu button "endpoint" overlays
menuButtonMapDeleteId: 'pf-menu-button-map-delete', // id for menu button "delete map"
settingsMessageVelocityOptions: {
duration: 180
@@ -565,10 +565,18 @@ define([
browserTabId: getBrowserTabId(),
routes: Init.routes,
userData: userData,
otherCharacters: $.grep( userData.characters, function( character ) {
// exclude current active character
return character.id !== userData.character.id;
})
otherCharacters: () => {
return userData.characters.filter((character, i) => {
let characterImage = Init.url.ccpImageServer + '/Character/' + character.id + '_32.jpg';
// preload image (prevent UI flicker
let img= new Image();
img.src = characterImage;
userData.characters[i].image = characterImage;
return character.id !== userData.character.id;
});
}
};
let content = Mustache.render(template, data);
@@ -832,10 +840,85 @@ define([
console.info('PATHFINDER ' + getVersion());
};
/**
* polyfill for "passive" events
* -> see https://github.com/zzarcon/default-passive-events
*/
let initPassiveEvents = () => {
const defaultOptions = {
passive: true,
capture: false
};
const supportedPassiveTypes = [
'scroll', 'wheel',
'touchstart', 'touchmove', 'touchenter', 'touchend', 'touchleave',
'mouseout', 'mouseleave', 'mouseup', 'mousedown', 'mousemove', 'mouseenter', 'mousewheel', 'mouseover'
];
const getDefaultPassiveOption = (passive, eventName) => {
if (passive !== undefined) return passive;
return supportedPassiveTypes.indexOf(eventName) === -1 ? false : defaultOptions.passive;
};
const getWritableOptions = (options) => {
const passiveDescriptor = Object.getOwnPropertyDescriptor(options, 'passive');
return passiveDescriptor &&
passiveDescriptor.writable !== true &&
passiveDescriptor.set === undefined ? Object.assign({}, options) : options;
};
const prepareSafeListener = (listener, passive) => {
if (!passive) return listener;
return function (e) {
e.preventDefault = () => {};
return listener.call(this, e);
};
};
const overwriteAddEvent = (superMethod) => {
EventTarget.prototype.addEventListener = function (type, listener, options) { // jshint ignore:line
const usesListenerOptions = typeof options === 'object';
const useCapture = usesListenerOptions ? options.capture : options;
options = usesListenerOptions ? getWritableOptions(options) : {};
options.passive = getDefaultPassiveOption(options.passive, type);
options.capture = useCapture === undefined ? defaultOptions.capture : useCapture;
listener = prepareSafeListener(listener, options.passive);
superMethod.call(this, type, listener, options);
};
};
let eventListenerOptionsSupported = () => {
let supported = false;
try {
const opts = Object.defineProperty({}, 'passive', {
get() {
supported = true;
}
});
window.addEventListener('test', null, opts);
window.removeEventListener('test', null, opts);
} catch (e) {}
return supported;
};
let supportsPassive = eventListenerOptionsSupported ();
if (supportsPassive) {
const addEvent = EventTarget.prototype.addEventListener; // jshint ignore:line
overwriteAddEvent(addEvent);
}
};
/**
* init utility prototype functions
*/
let initPrototypes = function(){
let initPrototypes = () => {
// Array diff
// [1,2,3,4,5,6].diff( [3,4,5] );
// => [1, 2, 6]
@@ -853,6 +936,8 @@ define([
return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
});
};
initPassiveEvents();
};
/**
@@ -1807,6 +1892,10 @@ define([
if(option === 'corporationId' && characterData.corporation){
userInfo = characterData.corporation.id;
}
if(option === 'roleName' && characterData.role){
userInfo = characterData.role.name;
}
}
}

View File

@@ -155,6 +155,7 @@ $.fn.dragToSelect = function (conf) {
}
// get scroll position
/*
var leftScroll = 0;
var topScroll = 0;
@@ -165,14 +166,15 @@ $.fn.dragToSelect = function (conf) {
if(realParent.attr('data-scroll-top')){
topScroll = parseInt(realParent.attr('data-scroll-top'));
}
*/
var left = lastMousePosition.x - parentDim.left + parent[0].scrollLeft;
var top = lastMousePosition.y - parentDim.top + parent[0].scrollTop;
var tempWidth = selectBoxOrigin.left - left;
var tempHeight = selectBoxOrigin.top - top;
let newLeft = selectBoxOrigin.left - leftScroll;
let newTop = selectBoxOrigin.top - topScroll;
let newLeft = selectBoxOrigin.left;// - leftScroll;
let newTop = selectBoxOrigin.top;// - topScroll;
var newWidth = left - selectBoxOrigin.left;
var newHeight = top - selectBoxOrigin.top;

View File

@@ -6,7 +6,12 @@
<check if="{{ @tplLogged }}">
<true>
{* Logged IN *}
<h2>Admin <span class="txt-color txt-color-warning">{{ @character->name }}</span></h2>
<h2>
<span class="pf-font-capitalize">{{ strtolower(@character->roleId->name) }}</span>
<span class="txt-color txt-color-{{ @character->roleId->style }}">{{ @character->roleId->label }}</span>
<span>{{ @character->name }}</span>
</h2>
<p class="lead">
You have already admin privileges. Switch character?
</p>

View File

@@ -31,8 +31,6 @@
<tbody>
<repeat group="{{ @corpMap }}" value="{{ @map }}" counter="{{ @mapCount }}">
<tr>
<td class="text-right">{{ @map->_id }}</td>
<td data-order="{{ @map->name }}" data-search="{{ @map->name }}">{{ @map->name }}</td>
@@ -49,15 +47,15 @@
<td class="text-right pf-table-button-sm-cell">
<check if="{{ @map->active }}">
<true>
<a class="btn btn-sm btn-warning" href="/admin/{{ @tplPage}}/active/{{ @map->_id }}/0">deactivate</a>
<a class="btn btn-sm btn-warning pf-overlay-trigger" href="/admin/{{ @tplPage}}/active/{{ @map->_id }}/0">deactivate</a>
</true>
<false>
<a class="btn btn-sm btn-success" href="/admin/{{ @tplPage}}/active/{{ @map->_id }}/1">activate</a>
<a class="btn btn-sm btn-success pf-overlay-trigger" href="/admin/{{ @tplPage}}/active/{{ @map->_id }}/1">activate</a>
</false>
</check>
</td>
<td class="text-right pf-table-button-sm-cell">
<a class="btn btn-sm btn-danger {{ @map->active ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/delete/{{ @map->_id }}">delete</a>
<a class="btn btn-sm btn-danger pf-overlay-trigger {{ @map->active ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/delete/{{ @map->_id }}">delete</a>
</td>
</tr>
</repeat>

View File

@@ -33,15 +33,21 @@
<repeat group="{{ @corpMember }}" value="{{ @member }}" counter="{{ @memberCount }}">
<set disableRow = "{{@member->_id}} == {{@character->_id}}" />
<set roleType="label-default" />
<check if="{{ @member->roleId->_id==2 }}">
<set roleType="label-danger" />
</check>
<check if="{{ @member->roleId->_id==3 }}">
<set roleType="label-info" />
</check>
<tr>
<td class="text-right">{{ @member->_id }}</td>
<td class="pf-table-button-sm-cell" data-order="{{ @member->name }}" data-search="{{ @member->name }}">
<img src="//image.eveonline.com/Character/{{ @member->_id }}_32.jpg" >&nbsp;{{ @member->name }}
</td>
<td class="text-right">
<check if="{{ @member->roleId }}">
<span class="label label-success">admin</span>
</check>
<span class="label {{@roleType}}">{{ @member->roleId->label }}</span>
</td>
<td class="text-right">
<check if="{{ @member->shared }}">
@@ -70,11 +76,11 @@
<div class="btn-group btn-group-sm" role="group">
<check if="{{ @member->kicked }}">
<true>
<a class="btn btn-primary {{ @disableRow ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/kick/{{ @member->_id }}">revoke</a>
<a class="btn btn-primary pf-overlay-trigger {{ @disableRow ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/kick/{{ @member->_id }}">revoke</a>
</true>
<false>
<repeat group="{{ @tplKickOptions }}" key="{{ @key }}" value="{{ @label }}">
<a class="btn btn-default {{ @disableRow ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/kick/{{ @member->_id }}/{{ @key }}">{{ @label }}</a>
<a class="btn btn-default pf-overlay-trigger {{ @disableRow ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/kick/{{ @member->_id }}/{{ @key }}">{{ @label }}</a>
</repeat>
</false>
</check>
@@ -84,10 +90,10 @@
<td class="text-right pf-table-button-sm-cell">
<check if="{{ @member->banned }}">
<true>
<a class="btn btn-sm btn-primary {{ @disableRow ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/ban/{{ @member->_id }}">revoke</a>
<a class="btn btn-sm btn-primary pf-overlay-trigger {{ @disableRow ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/ban/{{ @member->_id }}">revoke</a>
</true>
<false>
<a class="btn btn-sm btn-danger {{ @disableRow ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/ban/{{ @member->_id }}/1">ban</a>
<a class="btn btn-sm btn-danger pf-overlay-trigger {{ @disableRow ? 'disabled' : '' }}" href="/admin/{{ @tplPage}}/ban/{{ @member->_id }}/1">ban</a>
</false>
</check>
</td>

View File

@@ -3,5 +3,68 @@
<div class="row"></div>
<h4><i class="fas fa-fw fa-cogs"></i>&nbsp;{{ ucfirst(@tplPage) }}</h4>
<div class="row text-center">
<repeat group="{{ @tplSettings->corporations }}" value="{{ @corporation }}" key="{{@corpName}}" counter="{{ @corpCount }}">
<form role="form" class="form-horizontal" action="/admin/{{ @tplPage}}/save/{{ @corporation->_id }}">
<div class="col-sm-12 col-md-6 col-lg-4 pf-landing-pricing-panel">
<div class="panel panel-default pricing-big">
<div class="panel-heading text-left">
<h3 class="panel-title">
{{ @corpCount }}. {{@corpName}}
<img class="pf-character-image" src="https://image.eveonline.com/Corporation/{{ @corporation->_id }}_32.png" alt="{{ @corporation->name }}"/>
</h3>
</div>
<div class="panel-body panel-reverse-order">
<set isManaged = "" />
<fieldset class="reverse-order-footer">
<h4 class="pf-dynamic-area">Map roles</h4>
<repeat group="{{ @corporation->getRights() }}" value="{{ @corporationRight }}" >
<check if="{{ @tplDefaultRole->_id != @corporationRight->roleId->_id }}">
<set isManaged = "checked" />
</check>
<include href="templates/modules/role_select_row.html"with="right={{@corporationRight}},id={{@corporation->_id}},roles={{@tplRoles}}"/>
</repeat>
</fieldset>
<div class="reverse-order-header">
<div class="row">
<div class="col-sm-12">
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-xs-6 col-sm-12 checkbox checkbox-warning">
<input id="form_managed" name="managed" value="1" type="checkbox" {{ @isManaged }}>
<label for="form_managed">Managed mode
<i class="fas fa-fw fa-sm fa-question-circle pf-help-light" title="If active only corporation admins can add/update/delete corporation map settings"></i>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<exclude>
<div class="pf-dialog-warning-container alert alert-warning">
<span class="txt-color txt-color-warning">Warning</span>
<small> (important non-critical information)</small>
</div>
</exclude>
</div>
<div class="panel-footer text-right">
<button type="submit" class="btn btn-success pf-overlay-trigger">
<i class="fas fa-fw fa-check"></i> save
</button>
</div>
</div>
</div>
</form>
</repeat>
</div>
</div>
</section>

View File

@@ -40,146 +40,147 @@
{{^hideSettingsTab}}
<div role="tabpanel" class="tab-pane fade {{#openTabSettings}}in active{{/openTabSettings}}" id="{{dialogMapSettingsContainerId}}">
<form role="form" class="form-horizontal">
{{#hasRightMapUpdate}}
<form role="form" class="form-horizontal">
<div class="row ">
<div class="col-sm-6">
<h4 class="pf-dynamic-area">Map options</h4>
<div class="row ">
<div class="col-sm-6">
<h4 class="pf-dynamic-area">Map options</h4>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<input id="{{deleteExpiredConnectionsId}}" name="deleteExpiredConnections" value="1" type="checkbox" {{#deleteExpiredConnections}}checked{{/deleteExpiredConnections}}>
<label for="{{deleteExpiredConnectionsId}}">Auto delete outdated wormholes
<i class="fas fa-fw fa-question-circle pf-help-light" title="outdated WHs (~2 days)"></i>
</label>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox" >
<input id="{{deleteEolConnectionsId}}" name="deleteEolConnections" value="1" type="checkbox" {{#deleteEolConnections}}checked{{/deleteEolConnections}}>
<label for="{{deleteEolConnectionsId}}">Auto delete expired wormholes
<i class="fas fa-fw fa-question-circle pf-help-light" title="expired EOL WHs (~4h 15min)"></i>
</label>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<input id="{{persistentAliasesId}}" name="persistentAliases" value="1" type="checkbox" {{#persistentAliases}}checked{{/persistentAliases}}>
<label for="{{persistentAliasesId}}">Persistent system aliases
<i class="fas fa-fw fa-question-circle pf-help-light" title="Keep custom aliases after delete"></i>
</label>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<h4 class="pf-dynamic-area">Map logging</h4>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<input id="{{logHistoryId}}" name="logHistory" value="1" type="checkbox" {{#logHistory}}checked{{/logHistory}}>
<label for="{{logHistoryId}}">Save map changes to logfile
<i class="fas fa-fw fa-question-circle pf-help-light" title="Map changes will be stored in a log file"></i>
</label>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<input id="{{logActivityId}}" name="logActivity" value="1" type="checkbox" {{#logActivity}}checked{{/logActivity}}>
<label for="{{logActivityId}}">Store user statistics
<i class="fas fa-fw fa-question-circle pf-help-light" title="Map changes will be tracked in order to generate user statistics"></i>
</label>
</div>
</div>
</div>
</div>
</div>
{{! Slack notification --------------------------------------------- }}
<h4 class="pf-dynamic-area {{^slackSectionShow}}collapsed{{/slackSectionShow}}" data-toggle="collapse" data-target="#pf-map-dialog-slack-section"><i class="fab fa-slack-hash"></i>&nbsp;&nbsp;Slack notifications</h4>
<div id="pf-map-dialog-slack-section" class="collapse {{#slackSectionShow}}in{{/slackSectionShow}}">
<fieldset {{^slackEnabled}}disabled{{/slackEnabled}}>
<div class="row ">
<div class="col-xs-12 col-sm-12 col-md-6">
<div class="col-sm-12 col-md-6">
<div class="form-group">
<label for="{{slackWebHookURLId}}" class="col-sm-3 col-md-4 control-label">WebHook
<i class="fas fa-fw fa-question-circle pf-help-light" title="Copy a WebHook URL from Slack configuration page for 'Incoming WebHooks'"></i>
</label>
<div class="col-sm-9 col-md-8">
<div class="input-group">
<input name="slackWebHookURL" type="url" class="form-control" id="{{slackWebHookURLId}}" value="{{slackWebHookURL}}" placeholder="https://hooks.slack.com/services/XXX/YYY/ZZZ" data-type-error="No valid URL" pattern="^https://hooks.slack.com/.*" data-pattern-error="Wrong domain. https://hooks.slack.com/)">
<div class="input-group-btn">
<a class="btn btn-default" href="//slack.com/apps/A0F7XDUAZ-incoming-webhooks" target="_blank">
add… <i class="fas fa-fw fa-external-link-alt"></i>
</a>
<div class="col-sm-12 col-xs-6 checkbox">
<input id="{{deleteExpiredConnectionsId}}" name="deleteExpiredConnections" value="1" type="checkbox" {{#deleteExpiredConnections}}checked{{/deleteExpiredConnections}}>
<label for="{{deleteExpiredConnectionsId}}">Auto delete outdated wormholes
<i class="fas fa-fw fa-question-circle pf-help-light" title="outdated WHs (~2 days)"></i>
</label>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox" >
<input id="{{deleteEolConnectionsId}}" name="deleteEolConnections" value="1" type="checkbox" {{#deleteEolConnections}}checked{{/deleteEolConnections}}>
<label for="{{deleteEolConnectionsId}}">Auto delete expired wormholes
<i class="fas fa-fw fa-question-circle pf-help-light" title="expired EOL WHs (~4h 15min)"></i>
</label>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<input id="{{persistentAliasesId}}" name="persistentAliases" value="1" type="checkbox" {{#persistentAliases}}checked{{/persistentAliases}}>
<label for="{{persistentAliasesId}}">Persistent system aliases
<i class="fas fa-fw fa-question-circle pf-help-light" title="Keep custom aliases after delete"></i>
</label>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<h4 class="pf-dynamic-area">Map logging</h4>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<input id="{{logHistoryId}}" name="logHistory" value="1" type="checkbox" {{#logHistory}}checked{{/logHistory}}>
<label for="{{logHistoryId}}">Save map changes to logfile
<i class="fas fa-fw fa-question-circle pf-help-light" title="Map changes will be stored in a log file"></i>
</label>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<input id="{{logActivityId}}" name="logActivity" value="1" type="checkbox" {{#logActivity}}checked{{/logActivity}}>
<label for="{{logActivityId}}">Store user statistics
<i class="fas fa-fw fa-question-circle pf-help-light" title="Map changes will be tracked in order to generate user statistics"></i>
</label>
</div>
</div>
</div>
</div>
</div>
{{! Slack notification --------------------------------------------- }}
<h4 class="pf-dynamic-area {{^slackSectionShow}}collapsed{{/slackSectionShow}}" data-toggle="collapse" data-target="#pf-map-dialog-slack-section"><i class="fab fa-slack-hash"></i>&nbsp;&nbsp;Slack notifications</h4>
<div id="pf-map-dialog-slack-section" class="collapse {{#slackSectionShow}}in{{/slackSectionShow}}">
<fieldset {{^slackEnabled}}disabled{{/slackEnabled}}>
<div class="row ">
<div class="col-xs-12 col-sm-12 col-md-6">
<div class="form-group">
<label for="{{slackWebHookURLId}}" class="col-sm-3 col-md-4 control-label">WebHook
<i class="fas fa-fw fa-question-circle pf-help-light" title="Copy a WebHook URL from Slack configuration page for 'Incoming WebHooks'"></i>
</label>
<div class="col-sm-9 col-md-8">
<div class="input-group">
<input name="slackWebHookURL" type="url" class="form-control" id="{{slackWebHookURLId}}" value="{{slackWebHookURL}}" placeholder="https://hooks.slack.com/services/XXX/YYY/ZZZ" data-type-error="No valid URL" pattern="^https://hooks.slack.com/.*" data-pattern-error="Wrong domain. https://hooks.slack.com/)">
<div class="input-group-btn">
<a class="btn btn-default" href="//slack.com/apps/A0F7XDUAZ-incoming-webhooks" target="_blank">
add… <i class="fas fa-fw fa-external-link-alt"></i>
</a>
</div>
</div>
<div class="note help-block with-errors"></div>
</div>
<div class="note help-block with-errors"></div>
</div>
</div>
</div>
<div class="col-xs-6 col-sm-6 col-md-3">
<div class="form-group">
<label for="{{slackUsernameId}}" class="col-sm-6 col-md-4 control-label">Name
<i class="fas fa-fw fa-question-circle pf-help-light" title="Set a 'username' for your Slack bot"></i>
</label>
<div class="col-sm-6 col-md-8">
<input name="slackUsername" type="text" class="form-control" id="{{slackUsernameId}}" value="{{slackUsername}}" placeholder="Pathfinder bot" data-error="Please set a bot name">
<div class="note help-block with-errors"></div>
</div>
</div>
</div>
<div class="col-xs-6 col-sm-6 col-md-3">
<div class="form-group">
<label for="{{slackIconId}}" class="col-sm-6 col-md-4 control-label">Bot icon
<i class="fas fa-fw fa-question-circle pf-help-light" title="Set a 'icon' for your Slack bot (e.g. :boom:)"></i>
</label>
<div class="col-sm-6 col-md-8">
<div class="input-icon-left input-icon-right">
<span class="fa-stack">
<i class="fas fa-fw fa-lg fa-ellipsis-v fa-stack-1x"></i>
<i class="fas fa-fw fa-lg fa-minus fa-stack-1x"></i>
</span>
<span class="fa-stack">
<i class="fas fa-fw fa-lg fa-ellipsis-v fa-stack-1x"></i>
<i class="fas fa-fw fa-lg fa-minus fa-stack-1x"></i>
</span>
<input name="slackIcon" type="text" class="form-control text-center" id="{{slackIconId}}" value="{{slackIcon}}" placeholder="slack" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)">
<div class="col-xs-6 col-sm-6 col-md-3">
<div class="form-group">
<label for="{{slackUsernameId}}" class="col-sm-6 col-md-4 control-label">Name
<i class="fas fa-fw fa-question-circle pf-help-light" title="Set a 'username' for your Slack bot"></i>
</label>
<div class="col-sm-6 col-md-8">
<input name="slackUsername" type="text" class="form-control" id="{{slackUsernameId}}" value="{{slackUsername}}" placeholder="Pathfinder bot" data-error="Please set a bot name">
<div class="note help-block with-errors"></div>
</div>
<div class="note help-block with-errors"></div>
</div>
</div>
</div>
{{! group some columns -> Otherwise error msg would break the layout }}
<div class="clearfix"></div>
<div class="col-xs-6 col-sm-6 col-md-3">
<div class="form-group">
<label for="{{slackIconId}}" class="col-sm-6 col-md-4 control-label">Bot icon
<i class="fas fa-fw fa-question-circle pf-help-light" title="Set a 'icon' for your Slack bot (e.g. :boom:)"></i>
</label>
<div class="col-sm-6 col-md-8">
<div class="input-icon-left input-icon-right">
<span class="fa-stack">
<i class="fas fa-fw fa-lg fa-ellipsis-v fa-stack-1x"></i>
<i class="fas fa-fw fa-lg fa-minus fa-stack-1x"></i>
</span>
<span class="fa-stack">
<i class="fas fa-fw fa-lg fa-ellipsis-v fa-stack-1x"></i>
<i class="fas fa-fw fa-lg fa-minus fa-stack-1x"></i>
</span>
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<label for="{{slackChannelRallyId}}" class="col-sm-6 col-md-4 control-label">Channel 'rally point'
<i class="fas fa-fw fa-question-circle pf-help-light" title="Send new 'Rally point' notifications to a Slack channel"></i>
</label>
<div class="col-sm-6 col-md-8">
<div class="input-icon-left" {{^slackRallyEnabled}}title="Globally disabled for this map type"{{/slackRallyEnabled}}>
<input name="slackIcon" type="text" class="form-control text-center" id="{{slackIconId}}" value="{{slackIcon}}" placeholder="slack" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)">
</div>
<div class="note help-block with-errors"></div>
</div>
</div>
</div>
{{! group some columns -> Otherwise error msg would break the layout }}
<div class="clearfix"></div>
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<label for="{{slackChannelRallyId}}" class="col-sm-6 col-md-4 control-label">Channel 'rally point'
<i class="fas fa-fw fa-question-circle pf-help-light" title="Send new 'Rally point' notifications to a Slack channel"></i>
</label>
<div class="col-sm-6 col-md-8">
<div class="input-icon-left" {{^slackRallyEnabled}}title="Globally disabled for this map type"{{/slackRallyEnabled}}>
<i class="fas fa-hashtag"></i>
<input name="slackChannelRally" type="text" class="form-control" id="{{slackChannelRallyId}}" value="{{slackChannelRally}}" placeholder="map_rally" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)" {{^slackRallyEnabled}}disabled{{/slackRallyEnabled}}>
</div>
@@ -195,39 +196,37 @@
</label>
<div class="col-sm-6 col-md-8">
<div class="input-icon-left" {{^slackHistoryEnabled}}title="Globally disabled for this map type"{{/slackHistoryEnabled}}>
<i class="fas fa-hashtag"></i>
<input name="slackChannelHistory" type="text" class="form-control" id="{{slackChannelHistoryId}}" value="{{slackChannelHistory}}" placeholder="map_logging" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)" {{^slackHistoryEnabled}}disabled{{/slackHistoryEnabled}}>
</div>
<div class="help-block with-errors"></div>
<i class="fas fa-hashtag"></i>
<input name="slackChannelHistory" type="text" class="form-control" id="{{slackChannelHistoryId}}" value="{{slackChannelHistory}}" placeholder="map_logging" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)" {{^slackHistoryEnabled}}disabled{{/slackHistoryEnabled}}>
</div>
<div class="help-block with-errors"></div>
</div>
</div>
</fieldset>
{{^slackEnabled}}
<div class="pf-dialog-info-container alert alert-info" style="display: block; opacity: 1; transform: translateY(0px);">
<span class="txt-color txt-color-information">Info</span>
<small>Slack WebHooks are disabled for your map type '{{ mapData.config.type.name }}'</small>
</div>
</fieldset>
{{^slackEnabled}}
<div class="pf-dialog-info-container alert alert-info" style="display: block; opacity: 1; transform: translateY(0px);">
<span class="txt-color txt-color-information">Info</span>
<small>Slack WebHooks are disabled for your map type '{{ mapData.config.type.name }}'</small>
{{/slackEnabled}}
</div>
{{/slackEnabled}}
</div>
{{! Discord notification --------------------------------------------- }}
<h4 class="pf-dynamic-area {{^discordSectionShow}}collapsed{{/discordSectionShow}}" data-toggle="collapse" data-target="#pf-map-dialog-discord-section"><i class="fab fa-discord"></i>&nbsp;&nbsp;Discord notifications <small class="txt-color txt-color-warning">[BETA]</small></h4>
{{! Discord notification --------------------------------------------- }}
<h4 class="pf-dynamic-area {{^discordSectionShow}}collapsed{{/discordSectionShow}}" data-toggle="collapse" data-target="#pf-map-dialog-discord-section"><i class="fab fa-discord"></i>&nbsp;&nbsp;Discord notifications <small class="txt-color txt-color-warning">[BETA]</small></h4>
<div id="pf-map-dialog-discord-section" class="collapse {{#discordSectionShow}}in{{/discordSectionShow}}">
<fieldset {{^discordEnabled}}disabled{{/discordEnabled}}>
<div class="row ">
<div class="col-xs-12 col-sm-12 col-md-6">
<div class="form-group">
<label for="{{discordWebHookURLRallyId}}" class="col-sm-3 col-md-4 control-label">WebHook 'rally point'
<i class="fas fa-fw fa-question-circle pf-help-light" title="Copy a WebHook URL from Discord channel configuration page"></i>
</label>
<div class="col-sm-9 col-md-8">
<div class="input-group" {{^discordRallyEnabled}}title="Globally disabled for this map type"{{/discordRallyEnabled}}>
<div id="pf-map-dialog-discord-section" class="collapse {{#discordSectionShow}}in{{/discordSectionShow}}">
<fieldset {{^discordEnabled}}disabled{{/discordEnabled}}>
<div class="row ">
<div class="col-xs-12 col-sm-12 col-md-6">
<div class="form-group">
<label for="{{discordWebHookURLRallyId}}" class="col-sm-3 col-md-4 control-label">WebHook 'rally point'
<i class="fas fa-fw fa-question-circle pf-help-light" title="Copy a WebHook URL from Discord channel configuration page"></i>
</label>
<div class="col-sm-9 col-md-8">
<div class="input-group" {{^discordRallyEnabled}}title="Globally disabled for this map type"{{/discordRallyEnabled}}>
<input name="discordWebHookURLRally" type="url" class="form-control" id="{{discordWebHookURLRallyId}}" value="{{discordWebHookURLRally}}" placeholder="https://discordapp.com/api/webhooks/XXXYYYZZZ" data-type-error="No valid URL" pattern="^https://discordapp.com/.*" data-pattern-error="Wrong domain. https://discordapp.com/)" {{^discordRallyEnabled}}disabled{{/discordRallyEnabled}}>
<div class="input-group-btn">
<a class="btn btn-default" href="//support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks" target="_blank">
@@ -262,114 +261,117 @@
</label>
<div class="col-sm-9 col-md-8">
<div class="input-group" {{^discordHistoryEnabled}}title="Globally disabled for this map type"{{/discordHistoryEnabled}}>
<input name="discordWebHookURLHistory" type="url" class="form-control" id="{{discordWebHookURLHistoryId}}" value="{{discordWebHookURLHistory}}" placeholder="https://discordapp.com/api/webhooks/XXXYYYZZZ" data-type-error="No valid URL" pattern="^https://discordapp.com/.*" data-pattern-error="Wrong domain. https://discordapp.com/)" {{^discordHistoryEnabled}}disabled{{/discordHistoryEnabled}}>
<div class="input-group-btn">
<a class="btn btn-default" href="//support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks" target="_blank">
add… <i class="fas fa-fw fa-external-link-alt"></i>
</a>
</div>
<input name="discordWebHookURLHistory" type="url" class="form-control" id="{{discordWebHookURLHistoryId}}" value="{{discordWebHookURLHistory}}" placeholder="https://discordapp.com/api/webhooks/XXXYYYZZZ" data-type-error="No valid URL" pattern="^https://discordapp.com/.*" data-pattern-error="Wrong domain. https://discordapp.com/)" {{^discordHistoryEnabled}}disabled{{/discordHistoryEnabled}}>
<div class="input-group-btn">
<a class="btn btn-default" href="//support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks" target="_blank">
add… <i class="fas fa-fw fa-external-link-alt"></i>
</a>
</div>
<div class="note help-block with-errors"></div>
</div>
<div class="note help-block with-errors"></div>
</div>
</div>
</fieldset>
{{^discordEnabled}}
<div class="pf-dialog-info-container alert alert-info" style="display: block; opacity: 1; transform: translateY(0px);">
<span class="txt-color txt-color-information">Info</span>
<small>Discord WebHooks are disabled for your map type '{{ mapData.config.type.name }}'</small>
</div>
{{/discordEnabled}}
</div>
<h4 class="pf-dynamic-area">Share settings</h4>
<div class="row">
<div class="col-sm-11">
<blockquote>
<p>
Use this feature with caution! - Shared map entities have full map access.
They even can take over control by removing other entities from this list.
</p>
<small>Reduce this risk by creating a new map for joined OPs.
</small>
</blockquote>
</div>
</div>
{{! character search ------------------------------------------------ }}
{{#accessCharacter.length}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{characterSelectId}}">Character</label>
<div class="col-sm-10">
<div class="input-group" title="add/remove character">
<label for="{{characterSelectId}}"></label>
<select id="{{characterSelectId}}" name="mapCharacters[]" multiple="multiple">
{{#accessCharacter}}
<option value="{{id}}" selected>{{name}}</option>
{{/accessCharacter}}
</select>
<span class="note help-block with-errors">Search character name (max {{maxCharacter}})</span>
</div>
</div>
</div>
</div>
</fieldset>
{{^discordEnabled}}
<div class="pf-dialog-info-container alert alert-info" style="display: block; opacity: 1; transform: translateY(0px);">
<span class="txt-color txt-color-information">Info</span>
<small>Discord WebHooks are disabled for your map type '{{ mapData.config.type.name }}'</small>
</div>
{{/discordEnabled}}
{{/accessCharacter.length}}
</div>
<h4 class="pf-dynamic-area">Share settings</h4>
<div class="row">
<div class="col-sm-11">
<blockquote>
<p>
Use this feature with caution! - Shared map entities have full map access.
They even can take over control by removing other entities from this list.
</p>
<small>Reduce this risk by creating a new map for joined OPs.
</small>
</blockquote>
</div>
</div>
{{! character search ------------------------------------------------ }}
{{#accessCharacter.length}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{characterSelectId}}">Character</label>
<div class="col-sm-10">
<div class="input-group" title="add/remove character">
<label for="{{characterSelectId}}"></label>
<select id="{{characterSelectId}}" name="mapCharacters[]" multiple="multiple">
{{#accessCharacter}}
<option value="{{id}}" selected>{{name}}</option>
{{/accessCharacter}}
</select>
<span class="note help-block with-errors">Search character name (max {{maxCharacter}})</span>
{{! corporation search ---------------------------------------------- }}
{{#accessCorporation.length}}
<div class="row">
<div class="col-sm-9">
<div class="form-group">
<label class="col-sm-3 control-label" for="{{corporationSelectId}}">Corporations</label>
<div class="col-sm-9">
<div class="input-group" title="add/remove corporations">
<label for="{{corporationSelectId}}"></label>
<select id="{{corporationSelectId}}" name="mapCorporations[]" multiple="multiple">
{{#accessCorporation}}
<option value="{{id}}" selected="selected" >{{name}}</option>
{{/accessCorporation}}
</select>
<span class="note help-block with-errors">Search corporation name (max {{maxCorporation}})</span>
</div>
</div>
</div>
</div>
</div>
</div>
{{/accessCharacter.length}}
{{/accessCorporation.length}}
{{! corporation search ---------------------------------------------- }}
{{#accessCorporation.length}}
<div class="row">
<div class="col-sm-9">
<div class="form-group">
<label class="col-sm-3 control-label" for="{{corporationSelectId}}">Corporations</label>
<div class="col-sm-9">
<div class="input-group" title="add/remove corporations">
<label for="{{corporationSelectId}}"></label>
<select id="{{corporationSelectId}}" name="mapCorporations[]" multiple="multiple">
{{#accessCorporation}}
<option value="{{id}}" selected="selected" >{{name}}</option>
{{/accessCorporation}}
</select>
<span class="note help-block with-errors">Search corporation name (max {{maxCorporation}})</span>
{{! alliance search ------------------------------------------------- }}
{{#accessAlliance.length}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{allianceSelectId}}">Alliances</label>
<div class="col-sm-10">
<div class="input-group" title="add/remove alliances">
<label for="{{allianceSelectId}}"></label>
<select id="{{allianceSelectId}}" name="mapAlliances[]" multiple="multiple" >
{{#accessAlliance}}
<option value="{{id}}" selected="selected" >{{name}}</option>
{{/accessAlliance}}
</select>
<span class="note help-block with-errors">Search alliance name (max {{maxAlliance}})</span>
</div>
</div>
</div>
</div>
</div>
{{/accessAlliance.length}}
<input type="hidden" name="id" value="{{ mapData.config.id }}" />
</form>
{{/hasRightMapUpdate}}
{{^hasRightMapUpdate}}
<div class="alert alert-info">
<span class="txt-color txt-color-info">Restricted</span>
<small>You don´t have the required roles.</small>
</div>
{{/accessCorporation.length}}
{{! alliance search ------------------------------------------------- }}
{{#accessAlliance.length}}
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{allianceSelectId}}">Alliances</label>
<div class="col-sm-10">
<div class="input-group" title="add/remove alliances">
<label for="{{allianceSelectId}}"></label>
<select id="{{allianceSelectId}}" name="mapAlliances[]" multiple="multiple" >
{{#accessAlliance}}
<option value="{{id}}" selected="selected" >{{name}}</option>
{{/accessAlliance}}
</select>
<span class="note help-block with-errors">Search alliance name (max {{maxAlliance}})</span>
</div>
</div>
</div>
</div>
</div>
{{/accessAlliance.length}}
<input type="hidden" name="id" value="{{ mapData.config.id }}" />
</form>
{{/hasRightMapUpdate}}
</div>
{{/hideSettingsTab}}
@@ -378,90 +380,104 @@
<div role="tabpanel" class="tab-pane fade {{#openTabDownload}}in active{{/openTabDownload}}" id="{{dialogMapDownloadContainerId}}">
<h4 class="pf-dynamic-area">Map export</h4>
<form role="form" class="form-horizontal" id="{{dialogMapExportFormId}}">
{{#hasRightMapExport}}
<form role="form" class="form-horizontal" id="{{dialogMapExportFormId}}">
<div class="form-group">
<label class="control-label col-sm-2" for="{{fieldExportId}}">Export name</label>
<div class="col-sm-10">
<div class="input-group">
<input class="form-control" id="{{fieldExportId}}" type="text" name="{{fieldExportId}}" value="{{#formatFilename}}{{mapData.config.name}}{{/formatFilename}}" pattern="^[_a-zA-Z0-9]{1,}$" data-minlength="3" data-minlength-error="Min. of 3 characters" data-error="Invalid format: _ a-z A-Z 0-9" required>
<div class="input-group-btn">
<a type="button" id="{{buttonExportId}}" class="btn btn-default" href="" download="data.json">
<i class="fas fa-fw fa-upload"></i> Export
</a>
<div class="form-group">
<label class="control-label col-sm-2" for="{{fieldExportId}}">Export name</label>
<div class="col-sm-10">
<div class="input-group">
<input class="form-control" id="{{fieldExportId}}" type="text" name="{{fieldExportId}}" value="{{#formatFilename}}{{mapData.config.name}}{{/formatFilename}}" pattern="^[_a-zA-Z0-9]{1,}$" data-minlength="3" data-minlength-error="Min. of 3 characters" data-error="Invalid format: _ a-z A-Z 0-9" required>
<div class="input-group-btn">
<a type="button" id="{{buttonExportId}}" class="btn btn-default" href="" download="data.json">
<i class="fas fa-fw fa-upload"></i> Export
</a>
</div>
</div>
<div class="note help-block with-errors"></div>
</div>
<div class="note help-block with-errors"></div>
</div>
</div>
</form>
</form>
{{/hasRightMapExport}}
{{^hasRightMapExport}}
<div class="alert alert-info">
<span class="txt-color txt-color-info">Restricted</span>
<small>You don´t have the required roles.</small>
</div>
{{/hasRightMapExport}}
<h4 class="pf-dynamic-area">Map Import</h4>
<form role="form" class="form-horizontal" id="{{dialogMapImportFormId}}">
<div class="form-group">
<label for="type" class="col-sm-2 control-label">Type</label>
<div class="col-sm-3">
<select name="typeId" id="type" class="form-control" title="Alliance/Corporation maps require character authentication" data-placement="top">
{{#type}}
<option value="{{id}}">{{label}}</option>
{{/type}}
</select>
{{#hasRightMapImport}}
<form role="form" class="form-horizontal" id="{{dialogMapImportFormId}}">
<div class="form-group">
<label for="type" class="col-sm-2 control-label">Type</label>
<div class="col-sm-3">
<select name="typeId" id="type" class="form-control" title="Alliance/Corporation maps require character authentication" data-placement="top">
{{#type}}
<option value="{{id}}">{{label}}</option>
{{/type}}
</select>
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="{{fieldImportId}}">Import file</label>
<div class="col-sm-10">
<div class="row">
<div class="col-sm-12">
<div class="input-group">
<input class="form-control" id="{{fieldImportId}}" type="file" name="{{fieldImportId}}" accept=".json" data-error="Select a valid file" required>
<div class="input-group-btn">
<button type="button" id="{{buttonImportId}}" class="btn btn-default">
<i class="fas fa-fw fa-download"></i> Import
</button>
<div class="form-group">
<label class="control-label col-sm-2" for="{{fieldImportId}}">Import file</label>
<div class="col-sm-10">
<div class="row">
<div class="col-sm-12">
<div class="input-group">
<input class="form-control" id="{{fieldImportId}}" type="file" name="{{fieldImportId}}" accept=".json" data-error="Select a valid file" required>
<div class="input-group-btn">
<button type="button" id="{{buttonImportId}}" class="btn btn-default">
<i class="fas fa-fw fa-download"></i> Import
</button>
</div>
</div>
<div class="note help-block with-errors"></div>
</div>
<div class="note help-block with-errors"></div>
</div>
</div>
</div>
<div class="pf-form-dropzone">Drop file here</div>
<h4 id="{{dialogMapImportInfoId}}" class="pf-dynamic-area" style="display: none;"></h4>
<div class="{{formErrorContainerClass}} alert alert-danger" style="display: none;">
<span class="txt-color txt-color-danger">Error</span>
<small>(important non-critical information)</small>
</div>
<div class="{{formWarningContainerClass}} alert alert-warning" style="display: none;">
<span class="txt-color txt-color-warning">Warning</span>
<small>(important non-critical information)</small>
</div>
</form>
{{/hasRightMapImport}}
{{^hasRightMapImport}}
<div class="alert alert-info">
<span class="txt-color txt-color-info">Restricted</span>
<small>You don´t have the required roles.</small>
</div>
{{/hasRightMapImport}}
<div class="pf-form-dropzone">Drop file here</div>
<h4 id="{{dialogMapImportInfoId}}" class="pf-dynamic-area" style="display: none;"></h4>
<div class="{{formErrorContainerClass}} alert alert-danger" style="display: none;">
<span class="txt-color txt-color-danger">Error</span>
<small> (important non-critical information)</small>
</div>
<div class="{{formWarningContainerClass}} alert alert-warning" style="display: none;">
<span class="txt-color txt-color-warning">Warning</span>
<small> (important non-critical information)</small>
</div>
</form>
</div>
{{/hideDownloadTab}}
<div class="{{formInfoContainerClass}} alert alert-info" style="display: none;">
<span class="txt-color txt-color-information">Info</span>
<small> (important non-critical information)</small>
<small>(important non-critical information)</small>
</div>
<div class="{{formWarningContainerClass}} alert alert-warning" style="display: none;">
<span class="txt-color txt-color-warning">Warning</span>
<small> (important non-critical information)</small>
<small>(important non-critical information)</small>
</div>
<div class="{{formErrorContainerClass}} alert alert-danger" style="display: none;">
<span class="txt-color txt-color-danger">Error</span>
<small> (important non-critical information)</small>
<small>(important non-critical information)</small>
</div>
</div>
</div>

View File

@@ -224,7 +224,9 @@
<form role="form" class="form-horizontal">
{{#userData.character}}
<h4 class="pf-dynamic-area"><img src="{{ccpImageServer}}/Character/{{id}}_64.jpg">&nbsp;&nbsp;{{name}}</h4>
<h4 class="pf-dynamic-area"><img src="{{ccpImageServer}}/Character/{{id}}_64.jpg">&nbsp;&nbsp;{{name}}&nbsp;
<span class="label {{roleLabelClass}}">{{role.label}}</span>
</h4>
<div class="row">
<div class="col-xs-8 col-sm-6">

View File

@@ -1,71 +1,79 @@
<form role="form" class="form-horizontal">
{{#hasRightMapForm}}
<form role="form" class="form-horizontal">
<div class="row">
<div class="col-xs-6 col-sm-3">
<div class="form-group">
<label for="icon" class="col-sm-4 control-label">Icon</label>
<div class="col-sm-8">
<select style="font-family: FontAwesome" name="icon" id="icon" class="form-control" title="Map icon" data-placement="top">
{{#icon}}
<option value="{{class}}">{{{unicode}}}</option>
{{/icon}}
</select>
<div class="row">
<div class="col-xs-6 col-sm-3">
<div class="form-group">
<label for="icon" class="col-sm-4 control-label">Icon</label>
<div class="col-sm-8">
<select class="form-control pf-form-icon-field" name="icon" id="icon" title="Map icon" data-placement="top">
{{#icon}}
<option value="{{class}}" >{{{unicode}}}</option>
{{/icon}}
</select>
</div>
</div>
</div>
<div class="col-xs-6 col-sm-9">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<div class="col-sm-10">
<input name="name" type="text" class="form-control" id="name" value="" placeholder="Map name" data-error="Name required" data-minlength="3" data-minlength-error="Min. of 3 characters" required>
<span class="note help-block with-errors">Choose a meaningful name</span>
</div>
</div>
</div>
</div>
<div class="col-xs-6 col-sm-9">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<div class="col-sm-10">
<input name="name" type="text" class="form-control" id="name" value="" placeholder="Map name" data-error="Name required" data-minlength="3" data-minlength-error="Min. of 3 characters" required>
<span class="note help-block with-errors">Choose a meaningful name</span>
<div class="row">
<div class="col-xs-6 col-sm-6">
<div class="form-group">
<label for="scope" class="col-sm-2 control-label">Scope</label>
<div class="col-sm-10">
<select name="scopeId" id="scope" class="form-control" title="Connections that get tracked on this map" data-placement="top">
{{#scope}}
<option value="{{id}}">{{label}}</option>
{{/scope}}
</select>
</div>
</div>
</div>
<div class="col-xs-6 col-sm-6">
<div class="form-group">
<label for="type" class="col-sm-2 control-label">Type</label>
<div class="col-sm-10">
<select name="typeId" id="type" class="form-control" title="Alliance/Corporation maps require character authentication" data-placement="top">
{{#type}}
<option value="{{id}}">{{label}}</option>
{{/type}}
</select>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-sm-6">
<div class="form-group">
<label for="scope" class="col-sm-2 control-label">Scope</label>
<div class="col-sm-10">
<select name="scopeId" id="scope" class="form-control" title="Connections that get tracked on this map" data-placement="top">
{{#scope}}
<option value="{{id}}">{{label}}</option>
{{/scope}}
</select>
</div>
</div>
<input type="hidden" name="id" value="0" />
<div class="{{formInfoContainerClass}} alert alert-info" style="display: none;">
<span class="txt-color txt-color-information">Info</span>
<small> (important non-critical information)</small>
</div>
<div class="col-xs-6 col-sm-6">
<div class="form-group">
<label for="type" class="col-sm-2 control-label">Type</label>
<div class="col-sm-10">
<select name="typeId" id="type" class="form-control" title="Alliance/Corporation maps require character authentication" data-placement="top">
{{#type}}
<option value="{{id}}">{{label}}</option>
{{/type}}
</select>
</div>
</div>
<div class="{{formWarningContainerClass}} alert alert-warning" style="display: none;">
<span class="txt-color txt-color-warning">Warning</span>
<small> (important non-critical information)</small>
</div>
<div class="{{formErrorContainerClass}} alert alert-danger" style="display: none;">
<span class="txt-color txt-color-danger">Error</span>
<small> (important non-critical information)</small>
</div>
</form>
{{/hasRightMapForm}}
{{^hasRightMapForm}}
<div class="alert alert-info">
<span class="txt-color txt-color-info">Restricted</span>
<small>You don´t have the required roles.</small>
</div>
<input type="hidden" name="id" value="0" />
<div class="{{formInfoContainerClass}} alert alert-info" style="display: none;">
<span class="txt-color txt-color-information">Info</span>
<small> (important non-critical information)</small>
</div>
<div class="{{formWarningContainerClass}} alert alert-warning" style="display: none;">
<span class="txt-color txt-color-warning">Warning</span>
<small> (important non-critical information)</small>
</div>
<div class="{{formErrorContainerClass}} alert alert-danger" style="display: none;">
<span class="txt-color txt-color-danger">Error</span>
<small> (important non-critical information)</small>
</div>
</form>
{{/hasRightMapForm}}

View File

@@ -0,0 +1,42 @@
<div class="row">
<div class="col-xs-6 col-sm-3">
<div class="form-group">
<div class="col-sm-12">
<p class="form-control-static">{{@right->rightId->label}}
<i class="fas fa-fw fa-sm fa-question-circle pf-help-light" title="{{@right->rightId->description}}"></i>
</p>
</div>
</div>
</div>
<repeat group="{{ @roles }}" value="{{ @role }}" >
<set roleType="" />
<check if="{{ @role->_id==2 }}">
<set roleType="radio-danger" />
</check>
<check if="{{ @role->_id==3 }}">
<set roleType="radio-info" />
</check>
<set checked="" />
<check if="{{ @role->_id == @right->roleId->_id }}">
<set checked="checked" />
</check>
<div class="col-xs-6 col-sm-3 text-right">
<div class="form-group">
<div class="col-xs-12">
<div class="radio {{ @roleType }}">
<input id="form_role_{{ (int)@@id }}_{{ (int)@right->rightId->_id }}_{{ (int)@role->_id }}" name="rights[{{(int)@right->rightId->_id}}][roleId]" value="{{ (int)@role->_id }}" type="radio" {{ @checked }}>
<label for="form_role_{{ (int)@@id }}_{{ (int)@right->rightId->_id }}_{{ (int)@role->_id }}">{{ @role->label }}</label >
</div>
</div>
</div>
</div>
</repeat>
<input type="hidden" name="rights[{{@right->rightId->_id}}][id]" value="{{(int)@right->_id}}">
<input type="hidden" name="rights[{{@right->rightId->_id}}][corporationId]" value="{{(int)@id}}">
<input type="hidden" name="rights[{{@right->rightId->_id}}][rightId]" value="{{(int)@right->rightId->_id}}">
</div>

View File

@@ -1,4 +1,4 @@
<div class="pf-splash-warning">
<div class="pf-splash pf-splash-warning">
<div class="pf-color-line warning"></div>
<div class="pf-splash-title">
<h1>

View File

@@ -1,4 +1,4 @@
<div class="pf-splash-error">
<div class="pf-splash pf-splash-error">
<div class="pf-color-line danger"></div>
<div class="pf-splash-title">
<h1>

View File

@@ -2,7 +2,7 @@
<tbody>
{{#otherCharacters}}
<tr>
<td><img src="https://image.eveonline.com/Character/{{id}}_32.jpg" alt="{{name}}"></td>
<td><img src="{{image}}" alt="{{name}}"></td>
<td>{{name}}</td>
<td class="text-right text-nowrap">
<a class="btn btn-sm btn-default" href="{{routes.ssoLogin}}?characterId={{id}}" target="_blank" title="new tab">

View File

@@ -11,16 +11,16 @@
</span>
<p class="navbar-text">
{{ @character->name }}&nbsp;&nbsp;&nbsp;&nbsp;<span class="label label-success">{{ @character->role }}</span>
{{ @character->name }}&nbsp;&nbsp;&nbsp;&nbsp;<span class="label label-{{ @character->roleId->style }}">{{ @character->roleId->label }}</span>
</p>
</div>
<div class="navbar-collapse">
<ul class="nav navbar-nav navbar-right" role="tablist">
<li class="{{ @tplPage == 'settings' ? 'active' : '' }} disabled"><a href="javascript:void(0);"><i class="fas fa-fw fa-cogs"></i>&nbsp;Settings</a></li>
<li class="{{ @tplPage == 'members' ? 'active' : '' }}"><a href="/admin/members"><i class="fas fa-fw fa-users"></i>&nbsp;Members</a></li>
<li class="{{ @tplPage == 'maps' ? 'active' : '' }}"><a href="/admin/maps"><i class="fas fa-fw fa-sitemap"></i>&nbsp;Maps</a></li>
<li class="{{ @tplPage == 'activity' ? 'active' : '' }} disabled"><a href="javascript:void(0);"><i class="fas fa-fw fa-chart-line"></i>&nbsp;Activity</a></li>
<li class="{{ @tplPage == 'login' ? 'active' : '' }}"><a href="/admin/login"><i class="fas fa-fw fa-sign-in-alt"></i>&nbsp;SSO</a></li>
<li class="pf-overlay-trigger {{ @tplPage == 'settings' ? 'active' : '' }}"><a href="/admin/settings"><i class="fas fa-fw fa-cogs"></i>&nbsp;Settings</a></li>
<li class="pf-overlay-trigger {{ @tplPage == 'members' ? 'active' : '' }}"><a href="/admin/members"><i class="fas fa-fw fa-users"></i>&nbsp;Members</a></li>
<li class="pf-overlay-trigger {{ @tplPage == 'maps' ? 'active' : '' }}"><a href="/admin/maps"><i class="fas fa-fw fa-sitemap"></i>&nbsp;Maps</a></li>
<li class="pf-overlay-trigger {{ @tplPage == 'activity' ? 'active' : '' }} disabled"><a href="javascript:void(0);"><i class="fas fa-fw fa-chart-line"></i>&nbsp;Activity</a></li>
<li class="pf-overlay-trigger {{ @tplPage == 'login' ? 'active' : '' }}"><a href="/admin/login"><i class="fas fa-fw fa-sign-in-alt"></i>&nbsp;SSO</a></li>
</ul>
</div>
</check>

View File

@@ -580,7 +580,8 @@
<i class="fas fa-fw fa-battery-half"></i>
</th>
<th class="col-sm-1 col-md-1"></th>
<th class="col-sm-1 col-md-1">&nbsp;</th>
<th class="col-sm-1 col-md-1"></th>
<th class="col-sm-1 col-md-1"></th>
</tr>
</thead>
<tbody>
@@ -610,7 +611,7 @@
</false>
</check>
</td>
<td>&nbsp;</td>
<td></td>
<td class="text-center">
<check if="{{ @tableData.empty === true }}">
<true>
@@ -622,6 +623,7 @@
</check>
</td>
<td></td>
<td></td>
<td class="text-center">
<check if="{{ @tableData.statusCheckCount == 0 && @tableData.exists }}">
<true>
@@ -643,6 +645,7 @@
<td class="bg-color bg-color-tealDarkest">column</td>
<td class="bg-color bg-color-tealDarkest text-center">exist</td>
<td class="bg-color bg-color-tealDarkest" title="data type">type</td>
<td class="bg-color bg-color-tealDarkest text-center" title="nullable">null</td>
<td class="bg-color bg-color-tealDarkest text-center" title="index">I</td>
<td class="bg-color bg-color-tealDarkest text-center" title="unique">U</td>
<td class="bg-color bg-color-tealDarkest text-center"></td>
@@ -679,6 +682,23 @@
</false>
</check>
</td>
<td class="bg-color bg-color-tealDarker text-center">
<check if="{{ @columnData.exists }}">
<true>
<check if="{{ @columnData.changedNullable }}">
<true>
<kbd class="txt-color txt-color-warning" title="{{ @columnData.requiredNullable }}">{{ @columnData.currentNullable }}</kbd>
</true>
<false>
<kbd class="txt-color txt-color-success">{{ @columnData.currentNullable }}</kbd>
</false>
</check>
</true>
<false>
<i class="fas fa-fw fa-ellipsis-h txt-color txt-color-danger" title="{{ @columnData.requiredNullable }}"></i>
</false>
</check>
</td>
<td class="bg-color bg-color-tealDarker text-center">
<check if="{{ @columnData.exists }}">
<true>
@@ -740,11 +760,12 @@
<td class="bg-color bg-color-tealDarkest"></td>
<td class="bg-color bg-color-tealDarkest"></td>
<td class="bg-color bg-color-tealDarkest"></td>
<td class="bg-color bg-color-tealDarkest"></td>
</tr>
<repeat group="{{ @tableData.foreignKeys }}" value="{{ @keyData }}" counter="{{ @countForeignKey }}">
<tr class="{{ @tableName }}_col collapse">
<td class="text-center bg-color bg-color-tealDarker">{{@countForeignKey}}.</td>
<td class="bg-color bg-color-tealDarker" colspan="5">{{ @keyData.keyName }}</td>
<td class="bg-color bg-color-tealDarker" colspan="6">{{ @keyData.keyName }}</td>
<td class="bg-color bg-color-tealDarker text-center">
<check if="{{ @keyData.exists }}">
<true>

View File

@@ -42,6 +42,7 @@
&.txt-color-primary { color: $brand-primary !important; }
&.txt-color-success { color: $brand-success !important; }
&.txt-color-information { color: $brand-info !important; }
&.txt-color-info { color: $brand-info !important; }
&.txt-color-warning { color: $brand-warning !important; }
&.txt-color-danger { color: $brand-danger !important; }
}

View File

@@ -231,30 +231,6 @@ h6 {
position:relative;
}
.form-actions {
display: block;
padding: 13px 14px 15px;
border-top: 1px solid rgba(red($black), green($black), blue($black), 0.1);
background: rgba(red(lighten($gray-lightest, 2%)), green(lighten($gray-lightest, 2%)), blue(lighten($gray-lightest, 2%)), 0.9);
margin-top:25px;
margin-left: -13px;
margin-right: -13px;
margin-bottom: -13px;
text-align:right;
}.well .form-actions {
margin-left: -19px;
margin-right: -19px;
margin-bottom: -19px;
}.well.well-lg .form-actions {
margin-left: -24px;
margin-right: -24px;
margin-bottom: -24px;
}.well.well-sm .form-actions {
margin-left: -9px;
margin-right: -9px;
margin-bottom: -9px;
}
.popover-content .form-actions {
margin:0 -14px -9px;

View File

@@ -313,12 +313,12 @@ $container-tablet: ((720px + $grid-gutter-width));
$container-sm: $container-tablet;
// Medium screen / desktop
$container-desktop: ((940px + $grid-gutter-width));
$container-desktop: ((1040px + $grid-gutter-width));
//** For `$screen-md-min` and up.
$container-md: $container-desktop;
// Large screen / wide desktop
$container-large-desktop: ((1140px + $grid-gutter-width));
$container-large-desktop: ((1240px + $grid-gutter-width));
//** For `$screen-lg-min` and up.
$container-lg: $container-large-desktop;
@@ -529,7 +529,7 @@ $label-info-bg: $brand-info;
$label-warning-bg: $brand-warning;
$label-danger-bg: $brand-danger;
$label-color: $gray-darkest;
$label-color: $black;
$label-link-hover-color: #fff;

View File

@@ -1,31 +1,31 @@
.modal-content {
.modal-content, .panel-body {
// dialog headline ==========================================================
h2 {
font-family: $font-family-sans-serif;
letter-spacing: 0px;
letter-spacing: 0;
font-size: $font-size-h5;
margin: 20px 0;
line-height: normal;
}
h2, h4{
&.pf-dynamic-area{
h2, h4 {
&.pf-dynamic-area {
min-height: 0;
margin: 0 0 10px 0;
& > img{
& > img {
margin: -10px 5px -10px -10px;
width: 35px;
}
}
// collapse-able headline =================================================
&[data-toggle="collapse"]{
&[data-toggle="collapse"] {
cursor: pointer;
&:hover{
&:after{
&:hover {
&:after {
color: $orange !important;
}
}
@@ -46,17 +46,20 @@
@extend .fa-fw;
}
&.collapsed{
&:after{
&.collapsed {
&:after {
top: 13px;
right: 5px;
color: $gray-light;
@include rotate( 90deg );
@include rotate(90deg);
}
}
}
}
}
.modal-content {
// data tables ==============================================================
.dataTables_wrapper{
+ .alert{

View File

@@ -53,6 +53,15 @@ fieldset[disabled]{
}
}
.pf-form-icon-field {
font-family: "Font Awesome 5 Free";
font-weight: bold;
option {
font-family: inherit;
font-weight: inherit;
}
}
// form fields with icons groups (stacked icons) ==================================================
.input-icon-left:not(.input-icon-right){

View File

@@ -23,7 +23,7 @@
}
}
.pf-splash, .pf-splash-warning, .pf-splash-error {
.pf-splash{
position: absolute;
z-index: 2000;
background-color: $gray-darkest;
@@ -34,6 +34,10 @@
right: 0;
will-change: opacity;
&:not(.pf-splash-warning):not(.pf-splash-error){
cursor: wait;
}
.pf-splash-title{
position: fixed;
left: 50%;
@@ -81,12 +85,16 @@
// content
.pf-landing{
section:not(:last-of-type){
border-bottom: 1px solid $gray-darker;
}
section{
min-height: 200px;
padding: 20px 0 40px 0;
border-bottom: 1px solid $gray-darker;
h4{
h4:not(.pf-dynamic-area){
font-size: 18px;
font-family: $font-family-sans-serif;
margin: 5px 0 10px 0;
@@ -647,6 +655,30 @@
}
}
// admin page -----------------------------------------------------------------
.pf-body[data-script='admin']{
.navbar-brand:hover{
color: #777; // overwrite default
}
.panel{
text-align: initial;
h3 img{
position: absolute;
right: 0;
top: 0;
margin: 4px 14px 0 0;
border-radius: 30%;
}
}
.form-horizontal .panel{
color: $gray-lighter;
}
}
// TEST ---
/*
.form-control {

View File

@@ -460,6 +460,19 @@ table{
}
}
.list-group-item{
&.disabled{
&:after{
content: '\f023';
font-family: 'Font Awesome 5 Free';
font-weight: bold;
color: $gray-darker;
position: absolute;
right: 8px;
}
}
}
// custom scrollbars ==============================================================================
.mCSB_container, .mCSB_dragger{
will-change: top, left;
@@ -653,7 +666,6 @@ table{
// WH effects =====================================================================================
.pf-system-effect{
display: none; // if effect is active it will be overwritten
cursor: default;
color: $gray-lighter;
}
@@ -1309,6 +1321,20 @@ code {
margin-bottom: 0; // overwrite default "alert" style
}
// reverse order (reverse render order)
.panel-reverse-order{
display: table;
width: 100%;
.reverse-order-header{
display: table-header-group;
}
.reverse-order-footer{
display: table-footer-group;
}
}
// drag&drop (Sortable) ===========================================================================
.pf-sortable-ghost{
will-change: opacity;

View File

@@ -205,6 +205,10 @@ $mapWrapperMaxWidth: $mapWidth + 35px;
.pf-map-overlay-local-trigger{
margin-bottom: 10px;
&:hover, &.right{
color: $orange-dark; // overlay open
}
}
i{
@@ -313,7 +317,12 @@ $mapWrapperMaxWidth: $mapWidth + 35px;
margin-right: 2px;
}
.pf-system-effect{
font-size: 11px;
}
.fa-lock{
font-size: 11px;
display: none; // triggered by system-lock class
}
@@ -655,13 +664,14 @@ $mapWrapperMaxWidth: $mapWidth + 35px;
filter: blur(0.000001px);
-webkit-font-smoothing: antialiased;
font-family: Arial, sans-serif; // fix for element width on custom font family
padding: 0 2px;
font-size: 10px;
padding: 2px;
font-size: 9.5px;
line-height: 100%;
z-index: 1020;
background-color: $gray;
color: $gray-lighter;
@include border-radius(4px);
@include box-shadow(0 6px 12px rgba(0,0,0,.4));
@include border-radius(50%);
@include box-shadow(0 3px 6px rgba(0,0,0,.3));
}
}

View File

@@ -54,7 +54,7 @@ $check-icon: fa-content($fa-var-check) !default;
border: 1px solid $input-border;
border-radius: 3px;
background-color: $gray-dark;
@include transition(border 0.15s ease-in-out, color 0.15s ease-in-out);
@include transition(border 0.18s ease, color 0.18s ease, background-color 0.18s ease);
}
&::after {
@@ -74,7 +74,7 @@ $check-icon: fa-content($fa-var-check) !default;
margin-left: -20px;
padding-left: 3px;
padding-top: 1px;
font-size: 11px;
font-size: calc(100% - 1px);
color: $input-color;
}
}
@@ -185,7 +185,7 @@ $check-icon: fa-content($fa-var-check) !default;
border: 1px solid $input-border;
border-radius: 50%;
background-color: $gray-dark;
@include transition(border 0.15s ease-in-out);
@include transition(border 0.18s ease, color 0.18s ease);
}
&::after{
@@ -196,12 +196,16 @@ $check-icon: fa-content($fa-var-check) !default;
height: 11px;
left: 3px;
top: 3px;
opacity: 0;
transform: scale(2) rotateZ(-20deg);
transition: all .18s ease;
will-change: transform, opacity;
margin-left: -20px;
border-radius: 50%;
background-color: $input-color;
@include scale(0, 0);
@include transition-transform(.1s cubic-bezier(.8,-0.33,.2,1.33));
@include transition-transform(.18s cubic-bezier(.8,-0.33,.2,1.33));
//curve - http://cubic-bezier.com/#.8,-0.33,.2,1.33
}
}
@@ -209,13 +213,15 @@ $check-icon: fa-content($fa-var-check) !default;
input[type="radio"]{
opacity: 0;
z-index: 1;
cursor: pointer;
&:focus + label::before{
@include tab-focus();
// @include tab-focus(); // no outline frame for Pathfinder
}
&:checked + label::after{
@include scale(1, 1);
opacity: 1;
}
&:disabled + label{