- New "Intel module" for Citadel tracking, closed #246

- updated "Select2" js lib `4.0.3` -> `4.0.6-rc.1`
- fixed some login Issues
- fixed broken `map/*` reroute URL wildcard replacement
- fixed broken cache layer for Universe models
This commit is contained in:
Mark Friedrich
2018-05-01 19:51:17 +02:00
parent 9641b2e075
commit efd768974f
79 changed files with 4504 additions and 427 deletions

View File

@@ -6,7 +6,8 @@
* Time: 17:42
*/
namespace controller\api;
namespace Controller\Api;
use Controller;
use Model;
@@ -20,15 +21,14 @@ class Access extends Controller\AccessController {
* @throws \Exception
*/
public function search($f3, $params){
$accessData = [];
if(
array_key_exists( 'arg1', $params) &&
array_key_exists( 'arg2', $params)
array_key_exists('arg1', $params) &&
array_key_exists('arg2', $params)
){
$searchType = strtolower( $params['arg1'] );
$searchToken = strtolower( $params['arg2'] );
$searchType = strtolower($params['arg1']);
$searchToken = strtolower($params['arg2']);
$accessModel = null;
switch($searchType){
@@ -59,13 +59,9 @@ class Access extends Controller\AccessController {
}
}
}
}
echo json_encode($accessData);
}
}

View File

@@ -70,7 +70,6 @@ class Map extends Controller\AccessController {
// default map type config
$mapsDefaultConfig = Config::getMapsDefaultConfig();
$mapTypeData = [];
foreach((array)$rows as $rowData){
$data = [
@@ -81,7 +80,6 @@ class Map extends Controller\AccessController {
'defaultConfig' => $mapsDefaultConfig[$rowData->name]
];
$mapTypeData[$rowData->name] = $data;
}
$return->mapTypes = $mapTypeData;
@@ -190,6 +188,17 @@ class Map extends Controller\AccessController {
'status' => (bool)Config::getPathfinderData('discord.status')
];
// structure status ---------------------------------------------------------------------------------------
$structureStatus = Model\StructureStatusModel::getAll();
$structureData = [];
foreach($structureStatus as $status){
$structureData[$status->_id] = $status->getData();
}
$return->structureStatus = $structureData;
// universe category data ---------------------------------------------------------------------------------
$return->universeCategories = [65 => Model\Universe\BasicUniverseModel::getNew('CategoryModel')->getById(65)->getData()];
$f3->set(self::CACHE_KEY_INIT, $return, $expireTimeCache );
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 24.04.2018
* Time: 22:23
*/
namespace Controller\Api;
use Controller;
use Model;
use Exception;
class Structure extends Controller\AccessController {
/**
* save/update structure
* @param \Base $f3
* @throws Exception
*/
public function save(\Base $f3){
$structureData = (array)$f3->get('POST');
$activeCharacter = $this->getCharacter();
$return = (object) [];
$return->error = [];
/**
* @var $structure Model\StructureModel
*/
$structure = Model\BasicModel::getNew('StructureModel');
$structure->getById((int)$structureData['id']);
if($structure->dry() || $structure->hasAccess($activeCharacter)){
$newStructure = $structure->dry();
try{
$structure->copyfrom($structureData, ['structureId', 'corporationId', 'systemId', 'statusId', 'name', 'description']);
$structure->save();
if($newStructure){
$activeCharacter->getCorporation()->saveStructure($structure);
}
$return->structures = $structure->getDataByCorporations();
}catch(Exception\ValidationException $e){
$return->error[] = $e->getError();
}
}
echo json_encode($return);
}
/**
* delete structure
* @param \Base $f3
* @throws Exception
*/
public function delete(\Base $f3){
$structureData = (array)$f3->get('POST');
$structureId = (int)$structureData['id'];
$return = (object) [];
if($structureId){
$activeCharacter = $this->getCharacter();
/**
* @var $structure Model\StructureModel
*/
$structure = Model\BasicModel::getNew('StructureModel');
$structure->getById($structureId);
if($structure->hasAccess($activeCharacter) && $structure->erase()){
$return->deletedStructureIds = [$structureId];
}
}
echo json_encode($return);
}
}

View File

@@ -7,6 +7,7 @@
*/
namespace Controller\Api;
use Controller;
use Data\Mapper as Mapper;
use lib\Config;
@@ -441,6 +442,30 @@ class System extends Controller\AccessController {
echo json_encode($return);
}
/**
* @param \Base $f3
* @throws \Exception
*/
public function getData(\Base $f3){
$requestData = (array)$f3->get('POST');
$mapId = (int)$requestData['mapId'];
$systemId = (int)$requestData['systemId'];
$activeCharacter = $this->getCharacter();
$return = (object) [];
if(
!is_null($map = $activeCharacter->getMap($mapId)) &&
!is_null($system = $map->getSystemById($systemId))
){
$return->system = $system->getData();
$return->system->signatures = $system->getSignaturesData();
$return->system->structures = $system->getStructuresData();
}
echo json_encode($return);
}
/**
* delete systems and all its connections from map
* -> set "active" flag

View File

@@ -0,0 +1,36 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 21.04.2018
* Time: 15:49
*/
namespace Controller\Api;
use Controller;
use Controller\Ccp as Ccp;
class Universe extends Controller\AccessController {
/**
* search static Universe data by string within categories
* @param \Base $f3
* @param $params
*/
public function search(\Base $f3, $params){
$postData = (array)$f3->get('POST');
$categories = (array)$postData['categories'];
$universeNameData = [];
if(
array_key_exists('arg1', $params) &&
!empty($search = strtolower($params['arg1'])) &&
!empty($categories)
){
$universeNameData = Ccp\Universe::searchUniverseNameData($categories, $search);
}
echo json_encode($universeNameData);
}
}

View File

@@ -42,6 +42,7 @@ class Sso extends Api\User{
const ERROR_VERIFY_CHARACTER = 'Unable to verify character data. %s';
const ERROR_LOGIN_FAILED = 'Failed authentication due to technical problems: %s';
const ERROR_CHARACTER_VERIFICATION = 'Character verification failed by SSP SSO';
const ERROR_CHARACTER_DATA = 'Failed to load characterData from ESI';
const ERROR_CHARACTER_FORBIDDEN = 'Character "%s" is not authorized to log in. Reason: %s';
const ERROR_SERVICE_TIMEOUT = 'CCP SSO service timeout (%ss). Try again later';
const ERROR_COOKIE_LOGIN = 'Login from Cookie failed. Please retry by CCP SSO';
@@ -207,7 +208,7 @@ class Sso extends Api\User{
// verification available data. Data is needed for "ownerHash" check
// get character data from ESI
$characterData = $this->getCharacterData($verificationCharacterData->CharacterID);
$characterData = $this->getCharacterData((int)$verificationCharacterData->CharacterID);
if( isset($characterData->character) ){
// add "ownerHash" and SSO tokens
@@ -281,6 +282,9 @@ class Sso extends Api\User{
);
}
}
}else{
// failed to load characterData from API
$f3->set(self::SESSION_KEY_SSO_ERROR, self::ERROR_CHARACTER_DATA);
}
}else{
// failed to verify character by CCP SSO
@@ -325,7 +329,7 @@ class Sso extends Api\User{
$loginCheck = $this->loginByCharacter($character);
if($loginCheck){
// route to "map"
$f3->reroute(['map']);
$f3->reroute($f3->alias('map','*= ') );
}
}
@@ -494,67 +498,44 @@ class Sso extends Api\User{
/**
* get character data
* @param int $characterId
* @return object
* @return \stdClass
* @throws \Exception
*/
public function getCharacterData($characterId){
public function getCharacterData(int $characterId) : \stdClass{
$characterData = (object) [];
$characterDataBasic = $this->getF3()->ccpClient->getCharacterData($characterId);
if($characterId){
$characterDataBasic = $this->getF3()->ccpClient->getCharacterData($characterId);
if( !empty($characterDataBasic) ){
// remove some "unwanted" data -> not relevant for Pathfinder
$characterData->character = array_filter($characterDataBasic, function($key){
return in_array($key, ['id', 'name', 'securityStatus']);
}, ARRAY_FILTER_USE_KEY);
if( !empty($characterDataBasic) ){
// remove some "unwanted" data -> not relevant for Pathfinder
$characterData->character = array_filter($characterDataBasic, function($key){
return in_array($key, ['id', 'name', 'securityStatus']);
}, ARRAY_FILTER_USE_KEY);
$characterData->corporation = null;
$characterData->alliance = null;
$characterData->corporation = null;
$characterData->alliance = null;
if(isset($characterDataBasic['corporation'])){
$corporationId = (int)$characterDataBasic['corporation']['id'];
/**
* @var Model\CorporationModel $corporationModel
*/
$corporationModel = Model\BasicModel::getNew('CorporationModel');
$corporationModel->getById($corporationId, 0);
if($corporationModel->dry()){
// request corporation data
$corporationData = $this->getF3()->ccpClient->getCorporationData($corporationId);
if( !empty($corporationData) ){
// check for NPC corporation
$corporationData['isNPC'] = $this->getF3()->ccpClient->isNpcCorporation($corporationId);
$corporationModel->copyfrom($corporationData, ['id', 'name', 'isNPC']);
$characterData->corporation = $corporationModel->save();
if($corporationId = (int)$characterDataBasic['corporation']['id']){
/**
* @var Model\CorporationModel $corporation
*/
$corporation = Model\BasicModel::getNew('CorporationModel');
$corporation->getById($corporationId, 0);
if( !$corporation->dry() ){
$characterData->corporation = $corporation;
}
}else{
$characterData->corporation = $corporationModel;
}
}
if(isset($characterDataBasic['alliance'])){
$allianceId = (int)$characterDataBasic['alliance']['id'];
/**
* @var Model\AllianceModel $allianceModel
*/
$allianceModel = Model\BasicModel::getNew('AllianceModel');
$allianceModel->getById($allianceId, 0);
if($allianceModel->dry()){
// request alliance data
$allianceData = $this->getF3()->ccpClient->getAllianceData($allianceId);
if( !empty($allianceData) ){
$allianceModel->copyfrom($allianceData, ['id', 'name']);
$characterData->alliance = $allianceModel->save();
if($allianceId = (int)$characterDataBasic['alliance']['id']){
/**
* @var Model\AllianceModel $allianceModel
*/
$alliance = Model\BasicModel::getNew('AllianceModel');
$alliance->getById($allianceId, 0);
if( !$alliance->dry() ){
$characterData->alliance = $alliance;
}
}else{
$characterData->alliance = $allianceModel;
}
}
}
@@ -564,29 +545,29 @@ class Sso extends Api\User{
/**
* update character
* @param $characterData
* @return \Model\CharacterModel
* @param \stdClass $characterData
* @return \Model\CharacterModel|null
* @throws \Exception
*/
protected function updateCharacter($characterData){
$characterModel = null;
protected function updateCharacter(\stdClass $characterData){
$character = null;
if( !empty($characterData->character) ){
/**
* @var Model\CharacterModel $characterModel
* @var Model\CharacterModel $character
*/
$characterModel = Model\BasicModel::getNew('CharacterModel');
$characterModel->getById((int)$characterData->character['id'], 0);
$characterModel->copyfrom($characterData->character, [
$character = Model\BasicModel::getNew('CharacterModel');
$character->getById((int)$characterData->character['id'], 0);
$character->copyfrom($characterData->character, [
'id', 'name', 'ownerHash', 'crestAccessToken', 'crestRefreshToken', 'esiScopes', 'securityStatus'
]);
$characterModel->corporationId = $characterData->corporation;
$characterModel->allianceId = $characterData->alliance;
$characterModel = $characterModel->save();
$character->corporationId = $characterData->corporation;
$character->allianceId = $characterData->alliance;
$character = $character->save();
}
return $characterModel;
return $character;
}
/**

View File

@@ -10,7 +10,8 @@ namespace Controller\Ccp;
use Controller\Controller;
use Model\BasicModel;
use lib\Util;
use Model;
class Universe extends Controller {
@@ -20,8 +21,9 @@ class Universe extends Controller {
* @throws \Exception
*/
public function setupDB(\Base $f3){
$this->setupRegions($f3);
$this->setupConstellations($f3);
//$this->setupRegions($f3);
//$this->setupConstellations($f3);
//$this->setupCategories($f3);
}
/**
@@ -93,4 +95,54 @@ class Universe extends Controller {
$constellationModel->reset();
}
}
/**
* @param \Base $f3
* @throws \Exception
*/
private function setupCategories(\Base $f3){
$categoryIdsWhitelist = [
6, // Ship
65 // Structure
];
$categoryIds = $f3->ccpClient->getUniverseCategories();
$categoryIds = array_intersect ($categoryIdsWhitelist, $categoryIds);
$categoryModel = Model\Universe\BasicUniverseModel::getNew('CategoryModel');
foreach($categoryIds as $categoryId){
$categoryModel->loadById($categoryId);
$categoryModel->loadGroupsData();
foreach((array)$categoryModel->groups as $group){
$group->loadTypesData();
}
$categoryModel->reset();
}
}
/**
* search universeName data by search term
* @param array $categories
* @param string $search
* @param bool $strict
* @return array
*/
public static function searchUniverseNameData(array $categories, string $search, bool $strict = false) : array {
$f3 = \Base::instance();
$universeNameData = [];
if( !empty($categories) && !empty($search)){
$universeIds = $f3->ccpClient->search($categories, $search, $strict);
if(isset($universeIds['error'])){
// ESI error
$universeNameData = $universeIds;
}elseif( !empty($universeIds) ){
$universeIds = Util::arrayFlattenByValue($universeIds);
$universeNameData = $f3->ccpClient->getUniverseNamesData($universeIds);
}
}
return $universeNameData;
}
}

View File

@@ -69,15 +69,18 @@ class Setup extends Controller {
'Model\WormholeModel',
'Model\RightModel',
'Model\RoleModel',
'Model\StructureModel',
'Model\CharacterStatusModel',
'Model\ConnectionScopeModel',
'Model\StructureStatusModel',
'Model\CharacterMapModel',
'Model\AllianceMapModel',
'Model\CorporationMapModel',
'Model\CorporationRightModel',
'Model\CorporationStructureModel',
'Model\UserCharacterModel',
'Model\CharacterModel',
@@ -105,6 +108,8 @@ class Setup extends Controller {
'info' => [],
'models' => [
'Model\Universe\TypeModel',
'Model\Universe\GroupModel',
'Model\Universe\CategoryModel',
'Model\Universe\StructureModel',
//'Model\Universe\RegionModel',
//'Model\Universe\ConstellationModel'

View File

@@ -65,8 +65,10 @@ class AbstractIterator extends \RecursiveArrayIterator {
$mapValue = static::$map[$iterator->key()];
// check for mapping key
if($iterator->hasChildren()){
if(
$iterator->hasChildren() &&
Util::is_assoc($iterator->current())
){
// recursive call for child elements
$iterator->offsetSet($iterator->key(), forward_static_call(array('self', __METHOD__), $iterator->getChildren())->getArrayCopy());
$iterator->next();

View File

@@ -29,12 +29,23 @@ class Util {
}
/**
* flatten multidimensional array
* flatten multidimensional array ignore keys
* @param array $array
* @return array
*/
static function arrayFlattenByValue(array $array) : array {
$return = [];
array_walk_recursive($array, function($value) use (&$return) { $return[] = $value; });
return $return;
}
/**
* flatten multidimensional array merge keys
* -> overwrites duplicate keys!
* @param array $array
* @return array
*/
static function arrayFlatten(array $array) : array {
static function arrayFlattenByKey(array $array) : array {
$return = [];
array_walk_recursive($array, function($value, $key) use (&$return) { $return[$key] = $value; });
return $return;

View File

@@ -27,6 +27,11 @@ class AllianceModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'ticker' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'shared' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
@@ -35,6 +40,9 @@ class AllianceModel extends BasicModel {
'allianceCharacters' => [
'has-many' => ['Model\CharacterModel', 'allianceId']
],
'alliancCorporations' => [
'has-many' => ['Model\CharacterModel', 'allianceId']
],
'mapAlliances' => [
'has-many' => ['Model\AllianceMapModel', 'allianceId']
]
@@ -54,6 +62,21 @@ class AllianceModel extends BasicModel {
return $allianceData;
}
/**
* Event "Hook" function
* return false will stop any further action
* @param self $self
* @param $pkeys
* @return bool
*/
public function beforeUpdateEvent($self, $pkeys){
// if model changed, 'update' col needs to be updated as well
// -> data no longer "outdated"
$this->touch('updated');
return parent::beforeUpdateEvent($self, $pkeys);
}
/**
* get all maps for this alliance
* @return array|mixed
@@ -87,9 +110,9 @@ class AllianceModel extends BasicModel {
* get all characters in this alliance
* @param array $characterIds
* @param array $options
* @return array
* @return CharacterModel[]
*/
public function getCharacters($characterIds = [], $options = []){
public function getCharacters($characterIds = [], $options = []) : array {
$characters = [];
$filter = ['active = ?', 1];
@@ -114,4 +137,21 @@ class AllianceModel extends BasicModel {
return $characters;
}
public function getById(int $id, int $ttl = self::DEFAULT_SQL_TTL, bool $isActive = true){
/**
* @var AllianceModel $alliance
*/
$alliance = parent::getById($id, $ttl, $isActive);
if($alliance->isOutdated()){
// request alliance data
$allianceData = self::getF3()->ccpClient->getAllianceData($id);
if( !empty($allianceData) ){
$alliance->copyfrom($allianceData, ['id', 'name', 'ticker']);
$alliance->save();
}
}
return $alliance;
}
}

View File

@@ -104,6 +104,11 @@ abstract class BasicModel extends \DB\Cortex {
*/
const DEFAULT_CACHE_TTL = 120;
/**
* default TTL for SQL query cache
*/
const DEFAULT_SQL_TTL = 3;
const ERROR_INVALID_MODEL_CLASS = 'Model class (%s) not found';
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = 0){
@@ -464,7 +469,7 @@ abstract class BasicModel extends \DB\Cortex {
* @param bool $isActive
* @return \DB\Cortex
*/
public function getById(int $id, int $ttl = 3, bool $isActive = true){
public function getById(int $id, int $ttl = self::DEFAULT_SQL_TTL, bool $isActive = true){
return $this->getByForeignKey('id', (int)$id, ['limit' => 1], $ttl, $isActive);
}
@@ -763,6 +768,28 @@ abstract class BasicModel extends \DB\Cortex {
return $this->validationError;
}
/**
* checks whether data is outdated and should be refreshed
* @return bool
*/
protected function isOutdated(): bool {
$outdated = true;
if(!$this->dry()){
$timezone = $this->getF3()->get('getTimeZone')();
$currentTime = new \DateTime('now', $timezone);
$updateTime = \DateTime::createFromFormat(
'Y-m-d H:i:s',
$this->updated,
$timezone
);
$interval = $updateTime->diff($currentTime);
if($interval->days < Universe\BasicUniverseModel::CACHE_MAX_DAYS){
$outdated = false;
}
}
return $outdated;
}
public function save(){
try{
return parent::save();

View File

@@ -181,7 +181,7 @@ class CharacterModel extends BasicModel {
// no cached character data found
$characterData = (object) [];
$characterData->id = $this->id;
$characterData->id = $this->_id;
$characterData->name = $this->name;
$characterData->role = $this->roleId->getData();
$characterData->shared = $this->shared;

View File

@@ -379,7 +379,7 @@ class ConnectionModel extends AbstractMapTrackingModel {
* get all connection log data linked to this connection
* @return array
*/
public function getLogsData() : array{
public function getLogsData() : array {
$logsData = [];
$logs = $this->getLogs();

View File

@@ -103,6 +103,16 @@ class CorporationModel extends BasicModel {
'nullable' => false,
'default' => ''
],
'ticker' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'memberCount' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0
],
'shared' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
@@ -121,6 +131,12 @@ class CorporationModel extends BasicModel {
],
'corporationRights' => [
'has-many' => ['Model\CorporationRightModel', 'corporationId']
],
'corporationStructures' => [
'has-many' => ['Model\CorporationStructureModel', 'corporationId']
],
'structures' => [
'has-many' => ['Model\StructureModel', 'corporationId']
]
];
@@ -145,6 +161,21 @@ class CorporationModel extends BasicModel {
return $cooperationData;
}
/**
* Event "Hook" function
* return false will stop any further action
* @param self $self
* @param $pkeys
* @return bool
*/
public function beforeUpdateEvent($self, $pkeys){
// if model changed, 'update' col needs to be updated as well
// -> data no longer "outdated"
$this->touch('updated');
return parent::beforeUpdateEvent($self, $pkeys);
}
/**
* get all maps for this corporation
* @param array $mapIds
@@ -210,6 +241,38 @@ class CorporationModel extends BasicModel {
return $characters;
}
/**
* get all structure data for this corporation
* @param array $systemIds
* @return array
*/
public function getStructuresData(array $systemIds = []) : array {
$structuresData = [];
$this->filter('corporationStructures', ['active = ?', 1]);
$this->has('corporationStructures.structureId', ['active = ?', 1]);
if($systemIds){
if(count($systemIds) == 1){
$filterSystems = 'systemId = ?';
$filterSystemIds = reset($systemIds);
}else{
$filterSystems = 'systemId IN (?)';
$filterSystemIds = $systemIds;
}
$this->has('corporationStructures.structureId', [$filterSystems, $filterSystemIds]);
}
if($this->corporationStructures) {
foreach($this->corporationStructures as $corporationStructure){
$structuresData[] = $corporationStructure->structureId->getData();
}
}
return $structuresData;
}
/**
* get roles for each character in this corp
* -> CCP API call
@@ -272,6 +335,46 @@ class CorporationModel extends BasicModel {
return $corporationRights;
}
/**
* load corporation by Id either from DB or load data from API
* @param int $id
* @param int $ttl
* @param bool $isActive
* @return \DB\Cortex
*/
public function getById(int $id, int $ttl = self::DEFAULT_SQL_TTL, bool $isActive = true){
/**
* @var CorporationModel $corporation
*/
$corporation = parent::getById($id, $ttl, $isActive);
if($corporation->isOutdated()){
// request corporation data
$corporationData = self::getF3()->ccpClient->getCorporationData($id);
if( !empty($corporationData) ){
// check for NPC corporation
$corporationData['isNPC'] = self::getF3()->ccpClient->isNpcCorporation($id);
$corporation->copyfrom($corporationData, ['id', 'name', 'ticker', 'memberCount', 'isNPC']);
$corporation->save();
}
}
return $corporation;
}
/**
* add new structure for this corporation
* @param StructureModel $structure
*/
public function saveStructure(StructureModel $structure){
if( !$structure->dry() ){
$corporationStructure = $this->rel('corporationStructures');
$corporationStructure->corporationId = $this;
$corporationStructure->structureId = $structure;
$corporationStructure->save();
}
}
/**
* get all corporations
* @param array $options

View File

@@ -0,0 +1,65 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 15.04.2018
* Time: 19:23
*/
namespace Model;
use DB\SQL\Schema;
class CorporationStructureModel extends BasicModel {
protected $table = 'corporation_structure';
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'
]
]
],
'structureId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\StructureModel',
'constraint' => [
[
'table' => 'structure',
'on-delete' => 'CASCADE'
]
]
]
];
/**
* 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', 'structureId'], true);
}
return $status;
}
}

View File

@@ -649,7 +649,7 @@ class MapModel extends AbstractMapTrackingModel {
* get all connection data in this map
* @return \stdClass[]
*/
public function getConnectionData(){
public function getConnectionData() : array {
$connectionData = [];
$connections = $this->getConnections();
@@ -663,6 +663,34 @@ class MapModel extends AbstractMapTrackingModel {
return $connectionData;
}
/**
* get all structures data for this map
* @param array $systemIds
* @return array
*/
public function getStructuresData(array $systemIds = []) : array {
$structuresData = [];
$corporations = $this->getAllCorporations();
foreach($corporations as $corporation){
// corporations should be unique
if( !isset($structuresData[$corporation->_id]) ){
// get all structures for current corporation
$corporationStructuresData = $corporation->getStructuresData($systemIds);
if( !empty($corporationStructuresData) ){
// corporation has structures
$structuresData[$corporation->_id] = [
'id' => $corporation->_id,
'name' => $corporation->name,
'structures' => $corporationStructuresData
];
}
}
}
return $structuresData;
}
/**
* set map access for an object (character, corporation or alliance)
* @param $obj
@@ -767,7 +795,7 @@ class MapModel extends AbstractMapTrackingModel {
* @return bool
* @throws PathfinderException
*/
public function hasAccess(CharacterModel $characterModel){
public function hasAccess(CharacterModel $characterModel) : bool {
$hasAccess = false;
if( !$this->dry() ){
@@ -809,20 +837,58 @@ class MapModel extends AbstractMapTrackingModel {
return $characters;
}
/**
* get corporations that have access to this map
* @return CorporationModel[]
*/
public function getCorporations() : array {
$corporations = [];
if($this->isCorporation()){
$this->filter('mapCorporations', ['active = ?', 1]);
if($this->mapCorporations){
foreach($this->mapCorporations as $mapCorporation){
$corporations[] = $mapCorporation->corporationId;
}
}
}
return $corporations;
}
/**
* get alliances that have access to this map
* @return AllianceModel[]
*/
public function getAlliances() : array {
$alliances = [];
if($this->isAlliance()){
$this->filter('mapAlliances', ['active = ?', 1]);
if($this->mapAlliances){
foreach($this->mapAlliances as $mapAlliance){
$alliances[] = $mapAlliance->allianceId;
}
}
}
return $alliances;
}
/**
* get all character models that are currently online "viewing" this map
* @param array $options filter options
* @return CharacterModel[]
*/
private function getAllCharacters($options = []){
private function getAllCharacters($options = []) : array {
$characters = [];
if($this->isPrivate()){
$activeCharacters = $this->getCharacters();
// add active character for each user
foreach($activeCharacters as $activeCharacter){
$characters[] = $activeCharacter;
foreach($this->getCharacters() as $character){
$characters[] = $character;
}
}elseif($this->isCorporation()){
$corporations = $this->getCorporations();
@@ -870,18 +936,35 @@ class MapModel extends AbstractMapTrackingModel {
}
/**
* get all corporations that have access to this map
* get all corporations that have access
* -> for private maps -> get corporations from characters
* -> for corporation maps -> get corporations
* -> for alliance maps -> get corporations from alliances
* @return CorporationModel[]
*/
public function getCorporations(){
public function getAllCorporations() : array {
$corporations = [];
if($this->isCorporation()){
$this->filter('mapCorporations', ['active = ?', 1]);
if($this->mapCorporations){
foreach($this->mapCorporations as $mapCorporation){
$corporations[] = $mapCorporation->corporationId;
if($this->isPrivate()){
foreach($this->getCharacters() as $character){
if(
$character->hasCorporation() &&
!array_key_exists($character->get('corporationId', true), $corporations)
){
$corporations[$character->getCorporation()->_id] = $character->getCorporation();
}
}
}elseif($this->isCorporation()){
$corporations = $this->getCorporations();
}elseif($this->isAlliance()){
foreach($this->getAlliances() as $alliance){
foreach($alliance->getCharacters() as $character){
if(
$character->hasCorporation() &&
!array_key_exists($character->get('corporationId', true), $corporations)
){
$corporations[$character->getCorporation()->_id] = $character->getCorporation();
}
}
}
}
@@ -889,26 +972,6 @@ class MapModel extends AbstractMapTrackingModel {
return $corporations;
}
/**
* get all alliances that have access to this map
* @return AllianceModel[]
*/
public function getAlliances(){
$alliances = [];
if($this->isAlliance()){
$this->filter('mapAlliances', ['active = ?', 1]);
if($this->mapAlliances){
foreach($this->mapAlliances as $mapAlliance){
$alliances[] = $mapAlliance->allianceId;
}
}
}
return $alliances;
}
/**
* @param string $action
* @return Logging\LogInterface

View File

@@ -0,0 +1,218 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 15.04.2018
* Time: 19:41
*/
namespace Model;
use DB\SQL\Schema;
class StructureModel extends BasicModel {
protected $table = 'structure';
/**
* categoryId (from CCP´s SDE) that holds all "groups" with structure "types"
*/
const CATEGORY_STRUCTURE_ID = 65;
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'structureId' => [
'type' => Schema::DT_INT,
'index' => true
],
'corporationId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CorporationModel',
'constraint' => [
[
'table' => 'corporation',
'on-delete' => 'SET NULL'
]
]
],
'systemId' => [
'type' => Schema::DT_INT,
'index' => true,
'validate' => true
],
'statusId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\StructureStatusModel',
'constraint' => [
[
'table' => 'structure_status',
'on-delete' => 'SET NULL'
]
]
],
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'description' => [
'type' => Schema::DT_VARCHAR512,
'nullable' => false,
'default' => ''
],
'structureCorporations' => [
'has-many' => ['Model\CorporationStructureModel', 'structureId']
]
];
/**
* get structure data
* @return \stdClass
* @throws \Exception
*/
public function getData() : \stdClass {
$structureData = (object) [];
$structureData->id = $this->_id;
$structureData->systemId = $this->systemId;
$structureData->status = $this->statusId->getData();
$structureData->name = $this->name;
$structureData->description = $this->description;
if($this->structureId){
$structureData->structure = $this->getUniverseTypeData($this->structureId);
}
if($this->corporationId){
$structureData->owner = (object) [];
$structureData->owner->id = $this->corporationId->_id;
$structureData->owner->name = $this->corporationId->name;
}
$structureData->updated = (object) [];
$structureData->updated->updated = strtotime($this->updated);
return $structureData;
}
/**
* set structureId (universeType) for this structure
* @param $structureId
* @return int|null
*/
public function set_structureId($structureId){
$structureId = (int)$structureId;
$structureId = $structureId ? : null;
return $structureId;
}
/**
* set corporationId (owner) for this structure
* -> if corporation does not exists in DB -> load from API
* @param $corporationId
* @return int|null
*/
public function set_corporationId($corporationId){
$oldCorporationId = $this->get('corporationId', true) ? : 0;
$corporationId = !is_string($corporationId) ? : (int)$corporationId;
if($corporationId){
if($corporationId !== $oldCorporationId){
// make sure there is already corporation data stored for new corporationId
/**
* @var CorporationModel $corporation
*/
$corporation = $this->rel('corporationId');
$corporation->getById($corporationId);
if($corporation->dry()){
$corporationId = null;
}
}
}else{
$corporationId = null;
}
return $corporationId;
}
/**
* validates systemId
* @param string $key
* @param string $val
* @return bool
*/
protected function validate_systemId(string $key, string $val): bool {
$valid = true;
if( !$this->dry() && $this->systemId !== (int)$val ){
// structure always belongs to the same system
$valid = false;
}
return $valid;
}
/**
* check access by chraacter
* @param CharacterModel $characterModel
* @return bool
*/
public function hasAccess(CharacterModel $characterModel) : bool {
$access = false;
if($this->dry()){
$access = true;
}elseif($characterModel->hasCorporation()){
$this->filter('structureCorporations', ['active = ?', 1]);
$this->has('structureCorporations.corporationId', ['active = ?', 1]);
$this->has('structureCorporations.corporationId', ['id = ?', $characterModel->get('corporationId', true)]);
if($this->structureCorporations){
$access = true;
}
}
return $access;
}
/**
* get structure data grouped by corporations
* @return array
* @throws \Exception
*/
public function getDataByCorporations() : array {
$structuresData = [];
foreach((array)$this->structureCorporations as $structureCorporation){
if($structureCorporation->isActive() && $structureCorporation->corporationId->isActive()){
$structuresData[$structureCorporation->corporationId->_id] = [
'id' => $structureCorporation->corporationId->_id,
'name' => $structureCorporation->corporationId->name,
'structures' => [$this->getData()]
];
}
}
return $structuresData;
}
/**
* get universe type data for structureId
* @param int $structureId
* @return \stdClass
* @throws \Exception
*/
protected function getUniverseTypeData(int $structureId) : \stdClass {
/**
* @var $type Universe\TypeModel
*/
$type = Universe\BasicUniverseModel::getNew('TypeModel')->getById($structureId);
return $type->dry() ? (object)[] : $type->getData();
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 15.04.2018
* Time: 20:13
*/
namespace Model;
use DB\SQL\Schema;
class StructureStatusModel extends BasicModel {
protected $table = 'structure_status';
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' => ''
],
'class' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'structures' => [
'has-many' => ['Model\StructureModel', 'statusId']
]
];
protected static $tableData = [
[
'id' => 1,
'name' => 'unknown',
'label' => '',
'class' => 'pf-structure-status-unknown'
],
[
'id' => 2,
'name' => 'online',
'label' => 'online',
'class' => 'pf-structure-status-online'
],
[
'id' => 3,
'name' => 'offline',
'label' => 'offline',
'class' => 'pf-structure-status-offline'
]
];
/**
* get structure status data
* @return \stdClass
*/
public function getData() : \stdClass {
$statusData = (object) [];
$statusData->id = $this->_id;
$statusData->name = $this->name;
$statusData->label = $this->label;
$statusData->class = $this->class;
return $statusData;
}
/**
* get all status options
* @return \DB\CortexCollection
*/
public static function getAll(){
$query = [
'active = :active',
':active' => 1
];
return (new self())->find($query);
}
}

View File

@@ -203,7 +203,7 @@ class SystemModel extends AbstractMapTrackingModel {
// no cached system data found
$systemData = (object) [];
$systemData->id = $this->id;
$systemData->id = $this->_id;
$systemData->mapId = is_object($this->mapId) ? $this->get('mapId', true) : 0;
$systemData->systemId = $this->systemId;
$systemData->name = $this->name;
@@ -485,7 +485,6 @@ class SystemModel extends AbstractMapTrackingModel {
public function getSignatures(){
$signatures = [];
$this->filter('signatures', ['active = ?', 1], ['order' => 'name']);
if($this->signatures){
$signatures = $this->signatures;
}
@@ -494,13 +493,12 @@ class SystemModel extends AbstractMapTrackingModel {
}
/**
* get all data for all Signatures in this system
* get data for all Signatures in this system
* @return \stdClass[]
*/
public function getSignaturesData(){
$signaturesData = [];
$signatures = $this->getSignatures();
foreach($signatures as $signature){
$signaturesData[] = $signature->getData();
}
@@ -516,7 +514,6 @@ class SystemModel extends AbstractMapTrackingModel {
*/
public function getSignatureById(CharacterModel $characterModel, $id){
$signature = null;
if($this->hasAccess($characterModel)){
$this->filter('signatures', ['active = ? AND id = ?', 1, $id]);
if($this->signatures){
@@ -535,7 +532,6 @@ class SystemModel extends AbstractMapTrackingModel {
*/
public function getSignatureByName(CharacterModel $characterModel, $name){
$signature = null;
if($this->hasAccess($characterModel)){
$this->filter('signatures', ['active = ? AND name = ?', 1, $name]);
if($this->signatures){
@@ -546,6 +542,14 @@ class SystemModel extends AbstractMapTrackingModel {
return $signature;
}
/**
* get data for all structures in this system
* @return \stdClass[]
*/
public function getStructuresData() : array {
return $this->getMap()->getStructuresData([$this->systemId]);
}
/**
* check whether this system is a wormhole
* @return bool

View File

@@ -0,0 +1,121 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 13.04.2018
* Time: 23:58
*/
namespace Model\Universe;
use DB\SQL\Schema;
class GroupModel extends BasicUniverseModel {
protected $table = 'group';
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'published' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'categoryId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\Universe\CategoryModel',
'constraint' => [
[
'table' => 'category',
'on-delete' => 'CASCADE'
]
]
],
'types' => [
'has-many' => ['Model\Universe\TypeModel', 'groupId']
]
];
/**
* get group data
* @return object
*/
public function getData(){
$groupData = (object) [];
$groupData->id = $this->id;
$groupData->name = $this->name;
if($typesData = $this->getTypesData()){
$groupData->types = $typesData;
}
return $groupData;
}
/**
* get all types for this group
* @return array|mixed
*/
protected function getTypes(){
$types = [];
$this->filter('types', [
'published = :published',
':published' => 1
]);
if($this->types){
$types = $this->types;
}
return $types;
}
/**
* @return array
*/
protected function getTypesData() : array {
$typesData = [];
$types = $this->getTypes();
foreach($types as $type){
$typesData[] = $type->getData();
}
return $typesData;
}
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseGroupData($id);
if(!empty($data)){
$category = $this->rel('categoryId');
$category->loadById($data['categoryId'], $accessToken, $additionalOptions);
$data['categoryId'] = $category;
$this->copyfrom($data, ['id', 'name', 'published', 'categoryId']);
$this->save();
}
}
/**
* load types data for this group
*/
public function loadTypesData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseGroupData($this->_id);
if(!empty($data)){
foreach((array)$data['types'] as $typeId){
$type = $this->rel('types');
$type->loadById($typeId);
$type->reset();
}
}
}
}
}

View File

@@ -22,6 +22,53 @@ abstract class BasicUniverseModel extends BasicModel {
protected $db = 'DB_UNIVERSE';
/**
* Event "Hook" function
* return false will stop any further action
* @param self $self
* @param $pkeys
* @return bool
*/
public function beforeUpdateEvent($self, $pkeys){
// if model changed, 'update' col needs to be updated as well
// -> data no longer "outdated"
$this->touch('updated');
return parent::beforeUpdateEvent($self, $pkeys);
}
/**
* load object by $id
* -> if $id not exists in DB -> query API
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
public function loadById(int $id, string $accessToken = '', array $additionalOptions = []){
/**
* @var $model self
*/
$model = $this->getById($id, 0);
if($model->isOutdated()){
$model->loadData($id, $accessToken, $additionalOptions);
}
}
/**
* load data from API into $this and save $this
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
abstract protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []);
/**
* factory for all UniverseModels
* @param string $model
* @param int $ttl
* @return BasicModel|null
* @throws \Exception
*/
public static function getNew($model, $ttl = self::DEFAULT_TTL){
$class = null;
@@ -35,51 +82,4 @@ abstract class BasicUniverseModel extends BasicModel {
return $class;
}
/**
* load data from API into $this and save $this
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
abstract protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []);
/**
* load object by $id
* -> if $id not exists in DB -> query API
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
public function loadById(int $id, string $accessToken = '', array $additionalOptions = []){
/**
* @var $model self
*/
$model = $this->getById($id);
if($model->isOutdated()){
$model->loadData($id, $accessToken, $additionalOptions);
}
}
/**
* checks whether data is outdated and should be refreshed
* @return bool
*/
protected function isOutdated(): bool {
$outdated = true;
if(!$this->dry()){
$timezone = $this->getF3()->get('getTimeZone')();
$currentTime = new \DateTime('now', $timezone);
$updateTime = \DateTime::createFromFormat(
'Y-m-d H:i:s',
$this->updated,
$timezone
);
$interval = $updateTime->diff($currentTime);
if($interval->days < self::CACHE_MAX_DAYS ){
$outdated = false;
}
}
return $outdated;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 13.04.2018
* Time: 23:49
*/
namespace Model\Universe;
use DB\SQL\Schema;
class CategoryModel extends BasicUniverseModel {
protected $table = 'category';
protected $fieldConf = [
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
],
'published' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'groups' => [
'has-many' => ['Model\Universe\GroupModel', 'categoryId']
]
];
/**
* get category data
* @return object
*/
public function getData(){
$categoryData = (object) [];
$categoryData->id = $this->id;
$categoryData->name = $this->name;
if($groupsData = $this->getGroupsData()){
$categoryData->groups = $groupsData;
}
return $categoryData;
}
/**
* get all groups for this category
* @return array|mixed
*/
protected function getGroups(){
$groups = [];
$this->filter('groups', [
'published = :published',
':published' => 1
]);
if($this->groups){
$groups = $this->groups;
}
return $groups;
}
/**
* @return array
*/
protected function getGroupsData() : array {
$groupsData = [];
$groups = $this->getGroups();
foreach($groups as $group){
$groupsData[] = $group->getData();
}
return $groupsData;
}
/**
* load data from API into $this and save $this
* @param int $id
* @param string $accessToken
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseCategoryData($id);
if(!empty($data)){
$this->copyfrom($data, ['id', 'name', 'published']);
$this->save();
}
}
/**
* load groups data for this category
*/
public function loadGroupsData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseCategoryData($this->_id);
if(!empty($data)){
foreach((array)$data['groups'] as $groupId){
$group = $this->rel('groups');
$group->loadById($groupId);
$group->reset();
}
}
}
}
}

View File

@@ -96,7 +96,7 @@ class StructureModel extends BasicUniverseModel {
*/
public function copyfrom($key, $fields = null){
// flatten array (e.g. "position" key)
$key = Util::arrayFlatten((array)$key);
$key = Util::arrayFlattenByKey((array)$key);
parent::copyfrom($key, $fields);
}

View File

@@ -52,9 +52,14 @@ class TypeModel extends BasicUniverseModel {
],
'groupId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0,
'index' => true
'index' => true,
'belongs-to-one' => 'Model\Universe\GroupModel',
'constraint' => [
[
'table' => 'group',
'on-delete' => 'CASCADE'
]
]
],
'marketGroupId' => [
'type' => Schema::DT_INT,
@@ -83,6 +88,18 @@ class TypeModel extends BasicUniverseModel {
]
];
/**
* get type data
* @return object
*/
public function getData(){
$typeData = (object) [];
$typeData->id = $this->id;
$typeData->name = $this->name;
return $typeData;
}
/**
* get shipData from object
* -> more fields can be added in here if needed
@@ -107,6 +124,10 @@ class TypeModel extends BasicUniverseModel {
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseTypesData($id, $additionalOptions);
if(!empty($data)){
$group = $this->rel('groupId');
$group->loadById($data['groupId'], $accessToken, $additionalOptions);
$data['groupId'] = $group;
$this->copyfrom($data);
$this->save();
}

View File

@@ -153,7 +153,7 @@ requirejs.config({
deps : ['jquery']
},
select2: {
deps : ['jquery'],
deps : ['jquery', 'mousewheel'],
exports: 'Select2'
},
validator: {

View File

@@ -67,9 +67,10 @@ define([
*/
$.fn.destroyTimestampCounter = function(){
return this.each(function(){
let element = $(this);
element.find('[data-counter="init"]').each(function(){
let interval = $(this).data('interval');
let parentElement = $(this);
parentElement.find('[data-counter="init"]').each(function(){
let element = $(this);
let interval = element.data('interval');
if(interval){
clearInterval(interval);
element.removeAttr('data-counter')

View File

@@ -33,6 +33,7 @@ define(['jquery'], ($) => {
getMapConnectionData: '/api/map/getConnectionData', // ajax URL - get connection data
getMapLogData: '/api/map/getLogData', // ajax URL - get logs data
// system API
getSystemData: '/api/system/getData', // ajax URL - get system data
searchSystem: '/api/system/search', // ajax URL - search system by name
saveSystem: '/api/system/save', // ajax URL - saves system to map
deleteSystem: '/api/system/delete', // ajax URL - delete system from map
@@ -47,10 +48,15 @@ define(['jquery'], ($) => {
getSignatures: '/api/signature/getAll', // ajax URL - get all signature data for system
saveSignatureData: '/api/signature/save', // ajax URL - save signature data for system
deleteSignatureData: '/api/signature/delete', // ajax URL - delete signature data for system
// structure API
saveStructureData: '/api/structure/save', // ajax URL - save structure data
deleteStructureData: '/api/structure/delete', // ajax URL - delete structure data
// route API
searchRoute: '/api/route/search', // ajax URL - search system routes
// stats API
getStatisticsData: '/api/statistic/getData', // ajax URL - get statistics data (activity log)
// universe API
searchUniverseData: '/api/universe/search', // ajax URL - search universe data
// GitHub API
gitHubReleases: '/api/github/releases' // ajax URL - get release info from GitHub
},

View File

@@ -105,6 +105,10 @@ define([
let ssoButtonElement = $('.' + config.ssoButtonClass);
let cookieHintElement = $('#' + config.cookieHintId);
$(document).on('click', '.' + config.ssoButtonClass + ', .' + config.characterSelectionClass + ' a', function(){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
// cookie hint --------------------------------------------------------
cookieHintElement.find('.btn-success').on('click', function(){
setAcceptCookie();
@@ -683,11 +687,6 @@ define([
let content = Mustache.render(template, data);
this.characterElement.html(content);
// lock character selection on click (prevent click spamming)
this.characterElement.find('a').on('click', function(){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
// show character panel (animation settings)
initCharacterAnimation(this.characterElement.find('.' + config.characterImageWrapperClass));
}else{

View File

@@ -14,7 +14,6 @@ define([
'app/map/magnetizing',
'app/map/scrollbar',
'dragToSelect',
'select2',
'app/map/contextmenu',
'app/map/overlay',
'app/map/local'

View File

@@ -32,6 +32,9 @@ define([
// set default dialog config
Util.initDefaultBootboxConfig();
// set default select2 config
Util.initDefaultSelect2Config();
// load page
// load info (maintenance) info panel (if scheduled)
$('body').loadPageStructure().setGlobalShortcuts();
@@ -128,6 +131,8 @@ define([
Init.url = response.url;
Init.slack = response.slack;
Init.discord = response.discord;
Init.structureStatus = response.structureStatus;
Init.universeCategories = response.universeCategories;
Init.routeSearch = response.routeSearch;
Init.programMode = response.programMode;

View File

@@ -9,6 +9,7 @@ define([
'app/ui/system_graph',
'app/ui/system_signature',
'app/ui/system_route',
'app/ui/system_intel',
'app/ui/system_killboard',
'app/ui/connection_info',
'app/counter'
@@ -23,6 +24,7 @@ define([
SystemGraphModule,
SystemSignatureModule,
SystemRouteModule,
SystemIntelModule,
SystemKillboardModule,
ConnectionInfoModule
) => {
@@ -276,6 +278,9 @@ define([
// draw system routes module
drawModule(secondCell, SystemRouteModule, currentSystemData.mapId, currentSystemData.systemData);
// draw system intel module
drawModule(secondCell, SystemIntelModule, currentSystemData.mapId, currentSystemData.systemData);
// draw system killboard module
drawModule(secondCell, SystemKillboardModule, currentSystemData.mapId, currentSystemData.systemData);
});

View File

@@ -346,7 +346,6 @@ define([
let formValid = form.isValidForm();
if(formValid === true){
// lock dialog
let dialogContent = mapInfoDialog.find('.modal-content');
dialogContent.showLoadingAnimation();
@@ -391,7 +390,6 @@ define([
data: requestData,
dataType: 'json'
}).done(function(responseData){
if(responseData.error.length){
form.showFormMessage(responseData.error);
}else{

View File

@@ -7,10 +7,67 @@ define([
'app/init',
'app/util',
'app/map/util'
], function($, Init, Util, MapUtil) {
], ($, Init, Util, MapUtil) => {
'use strict';
/**
* format result data
* @param data
* @returns {*}
*/
let formatCategoryTypeResultData = (data) => {
if(data.loading) return data.text;
if(data.placeholder) return data.placeholder;
let markup = '<div class="clearfix">';
if(data.hasOwnProperty('children')){
// category group label
markup += '<div class="col-xs-9">' + data.text + '</div>';
markup += '<div class="col-xs-3 text-right">(' + data.children.length + ')</div>';
}else{
let imagePath = '';
let iconName = '';
let thumb = '';
switch(data.categoryType){
case 'character':
imagePath = Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg';
break;
case 'corporation':
imagePath = Init.url.ccpImageServer + '/Corporation/' + data.id + '_32.png';
break;
case 'alliance':
imagePath = Init.url.ccpImageServer + '/Alliance/' + data.id + '_32.png';
break;
case 'inventory_type':
imagePath = Init.url.ccpImageServer + '/Type/' + data.id + '_32.png';
break;
case 'render':
imagePath = Init.url.ccpImageServer + '/Render/' + data.id + '_32.png';
break;
case 'station':
iconName = 'fa-home';
break;
case 'solar_system':
iconName = 'fa-sun';
break;
}
if(imagePath){
thumb = '<img src="' + imagePath + '" style="max-width: 100%" />';
}else if(iconName){
thumb = '<i class="fas fa-fw ' + iconName + '" ></i>';
}
markup += '<div class="col-xs-2 text-center">' + thumb + '</div>';
markup += '<div class="col-xs-10">' + data.text + '</div>';
}
markup += '</div>';
return markup;
};
/**
* init a select element as "select2" for map selection
*/
@@ -20,7 +77,6 @@ define([
$.when(
selectElement.select2({
dropdownParent: selectElement.parents('.modal-body'),
theme: 'pathfinder',
maximumSelectionLength: 5
})
);
@@ -133,8 +189,7 @@ define([
}
},
dropdownParent: selectElement.parents('.modal-body'),
theme: 'pathfinder',
minimumInputLength: 2,
minimumInputLength: 3,
templateResult: formatResultData,
placeholder: 'System name',
allowClear: true,
@@ -171,52 +226,8 @@ define([
$.fn.initAccessSelect = function(options){
return this.each(function(){
let selectElement = $(this);
// format result data
function formatResultData (data) {
if (data.loading){
return data.text;
}
// check if an option is already selected
// do not show the same result twice
let currentValues = selectElement.val();
if(
currentValues &&
currentValues.indexOf( data.id.toString() ) !== -1
){
return ;
}
let imagePath = '';
let previewContent = '';
switch(options.type){
case 'character':
imagePath = Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
case 'corporation':
imagePath = Init.url.ccpImageServer + '/Corporation/' + data.id + '_32.png';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
case 'alliance':
imagePath = Init.url.ccpImageServer + '/Alliance/' + data.id + '_32.png';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
}
let markup = '<div class="clearfix">';
markup += '<div class="col-sm-2">' + previewContent + '</div>';
markup += '<div class="col-sm-10">' + data.text + '</div></div>';
return markup;
}
// format selection data
function formatSelectionData (data){
@@ -251,7 +262,8 @@ define([
results: data.map( function(item){
return {
id: item.id,
text: item.name
text: item.name,
categoryType: options.type
};
})
};
@@ -266,12 +278,11 @@ define([
}
},
dropdownParent: selectElement.parents('.modal-body'),
theme: 'pathfinder',
minimumInputLength: 3,
placeholder: options.type + ' names',
allowClear: false,
maximumSelectionLength: options.maxSelectionLength,
templateResult: formatResultData,
templateResult: formatCategoryTypeResultData,
templateSelection: formatSelectionData,
escapeMarkup: function(markup){
// let our custom formatter work
@@ -288,4 +299,190 @@ define([
};
/**
* init a select element as an ajax based "select2" object for universeTypes
* e.g. 'alliance', 'corporation', 'character', ...
* @param options
* @returns {*}
*/
$.fn.initUniverseSearch = function(options) {
let showErrorNotification = (reason) => {
Util.showNotify({title: 'Search failed', text: reason + ' deleted', type: 'warning'});
};
/**
* format selection data
* @param data
* @returns {*}
*/
function formatSelectionData (data){
if(data.loading) return data.text;
if(data.placeholder) return data.placeholder;
let markup = '<div class="clearfix">';
markup += '<div class="col-sm-10">' + data.text + '</div></div>';
return markup;
}
return this.each(function() {
let selectElement = $(this);
$.when(
selectElement.select2({
ajax: {
type: 'POST',
url: function(params){
// add params to URL
return Init.path.searchUniverseData + '/' + params.term;
},
dataType: 'json',
delay: 250,
timeout: 5000,
cache: true,
data: function(params){
return {
categories: options.categoryNames
};
},
processResults: function(result, page) {
let data = {results: []};
if(result.hasOwnProperty('error')){
showErrorNotification(result.error);
}else{
let mapChildren = function(item){
return {
id: item.id,
text: item.name,
categoryType: this
};
};
for(let category in result){
// skip custom functions in case result = [] (array functions)
if(result.hasOwnProperty(category)){
data.results.push({
text: category,
children: result[category].map(mapChildren, category)
});
}
}
}
return data;
},
error: function (jqXHR, status, error) {
if( !Util.isXHRAborted(jqXHR) ){
let reason = status + ' ' + jqXHR.status + ': ' + error;
showErrorNotification(reason);
}
}
},
dropdownParent: selectElement.parents('.modal-body') ,
minimumInputLength: 3,
placeholder: '',
allowClear: options.maxSelectionLength <= 1,
maximumSelectionLength: options.maxSelectionLength,
templateResult: formatCategoryTypeResultData,
// templateSelection: formatSelectionData, // some issues with "clear" selection on single selects (empty option is needed)
escapeMarkup: function(markup){
// let our custom formatter work
return markup;
}
}).on('change', function(e){
// select changed
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" whe it could be cleared manually
if(
options.maxSelectionLength === 1 &&
$(this).val() !== null
){
$(this).val('').trigger('change');
}
})
).done(function(){
// after init finish
});
});
};
$.fn.initUniverseTypeSelect = function(options) {
/**
* get select option data by categoryIds
* @param categoryIds
* @returns {{results: Array}}
*/
let getOptionsData = categoryIds => {
let data = [];
let mapChildren = function(type){
return {
id: type.id,
text: type.name,
groupId: this.groupId,
categoryId: this.categoryId,
categoryType: this.categoryType
};
};
for(let categoryId of categoryIds){
let categoryData = Util.getObjVal(Init, 'universeCategories.' + categoryId);
if(categoryData && categoryData.groups){
// categoryId data exists and has groups...
for(let groupData of categoryData.groups){
if(groupData && groupData.types){
// groupData exists and has types...
data.push({
text: groupData.name,
children: groupData.types.map(mapChildren, {
groupId: groupData.id,
categoryId: categoryData.id,
categoryType: 'inventory_type',
})
});
}
}
}
}
return data;
};
return this.each(function() {
let selectElement = $(this);
$.when(
selectElement.select2({
data: getOptionsData(options.categoryIds),
dropdownParent: selectElement.parents('.modal-body'),
minimumInputLength: 0, // minimum number of characters required to start a search
maximumInputLength: 100, // maximum number of characters that may be provided for a search term
placeholder: '',
allowClear: options.maxSelectionLength <= 1,
multiple: options.maxSelectionLength > 1,
maximumSelectionLength: options.maxSelectionLength,
// maximumSelectionLength: options.maxSelectionLength > 1 ? options.maxSelectionLength > 1 : 0,
// minimumResultsForSearch: 5, // minimum number of results required to display the search box
templateResult: formatCategoryTypeResultData,
escapeMarkup: function(markup){
return markup;
}
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" whe it could be cleared manually
if(
options.maxSelectionLength === 1 &&
$(this).val() !== null
){
$(this).val('').trigger('change');
}
}).val(options.selected).trigger('change')
);
});
};
});

View File

@@ -169,33 +169,6 @@ define([
moduleElement.find('.' + config.descriptionArea).hideLoadingAnimation();
};
/**
* update a character counter field with current value length - maxCharLength
* @param field
* @param charCounterElement
* @param maxCharLength
*/
let updateCounter = function(field, charCounterElement, maxCharLength){
let value = field.val();
let inputLength = value.length;
// line breaks are 2 characters!
let newLines = value.match(/(\r\n|\n|\r)/g);
let addition = 0;
if (newLines != null) {
addition = newLines.length;
}
inputLength += addition;
charCounterElement.text(maxCharLength - inputLength);
if(maxCharLength <= inputLength){
charCounterElement.toggleClass('txt-color-red', true);
}else{
charCounterElement.toggleClass('txt-color-red', false);
}
};
/**
* get module element
* @param parentElement
@@ -319,10 +292,10 @@ define([
textarea.parent().next().append(charCounter);
// update character counter
updateCounter(textarea, charCounter, maxDescriptionLength);
Util.updateCounter(textarea, charCounter, maxDescriptionLength);
textarea.on('keyup', function(){
updateCounter($(this), charCounter, maxDescriptionLength);
Util.updateCounter($(this), charCounter, maxDescriptionLength);
});
});

626
js/app/ui/system_intel.js Normal file
View File

@@ -0,0 +1,626 @@
/**
* system route module
*/
define([
'jquery',
'app/init',
'app/util',
'bootbox'
], ($, Init, Util, bootbox) => {
'use strict';
let config = {
// module info
modulePosition: 1,
moduleName: 'systemIntel',
moduleHeadClass: 'pf-module-head', // class for module header
moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler
moduleTypeClass: 'pf-system-intel-module', // class for this module
// headline toolbar
moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head
moduleHeadlineIconAddClass: 'pf-module-icon-button-add', // class for "add structure" icon
moduleHeadlineIconRefreshClass: 'pf-module-icon-button-refresh', // class for "refresh" icon
// system intel module
systemStructuresTableClass: 'pf-system-structure-table', // class for route tables
// structure dialog
structureDialogId: 'pf-structure-dialog', // id for "structure" dialog
typeSelectId: 'pf-structure-dialog-type-select', // id for "type" select
corporationSelectId: 'pf-structure-dialog-corporation-select', // id for "corporation" select
descriptionTextareaId: 'pf-structure-dialog-description-textarea', // id for "description" textarea
descriptionTextareaCharCounter: 'pf-form-field-char-count', // class for "character counter" element for form field
// dataTable
tableRowIdPrefix: 'pf-structure-row_', // id prefix for table rows
tableCellImageClass: 'pf-table-image-smaller-cell', // class for table "image" cells
tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells
tableCellEllipsisClass: 'pf-table-cell-ellipses-auto', // class for table "ellipsis" cells
dataTableActionCellClass: 'pf-table-action-cell' // class for "action" cells
};
let maxDescriptionLength = 512;
/**
* get status icon for structure
* @param statusData
* @returns {string}
*/
let getStatusData = (statusData) => {
return '<i class="fas fa-fw fa-circle ' + statusData.class + '" title="' + statusData.label + '"></i>';
};
/**
* get <tr> DOM id by id
* @param tableApi
* @param id
* @returns {*}
*/
let getRowId = (tableApi, id) => {
return tableApi.rows().ids().toArray().find(rowId => rowId === config.tableRowIdPrefix + id);
};
/**
* callback -> add structure rows from responseData
* @param context
* @param responseData
*/
let callbackAddStructureRows = (context, responseData) => {
let systemData = Util.getObjVal(responseData, 'system');
callbackUpdateStructureRows(context, systemData);
};
/**
* callback -> add structure rows from systemData
* @param context
* @param systemData
*/
let callbackUpdateStructureRows = (context, systemData) => {
let touchedRows = [];
if(systemData){
let corporations = Util.getObjVal(systemData, 'structures');
if(corporations) {
for (let [corporationId, corporationData] of Object.entries(corporations)){
if(corporationData.structures && corporationData.structures.length){
for(let structureData of corporationData.structures){
let rowId = getRowId(context.tableApi, structureData.id);
// add corporation data
structureData.corporation = {
id: corporationData.id,
name: corporationData.name
};
if(rowId){
// update row
let api = context.tableApi.row('#' + rowId).data(structureData);
api.nodes().to$().data('animationStatus', 'changed').destroyTimestampCounter();
touchedRows.push(api.id());
}else{
// insert new row
//context.tableApi.row.add(structureData).nodes().to$().data('animationStatus', 'added');
let api = context.tableApi.row.add(structureData);
api.nodes().to$().data('animationStatus', 'added');
touchedRows.push(api.id());
}
}
}
}
}
}
if(context.removeMissing){
context.tableApi.rows((idx, data, node) => !touchedRows.includes(node.id)).remove();
}
context.tableApi.draw();
};
/**
* callback -> delete structure rows
* @param context
* @param responseData
*/
let callbackDeleteStructures = (context, responseData) => {
let structureIds = Util.getObjVal(responseData, 'deletedStructureIds');
if(structureIds && structureIds.length){
for(let structureId of structureIds){
let rowId = getRowId(context.tableApi, structureId);
if(rowId){
context.tableApi.row('#' + rowId).remove();
}
}
context.tableApi.draw();
}
};
/**
* send ajax request
* @param url
* @param requestData
* @param context
* @param callback
*/
let sendRequest = (url, requestData, context, callback) => {
context.moduleElement.showLoadingAnimation();
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data: requestData,
context: context
}).done(function(data){
callback(this, data);
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': System intel data', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
// hide loading animation
this.moduleElement.hideLoadingAnimation();
});
};
/**
* requests system data
* @param requestData
* @param context
* @param callback
*/
let getStructureData = (requestData, context, callback) => {
sendRequest(Init.path.getSystemData, requestData, context, callback);
};
/**
* save structure data
* @param requestData
* @param context
* @param callback
*/
let saveStructureData = (requestData, context, callback) => {
sendRequest(Init.path.saveStructureData, requestData, context, callback);
};
/**
* delete structure
* @param requestData
* @param context
* @param callback
*/
let deleteStructure = (requestData, context, callback) => {
sendRequest(Init.path.deleteStructureData, requestData, context, callback);
};
/**
* show structure dialog
* @param moduleElement
* @param tableApi
* @param systemId
* @param structureData
*/
let showStructureDialog = (moduleElement, tableApi, systemId, structureData) => {
let structureStatusData = Util.getObjVal(Init, 'structureStatus');
let structureTypeData = Util.getObjVal(Init, 'structureStatus');
let data = {
id: config.structureDialogId,
structureData: structureData,
structureStatus: Object.keys(structureStatusData).map((k) => {
let data = structureStatusData[k];
data.selected = data.id === Util.getObjVal(structureData, 'status.id');
return data;
}),
typeSelectId: config.typeSelectId,
corporationSelectId: config.corporationSelectId,
descriptionTextareaId: config.descriptionTextareaId,
descriptionTextareaCharCounter: config.descriptionTextareaCharCounter,
maxDescriptionLength: maxDescriptionLength
};
requirejs(['text!templates/dialog/structure.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
let structureDialog = bootbox.dialog({
title: 'Structure',
message: content,
show: true,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fas fa-fw fa-check"></i>&nbsp;save',
className: 'btn-success',
callback: function (){
let form = this.find('form');
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
if(formValid){
// get form data
let formData = form.getFormValues();
formData.id = Util.getObjVal(structureData, 'id') | 0;
formData.structureId = Util.getObjVal(formData, 'structureId') | 0;
formData.corporationId = Util.getObjVal(formData, 'corporationId') | 0;
formData.systemId = systemId | 0;
saveStructureData(formData,{
moduleElement: moduleElement,
tableApi: tableApi
}, callbackUpdateStructureRows);
}else{
return false;
}
}
}
}
});
structureDialog.on('shown.bs.modal', function(e) {
// init type select live search
let selectElementType = $(this).find('#' + config.typeSelectId);
selectElementType.initUniverseTypeSelect({
categoryIds: [65],
maxSelectionLength: 1,
selected: [Util.getObjVal(structureData, 'structure.id')]
});
// init corporation select live search
let selectElementCorporation = $(this).find('#' + config.corporationSelectId);
selectElementCorporation.initUniverseSearch({
categoryNames: ['corporation'],
maxSelectionLength: 1
});
// init character counter
let textarea = $(this).find('#' + config.descriptionTextareaId);
let charCounter = $(this).find('.' + config.descriptionTextareaCharCounter);
Util.updateCounter(textarea, charCounter, maxDescriptionLength);
textarea.on('keyup', function(){
Util.updateCounter($(this), charCounter, maxDescriptionLength);
});
// set form validator (after select2 init finish)
$(this).find('form').initFormValidation();
});
});
};
/**
* get module element
* @returns {*}
*/
let getModule = (parentElement, mapId, systemData) => {
let corporationId = Util.getCurrentUserInfo('corporationId');
// create new module container
let moduleElement = $('<div>').append(
$('<div>', {
class: config.moduleHeadClass
}).append(
$('<h5>', {
class: config.moduleHandlerClass
}),
$('<h5>', {
class: 'pull-right'
}).append(
$('<i>', {
class: ['fas', 'fa-fw', 'fa-plus', config.moduleHeadlineIconClass, config.moduleHeadlineIconAddClass].join(' '),
title: 'add'
}).attr('data-html', 'true').attr('data-toggle', 'tooltip'),
$('<i>', {
class: ['fas', 'fa-fw', 'fa-sync', config.moduleHeadlineIconClass, config.moduleHeadlineIconRefreshClass].join(' '),
title: 'refresh&nbsp;all'
}).attr('data-html', 'true').attr('data-toggle', 'tooltip')
),
$('<h5>', {
text: 'Intel'
})
)
);
let table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', 'pf-table-fixed', config.systemStructuresTableClass].join(' ')
});
moduleElement.append(table);
let structureTable = table.DataTable({
paging: false,
lengthChange: false,
ordering: true,
order: [[ 10, 'desc' ], [ 0, 'asc' ]],
info: false,
searching: false,
hover: false,
autoWidth: false,
rowId: rowData => config.tableRowIdPrefix + rowData.id,
language: {
emptyTable: 'No structures recorded',
info: '_START_ to _END_ of _MAX_',
infoEmpty: ''
},
rowGroup: {
enable: true,
dataSrc: 'systemId'
},
columnDefs: [
{
targets: 0,
title: '',
width: 2,
class: 'text-center',
data: 'status',
render: {
display: data => getStatusData(data),
sort: data => data.id
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).find('i').tooltip();
}
},{
targets: 1,
title: '',
width: 26,
orderable: false,
className: [config.tableCellImageClass, 'text-center'].join(' '),
data: 'structure.id',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display' && value){
value = '<img src="' + Init.url.ccpImageServer + '/Type/' + value + '_32.png" />';
}
return value;
}
}
},{
targets: 2,
title: 'structure',
width: 50,
className: [config.tableCellEllipsisClass].join(' '),
data: 'structure.name',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
},{
targets: 3,
title: 'name',
width: 60,
className: [config.tableCellEllipsisClass].join(' '),
data: 'name'
},{
targets: 4,
title: '',
width: 26,
orderable: false,
className: [config.tableCellImageClass, 'text-center'].join(' '),
data: 'owner.id',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display' && value){
value = '<img src="' + Init.url.ccpImageServer + '/Corporation/' + value + '_32.png" />';
}
return value;
}
}
},{
targets: 5,
title: 'owner',
width: 50,
className: [config.tableCellEllipsisClass].join(' '),
data: 'owner.name',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
},{
targets: 6,
title: 'note',
className: [config.tableCellEllipsisClass].join(' '),
data: 'description'
},{
targets: 7,
title: 'updated',
width: 80,
className: ['text-right', config.tableCellCounterClass].join(' '),
data: 'updated.updated'
},{
targets: 8,
title: '',
orderable: false,
width: 10,
class: ['text-center', config.dataTableActionCellClass, config.moduleHeadlineIconClass].join(' '),
data: null,
render: {
display: data => {
let icon = '<i class="fas fa-pencil-alt"></i>';
if(data.corporation.id !== corporationId){
icon = '';
}
return icon;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if($(cell).is(':empty')){
$(cell).removeClass(config.dataTableActionCellClass + ' ' + config.moduleHeadlineIconClass);
}else{
$(cell).on('click', function(e) {
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tableApi.row( $(cell).parents('tr')).data();
showStructureDialog(moduleElement, tableApi, systemData.systemId, rowData);
});
}
}
},{
targets: 9,
title: '',
orderable: false,
width: 10,
class: ['text-center', config.dataTableActionCellClass].join(' '),
data: null,
render: {
display: data => {
let icon = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
if(data.corporation.id !== corporationId){
icon = '<i class="fas fa-ban txt-color txt-color-grayLight" title="restricted" data-placement="left"></i>';
}
return icon;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if($(cell).find('.fa-ban').length){
$(cell).removeClass(config.dataTableActionCellClass + ' ' + config.moduleHeadlineIconClass);
$(cell).find('i').tooltip();
}else{
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete structure',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
onConfirm : function(e, target){
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tableApi.row( $(cell).parents('tr')).data();
// let deleteRowElement = $(cell).parents('tr');
// tableApi.rows(deleteRowElement).remove().draw();
deleteStructure({
id: rowData.id
},{
moduleElement: moduleElement,
tableApi: tableApi
}, callbackDeleteStructures);
}
};
// init confirmation dialog
$(cell).confirmation(confirmationSettings);
}
}
},{
targets: 10,
name: 'corporation',
data: 'corporation',
visible: false,
render: {
sort: function(data){
return data.name;
}
}
}
],
drawCallback: function (settings){
let tableApi = this.api();
let columnCount = tableApi.columns(':visible').count();
let rows = tableApi.rows( {page:'current'} ).nodes();
let last= null;
tableApi.column('corporation:name', {page:'current'} ).data().each( function ( group, i ) {
if ( !last || last.id !== group.id ) {
$(rows).eq(i).before(
'<tr class="group">' +
'<td></td>' +
'<td class="' + config.tableCellImageClass + '">' +
'<img src="' + Init.url.ccpImageServer + '/Corporation/' + group.id + '_32.png" />' +
'</td>' +
'<td colspan="' + (columnCount - 2 ) + '">' + group.name + '</td>' +
'</tr>'
);
last = group;
}
});
rows.to$().find('.' + config.tableCellCounterClass + ':not([data-counter])').initTimestampCounter();
let animationRows = rows.to$().filter(function() {
return (
$(this).data('animationStatus') ||
$(this).data('animationTimer')
);
});
for(let i = 0; i < animationRows.length; i++){
let animationRow = $(animationRows[i]);
animationRow.pulseTableRow(animationRow.data('animationStatus'));
animationRow.removeData('animationStatus');
}
},
initComplete: function(settings){
let tableApi = this.api();
// initial structure data request
getStructureData({
mapId: mapId,
systemId: systemData.id
},{
moduleElement: moduleElement,
tableApi: tableApi
}, callbackAddStructureRows);
}
});
// init tooltips for this module
let tooltipElements = moduleElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip({
container: 'body'
});
return moduleElement;
};
/**
* init intel module
* @param moduleElement
* @param mapId
* @param systemData
*/
let initModule = (moduleElement, mapId, systemData) => {
let structureTableElement = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApi = structureTableElement.DataTable();
// init structure dialog --------------------------------------------------------------------------------------
moduleElement.find('.' + config.moduleHeadlineIconAddClass).on('click', function(e){
showStructureDialog(moduleElement, tableApi, systemData.systemId);
});
// init refresh button ----------------------------------------------------------------------------------------
moduleElement.find('.' + config.moduleHeadlineIconRefreshClass).on('click', function(e){
getStructureData({
mapId: mapId,
systemId: systemData.id
},{
moduleElement: moduleElement,
tableApi: tableApi,
removeMissing: true
}, callbackAddStructureRows);
});
};
return {
config: config,
getModule: getModule,
initModule: initModule
};
});

View File

@@ -46,6 +46,11 @@ define([
return label;
};
/**
* show killMails
* @param moduleElement
* @param killboardData
*/
let showKillmails = function(moduleElement, killboardData){
// show number of killMails

View File

@@ -8,7 +8,7 @@ define([
'app/util',
'bootbox',
'app/map/util'
], function($, Init, Util, bootbox, MapUtil) {
], ($, Init, Util, bootbox, MapUtil) => {
'use strict';
let config = {
@@ -103,7 +103,7 @@ define([
* @param context
* @param routesData
*/
let callbackAddRouteRow = (context, routesData) => {
let callbackAddRouteRows = (context, routesData) => {
if(routesData.length > 0){
for(let i = 0; i < routesData.length; i++){
@@ -170,7 +170,6 @@ define([
return rowElement;
};
/**
* requests route data from eveCentral API and execute callback
* @param requestData
@@ -210,7 +209,7 @@ define([
routeData.push( getRouteRequestDataFromRowData( this.data() ));
});
getRouteData({routeData: routeData}, context, callbackAddRouteRow);
getRouteData({routeData: routeData}, context, callbackAddRouteRows);
};
/**
@@ -261,7 +260,7 @@ define([
mapSelectOptions: mapSelectOptions
};
requirejs(['text!templates/dialog/route.html', 'mustache'], function(template, Mustache) {
requirejs(['text!templates/dialog/route.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
@@ -326,7 +325,7 @@ define([
}]
};
getRouteData(requestData, context, callbackAddRouteRow);
getRouteData(requestData, context, callbackAddRouteRows);
}
}
}
@@ -418,7 +417,7 @@ define([
routeData: requestRouteData
};
getRouteData(requestData, contextData, callbackAddRouteRow);
getRouteData(requestData, contextData, callbackAddRouteRows);
}
};
@@ -432,7 +431,7 @@ define([
let showSettingsDialog = (dialogData, moduleElement, systemFromData, routesTable) => {
let promiseStore = MapUtil.getLocaleData('map', dialogData.mapId);
promiseStore.then(function(dataStore) {
promiseStore.then(dataStore => {
// selected systems (if already stored)
let systemSelectOptions = [];
if(
@@ -902,7 +901,6 @@ define([
)
);
// crate new route table
let table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.systemInfoRoutesTableClass].join(' ')
});
@@ -1039,7 +1037,7 @@ define([
routeData: [routeData]
};
getRouteData(requestData, context, callbackAddRouteRow);
getRouteData(requestData, context, callbackAddRouteRows);
});
}
},{
@@ -1074,7 +1072,7 @@ define([
routeData: [routeData]
};
getRouteData(requestData, context, callbackAddRouteRow);
getRouteData(requestData, context, callbackAddRouteRows);
});
}
},{
@@ -1122,10 +1120,10 @@ define([
});
for(let i = 0; i < animationRows.length; i++){
$(animationRows[i]).pulseTableRow($(animationRows[i]).data('animationStatus'));
$(animationRows[i]).removeData('animationStatus');
let animationRow = $(animationRows[i]);
animationRow.pulseTableRow(animationRow.data('animationStatus'));
animationRow.removeData('animationStatus');
}
},
initComplete: function(settings, json){
// click on "fake connection" -------------------------------------------------------------------------
@@ -1231,7 +1229,6 @@ define([
};
let routesTableElement = moduleElement.find('.' + config.systemInfoRoutesTableClass);
let routesTable = routesTableElement.DataTable();
// init refresh routes --------------------------------------------------------------------

View File

@@ -15,7 +15,8 @@ define([
'easyPieChart',
'hoverIntent',
'bootstrapConfirmation',
'bootstrapToggle'
'bootstrapToggle',
'select2'
], ($, Init, SystemEffect, SignatureType, bootbox, localforage) => {
'use strict';
@@ -55,6 +56,8 @@ define([
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapClass: 'pf-map' , // class for all maps
// select2
select2Class: 'pf-select2', // class for all "Select2" <select> elements
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
@@ -962,14 +965,78 @@ define([
};
/**
* set default configuration for "Bootbox" dialogs
* set default configuration for "Bootbox"
*/
let initDefaultBootboxConfig = function(){
let initDefaultBootboxConfig = () => {
bootbox.setDefaults({
onEscape: true // enables close dialogs on ESC key
});
};
/**
* set default configuration for "Select2"
*/
let initDefaultSelect2Config = () => {
$.fn.select2.defaults.set('theme', 'pathfinder');
let initScrollbar = (resultsWrapper) => {
// default 'mousewheel' event set by select2 needs to be disabled
// in order to make mCustomScrollbar mouseWheel enable works correctly
$(resultsWrapper).find('ul.select2-results__options').off('mousewheel');
resultsWrapper.mCustomScrollbar({
mouseWheel: {
enable: true,
scrollAmount: 'auto',
axis: 'y',
preventDefault: true
},
keyboard: {
enable: false,
scrollType: 'stepless',
scrollAmount: 'auto'
},
scrollbarPosition: 'inside',
autoDraggerLength: true,
autoHideScrollbar: false,
advanced: {
updateOnContentResize: true
}
});
};
let getResultsWrapper = (selectElement) => {
let wrapper = null;
let selectElementId = selectElement.getAttribute('data-select2-id');
if(selectElementId){
let resultsOptions = $('#select2-' + selectElementId + '-results');
if(resultsOptions.length) {
let resultsWrapper = resultsOptions.parents('.select2-results');
if(resultsWrapper.length){
wrapper = resultsWrapper;
}
}
}
return wrapper;
};
// global open event
$(document).on('select2:open', '.' + config.select2Class, function(e){
let resultsWrapper = getResultsWrapper(this);
if(resultsWrapper){
initScrollbar(resultsWrapper);
}
});
// global select2:closing event
$(document).on('select2:closing', '.' + config.select2Class, function(e){
let resultsWrapper = getResultsWrapper(this);
if(resultsWrapper){
resultsWrapper.mCustomScrollbar('destroy');
}
});
};
/**
* get the current main trigger delay for the main trigger functions
* optional in/decrease the delay
@@ -1104,6 +1171,33 @@ define([
return duration;
};
/**
* update a character counter field with current value length - maxCharLength
* @param field
* @param charCounterElement
* @param maxCharLength
*/
let updateCounter = function(field, charCounterElement, maxCharLength){
let value = field.val();
let inputLength = value.length;
// line breaks are 2 characters!
let newLines = value.match(/(\r\n|\n|\r)/g);
let addition = 0;
if (newLines != null) {
addition = newLines.length;
}
inputLength += addition;
charCounterElement.text(maxCharLength - inputLength);
if(maxCharLength <= inputLength){
charCounterElement.toggleClass('txt-color-red', true);
}else{
charCounterElement.toggleClass('txt-color-red', false);
}
};
/**
* trigger main logging event with log information
* @param logKey
@@ -2413,12 +2507,14 @@ define([
showVersionInfo: showVersionInfo,
initPrototypes: initPrototypes,
initDefaultBootboxConfig: initDefaultBootboxConfig,
initDefaultSelect2Config: initDefaultSelect2Config,
getCurrentTriggerDelay: getCurrentTriggerDelay,
getServerTime: getServerTime,
convertTimestampToServerTime: convertTimestampToServerTime,
getTimeDiffParts: getTimeDiffParts,
timeStart: timeStart,
timeStop: timeStop,
updateCounter: updateCounter,
log: log,
showNotify: showNotify,
stopTabBlink: stopTabBlink,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -153,7 +153,7 @@ requirejs.config({
deps : ['jquery']
},
select2: {
deps : ['jquery'],
deps : ['jquery', 'mousewheel'],
exports: 'Select2'
},
validator: {

View File

@@ -67,9 +67,10 @@ define([
*/
$.fn.destroyTimestampCounter = function(){
return this.each(function(){
let element = $(this);
element.find('[data-counter="init"]').each(function(){
let interval = $(this).data('interval');
let parentElement = $(this);
parentElement.find('[data-counter="init"]').each(function(){
let element = $(this);
let interval = element.data('interval');
if(interval){
clearInterval(interval);
element.removeAttr('data-counter')

View File

@@ -33,6 +33,7 @@ define(['jquery'], ($) => {
getMapConnectionData: '/api/map/getConnectionData', // ajax URL - get connection data
getMapLogData: '/api/map/getLogData', // ajax URL - get logs data
// system API
getSystemData: '/api/system/getData', // ajax URL - get system data
searchSystem: '/api/system/search', // ajax URL - search system by name
saveSystem: '/api/system/save', // ajax URL - saves system to map
deleteSystem: '/api/system/delete', // ajax URL - delete system from map
@@ -47,10 +48,15 @@ define(['jquery'], ($) => {
getSignatures: '/api/signature/getAll', // ajax URL - get all signature data for system
saveSignatureData: '/api/signature/save', // ajax URL - save signature data for system
deleteSignatureData: '/api/signature/delete', // ajax URL - delete signature data for system
// structure API
saveStructureData: '/api/structure/save', // ajax URL - save structure data
deleteStructureData: '/api/structure/delete', // ajax URL - delete structure data
// route API
searchRoute: '/api/route/search', // ajax URL - search system routes
// stats API
getStatisticsData: '/api/statistic/getData', // ajax URL - get statistics data (activity log)
// universe API
searchUniverseData: '/api/universe/search', // ajax URL - search universe data
// GitHub API
gitHubReleases: '/api/github/releases' // ajax URL - get release info from GitHub
},

View File

@@ -105,6 +105,10 @@ define([
let ssoButtonElement = $('.' + config.ssoButtonClass);
let cookieHintElement = $('#' + config.cookieHintId);
$(document).on('click', '.' + config.ssoButtonClass + ', .' + config.characterSelectionClass + ' a', function(){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
// cookie hint --------------------------------------------------------
cookieHintElement.find('.btn-success').on('click', function(){
setAcceptCookie();
@@ -683,11 +687,6 @@ define([
let content = Mustache.render(template, data);
this.characterElement.html(content);
// lock character selection on click (prevent click spamming)
this.characterElement.find('a').on('click', function(){
$('.' + config.splashOverlayClass).showSplashOverlay();
});
// show character panel (animation settings)
initCharacterAnimation(this.characterElement.find('.' + config.characterImageWrapperClass));
}else{

View File

@@ -14,7 +14,6 @@ define([
'app/map/magnetizing',
'app/map/scrollbar',
'dragToSelect',
'select2',
'app/map/contextmenu',
'app/map/overlay',
'app/map/local'

View File

@@ -32,6 +32,9 @@ define([
// set default dialog config
Util.initDefaultBootboxConfig();
// set default select2 config
Util.initDefaultSelect2Config();
// load page
// load info (maintenance) info panel (if scheduled)
$('body').loadPageStructure().setGlobalShortcuts();
@@ -128,6 +131,8 @@ define([
Init.url = response.url;
Init.slack = response.slack;
Init.discord = response.discord;
Init.structureStatus = response.structureStatus;
Init.universeCategories = response.universeCategories;
Init.routeSearch = response.routeSearch;
Init.programMode = response.programMode;

View File

@@ -9,6 +9,7 @@ define([
'app/ui/system_graph',
'app/ui/system_signature',
'app/ui/system_route',
'app/ui/system_intel',
'app/ui/system_killboard',
'app/ui/connection_info',
'app/counter'
@@ -23,6 +24,7 @@ define([
SystemGraphModule,
SystemSignatureModule,
SystemRouteModule,
SystemIntelModule,
SystemKillboardModule,
ConnectionInfoModule
) => {
@@ -276,6 +278,9 @@ define([
// draw system routes module
drawModule(secondCell, SystemRouteModule, currentSystemData.mapId, currentSystemData.systemData);
// draw system intel module
drawModule(secondCell, SystemIntelModule, currentSystemData.mapId, currentSystemData.systemData);
// draw system killboard module
drawModule(secondCell, SystemKillboardModule, currentSystemData.mapId, currentSystemData.systemData);
});

View File

@@ -346,7 +346,6 @@ define([
let formValid = form.isValidForm();
if(formValid === true){
// lock dialog
let dialogContent = mapInfoDialog.find('.modal-content');
dialogContent.showLoadingAnimation();
@@ -391,7 +390,6 @@ define([
data: requestData,
dataType: 'json'
}).done(function(responseData){
if(responseData.error.length){
form.showFormMessage(responseData.error);
}else{

View File

@@ -7,10 +7,67 @@ define([
'app/init',
'app/util',
'app/map/util'
], function($, Init, Util, MapUtil) {
], ($, Init, Util, MapUtil) => {
'use strict';
/**
* format result data
* @param data
* @returns {*}
*/
let formatCategoryTypeResultData = (data) => {
if(data.loading) return data.text;
if(data.placeholder) return data.placeholder;
let markup = '<div class="clearfix">';
if(data.hasOwnProperty('children')){
// category group label
markup += '<div class="col-xs-9">' + data.text + '</div>';
markup += '<div class="col-xs-3 text-right">(' + data.children.length + ')</div>';
}else{
let imagePath = '';
let iconName = '';
let thumb = '';
switch(data.categoryType){
case 'character':
imagePath = Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg';
break;
case 'corporation':
imagePath = Init.url.ccpImageServer + '/Corporation/' + data.id + '_32.png';
break;
case 'alliance':
imagePath = Init.url.ccpImageServer + '/Alliance/' + data.id + '_32.png';
break;
case 'inventory_type':
imagePath = Init.url.ccpImageServer + '/Type/' + data.id + '_32.png';
break;
case 'render':
imagePath = Init.url.ccpImageServer + '/Render/' + data.id + '_32.png';
break;
case 'station':
iconName = 'fa-home';
break;
case 'solar_system':
iconName = 'fa-sun';
break;
}
if(imagePath){
thumb = '<img src="' + imagePath + '" style="max-width: 100%" />';
}else if(iconName){
thumb = '<i class="fas fa-fw ' + iconName + '" ></i>';
}
markup += '<div class="col-xs-2 text-center">' + thumb + '</div>';
markup += '<div class="col-xs-10">' + data.text + '</div>';
}
markup += '</div>';
return markup;
};
/**
* init a select element as "select2" for map selection
*/
@@ -20,7 +77,6 @@ define([
$.when(
selectElement.select2({
dropdownParent: selectElement.parents('.modal-body'),
theme: 'pathfinder',
maximumSelectionLength: 5
})
);
@@ -133,8 +189,7 @@ define([
}
},
dropdownParent: selectElement.parents('.modal-body'),
theme: 'pathfinder',
minimumInputLength: 2,
minimumInputLength: 3,
templateResult: formatResultData,
placeholder: 'System name',
allowClear: true,
@@ -171,52 +226,8 @@ define([
$.fn.initAccessSelect = function(options){
return this.each(function(){
let selectElement = $(this);
// format result data
function formatResultData (data) {
if (data.loading){
return data.text;
}
// check if an option is already selected
// do not show the same result twice
let currentValues = selectElement.val();
if(
currentValues &&
currentValues.indexOf( data.id.toString() ) !== -1
){
return ;
}
let imagePath = '';
let previewContent = '';
switch(options.type){
case 'character':
imagePath = Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
case 'corporation':
imagePath = Init.url.ccpImageServer + '/Corporation/' + data.id + '_32.png';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
case 'alliance':
imagePath = Init.url.ccpImageServer + '/Alliance/' + data.id + '_32.png';
previewContent = '<img src="' + imagePath + '" style="max-width: 100%" />';
break;
}
let markup = '<div class="clearfix">';
markup += '<div class="col-sm-2">' + previewContent + '</div>';
markup += '<div class="col-sm-10">' + data.text + '</div></div>';
return markup;
}
// format selection data
function formatSelectionData (data){
@@ -251,7 +262,8 @@ define([
results: data.map( function(item){
return {
id: item.id,
text: item.name
text: item.name,
categoryType: options.type
};
})
};
@@ -266,12 +278,11 @@ define([
}
},
dropdownParent: selectElement.parents('.modal-body'),
theme: 'pathfinder',
minimumInputLength: 3,
placeholder: options.type + ' names',
allowClear: false,
maximumSelectionLength: options.maxSelectionLength,
templateResult: formatResultData,
templateResult: formatCategoryTypeResultData,
templateSelection: formatSelectionData,
escapeMarkup: function(markup){
// let our custom formatter work
@@ -288,4 +299,190 @@ define([
};
/**
* init a select element as an ajax based "select2" object for universeTypes
* e.g. 'alliance', 'corporation', 'character', ...
* @param options
* @returns {*}
*/
$.fn.initUniverseSearch = function(options) {
let showErrorNotification = (reason) => {
Util.showNotify({title: 'Search failed', text: reason + ' deleted', type: 'warning'});
};
/**
* format selection data
* @param data
* @returns {*}
*/
function formatSelectionData (data){
if(data.loading) return data.text;
if(data.placeholder) return data.placeholder;
let markup = '<div class="clearfix">';
markup += '<div class="col-sm-10">' + data.text + '</div></div>';
return markup;
}
return this.each(function() {
let selectElement = $(this);
$.when(
selectElement.select2({
ajax: {
type: 'POST',
url: function(params){
// add params to URL
return Init.path.searchUniverseData + '/' + params.term;
},
dataType: 'json',
delay: 250,
timeout: 5000,
cache: true,
data: function(params){
return {
categories: options.categoryNames
};
},
processResults: function(result, page) {
let data = {results: []};
if(result.hasOwnProperty('error')){
showErrorNotification(result.error);
}else{
let mapChildren = function(item){
return {
id: item.id,
text: item.name,
categoryType: this
};
};
for(let category in result){
// skip custom functions in case result = [] (array functions)
if(result.hasOwnProperty(category)){
data.results.push({
text: category,
children: result[category].map(mapChildren, category)
});
}
}
}
return data;
},
error: function (jqXHR, status, error) {
if( !Util.isXHRAborted(jqXHR) ){
let reason = status + ' ' + jqXHR.status + ': ' + error;
showErrorNotification(reason);
}
}
},
dropdownParent: selectElement.parents('.modal-body') ,
minimumInputLength: 3,
placeholder: '',
allowClear: options.maxSelectionLength <= 1,
maximumSelectionLength: options.maxSelectionLength,
templateResult: formatCategoryTypeResultData,
// templateSelection: formatSelectionData, // some issues with "clear" selection on single selects (empty option is needed)
escapeMarkup: function(markup){
// let our custom formatter work
return markup;
}
}).on('change', function(e){
// select changed
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" whe it could be cleared manually
if(
options.maxSelectionLength === 1 &&
$(this).val() !== null
){
$(this).val('').trigger('change');
}
})
).done(function(){
// after init finish
});
});
};
$.fn.initUniverseTypeSelect = function(options) {
/**
* get select option data by categoryIds
* @param categoryIds
* @returns {{results: Array}}
*/
let getOptionsData = categoryIds => {
let data = [];
let mapChildren = function(type){
return {
id: type.id,
text: type.name,
groupId: this.groupId,
categoryId: this.categoryId,
categoryType: this.categoryType
};
};
for(let categoryId of categoryIds){
let categoryData = Util.getObjVal(Init, 'universeCategories.' + categoryId);
if(categoryData && categoryData.groups){
// categoryId data exists and has groups...
for(let groupData of categoryData.groups){
if(groupData && groupData.types){
// groupData exists and has types...
data.push({
text: groupData.name,
children: groupData.types.map(mapChildren, {
groupId: groupData.id,
categoryId: categoryData.id,
categoryType: 'inventory_type',
})
});
}
}
}
}
return data;
};
return this.each(function() {
let selectElement = $(this);
$.when(
selectElement.select2({
data: getOptionsData(options.categoryIds),
dropdownParent: selectElement.parents('.modal-body'),
minimumInputLength: 0, // minimum number of characters required to start a search
maximumInputLength: 100, // maximum number of characters that may be provided for a search term
placeholder: '',
allowClear: options.maxSelectionLength <= 1,
multiple: options.maxSelectionLength > 1,
maximumSelectionLength: options.maxSelectionLength,
// maximumSelectionLength: options.maxSelectionLength > 1 ? options.maxSelectionLength > 1 : 0,
// minimumResultsForSearch: 5, // minimum number of results required to display the search box
templateResult: formatCategoryTypeResultData,
escapeMarkup: function(markup){
return markup;
}
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" whe it could be cleared manually
if(
options.maxSelectionLength === 1 &&
$(this).val() !== null
){
$(this).val('').trigger('change');
}
}).val(options.selected).trigger('change')
);
});
};
});

View File

@@ -169,33 +169,6 @@ define([
moduleElement.find('.' + config.descriptionArea).hideLoadingAnimation();
};
/**
* update a character counter field with current value length - maxCharLength
* @param field
* @param charCounterElement
* @param maxCharLength
*/
let updateCounter = function(field, charCounterElement, maxCharLength){
let value = field.val();
let inputLength = value.length;
// line breaks are 2 characters!
let newLines = value.match(/(\r\n|\n|\r)/g);
let addition = 0;
if (newLines != null) {
addition = newLines.length;
}
inputLength += addition;
charCounterElement.text(maxCharLength - inputLength);
if(maxCharLength <= inputLength){
charCounterElement.toggleClass('txt-color-red', true);
}else{
charCounterElement.toggleClass('txt-color-red', false);
}
};
/**
* get module element
* @param parentElement
@@ -319,10 +292,10 @@ define([
textarea.parent().next().append(charCounter);
// update character counter
updateCounter(textarea, charCounter, maxDescriptionLength);
Util.updateCounter(textarea, charCounter, maxDescriptionLength);
textarea.on('keyup', function(){
updateCounter($(this), charCounter, maxDescriptionLength);
Util.updateCounter($(this), charCounter, maxDescriptionLength);
});
});

View File

@@ -0,0 +1,628 @@
/**
* system route module
*/
define([
'jquery',
'app/init',
'app/util',
'bootbox'
], ($, Init, Util, bootbox) => {
'use strict';
let config = {
// module info
modulePosition: 1,
moduleName: 'systemIntel',
moduleHeadClass: 'pf-module-head', // class for module header
moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler
moduleTypeClass: 'pf-system-intel-module', // class for this module
// headline toolbar
moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head
moduleHeadlineIconAddClass: 'pf-module-icon-button-add', // class for "add structure" icon
moduleHeadlineIconRefreshClass: 'pf-module-icon-button-refresh', // class for "refresh" icon
// system intel module
systemStructuresTableClass: 'pf-system-structure-table', // class for route tables
// structure dialog
structureDialogId: 'pf-structure-dialog', // id for "structure" dialog
typeSelectId: 'pf-structure-dialog-type-select', // id for "type" select
corporationSelectId: 'pf-structure-dialog-corporation-select', // id for "corporation" select
descriptionTextareaId: 'pf-structure-dialog-description-textarea', // id for "description" textarea
descriptionTextareaCharCounter: 'pf-form-field-char-count', // class for "character counter" element for form field
// dataTable
tableRowIdPrefix: 'pf-structure-row_', // id prefix for table rows
tableCellImageClass: 'pf-table-image-smaller-cell', // class for table "image" cells
tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells
tableCellEllipsisClass: 'pf-table-cell-ellipses-auto', // class for table "ellipsis" cells
dataTableActionCellClass: 'pf-table-action-cell' // class for "action" cells
};
let maxDescriptionLength = 512;
/**
* get status icon for structure
* @param statusData
* @returns {string}
*/
let getStatusData = (statusData) => {
return '<i class="fas fa-fw fa-circle ' + statusData.class + '" title="' + statusData.label + '"></i>';
};
/**
* get <tr> DOM id by id
* @param tableApi
* @param id
* @returns {*}
*/
let getRowId = (tableApi, id) => {
return tableApi.rows().ids().toArray().find(rowId => rowId === config.tableRowIdPrefix + id);
};
/**
* callback -> add structure rows from responseData
* @param context
* @param responseData
*/
let callbackAddStructureRows = (context, responseData) => {
let systemData = Util.getObjVal(responseData, 'system');
callbackUpdateStructureRows(context, systemData);
};
/**
* callback -> add structure rows from systemData
* @param context
* @param systemData
*/
let callbackUpdateStructureRows = (context, systemData) => {
let touchedRows = [];
if(systemData){
let corporations = Util.getObjVal(systemData, 'structures');
if(corporations) {
for (let [corporationId, corporationData] of Object.entries(corporations)){
if(corporationData.structures && corporationData.structures.length){
for(let structureData of corporationData.structures){
let rowId = getRowId(context.tableApi, structureData.id);
// add corporation data
structureData.corporation = {
id: corporationData.id,
name: corporationData.name
};
if(rowId){
// update row
let api = context.tableApi.row('#' + rowId).data(structureData);
api.nodes().to$().data('animationStatus', 'changed').destroyTimestampCounter();
touchedRows.push(api.id());
}else{
// insert new row
//context.tableApi.row.add(structureData).nodes().to$().data('animationStatus', 'added');
let api = context.tableApi.row.add(structureData);
api.nodes().to$().data('animationStatus', 'added');
touchedRows.push(api.id());
}
}
}
}
}
}
console.log(touchedRows)
if(context.removeMissing){
context.tableApi.rows((idx, data, node) => !touchedRows.includes(node.id)).remove();
}
context.tableApi.draw();
};
/**
* callback -> delete structure rows
* @param context
* @param responseData
*/
let callbackDeleteStructures = (context, responseData) => {
let structureIds = Util.getObjVal(responseData, 'deletedStructureIds');
if(structureIds && structureIds.length){
for(let structureId of structureIds){
let rowId = getRowId(context.tableApi, structureId);
if(rowId){
context.tableApi.row('#' + rowId).remove();
}
}
context.tableApi.draw();
}
};
/**
* send ajax request
* @param url
* @param requestData
* @param context
* @param callback
*/
let sendRequest = (url, requestData, context, callback) => {
context.moduleElement.showLoadingAnimation();
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data: requestData,
context: context
}).done(function(data){
callback(this, data);
}).fail(function( jqXHR, status, error) {
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': System intel data', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
// hide loading animation
this.moduleElement.hideLoadingAnimation();
});
};
/**
* requests system data
* @param requestData
* @param context
* @param callback
*/
let getStructureData = (requestData, context, callback) => {
sendRequest(Init.path.getSystemData, requestData, context, callback);
};
/**
* save structure data
* @param requestData
* @param context
* @param callback
*/
let saveStructureData = (requestData, context, callback) => {
sendRequest(Init.path.saveStructureData, requestData, context, callback);
};
/**
* delete structure
* @param requestData
* @param context
* @param callback
*/
let deleteStructure = (requestData, context, callback) => {
sendRequest(Init.path.deleteStructureData, requestData, context, callback);
};
/**
* show structure dialog
* @param moduleElement
* @param tableApi
* @param systemId
* @param structureData
*/
let showStructureDialog = (moduleElement, tableApi, systemId, structureData) => {
let structureStatusData = Util.getObjVal(Init, 'structureStatus');
let structureTypeData = Util.getObjVal(Init, 'structureStatus');
let data = {
id: config.structureDialogId,
structureData: structureData,
structureStatus: Object.keys(structureStatusData).map((k) => {
let data = structureStatusData[k];
data.selected = data.id === Util.getObjVal(structureData, 'status.id');
return data;
}),
typeSelectId: config.typeSelectId,
corporationSelectId: config.corporationSelectId,
descriptionTextareaId: config.descriptionTextareaId,
descriptionTextareaCharCounter: config.descriptionTextareaCharCounter,
maxDescriptionLength: maxDescriptionLength
};
requirejs(['text!templates/dialog/structure.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
let structureDialog = bootbox.dialog({
title: 'Structure',
message: content,
show: true,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fas fa-fw fa-check"></i>&nbsp;save',
className: 'btn-success',
callback: function (){
let form = this.find('form');
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
if(formValid){
// get form data
let formData = form.getFormValues();
formData.id = Util.getObjVal(structureData, 'id') | 0;
formData.structureId = Util.getObjVal(formData, 'structureId') | 0;
formData.corporationId = Util.getObjVal(formData, 'corporationId') | 0;
formData.systemId = systemId | 0;
saveStructureData(formData,{
moduleElement: moduleElement,
tableApi: tableApi
}, callbackUpdateStructureRows);
}else{
return false;
}
}
}
}
});
structureDialog.on('shown.bs.modal', function(e) {
// init type select live search
let selectElementType = $(this).find('#' + config.typeSelectId);
selectElementType.initUniverseTypeSelect({
categoryIds: [65],
maxSelectionLength: 1,
selected: [Util.getObjVal(structureData, 'structure.id')]
});
// init corporation select live search
let selectElementCorporation = $(this).find('#' + config.corporationSelectId);
selectElementCorporation.initUniverseSearch({
categoryNames: ['corporation'],
maxSelectionLength: 1
});
// init character counter
let textarea = $(this).find('#' + config.descriptionTextareaId);
let charCounter = $(this).find('.' + config.descriptionTextareaCharCounter);
Util.updateCounter(textarea, charCounter, maxDescriptionLength);
textarea.on('keyup', function(){
Util.updateCounter($(this), charCounter, maxDescriptionLength);
});
// set form validator (after select2 init finish)
$(this).find('form').initFormValidation();
});
});
};
/**
* get module element
* @returns {*}
*/
let getModule = (parentElement, mapId, systemData) => {
let corporationId = Util.getCurrentUserInfo('corporationId');
// create new module container
let moduleElement = $('<div>').append(
$('<div>', {
class: config.moduleHeadClass
}).append(
$('<h5>', {
class: config.moduleHandlerClass
}),
$('<h5>', {
class: 'pull-right'
}).append(
$('<i>', {
class: ['fas', 'fa-fw', 'fa-plus', config.moduleHeadlineIconClass, config.moduleHeadlineIconAddClass].join(' '),
title: 'add'
}).attr('data-html', 'true').attr('data-toggle', 'tooltip'),
$('<i>', {
class: ['fas', 'fa-fw', 'fa-sync', config.moduleHeadlineIconClass, config.moduleHeadlineIconRefreshClass].join(' '),
title: 'refresh&nbsp;all'
}).attr('data-html', 'true').attr('data-toggle', 'tooltip')
),
$('<h5>', {
text: 'Intel'
})
)
);
let table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', 'pf-table-fixed', config.systemStructuresTableClass].join(' ')
});
moduleElement.append(table);
let structureTable = table.DataTable({
paging: false,
lengthChange: false,
ordering: true,
order: [[ 10, 'desc' ], [ 0, 'asc' ]],
info: false,
searching: false,
hover: false,
autoWidth: false,
rowId: rowData => config.tableRowIdPrefix + rowData.id,
language: {
emptyTable: 'No structures recorded',
info: '_START_ to _END_ of _MAX_',
infoEmpty: ''
},
rowGroup: {
enable: true,
dataSrc: 'systemId'
},
columnDefs: [
{
targets: 0,
title: '',
width: 2,
class: 'text-center',
data: 'status',
render: {
display: data => getStatusData(data),
sort: data => data.id
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).find('i').tooltip();
}
},{
targets: 1,
title: '',
width: 26,
orderable: false,
className: [config.tableCellImageClass, 'text-center'].join(' '),
data: 'structure.id',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display' && value){
value = '<img src="' + Init.url.ccpImageServer + '/Type/' + value + '_32.png" />';
}
return value;
}
}
},{
targets: 2,
title: 'structure',
width: 50,
className: [config.tableCellEllipsisClass].join(' '),
data: 'structure.name',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
},{
targets: 3,
title: 'name',
width: 60,
className: [config.tableCellEllipsisClass].join(' '),
data: 'name'
},{
targets: 4,
title: '',
width: 26,
orderable: false,
className: [config.tableCellImageClass, 'text-center'].join(' '),
data: 'owner.id',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
render: {
_: function(data, type, row, meta){
let value = data;
if(type === 'display' && value){
value = '<img src="' + Init.url.ccpImageServer + '/Corporation/' + value + '_32.png" />';
}
return value;
}
}
},{
targets: 5,
title: 'owner',
width: 50,
className: [config.tableCellEllipsisClass].join(' '),
data: 'owner.name',
defaultContent: '<i class="fas fa-question txt-color txt-color-orangeDark"></i>',
},{
targets: 6,
title: 'note',
className: [config.tableCellEllipsisClass].join(' '),
data: 'description'
},{
targets: 7,
title: 'updated',
width: 80,
className: ['text-right', config.tableCellCounterClass].join(' '),
data: 'updated.updated'
},{
targets: 8,
title: '',
orderable: false,
width: 10,
class: ['text-center', config.dataTableActionCellClass, config.moduleHeadlineIconClass].join(' '),
data: null,
render: {
display: data => {
let icon = '<i class="fas fa-pencil-alt"></i>';
if(data.corporation.id !== corporationId){
icon = '';
}
return icon;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if($(cell).is(':empty')){
$(cell).removeClass(config.dataTableActionCellClass + ' ' + config.moduleHeadlineIconClass);
}else{
$(cell).on('click', function(e) {
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tableApi.row( $(cell).parents('tr')).data();
showStructureDialog(moduleElement, tableApi, systemData.systemId, rowData);
});
}
}
},{
targets: 9,
title: '',
orderable: false,
width: 10,
class: ['text-center', config.dataTableActionCellClass].join(' '),
data: null,
render: {
display: data => {
let icon = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
if(data.corporation.id !== corporationId){
icon = '<i class="fas fa-ban txt-color txt-color-grayLight" title="restricted" data-placement="left"></i>';
}
return icon;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if($(cell).find('.fa-ban').length){
$(cell).removeClass(config.dataTableActionCellClass + ' ' + config.moduleHeadlineIconClass);
$(cell).find('i').tooltip();
}else{
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete structure',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
onConfirm : function(e, target){
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tableApi.row( $(cell).parents('tr')).data();
// let deleteRowElement = $(cell).parents('tr');
// tableApi.rows(deleteRowElement).remove().draw();
deleteStructure({
id: rowData.id
},{
moduleElement: moduleElement,
tableApi: tableApi
}, callbackDeleteStructures);
}
};
// init confirmation dialog
$(cell).confirmation(confirmationSettings);
}
}
},{
targets: 10,
name: 'corporation',
data: 'corporation',
visible: false,
render: {
sort: function(data){
return data.name;
}
}
}
],
drawCallback: function (settings){
let tableApi = this.api();
let columnCount = tableApi.columns(':visible').count();
let rows = tableApi.rows( {page:'current'} ).nodes();
let last= null;
tableApi.column('corporation:name', {page:'current'} ).data().each( function ( group, i ) {
if ( !last || last.id !== group.id ) {
$(rows).eq(i).before(
'<tr class="group">' +
'<td></td>' +
'<td class="' + config.tableCellImageClass + '">' +
'<img src="' + Init.url.ccpImageServer + '/Corporation/' + group.id + '_32.png" />' +
'</td>' +
'<td colspan="' + (columnCount - 2 ) + '">' + group.name + '</td>' +
'</tr>'
);
last = group;
}
});
rows.to$().find('.' + config.tableCellCounterClass + ':not([data-counter])').initTimestampCounter();
let animationRows = rows.to$().filter(function() {
return (
$(this).data('animationStatus') ||
$(this).data('animationTimer')
);
});
for(let i = 0; i < animationRows.length; i++){
let animationRow = $(animationRows[i]);
animationRow.pulseTableRow(animationRow.data('animationStatus'));
animationRow.removeData('animationStatus');
}
},
initComplete: function(settings){
let tableApi = this.api();
// initial structure data request
getStructureData({
mapId: mapId,
systemId: systemData.id
},{
moduleElement: moduleElement,
tableApi: tableApi
}, callbackAddStructureRows);
}
});
// init tooltips for this module
let tooltipElements = moduleElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip({
container: 'body'
});
return moduleElement;
};
/**
* init intel module
* @param moduleElement
* @param mapId
* @param systemData
*/
let initModule = (moduleElement, mapId, systemData) => {
let structureTableElement = moduleElement.find('.' + config.systemStructuresTableClass);
let tableApi = structureTableElement.DataTable();
// init structure dialog --------------------------------------------------------------------------------------
moduleElement.find('.' + config.moduleHeadlineIconAddClass).on('click', function(e){
showStructureDialog(moduleElement, tableApi, systemData.systemId);
});
// init refresh button ----------------------------------------------------------------------------------------
moduleElement.find('.' + config.moduleHeadlineIconRefreshClass).on('click', function(e){
getStructureData({
mapId: mapId,
systemId: systemData.id
},{
moduleElement: moduleElement,
tableApi: tableApi,
removeMissing: true
}, callbackAddStructureRows);
});
};
return {
config: config,
getModule: getModule,
initModule: initModule
};
});

View File

@@ -46,6 +46,11 @@ define([
return label;
};
/**
* show killMails
* @param moduleElement
* @param killboardData
*/
let showKillmails = function(moduleElement, killboardData){
// show number of killMails

View File

@@ -8,7 +8,7 @@ define([
'app/util',
'bootbox',
'app/map/util'
], function($, Init, Util, bootbox, MapUtil) {
], ($, Init, Util, bootbox, MapUtil) => {
'use strict';
let config = {
@@ -103,7 +103,7 @@ define([
* @param context
* @param routesData
*/
let callbackAddRouteRow = (context, routesData) => {
let callbackAddRouteRows = (context, routesData) => {
if(routesData.length > 0){
for(let i = 0; i < routesData.length; i++){
@@ -170,7 +170,6 @@ define([
return rowElement;
};
/**
* requests route data from eveCentral API and execute callback
* @param requestData
@@ -210,7 +209,7 @@ define([
routeData.push( getRouteRequestDataFromRowData( this.data() ));
});
getRouteData({routeData: routeData}, context, callbackAddRouteRow);
getRouteData({routeData: routeData}, context, callbackAddRouteRows);
};
/**
@@ -261,7 +260,7 @@ define([
mapSelectOptions: mapSelectOptions
};
requirejs(['text!templates/dialog/route.html', 'mustache'], function(template, Mustache) {
requirejs(['text!templates/dialog/route.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
@@ -326,7 +325,7 @@ define([
}]
};
getRouteData(requestData, context, callbackAddRouteRow);
getRouteData(requestData, context, callbackAddRouteRows);
}
}
}
@@ -418,7 +417,7 @@ define([
routeData: requestRouteData
};
getRouteData(requestData, contextData, callbackAddRouteRow);
getRouteData(requestData, contextData, callbackAddRouteRows);
}
};
@@ -432,7 +431,7 @@ define([
let showSettingsDialog = (dialogData, moduleElement, systemFromData, routesTable) => {
let promiseStore = MapUtil.getLocaleData('map', dialogData.mapId);
promiseStore.then(function(dataStore) {
promiseStore.then(dataStore => {
// selected systems (if already stored)
let systemSelectOptions = [];
if(
@@ -902,7 +901,6 @@ define([
)
);
// crate new route table
let table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', config.systemInfoRoutesTableClass].join(' ')
});
@@ -1039,7 +1037,7 @@ define([
routeData: [routeData]
};
getRouteData(requestData, context, callbackAddRouteRow);
getRouteData(requestData, context, callbackAddRouteRows);
});
}
},{
@@ -1074,7 +1072,7 @@ define([
routeData: [routeData]
};
getRouteData(requestData, context, callbackAddRouteRow);
getRouteData(requestData, context, callbackAddRouteRows);
});
}
},{
@@ -1122,10 +1120,10 @@ define([
});
for(let i = 0; i < animationRows.length; i++){
$(animationRows[i]).pulseTableRow($(animationRows[i]).data('animationStatus'));
$(animationRows[i]).removeData('animationStatus');
let animationRow = $(animationRows[i]);
animationRow.pulseTableRow(animationRow.data('animationStatus'));
animationRow.removeData('animationStatus');
}
},
initComplete: function(settings, json){
// click on "fake connection" -------------------------------------------------------------------------
@@ -1231,7 +1229,6 @@ define([
};
let routesTableElement = moduleElement.find('.' + config.systemInfoRoutesTableClass);
let routesTable = routesTableElement.DataTable();
// init refresh routes --------------------------------------------------------------------

View File

@@ -15,7 +15,8 @@ define([
'easyPieChart',
'hoverIntent',
'bootstrapConfirmation',
'bootstrapToggle'
'bootstrapToggle',
'select2'
], ($, Init, SystemEffect, SignatureType, bootbox, localforage) => {
'use strict';
@@ -55,6 +56,8 @@ define([
mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable)
mapClass: 'pf-map' , // class for all maps
// select2
select2Class: 'pf-select2', // class for all "Select2" <select> elements
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
@@ -962,14 +965,78 @@ define([
};
/**
* set default configuration for "Bootbox" dialogs
* set default configuration for "Bootbox"
*/
let initDefaultBootboxConfig = function(){
let initDefaultBootboxConfig = () => {
bootbox.setDefaults({
onEscape: true // enables close dialogs on ESC key
});
};
/**
* set default configuration for "Select2"
*/
let initDefaultSelect2Config = () => {
$.fn.select2.defaults.set('theme', 'pathfinder');
let initScrollbar = (resultsWrapper) => {
// default 'mousewheel' event set by select2 needs to be disabled
// in order to make mCustomScrollbar mouseWheel enable works correctly
$(resultsWrapper).find('ul.select2-results__options').off('mousewheel');
resultsWrapper.mCustomScrollbar({
mouseWheel: {
enable: true,
scrollAmount: 'auto',
axis: 'y',
preventDefault: true
},
keyboard: {
enable: false,
scrollType: 'stepless',
scrollAmount: 'auto'
},
scrollbarPosition: 'inside',
autoDraggerLength: true,
autoHideScrollbar: false,
advanced: {
updateOnContentResize: true
}
});
};
let getResultsWrapper = (selectElement) => {
let wrapper = null;
let selectElementId = selectElement.getAttribute('data-select2-id');
if(selectElementId){
let resultsOptions = $('#select2-' + selectElementId + '-results');
if(resultsOptions.length) {
let resultsWrapper = resultsOptions.parents('.select2-results');
if(resultsWrapper.length){
wrapper = resultsWrapper;
}
}
}
return wrapper;
};
// global open event
$(document).on('select2:open', '.' + config.select2Class, function(e){
let resultsWrapper = getResultsWrapper(this);
if(resultsWrapper){
initScrollbar(resultsWrapper);
}
});
// global select2:closing event
$(document).on('select2:closing', '.' + config.select2Class, function(e){
let resultsWrapper = getResultsWrapper(this);
if(resultsWrapper){
resultsWrapper.mCustomScrollbar('destroy');
}
});
};
/**
* get the current main trigger delay for the main trigger functions
* optional in/decrease the delay
@@ -1104,6 +1171,33 @@ define([
return duration;
};
/**
* update a character counter field with current value length - maxCharLength
* @param field
* @param charCounterElement
* @param maxCharLength
*/
let updateCounter = function(field, charCounterElement, maxCharLength){
let value = field.val();
let inputLength = value.length;
// line breaks are 2 characters!
let newLines = value.match(/(\r\n|\n|\r)/g);
let addition = 0;
if (newLines != null) {
addition = newLines.length;
}
inputLength += addition;
charCounterElement.text(maxCharLength - inputLength);
if(maxCharLength <= inputLength){
charCounterElement.toggleClass('txt-color-red', true);
}else{
charCounterElement.toggleClass('txt-color-red', false);
}
};
/**
* trigger main logging event with log information
* @param logKey
@@ -2413,12 +2507,14 @@ define([
showVersionInfo: showVersionInfo,
initPrototypes: initPrototypes,
initDefaultBootboxConfig: initDefaultBootboxConfig,
initDefaultSelect2Config: initDefaultSelect2Config,
getCurrentTriggerDelay: getCurrentTriggerDelay,
getServerTime: getServerTime,
convertTimestampToServerTime: convertTimestampToServerTime,
getTimeDiffParts: getTimeDiffParts,
timeStart: timeStart,
timeStop: timeStop,
updateCounter: updateCounter,
log: log,
showNotify: showNotify,
stopTabBlink: stopTabBlink,

View File

@@ -0,0 +1,414 @@
/*! RowGroup 1.0.3-dev
* ©2017-2018 SpryMedia Ltd - datatables.net/license
*/
/**
* @summary RowGroup
* @description RowGrouping for DataTables
* @version 1.0.3-dev
* @file dataTables.rowGroup.js
* @author SpryMedia Ltd (www.sprymedia.co.uk)
* @contact datatables.net
* @copyright Copyright 2017-2018 SpryMedia Ltd.
*
* This source file is free software, available under the following license:
* MIT license - http://datatables.net/license/mit
*
* This source file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
*
* For details please refer to: http://www.datatables.net
*/
(function( factory ){
if ( typeof define === 'function' && define.amd ) {
// AMD
define( ['jquery', 'datatables.net'], function ( $ ) {
return factory( $, window, document );
} );
}
else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = function (root, $) {
if ( ! root ) {
root = window;
}
if ( ! $ || ! $.fn.dataTable ) {
$ = require('datatables.net')(root, $).$;
}
return factory( $, root, root.document );
};
}
else {
// Browser
factory( jQuery, window, document );
}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;
var RowGroup = function ( dt, opts ) {
// Sanity check that we are using DataTables 1.10 or newer
if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
throw 'RowGroup requires DataTables 1.10.8 or newer';
}
// User and defaults configuration object
this.c = $.extend( true, {},
DataTable.defaults.rowGroup,
RowGroup.defaults,
opts
);
// Internal settings
this.s = {
dt: new DataTable.Api( dt ),
dataFn: DataTable.ext.oApi._fnGetObjectDataFn( this.c.dataSrc )
};
// DOM items
this.dom = {
};
// Check if row grouping has already been initialised on this table
var settings = this.s.dt.settings()[0];
var existing = settings.rowGroup;
if ( existing ) {
return existing;
}
settings.rowGroup = this;
this._constructor();
};
$.extend( RowGroup.prototype, {
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* API methods for DataTables API interface
*/
/**
* Get/set the grouping data source - need to call draw after this is
* executed as a setter
* @returns string~RowGroup
*/
dataSrc: function ( val )
{
if ( val === undefined ) {
return this.c.dataSrc;
}
var dt = this.s.dt;
this.c.dataSrc = val;
this.s.dataFn = DataTable.ext.oApi._fnGetObjectDataFn( this.c.dataSrc );
$(dt.table().node()).triggerHandler( 'rowgroup-datasrc.dt', [ dt, val ] );
return this;
},
/**
* Disable - need to call draw after this is executed
* @returns RowGroup
*/
disable: function ()
{
this.c.enable = false;
return this;
},
/**
* Enable - need to call draw after this is executed
* @returns RowGroup
*/
enable: function ( flag )
{
if ( flag === false ) {
return this.disable();
}
this.c.enable = true;
return this;
},
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Constructor
*/
_constructor: function ()
{
var that = this;
var dt = this.s.dt;
var rows = dt.rows();
var groups = [];
rows.every( function () {
var d = this.data();
var group = that.s.dataFn( d );
if ( groups.indexOf(group) == -1 ) {
groups.push( group );
}
} );
dt.on( 'draw.dtrg', function () {
if ( that.c.enable ) {
that._draw();
}
} );
dt.on( 'column-visibility.dt.dtrg responsive-resize.dt.dtrg', function () {
that._adjustColspan();
} );
dt.on( 'destroy', function () {
dt.off( '.dtrg' );
} );
},
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private methods
*/
/**
* Adjust column span when column visibility changes
* @private
*/
_adjustColspan: function ()
{
$( 'tr.'+this.c.className, this.s.dt.table().body() )
.attr( 'colspan', this._colspan() );
},
/**
* Get the number of columns that a grouping row should span
* @private
*/
_colspan: function ()
{
return this.s.dt.columns().visible().reduce( function (a, b) {
return a + b;
}, 0 );
},
/**
* Update function that is called whenever we need to draw the grouping rows
* @private
*/
_draw: function ()
{
var that = this;
var dt = this.s.dt;
var rows = dt.rows( { page: 'current' } );
var groupedRows = [];
var last, display;
rows.every( function () {
var d = this.data();
var group = that.s.dataFn( d );
if ( group === null || group === undefined ) {
group = that.c.emptyDataGroup;
}
if ( last === undefined || group !== last ) {
groupedRows.push( [] );
last = group;
}
groupedRows[ groupedRows.length - 1 ].push( this.index() );
} );
for ( var i=0, ien=groupedRows.length ; i<ien ; i++ ) {
var group = groupedRows[i];
var firstRow = dt.row(group[0]);
var groupName = this.s.dataFn( firstRow.data() );
if ( this.c.startRender ) {
display = this.c.startRender.call( this, dt.rows(group), groupName );
this
._rowWrap( display, this.c.startClassName )
.insertBefore( firstRow.node() );
}
if ( this.c.endRender ) {
display = this.c.endRender.call( this, dt.rows(group), groupName );
this
._rowWrap( display, this.c.endClassName )
.insertAfter( dt.row( group[ group.length-1 ] ).node() );
}
}
},
/**
* Take a rendered value from an end user and make it suitable for display
* as a row, by wrapping it in a row, or detecting that it is a row.
* @param [node|jQuery|string] display Display value
* @param [string] className Class to add to the row
* @private
*/
_rowWrap: function ( display, className )
{
var row;
if ( display === null || display === undefined ) {
display = this.c.emptyDataGroup;
}
if ( typeof display === 'object' && display.nodeName && display.nodeName.toLowerCase() === 'tr') {
row = $(display);
}
else if (display instanceof $ && display.length && display[0].nodeName.toLowerCase() === 'tr') {
row = display;
}
else {
row = $('<tr/>')
.append(
$('<td/>')
.attr( 'colspan', this._colspan() )
.append( display )
);
}
return row
.addClass( this.c.className )
.addClass( className );
}
} );
/**
* RowGroup default settings for initialisation
*
* @namespace
* @name RowGroup.defaults
* @static
*/
RowGroup.defaults = {
/**
* Class to apply to grouping rows - applied to both the start and
* end grouping rows.
* @type string
*/
className: 'group',
/**
* Data property from which to read the grouping information
* @type string|integer
*/
dataSrc: 0,
/**
* Text to show if no data is found for a group
* @type string
*/
emptyDataGroup: 'No group',
/**
* Initial enablement state
* @boolean
*/
enable: true,
/**
* Class name to give to the end grouping row
* @type string
*/
endClassName: 'group-end',
/**
* End grouping label function
* @function
*/
endRender: null,
/**
* Class name to give to the start grouping row
* @type string
*/
startClassName: 'group-start',
/**
* Start grouping label function
* @function
*/
startRender: function ( rows, group ) {
return group;
}
};
RowGroup.version = "1.0.3-dev";
$.fn.dataTable.RowGroup = RowGroup;
$.fn.DataTable.RowGroup = RowGroup;
DataTable.Api.register( 'rowGroup()', function () {
return this;
} );
DataTable.Api.register( 'rowGroup().disable()', function () {
return this.iterator( 'table', function (ctx) {
if ( ctx.rowGroup ) {
ctx.rowGroup.enable( false );
}
} );
} );
DataTable.Api.register( 'rowGroup().enable()', function ( opts ) {
return this.iterator( 'table', function (ctx) {
if ( ctx.rowGroup ) {
ctx.rowGroup.enable( opts === undefined ? true : opts );
}
} );
} );
DataTable.Api.register( 'rowGroup().dataSrc()', function ( val ) {
if ( val === undefined ) {
return this.context[0].rowGroup.dataSrc();
}
return this.iterator( 'table', function (ctx) {
if ( ctx.rowGroup ) {
ctx.rowGroup.dataSrc( val );
}
} );
} );
// Attach a listener to the document which listens for DataTables initialisation
// events so we can automatically initialise
/*
$(document).on( 'preInit.dt.dtrg', function (e, settings, json) {
if ( e.namespace !== 'dt' ) {
return;
}
var init = settings.oInit.rowGroup;
var defaults = DataTable.defaults.rowGroup;
if ( init || defaults ) {
var opts = $.extend( {}, defaults, init );
if ( init !== false ) {
new RowGroup( settings, opts );
}
}
} );
*/
return RowGroup;
}));

View File

@@ -0,0 +1,55 @@
describe( 'rowGroup().dataSrc()', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'A DataTable can be created with RowGrouping', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2
}
} );
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(1) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
it( 'Get the current data source', function () {
expect( table.rowGroup().dataSrc() ).toBe( 2 );
} );
it( 'Can change the data source', function () {
table.rowGroup().dataSrc( 3 ).draw();
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( '61' );
expect( $('#example tbody tr:eq(1) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
expect( $('#example tbody tr:eq(2) td:eq(0)').html() ).toBe( '22' );
expect( $('#example tbody tr:eq(3) td:eq(0)').html() ).toBe( 'Cedric Kelly' );
} );
it( 'Return as a setter is an API instance', function () {
expect( table.rowGroup().dataSrc( 3 ) instanceof $.fn.dataTable.Api ).toBe( true );
} );
it( 'Read the set value back', function () {
expect( table.rowGroup().dataSrc() ).toBe( 3 );
} );
it( 'Setting does not show any difference until redraw', function () {
table.rowGroup().dataSrc( 0 );
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( '61' );
} );
it( 'Setting does not show any difference until redraw', function () {
table.draw();
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
} );

View File

@@ -0,0 +1,35 @@
describe( 'rowGroup().disable()', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'A DataTable can be created with RowGrouping', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2
}
} );
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(1) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
it( 'Does not redraw automatically', function () {
table.rowGroup().disable();
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(1) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
it( 'Disabled after a redraw', function () {
table.draw();
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
} );

View File

@@ -0,0 +1,48 @@
describe( 'rowGroup().enable()', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'A DataTable can be created with RowGrouping', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2
}
} );
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(1) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
it( 'Disable', function () {
table.rowGroup().disable().draw();
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
it( 'Can be enabled', function () {
table.rowGroup().enable().draw();
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(1) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
it( 'Can be used as a toggle to disable', function () {
table.rowGroup().enable( false ).draw();
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
it( 'Can be used as a toggle to enable', function () {
table.rowGroup().enable( true ).draw();
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(1) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
} );

View File

@@ -0,0 +1,52 @@
describe( 'rowgroup-datasrc', function() {
var table;
var args;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'A DataTable can be created with RowGrouping', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2
}
} );
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(1) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
it( 'Change in the data source will trigger rowgroup-datasrc', function ( done ) {
table.on( 'rowgroup-datasrc', function () {
args = arguments;
done();
} );
table.rowGroup().dataSrc( 3 ).draw();
} );
it( 'Three arguments', function () {
expect( args.length ).toBe( 3 );
} );
it( 'First is jQuery object', function () {
expect( args[0] instanceof $.Event ).toBe( true );
} );
it( 'Second is DataTable API instance', function () {
expect( args[1] instanceof $.fn.dataTable.Api ).toBe( true );
} );
it( 'Third is the new data source value', function () {
expect( args[2] ).toBe( 3 );
} );
it( 'Event is triggered with .dt namespace', function () {
expect( args[0].namespace ).toBe( 'dt' );
} );
} );

View File

@@ -0,0 +1,26 @@
describe( 'RowGroup exists and can be initialised', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'Exists', function () {
expect( $.fn.dataTable.RowGroup ).toBeDefined();
} );
it( 'A DataTable can be created with RowGrouping', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2
}
} );
expect( $('#example tbody tr:eq(0) td:eq(0)').html() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(1) td:eq(0)').html() ).toBe( 'Tiger Nixon' );
} );
} );

View File

@@ -0,0 +1,56 @@
describe( 'Class name', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'Default is `group`', function () {
expect( $.fn.dataTable.RowGroup.defaults.className ).toBe( 'group' );
} );
it( 'Is used for header rows', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2
}
} );
expect( $('#example tbody tr:eq(0)').hasClass('group') ).toBe( true );
} );
dt.html( 'basic' );
it( 'Can be set to a different value', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
className: 'test'
}
} );
expect( $('#example tbody tr:eq(0)').hasClass('group') ).toBe( false );
expect( $('#example tbody tr:eq(0)').hasClass('test') ).toBe( true );
} );
dt.html( 'basic' );
it( 'Is applied to the footer grouping row', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
endRender: function () {
return 'Test';
}
}
} );
expect( $('#example tbody tr:eq(10)').hasClass('group') ).toBe( true );
} );
} );

View File

@@ -0,0 +1,43 @@
describe( 'dataSrc', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'Default is 0', function () {
expect( $.fn.dataTable.RowGroup.defaults.dataSrc ).toBe( 0 );
} );
it( 'Is indeed 0 when run', function () {
table = $('#example').DataTable( {
rowGroup: true
} );
expect( $('#example tbody tr:eq(0)').text() ).toBe( 'Airi Satou' );
} );
dt.html( 'basic' );
it( 'Can be used with object data', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
columns: [
{ data: 'name' },
{ data: 'position' },
{ data: 'office' },
{ data: 'age' },
{ data: 'startDate' },
{ data: 'salary' }
],
rowGroup: {
dataSrc: 'office'
}
} );
expect( $('#example tbody tr:eq(0)').text() ).toBe( 'Edinburgh' );
} );
} );

View File

@@ -0,0 +1,40 @@
describe( 'Enable', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'Default is enable', function () {
expect( $.fn.dataTable.RowGroup.defaults.enable ).toBe( true );
} );
it( 'Is indeed enabled', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2
}
} );
expect( $('#example tbody tr:eq(0)').hasClass('group') ).toBe( true );
} );
dt.html( 'basic' );
it( 'Can be disabled', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
enable: false
}
} );
expect( $('#example tbody tr:eq(0)').hasClass('group') ).toBe( false );
expect( $('#example tbody tr').length ).toBe( 10 );
} );
} );

View File

@@ -0,0 +1,46 @@
describe( 'End class name', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'Default is `group-end`', function () {
expect( $.fn.dataTable.RowGroup.defaults.endClassName ).toBe( 'group-end' );
} );
it( 'Is used', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
endRender: function () {
return 'Test';
}
}
} );
expect( $('#example tbody tr:eq(10)').hasClass('group-end') ).toBe( true );
} );
dt.html( 'basic' );
it( 'Can be changed', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
endRender: function () {
return 'Test';
},
endClassName: 'test'
}
} );
expect( $('#example tbody tr:eq(10)').hasClass('group-end') ).toBe( false );
expect( $('#example tbody tr:eq(10)').hasClass('test') ).toBe( true );
} );
} );

View File

@@ -0,0 +1,88 @@
describe( 'End render', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'Default is null', function () {
expect( $.fn.dataTable.RowGroup.defaults.endRender ).toBe( null );
} );
it( 'Can be used to show the grouping data name', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
endRender: function ( rows, group ) {
return group;
}
}
} );
expect( $('#example tbody tr:eq(10)').text() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(13)').text() ).toBe( 'London' );
} );
dt.html( 'basic' );
it( 'Will show a static value', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
endRender: function ( rows, group ) {
return 'Test';
}
}
} );
expect( $('#example tbody tr:eq(10)').text() ).toBe( 'Test' );
expect( $('#example tbody tr:eq(13)').text() ).toBe( 'Test' );
} );
dt.html( 'basic' );
var a1 = [];
var a2 = [];
it( 'Renderer is called with two arguments', function () {
var args;
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
endRender: function ( rows, group ) {
a1.push( rows );
a2.push( group );
args = arguments.length;
return group;
}
}
} );
expect( args ).toBe( 2 );
} );
it( 'Is called once for each group on the page', function () {
expect( a1.length ).toBe( 2 );
} );
it( 'First argument is an API instance', function () {
expect( a1[0] instanceof $.fn.dataTable.Api ).toBe( true );
} );
it( 'First argument has the rows for the group in it', function () {
expect( a1[0].count() ).toBe( 9 );
expect( a1[1].count() ).toBe( 1 );
} );
it( 'Second argument has the group name', function () {
expect( a2[0] ).toBe( 'Edinburgh' );
expect( a2[1] ).toBe( 'London' );
} );
} );

View File

@@ -0,0 +1,40 @@
describe( 'Start class name', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'Default is `group-start`', function () {
expect( $.fn.dataTable.RowGroup.defaults.startClassName ).toBe( 'group-start' );
} );
it( 'Is used', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2
}
} );
expect( $('#example tbody tr:eq(0)').hasClass('group-start') ).toBe( true );
} );
dt.html( 'basic' );
it( 'Can be changed', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
startClassName: 'test'
}
} );
expect( $('#example tbody tr:eq(0)').hasClass('group-start') ).toBe( false );
expect( $('#example tbody tr:eq(0)').hasClass('test') ).toBe( true );
} );
} );

View File

@@ -0,0 +1,121 @@
describe( 'Start render', function() {
var table;
dt.libs( {
js: [ 'jquery', 'datatables', 'rowgroup' ],
css: [ 'datatables', 'rowgroup' ]
} );
dt.html( 'basic' );
it( 'Default is defined as a function', function () {
expect( typeof $.fn.dataTable.RowGroup.defaults.startRender ).toBe( 'function' );
} );
it( 'Default is to show the grouping data name', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2
}
} );
expect( $('#example tbody tr:eq(0)').text() ).toBe( 'Edinburgh' );
expect( $('#example tbody tr:eq(10)').text() ).toBe( 'London' );
} );
dt.html( 'basic' );
it( 'Will show a static value', function () {
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
startRender: function ( rows, group ) {
return 'Test';
}
}
} );
expect( $('#example tbody tr:eq(0)').text() ).toBe( 'Test' );
expect( $('#example tbody tr:eq(10)').text() ).toBe( 'Test' );
} );
dt.html( 'basic' );
var a1 = [];
var a2 = [];
it( 'Renderer is called with two arguments', function () {
var args;
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
startRender: function ( rows, group ) {
a1.push( rows );
a2.push( group );
args = arguments.length;
return group;
}
}
} );
expect( args ).toBe( 2 );
} );
it( 'Is called once for each group on the page', function () {
expect( a1.length ).toBe( 2 );
} );
it( 'First argument is an API instance', function () {
expect( a1[0] instanceof $.fn.dataTable.Api ).toBe( true );
} );
it( 'First argument has the rows for the group in it', function () {
expect( a1[0].count() ).toBe( 9 );
expect( a1[1].count() ).toBe( 1 );
} );
it( 'Second argument has the group name', function () {
expect( a2[0] ).toBe( 'Edinburgh' );
expect( a2[1] ).toBe( 'London' );
} );
dt.html( 'basic' );
it( 'Can return a jQuery object', function () {
var args;
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
startRender: function ( rows, group ) {
return $('<tr><td>Test jQuery</td></tr>');
}
}
} );
expect( $('#example tbody tr:eq(0)').text() ).toBe( 'Test jQuery' );
} );
dt.html( 'basic' );
it( 'Can return a node', function () {
var args;
table = $('#example').DataTable( {
order: [[2, 'asc']],
rowGroup: {
dataSrc: 2,
startRender: function ( rows, group ) {
return $('<tr><td>Test node</td></tr>')[0];
}
}
} );
expect( $('#example tbody tr:eq(0)').text() ).toBe( 'Test node' );
} );
} );

File diff suppressed because one or more lines are too long

View File

@@ -306,7 +306,7 @@
<div class="col-sm-10">
<div class="input-group" title="add/remove character">
<label for="{{characterSelectId}}"></label>
<select id="{{characterSelectId}}" name="mapCharacters[]" multiple="multiple">
<select id="{{characterSelectId}}" class="pf-select2" name="mapCharacters[]" multiple="multiple">
{{#accessCharacter}}
<option value="{{id}}" selected>{{name}}</option>
{{/accessCharacter}}
@@ -328,7 +328,7 @@
<div class="col-sm-9">
<div class="input-group" title="add/remove corporations">
<label for="{{corporationSelectId}}"></label>
<select id="{{corporationSelectId}}" name="mapCorporations[]" multiple="multiple">
<select id="{{corporationSelectId}}" class="pf-select2" name="mapCorporations[]" multiple="multiple">
{{#accessCorporation}}
<option value="{{id}}" selected="selected" >{{name}}</option>
{{/accessCorporation}}
@@ -350,7 +350,7 @@
<div class="col-sm-10">
<div class="input-group" title="add/remove alliances">
<label for="{{allianceSelectId}}"></label>
<select id="{{allianceSelectId}}" name="mapAlliances[]" multiple="multiple" >
<select id="{{allianceSelectId}}" class="pf-select2" name="mapAlliances[]" multiple="multiple" >
{{#accessAlliance}}
<option value="{{id}}" selected="selected" >{{name}}</option>
{{/accessAlliance}}

View File

@@ -0,0 +1,75 @@
<div id="{{id}}">
<form role="form" class="form-horizontal">
<div class="row">
<div class="col-xs-6 ">
<div class="form-group">
<label for="form_name" class="col-sm-2 control-label">Name</label>
<div class="col-sm-10">
<input name="name" type="text" class="form-control" id="form_name" value="{{structureData.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 class="col-xs-6">
<div class="form-group">
<label for="form_status" class="col-sm-4 control-label">Status</label>
<div class="col-sm-8">
<select name="statusId" id="form_status" class="form-control">
{{#structureStatus}}
<option value="{{id}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/structureStatus}}
</select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{typeSelectId}}">Type</label>
<div class="col-sm-10">
<div class="input-group">
<label for="{{typeSelectId}}"></label>
<select id="{{typeSelectId}}" class="pf-select2" name="structureId"></select>
<span class="note help-block with-errors"></span>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{corporationSelectId}}">Owner</label>
<div class="col-sm-10">
<div class="input-group">
<label for="{{corporationSelectId}}"></label>
<select id="{{corporationSelectId}}" class="pf-select2" name="corporationId">
{{#structureData.owner}}
<option value="{{id}}" selected="selected">{{name}}</option>
{{/structureData.owner}}
</select>
<span class="note help-block with-errors"></span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-8 col-sm-12">
<div class="form-group">
<label for="{{descriptionTextareaId}}" class="col-sm-1 control-label">Note
<kbd class="{{descriptionTextareaCharCounter}} txt-color text-right">&nbsp;</kbd>
</label>
<div class="col-sm-6">
<textarea id="{{descriptionTextareaId}}" name="description" class="form-control" maxlength="{{maxDescriptionLength}}" rows="3">{{structureData.description}}</textarea>
<div class="note help-block with-errors"></div>
</div>
</div>
</div>
</div>
</form>
</div>

View File

@@ -8,7 +8,7 @@
<div class="col-sm-10">
<div class="input-group">
<label for="form_system"></label>
<select id="form_system" name="systemId" class="{{selectClass}}" data-error="Choose a valid system" required>
<select id="form_system" name="systemId" class="pf-select2 {{selectClass}}" data-error="Choose a valid system" required>
{{#currentSystem}}
<option value="{{id}}">{{name}}</option>
{{/currentSystem}}

View File

@@ -215,6 +215,13 @@
}
}
// structure dialog ===========================================================
#pf-structure-dialog{
#pf-structure-dialog-corporation-select, #pf-structure-dialog-type-select{
width: 267px !important;
}
}
// jump info dialog ===========================================================
.pf-jump-info-dialog{
blockquote{

View File

@@ -30,6 +30,11 @@ input, select{
&[disabled]{
@include placeholder(transparent);
}
&.pf-select2{
height: 32px; // supposed to be select2 field (before initialization)
padding: 6px 12px;
}
}
textarea{
@@ -139,3 +144,9 @@ fieldset[disabled]{
box-shadow: none;
}
}
.pf-form-field-char-count{
display: block;
margin-top: 10px;
}

View File

@@ -209,7 +209,8 @@ select:active, select:hover {
.dataTable{
th{
&.pf-table-image-cell,
&.pf-table-image-small-cell{
&.pf-table-image-small-cell,
&.pf-table-image-smaller-cell{
// no padding for image content
padding-left: 0 !important;
padding-right: 0 !important;
@@ -223,8 +224,17 @@ select:active, select:hover {
}
}
tr.group{
background-color: rgba($gray-darker, 0.4);
}
// "special" column styles
td{
> .fa-circle{
font-size: 9px !important; // should not be "so big" (10px default)
}
&.pf-table-link-cell{
cursor: pointer;
@extend .txt-color;
@@ -254,7 +264,6 @@ select:active, select:hover {
}
&.pf-table-image-cell{
// no padding for image content
padding: 0 !important;
image-rendering: -webkit-optimize-contrast;
@@ -275,6 +284,16 @@ select:active, select:hover {
}
}
&.pf-table-image-smaller-cell{
padding: 0 !important;
img{
width: 25px; // smaller image (default 32)
// overwrite "default" border for image cells
border-left: 1px solid transparent;
border-right: 1px solid transparent;
}
}
&.pf-table-button-sm-cell{
padding: 0 5px;
}
@@ -955,6 +974,18 @@ table{
}
// structure status ===============================================================================
.pf-structure-status-unknown{
color: $teal-lighter;
}
.pf-structure-status-online{
color: $green;
}
.pf-structure-status-offline{
color: $red-darker;
}
// global tooltip settings ========================================================================
.tooltip-inner{

View File

@@ -28,11 +28,6 @@
}
}
}
.pf-form-field-char-count{
display: block;
margin-top: 10px;
}
}
}
@@ -154,6 +149,14 @@
}
}
// intel module ============================================================
.pf-system-intel-module{
.pf-system-structure-table{
font-size: 10px;
}
}
// killboard module ========================================================
.pf-system-killboard-module{

View File

@@ -24,6 +24,11 @@
border: 1px solid transparent;
overflow: hidden;
@include box-shadow(0 6px 12px rgba(0,0,0, 0.3));
img{
image-rendering: -webkit-optimize-contrast;
width: 21px;
}
}
.select2-dropdown--above {
@@ -34,12 +39,14 @@
border-top: none;
}
.select2-results > .select2-results__options {
.select2-results {
max-height: $results-max-height;
overflow-y: auto;
}
.select2-results__option {
padding: 3px 6px;
&[role=group] {
padding: 0;
}
@@ -71,7 +78,9 @@
.select2-results__group {
cursor: default;
display: block;
padding: 6px;
padding: 3px 6px;
background-color: $gray;
text-transform: capitalize;
}
&.select2-container--open .select2-dropdown {

View File

@@ -3,15 +3,18 @@
border: 1px solid $border-color;
border-radius: $border-radius;
outline: 0;
height: 32px;
padding: 6px 12px;
&:focus {
border: 1px solid $focus-border-color;
}
.select2-selection__rendered {
font-family: 'Oxygen Bold';
color: $gray-lighter;
line-height: 28px;
line-height: 1.5;
padding: 0 10px 0 0;
}
.select2-selection__clear {