diff --git a/app/app/cortex.php b/app/app/cortex.php deleted file mode 100644 index 6b5cabcb..00000000 --- a/app/app/cortex.php +++ /dev/null @@ -1,54 +0,0 @@ -set('AUTOLOAD', $f3->get('AUTOLOAD').';app/cortex/'); - $f3->set('QUIET', false); - - $dbs = array( - 'sql' => new \DB\SQL('mysql:host=localhost;port=3306;dbname=fatfree', 'fatfree', ''), -// 'sql-sqlite' => new \DB\SQL('sqlite:data/sqlite.db'), -// 'sql-pgsql' => new \DB\SQL('pgsql:host=localhost;dbname=fatfree', 'fatfree', 'fatfree'), - 'jig' => new \DB\Jig('data/'), - 'mongo' => new \DB\Mongo('mongodb://localhost:27017', 'testdb'), -// 'sqlsrv2012' => new \DB\SQL('sqlsrv:SERVER=LOCALHOST\SQLEXPRESS2012;Database=fatfree','fatfree', 'fatfree'), -// 'sqlsrv2008' => new \DB\SQL('sqlsrv:SERVER=LOCALHOST\SQLEXPRESS2008;Database=fatfree','fatfree', 'fatfree'), - ); - $results = array(); - - // Test Syntax - foreach ($dbs as $type => $db) { - $test = new \Test_Syntax(); - $results = array_merge((array) $results, (array) $test->run($db, $type)); - } - - // Test Relations - foreach ($dbs as $type => $db) { - $f3->set('DB',$db); - $test = new \Test_Relation(); - $results = array_merge((array) $results, (array) $test->run($db, $type)); - } - - // Test Filter - foreach ($dbs as $type => $db) { - $f3->set('DB',$db); - $test = new \Test_Filter(); - $results = array_merge((array) $results, (array) $test->run($db, $type)); - } - - // Further Common Tests - if (isset($dbs['sql'])) { - $test = new \Test_Common(); - $f3->set('DB', $dbs['sql']); - $results = array_merge((array) $results, (array) $test->run()); - } - $f3->set('results', $results); - } - - -} \ No newline at end of file diff --git a/app/app/cortex/authormodel.php b/app/app/cortex/authormodel.php deleted file mode 100644 index 0aa19264..00000000 --- a/app/app/cortex/authormodel.php +++ /dev/null @@ -1,27 +0,0 @@ - array( - 'type' => \DB\SQL\Schema::DT_VARCHAR256 - ), - 'mail' => array( - 'type' => \DB\SQL\Schema::DT_VARCHAR256 - ), - 'website' => array( - 'type' => \DB\SQL\Schema::DT_VARCHAR256 - ), - 'news' => array( - 'has-many' => array('\NewsModel','author'), - ), - 'profile' => array( - 'has-one' => array('\ProfileModel','author'), - ), - ), -// $primary = 'aid', - $table = 'author', - $db = 'DB'; - -} diff --git a/app/app/cortex/newsmodel.php b/app/app/cortex/newsmodel.php deleted file mode 100644 index 33b6ccff..00000000 --- a/app/app/cortex/newsmodel.php +++ /dev/null @@ -1,28 +0,0 @@ - array( - 'type' => \DB\SQL\Schema::DT_VARCHAR128 - ), - 'text' => array( - 'type' => \DB\SQL\Schema::DT_TEXT - ), - 'author' => array( - 'belongs-to-one' => '\AuthorModel', - ), - 'tags' => array( - 'belongs-to-many' => '\TagModel', - ), - 'tags2' => array( - 'has-many' => array('\TagModel','news','news_tags'), -// 'has-many' => array('\TagModel','news'), - ), - ), -// $primary='nid', - $table = 'news', - $db = 'DB'; - -} \ No newline at end of file diff --git a/app/app/cortex/profilemodel.php b/app/app/cortex/profilemodel.php deleted file mode 100644 index e749cf33..00000000 --- a/app/app/cortex/profilemodel.php +++ /dev/null @@ -1,21 +0,0 @@ - array( - 'type' => \DB\SQL\Schema::DT_TEXT - ), - 'image' => array( - 'type' => \DB\SQL\Schema::DT_VARCHAR256 - ), - 'author' => array( - 'belongs-to-one' => '\AuthorModel' - ) - ), -// $primary = 'profile_id', - $table = 'profile', - $db = 'DB'; - -} \ No newline at end of file diff --git a/app/app/cortex/tagmodel.php b/app/app/cortex/tagmodel.php deleted file mode 100644 index 668bd34e..00000000 --- a/app/app/cortex/tagmodel.php +++ /dev/null @@ -1,18 +0,0 @@ - array( - 'type' => \DB\SQL\Schema::DT_VARCHAR128 - ), - 'news' => array( - 'has-many' => array('\NewsModel','tags2','news_tags'), - ), - ), -// $primary = 'tid', - $table = 'tags', - $db = 'DB'; - -} \ No newline at end of file diff --git a/app/app/cortex/test_common.php b/app/app/cortex/test_common.php deleted file mode 100644 index bdcc7477..00000000 --- a/app/app/cortex/test_common.php +++ /dev/null @@ -1,125 +0,0 @@ -load(); - - $dummy = array( - 'title'=>'copy test', - 'text'=>'Lorem ipsum dolor sit amet.', - 'author'=>1, - 'tags'=>array(3) - ); - $f3->set('record1', $dummy); - $news->copyto('record2'); - - $test->expect( - $f3->exists('record2'), - 'copyto: raw record copied to hive' - ); - - $news->reset(); - - $news->copyfrom('record1'); - - $test->expect( - $news->title = 'copy test' && - $news->text = 'Lorem ipsum dolor sit amet.', - 'copyfrom: hydrate from hive key' - ); - $test->expect( - $news->author instanceof AuthorModel - && !$news->author->dry() && - $news->tags instanceof \DB\CortexCollection, - 'copyfrom: relations hydrated successful' - ); - - $test->expect( - $news->get('author',true) == 1, - 'get raw data from relational field' - ); - - $news->reset(); - $news->copyfrom('record2','title;author'); - - $test->expect( - $news->title = 'Responsive Images' && - $news->get('author',true) == 2 && - $news->text == NULL, - 'copyfrom: limit fields with split-able string' - ); - - $news->reset(); - $news->copyfrom('record2',array('title')); - - $test->expect( - $news->title = 'Responsive Images' && $news->text == NULL, - 'copyfrom: limit fields by array' - ); - - $news->reset(); - $news->copyfrom($dummy,function($fields) { - return array_intersect_key($fields,array_flip(array('title'))); - }); - - $test->expect( - $news->title = 'copy test', - 'copyfrom: copy from array instead of hive key' - ); - - $test->expect( - $news->title = 'copy test' && $news->text == NULL, - 'copyfrom: limit fields by callback function' - ); - - $all = $news->find(); - $allTitle = $all->getAll('title'); - - $test->expect( - count($allTitle) == 3 && - $allTitle[0] == 'Responsive Images' && - $allTitle[1] == 'CSS3 Showcase' && - $allTitle[2] == 'Touchable Interfaces', - 'collection getAll returns all values of selected field' - ); - - $newsByID = $all->getBy('_id'); - $test->expect( - array_keys($newsByID) == array(1,2,3), - 'collection getBy sorts by given field' - ); - - $newsByAuthorID = $all->getBy('author',true); - $test->expect( - array_keys($newsByAuthorID) == array(2, 1) && - count($newsByAuthorID[2]) == 2 && - count($newsByAuthorID[1]) == 1, - 'collection getBy nested sort by author' - ); - - $allTitle = array(); - foreach($all as $record) - $allTitle[] = $record->title; - - $test->expect( - count($allTitle) == 3 && - $allTitle[0] == 'Responsive Images' && - $allTitle[1] == 'CSS3 Showcase' && - $allTitle[2] == 'Touchable Interfaces', - 'collection is traversable' - ); - - - /////////////////////////////////// - return $test->results(); - } -} \ No newline at end of file diff --git a/app/app/cortex/test_filter.php b/app/app/cortex/test_filter.php deleted file mode 100644 index 9047cfea..00000000 --- a/app/app/cortex/test_filter.php +++ /dev/null @@ -1,302 +0,0 @@ -find()->getAll('_id'); - $all = $news->find(); - $newsIDs = $all->getAll('_id'); - $profileIDs = $profile->find()->getAll('_id'); - $tagIDs = $tag->find()->getAll('_id'); - - // add another relation - $news->load(array('title = ?','CSS3 Showcase')); - $news->author = $author->load(array($author_pk.' = ?',$authorIDs[0])); - $news->save(); - $news->reset(); - $author->reset(); - - - // has-filter on belongs-to relation - /////////////////////////////////// - - $result = $author->has('news', array('title like ?', '%Image%'))->afind(); - - $test->expect( - count($result) == 1 && - $result[0]['name'] == 'Johnny English', - $type.': has filter on many-to-one field' - ); - $test->expect( - count($result[0]['news']) == 2 && - $result[0]['news'][0]['title'] == 'Responsive Images' && - $result[0]['news'][1]['title'] == 'CSS3 Showcase', - $type.': has filter does not prune relation set' - ); - - $result = $news->has('author', array('name = ?', 'Johnny English'))->afind(); - $test->expect( - count($result) == 2 && // has 2 news - $result[0]['title'] == 'Responsive Images' && - $result[1]['title'] == 'CSS3 Showcase', - $type.': has filter on one-to-many field' - ); - - // add another profile - $profile->message = 'Beam me up, Scotty!'; - $profile->author = $authorIDs[2]; - $profile->save(); - $profile->reset(); - - $result = $author->has('profile',array('message LIKE ?','%Scotty%'))->afind(); - $test->expect( - count($result) == 1 && - $result[0]['name'] == 'James T. Kirk' && - $result[0]['profile']['message'] == 'Beam me up, Scotty!', - $type.': has filter on one-to-one field' - ); - - $result = $profile->has('author',array('name LIKE ?','%Kirk%'))->afind(); - $test->expect( - count($result) == 1 && - $result[0]['message'] == 'Beam me up, Scotty!' && - $result[0]['author']['name'] == 'James T. Kirk', - $type.': has filter on one-to-one field, inverse' - ); - - // add mm tags - $news->load(array('title = ?','Responsive Images')); - $news->tags2 = array($tagIDs[0],$tagIDs[1]); - $news->save(); - $news->load(array('title = ?','CSS3 Showcase')); - $news->tags2 = array($tagIDs[1],$tagIDs[2]); - $news->save(); - $news->reset(); - - $result = $news->has('tags2',array('title like ?','%Design%'))->find(); - $test->expect( - count($result) == 1 && - $result[0]['title'] == 'Responsive Images', - $type.': has filter on many-to-many field' - ); - - $result = $news->has('tags2',array('title = ?','Responsive'))->find(); - $test->expect( - count($result) == 2 && - $result[0]['title'] == 'Responsive Images' && - $result[1]['title'] == 'CSS3 Showcase', - $type.': has filter on many-to-many field, additional test' - ); - - - $result = $tag->has('news',array('title = ?','Responsive Images'))->find(); - $test->expect( - count($result) == 2 && - $result[0]['title'] == 'Web Design' && - $result[1]['title'] == 'Responsive', - $type.': has filter on many-to-many field, inverse' - ); - - // add another tag - $news->load(array('title = ?', 'Touchable Interfaces')); - $news->tags2 = array($tagIDs[1]); - $news->save(); - $news->reset(); - - $tag->has('news',array('text LIKE ? and title LIKE ?', '%Lorem%', '%Interface%')); - $result = $tag->find(); - $test->expect( - count($result) == 1 && - $result[0]['title'] == 'Responsive', - $type.': has filter with multiple conditions' - ); - - $news->has('tags2', array('title = ? OR title = ?', 'Usability', 'Web Design')); - $result = $news->afind(array('text = ?', 'Lorem Ipsun')); - $test->expect( - count($result) == 1 && - $result[0]['title'] == 'Responsive Images', - $type.': find with condition and has filter' - ); - - $news->load(array('title = ?', 'Responsive Images')); - $news->author = $authorIDs[1]; - $news->save(); - $news->reset(); - - - $news->has('tags2', array('title = ? OR title = ?', 'Usability', 'Web Design')); - $news->has('author', array('name = ?', 'Ridley Scott')); - $result = $news->afind(); - $test->expect( - count($result) == 1 && - $result[0]['title'] == 'Responsive Images', - $type.': find with multiple has filters on different relations' - ); - - // add another news to author 2 - $news->load(array($news_pk.' = ?',$newsIDs[2])); - $news->author = $authorIDs[1]; - $news->save(); - - $news->reset(); - $news->has('author', array('name = ?', 'Ridley Scott')); - $news->load(); - $res = array(); - while (!$news->dry()) { - $res[] = $news->title; - $news->next(); - } - - $test->expect( - count($res) == 2 && - $res[0] == 'Responsive Images' && - $res[1] == 'Touchable Interfaces' - , - $type.': has filter in load context' - ); - - $news->reset(); - $news->fields(array('title')); - $news->load(); - - $test->expect( - !empty($news->title) && - empty($news->author) && - empty($news->text) && - empty($news->tags) && - empty($news->tags2), - $type.': use a whitelist to restrict fields' - ); - - unset($news); - $news = new \NewsModel(); - - $news->fields(array('title','tags','tags2','author'),true); - $news->load(); - - $test->expect( - empty($news->title) && - empty($news->author) && - !empty($news->text) && - empty($news->tags) && - empty($news->tags2), - $type.': use a blacklist to restrict fields' - ); - - unset($news); - $news = new \NewsModel(); - - $news->fields(array('tags.title')); - $news->load(); - - $test->expect( - !empty($news->tags[0]->title) && - empty($news->tags[0]->news), - $type.': set restricted fields to related mappers' - ); - - $news->filter('tags2',null,array('order'=>'title ASC')); - $news->load(array('title = ?','Responsive Images')); - $test->expect( - $news->tags2[0]->title == 'Responsive' && - $news->tags2[1]->title == 'Web Design', - $type.': filter with sorting of related records' - ); - - // get all tags sorted by their usage in news articles - $tag->reset(); - $tag->countRel('news'); - $result = $tag->find(null,array('order'=>'count_news DESC, title'))->castAll(0); - - $test->expect( - $result[0]['title'] == 'Responsive' && - $result[0]['count_news'] == 3 && - $result[1]['title'] == 'Usability' && - $result[1]['count_news'] == 1 && - $result[2]['title'] == 'Web Design' && - $result[2]['count_news'] == 1, - $type.': count and sort on many-to-many relation' - ); - - // get all authors sorted by the amount of news they have written - $author->reset(); - $author->countRel('news'); - $result = $author->find(null,array('order'=>'count_news DESC'))->castAll(0); - - $test->expect( - $result[0]['name'] == 'Ridley Scott' && - $result[0]['count_news'] == 2 && - $result[1]['name'] == 'Johnny English' && - $result[1]['count_news'] == 1 && - $result[2]['name'] == 'James T. Kirk' && - $result[2]['count_news'] == null, - $type.': count and sort on one-to-many relation' - ); - - $tag->reset(); - $tag->countRel('news'); - $result = $tag->find(null,array('order'=>'count_news DESC, title DESC','limit'=>1,'offset'=>1))->castAll(0); - - $test->expect( - $result[0]['title'] == 'Web Design' && - $result[0]['count_news'] == 1, - $type.': apply limit and offset on aggregated collection' - ); - - - $author->reset(); - $author->countRel('news'); - $author->has('news',array('text like ?','%Lorem%')); - $result = $author->find()->castAll(0); - - $test->expect( - count($result) == 1 && - $result[0]['name'] == 'Ridley Scott' && - $result[0]['count_news'] == 2 , - $type.': has-filter and 1:M relation counter' - ); - - - $author->reset(); - $id = $author->load()->next()->_id; - $tag->reset(); - $tag->countRel('news'); - $tag->has('news',array('author = ?',$id)); - $result = $tag->find(null,array('order'=>'count_news desc'))->castAll(0); - - $test->expect( - count($result) == 2 && - $result[0]['title'] == 'Responsive' && - $result[0]['count_news'] == 3 && - $result[1]['title'] == 'Web Design' && - $result[1]['count_news'] == 1, - $type.': has-filter and M:M relation counter' - ); - - /////////////////////////////////// - return $test->results(); - } - -} \ No newline at end of file diff --git a/app/app/cortex/test_relation.php b/app/app/cortex/test_relation.php deleted file mode 100644 index 08d319ab..00000000 --- a/app/app/cortex/test_relation.php +++ /dev/null @@ -1,335 +0,0 @@ -cast(); - unset($row['_id']); - unset($row['id']); - unset($row['aid']); - unset($row['uid']); - unset($row['nid']); - unset($row['tid']); - unset($row['pid']); - unset($row['profile_id']); - foreach ($row as $col => $val) { - if (empty($val) || is_null($val)) - unset($row[$col]); - } - $out[] = $row; - } - return $out; - } - - function run($db,$type) - { - $test = new \Test(); - - // clear existing data - \AuthorModel::setdown(); - \TagModel::setdown(); - \NewsModel::setdown(); - \ProfileModel::setdown(); - - // setup models - \AuthorModel::setup(); - \TagModel::setup(); - \NewsModel::setup(); - \ProfileModel::setup(); - - // setup Author - /////////////////////////////////// - $author_id = array(); - - $author = new \AuthorModel(); - $ac=$author::resolveConfiguration(); - $author_pk = (is_int(strpos($type,'sql'))?$ac['primary']:'_id'); - - $author->name = 'Johnny English'; - $author->save(); - $author_id[] = $author->_id; - $author->reset(); - $author->name = 'Ridley Scott'; - $author->save(); - $author_id[] = $author->_id; - $author->reset(); - $author->name = 'James T. Kirk'; - $author->save(); - $author_id[] = $author->_id; - $author->reset(); - - $allauthors = $author->find()->castAll(); - $allauthors = $this->getResult($allauthors); - $test->expect( - json_encode($allauthors) == - '[{"name":"Johnny English"},{"name":"Ridley Scott"},{"name":"James T. Kirk"}]', - $type.': all AuthorModel items created' - ); - - // setup Tags - /////////////////////////////////// - $tag_id = array(); - - $tag = new \TagModel(); - $tc=$tag::resolveConfiguration(); - $tag_pk = (is_int(strpos($type,'sql'))?$tc['primary']:'_id'); - - $tag->title = 'Web Design'; - $tag->save(); - $tag_id[] = $tag->_id; - $tag->reset(); - $tag->title = 'Responsive'; - $tag->save(); - $tag_id[] = $tag->_id; - $tag->reset(); - $tag->title = 'Usability'; - $tag->save(); - $tag_id[] = $tag->_id; - $tag->reset(); - - $allTags = $this->getResult($tag->find()); - $test->expect( - json_encode($allTags) == - '[{"title":"Web Design"},{"title":"Responsive"},{"title":"Usability"}]', - $type.': all TagModel items created' - ); - - // setup News - /////////////////////////////////// - $news_id = array(); - - $news = new \NewsModel(); - $nc=$news::resolveConfiguration(); - $news_pk = (is_int(strpos($type,'sql'))?$nc['primary']:'_id'); - - $news->title = 'Responsive Images'; - $news->text = 'Lorem Ipsun'; - $news->save(); - $news_id[] = $news->_id; - $news->reset(); - $news->title = 'CSS3 Showcase'; - $news->text = 'News Text 2'; - $news->save(); - $news_id[] = $news->_id; - $news->reset(); - $news->title = 'Touchable Interfaces'; - $news->text = 'Lorem Foo'; - $news->save(); - $news_id[] = $news->_id; - $news->reset(); - - $allnews = $this->getResult($news->find()); - $test->expect( - json_encode($allnews) == - '[{"title":"Responsive Images","text":"Lorem Ipsun"},{"title":"CSS3 Showcase","text":"News Text 2"},{"title":"Touchable Interfaces","text":"Lorem Foo"}]', - $type.': all NewsModel items created' - ); - - // belongs-to author relation - /////////////////////////////////// - - $author->load(); - $news->load(array($news_pk.' = ?',$news_id[0])); - $news->author = $author; - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[0])); - $test->expect( - $news->author->name == 'Johnny English', - $type.': belongs-to-one: author relation created' - ); - - $news->author = NULL; - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[0])); - $test->expect( - empty($news->author), - $type.': belongs-to-one: author relation released' - ); - - $news->author = $author->_id; - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[0])); - $test->expect( - $news->author->name == 'Johnny English', - $type.': belongs-to-one: relation created by raw id' - ); - - // belongs-to-many tag relation - /////////////////////////////////// - - $tag1 = new \TagModel(); - $tag1->load(array($tag_pk.' = ?', $tag_id[0])); - $tag2 = new \TagModel(); - $tag2->load(array($tag_pk.' = ?', $tag_id[1])); - $news->tags = array($tag1,$tag2); - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[0])); - $test->expect( - $news->tags[0]->title == 'Web Design' && $news->tags[1]->title == 'Responsive', - $type.': belongs-to-many: relations created with array of mapper objects' - ); - - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[1])); - $news->tags = array($tag_id[1],$tag_id[2]); - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[1])); - $test->expect( - $news->tags[0]->title == 'Responsive' && $news->tags[1]->title == 'Usability', - $type.': belongs-to-many: relations created with array of IDs' - ); - - $news->tags = null; - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[1])); - $test->expect( - empty($news->tags), - $type.': belongs-to-many: relations released' - ); - - $tag->reset(); - $news->load(array($news_pk.' = ?', $news_id[1])); - $news->tags = $tag->load(array($tag_pk.' != ?',$tag_id[0])); - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[1])); - $test->expect( - $news->tags[0]->title == 'Responsive' && $news->tags[1]->title == 'Usability', - $type.': belongs-to-many: relations created with hydrated mapper' - ); - - - $news->reset(); - $tag->reset(); - $news->load(array($news_pk.' = ?', $news_id[2])); - $news->tags = $tag_id[0].';'.$tag_id[2]; - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[2])); - $test->expect( - $news->tags[0]->title == 'Web Design' && $news->tags[1]->title == 'Usability', - $type.': belongs-to-many: relations created with split-able string' - ); - $test->expect( - is_object($news->tags) && $news->tags instanceof \DB\CortexCollection, - $type.': belongs-to-many: result is collection' - ); - - - // has-one relation - /////////////////////////////////// - $profile = new ProfileModel(); - $pc=$profile::resolveConfiguration(); - $profile_pk = (is_int(strpos($type,'sql'))?$pc['primary']:'_id'); - - $profile->message = 'Hello World'; - $profile->author = $author->load(array($author_pk.' = ?',$author_id[0])); - $profile->save(); - $profile_id = $profile->_id; - $profile->reset(); - $author->reset(); - $author->load(array($author_pk.' = ?', $author_id[0])); - $profile->load(array($profile_pk.' = ?', $profile_id)); - $test->expect( - $author->profile->message == 'Hello World' && - $profile->author->name == "Johnny English", - $type.': has-one: relation assigned' - ); - - $profile->reset(); - $profile->message = 'I\'m feeling lucky'; - $profile->image = 'lolcat.jpg'; - $author->reset(); - $author->load(array($author_pk.' = ?',$author_id[1])); - $author->profile = $profile; - $author->save(); - $profile->reset(); - $author->reset(); - $author->load(array($author_pk.' = ?', $author_id[1])); - $test->expect( - $author->profile->message == 'I\'m feeling lucky', - $type.': has-one: inverse relation' - ); - - - // has-many relation - /////////////////////////////////// - - $author->load(array($author_pk.' = ?', $author_id[0])); - $result = $this->getResult($author->news); - $test->expect( - $result[0]['title'] == "Responsive Images" && - $result[0]['tags'][0]['title'] == 'Web Design' && - $result[0]['tags'][1]['title'] == 'Responsive', - $type.': has-many inverse relation' - ); - - // many to many relation - /////////////////////////////////// - - $news->load(array($news_pk.' = ?',$news_id[0])); - $news->tags2 = array($tag_id[0],$tag_id[1]); - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?',$news_id[0])); - $test->expect( - $news->tags2[0]['title'] == 'Web Design' && - $news->tags2[1]['title'] == 'Responsive', - $type.': many-to-many relation created' - ); - - $test->expect( - is_object($news->tags2) && $news->tags2 instanceof \DB\CortexCollection, - $type.': many-to-many: result is collection' - ); - - $news->load(array($news_pk.' = ?', $news_id[0])); - $news->tags2 = NULL; - $news->save(); - $news->reset(); - $news->load(array($news_pk.' = ?', $news_id[0])); - $test->expect( - is_null($news->tags2), - $type.': many-to-many relation released' - ); - - $all = $news->find(); - $test->expect( - $all[1]->tags2 === NULL - && $all[2]->author === NULL, - $type.': empty relations are NULL' - ); - - $arr = $news->cast(); - $test->expect( - is_array($arr['tags']), - $type.': collection becomes array in casted model' - ); - - if ($type == 'mongo') { - $test->expect( - is_string($arr['_id']), - $type.': id becomes string in casted model' - ); - } - - /////////////////////////////////// - return $test->results(); - } - -} \ No newline at end of file diff --git a/app/app/cortex/test_syntax.php b/app/app/cortex/test_syntax.php deleted file mode 100644 index 9efa7e28..00000000 --- a/app/app/cortex/test_syntax.php +++ /dev/null @@ -1,364 +0,0 @@ - array('type' => \DB\SQL\Schema::DT_TEXT), - 'num1' => array('type' => \DB\SQL\Schema::DT_INT4), - 'num2' => array('type' => \DB\SQL\Schema::DT_INT4), - ); - \DB\Cortex::setup($db, $tname, $fields); - - // adding some testing data - $cx = new \DB\Cortex($db, $tname); - $cx->title = 'bar1'; - $cx->save(); - $cx->reset(); - - $cx->title = 'baz2'; - $cx->num1 = 1; - $cx->save(); - $cx->reset(); - - $cx->title = 'foo3'; - $cx->num1 = 4; - $cx->save(); - $cx->reset(); - - $cx->title = 'foo4'; - $cx->num1 = 3; - $cx->save(); - $cx->reset(); - - $cx->title = 'foo5'; - $cx->num1 = 3; - $cx->num2 = 5; - $cx->save(); - $cx->reset(); - - $cx->title = 'foo6'; - $cx->num1 = 3; - $cx->num2 = 1; - $cx->save(); - $cx->reset(); - - $cx->title = 'foo7'; - $cx->num1 = 3; - $cx->num2 = 10; - $cx->save(); - $cx->reset(); - - $cx->title = 'foo8'; - $cx->num1 = 5; - $cx->save(); - $cx->reset(); - - $cx->title = 'foo9'; - $cx->num1 = 8; - $cx->save(); - $cx->reset(); - - $result = $this->getResult($cx->find()); - - $expected = array( - 0 => array( - 'title' => 'bar1', - ), - 1 => array( - 'num1' => 1, - 'title' => 'baz2', - ), - 2 => array( - 'num1' => 4, - 'title' => 'foo3', - ), - 3 => array( - 'num1' => 3, - 'title' => 'foo4', - ), - 4 => array( - 'num1' => 3, - 'num2' => 5, - 'title' => 'foo5', - ), - 5 => array( - 'num1' => 3, - 'num2' => 1, - 'title' => 'foo6', - ), - 6 => array( - 'num1' => 3, - 'num2' => 10, - 'title' => 'foo7', - ), - 7 => array( - 'num1' => 5, - 'title' => 'foo8', - ), - 8 => array( - 'num1' => 8, - 'title' => 'foo9', - ), - ); - - $test->expect( - json_encode($result) == json_encode($expected), - $type.': init mapper, adding records' - ); - - // operator = - $result = $this->getResult($cx->find(array('title = ?', 'foo7'))); - $expected = array( - 0 => array( - 'num1' => 3, - 'num2' => 10, - 'title' => 'foo7', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': operator check: =' - ); - - // operator > - $result = $this->getResult($cx->find(array('num1 > ?', 4))); - $expected = array( - 0 => array( - 'num1' => 5, - 'title' => 'foo8', - ), - 1 => array( - 'num1' => 8, - 'title' => 'foo9', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': operator check: >' - ); - - // operator >= - $result = $this->getResult($cx->find(array('num1 >= ?', 5))); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': operator check: >=' - ); - - // operator < - $result = $this->getResult($cx->find(array('num2 < ?', 2))); - $expected = array( - 0 => array( - 'num1' => 3, - 'num2' => 1, - 'title' => 'foo6', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': operator check: <' - ); - - // operator <= - $result = $this->getResult($cx->find(array('num2 <= ?', 1))); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': operator check: <=' - ); - - // operator without binding - $result = $this->getResult($cx->find(array('num1 > 4'))); - $expected = array( - 0 => array( - 'num1' => 5, - 'title' => 'foo8', - ), - 1 => array( - 'num1' => 8, - 'title' => 'foo9', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': operator without binding' - ); - - // field comparision - $result = $this->getResult($cx->find( - array('num2 > num1', 1))); - $expected = array( - 0 => array( - 'num1' => 3, - 'num2' => 5, - 'title' => 'foo5', - ), - 1 => array( - 'num1' => 3, - 'num2' => 10, - 'title' => 'foo7', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': check field comparision' - ); - - // lookahead search - $result = $this->getResult($cx->find(array('title like ?', '%o6'))); - $expected = array( - 0 => array( - 'num1' => 3, - 'num2' => 1, - 'title' => 'foo6', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': lookahead search' - ); - - // lookbehind search - $result = $this->getResult($cx->find(array('title like ?', 'bar%'))); - $expected = array( - 0 => array( - 'title' => 'bar1', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': lookbehind search' - ); - - // full search - $result = $this->getResult($cx->find(array('title like ?', '%a%'))); - $expected = array( - 0 => array( - 'title' => 'bar1', - ), - 1 => array( - 'num1' => 1, - 'title' => 'baz2', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': full search' - ); - - // negated search - $result = $this->getResult($cx->find(array('title not like ?', 'foo%'))); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': negated search' - ); - - // AND / OR chaining - $result = $this->getResult($cx->find( - array('(num2 < ? AND num1 > ?) OR title like ?', 2, 1, '%o9'))); - $expected = array( - 0 => array( - 'num1' => 3, - 'num2' => 1, - 'title' => 'foo6', - ), - 1 => array( - 'num1' => 8, - 'title' => 'foo9', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': check logical operator chaining' - ); - - // check limit - $result = $this->getResult($cx->find( - null, array('limit' => '2'))); - $expected = array( - 0 => array( - 'title' => 'bar1', - ), - 1 => array( - 'num1' => 1, - 'title' => 'baz2', - ), - ); - $test->expect( - json_encode($result) == json_encode($expected), - $type.': check limit' - ); - - // check order - $result = $this->getResult($cx->find( - array('num2 >= ?', 1), array('order' => 'num2 desc'))); - $expected = array( - 0 => array( - 'num1' => 3, - 'num2' => 10, - 'title' => 'foo7', - ), - 1 => array( - 'num1' => 3, - 'num2' => 5, - 'title' => 'foo5', - ), - 2 => array( - 'num1' => 3, - 'num2' => 1, - 'title' => 'foo6', - ), - ); - - $test->expect( - json_encode($result) == json_encode($expected), - $type.': check order' - ); - - // IN search - $rc = $cx->find(array('num1 IN ?',array(4,5,8))); - $result = $rc->getAll('title'); - sort($result); - $test->expect( - json_encode($result) == json_encode(array('foo3','foo8','foo9')), - $type.': IN operator' - ); - - $rc = $cx->find(array('num1 IN ? && num2 > ? && num2 NOT IN ?',array(3,4),1,array(10))); - $result = $rc->getAll('title'); - $test->expect( - json_encode($result) == json_encode(array('foo5')), - $type.': enhanced IN, NOT IN operator' - ); - - /////////////////////////////////// - return $test->results(); - } - - /** - * unify results for better comparison - */ - private function getResult($result) - { - $out = array(); - foreach ($result as $row) { - $row = $row->cast(); - unset($row['_id']); - unset($row['id']); - ksort($row); - foreach ($row as $col => $val) { - if (empty($val) || is_null($val)) - unset($row[$col]); - } - $out[] = $row; - } - return $out; - } -} \ No newline at end of file diff --git a/app/app/schema.php b/app/app/schema.php deleted file mode 100644 index 1aaee30f..00000000 --- a/app/app/schema.php +++ /dev/null @@ -1,668 +0,0 @@ -f3->get('timer') - $this->roundTime; - $this->roundTime = microtime(TRUE) - $this->f3->get('timer'); - return ' [ '.sprintf('%.3f', $time).'s ]'; - } - - private function getTestDesc($text) - { - return $this->getTime().' '.$this->current_engine.': #'.$this->current_test++.' - '.$text; - } - - function get() - { - $this->f3 = \Base::instance(); - $this->test = new \Test; - - $this->f3->set('QUIET', false); - $this->f3->set('CACHE', false); - - $dbs = array( - 'mysql' => new \DB\SQL( - 'mysql:host=localhost;port=3306;dbname=fatfree', 'fatfree', '' - ), - 'sqlite' => new \DB\SQL( - 'sqlite::memory:' -// 'sqlite:db/sqlite.db' - ), - 'pgsql' => new \DB\SQL( - 'pgsql:host=localhost;dbname=fatfree', 'fatfree', 'fatfree' - ), -// 'sqlsrv2012' => new \DB\SQL( -// 'sqlsrv:SERVER=LOCALHOST\SQLEXPRESS2012;Database=fatfree','fatfree', 'fatfree' -// ), -// 'sqlsrv2008' => new \DB\SQL( -// 'sqlsrv:SERVER=LOCALHOST\SQLEXPRESS2008;Database=fatfree','fatfree', 'fatfree' -// ) - ); - - $this->roundTime = microtime(TRUE) - \Base::instance()->get('timer'); - $this->tname = 'test_table'; - - foreach ($dbs as $type => $db) { - $this->current_engine = $type; - $this->runTestSuite($db); - $this->current_test = 1; - } - $this->f3->set('results', $this->test->results()); - } - - private function runTestSuite($db) - { - $schema = new \DB\SQL\Schema($db); - - $schema->dropTable($this->tname); - - // create table - $table = $schema->createTable($this->tname); - $table = $table->build(); - $result = $schema->getTables(); - $this->test->expect( - in_array($this->tname, $result), - $this->getTestDesc('create default table') - ); - unset($result); - - $this->test->expect( - $table instanceof \DB\SQL\TableModifier, - $this->getTestDesc('$table->build() returns TableModifier') - ); - - // drop table - $table->drop(); - $this->test->expect( - in_array($this->tname, $schema->getTables()) == false, - $this->getTestDesc('drop table') - ); - unset($table); - - // create table with columns - $table = $schema->createTable($this->tname); - $table->addColumn('title')->type($schema::DT_VARCHAR128); - $table->addColumn('number')->type($schema::DT_INT4); - $table = $table->build(); - $r1 = $schema->getTables(); - $r2 = $table->getCols(); - $this->test->expect( - in_array($this->tname, $r1) && in_array('id', $r2) - && in_array('title', $r2) && in_array('number', $r2), - $this->getTestDesc('create new table with additional columns') - ); - unset($r1,$r2); - - // testing all datatypes - foreach (array_keys($schema->dataTypes) as $index => $field) { - // testing column type - $table->addColumn('column_'.$index)->type($field); - $table->build(); - $r1 = $table->getCols(); - $this->test->expect( - in_array('column_'.$index, $r1), - $this->getTestDesc('adding column ['.$field.'], nullable') - ); - } - - $r1 = $table->getCols(true); - foreach (array_keys($schema->dataTypes) as $index => $field) { - if (isset($r1['column_'.$index])) { - $datType=$schema->findQuery($schema->dataTypes[$field]); - $compatible = $schema->isCompatible($field,$r1['column_'.$index]['type']); - $this->test->expect( - $compatible, - $this->getTestDesc('reverse lookup compatible: '. - ($compatible?'YES':'NO'). - ', '.$field.': ['.$datType.' > '.$r1['column_'.$index]['type'].']') - ); - } - } - unset($r1); - - - // adding some testing data - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->column_5 = 123.456; - $mapper->column_6 = 123456.789012; - $mapper->column_7 = 'hello world'; - $mapper->save(); - $mapper->reset(); - $result = $mapper->findone(array('column_7 = ?', 'hello world'))->cast(); - unset($mapper); - $this->test->expect( - $result['column_7'] == 'hello world', - $this->getTestDesc('mapping dummy data') - ); - $this->test->expect( - $result['column_5'] == 123.456, - $this->getTestDesc('testing float value: '.$result['column_5']) - ); - $this->test->expect( - $result['column_6'] == 123456.789012, - $this->getTestDesc('testing decimal value: '.$result['column_6']) - ); - - - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->load(); - $num = $this->current_engine == 'sqlite' ? '123456789.012345' : '123456789012.345678'; - $mapper->column_6 = $num; - $mapper->save(); - $mapper->reset(); - $result = $mapper->findone(array('column_7 = ?', 'hello world'))->cast(); - $this->test->expect( - $result['column_6'] == $num, - $this->getTestDesc('testing max decimal precision: '.$result['column_6']) - ); - unset($mapper); - - // default value text, not nullable - $table->addColumn('text_default_not_null') - ->type($schema::DT_VARCHAR128) - ->nullable(false)->defaults('foo bar'); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - in_array('text_default_not_null', array_keys($r1)) && - $r1['text_default_not_null']['default'] == 'foo bar' && - $r1['text_default_not_null']['nullable'] == false, - $this->getTestDesc('adding column [VARCHAR128], not nullable with default value') - ); - unset($r1); - - // some testing dummy data - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->column_7 = 'tanduay'; - $mapper->save(); - $mapper->reset(); - $result = $mapper->findone(array('column_7 = ?','tanduay'))->cast(); - $this->test->expect( - $result['column_7'] == 'tanduay' && - $result['text_default_not_null'] == 'foo bar', - $this->getTestDesc('mapping dummy data') - ); - unset($mapper,$result); - - // default value numeric, not nullable - $table->addColumn('int_default_not_null') - ->type($schema::DT_INT4)->nullable(false)->defaults(123); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - in_array('int_default_not_null', array_keys($r1)) && - $r1['int_default_not_null']['default'] == 123 && - $r1['int_default_not_null']['nullable'] == false, - $this->getTestDesc('adding column [INT4], not nullable with default value') - ); - unset($r1); - - // adding testing data - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->column_7 = 'test3'; - $mapper->save(); - $mapper->reset(); - $r1 = $mapper->findone(array('column_7 = ?','test3'))->cast(); - $this->test->expect( - $r1['column_7'] == 'test3' && - $r1['int_default_not_null'] == 123, - $this->getTestDesc('mapping dummy data') - ); - unset($mapper,$r1); - - - // default value text, nullable - $table->addColumn('text_default_nullable') - ->type($schema::DT_VARCHAR128) - ->defaults('foo bar'); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - in_array('text_default_nullable', array_keys($r1)) && - $r1['text_default_nullable']['default'] == 'foo bar', - $this->getTestDesc('adding column [VARCHAR128], nullable with default value') - ); - unset($r1); - - // adding some dummy data - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->column_7 = 'test4'; - $mapper->save(); - $mapper->reset(); - $mapper->column_7 = 'test5'; - $mapper->text_default_nullable = null; - $mapper->save(); - $mapper->reset(); - $result = $mapper->find(array('column_7 = ? OR column_7 = ?','test4','test5')); - foreach ($result as &$r) - $r = $r->cast(); - - $this->test->expect( - array_key_exists(0, $result) && array_key_exists(1, $result) && - $result[0]['column_7'] == 'test4' && $result[0]['text_default_nullable'] == 'foo bar' && - $result[1]['column_7'] == 'test5' && $result[1]['text_default_nullable'] === null, - $this->getTestDesc('mapping dummy data') - ); - unset($mapper, $result); - - // default value numeric, nullable - $table->addColumn('int_default_nullable')->type($schema::DT_INT4)->defaults(123); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - in_array('int_default_nullable', array_keys($r1)) == true && - $r1['int_default_nullable']['default'] == 123, - $this->getTestDesc('adding column [INT4], nullable with default value') - ); - unset($r1); - - // adding dummy data - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->column_7 = 'test6'; - $mapper->save(); - $mapper->reset(); - $mapper->column_7 = 'test7'; - $mapper->int_default_nullable = null; - $mapper->save(); - $mapper->reset(); - $result = $mapper->find(array('column_7 = ? OR column_7 = ?', 'test6', 'test7')); - foreach ($result as &$r) - $r = $r->cast(); - - $this->test->expect( - array_key_exists(0, $result) && array_key_exists(1, $result) && - $result[0]['column_7'] == 'test6' && $result[0]['int_default_nullable'] === 123 && - $result[1]['column_7'] == 'test7' && $result[1]['int_default_nullable'] === null, - $this->getTestDesc('mapping dummy data') - ); - unset($mapper, $result); - - // current timestamp - $table->addColumn('stamp') - ->type($schema::DT_TIMESTAMP) - ->nullable(false) - ->defaults($schema::DF_CURRENT_TIMESTAMP); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - in_array('stamp', array_keys($r1)) && - $r1['stamp']['default'] == $schema::DF_CURRENT_TIMESTAMP, - $this->getTestDesc( - 'adding column [TIMESTAMP], not nullable with current_timestamp default value') - ); - unset($r1); - - - // datetime nullable - $table->addColumn('datetime')->type_datetime()->nullable(true); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - in_array('datetime', array_keys($r1)) && - $r1['datetime']['nullable'] == true, - $this->getTestDesc( - 'adding column [DATETIME], nullable, no default') - ); - unset($r1); - - - // adding dummy data - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->column_7 = 'test_datetime'; - $mapper->datetime = NULL; - $mapper->save(); - $mapper->reset(); - $result = $mapper->find(array('column_7 = ?', 'test_datetime')); - foreach ($result as &$r) - $r = $r->cast(); - - $this->test->expect( - array_key_exists(0, $result) && $result[0]['column_7'] == 'test_datetime' && - $result[0]['datetime'] === null, - $this->getTestDesc('mapping dummy data') - ); - unset($mapper, $result); - - - // rename column - $table->renameColumn('text_default_not_null', 'title123'); - $table->build(); - $r1 = $table->getCols(); - $this->test->expect( - in_array('title123', $r1) && !in_array('text_default_not_null', $r1), - $this->getTestDesc('renaming column') - ); - unset($r1); - - // adding dummy data - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->title123 = 'test8'; - $mapper->save(); - $mapper->reset(); - $result = $mapper->findone(array('title123 = ?','test8')); - $this->test->expect( - !$result->dry(), - $this->getTestDesc('mapping dummy data') - ); - $table->renameColumn('title123', 'text_default_not_null'); - $table->build(); - unset($result,$mapper); - - // remove column - $table->dropColumn('column_1'); - $table->build(); - $r1 = $table->getCols(); - $this->test->expect( - !in_array('column_1', $r1), - $this->getTestDesc('removing column') - ); - unset($r1); - - // rename table - $schema->dropTable('test123'); - $table->rename('test123'); - $result = $schema->getTables(); - $this->test->expect( - in_array('test123', $result) && !in_array($this->tname, $result), - $this->getTestDesc('renaming table') - ); - $table->rename($this->tname); - unset($result); - - // check record count - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $this->test->expect( - count($mapper->find()) == 9, - $this->getTestDesc('check record count') - ); - unset($mapper); - - // adding composite primary keys - $table->addColumn('version')->type($schema::DT_INT4)->nullable(false)->defaults(1); - $table->primary(array('id', 'version')); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect(!empty($r1) && isset($r1['version']) && - $r1['id']['pkey'] == true && $r1['version']['pkey'] == true, - $this->getTestDesc('adding composite primary-keys') - ); - unset($r1); - - // check record count - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $this->test->expect( - count($mapper->find()) == 9, - $this->getTestDesc('check record count') - ); - unset($mapper); - - // drop table - $schema->dropTable($this->tname); - $this->test->expect( - !in_array($this->tname, $schema->getTables()), - $this->getTestDesc('drop table') - ); - - // adding composite primary keys - $table = $schema->createTable($this->tname); - $table->addColumn('version')->type($schema::DT_INT4) - ->defaults(1)->nullable(false); - $table->primary(array('id', 'version')); - $table = $table->build(); - $r1 = $table->getCols(true); - - $this->test->expect(!empty($r1) && - $r1['id']['pkey'] == true && $r1['version']['pkey'] == true, - $this->getTestDesc('creating new table with composite key') - ); - $this->test->expect(!empty($r1) && - $r1['version']['default'] == '1', - $this->getTestDesc('default value on composite primary key') - ); - unset($r1); - - // more fields to composite primary key table - $table->addColumn('title')->type($schema::DT_VARCHAR256); - $table->addColumn('title2')->type($schema::DT_TEXT); - $table->addColumn('title_notnull') - ->type($schema::DT_VARCHAR128)->nullable(false)->defaults("foo"); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - array_key_exists('title', $r1) && - array_key_exists('title_notnull', $r1) && - $r1['id']['pkey'] == true && $r1['version']['pkey'] == true, - $this->getTestDesc('adding more fields to composite pk table') - ); - unset($r1); - - // testing primary keys with inserted data - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->title = 'test1'; - $mapper->save(); - $mapper->reset(); - - $mapper->id = 1; - $mapper->title = 'nullable'; - $mapper->version = 2; - $mapper->save(); - $mapper->reset(); - - $mapper->title = 'test3'; - $mapper->title2 = 'foobar'; - $mapper->title_notnull = 'bar'; - $mapper->save(); - - $result = array_map(array($mapper,'cast'),$mapper->find()); - - $cpk_expected = array( - 0=>array( - 'id' => 1, - 'version' => 1, - 'title' => 'test1', - 'title2' => NULL, - 'title_notnull' => 'foo', - ), - 1=>array( - 'id' => 1, - 'version' => 2, - 'title' => 'nullable', - 'title2' => NULL, - 'title_notnull' => 'foo', - ), - 2=>array( - 'id' => 2, - 'version' => 1, - 'title' => 'test3', - 'title2' => 'foobar', - 'title_notnull' => 'bar', - ), - ); - foreach ($result as &$r) - ksort($r); - foreach ($cpk_expected as &$r) - ksort($r); - $this->test->expect( - json_encode($result) == json_encode($cpk_expected), - $this->getTestDesc('adding items with composite primary-keys') - ); - - $mapper = new \DB\SQL\Mapper($db, $this->tname); - $mapper->load(); - $rec_count_cur = $mapper->loaded(); - $schema->truncateTable($this->tname); - $mapper->reset(); - $mapper->load(); - $rec_count_new = $mapper->loaded(); - $this->test->expect( - $rec_count_cur==3 && $rec_count_new == 0, - $this->getTestDesc('truncate table') - ); - - $schema->dropTable($this->tname); - - // indexes - $table = $schema->createTable($this->tname); - $table->addColumn('rawtest', array('type' => $schema::DT_VARCHAR256, 'default' => 'foo')); - $table->addColumn('text')->type($schema::DT_TEXT); - $table->addColumn('foo')->type($schema::DT_VARCHAR128)->index(); - $table = $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - isset($r1['rawtest']) && $r1['rawtest']['default'] = 'foo', - $this->getTestDesc('adding column with options array') - ); - $indexes = $table->listIndex(); - $this->test->expect( - isset($indexes[$table->name.'___foo']), - $this->getTestDesc('column index on table creation') - ); - $table->addColumn('bar')->type($schema::DT_VARCHAR128)->index(true); - $table->addColumn('baz')->type($schema::DT_VARCHAR128); - $table->addIndex(array('foo', 'baz')); - $table->build(); - $indexes = $table->listIndex(); - $this->test->expect( - isset($indexes[$table->name.'___bar']), - $this->getTestDesc('column index on table alteration') - ); - $this->test->expect( - isset($indexes[$table->name.'___bar']) && $indexes[$table->name.'___bar']['unique'] == true, - $this->getTestDesc('unique index') - ); - $this->test->expect( - isset($indexes[$table->name.'___foo__baz']), - $this->getTestDesc('index on combined columns') - ); - - if($this->current_engine == 'sqlite') { - $table->dropColumn('rawtest'); - $table->build(); - $indexes = $table->listIndex(); - $this->test->expect( - isset($indexes[$table->name.'___foo__baz']) && isset($indexes[$table->name.'___bar']) - && $indexes[$table->name.'___bar']['unique'], - $this->getTestDesc('preserve indexes after table rebuild') - ); - } - - $table->dropIndex($table->name.'___bar'); - $table->build(); - $indexes = $table->listIndex(); - $this->test->expect( - !array_key_exists($table->name.'___bar',$indexes), - $this->getTestDesc('drop index') - ); - - // update column - $table->updateColumn('bar',$schema::DT_TEXT); - $table->build(); - $r1 = $table->getCols(true); - $text = preg_match('/sybase|dblib|odbc|sqlsrv/',$this->current_engine) - ? 'nvarchar' : 'text'; - $this->test->expect( - array_key_exists('bar', $r1) && $r1['bar']['type'] == $text, - $this->getTestDesc('update column') - ); - - // update column - $cols = $table->getCols(true); - $bar = $cols['bar']; - $col = new \DB\SQL\Column('bar',$table); - $col->copyfrom($bar); - $col->type_varchar(60); - $col->defaults('great'); - $table->updateColumn('bar',$col); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - array_key_exists('bar', $r1) - && $r1['bar']['default'] == 'great', - $this->getTestDesc('update column and default') - ); - - // update column default only - $cols = $table->getCols(true); - $bar = $cols['bar']; - $col = new \DB\SQL\Column('bar',$table); - $col->copyfrom($bar); - $col->passThrough(); - $col->defaults(''); - $table->updateColumn('bar',$col); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - array_key_exists('bar', $r1) && $r1['bar']['default'] == '', - $this->getTestDesc('update default value') - ); - - $col->nullable(false); - $table->updateColumn('bar',$col); - $table->build(); - $r1 = $table->getCols(true); - $this->test->expect( - array_key_exists('bar', $r1) && $r1['bar']['nullable'] == false, - $this->getTestDesc('update nullable flag') - ); - - - // create table with text not nullable column - $table2 = $schema->createTable($this->tname.'_notnulltext'); - $table2->addColumn('desc')->type($schema::DT_TEXT)->nullable(false); - $table2 = $table2->build(); - $r1 = $schema->getTables(); - $r2 = $table2->getCols(true); - $this->test->expect( - in_array($this->tname.'_notnulltext', $r1) && array_key_exists('desc', $r2) - && $r2['desc']['nullable']==false, - $this->getTestDesc('create new table with not nullable text column') - ); - $table2->drop(); - - // boolean fields are actually bit/tinyint - $schema->dropTable($this->tname.'_notnullbool'); - $table2 = $schema->createTable($this->tname.'_notnullbool'); - $table2->addColumn('active')->type($schema::DT_BOOL)->nullable(false); - $table2 = $table2->build(); - $r1 = $schema->getTables(); - $r2 = $table2->getCols(true); - $this->test->expect( - in_array($this->tname.'_notnullbool', $r1) && array_key_exists('active', $r2) - && $r2['active']['nullable']==false, - $this->getTestDesc('create new table with not nullable boolean column') - ); - - $table2->addColumn('active2')->type($schema::DT_BOOL)->nullable(false)->defaults(0); - $table2->addColumn('active3')->type($schema::DT_BOOL)->nullable(false)->defaults(1); - $table2->build(); - $r1 = $schema->getTables(); - $r2 = $table2->getCols(true); - $this->test->expect( - in_array($this->tname.'_notnullbool', $r1) - && array_key_exists('active2', $r2) && $r2['active2']['nullable']==false && - ((int)$r2['active2']['default']==0||$r2['active2']['default']=='false') - && array_key_exists('active3', $r2) && $r2['active3']['nullable']==false && - ((int)$r2['active3']['default']==1||$r2['active3']['default']=='true'), - $this->getTestDesc('add not nullable boolean columns with default to existing table') - ); - - - $table2->drop(); - - - } - -} \ No newline at end of file diff --git a/app/main/controller/accesscontroller.php b/app/main/controller/accesscontroller.php index 1c095b5b..19c1348b 100644 --- a/app/main/controller/accesscontroller.php +++ b/app/main/controller/accesscontroller.php @@ -19,6 +19,9 @@ class AccessController extends Controller { * @param \Base $f3 * @param $params * @return bool + * @throws \Exception + * @throws \Exception\PathfinderException + * @throws \ZMQSocketException */ function beforeroute(\Base $f3, $params): bool { if($return = parent::beforeroute($f3, $params)){ @@ -47,6 +50,8 @@ class AccessController extends Controller { * get current character and check if it is a valid character * @param \Base $f3 * @return bool + * @throws \Exception + * @throws \Exception\PathfinderException */ protected function isLoggedIn(\Base $f3): bool { $loginCheck = false; @@ -66,6 +71,7 @@ class AccessController extends Controller { * @param \Base $f3 * @param Model\CharacterModel $character * @return bool + * @throws \Exception\PathfinderException */ private function checkLogTimer(\Base $f3, Model\CharacterModel $character){ $loginCheck = false; @@ -97,6 +103,9 @@ class AccessController extends Controller { * -> send over TCP Socket * @param Model\MapModel $map * @return int (number of active connections for this map) + * @throws \Exception + * @throws \Exception\PathfinderException + * @throws \ZMQSocketException */ protected function broadcastMapData(Model\MapModel $map){ $mapData = $this->getFormattedMapData($map); @@ -107,6 +116,8 @@ class AccessController extends Controller { * get formatted Map Data * @param Model\MapModel $map * @return array + * @throws \Exception + * @throws \Exception\PathfinderException */ protected function getFormattedMapData(Model\MapModel $map){ $mapData = $map->getData(); diff --git a/app/main/controller/admin.php b/app/main/controller/admin.php index 20a514b9..fbfb6677 100644 --- a/app/main/controller/admin.php +++ b/app/main/controller/admin.php @@ -34,6 +34,8 @@ class Admin extends Controller{ * @param \Base $f3 * @param $params * @return bool + * @throws \Exception + * @throws \Exception\PathfinderException */ function beforeroute(\Base $f3, $params): bool { $return = parent::beforeroute($f3, $params); @@ -63,6 +65,7 @@ class Admin extends Controller{ /** * event handler after routing * @param \Base $f3 + * @throws \Exception\PathfinderException */ public function afterroute(\Base $f3) { // js view (file) @@ -81,6 +84,7 @@ class Admin extends Controller{ * returns valid admin $characterModel for current user * @param \Base $f3 * @return CharacterModel|null + * @throws \Exception */ protected function getAdminCharacter(\Base $f3){ $adminCharacter = null; @@ -148,6 +152,7 @@ class Admin extends Controller{ * @param \Base $f3 * @param array $params * @param null $character + * @throws \Exception\PathfinderException */ public function dispatch(\Base $f3, $params, $character = null){ if($character instanceof CharacterModel){ @@ -191,6 +196,7 @@ class Admin extends Controller{ * @param CharacterModel $character * @param int $kickCharacterId * @param int $minutes + * @throws \Exception\PathfinderException */ protected function kickCharacter(CharacterModel $character, $kickCharacterId, $minutes){ $kickOptions = self::KICK_OPTIONS; @@ -220,6 +226,7 @@ class Admin extends Controller{ * @param CharacterModel $character * @param int $banCharacterId * @param int $value + * @throws \Exception\PathfinderException */ protected function banCharacter(CharacterModel $character, $banCharacterId, $value){ $banCharacters = $this->filterValidCharacters($character, $banCharacterId); @@ -267,6 +274,7 @@ class Admin extends Controller{ * get log file for "admin" logs * @param string $type * @return \Log + * @throws \Exception\PathfinderException */ static function getLogger($type = 'ADMIN'){ return parent::getLogger('ADMIN'); diff --git a/app/main/controller/api/access.php b/app/main/controller/api/access.php index 94898f54..0096db85 100644 --- a/app/main/controller/api/access.php +++ b/app/main/controller/api/access.php @@ -17,6 +17,7 @@ class Access extends Controller\AccessController { * search character/corporation or alliance by name * @param \Base $f3 * @param $params + * @throws \Exception */ public function search($f3, $params){ diff --git a/app/main/controller/api/connection.php b/app/main/controller/api/connection.php index 85a490c6..24fe6579 100644 --- a/app/main/controller/api/connection.php +++ b/app/main/controller/api/connection.php @@ -17,6 +17,7 @@ class Connection extends Controller\AccessController { * save a new connection or updates an existing (drag/drop) between two systems * if a connection is changed (drag&drop) to another system. -> this function is called for update * @param \Base $f3 + * @throws \Exception */ public function save(\Base $f3){ $postData = (array)$f3->get('POST'); diff --git a/app/main/controller/api/github.php b/app/main/controller/api/github.php index 18ccc093..974ed3da 100644 --- a/app/main/controller/api/github.php +++ b/app/main/controller/api/github.php @@ -21,6 +21,7 @@ class GitHub extends Controller\Controller { /** * get HTTP request options for API (curl) request * @return array + * @throws \Exception\PathfinderException */ protected function getRequestOptions(){ $requestOptions = [ @@ -36,6 +37,7 @@ class GitHub extends Controller\Controller { /** * get release information from GitHub * @param $f3 + * @throws \Exception\PathfinderException */ public function releases($f3){ $cacheKey = 'CACHE_GITHUB_RELEASES'; diff --git a/app/main/controller/api/map.php b/app/main/controller/api/map.php index 2719ff45..fcd3b528 100644 --- a/app/main/controller/api/map.php +++ b/app/main/controller/api/map.php @@ -71,6 +71,8 @@ class Map extends Controller\AccessController { /** * Get all required static config data for program initialization * @param \Base $f3 + * @throws Exception + * @throws Exception\PathfinderException */ public function init(\Base $f3){ // expire time in seconds @@ -159,6 +161,15 @@ class Map extends Controller\AccessController { } $return->connectionScopes = $connectionScopeData; + // get available wormhole types --------------------------------------------------------------------------- + $wormholes = Model\BasicModel::getNew('WormholeModel'); + $rows = $wormholes->find('id > 0', null, $expireTimeSQL); + $wormholesData = []; + foreach((array)$rows as $rowData){ + $wormholesData[$rowData->name] = $rowData->getData(); + } + $return->wormholes = $wormholesData; + // get available character status ------------------------------------------------------------------------- $characterStatus = Model\BasicModel::getNew('CharacterStatusModel'); $rows = $characterStatus->find('active = 1', null, $expireTimeSQL); @@ -223,6 +234,7 @@ class Map extends Controller\AccessController { /** * import new map data * @param \Base $f3 + * @throws Exception */ public function import(\Base $f3){ $importData = (array)$f3->get('POST'); @@ -367,6 +379,7 @@ class Map extends Controller\AccessController { /** * save a new map or update an existing map * @param \Base $f3 + * @throws Exception */ public function save(\Base $f3){ $formData = (array)$f3->get('POST.formData'); @@ -563,6 +576,7 @@ class Map extends Controller\AccessController { /** * delete a map and all dependencies * @param \Base $f3 + * @throws Exception */ public function delete(\Base $f3){ $mapData = (array)$f3->get('POST.mapData'); @@ -597,6 +611,9 @@ class Map extends Controller\AccessController { * -> if characters with map access found -> broadcast mapData to them * @param Model\MapModel $map * @param array $characterIds + * @throws Exception + * @throws Exception\PathfinderException + * @throws \ZMQSocketException */ protected function broadcastMapAccess($map, $characterIds){ $mapAccess = [ @@ -614,6 +631,7 @@ class Map extends Controller\AccessController { * broadcast map delete information to clients * @param int $mapId * @return bool|string + * @throws \ZMQSocketException */ protected function broadcastMapDeleted($mapId){ return (new Socket( Config::getSocketUri() ))->sendData('mapDeleted', $mapId); @@ -623,6 +641,7 @@ class Map extends Controller\AccessController { * get map access tokens for current character * -> send access tokens via TCP Socket for WebSocket auth * @param \Base $f3 + * @throws Exception */ public function getAccessData(\Base $f3){ $return = (object) []; @@ -656,6 +675,8 @@ class Map extends Controller\AccessController { * update map data * -> function is called continuously (trigger) by any active client * @param \Base $f3 + * @throws Exception + * @throws Exception\PathfinderException */ public function updateData(\Base $f3){ $mapData = (array)$f3->get('POST.mapData'); @@ -805,6 +826,8 @@ class Map extends Controller\AccessController { * get formatted map data * @param Model\MapModel[] $mapModels * @return array + * @throws Exception + * @throws Exception\PathfinderException */ protected function getFormattedMapsData($mapModels){ $mapData = []; @@ -819,6 +842,8 @@ class Map extends Controller\AccessController { * update map data api * -> function is called continuously by any active client * @param \Base $f3 + * @throws Exception + * @throws Exception\PathfinderException */ public function updateUserData(\Base $f3){ $return = (object) []; @@ -896,6 +921,7 @@ class Map extends Controller\AccessController { * @param Model\CharacterModel $character * @param Model\MapModel $map * @return Model\MapModel + * @throws Exception */ protected function updateMapData(Model\CharacterModel $character, Model\MapModel $map){ @@ -1056,21 +1082,34 @@ class Map extends Controller\AccessController { } } - // save connection ------------------------------------------------------------------------------------ if( - $addConnection && $sourceExists && $targetExists && $sourceSystem && - $targetSystem && - !$map->searchConnection( $sourceSystem, $targetSystem ) + $targetSystem ){ - $connection = $map->getNewConnection($sourceSystem, $targetSystem); - $connection = $map->saveConnection($connection, $character); - // get updated maps object - if($connection){ - $map = $connection->mapId; - $mapDataChanged = true; + $connection = $map->searchConnection( $sourceSystem, $targetSystem); + + // save connection -------------------------------------------------------------------------------- + if( + $addConnection && + !$connection + ){ + $connection = $map->getNewConnection($sourceSystem, $targetSystem); + $connection = $map->saveConnection($connection, $character); + // get updated maps object + if($connection){ + $map = $connection->mapId; + $mapDataChanged = true; + } + } + + // log jump mass ---------------------------------------------------------------------------------- + if( + $connection && + $connection->isWormhole() + ){ + $connection->logMass($log); } } } @@ -1087,9 +1126,13 @@ class Map extends Controller\AccessController { /** * get connectionData * @param \Base $f3 + * @throws Exception */ public function getConnectionData (\Base $f3){ $postData = (array)$f3->get('POST'); + + $addData = (array)$postData['addData']; + $filterData = (array)$postData['filterData']; $connectionData = []; if($mapId = (int)$postData['mapId']){ @@ -1102,13 +1145,27 @@ class Map extends Controller\AccessController { $map->getById($mapId); if($map->hasAccess($activeCharacter)){ - $connections = $map->getConnections('wh'); - foreach($connections as $connection){ - $data = $connection->getData(true); - // skip connections whiteout signature data - if($data->signatures){ - $connectionData[] = $data; + // get specific connections by id + $connectionIds = null; + if(is_array($postData['connectionIds'])){ + $connectionIds = $postData['connectionIds']; + } + $connections = $map->getConnections($connectionIds, 'wh'); + foreach($connections as $connection){ + $check = true; + $data = $connection->getData(in_array('signatures', $addData), in_array('logs', $addData)); + // filter result + if(in_array('signatures', $filterData) && !$data->signatures){ + $check = false; + } + + if(in_array('logs', $filterData) && !$data->logs){ + $check = false; + } + + if($check){ + $connectionData[] = $data; } } } @@ -1120,6 +1177,8 @@ class Map extends Controller\AccessController { /** * get map log data * @param \Base $f3 + * @throws Exception + * @throws Exception\PathfinderException */ public function getLogData(\Base $f3){ $postData = (array)$f3->get('POST'); diff --git a/app/main/controller/api/route.php b/app/main/controller/api/route.php index 807b0233..522a7ec7 100644 --- a/app/main/controller/api/route.php +++ b/app/main/controller/api/route.php @@ -509,6 +509,8 @@ class Route extends Controller\AccessController { /** * search multiple route between two systems * @param \Base $f3 + * @throws \Exception + * @throws \Exception\PathfinderException */ public function search($f3){ $requestData = (array)$f3->get('POST'); diff --git a/app/main/controller/api/signature.php b/app/main/controller/api/signature.php index 39b8250d..427fbcfb 100644 --- a/app/main/controller/api/signature.php +++ b/app/main/controller/api/signature.php @@ -18,6 +18,7 @@ class Signature extends Controller\AccessController { * get signature data for systems * -> return value of this is limited to a "SINGLE" system * @param \Base $f3 + * @throws \Exception */ public function getAll(\Base $f3){ $signatureData = []; @@ -50,6 +51,7 @@ class Signature extends Controller\AccessController { * save or update a full signature data set * or save/update just single or multiple signature data * @param \Base $f3 + * @throws \Exception */ public function save(\Base $f3){ $requestData = $f3->get('POST'); @@ -221,6 +223,7 @@ class Signature extends Controller\AccessController { /** * delete signatures * @param \Base $f3 + * @throws \Exception */ public function delete(\Base $f3){ $signatureIds = $f3->get('POST.signatureIds'); diff --git a/app/main/controller/api/statistic.php b/app/main/controller/api/statistic.php index e42dc68c..a2eb9d66 100644 --- a/app/main/controller/api/statistic.php +++ b/app/main/controller/api/statistic.php @@ -123,6 +123,7 @@ class Statistic extends Controller\AccessController { * @param int $yearEnd * @param int $weekEnd * @return array + * @throws \Exception\PathfinderException */ protected function queryStatistic( CharacterModel $character, $typeId, $yearStart, $weekStart, $yearEnd, $weekEnd){ $data = []; @@ -231,6 +232,7 @@ class Statistic extends Controller\AccessController { /** * get statistics data * @param \Base $f3 + * @throws \Exception */ public function getData(\Base $f3){ $postData = (array)$f3->get('POST'); diff --git a/app/main/controller/api/system.php b/app/main/controller/api/system.php index be00945b..6349aa3c 100644 --- a/app/main/controller/api/system.php +++ b/app/main/controller/api/system.php @@ -175,6 +175,7 @@ class System extends Controller\AccessController { /** * save a new system to a a map * @param \Base $f3 + * @throws \Exception */ public function save(\Base $f3){ $postData = (array)$f3->get('POST'); @@ -278,6 +279,7 @@ class System extends Controller\AccessController { * get system log data from CCP API import * system Kills, Jumps,.... * @param \Base $f3 + * @throws \Exception */ public function graphData(\Base $f3){ $graphData = []; @@ -329,6 +331,8 @@ class System extends Controller\AccessController { * get system data for all systems within a constellation * @param \Base $f3 * @param array $params + * @throws \Exception + * @throws \Exception\PathfinderException */ public function constellationData(\Base $f3, $params){ $return = (object) []; @@ -363,6 +367,7 @@ class System extends Controller\AccessController { /** * set destination for specific systemIds * @param \Base $f3 + * @throws \Exception */ public function setDestination(\Base $f3){ $postData = (array)$f3->get('POST'); @@ -405,6 +410,8 @@ class System extends Controller\AccessController { /** * send Rally Point poke * @param \Base $f3 + * @throws \Exception + * @throws \Exception\PathfinderException */ public function pokeRally(\Base $f3){ $rallyData = (array)$f3->get('POST'); @@ -437,6 +444,7 @@ class System extends Controller\AccessController { * delete systems and all its connections from map * -> set "active" flag * @param \Base $f3 + * @throws \Exception */ public function delete(\Base $f3){ $mapId = (int)$f3->get('POST.mapId'); @@ -455,11 +463,20 @@ class System extends Controller\AccessController { $map->getById($mapId); if($map->hasAccess($activeCharacter)){ + $newSystemModel = Model\BasicModel::getNew('SystemModel'); foreach($systemIds as $systemId){ if( $system = $map->getSystemById($systemId) ){ // check whether system should be deleted OR set "inactive" if( $this->checkDeleteMode($map, $system) ){ - $system->erase(); + // delete log + // -> first set updatedCharacterId -> required for activity log + $system->updatedCharacterId = $activeCharacter; + $system->update(); + + // ... now get fresh object and delete.. + $newSystemModel->getById( $system->id, 0); + $newSystemModel->erase(); + $newSystemModel->reset(); }else{ // keep data -> set "inactive" $system->setActive(false); diff --git a/app/main/controller/api/user.php b/app/main/controller/api/user.php index 7b33b808..a22ef1e0 100644 --- a/app/main/controller/api/user.php +++ b/app/main/controller/api/user.php @@ -42,10 +42,10 @@ class User extends Controller\Controller{ /** * login a valid character * @param Model\CharacterModel $characterModel - * @param string $browserTabId * @return bool + * @throws Exception */ - protected function loginByCharacter(Model\CharacterModel &$characterModel, string $browserTabId){ + protected function loginByCharacter(Model\CharacterModel &$characterModel){ $login = false; if($user = $characterModel->getUser()){ @@ -96,7 +96,7 @@ class User extends Controller\Controller{ // set temp character data ------------------------------------------------------------ // -> pass character data over for next http request (reroute()) - $this->setTempCharacterData($characterModel->_id, $browserTabId); + $this->setTempCharacterData($characterModel->_id); $login = true; } @@ -108,6 +108,8 @@ class User extends Controller\Controller{ * validate cookie character information * -> return character data (if valid) * @param \Base $f3 + * @throws Exception + * @throws Exception\PathfinderException */ public function getCookieCharacter(\Base $f3){ $data = $f3->get('POST'); @@ -188,6 +190,7 @@ class User extends Controller\Controller{ /** * delete the character log entry for the current active (main) character * @param \Base $f3 + * @throws Exception */ public function deleteLog(\Base $f3){ if($activeCharacter = $this->getCharacter()){ @@ -198,6 +201,8 @@ class User extends Controller\Controller{ /** * log the current user out + clear character system log data * @param \Base $f3 + * @throws Exception + * @throws \ZMQSocketException */ public function logout(\Base $f3){ $this->logoutCharacter(false, true, true, true); @@ -211,6 +216,7 @@ class User extends Controller\Controller{ * remote open ingame information window (character, corporation or alliance) Id * -> the type is auto-recognized by CCP * @param \Base $f3 + * @throws Exception */ public function openIngameWindow(\Base $f3){ $data = $f3->get('POST'); @@ -241,6 +247,7 @@ class User extends Controller\Controller{ * -> a fresh user automatically generated on first login with a new character * -> see SSO login * @param \Base $f3 + * @throws Exception */ public function saveAccount(\Base $f3){ $data = $f3->get('POST'); @@ -361,6 +368,8 @@ class User extends Controller\Controller{ /** * delete current user account from DB * @param \Base $f3 + * @throws Exception + * @throws \ZMQSocketException */ public function deleteAccount(\Base $f3){ $data = $f3->get('POST.formData'); diff --git a/app/main/controller/ccp/sso.php b/app/main/controller/ccp/sso.php index 5e509e6c..8f364f6a 100644 --- a/app/main/controller/ccp/sso.php +++ b/app/main/controller/ccp/sso.php @@ -34,7 +34,6 @@ class Sso extends Api\User{ const SESSION_KEY_SSO_ERROR = 'SESSION.SSO.ERROR'; const SESSION_KEY_SSO_STATE = 'SESSION.SSO.STATE'; const SESSION_KEY_SSO_FROM = 'SESSION.SSO.FROM'; - const SESSION_KEY_SSO_TAB_ID = 'SESSION.SSO.TABID'; // error messages const ERROR_CCP_SSO_URL = 'Invalid "ENVIRONMENT.[ENVIRONMENT].CCP_SSO_URL" url. %s'; @@ -52,10 +51,10 @@ class Sso extends Api\User{ * redirect user to CCP SSO page and request authorization * -> cf. Controller->getCookieCharacters() ( equivalent cookie based login) * @param \Base $f3 + * @throws \Exception\PathfinderException */ public function requestAdminAuthorization($f3){ // store browser tabId to be "targeted" after login - $f3->set(self::SESSION_KEY_SSO_TAB_ID, ''); $f3->set(self::SESSION_KEY_SSO_FROM, 'admin'); $scopes = self::getScopesByAuthType('admin'); @@ -66,13 +65,11 @@ class Sso extends Api\User{ * redirect user to CCP SSO page and request authorization * -> cf. Controller->getCookieCharacters() ( equivalent cookie based login) * @param \Base $f3 + * @throws \Exception + * @throws \Exception\PathfinderException */ public function requestAuthorization($f3){ $params = $f3->get('GET'); - $browserTabId = trim((string)$params['tabId']); - - // store browser tabId to be "targeted" after login - $f3->set(self::SESSION_KEY_SSO_TAB_ID, $browserTabId); if( isset($params['characterId']) && @@ -108,7 +105,7 @@ class Sso extends Api\User{ $character->hasUserCharacter() && ($character->isAuthorized() === 'OK') ){ - $loginCheck = $this->loginByCharacter($character, $browserTabId); + $loginCheck = $this->loginByCharacter($character); if($loginCheck){ // set "login" cookie @@ -135,6 +132,7 @@ class Sso extends Api\User{ * @param \Base $f3 * @param array $scopes * @param string $rootAlias + * @throws \Exception\PathfinderException */ private function rerouteAuthorization(\Base $f3, $scopes = [], $rootAlias = 'login'){ if( !empty( Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') ) ){ @@ -166,6 +164,8 @@ class Sso extends Api\User{ * callback handler for CCP SSO user Auth * -> see requestAuthorization() * @param \Base $f3 + * @throws \Exception + * @throws \Exception\PathfinderException */ public function callbackAuthorization($f3){ $getParams = (array)$f3->get('GET'); @@ -178,8 +178,6 @@ class Sso extends Api\User{ $rootAlias = $f3->get(self::SESSION_KEY_SSO_FROM); } - $browserTabId = (string)$f3->get(self::SESSION_KEY_SSO_TAB_ID) ; - if($f3->exists(self::SESSION_KEY_SSO_STATE)){ // check response and validate 'state' if( @@ -192,7 +190,6 @@ class Sso extends Api\User{ // clear 'state' for new next login request $f3->clear(self::SESSION_KEY_SSO_STATE); $f3->clear(self::SESSION_KEY_SSO_FROM); - $f3->clear(self::SESSION_KEY_SSO_TAB_ID); $accessData = $this->getSsoAccessData($getParams['code']); @@ -259,7 +256,7 @@ class Sso extends Api\User{ $characterModel = $userCharactersModel->getCharacter(); // login by character - $loginCheck = $this->loginByCharacter($characterModel, $browserTabId); + $loginCheck = $this->loginByCharacter($characterModel); if($loginCheck){ // set "login" cookie @@ -305,11 +302,12 @@ class Sso extends Api\User{ /** * login by cookie name * @param \Base $f3 + * @throws \Exception + * @throws \Exception\PathfinderException */ public function login(\Base $f3){ $data = (array)$f3->get('GET'); $cookieName = empty($data['cookie']) ? '' : $data['cookie']; - $browserTabId = empty($data['tabId']) ? '' : $data['tabId']; $character = null; if( !empty($cookieName) ){ @@ -324,7 +322,7 @@ class Sso extends Api\User{ if( is_object($character)){ // login by character - $loginCheck = $this->loginByCharacter($character, $browserTabId); + $loginCheck = $this->loginByCharacter($character); if($loginCheck){ // route to "map" $f3->reroute(['map']); @@ -343,6 +341,7 @@ class Sso extends Api\User{ * -> else try to refresh auth and get fresh "access_token" * @param bool $authCode * @return null|\stdClass + * @throws \Exception\PathfinderException */ public function getSsoAccessData($authCode){ $accessData = null; @@ -362,6 +361,7 @@ class Sso extends Api\User{ * verify authorization code, and get an "access_token" data * @param $authCode * @return \stdClass + * @throws \Exception\PathfinderException */ protected function verifyAuthorizationCode($authCode){ $requestParams = [ @@ -377,6 +377,7 @@ class Sso extends Api\User{ * -> if "access_token" is expired, this function gets a fresh one * @param $refreshToken * @return \stdClass + * @throws \Exception\PathfinderException */ public function refreshAccessToken($refreshToken){ $requestParams = [ @@ -393,6 +394,7 @@ class Sso extends Api\User{ * OR by providing a valid "refresh_token" * @param $requestParams * @return \stdClass + * @throws \Exception\PathfinderException */ protected function requestAccessData($requestParams){ $verifyAuthCodeUrl = self::getVerifyAuthorizationCodeEndpoint(); @@ -457,6 +459,7 @@ class Sso extends Api\User{ * -> if more character information is required, use ESI "characters" endpoints request instead * @param $accessToken * @return mixed|null + * @throws \Exception\PathfinderException */ public function verifyCharacterData($accessToken){ $verifyUserUrl = self::getVerifyUserEndpoint(); @@ -492,6 +495,7 @@ class Sso extends Api\User{ * get character data * @param int $characterId * @return object + * @throws \Exception */ public function getCharacterData($characterId){ $characterData = (object) []; @@ -601,6 +605,7 @@ class Sso extends Api\User{ * get CCP SSO url from configuration file * -> throw error if url is broken/missing * @return string + * @throws \Exception\PathfinderException */ static function getSsoUrlRoot(){ $url = ''; @@ -630,6 +635,7 @@ class Sso extends Api\User{ /** * get logger for SSO logging * @return \Log + * @throws \Exception\PathfinderException */ static function getSSOLogger(){ return parent::getLogger('SSO'); diff --git a/app/main/controller/ccp/universe.php b/app/main/controller/ccp/universe.php index e199eec2..f30fb0a7 100644 --- a/app/main/controller/ccp/universe.php +++ b/app/main/controller/ccp/universe.php @@ -17,6 +17,7 @@ class Universe extends Controller { /** * Set up "Universe" Database * @param \Base $f3 + * @throws \Exception */ public function setupDB(\Base $f3){ $this->setupRegions($f3); @@ -26,6 +27,7 @@ class Universe extends Controller { /** * get all regions from CCP and store region data * @param \Base $f3 + * @throws \Exception */ private function setupRegions(\Base $f3){ $this->getDB('UNIVERSE'); @@ -50,6 +52,7 @@ class Universe extends Controller { /** * get all constellations from CCP and store constellation data * @param \Base $f3 + * @throws \Exception */ private function setupConstellations(\Base $f3){ $this->getDB('UNIVERSE'); diff --git a/app/main/controller/controller.php b/app/main/controller/controller.php index 7bf52a87..37f481ea 100644 --- a/app/main/controller/controller.php +++ b/app/main/controller/controller.php @@ -63,6 +63,7 @@ class Controller { * @param \Base $f3 * @param $params * @return bool + * @throws \Exception\PathfinderException */ function beforeroute(\Base $f3, $params): bool { // initiate DB connection @@ -107,6 +108,7 @@ class Controller { /** * init new Session handler + * @param \Base $f3 */ protected function initSession(\Base $f3){ $sessionCacheKey = $f3->get('SESSION_CACHE'); @@ -117,6 +119,7 @@ class Controller { * @param $session * @param $sid * @return bool + * @throws \Exception\PathfinderException */ $onSuspect = function($session, $sid){ self::getLogger('SESSION_SUSPECT')->write( sprintf( @@ -181,6 +184,7 @@ class Controller { * set/update logged in cookie by character model * -> store validation data in DB * @param Model\CharacterModel $character + * @throws \Exception\PathfinderException */ protected function setLoginCookie(Model\CharacterModel $character){ if( $this->getCookieState() ){ @@ -237,6 +241,8 @@ class Controller { * @param array $cookieData * @param bool $checkAuthorization * @return Model\CharacterModel[] + * @throws \Exception + * @throws \Exception\PathfinderException */ protected function getCookieCharacters($cookieData = [], $checkAuthorization = true){ $characters = []; @@ -339,8 +345,8 @@ class Controller { /** * get current character data from session - * -> * @return array + * @throws \Exception */ public function getSessionCharacterData(){ $data = []; @@ -348,35 +354,16 @@ class Controller { if($user = $this->getUser()){ $header = self::getRequestHeaders(); $requestedCharacterId = (int)$header['Pf-Character']; - $browserTabId = (string)$header['Pf-Tab-Id']; - $tempCharacterData = (array)$this->getF3()->get(Api\User::SESSION_KEY_TEMP_CHARACTER_DATA); - if($this->getF3()->get('AJAX')){ + if( !$this->getF3()->get('AJAX') ){ + $requestedCharacterId = (int)$_COOKIE['old_char_id']; + if(!$requestedCharacterId){ + $tempCharacterData = (array)$this->getF3()->get(Api\User::SESSION_KEY_TEMP_CHARACTER_DATA); + if((int)$tempCharacterData['ID'] > 0){ + $requestedCharacterId = (int)$tempCharacterData['ID']; + } - // _blank browser tab don´t have a $browserTabId jet.. - // first Ajax call from that new tab with empty $requestedCharacterId -> bind to that new tab - if( - !empty($browserTabId) && - $requestedCharacterId <= 0 && - (int)$tempCharacterData['ID'] > 0 && - empty($tempCharacterData['TAB_ID']) - ){ - $tempCharacterData['TAB_ID'] = $browserTabId; - // update tempCharacterData (SESSION) - $this->setTempCharacterData($tempCharacterData['ID'], $tempCharacterData['TAB_ID']); } - - if( - !empty($browserTabId) && - !empty($tempCharacterData['TAB_ID']) && - (int)$tempCharacterData['ID'] > 0 && - $browserTabId === $tempCharacterData['TAB_ID'] - ){ - $requestedCharacterId = (int)$tempCharacterData['ID']; - } - - }elseif((int)$tempCharacterData['ID'] > 0){ - $requestedCharacterId = (int)$tempCharacterData['ID']; } $data = $user->getSessionCharacterData($requestedCharacterId); @@ -417,6 +404,7 @@ class Controller { * get current user * @param int $ttl * @return Model\UserModel|null + * @throws \Exception */ public function getUser($ttl = 0){ $user = null; @@ -442,14 +430,12 @@ class Controller { /** * set temp login character data (required during HTTP redirects on login) * @param int $characterId - * @param string $browserTabId * @throws \Exception */ - protected function setTempCharacterData(int $characterId, string $browserTabId){ + protected function setTempCharacterData(int $characterId){ if($characterId > 0){ $tempCharacterData = [ - 'ID' => $characterId, - 'TAB_ID' => trim($browserTabId) + 'ID' => $characterId ]; $this->getF3()->set(Api\User::SESSION_KEY_TEMP_CHARACTER_DATA, $tempCharacterData); }else{ @@ -463,6 +449,8 @@ class Controller { * @param bool $deleteSession * @param bool $deleteLog * @param bool $deleteCookie + * @throws \Exception + * @throws \ZMQSocketException */ protected function logoutCharacter(bool $all = false, bool $deleteSession = true, bool $deleteLog = true, bool $deleteCookie = false){ $sessionCharacterData = (array)$this->getF3()->get(Api\User::SESSION_KEY_CHARACTERS); @@ -581,6 +569,7 @@ class Controller { /** * get a custom userAgent string for API calls * @return string + * @throws \Exception\PathfinderException */ protected function getUserAgent(){ $userAgent = ''; @@ -598,6 +587,7 @@ class Controller { * -> on HTTP request -> render error page * @param \Base $f3 * @return bool + * @throws \Exception\PathfinderException */ public function showError(\Base $f3){ @@ -817,6 +807,7 @@ class Controller { * get the current registration status * 0=registration stop |1=new registration allowed * @return int + * @throws \Exception\PathfinderException */ static function getRegistrationStatus(){ return (int)Config::getPathfinderData('registration.status'); @@ -827,6 +818,7 @@ class Controller { * -> set in pathfinder.ini * @param string $type * @return \Log|null + * @throws \Exception\PathfinderException */ static function getLogger($type){ return LogController::getLogger($type); @@ -856,6 +848,7 @@ class Controller { * health check for ICP socket -> ping request * @param $ttl * @param $load + * @throws \ZMQSocketException */ static function checkTcpSocket($ttl, $load){ (new Socket( Config::getSocketUri(), $ttl ))->sendData('healthCheck', $load); diff --git a/app/main/controller/logcontroller.php b/app/main/controller/logcontroller.php index 3c9ee6ac..891916b9 100644 --- a/app/main/controller/logcontroller.php +++ b/app/main/controller/logcontroller.php @@ -34,6 +34,7 @@ class LogController extends \Prefab { /** * get columns from ActivityLogModel that can be uses as counter * @return array + * @throws \Exception */ protected function getActivityLogColumns(): array{ if(empty($this->activityLogColumns)){ @@ -56,6 +57,7 @@ class LogController extends \Prefab { * -> this buffered data can be stored somewhere (e.g. DB) before HTTP response * -> should be cleared afterwards! * @param MapLog $log + * @throws \Exception */ public function push(MapLog $log){ $action = $log->getAction(); @@ -161,6 +163,7 @@ class LogController extends \Prefab { * get Logger instance * @param string $type * @return \Log|null + * @throws \Exception\PathfinderException */ public static function getLogger($type){ $logFiles = Config::getPathfinderData('logfiles'); diff --git a/app/main/controller/mapcontroller.php b/app/main/controller/mapcontroller.php index 300c7bcc..f3c0049d 100644 --- a/app/main/controller/mapcontroller.php +++ b/app/main/controller/mapcontroller.php @@ -15,10 +15,15 @@ class MapController extends AccessController { /** * @param \Base $f3 + * @throws \Exception + * @throws \Exception\PathfinderException */ public function init(\Base $f3) { $character = $this->getCharacter(); + // characterId + $f3->set('tplCharacterId', $character->id); + // page title $f3->set('tplPageTitle', $character->name . ' | ' . Config::getPathfinderData('name')); diff --git a/app/main/controller/setup.php b/app/main/controller/setup.php index c673bc0f..7bb83f73 100644 --- a/app/main/controller/setup.php +++ b/app/main/controller/setup.php @@ -85,6 +85,7 @@ class Setup extends Controller { 'Model\ConstellationWormholeModel', 'Model\ConnectionModel', + 'Model\ConnectionLogModel', 'Model\SystemSignatureModel', 'Model\ActivityLogModel', @@ -139,6 +140,7 @@ class Setup extends Controller { * @param \Base $f3 * @param array $params * @return bool + * @throws \Exception\PathfinderException */ function beforeroute(\Base $f3, $params): bool { // init dbLib class. Manages all DB connections @@ -161,6 +163,7 @@ class Setup extends Controller { /** * @param \Base $f3 + * @throws \Exception\PathfinderException */ public function afterroute(\Base $f3) { // js view (file) @@ -194,6 +197,7 @@ class Setup extends Controller { * works as dispatcher for setup functions * -> for security reasons all /setup "routes" are dispatched by GET params * @param \Base $f3 + * @throws \Exception */ public function init(\Base $f3){ $params = $f3->get('GET'); @@ -760,6 +764,7 @@ class Setup extends Controller { * get default map config * @param \Base $f3 * @return array + * @throws \Exception\PathfinderException */ protected function getMapsDefaultConfig(\Base $f3): array { $matrix = \Matrix::instance(); @@ -1245,6 +1250,7 @@ class Setup extends Controller { /** * get Socket information (TCP (internal)), (WebSocket (clients)) * @return array + * @throws \ZMQSocketException */ protected function getSocketInformation(){ // $ttl for health check @@ -1298,6 +1304,7 @@ class Setup extends Controller { /** get indexed (cache) data information * @return array + * @throws \Exception */ protected function getIndexData(){ // active DB and tables are required for obtain index data @@ -1509,6 +1516,7 @@ class Setup extends Controller { /** * clear all character authentication (Cookie) data * @param \Base $f3 + * @throws \Exception */ protected function invalidateCookies(\Base $f3){ $this->getDB('PF'); diff --git a/app/main/cron/characterupdate.php b/app/main/cron/characterupdate.php index 800bbc56..b259f228 100644 --- a/app/main/cron/characterupdate.php +++ b/app/main/cron/characterupdate.php @@ -38,6 +38,7 @@ class CharacterUpdate { * -> see deactivateLogData() * >> php index.php "/cron/deleteLogData" * @param \Base $f3 + * @throws \Exception */ function deleteLogData(\Base $f3){ DB\Database::instance()->getDB('PF'); @@ -80,6 +81,7 @@ class CharacterUpdate { * clean up outdated character data e.g. kicked until status * >> php index.php "/cron/cleanUpCharacterData" * @param \Base $f3 + * @throws \Exception */ function cleanUpCharacterData(\Base $f3){ DB\Database::instance()->getDB('PF'); @@ -111,6 +113,7 @@ class CharacterUpdate { * authentication data is used for cookie based login * >> php index.php "/cron/deleteAuthenticationData" * @param \Base $f3 + * @throws \Exception */ function deleteAuthenticationData($f3){ DB\Database::instance()->getDB('PF'); diff --git a/app/main/cron/mapupdate.php b/app/main/cron/mapupdate.php index 74d53f1c..99a61314 100644 --- a/app/main/cron/mapupdate.php +++ b/app/main/cron/mapupdate.php @@ -22,6 +22,7 @@ class MapUpdate { * deactivate all "private" maps whose lifetime is over * >> php index.php "/cron/deactivateMapData" * @param \Base $f3 + * @throws \Exception\PathfinderException */ function deactivateMapData(\Base $f3){ $privateMapLifetime = (int)Config::getMapsDefaultConfig('private.lifetime'); @@ -45,6 +46,7 @@ class MapUpdate { * delete all deactivated maps * >> php index.php "/cron/deleteMapData" * @param \Base $f3 + * @throws \Exception */ function deleteMapData(\Base $f3){ $pfDB = DB\Database::instance()->getDB('PF'); @@ -82,6 +84,7 @@ class MapUpdate { * delete expired EOL connections * >> php index.php "/cron/deleteEolConnections" * @param \Base $f3 + * @throws \Exception */ function deleteEolConnections(\Base $f3){ $eolExpire = (int)$f3->get('PATHFINDER.CACHE.EXPIRE_CONNECTIONS_EOL'); @@ -125,6 +128,7 @@ class MapUpdate { * delete expired WH connections after max lifetime for wormholes is reached * >> php index.php "/cron/deleteExpiredConnections" * @param \Base $f3 + * @throws \Exception */ function deleteExpiredConnections(\Base $f3){ $whExpire = (int)$f3->get('PATHFINDER.CACHE.EXPIRE_CONNECTIONS_WH'); diff --git a/app/main/db/database.php b/app/main/db/database.php index f5f09bcb..c947b6fd 100644 --- a/app/main/db/database.php +++ b/app/main/db/database.php @@ -116,6 +116,7 @@ class Database extends \Prefab { * @param string $password * @param string $alias * @return SQL|null + * @throws \Exception\PathfinderException */ protected function connect($dns, $name, $user, $password, $alias){ $db = null; @@ -285,6 +286,7 @@ class Database extends \Prefab { /** * get logger for DB logging * @return \Log + * @throws \Exception\PathfinderException */ static function getLogger(){ return LogController::getLogger('ERROR'); diff --git a/app/main/lib/Monolog.php b/app/main/lib/Monolog.php index d83c01be..667c465e 100644 --- a/app/main/lib/Monolog.php +++ b/app/main/lib/Monolog.php @@ -67,6 +67,7 @@ class Monolog extends \Prefab { * -> this buffered data can be stored/logged somewhere (e.g. DB/file) at any time * -> should be cleared afterwards! * @param Logging\AbstractLog $log + * @throws \Exception */ public function push(Logging\AbstractLog $log){ // check whether $log should be "grouped" by common handlers diff --git a/app/main/lib/ccpclient.php b/app/main/lib/ccpclient.php index 250801e6..84e5fd45 100644 --- a/app/main/lib/ccpclient.php +++ b/app/main/lib/ccpclient.php @@ -24,6 +24,7 @@ class CcpClient extends \Prefab { * get ApiClient instance * @param \Base $f3 * @return ApiClient|null + * @throws \Exception\PathfinderException */ protected function getClient(\Base $f3){ $client = null; @@ -43,6 +44,7 @@ class CcpClient extends \Prefab { /** * @return string + * @throws \Exception\PathfinderException */ protected function getUserAgent(){ $userAgent = ''; @@ -68,6 +70,7 @@ class CcpClient extends \Prefab { * @param $name * @param $arguments * @return array|mixed + * @throws \Exception\PathfinderException */ public function __call($name, $arguments){ $return = []; diff --git a/app/main/lib/config.php b/app/main/lib/config.php index a663fd87..e38507b5 100644 --- a/app/main/lib/config.php +++ b/app/main/lib/config.php @@ -208,6 +208,7 @@ class Config extends \Prefab { /** * get SMTP config values * @return \stdClass + * @throws Exception\PathfinderException */ static function getSMTPConfig(): \stdClass{ $config = new \stdClass(); @@ -252,6 +253,7 @@ class Config extends \Prefab { * get email for notifications by hive key * @param $key * @return mixed + * @throws Exception\PathfinderException */ static function getNotificationMail($key){ return self::getPathfinderData('notification' . ($key ? '.' . $key : '')); @@ -262,6 +264,7 @@ class Config extends \Prefab { * -> read from pathfinder.ini * @param string $mapType * @return mixed + * @throws Exception\PathfinderException */ static function getMapsDefaultConfig($mapType = ''){ if( $mapConfig = self::getPathfinderData('map' . ($mapType ? '.' . $mapType : '')) ){ diff --git a/app/main/lib/logging/AbstractCharacterLog.php b/app/main/lib/logging/AbstractCharacterLog.php index a4083070..1580b017 100644 --- a/app/main/lib/logging/AbstractCharacterLog.php +++ b/app/main/lib/logging/AbstractCharacterLog.php @@ -68,6 +68,7 @@ abstract class AbstractCharacterLog extends AbstractChannelLog{ /** * get character thumbnailUrl * @return string + * @throws \Exception\PathfinderException */ protected function getThumbUrl(): string { $url = ''; diff --git a/app/main/lib/logging/AbstractLog.php b/app/main/lib/logging/AbstractLog.php index 7a87ae96..670daa01 100644 --- a/app/main/lib/logging/AbstractLog.php +++ b/app/main/lib/logging/AbstractLog.php @@ -426,6 +426,7 @@ abstract class AbstractLog implements LogInterface { /** * get __construct() parameters for ZMQHandler() call * @return array + * @throws \ZMQSocketException */ protected function getHandlerParamsZMQ(): array { $params = []; diff --git a/app/main/lib/logging/LogCollection.php b/app/main/lib/logging/LogCollection.php index 60e18826..afb3e8da 100644 --- a/app/main/lib/logging/LogCollection.php +++ b/app/main/lib/logging/LogCollection.php @@ -60,6 +60,7 @@ class LogCollection extends AbstractLog { /** * add a new log object to this collection * @param AbstractLog $log + * @throws \Exception */ public function addLog(AbstractLog $log){ if(!$this->collection->contains($log)){ @@ -113,6 +114,7 @@ class LogCollection extends AbstractLog { /** * @param string $tag + * @throws \Exception */ public function setTag(string $tag){ $currentTag = parent::getTag(); @@ -147,6 +149,7 @@ class LogCollection extends AbstractLog { /** * @return string + * @throws \Exception */ public function getChannelName() : string{ return $this->getPrimaryLog()->getChannelName(); @@ -154,6 +157,7 @@ class LogCollection extends AbstractLog { /** * @return string + * @throws \Exception */ public function getLevel() : string{ return $this->getPrimaryLog()->getLevel(); @@ -161,6 +165,7 @@ class LogCollection extends AbstractLog { /** * @return bool + * @throws \Exception */ public function hasBuffer() : bool{ return $this->getPrimaryLog()->hasBuffer(); @@ -168,6 +173,7 @@ class LogCollection extends AbstractLog { /** * @return array + * @throws \Exception */ public function getTempData() : array{ return $this->getPrimaryLog()->getTempData(); diff --git a/app/main/lib/logging/RallyLog.php b/app/main/lib/logging/RallyLog.php index b6171c3c..7fa1b577 100644 --- a/app/main/lib/logging/RallyLog.php +++ b/app/main/lib/logging/RallyLog.php @@ -37,6 +37,7 @@ class RallyLog extends AbstractCharacterLog{ /** * @return string + * @throws \Exception\PathfinderException */ protected function getThumbUrl() : string{ $url = ''; diff --git a/app/main/lib/logging/handler/AbstractSlackWebhookHandler.php b/app/main/lib/logging/handler/AbstractSlackWebhookHandler.php index e045fe68..0a2fbab3 100644 --- a/app/main/lib/logging/handler/AbstractSlackWebhookHandler.php +++ b/app/main/lib/logging/handler/AbstractSlackWebhookHandler.php @@ -179,6 +179,7 @@ abstract class AbstractSlackWebhookHandler extends Handler\AbstractProcessingHan * @param array $attachment * @param array $characterData * @return array + * @throws \Exception\PathfinderException */ protected function setAuthor(array $attachment, array $characterData): array { if( !empty($characterData['id']) && !empty($characterData['name'])){ diff --git a/app/main/lib/logging/handler/SlackMapWebhookHandler.php b/app/main/lib/logging/handler/SlackMapWebhookHandler.php index 3d49aad6..53bb5733 100644 --- a/app/main/lib/logging/handler/SlackMapWebhookHandler.php +++ b/app/main/lib/logging/handler/SlackMapWebhookHandler.php @@ -15,6 +15,7 @@ class SlackMapWebhookHandler extends AbstractSlackWebhookHandler { /** * @param array $record * @return array + * @throws \Exception\PathfinderException */ protected function getSlackData(array $record) : array{ $postData = parent::getSlackData($record); diff --git a/app/main/lib/logging/handler/SlackRallyWebhookHandler.php b/app/main/lib/logging/handler/SlackRallyWebhookHandler.php index be61478f..1a53f29f 100644 --- a/app/main/lib/logging/handler/SlackRallyWebhookHandler.php +++ b/app/main/lib/logging/handler/SlackRallyWebhookHandler.php @@ -15,6 +15,7 @@ class SlackRallyWebhookHandler extends AbstractSlackWebhookHandler { /** * @param array $record * @return array + * @throws \Exception\PathfinderException */ protected function getSlackData(array $record) : array{ $postData = parent::getSlackData($record); diff --git a/app/main/lib/socket.php b/app/main/lib/socket.php index 98cc7e8e..0c8e1c34 100644 --- a/app/main/lib/socket.php +++ b/app/main/lib/socket.php @@ -87,9 +87,10 @@ class Socket { } /** - * @param $task + * @param string $task * @param string $load * @return bool|string + * @throws \ZMQSocketException */ public function sendData(string $task, $load = ''){ $response = false; diff --git a/app/main/lib/web.php b/app/main/lib/web.php index bf1ff510..6b0eb91d 100644 --- a/app/main/lib/web.php +++ b/app/main/lib/web.php @@ -100,6 +100,7 @@ class Web extends \Web { * @param array $additionalOptions * @param int $retryCount request counter for failed call * @return array|FALSE|mixed + * @throws \Exception\PathfinderException */ public function request($url,array $options = null, $additionalOptions = [], $retryCount = 0 ) { $f3 = \Base::instance(); diff --git a/app/main/model/abstractmaptrackingmodel.php b/app/main/model/abstractmaptrackingmodel.php index 5098eef4..fc8dea6d 100644 --- a/app/main/model/abstractmaptrackingmodel.php +++ b/app/main/model/abstractmaptrackingmodel.php @@ -52,6 +52,7 @@ abstract class AbstractMapTrackingModel extends BasicModel implements LogModelIn * @param $key * @param $val * @return bool + * @throws \Exception\ValidationException */ protected function validate_notDry($key, $val): bool { $valid = true; @@ -92,7 +93,7 @@ abstract class AbstractMapTrackingModel extends BasicModel implements LogModelIn /** * validates all required columns of this class * @return bool - * @throws \Exception\ValidationException + * @throws \Exception\DatabaseException */ public function isValid(): bool { if($valid = parent::isValid()){ diff --git a/app/main/model/alliancemodel.php b/app/main/model/alliancemodel.php index 23bcb703..69720f3f 100644 --- a/app/main/model/alliancemodel.php +++ b/app/main/model/alliancemodel.php @@ -57,6 +57,7 @@ class AllianceModel extends BasicModel { /** * get all maps for this alliance * @return array|mixed + * @throws \Exception\PathfinderException */ public function getMaps(){ $maps = []; diff --git a/app/main/model/basicmodel.php b/app/main/model/basicmodel.php index ba946bfa..c3aa9a58 100644 --- a/app/main/model/basicmodel.php +++ b/app/main/model/basicmodel.php @@ -822,6 +822,7 @@ abstract class BasicModel extends \DB\Cortex { * debug log function * @param string $text * @param string $type + * @throws \Exception\PathfinderException */ public static function log($text, $type = 'DEBUG'){ Controller\LogController::getLogger($type)->write($text); diff --git a/app/main/model/characterlogmodel.php b/app/main/model/characterlogmodel.php index a7c4b3bf..ae8c3271 100644 --- a/app/main/model/characterlogmodel.php +++ b/app/main/model/characterlogmodel.php @@ -139,25 +139,25 @@ class CharacterLogModel extends BasicModel { */ public function getData(){ - $logData = (object) []; - $logData->system = (object) []; - $logData->system->id = (int)$this->systemId; - $logData->system->name = $this->systemName; + $logData = (object) []; + $logData->system = (object) []; + $logData->system->id = (int)$this->systemId; + $logData->system->name = $this->systemName; - $logData->ship = (object) []; - $logData->ship->typeId = (int)$this->shipTypeId; - $logData->ship->typeName = $this->shipTypeName; - $logData->ship->id = $this->shipId; - $logData->ship->name = $this->shipName; - $logData->ship->mass = $this->shipMass; + $logData->ship = (object) []; + $logData->ship->typeId = (int)$this->shipTypeId; + $logData->ship->typeName = $this->shipTypeName; + $logData->ship->id = $this->shipId; + $logData->ship->name = $this->shipName; + $logData->ship->mass = $this->shipMass; - $logData->station = (object) []; - $logData->station->id = (int)$this->stationId; - $logData->station->name = $this->stationName; + $logData->station = (object) []; + $logData->station->id = (int)$this->stationId; + $logData->station->name = $this->stationName; - $logData->structure = (object) []; - $logData->structure->id = (int)$this->structureId; - $logData->structure->name = $this->structureName; + $logData->structure = (object) []; + $logData->structure->id = (int)$this->structureId; + $logData->structure->name = $this->structureName; return $logData; } @@ -166,6 +166,7 @@ class CharacterLogModel extends BasicModel { * setter for systemId * @param int $systemId * @return int + * @throws \Exception */ public function set_systemId($systemId){ if($systemId > 0){ @@ -219,6 +220,7 @@ class CharacterLogModel extends BasicModel { /** * update session data for active character * @param int $systemId + * @throws \Exception */ protected function updateCharacterSessionLocation(int $systemId){ $controller = new Controller(); diff --git a/app/main/model/charactermodel.php b/app/main/model/charactermodel.php index 890479be..dadbe64d 100644 --- a/app/main/model/charactermodel.php +++ b/app/main/model/charactermodel.php @@ -471,6 +471,7 @@ class CharacterModel extends BasicModel { /** * get ESI API "access_token" from OAuth * @return bool|string + * @throws \Exception\PathfinderException */ public function getAccessToken(){ $accessToken = false; @@ -541,6 +542,7 @@ class CharacterModel extends BasicModel { * checks whether this character is authorized to log in * -> check corp/ally whitelist config (pathfinder.ini) * @return bool + * @throws \Exception\PathfinderException */ public function isAuthorized(){ $authStatus = 'UNKNOWN'; @@ -596,6 +598,7 @@ class CharacterModel extends BasicModel { /** * get pathfinder roleId * @return int + * @throws \Exception\PathfinderException */ public function requestRoleId(){ $roleId = self::ROLES['MEMBER']; @@ -614,6 +617,7 @@ class CharacterModel extends BasicModel { /** * request all corporation roles granted to this character * @return array + * @throws \Exception\PathfinderException */ protected function requestRoles(){ $rolesData = []; @@ -655,6 +659,7 @@ class CharacterModel extends BasicModel { * -> API request for character log data * @param array $additionalOptions (optional) request options for cURL request * @return CharacterModel + * @throws \Exception */ public function updateLog($additionalOptions = []){ $deleteLog = false; @@ -874,6 +879,7 @@ class CharacterModel extends BasicModel { /** * update character data from CCPs ESI API * @return array (some status messages) + * @throws \Exception */ public function updateFromESI(){ $status = []; @@ -938,6 +944,7 @@ class CharacterModel extends BasicModel { * get mapModel by id and check if user has access * @param $mapId * @return MapModel|null + * @throws \Exception */ public function getMap($mapId){ /** @@ -957,6 +964,7 @@ class CharacterModel extends BasicModel { /** * get all accessible map models for this character * @return MapModel[] + * @throws \Exception\PathfinderException */ public function getMaps(){ $this->filter( diff --git a/app/main/model/characterstatusmodel.php b/app/main/model/characterstatusmodel.php index fe4764bc..51895fde 100644 --- a/app/main/model/characterstatusmodel.php +++ b/app/main/model/characterstatusmodel.php @@ -57,6 +57,7 @@ class CharacterStatusModel extends BasicModel { * @param null $table * @param null $fields * @return bool + * @throws \Exception */ public static function setup($db=null, $table=null, $fields=null){ $status = parent::setup($db,$table,$fields); diff --git a/app/main/model/connectionlogmodel.php b/app/main/model/connectionlogmodel.php new file mode 100644 index 00000000..d4cc21fe --- /dev/null +++ b/app/main/model/connectionlogmodel.php @@ -0,0 +1,84 @@ + [ + 'type' => Schema::DT_BOOL, + 'nullable' => false, + 'default' => 1, + 'index' => true + ], + 'connectionId' => [ + 'type' => Schema::DT_INT, + 'index' => true, + 'belongs-to-one' => 'Model\ConnectionModel', + 'constraint' => [ + [ + 'table' => 'connection', + 'on-delete' => 'CASCADE' + ] + ] + ], + 'shipTypeId' => [ + 'type' => Schema::DT_INT, + 'index' => true + ], + 'shipTypeName' => [ + 'type' => Schema::DT_VARCHAR128, + 'nullable' => false, + 'default' => '' + ], + 'shipMass' => [ + 'type' => Schema::DT_FLOAT, + 'nullable' => false, + 'default' => 0 + ], + 'characterId' => [ + 'type' => Schema::DT_INT, + 'index' => true + ], + 'characterName' => [ + 'type' => Schema::DT_VARCHAR128, + 'nullable' => false, + 'default' => '' + ] + ]; + + /** + * get connection log data + * @return \stdClass + */ + public function getData() : \stdClass { + $logData = (object) []; + $logData->id = $this->id; + + $logData->connection = (object) []; + $logData->connection->id = $this->get('connectionId', true); + + $logData->ship = (object) []; + $logData->ship->typeId = (int)$this->shipTypeId; + $logData->ship->typeName = $this->shipTypeName; + $logData->ship->mass = $this->shipMass; + + $logData->created = (object) []; + $logData->created->created = strtotime($this->created); + $logData->created->character = (object) []; + $logData->created->character->id = $this->characterId; + $logData->created->character->name = $this->characterName; + + return $logData; + } +} \ No newline at end of file diff --git a/app/main/model/connectionmodel.php b/app/main/model/connectionmodel.php index ec7cf2a1..68829c39 100644 --- a/app/main/model/connectionmodel.php +++ b/app/main/model/connectionmodel.php @@ -74,6 +74,9 @@ class ConnectionModel extends AbstractMapTrackingModel { ], 'signatures' => [ 'has-many' => ['Model\SystemSignatureModel', 'connectionId'] + ], + 'connectionLog' => [ + 'has-many' => ['Model\ConnectionLogModel', 'connectionId'] ] ]; @@ -101,20 +104,21 @@ class ConnectionModel extends AbstractMapTrackingModel { } /** - * get connection data as array + * get connection data * @param bool $addSignatureData + * @param bool $addLogData * @return \stdClass */ - public function getData($addSignatureData = false){ + public function getData($addSignatureData = false, $addLogData = false){ $connectionData = (object) []; - $connectionData->id = $this->id; - $connectionData->source = $this->source->id; - $connectionData->target = $this->target->id; - $connectionData->scope = $this->scope; - $connectionData->type = $this->type; - $connectionData->updated = strtotime($this->updated); - $connectionData->created = strtotime($this->created); - $connectionData->eolUpdated = strtotime($this->eolUpdated); + $connectionData->id = $this->id; + $connectionData->source = $this->source->id; + $connectionData->target = $this->target->id; + $connectionData->scope = $this->scope; + $connectionData->type = $this->type; + $connectionData->updated = strtotime($this->updated); + $connectionData->created = strtotime($this->created); + $connectionData->eolUpdated = strtotime($this->eolUpdated); if($addSignatureData){ if( !empty($signaturesData = $this->getSignaturesData()) ){ @@ -122,6 +126,12 @@ class ConnectionModel extends AbstractMapTrackingModel { } } + if($addLogData){ + if( !empty($logsData = $this->getLogsData()) ){ + $connectionData->logs = $logsData; + } + } + return $connectionData; } @@ -193,6 +203,7 @@ class ConnectionModel extends AbstractMapTrackingModel { /** * check whether this model is valid or not * @return bool + * @throws \Exception\DatabaseException */ public function isValid(): bool { if($valid = parent::isValid()){ @@ -218,6 +229,7 @@ class ConnectionModel extends AbstractMapTrackingModel { * @param ConnectionModel $self * @param $pkeys * @return bool + * @throws \Exception\DatabaseException */ public function beforeInsertEvent($self, $pkeys){ // check for "default" connection type and add them if missing @@ -269,6 +281,7 @@ class ConnectionModel extends AbstractMapTrackingModel { /** * @param string $action * @return Logging\LogInterface + * @throws \Exception\PathfinderException */ public function newLog($action = ''): Logging\LogInterface{ return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData()); @@ -330,11 +343,29 @@ class ConnectionModel extends AbstractMapTrackingModel { return $signatures; } + /** + * get all jump logs that are connected with this connection + * @return array|mixed + */ + public function getLogs(){ + $logs = []; + $this->filter('connectionLog', [ + 'active = :active', + ':active' => 1 + ]); + + if($this->connectionLog){ + $logs = $this->connectionLog; + } + + return $logs; + } + /** * get all signature data linked to this connection * @return array */ - public function getSignaturesData(){ + public function getSignaturesData() : array { $signaturesData = []; $signatures = $this->getSignatures(); @@ -345,6 +376,36 @@ class ConnectionModel extends AbstractMapTrackingModel { return $signaturesData; } + /** + * get all connection log data linked to this connection + * @return array + */ + public function getLogsData() : array{ + $logsData = []; + $logs = $this->getLogs(); + + foreach($logs as $log){ + $logsData[] = $log->getData(); + } + + return $logsData; + } + + public function logMass(CharacterLogModel $characterLog){ + if( !$characterLog->dry() ){ + $log = $this->rel('connectionLog'); + $log->shipTypeId = $characterLog->shipTypeId; + $log->shipTypeName = $characterLog->shipTypeName; + $log->shipMass = $characterLog->shipMass; + $log->characterId = $characterLog->characterId->_id; + $log->characterName = $characterLog->characterId->name; + $log->connectionId = $this; + $log->save(); + } + + return $this; + } + /** * overwrites parent * @param null $db diff --git a/app/main/model/connectionscopemodel.php b/app/main/model/connectionscopemodel.php index e72f8438..bec69ffa 100644 --- a/app/main/model/connectionscopemodel.php +++ b/app/main/model/connectionscopemodel.php @@ -66,6 +66,7 @@ class ConnectionScopeModel extends BasicModel{ * @param null $table * @param null $fields * @return bool + * @throws \Exception */ public static function setup($db=null, $table=null, $fields=null){ $status = parent::setup($db,$table,$fields); diff --git a/app/main/model/corporationmodel.php b/app/main/model/corporationmodel.php index a503d7df..5e982f46 100644 --- a/app/main/model/corporationmodel.php +++ b/app/main/model/corporationmodel.php @@ -128,6 +128,7 @@ class CorporationModel extends BasicModel { /** * get all maps for this corporation * @return MapModel[] + * @throws \Exception\PathfinderException */ public function getMaps(){ $maps = []; diff --git a/app/main/model/mapmodel.php b/app/main/model/mapmodel.php index 951bf630..390d2815 100644 --- a/app/main/model/mapmodel.php +++ b/app/main/model/mapmodel.php @@ -181,6 +181,8 @@ class MapModel extends AbstractMapTrackingModel { * get map data * -> this includes system and connection data as well! * @return \stdClass + * @throws PathfinderException + * @throws \Exception */ public function getData(){ // check if there is cached data @@ -294,6 +296,7 @@ class MapModel extends AbstractMapTrackingModel { * @param string $key * @param string $val * @return bool + * @throws \Exception\ValidationException */ protected function validate_name(string $key, string $val): bool { $valid = true; @@ -309,6 +312,7 @@ class MapModel extends AbstractMapTrackingModel { * @param string $key * @param string $val * @return bool + * @throws \Exception\ValidationException */ protected function validate_slackWebHookURL(string $key, string $val): bool { $valid = true; @@ -401,6 +405,7 @@ class MapModel extends AbstractMapTrackingModel { * -> check for "inactive" systems on this map first! * @param int $systemId * @return SystemModel + * @throws \Exception */ public function getNewSystem($systemId){ // check for "inactive" system @@ -510,6 +515,7 @@ class MapModel extends AbstractMapTrackingModel { /** * get all system data for all systems in this map * @return \stdClass[] + * @throws \Exception */ public function getSystemData(){ $systemData = []; @@ -545,10 +551,11 @@ class MapModel extends AbstractMapTrackingModel { /** * get all connections in this map + * @param null $connectionIds * @param string $scope * @return ConnectionModel[] */ - public function getConnections($scope = ''){ + public function getConnections($connectionIds = null, $scope = ''){ $connections = []; $query = [ @@ -561,6 +568,11 @@ class MapModel extends AbstractMapTrackingModel { $query[':scope'] = $scope; } + if(!empty($connectionIds)){ + $query[0] .= ' AND id IN (?)'; + $query[] = $connectionIds; + } + $this->filter('connections', $query); if($this->connections){ @@ -591,6 +603,7 @@ class MapModel extends AbstractMapTrackingModel { /** * set map access for an object (character, corporation or alliance) * @param $obj + * @throws \Exception */ public function setAccess($obj){ @@ -689,6 +702,7 @@ class MapModel extends AbstractMapTrackingModel { * checks whether a character has access to this map or not * @param CharacterModel $characterModel * @return bool + * @throws PathfinderException */ public function hasAccess(CharacterModel $characterModel){ $hasAccess = false; @@ -834,6 +848,7 @@ class MapModel extends AbstractMapTrackingModel { /** * @param string $action * @return Logging\LogInterface + * @throws PathfinderException */ public function newLog($action = ''): Logging\LogInterface{ $logChannelData = $this->getLogChannelData(); @@ -901,6 +916,7 @@ class MapModel extends AbstractMapTrackingModel { /** * check if "activity logging" is enabled for this map type * @return bool + * @throws PathfinderException */ public function isActivityLogEnabled(): bool { return $this->logActivity && (bool) Config::getMapsDefaultConfig($this->typeId->name)['log_activity_enabled']; @@ -909,6 +925,7 @@ class MapModel extends AbstractMapTrackingModel { /** * check if "history logging" is enabled for this map type * @return bool + * @throws PathfinderException */ public function isHistoryLogEnabled(): bool { return $this->logHistory && (bool) Config::getMapsDefaultConfig($this->typeId->name)['log_history_enabled']; @@ -946,6 +963,7 @@ class MapModel extends AbstractMapTrackingModel { * check if "E-Mail" Log is enabled for this map * @param string $type * @return bool + * @throws PathfinderException */ public function isMailSendEnabled(string $type): bool{ $enabled = false; @@ -1005,6 +1023,7 @@ class MapModel extends AbstractMapTrackingModel { * @param string $type * @param bool $addJson * @return \stdClass + * @throws PathfinderException */ public function getSMTPConfig(string $type, bool $addJson = true): \stdClass{ $config = Config::getSMTPConfig(); @@ -1141,6 +1160,8 @@ class MapModel extends AbstractMapTrackingModel { * get all active characters (with active log) * grouped by systems * @return \stdClass + * @throws PathfinderException + * @throws \Exception */ public function getUserData(){ diff --git a/app/main/model/mapscopemodel.php b/app/main/model/mapscopemodel.php index c0d78973..5928339f 100644 --- a/app/main/model/mapscopemodel.php +++ b/app/main/model/mapscopemodel.php @@ -59,6 +59,7 @@ class MapScopeModel extends BasicModel{ * @param null $table * @param null $fields * @return bool + * @throws \Exception */ public static function setup($db=null, $table=null, $fields=null){ $status = parent::setup($db,$table,$fields); diff --git a/app/main/model/maptypemodel.php b/app/main/model/maptypemodel.php index 3dc465fb..6cacf340 100644 --- a/app/main/model/maptypemodel.php +++ b/app/main/model/maptypemodel.php @@ -87,6 +87,7 @@ class MapTypeModel extends BasicModel{ * @param null $table * @param null $fields * @return bool + * @throws \Exception */ public static function setup($db=null, $table=null, $fields=null){ $status = parent::setup($db,$table,$fields); diff --git a/app/main/model/systemmodel.php b/app/main/model/systemmodel.php index b749671b..884b4b95 100644 --- a/app/main/model/systemmodel.php +++ b/app/main/model/systemmodel.php @@ -410,6 +410,7 @@ class SystemModel extends AbstractMapTrackingModel { /** * @param string $action * @return Logging\LogInterface + * @throws \Exception\PathfinderException */ public function newLog($action = ''): Logging\LogInterface{ return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData()); @@ -567,6 +568,7 @@ class SystemModel extends AbstractMapTrackingModel { * -> send to an Email * @param array $rallyData * @param CharacterModel $characterModel + * @throws \Exception\PathfinderException */ public function sendRallyPoke(array $rallyData, CharacterModel $characterModel){ // rally log needs at least one handler to be valid diff --git a/app/main/model/systemsignaturemodel.php b/app/main/model/systemsignaturemodel.php index bd457c46..dab0d37b 100644 --- a/app/main/model/systemsignaturemodel.php +++ b/app/main/model/systemsignaturemodel.php @@ -169,6 +169,7 @@ class SystemSignatureModel extends AbstractMapTrackingModel { * @param string $key * @param string $val * @return bool + * @throws \Exception\ValidationException */ protected function validate_name(string $key, string $val): bool { $valid = true; @@ -182,6 +183,7 @@ class SystemSignatureModel extends AbstractMapTrackingModel { /** * @param string $action * @return Logging\LogInterface + * @throws \Exception\PathfinderException */ public function newLog($action = ''): Logging\LogInterface{ return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData()); diff --git a/app/main/model/systemstatusmodel.php b/app/main/model/systemstatusmodel.php index 39eac501..01c62aed 100644 --- a/app/main/model/systemstatusmodel.php +++ b/app/main/model/systemstatusmodel.php @@ -83,6 +83,7 @@ class SystemStatusModel extends BasicModel { * @param null $table * @param null $fields * @return bool + * @throws \Exception */ public static function setup($db=null, $table=null, $fields=null){ $status = parent::setup($db,$table,$fields); diff --git a/app/main/model/systemtypemodel.php b/app/main/model/systemtypemodel.php index 1b071716..e2c15439 100644 --- a/app/main/model/systemtypemodel.php +++ b/app/main/model/systemtypemodel.php @@ -45,6 +45,7 @@ class SystemTypeModel extends BasicModel { * @param null $table * @param null $fields * @return bool + * @throws \Exception */ public static function setup($db=null, $table=null, $fields=null){ $status = parent::setup($db,$table,$fields); diff --git a/app/main/model/universe/constellationmodel.php b/app/main/model/universe/constellationmodel.php index 29c33516..4d8cb1b4 100644 --- a/app/main/model/universe/constellationmodel.php +++ b/app/main/model/universe/constellationmodel.php @@ -54,4 +54,13 @@ class ConstellationModel extends BasicUniverseModel { ] ]; + /** + * @param int $id + * @param string $accessToken + * @param array $additionalOptions + */ + protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){ + + } + } \ No newline at end of file diff --git a/app/main/model/universe/regionmodel.php b/app/main/model/universe/regionmodel.php index 44db09bd..6f373bd1 100644 --- a/app/main/model/universe/regionmodel.php +++ b/app/main/model/universe/regionmodel.php @@ -33,4 +33,13 @@ class RegionModel extends BasicUniverseModel { 'has-many' => ['Model\Universe\ConstellationModel', 'regionId'] ], ]; + + /** + * @param int $id + * @param string $accessToken + * @param array $additionalOptions + */ + protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){ + + } } \ No newline at end of file diff --git a/app/main/model/usermodel.php b/app/main/model/usermodel.php index f6a8de3c..27f4a3f5 100644 --- a/app/main/model/usermodel.php +++ b/app/main/model/usermodel.php @@ -49,6 +49,7 @@ class UserModel extends BasicModel { * -> ! caution ! this function returns sensitive data! (e.g. email,..) * -> user getSimpleData() for faster performance and public user data * @return \stdClass + * @throws Exception */ public function getData(){ @@ -93,6 +94,7 @@ class UserModel extends BasicModel { * @param UserModel $self * @param $pkeys * @return bool + * @throws Exception\PathfinderException * @throws Exception\RegistrationException */ public function beforeInsertEvent($self, $pkeys){ @@ -135,6 +137,7 @@ class UserModel extends BasicModel { /** * checks whether user has a valid email address and pathfinder has a valid SMTP config * @return bool + * @throws Exception\PathfinderException */ protected function isMailSendEnabled() : bool{ return Config::isValidSMTPConfig($this->getSMTPConfig()); @@ -143,6 +146,7 @@ class UserModel extends BasicModel { /** * get SMTP config for this user * @return \stdClass + * @throws Exception\PathfinderException */ protected function getSMTPConfig() : \stdClass{ $config = Config::getSMTPConfig(); @@ -155,6 +159,7 @@ class UserModel extends BasicModel { * @param string $key * @param string $val * @return bool + * @throws Exception\ValidationException */ protected function validate_name(string $key, string $val): bool { $valid = true; @@ -173,6 +178,7 @@ class UserModel extends BasicModel { * @param string $key * @param string $val * @return bool + * @throws Exception\ValidationException */ protected function validate_email(string $key, string $val): bool { $valid = true; @@ -207,6 +213,7 @@ class UserModel extends BasicModel { * @param int $characterId * @param bool $objectCheck * @return array + * @throws Exception */ public function getSessionCharacterData($characterId = 0, $objectCheck = true){ $data = []; @@ -289,6 +296,7 @@ class UserModel extends BasicModel { * -> EITHER - the current active one for the current user * -> OR - get the first active one * @return null|CharacterModel + * @throws Exception */ public function getActiveCharacter(){ $activeCharacter = null; diff --git a/app/main/model/wormholemodel.php b/app/main/model/wormholemodel.php index 3ddd18b8..022f3c8b 100644 --- a/app/main/model/wormholemodel.php +++ b/app/main/model/wormholemodel.php @@ -59,47 +59,27 @@ class WormholeModel extends BasicModel { */ protected $addStaticFields = false; - /** - * format mass values - * - no decimal separator - * - char '.' for thousands separator - * @param $value - * @return string - */ - static function formatMassValue($value){ - return number_format( $value, 0, '', '.' ); - } - /** * get wormhole data as object * @return object */ public function getData(){ - $systemStaticData = (object) []; $systemStaticData->name = $this->name; $systemStaticData->security = $this->security; // total (max) available wormhole mass - $systemStaticData->massTotal = (object) []; - $systemStaticData->massTotal->value = $this->massTotal; - $systemStaticData->massTotal->format = self::formatMassValue($this->massTotal) . ' Kg'; + $systemStaticData->massTotal = $this->massTotal; // individual jump mass (max) per jump - $systemStaticData->massIndividual = (object) []; - $systemStaticData->massIndividual->value = $this->massIndividual; - $systemStaticData->massIndividual->format = self::formatMassValue($this->massIndividual) . ' Kg'; + $systemStaticData->massIndividual = $this->massIndividual; // lifetime (max) for this wormhole - $systemStaticData->maxStableTime = (object) []; - $systemStaticData->maxStableTime->value = $this->maxStableTime; - $systemStaticData->maxStableTime->format = $this->maxStableTime . ' h'; + $systemStaticData->maxStableTime = $this->maxStableTime; // mass regeneration value per day if($this->massRegeneration > 0){ - $systemStaticData->massRegeneration = (object) []; - $systemStaticData->massRegeneration->value = $this->massRegeneration; - $systemStaticData->massRegeneration->format = self::formatMassValue($this->massRegeneration) . ' Kg/day'; + $systemStaticData->massRegeneration = $this->massRegeneration; } return $systemStaticData; diff --git a/app/pathfinder.ini b/app/pathfinder.ini index 54063594..4d1f156a 100644 --- a/app/pathfinder.ini +++ b/app/pathfinder.ini @@ -3,7 +3,7 @@ [PATHFINDER] NAME = Pathfinder ; installed version (used for CSS/JS cache busting) -VERSION = v1.3.0 +VERSION = v1.3.1 ; contact information [optional] CONTACT = https://github.com/exodus4d ; public contact email [optional] diff --git a/composer-dev.json b/composer-dev.json index a0b84c78..ef935787 100644 --- a/composer-dev.json +++ b/composer-dev.json @@ -21,7 +21,7 @@ }], "require": { "php-64bit": ">=7.0", - "ext-curl": ">=7.0", + "ext-curl": "*", "ext-zmq": ">=1.1.3", "react/zmq": "0.3.*", "monolog/monolog": "1.*", diff --git a/composer.json b/composer.json index 086fddf4..a859c312 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }], "require": { "php-64bit": ">=7.0", - "ext-curl": ">=7.0", + "ext-curl": "*", "ext-zmq": ">=1.1.3", "react/zmq": "0.3.*", "monolog/monolog": "1.*", diff --git a/gulpfile.js b/gulpfile.js index b39faf7f..c33f6c53 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -177,12 +177,14 @@ let CONF = { JS: { UGLIFY: options.hasOwnProperty('jsUglify') ? options.jsUglify === 'true': undefined, SOURCEMAPS: options.hasOwnProperty('jsSourcemaps') ? options.jsSourcemaps === 'true': undefined, + GZIP: options.hasOwnProperty('jsGzip') ? options.jsGzip === 'true': undefined, + BROTLI: options.hasOwnProperty('jsBrotli') ? options.jsBrotli === 'true': undefined }, CSS: { SOURCEMAPS: options.hasOwnProperty('cssSourcemaps') ? options.cssSourcemaps === 'true': undefined, + GZIP: options.hasOwnProperty('cssGzip') ? options.cssGzip === 'true': undefined, + BROTLI: options.hasOwnProperty('cssBrotli') ? options.cssBrotli === 'true': undefined }, - GZIP: options.hasOwnProperty('gzip') ? options.gzip === 'true': undefined, - BROTLI: options.hasOwnProperty('brotli') ? options.brotli === 'true': undefined, DEBUG: false }; @@ -271,18 +273,21 @@ let printHelp = () => { ${chalk.cyan('tasks:')} ${chalk.gray('help')} This view ${chalk.gray('default')} Development environment. Working with row src files and file watcher, default: - ${chalk.gray('')} ${chalk.gray('--jsUglify=false --jsSourcemaps=false --cssSourcemaps=false --gzip=false --brotli=false')} + ${chalk.gray('')} ${chalk.gray('--jsUglify=false --jsSourcemaps=false --cssSourcemaps=false --jsGzip=false --cssGzip=false --jsBrotli=false --cssBrotli=false')} ${chalk.gray('production')} Production build. Concat and uglify static resources, default: - ${chalk.gray('')} ${chalk.gray('--jsUglify=true --jsSourcemaps=true --cssSourcemaps=true --gzip=true --brotli=true')} + ${chalk.gray('')} ${chalk.gray('--jsUglify=true --jsSourcemaps=true --cssSourcemaps=true --jsGzip=true --cssGzip=true --jsBrotli=true --cssBrotli=true')} ${chalk.cyan('options:')} - ${chalk.gray('--tag')} Set build version. ${chalk.gray('default: --tag="v1.2.4" -> dest path: public/js/v1.2.4')} - ${chalk.gray('--jsUglify')} Set js uglification. ${chalk.gray('(true || false)')} - ${chalk.gray('--jsSourcemaps')} Set js sourcemaps generation. ${chalk.gray('(true || false)')} - ${chalk.gray('--cssSourcemaps')} Set CSS sourcemaps generation. ${chalk.gray('(true || false)')} - ${chalk.gray('--gzip')} Set "gzip" compression mode. ${chalk.gray('(true || false)')} - ${chalk.gray('--brotli')} Set "brotli" compression mode. ${chalk.gray('(true || false)')} - ${chalk.gray('--debug')} Set debug mode (more output). ${chalk.gray('(true || false)')} + ${chalk.gray('--tag')} Set build version. ${chalk.gray('default: --tag="v1.2.4" -> dest path: public/js/v1.2.4')} + ${chalk.gray('--jsUglify')} Set js uglification. ${chalk.gray('(true || false)')} + ${chalk.gray('--jsSourcemaps')} Set js sourcemaps generation. ${chalk.gray('(true || false)')} + ${chalk.gray('--jsGzip')} Set js "gzip" compression mode. ${chalk.gray('(true || false)')} + ${chalk.gray('--jsBrotli')} Set js "brotli" compression mode. ${chalk.gray('(true || false)')} + + ${chalk.gray('--cssSourcemaps')} Set CSS sourcemaps generation. ${chalk.gray('(true || false)')} + ${chalk.gray('--cssGzip')} Set CSS "gzip" compression mode. ${chalk.gray('(true || false)')} + ${chalk.gray('--cssBrotli')} Set CSS "brotli" compression mode. ${chalk.gray('(true || false)')} + ${chalk.gray('--debug')} Set debug mode (more output). ${chalk.gray('(true || false)')} `) .log(chalk.cyan('='.repeat(cliBoxLength))) .log(''); @@ -295,8 +300,8 @@ let printJsSummary = () => { let tableHead = trackTable.cols; let byteCols = [1,3,6,9,12]; let percentCols = [2, 5, 8, 10]; - let sortCol = (CONF.BROTLI || CONF.GZIP || CONF.JS.UGLIFY) ? 10 : CONF.JS.SOURCEMAPS ? 3 : 1; - let refAllCol = CONF.BROTLI ? 9 : CONF.GZIP ? 6 : CONF.JS.UGLIFY ? 3 : CONF.JS.SOURCEMAPS ? 3 : 1; + let sortCol = (CONF.JS.BROTLI || CONF.CSS.BROTLI || CONF.JS.GZIP || CONF.CSS.GZIP || CONF.JS.UGLIFY) ? 10 : CONF.JS.SOURCEMAPS ? 3 : 1; + let refAllCol = (CONF.JS.BROTLI || CONF.CSS.BROTLI) ? 9 : (CONF.JS.GZIP || CONF.CSS.GZIP) ? 6 : CONF.JS.UGLIFY ? 3 : CONF.JS.SOURCEMAPS ? 3 : 1; let highLightSections = {src_percent: [], gzip_percent: [], brotli_percent: [], all_percent: []}; let highLightRow = { success: JSON.parse(JSON.stringify(highLightSections)), @@ -446,7 +451,7 @@ let printJsSummary = () => { table.removeColumn(12); table.removeColumn(11); - if(!CONF.BROTLI && !CONF.GZIP){ + if(!CONF.JS.BROTLI && !CONF.CSS.BROTLI && !CONF.JS.GZIP && !CONF.CSS.GZIP){ table.removeColumn(10); table.removeColumn(9); @@ -559,16 +564,31 @@ gulp.task('task:diffJS', () => { .pipe(gulp.dest(PATH.JS.DIST_BUILD, {overwrite: false})); }); -gulp.task('task:gzipAssets', () => { - let filterGzip = filter(file => CONF.GZIP); - let fileExt = ['js', 'css']; - let srcModules = [ - PATH.ASSETS.DIST +'/**/*.{' + fileExt.join(',') + '}', - '!' + PATH.ASSETS.DIST + '/js/' + CONF.TAG + '{,/**/*}' - ]; +/** + * get assets filter options e.g. for gZip or Brotli assets + * @param compression + * @param fileExt + * @returns {{srcModules: *[], fileFilter}} + */ +let getAssetFilterOptions = (compression, fileExt) => { + return { + srcModules: [ + PATH.ASSETS.DIST +'/**/*.' + fileExt, + '!' + PATH.ASSETS.DIST + '/js/' + CONF.TAG + '{,/**/*}' + ], + fileFilter: filter(file => CONF[fileExt.toUpperCase()][compression.toUpperCase()]) + }; +}; - return gulp.src(srcModules, {base: 'public', since: gulp.lastRun('task:gzipAssets')}) - .pipe(filterGzip) +/** + * build gZip or Brotli assets + * @param config + * @param taskName + * @returns {JQuery.PromiseBase} + */ +let gzipAssets = (config, taskName) => { + return gulp.src(config.srcModules, {base: 'public', since: gulp.lastRun(taskName)}) + .pipe(config.fileFilter) .pipe(debug({title: 'Gzip asses dest: ', showFiles: false})) .pipe(bytediff.start()) .pipe(gzip(gZipOptions)) @@ -581,18 +601,17 @@ gulp.task('task:gzipAssets', () => { } })) .pipe(gulp.dest(PATH.ASSETS.DIST)); -}); +}; -gulp.task('task:brotliAssets', () => { - let filterBrotli = filter(file => CONF.BROTLI); - let fileExt = ['js', 'css']; - let srcModules = [ - PATH.ASSETS.DIST +'/**/*.{' + fileExt.join(',') + '}', - '!' + PATH.ASSETS.DIST + '/js/' + CONF.TAG + '{,/**/*}' - ]; - - return gulp.src(srcModules, {base: 'public', since: gulp.lastRun('task:brotliAssets')}) - .pipe(filterBrotli) +/** + * build Brotli or Brotli assets + * @param config + * @param taskName + * @returns {JQuery.PromiseBase} + */ +let brotliAssets = (config, taskName) => { + return gulp.src(config.srcModules, {base: 'public', since: gulp.lastRun(taskName)}) + .pipe(config.fileFilter) .pipe(debug({title: 'Brotli asses dest: ', showFiles: false})) .pipe(bytediff.start()) .pipe(brotli.compress(brotliOptions)) @@ -605,6 +624,22 @@ gulp.task('task:brotliAssets', () => { } })) .pipe(gulp.dest(PATH.ASSETS.DIST)); +}; + +gulp.task('task:gzipJsAssets', () => { + return gzipAssets(getAssetFilterOptions('gzip', 'js'), 'task:gzipJsAssets'); +}); + +gulp.task('task:gzipCssAssets', () => { + return gzipAssets(getAssetFilterOptions('gzip', 'css'), 'task:gzipCssAssets'); +}); + +gulp.task('task:brotliJsAssets', () => { + return brotliAssets(getAssetFilterOptions('brotli', 'js'), 'task:brotliJsAssets'); +}); + +gulp.task('task:brotliCssAssets', () => { + return brotliAssets(getAssetFilterOptions('brotli', 'css'), 'task:brotliCssAssets'); }); /** @@ -714,13 +749,15 @@ gulp.task('task:configDevelop', let CONF_DEVELOP = { JS: { UGLIFY: false, - SOURCEMAPS: false + SOURCEMAPS: false, + GZIP: false, + BROTLI: false }, CSS: { - SOURCEMAPS: false - }, - GZIP: false, - BROTLI: false + SOURCEMAPS: false, + GZIP: false, + BROTLI: false + } }; CONF = mergeConf(CONF, CONF_DEVELOP); @@ -739,13 +776,15 @@ gulp.task('task:configProduction', let CONF_PRODUCTION = { JS: { UGLIFY: true, - SOURCEMAPS: true + SOURCEMAPS: true, + GZIP: true, + BROTLI: true }, CSS: { - SOURCEMAPS: true - }, - GZIP: true, - BROTLI: true + SOURCEMAPS: true, + GZIP: true, + BROTLI: true + } }; CONF = mergeConf(CONF, CONF_PRODUCTION); @@ -760,8 +799,8 @@ gulp.task('task:configProduction', * updates JS destination move to (final) dir */ gulp.task('task:updateJsDest', gulp.series( - 'task:gzipAssets', - 'task:brotliAssets', + 'task:gzipJsAssets', + 'task:brotliJsAssets', 'task:renameJsDest', 'task:printJsSummary', 'task:cleanJsBuild' @@ -809,8 +848,8 @@ gulp.task( gulp.series( 'task:buildCss', // 'task:cleanCss', - 'task:gzipAssets', - 'task:brotliAssets', + 'task:gzipCssAssets', + 'task:brotliCssAssets', 'task:printJsSummary' ) ); diff --git a/js/app.js b/js/app.js index 6683161c..978e62de 100644 --- a/js/app.js +++ b/js/app.js @@ -1,3 +1,5 @@ +'use strict'; + // main script path var mainScriptPath = document.body.getAttribute('data-script'); @@ -40,7 +42,7 @@ requirejs.config({ raphael: 'lib/raphael-min', // v2.1.2 Raphaël - required for morris (dependency) bootbox: 'lib/bootbox.min', // v4.4.0 Bootbox.js - custom dialogs - http://bootboxjs.com easyPieChart: 'lib/jquery.easypiechart.min', // v2.1.6 Easy Pie Chart - HTML 5 pie charts - http://rendro.github.io/easy-pie-chart - peityInlineChart: 'lib/jquery.peity.min', // v3.2.0 Inline Chart - http://benpickles.github.io/peity/ + peityInlineChart: 'lib/jquery.peity.min', // v3.2.1 Inline Chart - http://benpickles.github.io/peity/ dragToSelect: 'lib/jquery.dragToSelect', // v1.1 Drag to Select - http://andreaslagerkvist.com/jquery/drag-to-select hoverIntent: 'lib/jquery.hoverIntent.minified', // v1.8.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html fullScreen: 'lib/jquery.fullscreen.min', // v0.6.0 Full screen mode - https://github.com/private-face/jquery.fullscreen @@ -51,8 +53,9 @@ requirejs.config({ blueImpGalleryHelper: 'lib/blueimp-helper', // helper function for Blue Imp Gallery blueImpGalleryBootstrap: 'lib/bootstrap-image-gallery', // v3.4.2 Bootstrap extension for Blue Imp Gallery - https://blueimp.github.io/Bootstrap-Image-Gallery bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.5 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation - bootstrapToggle: 'lib/bootstrap2-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com + bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com lazyload: 'lib/jquery.lazyload.min', // v1.9.5 LazyLoader images - http://www.appelsiini.net/projects/lazyload + sortable: 'lib/sortable.min', // v1.6.0 Sortable - drag&drop reorder - https://github.com/rubaxa/Sortable // header animation easePack: 'lib/EasePack.min', @@ -126,7 +129,10 @@ requirejs.config({ }, morris: { deps: ['jquery', 'raphael'], - exports: 'Morris' + exports: 'Morris', + init: function ($, Raphael) { + window.Raphael = Raphael; + } }, pnotify: { deps : ['jquery'] diff --git a/js/app/counter.js b/js/app/counter.js index 040a0e02..7562c130 100644 --- a/js/app/counter.js +++ b/js/app/counter.js @@ -14,49 +14,77 @@ define([ * update element with time information * @param element * @param tempDate + * @param round */ - let updateDateDiff = function(element, tempDate){ + let updateDateDiff = function(element, tempDate, round){ let diff = Util.getTimeDiffParts(tempDate, new Date()); let days = diff.days; let hrs = diff.hours; let min = diff.min; let leftSec = diff.sec; - let value = []; + let parts = []; if( - days > 0 || - value.length > 0 + round === 'd' && + days >= 1 ){ - value.push('' + days + 'd' + ''); - } - if( - hrs > 0 || - value.length > 0 - ){ - value.push('' + hrs + 'h' + ''); - } - if( - min > 0 || - value.length > 0 - ){ - value.push('' + min + 'm' + ''); + parts.push('' + '> 1d' + ''); + }else{ + if( + days > 0 || + parts.length > 0 + ){ + parts.push('' + days + 'd' + ''); + } + if( + hrs > 0 || + parts.length > 0 + ){ + parts.push('' + hrs + 'h' + ''); + } + if( + min > 0 || + parts.length > 0 + ){ + parts.push('' + min + 'm' + ''); + } + + if( + leftSec >= 0 || + parts.length > 0 + ){ + parts.push('' + leftSec + 's' + ''); + } } - if( - leftSec >= 0 || - value.length > 0 - ){ - value.push('' + leftSec + 's' + ''); - } - element.html(value.join(' ')); + + element.html(parts.join(' ')); + }; + + /** + * destroy all active counter recursive + */ + $.fn.destroyTimestampCounter = function(){ + return this.each(function(){ + let element = $(this); + element.find('[data-counter="init"]').each(function(){ + let interval = $(this).data('interval'); + if(interval){ + clearInterval(interval); + element.removeAttr('data-counter') + .removeData('interval') + .removeClass('stopCounter'); + } + }); + }); }; /** * init a live counter based on a unix timestamp - * @returns {*} + * @param round string e.g. 'd' => round days */ - $.fn.initTimestampCounter = function(){ + $.fn.initTimestampCounter = function(round){ return this.each(function(){ let element = $(this); let timestamp = parseInt( element.text() ); @@ -68,7 +96,7 @@ define([ let date = new Date( timestamp * 1000); - updateDateDiff(element, date); + updateDateDiff(element, date, round); // show element (if invisible) after first update element.css({'visibility': 'initial'}); @@ -77,7 +105,7 @@ define([ // update element with current time if( !element.hasClass('stopCounter')){ - updateDateDiff(element, date); + updateDateDiff(element, date, round); }else{ clearInterval( element.data('interval') ); } diff --git a/js/app/init.js b/js/app/init.js index 79360978..bbaab565 100644 --- a/js/app/init.js +++ b/js/app/init.js @@ -327,6 +327,9 @@ define(['jquery'], function($) { location: 0.6 }] ] + }, + active: { + cssClass: 'pf-map-connection-active' } }, // signature groups diff --git a/js/app/logging.js b/js/app/logging.js index 0cfc5cf6..70cad80f 100644 --- a/js/app/logging.js +++ b/js/app/logging.js @@ -205,7 +205,6 @@ define([ class: config.dialogDynamicAreaClass }).append( graphElement ); - // headline let headline = $('

', { text: key }).prepend( diff --git a/js/app/login.js b/js/app/login.js index 30ed0997..82d87079 100644 --- a/js/app/login.js +++ b/js/app/login.js @@ -76,42 +76,6 @@ define([ defaultAcceptCookieExpire: 365 // default expire for "accept coolies" cookie }; - /** - * set a cookie - * @param cname - * @param cvalue - * @param exdays - */ - let setCookie = function(cname, cvalue, exdays) { - let d = new Date(); - d.setTime(d.getTime() + (exdays*24*60*60*1000)); - let expires = 'expires=' + d.toUTCString(); - let path = 'path=' + Util.getDocumentPath(); - document.cookie = cname + '=' + cvalue + '; ' + expires + '; ' + path; - }; - - /** - * get cookie value by name - * @param cname - * @returns {*} - */ - let getCookie = function(cname) { - let name = cname + '='; - let ca = document.cookie.split(';'); - - for(let i = 0; i allow detaching them - maxConnections: 10, // due to isTarget is true, this is the max count of !out!-going connections + maxConnections: 10, // due to isTarget is true, this is the max count of !out!-going connections // isSource:true, anchor: 'Continuous' }, @@ -93,7 +94,7 @@ define([ filter: '.' + config.systemHeadNameClass, isSource:true, //isTarget:true, - //allowLoopback: false, // loopback connections are not allowed + //allowLoopBack: false, // loopBack connections are not allowed cssClass: config.endpointTargetClass, dropOptions: { hoverClass: config.systemActiveClass, @@ -580,7 +581,8 @@ define([ if(alias !== data.alias){ // alias changed - system.find('.' + config.systemHeadNameClass).editable('setValue', data.alias); + alias = data.alias ? data.alias : data.name; + system.find('.' + config.systemHeadNameClass).editable('setValue', alias); } } @@ -626,6 +628,7 @@ define([ // get map container let mapElement = $( map.getContainer() ); + let connectionCanvas = $(connection.canvas); // if the connection already exists -> do not set it twice connection.unbind('contextmenu').bind('contextmenu', function(component, e) { @@ -646,7 +649,7 @@ define([ * init context menu for all connections * must be triggered manually on demand */ - $(connection.canvas).contextMenu({ + connectionCanvas.contextMenu({ menuSelector: '#' + config.connectionContextMenuId, menuSelected: function (params){ @@ -708,6 +711,31 @@ define([ } }); + // connection click events ========================================================================== + + let single = function(e){ + let connection = this; + // left mouse button + if(e.which === 1){ + if(e.ctrlKey === true){ + // an "active" connection is required before adding more "selected" connections + let activeConnections = MapUtil.getConnectionsByType(map, 'active'); + if(activeConnections.length >= config.maxActiveConnections && !connection.hasType('active')){ + Util.showNotify({title: 'Connection select limit', text: 'You can´t select more connections', type: 'warning'}); + }else { + if(activeConnections.length > 0) { + MapUtil.toggleConnectionActive(map, [connection]); + }else{ + MapUtil.showConnectionInfo(map, [connection]); + } + } + }else{ + MapUtil.showConnectionInfo(map, [connection]); + } + } + }.bind(connection); + + connectionCanvas.singleDoubleClick(single, () => {}); }; /** @@ -921,7 +949,7 @@ define([ mapWrapper.append(mapContainer); // append mapWrapper to parent element (at the top) - $(parentElement).prepend(mapWrapper); + parentElement.prepend(mapWrapper); // set main Container for current map -> the container exists now in DOM !! very important mapConfig.map.setContainer( config.mapIdPrefix + mapId ); @@ -938,12 +966,17 @@ define([ * @returns {*} */ let updateMap = function(mapConfig){ - let mapContainer = mapConfig.map.getContainer(); + let mapContainer = null; + if(!mapConfig.map){ + // jsPlumb needs to be initialized. This is not the case when switching between map tabs right after refresh + return mapContainer; + }else{ + mapContainer = $(mapConfig.map.getContainer()); + } + let mapId = mapConfig.config.id; let newSystems = 0; - mapContainer = $(mapContainer); - // add additional information for this map if(mapContainer.data('updated') !== mapConfig.config.updated.updated){ mapContainer.data('name', mapConfig.config.name); @@ -1650,42 +1683,6 @@ define([ }); }; - /** - * get all relevant data for a connection object - * @param connection - * @returns {{id: Number, source: Number, sourceName: (*|T|JQuery|{}), target: Number, targetName: (*|T|JQuery), scope: *, type: *, updated: Number}} - */ - let getDataByConnection = function(connection){ - let source = $(connection.source); - let target = $(connection.target); - - let id = connection.getParameter('connectionId'); - let updated = connection.getParameter('updated'); - - let connectionTypes = connection.getType(); - - // normalize connection array - connectionTypes = $.grep(connectionTypes, function(n){ - // 'default' is added by jsPlumb by default -_- - return ( n.length > 0 && n !== 'default'); - }); - - let data = { - id: id ? id : 0, - source: parseInt( source.data('id') ), - sourceName: source.data('name'), - sourceAlias: source.getSystemInfo(['alias']) || source.data('name'), - target: parseInt( target.data('id') ), - targetName: target.data('name'), - targetAlias: target.getSystemInfo(['alias']) || target.data('name'), - scope: connection.scope, - type: connectionTypes, - updated: updated ? updated : 0 - }; - - return data; - }; - /** * stores a connection in database * @param connection @@ -1697,7 +1694,7 @@ define([ let mapContainer = $( map.getContainer() ); let mapId = mapContainer.data('id'); - let connectionData = getDataByConnection(connection); + let connectionData = MapUtil.getDataByConnection(connection); let requestData = { mapData: { @@ -1908,7 +1905,7 @@ define([ ]}, {divider: true, action: 'delete_connection'}, - {icon: 'fa-trash', action: 'delete_connection', text: 'delete'} + {icon: 'fa-chain-broken', action: 'delete_connection', text: 'detach'} ] }; @@ -1943,6 +1940,7 @@ define([ {icon: 'fa-plus', action: 'add_system', text: 'add system'}, {icon: 'fa-lock', action: 'lock_system', text: 'lock system'}, {icon: 'fa-volume-up', action: 'set_rally', text: 'set rally point'}, + {icon: 'fa-object-group', action: 'select_connections', text: 'select connections'}, {icon: 'fa-tags', text: 'set status', subitems: systemStatus}, {icon: 'fa-reply fa-rotate-180', text: 'waypoints', subitems: [ {subIcon: 'fa-flag-checkered', subAction: 'set_destination', subText: 'set destination'}, @@ -2298,6 +2296,10 @@ define([ currentSystem.markAsChanged(); } break; + case 'select_connections': + let connections = MapUtil.searchConnectionsBySystems(map, [currentSystem], '*'); + MapUtil.showConnectionInfo(map, connections); + break; case 'change_status_unknown': case 'change_status_friendly': case 'change_status_occupied': @@ -2362,9 +2364,9 @@ define([ if(! system.hasClass('no-click')){ if(e.ctrlKey === true){ // select system - system.toggleSelectSystem(map); + MapUtil.toggleSelectSystem(map, [system]); }else{ - system.showSystemInfo(map); + MapUtil.showSystemInfo(map, system); } } } @@ -2412,31 +2414,6 @@ define([ return changed; }; - /** - * triggers the "showSystemInfo" event, that is responsible for initializing the "map info" panel - * @param map - */ - $.fn.showSystemInfo = function(map){ - let system = $(this); - - // activate system - markSystemActive(map, system); - - // get parent Tab Content and fire update event - let tabContentElement = MapUtil.getTabContentElementByMapElement( system ); - - // collect all required data from map module to update the info element - // store them global and assessable for each module - let currentSystemData = { - systemData: system.getSystemData(), - mapId: parseInt( system.attr('data-mapid') ) - }; - - Util.setCurrentSystemData(currentSystemData); - - $(tabContentElement).trigger('pf:drawSystemModules'); - }; - /** * select all (selectable) systems on a mapElement */ @@ -2452,35 +2429,13 @@ define([ return ( $(el).data('locked') !== true ); }); - allSystems.toggleSelectSystem(map); + MapUtil.toggleSelectSystem(map, allSystems); Util.showNotify({title: allSystems.length + ' systems selected', type: 'success'}); }); }; - /** - * toggle selectable status of a system - */ - $.fn.toggleSelectSystem = function(map){ - - return this.each(function(){ - let system = $(this); - - if( system.data('locked') !== true ){ - - if( system.hasClass( config.systemSelectedClass ) ){ - system.removeClass( config.systemSelectedClass ); - - map.removeFromDragSelection(system); - }else{ - system.addClass( config.systemSelectedClass ); - map.addToDragSelection(system); - } - } - }); - }; - /** * toggle log status of a system * @param poke @@ -2904,7 +2859,7 @@ define([ tempMapWrapper.mCustomScrollbar('scrollTo', system); // select system - system.showSystemInfo(map); + MapUtil.showSystemInfo(map, system); } }); @@ -2969,22 +2924,6 @@ define([ }); }; - /** - * mark a system as active - * @param map - * @param system - */ - let markSystemActive = function(map, system){ - - // deactivate all systems in map - let mapContainer = $( map.getContainer() ); - - mapContainer.find('.' + config.systemClass).removeClass(config.systemActiveClass); - - // set current system active - system.addClass(config.systemActiveClass); - }; - /** * get system data out of its object * @param info @@ -3051,7 +2990,7 @@ define([ // data for header update let headerUpdateData = { mapId: userData.config.id, - userCount: 0 // active user in a map + userCount: 0 // active user on a map }; if( @@ -3191,7 +3130,7 @@ define([ // format connections for(let j = 0; j < connections.length; j++){ let tempConnection = connections[j]; - let connectionData = getDataByConnection(tempConnection); + let connectionData = MapUtil.getDataByConnection(tempConnection); // only add valid connections (id is required, this is not the case if connection is new) if(connectionData.id > 0){ @@ -3389,10 +3328,14 @@ define([ let mapElement = mapTabContentElement.find('.' + config.mapClass); let mapId = mapElement.data('id'); - scrollableElement.initCustomScrollbar({ callbacks: { onScroll: function(){ + // scroll complete + // update scroll position for drag-frame-selection + mapElement.attr('data-scroll-left', this.mcs.left); + mapElement.attr('data-scroll-top', this.mcs.top); + // store new map scrollOffset -> localDB MapUtil.storeLocalData('map', mapId, 'offsetX', Math.abs(this.mcs.left) ); }, @@ -3402,11 +3345,6 @@ define([ // hide all system head tooltips $(this).find('.' + config.systemHeadClass + ' .fa').tooltip('hide'); - }, - whileScrolling:function(){ - // update scroll position for drag-frame-selection - mapElement.data('scrollLeft', this.mcs.left); - mapElement.data('scrollTop', this.mcs.top); } } }); @@ -3422,7 +3360,6 @@ define([ return { getMapInstance: getMapInstance, clearMapInstance: clearMapInstance, - getDataByConnection: getDataByConnection, showNewSystemDialog: showNewSystemDialog }; diff --git a/js/app/map/overlay.js b/js/app/map/overlay.js index 328aa621..f71d52ae 100644 --- a/js/app/map/overlay.js +++ b/js/app/map/overlay.js @@ -5,8 +5,9 @@ define([ 'jquery', 'app/init', - 'app/util' -], function($, Init, Util) { + 'app/util', + 'app/map/util' +], function($, Init, Util, MapUtil) { 'use strict'; let config = { @@ -72,34 +73,15 @@ define([ * @param label */ let addEndpointOverlay = (endpoint, label) => { - let newLabel = ''; - let colorClass = 'txt-color-grayLighter'; - - if(label.length > 0){ - newLabel = label; - - // check if multiple labels found => conflict - if( label.includes(', ') ){ - colorClass = 'txt-color-orangeLight'; - }else if( !label.includes('K162') ){ - colorClass = 'txt-color-yellow'; - } - }else{ - // endpoint not connected with a signature - newLabel = ''; - colorClass = 'txt-color-red'; - } - endpoint.addOverlay([ 'Label', { - label: '' + newLabel + '', + label: MapUtil.getEndpointOverlayContent(label), id: config.connectionOverlaySmallId, cssClass: config.connectionOverlaySmallClass, - location: [ 0.5, 0.5 ] + location: [ 0.9, 0.9 ] } ]); - }; // loop through all map connections (get from DOM) @@ -119,53 +101,15 @@ define([ // ... find matching connectionData (from Ajax) for(let connectionData of connectionsData){ - if( - connectionData.id === connectionId && - connectionData.signatures // signature data is required... - ){ - - // ... collect overlay/label data from signatures - for(let signatureData of connectionData.signatures){ - // ... typeId is required to get a valid name - if(signatureData.typeId > 0){ - - // whether "source" or "target" system is relevant for current connection and current signature... - let tmpSystem = null; - let tmpSystemType = null; - - if(signatureData.system.id === sourceId){ - // relates to "source" endpoint - tmpSystemType = 'sourceLabels'; - tmpSystem = sourceSystem; - }else if(signatureData.system.id === targetId){ - // relates to "target" endpoint - tmpSystemType = 'targetLabels'; - tmpSystem = targetSystem; - } - - // ... get endpoint label for source || target system - if(tmpSystem && tmpSystem){ - // ... get all available signature type (wormholes) names - let availableSigTypeNames = SystemSignatures.getAllSignatureNamesBySystem(tmpSystem, 5); - let flattenSigTypeNames = Util.flattenXEditableSelectArray(availableSigTypeNames); - - if( flattenSigTypeNames.hasOwnProperty(signatureData.typeId) ){ - let label = flattenSigTypeNames[signatureData.typeId]; - // shorten label, just take the in game name - label = label.substr(0, label.indexOf(' ')); - signatureTypeNames[tmpSystemType].push(label); - } - } - } - } - + if(connectionData.id === connectionId){ + signatureTypeNames = MapUtil.getConnectionDataFromSignatures(connection, connectionData); // ... connection matched -> continue with next one break; } } - let sourceLabel = signatureTypeNames.sourceLabels.join(', '); - let targetLabel = signatureTypeNames.targetLabels.join(', '); + let sourceLabel = signatureTypeNames.sourceLabels; + let targetLabel = signatureTypeNames.targetLabels; // add endpoint overlays ------------------------------------------------------ addEndpointOverlay(sourceEndpoint, sourceLabel); @@ -176,19 +120,19 @@ define([ let arrowDirection = 1; if( - (sourceLabel.includes('K162') && targetLabel.includes('K162')) || + (sourceLabel.indexOf('K162') !== -1 && targetLabel.indexOf('K162') !== -1) || (sourceLabel.length === 0 && targetLabel.length === 0) || ( sourceLabel.length > 0 && targetLabel.length > 0 && - !sourceLabel.includes('K162') && !targetLabel.includes('K162') + sourceLabel.indexOf('K162') === -1 && targetLabel.indexOf('K162') === -1 ) ){ // unknown direction overlayType = 'Diamond'; // not specified arrowDirection = 1; }else if( - (sourceLabel.includes('K162')) || - (sourceLabel.length === 0 && !targetLabel.includes('K162')) + (sourceLabel.indexOf('K162') !== -1) || + (sourceLabel.length === 0 && targetLabel.indexOf('K162') === -1) ){ // convert default arrow direction overlayType = 'Arrow'; @@ -283,7 +227,9 @@ define([ showLoading(overlayConnectionIcon); let requestData = { - mapId: mapElement.data('id') + mapId: mapElement.data('id'), + addData : ['signatures'], + filterData : ['signatures'] }; $.ajax({ diff --git a/js/app/map/system.js b/js/app/map/system.js index b5dac0ff..3aa6b03a 100644 --- a/js/app/map/system.js +++ b/js/app/map/system.js @@ -18,7 +18,7 @@ define([ y: 0 }, - systemActiveClass: 'pf-system-active', // class for an active system in a map + systemActiveClass: 'pf-system-active', // class for an active system on a map dialogRallyId: 'pf-rally-dialog', // id for "Rally point" dialog diff --git a/js/app/map/util.js b/js/app/map/util.js index 65e18150..0b927aa3 100644 --- a/js/app/map/util.js +++ b/js/app/map/util.js @@ -24,7 +24,8 @@ define([ systemIdPrefix: 'pf-system-', // id prefix for a system systemClass: 'pf-system', // class for all systems - systemSelectedClass: 'pf-system-selected', // class for selected systems in a map + systemActiveClass: 'pf-system-active', // class for an active system on a map + systemSelectedClass: 'pf-system-selected', // class for selected systems on on map // dataTable tableCellEllipsisClass: 'pf-table-cell-ellipsis', @@ -60,7 +61,7 @@ define([ * @param {bool} filterByUser * @returns {Array} */ - let getMapTypes = function(filterByUser){ + let getMapTypes = (filterByUser) => { let mapTypes = []; $.each(Init.mapTypes, function(prop, data){ @@ -109,7 +110,7 @@ define([ * get all available scopes for a map * @returns {Array} */ - let getMapScopes = function(){ + let getMapScopes = () => { let scopes = []; $.each(Init.mapScopes, function(prop, data){ let tempData = data; @@ -126,7 +127,7 @@ define([ * @param {string} option * @returns {string} */ - let getScopeInfoForMap = function(info, option){ + let getScopeInfoForMap = (info, option) => { let scopeInfo = ''; if(Init.mapScopes.hasOwnProperty(info)){ scopeInfo = Init.mapScopes[info][option]; @@ -138,7 +139,7 @@ define([ * get all available map icons * @returns {Object[]} */ - let getMapIcons = function(){ + let getMapIcons = () => { return Init.mapIcons; }; @@ -148,7 +149,7 @@ define([ * @param {string} option * @returns {string} */ - let getInfoForMap = function(mapType, option){ + let getInfoForMap = (mapType, option) => { let mapInfo = ''; if(Init.mapTypes.hasOwnProperty(mapType)){ mapInfo = Init.mapTypes[mapType][option]; @@ -162,7 +163,7 @@ define([ * @param {string} option * @returns {string} */ - let getInfoForSystem = function(info, option){ + let getInfoForSystem = (info, option) => { let systemInfo = ''; if(Init.classes.systemInfo.hasOwnProperty(info)){ systemInfo = Init.classes.systemInfo[info][option]; @@ -176,7 +177,7 @@ define([ * @param {string} option * @returns {string} */ - let getSystemTypeInfo = function(systemTypeId, option){ + let getSystemTypeInfo = (systemTypeId, option) => { let systemTypeInfo = ''; $.each(Init.systemType, function(prop, data){ if(systemTypeId === data.id){ @@ -193,7 +194,7 @@ define([ * @param option * @returns {string} */ - let getEffectInfoForSystem = function(effect, option){ + let getEffectInfoForSystem = (effect, option) => { let effectInfo = ''; if( Init.classes.systemEffects.hasOwnProperty(effect) ){ effectInfo = Init.classes.systemEffects[effect][option]; @@ -210,7 +211,7 @@ define([ }; /** - * get all selected (NOT active) systems in a map + * get all selected (NOT active) systems on a map * @returns {*} */ $.fn.getSelectedSystems = function(){ @@ -219,21 +220,327 @@ define([ }; /** - * search connections by systems - * @param {Object} map - jsPlumb - * @param {JQuery[]} systems - system DOM elements - * @returns {Array} connections - found connection, DOM elements + * filter connections by type + * @param map + * @param type + * @returns {Array} */ - let searchConnectionsBySystems = function(map, systems){ + let getConnectionsByType = (map, type) => { + let connections = []; + // iterate through ALL connections and filter... + // -> there is no "filterByScope()" method in jsPlumb + for(let connection of map.getAllConnections()){ + if(connection.getType().indexOf(type) !== -1){ + connections.push(connection); + } + } + return connections; + }; + + /** + * get all relevant data for a connection object + * @param connection + * @returns {{id: Number, source: Number, sourceName: (*|T|JQuery|{}), target: Number, targetName: (*|T|JQuery), scope: *, type: *, updated: Number}} + */ + let getDataByConnection = (connection) => { + let source = $(connection.source); + let target = $(connection.target); + + let id = connection.getParameter('connectionId'); + let updated = connection.getParameter('updated'); + + let connectionTypes = connection.getType(); + + // normalize connection array + connectionTypes = $.grep(connectionTypes, function(n){ + // 'default' is added by jsPlumb by default -_- + return ( n.length > 0 && n !== 'default' && n !== 'active'); + }); + + let data = { + id: id ? id : 0, + source: parseInt( source.data('id') ), + sourceName: source.data('name'), + sourceAlias: source.getSystemInfo(['alias']) || source.data('name'), + target: parseInt( target.data('id') ), + targetName: target.data('name'), + targetAlias: target.getSystemInfo(['alias']) || target.data('name'), + scope: connection.scope, + type: connectionTypes, + updated: updated ? updated : 0 + }; + + return data; + }; + + /** + * @see getDataByConnection + * @param connections + * @returns {Array} + */ + let getDataByConnections = (connections) => { + let data = []; + for(let connection of connections){ + data.push(getDataByConnection(connection)); + } + return data; + }; + + /** + * get connection related data from a connection + * -> data requires a signature bind to that connection + * @param connection + * @param connectionData + * @returns {{sourceLabels: Array, targetLabels: Array}} + */ + let getConnectionDataFromSignatures = (connection, connectionData) => { + let signatureTypeNames = { + sourceLabels: [], + targetLabels: [] + }; + + if( + connection && + connectionData.signatures // signature data is required... + ){ + let SystemSignatures = require('app/ui/system_signature'); + + let connectionId = connection.getParameter('connectionId'); + let sourceEndpoint = connection.endpoints[0]; + let targetEndpoint = connection.endpoints[1]; + let sourceSystem = $(sourceEndpoint.element); + let targetSystem = $(targetEndpoint.element); + let sourceId = sourceSystem.data('id'); + let targetId = targetSystem.data('id'); + + // ... collect overlay/label data from signatures + for(let signatureData of connectionData.signatures){ + // ... typeId is required to get a valid name + if(signatureData.typeId > 0){ + + // whether "source" or "target" system is relevant for current connection and current signature... + let tmpSystem = null; + let tmpSystemType = null; + + if(signatureData.system.id === sourceId){ + // relates to "source" endpoint + tmpSystemType = 'sourceLabels'; + tmpSystem = sourceSystem; + }else if(signatureData.system.id === targetId){ + // relates to "target" endpoint + tmpSystemType = 'targetLabels'; + tmpSystem = targetSystem; + } + + // ... get endpoint label for source || target system + if(tmpSystem && tmpSystem){ + // ... get all available signature type (wormholes) names + let availableSigTypeNames = SystemSignatures.getAllSignatureNamesBySystem(tmpSystem, 5); + let flattenSigTypeNames = Util.flattenXEditableSelectArray(availableSigTypeNames); + + if( flattenSigTypeNames.hasOwnProperty(signatureData.typeId) ){ + let label = flattenSigTypeNames[signatureData.typeId]; + // shorten label, just take the in game name + label = label.substr(0, label.indexOf(' ')); + signatureTypeNames[tmpSystemType].push(label); + } + } + } + } + } + + return signatureTypeNames; + }; + + /** + * get overlay HTML for connection endpoints by Label array + * @param label + * @returns {string} + */ + let getEndpointOverlayContent = (label) => { + let newLabel = ''; + let colorClass = 'txt-color-grayLighter'; + + if(label.length > 0){ + newLabel = label.join(', '); + + // check if multiple labels found => conflict + if( newLabel.includes(', ') ){ + colorClass = 'txt-color-orangeLight'; + }else if( !newLabel.includes('K162') ){ + colorClass = 'txt-color-yellow'; + } + }else{ + // endpoint not connected with a signature + newLabel = ''; + colorClass = 'txt-color-red'; + } + return '' + newLabel + ''; + }; + + /** + * get TabContentElement by any element on a map e.g. system + * @param element + * @returns {*} + */ + let getTabContentElementByMapElement = (element) => { + let tabContentElement = $(element).parents('.' + config.mapTabContentClass); + return tabContentElement; + }; + + /** + * checks if there is an "active" connection on a map + * @param map + * @returns {boolean} + */ + let hasActiveConnection = (map) => { + let activeConnections = getConnectionsByType(map, 'active'); + return activeConnections.length > 0; + }; + + /** + * mark a system as "active" + * @param map + * @param system + */ + let setSystemActive = (map, system) => { + // deselect all selected systems on map + let mapContainer = $( map.getContainer() ); + mapContainer.find('.' + config.systemClass).removeClass(config.systemActiveClass); + + // set current system active + system.addClass(config.systemActiveClass); + }; + + /** + * mark a connection as "active" + * @param connections + */ + let setConnectionsActive = (map, connections) => { + // set all inactive + for(let connection of getConnectionsByType(map, 'active')){ + connection.removeType('active'); + } + + for(let connection of connections){ + connection.addType('active'); + } + }; + + /** + * toggle "selected" status of system + * @param map + * @param systems + */ + let toggleSelectSystem = (map, systems) => { + for(let system of systems){ + system = $(system); + if( system.data('locked') !== true ){ + if( system.hasClass( config.systemSelectedClass ) ){ + system.removeClass( config.systemSelectedClass ); + + map.removeFromDragSelection(system); + }else{ + system.addClass( config.systemSelectedClass ); + map.addToDragSelection(system); + } + } + } + }; + + /** + * toggle "selected" status of connections + * @param map + * @param connections + */ + let toggleConnectionActive = (map, connections) => { + let selectedConnections = []; + let deselectedConnections = []; + for(let connection of connections){ + if(connection.hasType('active')){ + connection.removeType('active'); + deselectedConnections.push(connection); + }else{ + connection.addType('active'); + selectedConnections.push(connection); + } + } + updateConnectionInfo(map, selectedConnections, deselectedConnections); + }; + + /** + * show system info panels + * @param map + * @param system + */ + let showSystemInfo = (map, system) => { + setSystemActive(map, system); + + // get parent Tab Content and fire update event + let tabContentElement = getTabContentElementByMapElement( system ); + + // collect all required data from map module to update the info element + // store them global and assessable for each module + Util.setCurrentSystemData({ + systemData: system.getSystemData(), + mapId: parseInt( system.attr('data-mapid') ) + }); + + $(tabContentElement).trigger('pf:drawSystemModules'); + }; + + /** + * show connection info panels + * @param map + * @param connections + */ + let showConnectionInfo = (map, connections) => { + setConnectionsActive(map, connections); + + // get parent Tab Content and fire update event + let mapContainer = $(map.getContainer()); + let tabContentElement = getTabContentElementByMapElement(mapContainer); + + $(tabContentElement).trigger('pf:drawConnectionModules', { + connections: connections, + mapId: parseInt(mapContainer.data('id')) + }); + }; + + /** + * update connection info panels + * @param map + * @param selectedConnections + * @param deselectedConnections + */ + let updateConnectionInfo = (map, selectedConnections, deselectedConnections) => { + // get parent Tab Content and fire update event + let mapContainer = $(map.getContainer()); + + $(document).trigger('pf:updateConnectionInfoModule', { + connectionsUpdate: selectedConnections, + connectionsRemove: deselectedConnections, + mapId: parseInt(mapContainer.data('id')) + }); + }; + + /** + * search connections by systems + * @param map + * @param systems + * @param scope + * @returns {Array} + */ + let searchConnectionsBySystems = (map, systems, scope) => { let connections = []; let withBackConnection = true; $.each(systems, function(i, system){ // get connections where system is source - connections = connections.concat( map.getConnections({source: system}) ); + connections = connections.concat( map.getConnections({scope: scope, source: system}) ); if(withBackConnection === true){ // get connections where system is target - connections = connections.concat( map.getConnections({target: system}) ); + connections = connections.concat( map.getConnections({scope: scope, target: system}) ); } }); return connections; @@ -247,7 +554,7 @@ define([ * @param {string|string[]} type * @returns {Array} */ - let searchConnectionsByScopeAndType = function(map, scope, type){ + let searchConnectionsByScopeAndType = (map, scope, type) => { let connections = []; let scopeArray = (scope === undefined) ? ['*'] : ((Array.isArray(scope)) ? scope : [scope]); let typeArray = (type === undefined) ? [] : ((Array.isArray(type)) ? type : [type]); @@ -276,7 +583,7 @@ define([ * @param {string} option * @returns {string} */ - let getConnectionInfo = function(connectionTyp, option){ + let getConnectionInfo = (connectionTyp, option) => { let connectionInfo = ''; if(Init.connectionTypes.hasOwnProperty(connectionTyp)){ connectionInfo = Init.connectionTypes[connectionTyp][option]; @@ -291,7 +598,7 @@ define([ * @param {JQuery} systemB * @returns {Array} */ - let checkForConnection = function(map, systemA, systemB){ + let checkForConnection = (map, systemA, systemB) => { let connections = []; connections = connections.concat( map.getConnections({scope: '*', source: systemA, target: systemB}) ); // get connections where system is target @@ -305,7 +612,7 @@ define([ * @param {string} scope * @returns {string} */ - let getDefaultConnectionTypeByScope = function(scope){ + let getDefaultConnectionTypeByScope = (scope) => { let type = ''; switch(scope){ case 'wh': @@ -329,7 +636,7 @@ define([ * @param {Object} connection - jsPlumb object * @param {string} status */ - let setConnectionWHStatus = function(connection, status){ + let setConnectionWHStatus = (connection, status) => { if( status === 'wh_fresh' && connection.hasType('wh_fresh') !== true @@ -370,7 +677,7 @@ define([ * @param {string} option * @returns {string} */ - let getScopeInfoForConnection = function(info, option){ + let getScopeInfoForConnection = (info, option) => { let scopeInfo = ''; if(Init.connectionScopes.hasOwnProperty(info)){ switch(option){ @@ -388,21 +695,11 @@ define([ return scopeInfo; }; - /** - * get TabContentElement by any element on a map e.g. system - * @param element - * @returns {*} - */ - let getTabContentElementByMapElement = function(element){ - let tabContentElement = $(element).parents('.' + config.mapTabContentClass); - return tabContentElement; - }; - /** * store mapId for current user (IndexedDB) * @param mapId */ - let storeDefaultMapId = function(mapId){ + let storeDefaultMapId = (mapId) => { if(mapId > 0){ let userData = Util.getCurrentUserData(); if( @@ -419,7 +716,7 @@ define([ * @param type * @returns {boolean} */ - let getLocalStoragePrefixByType = function(type){ + let getLocalStoragePrefixByType = (type) => { let prefix = false; switch(type){ case 'character': prefix = config.characterLocalStoragePrefix; break; @@ -435,7 +732,7 @@ define([ * @param objectId * @returns {*} */ - let getLocaleData = function(type, objectId){ + let getLocaleData = (type, objectId) => { if(objectId > 0){ let storageKey = getLocalStoragePrefixByType(type) + objectId; return Util.getLocalStorage().getItem(storageKey); @@ -451,7 +748,7 @@ define([ * @param key * @param value */ - let storeLocalData = function(type, objectId, key, value){ + let storeLocalData = (type, objectId, key, value) => { if(objectId > 0){ // get current map config let storageKey = getLocalStoragePrefixByType(type) + objectId; @@ -481,7 +778,7 @@ define([ * @param objectId * @param key */ - let deleteLocalData = function(type, objectId, key){ + let deleteLocalData = (type, objectId, key) => { if(objectId > 0){ // get current map config let storageKey = getLocalStoragePrefixByType(type) + objectId; @@ -625,6 +922,56 @@ define([ }); }; + /** + * add a wormhole tooltip with wh specific data to elements + * @param tooltipData + * @returns {*} + */ + $.fn.addWormholeInfoTooltip = function(tooltipData){ + return this.each(function() { + let element = $(this); + + requirejs(['text!templates/tooltip/wormhole_info.html', 'mustache'], function (template, Mustache) { + // format tooltip data + let data = {}; + if(tooltipData.massTotal){ + data.massTotal = Util.formatMassValue(tooltipData.massTotal); + } + if(tooltipData.massIndividual){ + data.massIndividual = Util.formatMassValue(tooltipData.massIndividual); + } + if(tooltipData.massRegeneration){ + data.massRegeneration = Util.formatMassValue(tooltipData.massRegeneration); + } + if(tooltipData.maxStableTime){ + data.maxStableTime = tooltipData.maxStableTime + ' h'; + } + + let title = tooltipData.name + + '' + tooltipData.security + ''; + let content = Mustache.render(template, data); + + element.popover({ + placement: 'top', + html: true, + trigger: 'hover', + content: '', + container: 'body', + title: title, + delay: { + show: 150, + hide: 0 + } + }); + + // set new popover content + let popover = element.data('bs.popover'); + popover.options.title = title; + popover.options.content = content; + }); + }); + }; + $.fn.findMapElement = function(){ return $(this).find('.' + config.mapClass); }; @@ -650,6 +997,12 @@ define([ getInfoForSystem: getInfoForSystem, getSystemTypeInfo: getSystemTypeInfo, getEffectInfoForSystem: getEffectInfoForSystem, + toggleSelectSystem: toggleSelectSystem, + toggleConnectionActive: toggleConnectionActive, + showSystemInfo: showSystemInfo, + showConnectionInfo: showConnectionInfo, + getConnectionsByType: getConnectionsByType, + getDataByConnection: getDataByConnection, searchConnectionsBySystems: searchConnectionsBySystems, searchConnectionsByScopeAndType: searchConnectionsByScopeAndType, getConnectionInfo: getConnectionInfo, @@ -657,7 +1010,11 @@ define([ getDefaultConnectionTypeByScope: getDefaultConnectionTypeByScope, setConnectionWHStatus: setConnectionWHStatus, getScopeInfoForConnection: getScopeInfoForConnection, + getDataByConnections: getDataByConnections, + getConnectionDataFromSignatures: getConnectionDataFromSignatures, + getEndpointOverlayContent: getEndpointOverlayContent, getTabContentElementByMapElement: getTabContentElementByMapElement, + hasActiveConnection: hasActiveConnection, storeDefaultMapId: storeDefaultMapId, getLocaleData: getLocaleData, storeLocalData: storeLocalData, diff --git a/js/app/mappage.js b/js/app/mappage.js index 34a0d29b..a7c4bccf 100644 --- a/js/app/mappage.js +++ b/js/app/mappage.js @@ -64,6 +64,7 @@ define([ Init.connectionScopes = initData.connectionScopes; Init.systemStatus = initData.systemStatus; Init.systemType = initData.systemType; + Init.wormholes = initData.wormholes; Init.characterStatus = initData.characterStatus; Init.routes = initData.routes; Init.url = initData.url; @@ -382,7 +383,18 @@ define([ // Send map update request on tab close/reload, in order to save map changes that // haven´t been saved through default update trigger window.addEventListener('beforeunload', function(e) { + // save unsaved map changes ... triggerMapUpdatePing(); + + // check if character should be switched on reload or current character should be loaded afterwards + let characterSwitch = Boolean( $('body').data('characterSwitch') ); + if(!characterSwitch){ + let characterId = Util.getCurrentCharacterId(); + if(characterId){ + Util.setCookie('old_char_id', characterId, 3, 's'); + } + } + // IMPORTANT, return false in order to not "abort" ajax request in background! return false; }); diff --git a/js/app/module_map.js b/js/app/module_map.js index 1337d183..220b7fe3 100644 --- a/js/app/module_map.js +++ b/js/app/module_map.js @@ -4,14 +4,28 @@ define([ 'app/util', 'app/map/map', 'app/map/util', - 'app/counter', + 'sortable', 'app/ui/system_info', 'app/ui/system_graph', 'app/ui/system_signature', 'app/ui/system_route', - 'app/ui/system_killboard' -], function($, Init, Util, Map, MapUtil) { - + 'app/ui/system_killboard', + 'app/ui/connection_info', + 'app/counter' +], function( + $, + Init, + Util, + Map, + MapUtil, + Sortable, + SystemInfoModule, + SystemGraphModule, + SystemSignatureModule, + SystemRouteModule, + SystemKillboardModule, + ConnectionInfoModule +){ 'use strict'; let config = { @@ -67,118 +81,245 @@ define([ */ $.fn.setTabContentObserver = function(){ return this.each(function(){ + let tabContentElement = $(this); // update Tab Content with system data information - $(this).on('pf:drawSystemModules', function(e){ - drawSystemModules($( e.target )); + tabContentElement.on('pf:drawSystemModules', function(e){ + drawSystemModules($(e.target)); }); - $(this).on('pf:removeSystemModules', function(e){ - removeSystemModules($( e.target )); + tabContentElement.on('pf:removeSystemModules', function(e){ + removeSystemModules($(e.target)); + }); + + tabContentElement.on('pf:drawConnectionModules', function(e, data){ + drawConnectionModules($(e.target), data); + }); + + tabContentElement.on('pf:removeConnectionModules', function(e){ + removeConnectionModules($(e.target)); }); }); }; /** - * clear all system info modules and remove them + * remove multiple modules * @param tabContentElement + * @param modules + */ + let removeModules = (tabContentElement, modules) => { + for(let Module of modules){ + let moduleElement = tabContentElement.find('.' + Module.config.moduleTypeClass); + removeModule(moduleElement, Module); + } + }; + + /** + * clear all system modules and remove them + * @param tabContentElement + */ + let removeSystemModules = (tabContentElement) => { + let systemModules = [SystemInfoModule, SystemGraphModule, SystemSignatureModule, SystemRouteModule, SystemKillboardModule]; + removeModules(tabContentElement, systemModules); + }; + + /** + * clear all connection modules and remove them + * @param tabContentElement + */ + let removeConnectionModules = (tabContentElement) => { + let connectionModules = [ConnectionInfoModule]; + removeModules(tabContentElement, connectionModules); + }; + + /** + * remove a single module + * @param moduleElement + * @param Module * @param callback */ - let removeSystemModules = function(tabContentElement, callback){ - tabContentElement.find('.' + config.moduleClass).velocity('transition.slideDownOut', { - duration: Init.animationSpeed.mapModule, - complete: function(tempElement){ - $(tempElement).remove(); - - if(callback){ - callback(); - } + let removeModule = (moduleElement, Module, callback) => { + if(moduleElement.length > 0){ + if(typeof Module.beforeReDraw === 'function'){ + Module.beforeReDraw(); } - }); + + moduleElement.velocity('reverse',{ + complete: function(moduleElement){ + moduleElement = $(moduleElement); + if(typeof Module.beforeDestroy === 'function'){ + Module.beforeDestroy(moduleElement); + } + moduleElement.remove(); + + if(typeof callback === 'function'){ + callback(); + } + } + }); + } + }; + + /** + * generic function that draws a modulePanel for a given Module object + * @param parentElement + * @param Module + * @param mapId + * @param data + */ + let drawModule = (parentElement, Module, mapId, data) => { + /** + * get module position within its parentElement + * @param parentElement + * @param Module + * @param defaultPosition + * @returns {number} + */ + let getModulePosition = (parentElement, Module, defaultPosition) => { + let position = 0; + if(defaultPosition > 0){ + parentElement.children().each((i, moduleElement) => { + position = i + 1; + let tempPosition = parseInt(moduleElement.getAttribute('data-position')) || 0; + if(tempPosition >= defaultPosition){ + position--; + return false; + } + }); + } + return position; + }; + + /** + * show/render a Module + * @param parentElement + * @param Module + * @param mapId + * @param data + */ + let showPanel = (parentElement, Module, mapId, data) => { + let moduleElement = Module.getModule(parentElement, mapId, data); + if (moduleElement) { + // store Module object to DOM element for further access + moduleElement.data('module', Module); + moduleElement.data('data', data); + moduleElement.addClass([config.moduleClass, Module.config.moduleTypeClass].join(' ')); + moduleElement.css({opacity: 0}); // will be animated + + // check module position from local storage + let promiseStore = MapUtil.getLocaleData('map', mapId); + promiseStore.then(function (dataStore) { + let Module = this.moduleElement.data('module'); + let defaultPosition = Module.config.modulePosition || 0; + + // check for stored module order in indexDB (client) ---------------------------------------------- + let key = 'modules_cell_' + this.parentElement.attr('data-position'); + if ( + dataStore && + dataStore[key] + ) { + let positionIndex = dataStore[key].indexOf(Module.config.moduleName); + if (positionIndex !== -1) { + // first index (0) => is position 1 + defaultPosition = positionIndex + 1; + } + } + + // find correct position for new moduleElement ---------------------------------------------------- + let position = getModulePosition(this.parentElement, Module, defaultPosition); + + this.moduleElement.attr('data-position', defaultPosition); + this.moduleElement.attr('data-module', Module.config.moduleName); + + // insert at correct position --------------------------------------------------------------------- + let prevModuleElement = this.parentElement.find('.' + config.moduleClass + ':nth-child(' + position + ')'); + if (prevModuleElement.length) { + this.moduleElement.insertAfter(prevModuleElement); + } else { + this.parentElement.prepend(this.moduleElement); + } + + if(typeof Module.beforeShow === 'function'){ + Module.beforeShow(this.moduleElement, moduleElement.data('data')); + } + + // show animation --------------------------------------------------------------------------------- + this.moduleElement.velocity({ + opacity: [1, 0], + translateY: [0, +20] + }, { + duration: Init.animationSpeed.mapModule, + easing: 'easeOutSine', + complete: function (moduleElement) { + moduleElement = $(moduleElement); + let Module = $(moduleElement).data('module'); + if (typeof Module.initModule === 'function') { + Module.initModule(moduleElement, mapId, moduleElement.data('data')); + } + } + }); + }.bind({ + parentElement: parentElement, + moduleElement: moduleElement + })); + } + }; + + // check if module already exists + let moduleElement = parentElement.find('.' + Module.config.moduleTypeClass); + if(moduleElement.length > 0){ + removeModule(moduleElement, Module, () => { + showPanel(parentElement, Module, mapId, data); + }); + }else{ + showPanel(parentElement, Module, mapId, data); + } }; /** * clears and updates the system info element (signature table, system info,...) * @param tabContentElement */ - let drawSystemModules = function(tabContentElement){ - require(['datatables.loader'], () => { + let drawSystemModules = (tabContentElement) => { + require(['datatables.loader'], function(){ let currentSystemData = Util.getCurrentSystemData(); - // get Table cell for system Info - let firstCell = $(tabContentElement).find('.' + config.mapTabContentCellFirst); - let secondCell = $(tabContentElement).find('.' + config.mapTabContentCellSecond); + // get grid cells + let firstCell = tabContentElement.find('.' + config.mapTabContentCellFirst); + let secondCell = tabContentElement.find('.' + config.mapTabContentCellSecond); // draw system info module - firstCell.drawSystemInfoModule(currentSystemData.mapId, currentSystemData.systemData); + drawModule(firstCell, SystemInfoModule, currentSystemData.mapId, currentSystemData.systemData); // draw system graph module - firstCell.drawSystemGraphModule(currentSystemData.systemData); + drawModule(firstCell, SystemGraphModule, currentSystemData.mapId, currentSystemData.systemData); // draw signature table module - firstCell.drawSignatureTableModule(currentSystemData.mapId, currentSystemData.systemData); + drawModule(firstCell, SystemSignatureModule, currentSystemData.mapId, currentSystemData.systemData); // draw system routes module - secondCell.drawSystemRouteModule(currentSystemData.mapId, currentSystemData.systemData); + drawModule(secondCell, SystemRouteModule, currentSystemData.mapId, currentSystemData.systemData); // draw system killboard module - secondCell.drawSystemKillboardModule(currentSystemData.systemData); - - // set Module Observer - setModuleObserver(); + drawModule(secondCell, SystemKillboardModule, currentSystemData.mapId, currentSystemData.systemData); }); }; /** - * set observer for each module + * clears and updates the connection info element (mass log) + * @param tabContentElement + * @param data */ - let setModuleObserver = function(){ + let drawConnectionModules = (tabContentElement, data) => { + require(['datatables.loader'], function(){ - // toggle height for a module - $(document).off('click.toggleModuleHeight').on('click.toggleModuleHeight', '.' + config.moduleClass, function(e){ - let moduleElement = $(this); - // get click position - let posX = moduleElement.offset().left; - let posY = moduleElement.offset().top; - let clickX = e.pageX - posX; - let clickY = e.pageY - posY; + // get grid cells + let firstCell = $(tabContentElement).find('.' + config.mapTabContentCellFirst); - // check for top-left click - if(clickX <= 8 && clickY <= 8){ - - // remember height - if(! moduleElement.data('origHeight')){ - - moduleElement.data('origHeight', moduleElement.outerHeight()); - } - - if(moduleElement.hasClass( config.moduleClosedClass )){ - let moduleHeight = moduleElement.data('origHeight'); - moduleElement.velocity('finish').velocity({ - height: [ moduleHeight + 'px', [ 400, 15 ] ] - },{ - duration: 400, - easing: 'easeOutSine', - complete: function(){ - moduleElement.removeClass( config.moduleClosedClass ); - moduleElement.removeData(); - } - }); - }else{ - moduleElement.velocity('finish').velocity({ - height: [ '35px', [ 400, 15 ] ] - },{ - duration: 400, - easing: 'easeOutSine', - complete: function(){ - moduleElement.addClass( config.moduleClosedClass ); - } - }); - } - } - }); + // draw connection info module + drawModule(firstCell, ConnectionInfoModule, this.mapId, this.connections); + }.bind(data)); }; - /** * updates only visible/active map module * @returns {boolean} @@ -236,26 +377,115 @@ define([ } }; + /** + * set observer for tab content (area where modules will be shown) + * @param contentStructure + * @param mapId + */ + let setContentStructureObserver = (contentStructure, mapId) => { + contentStructure.find('.' + config.mapTabContentCell).each((index, cellElement) => { + let sortable = Sortable.create(cellElement, { + group: { + name: 'cell_' + cellElement.getAttribute('data-position') + }, + animation: Init.animationSpeed.mapModule, + handle: '.pf-module-handler-drag', + draggable: '.' + config.moduleClass, + ghostClass: 'pf-sortable-ghost', + scroll: true, + scrollSensitivity: 50, + scrollSpeed: 20, + dataIdAttr: 'data-module', + sort: true, + store: { + get: function (sortable) { + return []; + }, + set: function (sortable) { + let key = 'modules_' + sortable.options.group.name; + MapUtil.storeLocalData('map', mapId, key, sortable.toArray()); + } + }, + onStart: function (e) { + // Element dragging started + // -> save initial sort state -> see store.set() + this.save(); + } + }); + }); + + // toggle height for a module + contentStructure.on('click.toggleModuleHeight', '.' + config.moduleClass, function(e){ + let moduleElement = $(this); + // get click position + let posX = moduleElement.offset().left; + let posY = moduleElement.offset().top; + let clickX = e.pageX - posX; + let clickY = e.pageY - posY; + + // check for top-left click + if(clickX <= 8 && clickY <= 8){ + + // remember height + if(! moduleElement.data('origHeight')){ + + moduleElement.data('origHeight', moduleElement.outerHeight()); + } + + if(moduleElement.hasClass( config.moduleClosedClass )){ + let moduleHeight = moduleElement.data('origHeight'); + moduleElement.velocity('finish').velocity({ + height: [ moduleHeight + 'px', [ 400, 15 ] ] + },{ + duration: 400, + easing: 'easeOutSine', + complete: function(){ + moduleElement.removeClass( config.moduleClosedClass ); + moduleElement.removeData('origHeight'); + } + }); + }else{ + moduleElement.velocity('finish').velocity({ + height: [ '35px', [ 400, 15 ] ] + },{ + duration: 400, + easing: 'easeOutSine', + complete: function(){ + moduleElement.addClass( config.moduleClosedClass ); + } + }); + } + } + }); + }; + /** * load all structure elements into a TabsContent div (tab body) */ - $.fn.initContentStructure = function(){ - return this.each(function(){ - // init bootstrap Grid - let contentStructure = $('
', { - class: ['row', config.mapTabContentRow].join(' ') - }).append( + let initContentStructure = (tabContentElements) => { + tabContentElements.each(function(){ + let tabContentElement = $(this); + let mapId = parseInt( tabContentElement.attr('data-mapid') ); + + // "add" tab does not need a structure and obervers... + if(mapId > 0){ + let contentStructure = $('
', { + class: ['row', config.mapTabContentRow].join(' ') + }).append( $('
', { class: ['col-xs-12', 'col-md-8', config.mapTabContentCellFirst, config.mapTabContentCell].join(' ') - }) + }).attr('data-position', 1) ).append( $('
', { class: ['col-xs-12', 'col-md-4', config.mapTabContentCellSecond, config.mapTabContentCell].join(' ') - }) + }).attr('data-position', 2) ); - // append grid structure - $(this).append(contentStructure); + // append grid structure + tabContentElement.append(contentStructure); + + setContentStructureObserver(contentStructure, mapId); + } }); }; @@ -264,7 +494,7 @@ define([ * @param options * @returns {*|jQuery|HTMLElement} */ - let getTabElement = function(options){ + let getTabElement = (options) => { let tabElement = $('
', { id: config.mapTabElementId }); @@ -401,17 +631,17 @@ define([ // update Tab element -> set data linkElement.updateTabData(options); - // tabs content ======================================================= + // tabs content ----------------------------------------------------------------------------------------------- let contentElement = $('
', { id: config.mapTabIdPrefix + parseInt( options.id ), class: [config.mapTabContentClass].join(' ') - }); + }).attr('data-mapid', parseInt( options.id )); contentElement.addClass('tab-pane'); tabContent.append(contentElement); - // init tab =========================================================== + // init tab --------------------------------------------------------------------------------------------------- linkElement.on('click', function(e){ e.preventDefault(); @@ -603,7 +833,7 @@ define([ newTabElements.contentElement.setTabContentObserver(); // load all the structure elements for the new tab - newTabElements.contentElement.initContentStructure(); + initContentStructure(newTabElements.contentElement); tabsChanged = true; @@ -671,7 +901,7 @@ define([ mapKeyTabSelector = 'nth-child(' + ( mapDataIndex + 1 ) + ')'; } - // ============================================================== + // ---------------------------------------------------------------------------------------------------- // this new created module let tabContentElements = tabMapElement.find('.' + config.mapTabContentClass); @@ -680,7 +910,7 @@ define([ tabContentElements.setTabContentObserver(); // load all the structure elements for ALL Tab Content Body - tabContentElements.initContentStructure(); + initContentStructure(tabContentElements); // set first Tab active tabMapElement.find('.' + config.mapTabClass + ':' + mapKeyTabSelector + ' a').tab('show'); diff --git a/js/app/page.js b/js/app/page.js index bfc863f9..6c7abbd0 100644 --- a/js/app/page.js +++ b/js/app/page.js @@ -120,8 +120,8 @@ define([ // load right menu $('.' + config.pageSlidebarRightClass).loadRightMenu(); - // set document observer for global events - setDocumentObserver(); + // set page observer for global events + setPageObserver(); }); }; @@ -568,7 +568,7 @@ define([ /** * catch all global document events */ - let setDocumentObserver = function(){ + let setPageObserver = function(){ // on "full-screen" change event $(document).on('fscreenchange', function(e, state, elem){ @@ -696,6 +696,10 @@ define([ // END menu events ============================================================================= + // global "modal" callback (for all modals) + $('body').on('hide.bs.modal', '> .modal', function(a,b) { + $(this).destroyTimestampCounter(); + }); // update header links with current map data $(document).on('pf:updateHeaderMapData', function(e, data){ @@ -783,45 +787,70 @@ define([ $.fn.updateHeaderUserData = function(){ let userData = Util.getCurrentUserData(); - let userInfoElement = $('.' + config.headUserCharacterClass); - let currentCharacterId = userInfoElement.data('characterId'); - let currentCharactersOptionIds = userInfoElement.data('characterOptionIds') ? userInfoElement.data('characterOptionIds') : []; - let newCharacterId = 0; - let newCharacterName = ''; + let userInfoElement = $('.' + config.headUserCharacterClass); + let currentCharacterId = userInfoElement.data('characterId'); + let currentCharactersOptionIds = userInfoElement.data('characterOptionIds') ? userInfoElement.data('characterOptionIds') : []; + let newCharacterId = 0; + let newCharacterName = ''; - let userShipElement = $('.' + config.headUserShipClass); - let currentShipId = userShipElement.data('shipId'); - let newShipId = 0; - let newShipName = ''; + let userShipElement = $('.' + config.headUserShipClass); + let currentShipData = userShipElement.data('shipData'); + let currentShipId = Util.getObjVal(currentShipData, 'typeId') || 0; + let newShipData = { + typeId: 0, + typeName: '' + }; // function for header element toggle animation - let animateHeaderElement = function(element, callback, triggerShow){ + let animateHeaderElement = (element, callback, triggerShow) => { + let currentOpacity = parseInt(element.css('opacity')); - element.show().velocity('stop').velocity({ - opacity: 0 - },{ - visibility : 'hidden', - duration: 500, - complete: function(){ - // callback - callback(); + let showHeaderElement = (element) => { + element.show().velocity({ + opacity: [ 1, 0 ] + },{ + // display: 'block', + visibility : 'visible', + duration: 1000 + }); + }; - // show element - if(triggerShow === true){ - element.velocity({ - opacity: 1 - }, { - visibility : 'visible', - duration: 500 - }); - }else{ - // hide element + let hideHeaderElement = (element, callback) => { + element.velocity('stop').velocity({ + opacity: [ 0, 1 ] + },{ + // display: 'none', + visibility : 'hidden', + duration: 1000, + complete: function(){ element.hide(); + // callback + callback($(this)); } + }); + }; - } - }); - + // run show/hide toggle in the correct order + if(currentOpacity > 0 && triggerShow){ + // hide then show animation + hideHeaderElement(element, (element) => { + callback(element); + showHeaderElement(element); + }); + }else if(currentOpacity > 0 && !triggerShow){ + // hide animation + hideHeaderElement(element, (element) => { + element.hide(); + callback(element); + }); + }else if(currentOpacity === 0 && triggerShow){ + // show animation + callback(element); + showHeaderElement(element); + }else{ + // no animation + callback(element); + } }; // check for character/ship changes --------------------------------------------- @@ -833,8 +862,7 @@ define([ newCharacterName = userData.character.name; if(userData.character.log){ - newShipId = userData.character.log.ship.typeId; - newShipName = userData.character.log.ship.typeName; + newShipData = userData.character.log.ship; } // en/disable "map tracking" toggle @@ -854,7 +882,7 @@ define([ } // toggle element - animateHeaderElement(userInfoElement, function(){ + animateHeaderElement(userInfoElement, (userInfoElement) => { if(currentCharacterChanged){ userInfoElement.find('span').text( newCharacterName ); userInfoElement.find('img').attr('src', Init.url.ccpImageServer + '/Character/' + newCharacterId + '_32.jpg' ); @@ -869,21 +897,21 @@ define([ } // update user ship data -------------------------------------------------------- - if(currentShipId !== newShipId){ + if(currentShipId !== newShipData.typeId){ + // set new data for next check + userShipElement.data('shipData', newShipData); - let showShipElement = true; - if(newShipId === 0){ - showShipElement = false; - } + let showShipElement = newShipData.typeId > 0; // toggle element - animateHeaderElement(userShipElement, function(){ - userShipElement.find('span').text( newShipName ); - userShipElement.find('img').attr('src', Init.url.ccpImageServer + '/Render/' + newShipId + '_32.png' ); + animateHeaderElement(userShipElement, (userShipElement) => { + userShipElement.find('span').text( newShipData.typeName ); + userShipElement.find('img').attr('src', Init.url.ccpImageServer + '/Render/' + newShipData.typeId + '_32.png' ); + // trigger ship change event + $(document).trigger('pf:activeShip', { + shipData: newShipData + }); }, showShipElement); - - // set new id for next check - userShipElement.data('shipId', newShipId); } }; diff --git a/js/app/render.js b/js/app/render.js index bbe43ca5..b94ea2f4 100644 --- a/js/app/render.js +++ b/js/app/render.js @@ -17,7 +17,7 @@ define(['jquery', 'mustache'], function($, Mustache) { typeof config.functions === 'object' && typeof config.functions[functionName] === 'function' ){ - config.functions[functionName](); + config.functions[functionName](config); } }; @@ -27,16 +27,13 @@ define(['jquery', 'mustache'], function($, Mustache) { * @param data */ let showModule = function(config, data){ - // require module template requirejs(['text!templates/' + config.name + '.html'], function(template) { - // check for an id, if module already exists, do not insert again if( data.id === 'undefined' || $('#' + data.id).length === 0 ){ - let content = Mustache.render(template, data); // display module @@ -57,8 +54,6 @@ define(['jquery', 'mustache'], function($, Mustache) { // init module function after render initModule('after', config); - - }); }; diff --git a/js/app/ui/connection_info.js b/js/app/ui/connection_info.js new file mode 100644 index 00000000..c7f473cd --- /dev/null +++ b/js/app/ui/connection_info.js @@ -0,0 +1,971 @@ +/** + * connection info module + */ + +define([ + 'jquery', + 'app/init', + 'app/util', + 'app/map/util' +], ($, Init, Util, MapUtil) => { + 'use strict'; + + let config = { + // module info + modulePosition: 1, + moduleName: 'connectionInfo', + moduleHeadClass: 'pf-module-head', // class for module header + moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler + + headUserShipClass: 'pf-head-user-ship', // class for "user settings" link + + // connection info module + moduleTypeClass: 'pf-connection-info-module', // class for this module + + // headline toolbar + moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head + moduleHeadlineIconRefreshClass: 'pf-module-icon-button-refresh', // class for "refresh" icon + moduleHeadlineIconCurrentMassClass: 'pf-module-icon-button-mass', // class for "current ship mass" toggle icon + + connectionInfoPanelClass: 'pf-connection-info-panel', // class for connection info panels + connectionInfoPanelId: 'pf-connection-info-panel-', // id prefix for connection info panels + + // info table + moduleTableClass: 'pf-module-table', // class for module tables + connectionInfoTableLabelSourceClass: 'pf-connection-info-label-source', // class for source label + connectionInfoTableLabelTargetClass: 'pf-connection-info-label-target', // class for target label + connectionInfoTableRowMassShipClass: 'pf-connection-info-row-mass-ship', // class for "current ship mass" table row + connectionInfoTableCellMassTotalTooltipClass: 'pf-connection-info-mass-total-tooltip', // class for "mass total tooltip" table cell + connectionInfoTableCellMassTotalClass: 'pf-connection-info-mass-total', // class for "mass total" table cell + connectionInfoTableCellMassLogClass: 'pf-connection-info-mass-log', // class for "mass log" table cell + connectionInfoTableCellMassShipClass: 'pf-connection-info-mass-ship', // class for "current ship mass" table cell + connectionInfoTableCellMassLeftClass: 'pf-connection-info-mass-left', // class for "mass left" table cell + + connectionInfoTableTooltipIconClass: 'pf-connection-info-tooltip-icon', // class for "tooltip" icon + connectionInfoTableWarningIconClass: 'pf-connection-info-warning-icon', // class for "warning" icon + + // dataTable + connectionInfoTableClass: 'pf-connection-info-table', // class for connection tables + tableCellImageClass: 'pf-table-image-cell', // class for table "image" cells + tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells + + // config + showShip: true // default for "show current ship mass" toggle + }; + + /** + * get module toolbar element + * @returns {*|jQuery|HTMLElement|void} + */ + let getHeadlineToolbar = () => { + let headlineToolbar = $('
', { + class: 'pull-right' + }).append( + $('', { + class: ['fa', 'fa-fw', 'fa-male', + config.showShip ? 'active' : '' , + config.moduleHeadlineIconClass, + config.moduleHeadlineIconCurrentMassClass].join(' '), + title: 'toggle current ship mass' + }).attr('data-html', 'true').attr('data-toggle', 'tooltip'), + $('', { + class: ['fa', 'fa-fw', 'fa-refresh', + config.moduleHeadlineIconClass, + config.moduleHeadlineIconRefreshClass].join(' '), + title: 'refresh all' + }).attr('data-html', 'true').attr('data-toggle', 'tooltip') + ); + + headlineToolbar.find('[data-toggle="tooltip"]').tooltip({ + container: 'body' + }); + + return headlineToolbar; + }; + + /** + * get new connection element + * @param mapId + * @param connectionId + * @returns {jQuery} + */ + let getConnectionElement = (mapId, connectionId) => { + let connectionElement = $('
', { + id: getConnectionElementId(connectionId), + class: ['col-xs-12', 'col-sm-4', 'col-lg-3' , config.connectionInfoPanelClass].join(' ') + }).data({ + mapId: mapId, + connectionId: connectionId + }); + + return connectionElement; + }; + + /** + * get info control panel element + * @param mapId + * @returns {void|jQuery|*} + */ + let getInfoPanelControl = (mapId) => { + let connectionElement = getConnectionElement(mapId, 0).append($('
', { + class: 'pf-dynamic-area', + html: ' add connection  ctrl + click' + })); + + return connectionElement; + }; + + /** + * get connection information element + * @param connectionData + * @returns {void|*|jQuery|HTMLElement} + */ + let getInformationElement = (connectionData) => { + + // connection scope ----------------------------------------------------------------------- + let scopeLabel = MapUtil.getScopeInfoForConnection(connectionData.scope, 'label'); + + // connection type (dummy) classes -------------------------------------------------------- + let connectionClasses = ['pf-fake-connection']; + for(let i = 0; i < connectionData.type.length; i++){ + connectionClasses.push( MapUtil.getConnectionInfo( connectionData.type[i], 'cssClass') ); + } + + let massLog = 0; + + let element = $('
', { + class: 'pf-dynamic-area' + }).append( + $('', { + class: ['table', 'table-condensed', 'pf-table-fixed', config.moduleTableClass].join(' ') + }).data('showShip', config.showShip).append( + $('').append( + $('').append( + $('').append( + $('').append( + $('').append( + $('').append( + $('', { + class: config.connectionInfoTableRowMassShipClass + }).append( + $('').append( + $('
', { + class: ['pf-table-cell-20', 'text-right', 'pf-help', 'pf-pie-chart'].join(' ') + }).attr('data-toggle', 'tooltip').attr('data-percent', '-100').easyPieChart({ + barColor: (percent) => { + let color = '#e28a0d'; + if((percent * -1) >= 100){ + color = '#a52521'; + } + return color; + }, + overrideOptions: 'signed', + trackColor: '#5cb85c', + size: 14, + scaleColor: false, + lineWidth: 2, + lineCap: 'butt', + animate: false + }), + $('', { + class: ['text-right'].join(' ') + }).attr('colspan', 2).append( + $('', { + class: 'pf-link', + html: connectionData.sourceAlias + '  ' + }).on('click', function(){ + Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: connectionData.source }); + }), + $('', { + class: [config.connectionInfoTableLabelSourceClass].join(' ') + }), + $('', { + class: 'fa fa-fw fa-angle-double-right' + }), + $('', { + class: [config.connectionInfoTableLabelTargetClass].join(' ') + }), + $('', { + class: 'pf-link', + html: '  ' + connectionData.targetAlias + }).on('click', function(){ + Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: connectionData.target }); + }) + ) + ) + ), + $('
', { + class: ['text-right', 'pf-help', config.connectionInfoTableCellMassTotalTooltipClass].join(' '), + html: '' + }), + $('', { + text: scopeLabel.charAt(0).toUpperCase() + scopeLabel.slice(1) + }), + $('', { + class: ['text-right'].join(' ') + }).append( + $('
', { + class: connectionClasses.join(' ') + }) + ) + ), + $('
', { + class: ['text-right', 'pf-help'].join(' '), + html: '', + title: 'initial mass. From signature table' + }).attr('data-toggle', 'tooltip'), + $('', { + text: 'Total mass' + }), + $('', { + class: ['text-right', 'txt-color', config.connectionInfoTableCellMassTotalClass].join(' ') + }) + ), + $('
', { + class: ['text-right', 'pf-help'].join(' '), + html: '', + title: 'recorded total jump mass' + }).attr('data-toggle', 'tooltip'), + $('', { + text: 'Logged mass' + }), + $('', { + class: ['text-right', config.connectionInfoTableCellMassLogClass].join(' ') + }) + ), + $('
', { + class: ['text-right', 'pf-help'].join(' '), + title: 'current ship mass' + }).attr('data-toggle', 'tooltip').append( + $('', { + class: [ + 'fa', 'fa-fw', 'fa-question-circle', + config.connectionInfoTableTooltipIconClass + ].join(' ') + }), + $('', { + class: [ + 'fa', 'fa-fw', 'fa-exclamation-triangle', + 'txt-color', 'txt-color-danger', + 'hidden', config.connectionInfoTableWarningIconClass + ].join(' ') + }) + ), + $('', { + class: ['pf-table-cell-ellipses-auto'].join(' '), + text: 'Ship mass' + }), + $('', { + class: ['text-right', 'txt-color', config.connectionInfoTableCellMassShipClass].join(' ') + }) + ), + $('
', { + class: ['text-right', 'pf-help'].join(' '), + html: '', + title: 'max. mass left' + }).attr('data-toggle', 'tooltip'), + $('', { + text: 'Mass left' + }), + $('', { + class: ['text-right', 'txt-color', config.connectionInfoTableCellMassLeftClass].join(' ') + }) + ) + ) + ).on('pf:updateInfoTable', function(e, data){ + // update information table ------------------------------------------------------- + let tableElement = $(this); + let connectionData = tableElement.data('connectionData'); + if(connectionData){ + if(connectionData.scope === 'wh'){ + // update signature information ------------------------------------------- + let sourceLabelElement = tableElement.find('.' + config.connectionInfoTableLabelSourceClass); + let targetLabelElement = tableElement.find('.' + config.connectionInfoTableLabelTargetClass); + + // get related jsPlumb connection + let connection = $().getConnectionById(data.mapId, data.connectionId); + let signatureTypeNames = MapUtil.getConnectionDataFromSignatures(connection, connectionData); + + let sourceLabel = signatureTypeNames.sourceLabels; + let targetLabel = signatureTypeNames.targetLabels; + sourceLabelElement.html(MapUtil.getEndpointOverlayContent(sourceLabel)); + targetLabelElement.html(MapUtil.getEndpointOverlayContent(targetLabel)); + + // remove K162 + sourceLabel.diff(['K162']); + targetLabel.diff(['K162']); + + // get static wormhole data by endpoint Labels + let wormholeName = ''; + let wormholeData = null; + if(sourceLabel.length === 1 && targetLabel.length === 0){ + wormholeName = sourceLabel[0]; + }else if(sourceLabel.length === 0 && targetLabel.length === 1){ + wormholeName = targetLabel[0]; + } + + if( + wormholeName && + Init.wormholes.hasOwnProperty(wormholeName) + ){ + wormholeData = Object.assign({}, Init.wormholes[wormholeName]); + wormholeData.class = Util.getSecurityClassForSystem(wormholeData.security); + + // init wormhole tooltip ---------------------------------------------- + let massTotalTooltipCell = tableElement.find('.' + config.connectionInfoTableCellMassTotalTooltipClass); + massTotalTooltipCell.addWormholeInfoTooltip(wormholeData); + } + + // all required data is set -> re-calculate rows + tableElement.data('wormholeData', wormholeData); + tableElement.trigger('pf:calcInfoTable'); + } + + } + }).on('pf:calcInfoTable', function(e){ + // re-calculate information table from .data() cell values ------------------------ + let tableElement = $(this); + let massChartCell = tableElement.find('[data-percent]'); + + let wormholeData = tableElement.data('wormholeData'); + let shipData = null; + let shipName = ''; + let showShip = Boolean(tableElement.data('showShip')); + let massShipRow = tableElement.find('.' + config.connectionInfoTableRowMassShipClass); + + // icons + let massShipTooltipIcon = massShipRow.find('.' + config.connectionInfoTableTooltipIconClass); + let massShipWarningIcon = massShipRow.find('.' + config.connectionInfoTableWarningIconClass); + + // table cells + let massTotalCell = tableElement.find('.' + config.connectionInfoTableCellMassTotalClass); + let massLogCell = tableElement.find('.' + config.connectionInfoTableCellMassLogClass); + let massShipCell = tableElement.find('.' + config.connectionInfoTableCellMassShipClass); + let massLeftCell = tableElement.find('.' + config.connectionInfoTableCellMassLeftClass); + let massTotal = null; // initial connection mass + let massLog = massLogCell.data('mass'); // recorded mass + let massLogTotal = massLog; // recorded mass + current ship + let massIndividual = null; // mass mass per jump + let massShip = 0; // current ship + let massIndividualError = false; + + // get wormhole data from signature binding --------------------------------------- + if(wormholeData){ + massTotal = parseInt(wormholeData.massTotal); + massIndividual = parseInt(wormholeData.massIndividual); + } + + // get current ship data ---------------------------------------------------------- + massShipCell.parent().toggle(showShip); + if(showShip){ + shipData = $('.' + config.headUserShipClass).data('shipData'); + if(shipData){ + if(shipData.mass){ + massShip = parseInt(shipData.mass); + + // check individual mass jump + if(massIndividual){ + massIndividualError = massShip > massIndividual; + } + } + if(shipData.typeId && shipData.typeName){ + shipName = shipData.typeName; + } + } + } + + // update ship mass and "individual mass" cells ---------------------------------- + massShipTooltipIcon.toggleClass('hidden', massIndividualError); + massShipWarningIcon.toggleClass('hidden', !massIndividualError); + let shipMassTooltip = 'current ship mass ' + (shipName ? '"' + shipName + '"' : ''); + if(massIndividualError){ + shipMassTooltip = '"' + shipName + '" exceeds max jump mass for this connection: ' + Util.formatMassValue(massIndividual); + }else{ + // current ship mass check is OK -> add to massLogTotal + massLogTotal += massShip; + } + massShipTooltipIcon.parent().attr('title', shipMassTooltip).tooltip('fixTitle'); + + // current ship mass -------------------------------------------------------------- + massShipCell.html( function(){ + let cell = $(this); + let value = ' '; + let error = false; + let textLineThrough = false; + if(massShip > 0){ + value += Util.formatMassValue(massShip); + if(massIndividualError){ + error = textLineThrough = true; + value = '  ' + value; + }else{ + value = '-' + value; + } + }else{ + error = true; + value = 'undefined'; + } + + // change cell style + cell.toggleClass('txt-color-red', error) + .toggleClass('txt-color-warning', !error) + .toggleClass('pf-font-line-through', textLineThrough); + + return value; + }); + + // calculate mass left ------------------------------------------------------------ + let massLeft = massTotal - massLogTotal; + massLeft = (massLeft < 0) ? 0 : massLeft; + let massPercentLog = (massTotal > 0) ? Math.floor((100 / massTotal) * massLogTotal) : 0; + + // update easyPieChart and tooltip ------------------------------------------------ + let massPercentLeft = (100 - massPercentLog <= 0) ? 0 : '< ' + (100 - massPercentLog); + massChartCell.data('easyPieChart').enableAnimation().update(massPercentLog * -1); + massChartCell.attr('title', massPercentLeft + '% mass left').tooltip('fixTitle'); + + // update mass cells -------------------------------------------------------------- + massTotalCell.html(massTotal > 0 ? Util.formatMassValue(massTotal) : 'undefined') + .toggleClass('txt-color-red', massTotal <= 0); + massLogCell.html('- ' + Util.formatMassValue(massLog)); + massLeftCell.html( + massLeft > 0 ? + '~ ' + Util.formatMassValue(massLeft) : + (massLeft === 0 && massTotal) ? + 'will collapse' : 'undefined') + .toggleClass('txt-color-red', massLeft <= 0) + .toggleClass('txt-color-success', massLeft > 0); + }) + ); + + element.find('[data-toggle="tooltip"]').tooltip({ + container: 'body' + }); + + return element; + }; + + /** + * get HTML id by connectionId + * @param connectionId + * @returns {string} + */ + let getConnectionElementId = (connectionId) => { + return config.connectionInfoPanelId + connectionId; + }; + + /** + * get all visible connection panel elements + * @param moduleElement + * @returns {*|T|{}} + */ + let getConnectionElements = (moduleElement) => { + return moduleElement.find('.' + config.connectionInfoPanelClass).not('#' + getConnectionElementId(0)); + }; + + /** + * request connection log data + * @param requestData + * @param context + * @param callback + */ + let requestConnectionLogData = (requestData, context, callback) => { + // show loading animation + for(let connectionId of requestData.connectionIds){ + context.moduleElement.find('#' + getConnectionElementId(connectionId) + ' table').showLoadingAnimation(); + } + + $.ajax({ + type: 'POST', + url: Init.path.getMapConnectionData, + data: requestData, + dataType: 'json', + context: context + }).done(function(connectionsData){ + // enrich connectionData with "logs" data (if available) and other "missing" data + for(let i = 0; i < this.connectionsData.length; i++){ + for(let connectionData of connectionsData) { + if(this.connectionsData[i].id === connectionData.id){ + // copy some missing data + this.connectionsData[i].created = connectionData.created; + // check for mass logs and copy data + if(connectionData.logs && connectionData.logs.length){ + this.connectionsData[i].logs = connectionData.logs; + } + // check for signatures and copy data + if(connectionData.signatures && connectionData.signatures.length){ + this.connectionsData[i].signatures = connectionData.signatures; + } + break; + } + } + } + + callback(this.moduleElement, this.connectionsData); + }).always(function(){ + // hide loading animation + for(let contextData of this.connectionsData){ + context.moduleElement.find('#' + getConnectionElementId(contextData.id) + ' table').hideLoadingAnimation(); + } + }); + }; + + /** + * @see requestConnectionLogData + * @param moduleElement + * @param mapId + * @param connectionsData + */ + let getConnectionsLogData = (moduleElement, mapId, connectionsData) => { + let connectionIds = []; + for(let connectionData of connectionsData) { + connectionIds.push(connectionData.id); + } + + let requestData = { + mapId: mapId, + connectionIds: connectionIds, + addData : ['signatures', 'logs'], + // filterData : ['logs'] // do not exclude connections with NO "logs" -> sig data will be used as well + }; + + let contextData = { + moduleElement: moduleElement, + connectionsData: connectionsData + }; + + requestConnectionLogData(requestData, contextData, addConnectionsData); + }; + + /** + * replace/insert dataTables log data + * @param moduleElement + * @param connectionsData + */ + let addConnectionsData = (moduleElement, connectionsData) => { + + let getRowIndexesByData = (dataTable, colName, value) => { + return dataTable.rows().eq(0).filter((rowIdx) => { + return (dataTable.cell(rowIdx, colName + ':name' ).data() === value); + }); + }; + + for(let connectionData of connectionsData) { + // find related dom element for current connection + let connectionElement = moduleElement.find('#' + getConnectionElementId(connectionData.id)); + if(connectionElement.length){ + // attach connectionData to connection information for later use ------------------ + let connectionInfoElement = connectionElement.find('.' + config.moduleTableClass); + connectionInfoElement.data('connectionData', connectionData); + + // update dataTable --------------------------------------------------------------- + let dataTable = connectionElement.find('.dataTable').dataTable().api(); + + if(connectionData.logs && connectionData.logs.length > 0){ + for(let i = 0; i < connectionData.logs.length; i++){ + let rowData = connectionData.logs[i]; + let row = null; + let animationStatus = null; + let indexes = getRowIndexesByData(dataTable, 'index', rowData.id); + if(indexes.length === 0){ + // row not found -> add new row + row = dataTable.row.add( rowData ); + animationStatus = 'added'; + } + /* else{ + // we DON´t expect changes -> no row update) + // update row with FIRST index + //row = dataTable.row( parseInt(indexes[0]) ); + // update row data + //row.data(connectionData.logs[i]); + //animationStatus = 'changed'; + } */ + + if( + animationStatus !== null && + row.length > 0 + ){ + row.nodes().to$().data('animationStatus', animationStatus); + } + } + }else{ + // clear table or leave empty + dataTable.clear(); + } + + // redraw dataTable + dataTable.draw(false); + } + } + }; + + /** + * + * @param moduleElement + * @param mapId + * @param connectionData + */ + let updateConnectionPanel = (moduleElement, mapId, connectionData) => { + let rowElement = moduleElement.find('.row'); + let connectionElement = rowElement.find('#' + getConnectionElementId(connectionData.id)); + + if( !connectionElement.length ){ + connectionElement = getConnectionElement(mapId, connectionData.id); + connectionElement.append(getInformationElement(connectionData)); + + let table = $('', { + class: ['compact', 'stripe', 'order-column', 'row-border', 'nowrap', config.connectionInfoTableClass].join(' ') + }).append(''); + connectionElement.append(table); + + // init empty table + let logTable = table.DataTable({ + pageLength: 8, + paging: true, + pagingType: 'simple', + lengthChange: false, + ordering: true, + order: [[ 4, 'desc' ]], + info: true, + searching: false, + hover: false, + autoWidth: false, + // rowId: 'systemTo', + language: { + emptyTable: 'No jumps recorded', + info: '_START_ to _END_ of _MAX_', + infoEmpty: '' + }, + columnDefs: [ + { + targets: 0, + name: 'index', + title: '', + orderable: false, + searchable: false, + width: 20, + class: 'text-center', + data: 'id' + },{ + targets: 1, + title: '', + width: 26, + orderable: false, + className: ['pf-help-default', 'text-center', config.tableCellImageClass].join(' '), + data: 'ship', + render: { + _: function(data, type, row){ + let value = data.typeId; + if(type === 'display'){ + value = ''; + } + return value; + } + }, + createdCell: function(cell, cellData, rowData, rowIndex, colIndex){ + $(cell).find('img').tooltip(); + } + },{ + targets: 2, + title: '', + width: 26, + orderable: false, + className: ['pf-help-default', 'text-center', config.tableCellImageClass].join(' '), + data: 'created.character', + render: { + _: function(data, type, row){ + let value = data.name; + if(type === 'display'){ + value = ''; + } + return value; + } + }, + createdCell: function(cell, cellData, rowData, rowIndex, colIndex){ + $(cell).find('img').tooltip(); + } + },{ + targets: 3, + title: 'mass', + className: ['text-right'].join(' ') , + data: 'ship.mass', + render: { + _: function(data, type, row){ + let value = data; + if(type === 'display'){ + value = Util.formatMassValue(value); + } + return value; + } + } + },{ + targets: 4, + title: 'log', + width: 55, + className: ['text-right', config.tableCellCounterClass].join(' '), + data: 'created.created', + createdCell: function(cell, cellData, rowData, rowIndex, colIndex){ + $(cell).initTimestampCounter('d'); + } + } + ], + drawCallback: function(settings){ + let animationRows = this.api().rows().nodes().to$().filter(function(a,b ) { + return ( + $(this).data('animationStatus') || + $(this).data('animationTimer') + ); + }); + + for(let i = 0; i < animationRows.length; i++){ + $(animationRows[i]).pulseTableRow($(animationRows[i]).data('animationStatus')); + $(animationRows[i]).removeData('animationStatus'); + } + + }, + footerCallback: function ( row, data, start, end, display ) { + + let api = this.api(); + let sumColumnIndexes = [3]; + + // column data for "sum" columns over this page + let pageTotalColumns = api + .columns( sumColumnIndexes, { page: 'all'} ) + .data(); + + // sum columns for "total" sum + pageTotalColumns.each((colData, index) => { + pageTotalColumns[index] = colData.reduce((a, b) => { + return parseInt(a) + parseInt(b); + }, 0); + }); + + $(sumColumnIndexes).each((index, value) => { + $( api.column( value ).footer() ).text( Util.formatMassValue(pageTotalColumns[index]) ); + + // save mass for further reCalculation of "info" table + connectionElement.find('.' + config.connectionInfoTableCellMassLogClass).data('mass', pageTotalColumns[index]); + }); + + // calculate "info" table ----------------------------------------------------- + connectionElement.find('.' + config.moduleTableClass).trigger('pf:updateInfoTable', connectionElement.data()); + } + }); + + // find position to insert + connectionElement.insertBefore(rowElement.find('#' + getConnectionElementId(0))); + + logTable.on('order.dt search.dt', function(){ + let pageInfo = logTable.page.info(); + logTable.column(0, {search:'applied', order:'applied'}).nodes().each((cell, i) => { + let content = (pageInfo.recordsTotal - i) + '.  '; + $(cell).html(content); + }); + }); + + logTable.on('destroy.dt', function(){ + $(this).destroyTimestampCounter(); + }); + } + }; + + /** + * remove connection Panel from moduleElement + * @param connectionElement + */ + let removeConnectionPanel = (connectionElement) => { + connectionElement = $(connectionElement); + if(connectionElement.length){ + // destroy dataTable (and remove table from DOM) + let logTable = connectionElement.find('.' + config.connectionInfoTableClass); + logTable.dataTable().api().destroy(true); + // remove belonging connectionElement + connectionElement.remove(); + } + }; + + /** + * get connections from ModuleElement + * -> validate with current map data + * @param moduleElement + * @param mapId + * @returns {{connectionsDataUpdate: Array, connectionsDataRemove: Array}} + */ + let getConnectionsDataFromModule = (moduleElement, mapId) => { + let activeMap = Util.getMapModule().getActiveMap(); + let mapData = activeMap.getMapDataFromClient({forceData: true}); + let connectionsData = { + connectionsDataUpdate: [], + connectionsDataRemove: [], + }; + + if(mapData !== false){ + getConnectionElements(moduleElement).each((i, connectionElement) => { + let removeConnectionPanel = true; + let connectionData = {id: $(connectionElement).data('connectionId') }; + + let connection = $().getConnectionById(mapId, connectionData.id); + if(connection){ + let connectionDataTemp = MapUtil.getDataByConnection(connection); + if(connectionDataTemp.id > 0){ + // connection still on map - OK + removeConnectionPanel = false; + connectionData = connectionDataTemp; + } + } + + if(removeConnectionPanel){ + connectionsData.connectionsDataRemove.push(connectionData); + }else{ + connectionsData.connectionsDataUpdate.push(connectionData); + } + }); + } + + return connectionsData; + }; + + /** + * update/init multiple connection panels at once + * @param moduleElement + * @param mapId + * @param connectionsDataUpdate + * @param connectionsDataRemove + */ + let updateConnectionPanels = (moduleElement, mapId, connectionsDataUpdate, connectionsDataRemove) => { + for(let connectionData of connectionsDataRemove){ + let connectionElement = moduleElement.find('#' + getConnectionElementId(connectionData.id)); + removeConnectionPanel(connectionElement); + } + + for(let connectionData of connectionsDataUpdate){ + updateConnectionPanel(moduleElement, mapId, connectionData); + } + + // request connectionsLogData for each updated connection + if(connectionsDataUpdate.length){ + getConnectionsLogData(moduleElement, mapId, connectionsDataUpdate); + } + + // remove module if no connection panel left + // --> all connection deselected on map + let connectionElements = getConnectionElements(moduleElement); + if(connectionElements.length === 0){ + MapUtil.getTabContentElementByMapElement(moduleElement).trigger('pf:removeConnectionModules'); + } + + // hide "control" panel when multiple connection + moduleElement.find('#' + getConnectionElementId(0)).toggle(connectionElements.length < 2); + }; + + /** + * set module observer + * @param moduleElement + * @param mapId + */ + let setModuleObserver = (moduleElement, mapId) => { + $(document).off('pf:updateConnectionInfoModule').on('pf:updateConnectionInfoModule', function(e, data){ + updateConnectionPanels( + moduleElement, + data.mapId, + MapUtil.getDataByConnections(data.connectionsUpdate), + MapUtil.getDataByConnections(data.connectionsRemove) + ); + }); + + $(document).off('pf:activeShip').on('pf:activeShip', function(e){ + moduleElement.find('.' + config.connectionInfoPanelClass).each((i, connectionElement) => { + $(connectionElement).find('.' + config.moduleTableClass).each((i, tableElement) => { + $(tableElement).trigger('pf:calcInfoTable'); + }); + }); + }); + + // init toggle active ship ---------------------------------------------------------------- + moduleElement.find('.' + config.moduleHeadlineIconCurrentMassClass).on('click', function(e){ + let currentMassIcon = $(this).toggleClass('active'); + moduleElement.find('.' + config.connectionInfoPanelClass).each((i, connectionElement) => { + $(connectionElement).find('.' + config.moduleTableClass).each((i, tableElement) => { + $(tableElement).data('showShip', currentMassIcon.hasClass('active')).trigger('pf:calcInfoTable'); + }); + }); + }); + + // init refresh connections --------------------------------------------------------------- + moduleElement.find('.' + config.moduleHeadlineIconRefreshClass).on('click', function(e){ + refreshConnectionPanels(moduleElement, mapId); + }); + }; + + /** + * refresh all connection panels in a module + * @param moduleElement + * @param mapId + */ + let refreshConnectionPanels = (moduleElement, mapId) => { + let connectionsData = getConnectionsDataFromModule(moduleElement, mapId); + updateConnectionPanels(moduleElement, mapId, connectionsData.connectionsDataUpdate, connectionsData.connectionsDataRemove); + }; + + /** + * before module destroy callback + * @param moduleElement + */ + let beforeDestroy = (moduleElement) => { + getConnectionElements(moduleElement).each((i, connectionElement) => { + removeConnectionPanel(connectionElement); + }); + }; + + /** + * init callback + * @param moduleElement + * @param mapId + * @param connectionData + */ + let initModule = (moduleElement, mapId, connectionData) => { + setModuleObserver(moduleElement, mapId); + }; + + /** + * get module element + * @param parentElement + * @param mapId + * @param connections + * @returns {*|jQuery|HTMLElement} + */ + let getModule = (parentElement, mapId, connections) => { + // create new module container + let moduleElement = $('
').append( + $('
', { + class: config.moduleHeadClass + }).append( + $('
', { + class: config.moduleHandlerClass + }), + $('
', { + text: 'Connection' + }), + getHeadlineToolbar() + ) + ); + + let rowElement = $('
', { + class: 'row' + }); + + moduleElement.append(rowElement); + + rowElement.append(getInfoPanelControl(mapId)); + + updateConnectionPanels(moduleElement, mapId, MapUtil.getDataByConnections(connections), []); + + return moduleElement; + }; + + return { + config: config, + getModule: getModule, + initModule: initModule, + beforeDestroy: beforeDestroy + }; +}); \ No newline at end of file diff --git a/js/app/ui/demo_map.js b/js/app/ui/demo_map.js index 48088dc3..ab5f78b1 100644 --- a/js/app/ui/demo_map.js +++ b/js/app/ui/demo_map.js @@ -9,7 +9,7 @@ define([ 'use strict'; - var config = { + let config = { headerSystemsContainerId: 'pf-header-systems', // id for systems layer headerSystemConnectorsId: 'pf-header-connectors', // id for connectors layer headerConnectionsContainerId: 'pf-header-connections', // id for connections layer @@ -26,9 +26,9 @@ define([ * draw systems layer * @param callback */ - var drawSystems = function(callback){ + let drawSystems = function(callback){ - var pathObj = { + let pathObj = { systems: { strokepath: [ // systems ======================================================================= @@ -98,11 +98,11 @@ define([ * draw connectors layer * @param callback */ - var drawConnectors = function(callback){ + let drawConnectors = function(callback){ - var connectorDuration = 150; + let connectorDuration = 150; - var pathObj = { + let pathObj = { connectors: { strokepath: [ // connectors ==================================================================== @@ -184,14 +184,14 @@ define([ * draw connections layer * @param callback */ - var drawConnections = function(callback){ + let drawConnections = function(callback){ - var connectionDuration = 250; - var connectionWidth = 8; - var connectionInnerWidth = 4; - var connectionBorderColor = '#63676A'; //gray + let connectionDuration = 250; + let connectionWidth = 8; + let connectionInnerWidth = 4; + let connectionBorderColor = '#63676A'; //gray - var pathObj = { + let pathObj = { connections: { strokepath: [ // connections ==================================================================== @@ -288,7 +288,7 @@ define([ * draw background layer * @param callback */ - var drawBackground = function(callback){ + let drawBackground = function(callback){ $('#' + config.headerBackgroundContainerId + ' .' + config.headerSystemClass).velocity('transition.bounceUpIn', { stagger: 150, complete: function(){ @@ -304,7 +304,7 @@ define([ * @param callback */ $.fn.drawDemoMap = function(callback){ - var canvasElement = $(this); + let canvasElement = $(this); // draw systems diff --git a/js/app/ui/dialog/account_settings.js b/js/app/ui/dialog/account_settings.js index e535ab4e..e8d18cc7 100644 --- a/js/app/ui/dialog/account_settings.js +++ b/js/app/ui/dialog/account_settings.js @@ -11,7 +11,7 @@ define([ ], function($, Init, Util, Render, bootbox) { 'use strict'; - var config = { + let config = { // select character dialog settingsDialogId: 'pf-settings-dialog', // id for "settings" dialog settingsAccountContainerId: 'pf-settings-dialog-account', // id for the "account" container @@ -37,14 +37,14 @@ define([ $.fn.showSettingsDialog = function(){ // check if there are other dialogs open - var openDialogs = Util.getOpenDialogs(); + let openDialogs = Util.getOpenDialogs(); if(openDialogs.length > 0){ return false; } requirejs(['text!templates/dialog/settings.html', 'mustache'], function(template, Mustache) { - var data = { + let data = { id: config.settingsDialogId, settingsAccountContainerId: config.settingsAccountContainerId, settingsShareContainerId: config.settingsShareContainerId, @@ -56,9 +56,9 @@ define([ ccpImageServer: Init.url.ccpImageServer }; - var content = Mustache.render(template, data); + let content = Mustache.render(template, data); - var accountSettingsDialog = bootbox.dialog({ + let accountSettingsDialog = bootbox.dialog({ title: 'Account settings', message: content, buttons: { @@ -72,19 +72,19 @@ define([ callback: function() { // get the current active form - var form = $('#' + config.settingsDialogId).find('form').filter(':visible'); + let form = $('#' + config.settingsDialogId).find('form').filter(':visible'); // validate form form.validator('validate'); // check whether the form is valid - var formValid = form.isValidForm(); + let formValid = form.isValidForm(); if(formValid === true){ - var tabFormValues = form.getFormValues(); + let tabFormValues = form.getFormValues(); // send Tab data and store values - var requestData = { + let requestData = { formData: tabFormValues }; @@ -134,7 +134,7 @@ define([ }).fail(function( jqXHR, status, error) { accountSettingsDialog.find('.modal-content').hideLoadingAnimation(); - var reason = status + ' ' + error; + let reason = status + ' ' + error; Util.showNotify({title: jqXHR.status + ': saveAccountSettings', text: reason, type: 'error'}); // set new captcha for any request @@ -147,7 +147,7 @@ define([ if(jqXHR.status === 500){ if(jqXHR.responseText){ - var errorObj = $.parseJSON(jqXHR.responseText); + let errorObj = $.parseJSON(jqXHR.responseText); if( errorObj.error && @@ -172,11 +172,11 @@ define([ // after modal is shown ======================================================================= accountSettingsDialog.on('shown.bs.modal', function(e) { - var dialogElement = $(this); - var form = dialogElement.find('form'); + let dialogElement = $(this); + let form = dialogElement.find('form'); // request captcha image and show - var captchaImageWrapperContainer = $('#' + config.captchaImageWrapperId); + let captchaImageWrapperContainer = $('#' + config.captchaImageWrapperId); captchaImageWrapperContainer.showCaptchaImage(config.captchaKeyUpdateAccount); // init captcha refresh button @@ -200,7 +200,7 @@ define([ off: 'Disable ', onstyle: 'success', offstyle: 'warning', - width: 90, + width: 100, height: 30 }); diff --git a/js/app/ui/dialog/credit.js b/js/app/ui/dialog/credit.js index 8a668b01..aa3ebe30 100644 --- a/js/app/ui/dialog/credit.js +++ b/js/app/ui/dialog/credit.js @@ -12,7 +12,7 @@ define([ ], function($, Init, Util, Render, bootbox) { 'use strict'; - var config = { + let config = { // jump info dialog creditsDialogClass: 'pf-credits-dialog', // class for credits dialog creditsDialogLogoContainerId: 'pf-logo-container' // id for logo element @@ -25,14 +25,14 @@ define([ requirejs(['text!templates/dialog/credit.html', 'mustache'], function(template, Mustache) { - var data = { + let data = { logoContainerId: config.creditsDialogLogoContainerId, version: Util.getVersion() }; - var content = Mustache.render(template, data); + let content = Mustache.render(template, data); - var creditDialog = bootbox.dialog({ + let creditDialog = bootbox.dialog({ className: config.creditsDialogClass, title: 'Licence', message: content diff --git a/js/app/ui/dialog/delete_account.js b/js/app/ui/dialog/delete_account.js index 5facd5c3..b60e9acb 100644 --- a/js/app/ui/dialog/delete_account.js +++ b/js/app/ui/dialog/delete_account.js @@ -10,7 +10,7 @@ define([ ], function($, Init, Util, bootbox) { 'use strict'; - var config = { + let config = { // global dialog deleteAccountId: 'pf-dialog-delete-account', // dialog id @@ -27,16 +27,16 @@ define([ requirejs(['text!templates/dialog/delete_account.html', 'mustache'], function(template, Mustache) { - var data = { + let data = { deleteAccountId: config.deleteAccountId, userData: Util.getCurrentUserData(), captchaImageWrapperId: config.captchaImageWrapperId, formErrorContainerClass: Util.config.formErrorContainerClass }; - var content = Mustache.render(template, data); + let content = Mustache.render(template, data); - var deleteAccountDialog = bootbox.dialog({ + let deleteAccountDialog = bootbox.dialog({ title: 'Delete account', message: content, buttons: { @@ -48,20 +48,20 @@ define([ label: ' delete account', className: 'btn-danger', callback: function() { - var dialogElement = $(this); - var form = dialogElement.find('form'); + let dialogElement = $(this); + let form = dialogElement.find('form'); // validate form form.validator('validate'); - var formValid = form.isValidForm(); + let formValid = form.isValidForm(); if(formValid){ - var formValues = form.getFormValues(); + let formValues = form.getFormValues(); if(! $.isEmptyObject(formValues) ){ // send Tab data and store values - var requestData = { + let requestData = { formData: formValues }; @@ -91,7 +91,7 @@ define([ }).fail(function( jqXHR, status, error) { dialogElement.find('.modal-content').hideLoadingAnimation(); - var reason = status + ' ' + error; + let reason = status + ' ' + error; Util.showNotify({title: jqXHR.status + ': deleteAccount', text: reason, type: 'error'}); }); diff --git a/js/app/ui/dialog/jump_info.js b/js/app/ui/dialog/jump_info.js index 00d4528e..c78c0484 100644 --- a/js/app/ui/dialog/jump_info.js +++ b/js/app/ui/dialog/jump_info.js @@ -8,31 +8,85 @@ define([ 'app/util', 'app/render', 'bootbox', -], function($, Init, Util, Render, bootbox) { - +], ($, Init, Util, Render, bootbox) => { 'use strict'; let config = { // jump info dialog - jumpInfoDialogClass: 'pf-jump-info-dialog' // class for jump info dialog + jumpInfoDialogClass: 'pf-jump-info-dialog', // class for jump info dialog + wormholeInfoMassTableClass: 'pf-wormhole-info-mass-table', // class for "wormhole mass" table + wormholeInfoJumpTableClass: 'pf-wormhole-info-jump-table' // class for "wormhole jump" table }; /** * show jump info dialog */ $.fn.showJumpInfoDialog = function(){ - - requirejs(['text!templates/dialog/jump_info.html', 'mustache'], function(template, Mustache) { - let data = {}; + requirejs(['text!templates/dialog/jump_info.html', 'mustache'], (template, Mustache) => { + let data = { + config: config, + wormholes: Object.keys(Init.wormholes).map(function(k) { return Init.wormholes[k]; }), // convert Json to array + securityClass: function(){ + return function(value, render){ + return this.Util.getSecurityClassForSystem( render(value) ); + }.bind(this); + }.bind({ + Util: Util + }), + massValue: function(){ + return function(value, render){ + let mass = render(value); + switch(mass.length){ + case 0: return ''; + case 1: return 'Yes'; + default: return this.Util.formatMassValue(mass); + } + }.bind(this); + }.bind({ + Util: Util + }) + }; let content = Mustache.render(template, data); - let signatureReaderDialog = bootbox.dialog({ + let jumpDialog = bootbox.dialog({ className: config.jumpInfoDialogClass, title: 'Wormhole jump information', - message: content + message: content, + show: false }); - }); + jumpDialog.on('show.bs.modal', function(e) { + // init dataTable + $(this).find('.' + config.wormholeInfoMassTableClass).DataTable({ + pageLength: 25, + lengthMenu: [[10, 20, 25, 30, 40, -1], [10, 20, 25, 30, 40, 'All']], + autoWidth: false, + language: { + emptyTable: 'No wormholes', + zeroRecords: 'No wormholes found', + lengthMenu: 'Show _MENU_ wormholes', + info: 'Showing _START_ to _END_ of _TOTAL_ wormholes' + }, + }); + $(this).find('.' + config.wormholeInfoJumpTableClass).DataTable({ + pageLength: -1, + paging: false, + lengthChange: false, + ordering: false, + searching: false, + info: false, + autoWidth: false, + language: { + emptyTable: 'No wormholes', + zeroRecords: 'No wormholes found', + lengthMenu: 'Show _MENU_ wormholes', + info: 'Showing _START_ to _END_ of _TOTAL_ wormholes' + }, + }); + }); + + jumpDialog.modal('show'); + }); }; }); \ No newline at end of file diff --git a/js/app/ui/dialog/map_info.js b/js/app/ui/dialog/map_info.js index c297daaf..fd617997 100644 --- a/js/app/ui/dialog/map_info.js +++ b/js/app/ui/dialog/map_info.js @@ -184,7 +184,6 @@ define([ systemsElement.showLoadingAnimation(config.loadingOptions); - // table init complete systemTable.on( 'init.dt', function () { systemsElement.hideLoadingAnimation(); @@ -193,6 +192,10 @@ define([ tooltipElements.tooltip(); }); + systemTable.on('destroy.dt', function(){ + $(this).destroyTimestampCounter(); + }); + // prepare data for dataTables let systemsData = []; for(let i = 0; i < mapData.data.systems.length; i++){ @@ -438,13 +441,6 @@ define([ data: 'updated', createdCell: function(cell, cellData, rowData, rowIndex, colIndex){ $(cell).initTimestampCounter(); - - // highlight cell - let diff = new Date().getTime() - cellData * 1000; - let dateDiff = new Date(diff); - if(dateDiff.getUTCDate() > 1){ - $(cell).addClass('txt-color txt-color-warning'); - } } },{ title: '', @@ -545,7 +541,6 @@ define([ let connectionClasses = []; for(let k = 0; k < tempConnectionData.type.length; k++){ connectionClasses.push( MapUtil.getConnectionInfo( tempConnectionData.type[k], 'cssClass') ); - } connectionClasses = connectionClasses.join(' '); @@ -626,11 +621,13 @@ define([ createdCell: function(cell, cellData, rowData, rowIndex, colIndex){ $(cell).initTimestampCounter(); - // highlight cell - let diff = new Date().getTime() - cellData * 1000; - let dateDiff = new Date(diff); - if(dateDiff.getUTCDate() > 1){ - $(cell).addClass('txt-color txt-color-warning'); + if(rowData.scope.scope_sort === 'wh'){ + // highlight cell + let diff = new Date().getTime() - cellData * 1000; + let dateDiff = new Date(diff); + if(dateDiff.getUTCDate() > 1){ + $(cell).addClass('txt-color txt-color-warning'); + } } } },{ @@ -1269,7 +1266,6 @@ define([ // load users table usersElement.initUsersInfoTable(mapData); - }); // events for tab change diff --git a/js/app/ui/dialog/releases.js b/js/app/ui/dialog/releases.js index b97fb46f..5e4d9159 100644 --- a/js/app/ui/dialog/releases.js +++ b/js/app/ui/dialog/releases.js @@ -11,7 +11,7 @@ define([ ], function($, Init, Util, Render, bootbox) { 'use strict'; - var config = { + let config = { releasesDialogClass: 'pf-releases-dialog' // class for "Releases" dialog }; @@ -19,10 +19,10 @@ define([ * load release information in dialog * @param releasesDialog */ - var loadDialogData = function(releasesDialog){ + let loadDialogData = function(releasesDialog){ // lock dialog - var dialogContent = releasesDialog.find('.modal-content'); + let dialogContent = releasesDialog.find('.modal-content'); dialogContent.showLoadingAnimation(); $.ajax({ @@ -32,18 +32,18 @@ define([ dataType: 'json' }).done(function(releasesData){ requirejs(['text!templates/ui/timeline_element.html', 'mustache'], function(template, Mustache) { - for(var i = 0; i < releasesData.length; i++){ - var releaseData = releasesData[i]; + for(let i = 0; i < releasesData.length; i++){ + let releaseData = releasesData[i]; // template vars - var data = { + let data = { isFirst: (i === 0), isOdd: (i % 2 !== 0), releaseDate: releaseData.published_at.substr(0, 10), releaseData: releaseData }; - var content = Mustache.render(template, data); + let content = Mustache.render(template, data); releasesDialog.find('ul.timeline').append(content); } @@ -55,7 +55,7 @@ define([ }); }); }).fail(function( jqXHR, status, error) { - var reason = status + ' ' + jqXHR.status + ': ' + error; + let reason = status + ' ' + jqXHR.status + ': ' + error; Util.showNotify({title: jqXHR.status + ': login', text: reason, type: 'error'}); }).always(function() { dialogContent.hideLoadingAnimation(); @@ -66,9 +66,9 @@ define([ * show releases dialog */ $.fn.releasesDialog = function(){ - var content = '
    '; + let content = '
      '; - var releasesDialog = bootbox.dialog({ + let releasesDialog = bootbox.dialog({ className: config.releasesDialogClass, title: 'Releases', size: 'large', diff --git a/js/app/ui/system_graph.js b/js/app/ui/system_graph.js index 965f8dcb..e0ac6550 100644 --- a/js/app/ui/system_graph.js +++ b/js/app/ui/system_graph.js @@ -12,14 +12,17 @@ define([ let config = { // module info - moduleClass: 'pf-module', // class for each module + modulePosition: 3, + moduleName: 'systemGraph', + moduleHeadClass: 'pf-module-head', // class for module header + moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler // system graph module - systemGraphModuleClass: 'pf-system-graph-module', // class for this module + moduleTypeClass: 'pf-system-graph-module', // class for this module systemGraphClass: 'pf-system-graph', // class for each graph // system graph labels - systemGraphLabels: { + systemGraphs: { jumps: { headline: 'Jumps', units: 'jumps', @@ -55,9 +58,8 @@ define([ */ let getInfoForGraph = function(graphKey, option){ let info = ''; - - if(config.systemGraphLabels.hasOwnProperty(graphKey)){ - info = config.systemGraphLabels[graphKey][option]; + if(config.systemGraphs.hasOwnProperty(graphKey)){ + info = config.systemGraphs[graphKey][option]; } return info; @@ -68,9 +70,9 @@ define([ * @param graphElement * @param graphKey * @param graphData + * @param eventLine */ let initGraph = function(graphElement, graphKey, graphData, eventLine){ - if(graphData.length > 0){ let labelYFormat = function(y){ return Math.round(y); @@ -117,125 +119,130 @@ define([ }; /** - * draw graph module - * @param parentElement - * @param systemData + * request graphs data + * @param requestData + * @param context + * @param callback */ - let drawModule = function(parentElement, systemData){ - - // graph data is available for k-space systems - if(systemData.type.id === 2){ - let requestData = { - systemIds: [systemData.systemId] - }; - - // calculate time offset until system created - let serverData = Util.getServerTime(); - - let timestampNow = Math.floor(serverData.getTime() / 1000); - let timeSinceUpdate = timestampNow - systemData.updated; - - let timeInHours = Math.floor(timeSinceUpdate / 3600); - let timeInMinutes = Math.floor((timeSinceUpdate % 3600) / 60); - let timeInMinutesPercent = ( timeInMinutes / 60 ).toFixed(2); - let eventLine = timeInHours + timeInMinutesPercent; - - // graph is from right to left -> convert event line - eventLine = 23 - eventLine; - - $.ajax({ - type: 'POST', - url: Init.path.getSystemGraphData, - data: requestData, - dataType: 'json' - }).done(function(systemGraphsData){ - - if( Object.keys(systemGraphsData).length > 0 ){ - // create new (hidden) module container - let moduleElement = $('
      ', { - class: [config.moduleClass, config.systemGraphModuleClass].join(' '), - css: {opacity: 0} - }); - - // insert at the correct position - if($(parentElement).children().length === 1){ - $(parentElement).append(moduleElement); - }else{ - $(parentElement).find('>:first-child').after(moduleElement); - } - - // row element - let rowElement = $('
      ', { - class: 'row' - }); - moduleElement.append(rowElement); - - $.each(systemGraphsData, function(systemId, graphsData){ - $.each(graphsData, function(graphKey, graphData){ - - let colElement = $('
      ', { - class: ['col-xs-12', 'col-sm-6', 'col-md-4'].join(' ') - }); - - let headlineElement = $('
      ').text( getInfoForGraph(graphKey, 'headline') ); - - colElement.append(headlineElement); - - let graphElement = $('
      ', { - class: config.systemGraphClass - }); - - colElement.append(graphElement); - - rowElement.append(colElement); - initGraph(graphElement, graphKey, graphData, eventLine); - }); - }); - - moduleElement.append($('
      ', { - css: {'clear': 'both'} - })); - - // show module - moduleElement.velocity('transition.slideDownIn', { - duration: Init.animationSpeed.mapModule, - delay: Init.animationSpeed.mapModule - }); - } - }).fail(function( jqXHR, status, error) { - let reason = status + ' ' + error; - Util.showNotify({title: jqXHR.status + ': System graph data', text: reason, type: 'warning'}); - $(document).setProgramStatus('problem'); - }); - } + let requestGraphData = (requestData, context, callback) => { + // show loading animation + context.moduleElement.find('.' + config.systemGraphClass).showLoadingAnimation(); + $.ajax({ + type: 'POST', + url: Init.path.getSystemGraphData, + data: requestData, + dataType: 'json', + context: context + }).done(function(systemGraphsData){ + callback(this, systemGraphsData); + }).fail(function( jqXHR, status, error) { + let reason = status + ' ' + error; + Util.showNotify({title: jqXHR.status + ': System graph data', text: reason, type: 'warning'}); + $(document).setProgramStatus('problem'); + this.moduleElement.hide(); + }).always(function(){ + // hide loading animation + context.moduleElement.find('.' + config.systemGraphClass).hideLoadingAnimation(); + }); }; - /** - * main module load function - * @param systemData + * update graph elements with data + * @param context + * @param systemGraphsData */ - $.fn.drawSystemGraphModule = function(systemData){ + let addGraphData = (context, systemGraphsData) => { - let parentElement = $(this); + // calculate time offset until system created ----------------------------------------------------------------- + let serverData = Util.getServerTime(); + let timestampNow = Math.floor(serverData.getTime() / 1000); + let timeSinceUpdate = timestampNow - context.systemData.updated.updated; - // check if module already exists - let moduleElement = parentElement.find('.' + config.systemGraphModuleClass); + let timeInHours = Math.floor(timeSinceUpdate / 3600); + let timeInMinutes = Math.floor((timeSinceUpdate % 3600) / 60); + let timeInMinutesPercent = ( timeInMinutes / 60 ).toFixed(2); + let eventLine = timeInHours + timeInMinutesPercent; - if(moduleElement.length > 0){ - moduleElement.velocity('transition.slideDownOut', { - duration: Init.animationSpeed.mapModule, - complete: function(tempElement){ - $(tempElement).remove(); - drawModule(parentElement, systemData); - } - }); - }else{ - drawModule(parentElement, systemData); + // graph is from right to left -> convert event line + eventLine = 23 - eventLine; + + // update graph data ------------------------------------------------------------------------------------------ + for (let [systemId, graphsData] of Object.entries(systemGraphsData)){ + for (let [graphKey, graphData] of Object.entries(graphsData)){ + let graphElement = context.moduleElement.find('[data-graph="' + graphKey + '"]'); + initGraph(graphElement, graphKey, graphData, eventLine); + } } }; + /** + * @see requestGraphData + * @param moduleElement + * @param mapId + * @param systemData + */ + let updateGraphPanel = (moduleElement, mapId, systemData) => { + let requestData = { + systemIds: [systemData.systemId] + }; + let contextData = { + moduleElement: moduleElement, + systemData: systemData + }; + + requestGraphData(requestData, contextData, addGraphData); + }; + + /** + * get module element + * @param parentElement + * @param mapId + * @param systemData + * @returns {*} + */ + let getModule = (parentElement, mapId, systemData) => { + // graph data is available for k-space systems + let moduleElement = null; + if(systemData.type.id === 2){ + moduleElement = $('
      '); + let rowElement = $('
      ', { + class: 'row' + }); + + for (let [graphKey, graphConfig] of Object.entries(config.systemGraphs)){ + rowElement.append( + $('
      ', { + class: ['col-xs-12', 'col-sm-6', 'col-md-4'].join(' ') + }).append( + $('
      ', { + class: config.moduleHeadClass + }).append( + $('
      ', { + class: config.moduleHandlerClass + }), + $('
      ', { + text: getInfoForGraph(graphKey, 'headline') + }) + ), + $('
      ', { + class: config.systemGraphClass + }).attr('data-graph', graphKey) + ) + ); + } + moduleElement.append(rowElement); + + updateGraphPanel(moduleElement, mapId, systemData); + } + + return moduleElement; + }; + + return { + config: config, + getModule: getModule + }; }); diff --git a/js/app/ui/system_info.js b/js/app/ui/system_info.js index 43ebcae6..a00b8db3 100644 --- a/js/app/ui/system_info.js +++ b/js/app/ui/system_info.js @@ -13,10 +13,11 @@ define([ let config = { // module info - moduleClass: 'pf-module', // class for each module + modulePosition: 2, + moduleName: 'systemInfo', // system info module - systemInfoModuleClass: 'pf-system-info-module', // module wrapper + moduleTypeClass: 'pf-system-info-module', // class for this module // breadcrumb constellationLinkClass: 'pf-system-info-constellation', // class for "constellation" name @@ -24,7 +25,7 @@ define([ typeLinkClass: 'pf-system-info-type', // class for "type" name // info table - systemInfoTableClass: 'pf-system-info-table', // class for system info table + systemInfoTableClass: 'pf-module-table', // class for system info table systemInfoNameInfoClass: 'pf-system-info-name', // class for "name" information element systemInfoEffectInfoClass: 'pf-system-info-effect', // class for "effect" information element systemInfoStatusLabelClass: 'pf-system-info-status-label', // class for "status" information element @@ -54,7 +55,6 @@ define([ * set module observer and look for relevant system data to update */ let setModuleObserver = function(moduleElement){ - $(document).off('pf:updateSystemInfoModule').on('pf:updateSystemInfoModule', function(e, data){ if(data){ moduleElement.updateSystemInfoModule(data); @@ -197,24 +197,19 @@ define([ }; /** - * + * get module element * @param parentElement * @param mapId * @param systemData */ - let drawModule = function(parentElement, mapId, systemData){ + let getModule = function(parentElement, mapId, systemData){ // create new module container - let moduleElement = $('
      ', { - class: [config.moduleClass, config.systemInfoModuleClass].join(' '), - css: {opacity: 0} - }); + let moduleElement = $('
      '); // store systemId -> module can be updated with the correct data moduleElement.data('id', systemData.id); - parentElement.prepend(moduleElement); - // shattered wormhole info data let shatteredWormholeInfo = false; @@ -240,8 +235,8 @@ define([ position: moduleElement, link: 'append', functions: { - after: function(){ - let tempModuleElement = parentElement.find('.' + config.systemInfoModuleClass); + after: function(conf){ + let tempModuleElement = conf.position; // lock "description" field until first update tempModuleElement.find('.' + config.descriptionArea).showLoadingAnimation(); @@ -426,7 +421,6 @@ define([ return 'Loading...'; } - showModule(moduleElement); } } }; @@ -464,52 +458,40 @@ define([ }; Render.showModule(moduleConfig, moduleData); + + return moduleElement; }; /** - * show system info module with animation + * init callback * @param moduleElement - */ - let showModule = function(moduleElement){ - moduleElement.velocity('transition.slideDownIn', { - duration: Init.animationSpeed.mapModule, - delay: Init.animationSpeed.mapModule, - complete: function(){ - // set module observer - setModuleObserver(moduleElement); - - // enable auto update - disableModuleUpdate = false; - } - }); - }; - - /** - * update system info module * @param mapId * @param systemData */ - $.fn.drawSystemInfoModule = function(mapId, systemData){ + let initModule = function(moduleElement, mapId, systemData){ + // set module observer + setModuleObserver(moduleElement); - let parentElement = $(this); - - // check if module already exists - let moduleElement = parentElement.find('.' + config.systemInfoModuleClass); - - if(moduleElement.length > 0){ - moduleElement.velocity('transition.slideDownOut', { - duration: Init.animationSpeed.mapModule, - complete: function(tempElement){ - $(tempElement).remove(); - - drawModule(parentElement, mapId, systemData); - } - }); - }else{ - drawModule(parentElement, mapId, systemData); - } + // enable auto update + disableModuleUpdate = false; }; + /** + * efore module destroy callback + * @param moduleElement + */ + let beforeDestroy = (moduleElement) => { + // remove xEditable description textarea + let descriptionTextareaElement = moduleElement.find('.' + config.descriptionTextareaElementClass); + descriptionTextareaElement.editable('destroy'); + }; + + return { + config: config, + getModule: getModule, + initModule: initModule, + beforeDestroy: beforeDestroy + }; }); diff --git a/js/app/ui/system_killboard.js b/js/app/ui/system_killboard.js index b58483b0..2fb74161 100644 --- a/js/app/ui/system_killboard.js +++ b/js/app/ui/system_killboard.js @@ -8,13 +8,16 @@ define([ let config = { // module info - moduleClass: 'pf-module', // class for each module + modulePosition: 2, + moduleName: 'systemKillboard', + moduleHeadClass: 'pf-module-head', // class for module header + moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler // headline toolbar - systemModuleHeadlineIcon: 'pf-module-icon-button', // class for toolbar icons in the head + moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head // system killboard module - systemKillboardModuleClass: 'pf-system-killboard-module', // module wrapper + moduleTypeClass: 'pf-system-killboard-module', // class for this module systemKillboardGraphKillsClass: 'pf-system-killboard-graph-kills', // class for system kill graph // system killboard list @@ -178,34 +181,8 @@ define([ * @param systemData */ $.fn.updateSystemInfoGraphs = function(systemData){ - let moduleElement = $(this); - - // headline toolbar icons - let headlineToolbar = $('
      ', { - class: 'pull-right' - }).append( - $('', { - class: ['fa', 'fa-fw', 'fa-external-link ', config.systemModuleHeadlineIcon].join(' '), - title: 'zkillboard.com' - }).on('click', function(e){ - window.open( - '//zkillboard.com/system/' + systemData.systemId, - '_blank' - ); - }).attr('data-toggle', 'tooltip') - ); - - moduleElement.append(headlineToolbar); - - // headline - let headline = $('
      ', { - text: 'Killboard' - }); - - moduleElement.append(headline); - let killboardGraphElement = $('
      ', { class: config.systemKillboardGraphKillsClass }); @@ -222,14 +199,11 @@ define([ // private function draws a "system kills" graph let drawGraph = function(data){ - let tableData = data.tableData; // change order (show right to left) tableData.reverse(); - - if(data.count === 0){ labelOptions.type = 'label-success'; label = getLabel( 'No kills found within the last 24h', labelOptions ); @@ -247,6 +221,7 @@ define([ Morris.Bar({ element: killboardGraphElement, resize: true, + redraw: true, grid: true, gridStrokeWidth: 0.3, gridTextSize: 9, @@ -418,64 +393,71 @@ define([ }); }; + /** + * get module toolbar element + * @param systemData + * @returns {*|jQuery|HTMLElement|void} + */ + let getHeadlineToolbar = (systemData) => { + let headlineToolbar = $('
      ', { + class: 'pull-right' + }).append( + $('', { + class: ['fa', 'fa-fw', 'fa-external-link ', config.moduleHeadlineIconClass].join(' '), + title: 'zkillboard.com' + }).on('click', function(e){ + window.open( + '//zkillboard.com/system/' + systemData.systemId, + '_blank' + ); + }).attr('data-toggle', 'tooltip') + ); + + headlineToolbar.find('[data-toggle="tooltip"]').tooltip({ + container: 'body' + }); + + return headlineToolbar; + }; + + /** + * before module "show" callback + * @param moduleElement + * @param systemData + */ + let beforeShow = (moduleElement, systemData) => { + // update graph + moduleElement.updateSystemInfoGraphs(systemData); + }; + /** * get module element * @param parentElement * @param systemData * @returns {*|jQuery|HTMLElement} */ - let getModule = function(parentElement, systemData){ - + let getModule = (parentElement, mapId, systemData) => { // create new module container - let moduleElement = $('
      ', { - class: [config.moduleClass, config.systemKillboardModuleClass].join(' '), - css: {opacity: 0} - }); - - parentElement.append(moduleElement); - - // update graph - moduleElement.updateSystemInfoGraphs(systemData); + let moduleElement = $('
      ').append( + $('
      ', { + class: config.moduleHeadClass + }).append( + $('
      ', { + class: config.moduleHandlerClass + }), + $('
      ', { + text: 'Killboard' + }), + getHeadlineToolbar(systemData) + ) + ); return moduleElement; }; - - /** - * main module load function - * @param systemData - */ - $.fn.drawSystemKillboardModule = function(systemData){ - - let parentElement = $(this); - - // show route module - let showModule = function(moduleElement){ - if(moduleElement){ - moduleElement.velocity('transition.slideDownIn', { - duration: Init.animationSpeed.mapModule, - delay: Init.animationSpeed.mapModule - }); - } - }; - - // check if module already exists - let moduleElement = parentElement.find('.' + config.systemKillboardModuleClass); - - if(moduleElement.length > 0){ - moduleElement.velocity('transition.slideDownOut', { - duration: Init.animationSpeed.mapModule, - complete: function(tempElement){ - $(tempElement).remove(); - - moduleElement = getModule(parentElement, systemData); - showModule(moduleElement); - } - }); - }else{ - moduleElement = getModule(parentElement, systemData); - showModule(moduleElement); - } - + return { + config: config, + getModule: getModule, + beforeShow: beforeShow }; }); \ No newline at end of file diff --git a/js/app/ui/system_route.js b/js/app/ui/system_route.js index e67b0e8c..154fec75 100644 --- a/js/app/ui/system_route.js +++ b/js/app/ui/system_route.js @@ -13,18 +13,21 @@ define([ let config = { // module info - moduleClass: 'pf-module', // class for each module + modulePosition: 1, + moduleName: 'systemRoute', + moduleHeadClass: 'pf-module-head', // class for module header + moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler routeCacheTTL: 10, // route cache timer (client) in seconds // system route module - systemRouteModuleClass: 'pf-system-route-module', // class for this module + moduleTypeClass: 'pf-system-route-module', // class for this module // headline toolbar - systemModuleHeadlineIcon: 'pf-module-icon-button', // class for toolbar icons in the head - systemModuleHeadlineIconSearch: 'pf-module-icon-button-search', // class for "search" icon - systemModuleHeadlineIconSettings: 'pf-module-icon-button-settings', // class for "settings" icon - systemModuleHeadlineIconRefresh: 'pf-module-icon-button-refresh', // class for "refresh" icon + moduleHeadlineIconClass: 'pf-module-icon-button', // class for toolbar icons in the head + moduleHeadlineIconSearchClass: 'pf-module-icon-button-search', // class for "search" icon + moduleHeadlineIconSettingsClass: 'pf-module-icon-button-settings', // class for "settings" icon + moduleHeadlineIconRefreshClass: 'pf-module-icon-button-refresh', // class for "refresh" icon systemSecurityClassPrefix: 'pf-system-security-', // prefix class for system security level (color) @@ -35,7 +38,7 @@ define([ systemInfoRoutesTableClass: 'pf-system-route-table', // class for route tables mapSelectId: 'pf-route-dialog-map-select', // id for "map" select - dataTableActionCellClass: 'pf-table-action-cell' // class for "action" cells + dataTableActionCellClass: 'pf-table-action-cell' // class for "action" cells }; // cache for system routes @@ -48,7 +51,7 @@ define([ * @param context * @param routesData */ - let callbackAddRouteRow = function(context, routesData){ + let callbackAddRouteRow = (context, routesData) => { if(routesData.length > 0){ for(let i = 0; i < routesData.length; i++){ @@ -86,7 +89,7 @@ define([ * @param rowData * @returns {*} */ - let addRow = function(context, rowData){ + let addRow = (context, rowData) => { let dataTable = context.dataTable; let rowElement = null; let row = null; @@ -94,7 +97,7 @@ define([ // search for an existing row (e.g. on mass "table refresh" [all routes]) // get rowIndex where column 1 (equals to "systemToData.name") matches rowData.systemToData.name - let indexes = dataTable.rows().eq(0).filter( function (rowIdx) { + let indexes = dataTable.rows().eq(0).filter((rowIdx) => { return (dataTable.cell(rowIdx, 1 ).data().name === rowData.systemToData.name); }); @@ -129,8 +132,7 @@ define([ * @param context * @param callback */ - let getRouteData = function(requestData, context, callback){ - + let getRouteData = (requestData, context, callback) => { context.moduleElement.showLoadingAnimation(); $.ajax({ @@ -145,7 +147,6 @@ define([ // execute callback callback(this, routesData.routesData); }); - }; /** @@ -153,7 +154,7 @@ define([ * @param moduleElement * @param dataTable */ - let updateRoutesTable = function(moduleElement, dataTable){ + let updateRoutesTable = (moduleElement, dataTable) => { let context = { moduleElement: moduleElement, dataTable: dataTable @@ -172,7 +173,7 @@ define([ * @param {Object} rowData * @returns {Object} */ - let getRouteRequestDataFromRowData = function(rowData){ + let getRouteRequestDataFromRowData = (rowData) => { return { mapIds: (rowData.hasOwnProperty('mapIds')) ? rowData.mapIds : [], systemFromData: (rowData.hasOwnProperty('systemFromData')) ? rowData.systemFromData : {}, @@ -193,7 +194,7 @@ define([ * show route dialog. User can search for systems and jump-info for each system is added to a data table * @param dialogData */ - let showFindRouteDialog = function(dialogData){ + let showFindRouteDialog = (dialogData) => { let mapSelectOptions = []; let currentMapData = Util.getCurrentMapData(); @@ -678,50 +679,45 @@ define([ }; /** - * get the route finder moduleElement + * get module element * @returns {*} */ let getModule = function(){ - // create new module container - let moduleElement = $('
      ', { - class: [config.moduleClass, config.systemRouteModuleClass].join(' ') - }); - - // headline toolbar icons - let headlineToolbar = $('
      ', { - class: 'pull-right' - }).append( - $('', { - class: ['fa', 'fa-fw', 'fa-search', config.systemModuleHeadlineIcon, config.systemModuleHeadlineIconSearch].join(' '), - title: 'find route' - }).attr('data-html', 'true').attr('data-toggle', 'tooltip'), - $('', { - class: ['fa', 'fa-fw', 'fa-sliders', config.systemModuleHeadlineIcon, config.systemModuleHeadlineIconSettings].join(' '), - title: 'settings' - }).attr('data-html', 'true').attr('data-toggle', 'tooltip'), - $('', { - class: ['fa', 'fa-fw', 'fa-refresh', config.systemModuleHeadlineIcon, config.systemModuleHeadlineIconRefresh].join(' '), - title: 'refresh all' - }).attr('data-html', 'true').attr('data-toggle', 'tooltip') + let moduleElement = $('
      ').append( + $('
      ', { + class: config.moduleHeadClass + }).append( + $('
      ', { + class: config.moduleHandlerClass + }), + $('
      ', { + class: 'pull-right' + }).append( + $('', { + class: ['fa', 'fa-fw', 'fa-search', config.moduleHeadlineIconClass, config.moduleHeadlineIconSearchClass].join(' '), + title: 'find route' + }).attr('data-html', 'true').attr('data-toggle', 'tooltip'), + $('', { + class: ['fa', 'fa-fw', 'fa-sliders', config.moduleHeadlineIconClass, config.moduleHeadlineIconSettingsClass].join(' '), + title: 'settings' + }).attr('data-html', 'true').attr('data-toggle', 'tooltip'), + $('', { + class: ['fa', 'fa-fw', 'fa-refresh', config.moduleHeadlineIconClass, config.moduleHeadlineIconRefreshClass].join(' '), + title: 'refresh all' + }).attr('data-html', 'true').attr('data-toggle', 'tooltip') + ), + $('
      ', { + text: 'Routes' + }) + ) ); - moduleElement.append(headlineToolbar); - - // headline - let headline = $('
      ', { - class: 'pull-left', - text: 'Routes' - }); - - moduleElement.append(headline); - // crate new route table let table = $('
      ', { class: ['compact', 'stripe', 'order-column', 'row-border', config.systemInfoRoutesTableClass].join(' ') }); - - moduleElement.append( $(table) ); + moduleElement.append(table); // init empty table let routesTable = table.DataTable( { @@ -1006,12 +1002,12 @@ define([ let routesTable = routesTableElement.DataTable(); // init refresh routes -------------------------------------------------------------------- - moduleElement.find('.' + config.systemModuleHeadlineIconRefresh).on('click', function(e){ + moduleElement.find('.' + config.moduleHeadlineIconRefreshClass).on('click', function(e){ updateRoutesTable(moduleElement, routesTable); }); // init search routes dialog -------------------------------------------------------------- - moduleElement.find('.' + config.systemModuleHeadlineIconSearch).on('click', function(e){ + moduleElement.find('.' + config.moduleHeadlineIconSearchClass).on('click', function(e){ let maxRouteSearchLimit = this.Init.routeSearch.limit; if(routesTable.rows().count() >= maxRouteSearchLimit){ @@ -1032,7 +1028,7 @@ define([ })); // init settings dialog ------------------------------------------------------------------- - moduleElement.find('.' + config.systemModuleHeadlineIconSettings).on('click', function(e){ + moduleElement.find('.' + config.moduleHeadlineIconSettingsClass).on('click', function(e){ let dialogData = { mapId: mapId }; @@ -1061,49 +1057,10 @@ define([ }; - /** - * updates an dom element with the system route module - * @param mapId - * @param systemData - */ - $.fn.drawSystemRouteModule = function(mapId, systemData){ - - let parentElement = $(this); - - // show route module - let showModule = function(moduleElement){ - if(moduleElement){ - moduleElement.css({ opacity: 0 }); - parentElement.append(moduleElement); - - moduleElement.velocity('transition.slideDownIn', { - duration: Init.animationSpeed.mapModule, - delay: Init.animationSpeed.mapModule, - complete: function(){ - initModule(moduleElement, mapId, systemData); - } - }); - } - }; - - // check if module already exists - let moduleElement = parentElement.find('.' + config.systemRouteModuleClass); - - if(moduleElement.length > 0){ - moduleElement.velocity('transition.slideDownOut', { - duration: Init.animationSpeed.mapModule, - complete: function(tempElement){ - $(tempElement).remove(); - - moduleElement = getModule(); - showModule(moduleElement); - } - }); - }else{ - moduleElement = getModule(); - showModule(moduleElement); - } - + return { + config: config, + getModule: getModule, + initModule: initModule }; }); \ No newline at end of file diff --git a/js/app/ui/system_signature.js b/js/app/ui/system_signature.js index 4ce53bad..5d6e46c1 100644 --- a/js/app/ui/system_signature.js +++ b/js/app/ui/system_signature.js @@ -15,10 +15,15 @@ define([ let config = { // module info + modulePosition: 4, + moduleName: 'systemSignature', + moduleHeadClass: 'pf-module-head', // class for module header + moduleHandlerClass: 'pf-module-handler-drag', // class for "drag" handler + moduleClass: 'pf-module', // class for each module // system signature module - systemSigModuleClass: 'pf-sig-table-module', // module wrapper + moduleTypeClass: 'pf-sig-table-module', // module wrapper // tables tableToolsClass: 'pf-table-tools', // class for table toolbar @@ -31,6 +36,7 @@ define([ signatureScannedProgressBarClass: 'pf-system-progress-scanned', // class for signature progress bar // toolbar + sigTableLazyToggleButtonClass: 'pf-sig-table-lazy-button', // class for "lazy update" toggle button sigTableClearButtonClass: 'pf-sig-table-clear-button', // class for "clear" signatures button // signature table @@ -965,19 +971,19 @@ define([ if( clearButton.is(':hidden') ){ // show button - clearButton.velocity('transition.bounceIn', { - duration: 180 + clearButton.velocity('transition.expandIn', { + duration: 100 }); }else{ // highlight button clearButton.velocity('callout.pulse', { - duration: 240 + duration: 200 }); } }else{ // hide button - clearButton.velocity('transition.bounceOut', { - duration: 180 + clearButton.velocity('transition.expandOut', { + duration: 100 }); } }; @@ -1048,6 +1054,12 @@ define([ checkDeleteSignaturesButton(moduleElement); } }) + ).append( + $('', { + type: 'checkbox', + class: [config.sigTableLazyToggleButtonClass, 'btn-labeled'].join(' '), + value: 1, + }).attr('data-toggle', 'toggle') ).append( getLabledButton({ type: 'danger', @@ -1073,6 +1085,24 @@ define([ moduleElement.append(tableToolbar); + // "lazy update" toggle button -------------------------------------------------------------------------------- + let lazyToggleCheckbox = moduleElement.find('.' + config.sigTableLazyToggleButtonClass).bootstrapToggle({ + size: 'small' , + on: '  lazy delete', + off: '  lazy update', + onstyle: 'warning' , + offstyle: 'default' , + width: 110 + }); + + let lazyToggleButton = lazyToggleCheckbox.parent(); + lazyToggleButton.find('.toggle-on').attr('title', 'lazy \'update\' and \'delete\' old
      from clipboard |ctrl + v|'); + lazyToggleButton.find('.toggle-off').attr('title', 'lazy \'update\' signatures
      from clipboard |ctrl + v|'); + lazyToggleButton.initTooltips({ + container: 'body', + html: true + }); + // add toolbar action for table ------------------------------------------------------------------------------- let tableToolbarAction = $('
      ', { class: config.tableToolsActionClass @@ -1469,11 +1499,11 @@ define([ let getSignatureConnectionOptions = (mapId, systemData) => { let map = Map.getMapInstance( mapId ); let systemId = MapUtil.getSystemId(mapId, systemData.id); - let systemConnections = MapUtil.searchConnectionsBySystems(map, [systemId]); + let systemConnections = MapUtil.searchConnectionsBySystems(map, [systemId], 'wh'); let connectionOptions = []; for(let i = 0; i < systemConnections.length; i++){ - let connectionData = Map.getDataByConnection(systemConnections[i]); + let connectionData = MapUtil.getDataByConnection(systemConnections[i]); // connectionId is required (must be stored) if(connectionData.id){ @@ -1739,7 +1769,7 @@ define([ let deleteSignatures = function(tableApi, rows){ let deletedSignatures = 0; - let moduleElement = $('.' + config.systemSigModuleClass); + let moduleElement = $('.' + config.moduleTypeClass); let data = rows.data(); let rowElements = rows.nodes().to$(); let signatureCount = data.length; @@ -2312,12 +2342,35 @@ define([ checkDeleteSignaturesButton(e.data.moduleElement); }); + // destroy dataTables event ----------------------------------------------------------------------------------- + tablePrimaryElement.on('destroy.dt', function(){ + $(this).destroyTimestampCounter(); + }); + signatureTableApi.on('destroy.dt', function(){ + $(this).destroyTimestampCounter(); + }); + // event listener for global "paste" signatures into the page ------------------------------------------------- moduleElement.on('pf:updateSystemSignatureModuleByClipboard', function(e, clipboard){ - $(this).updateSignatureTableByClipboard(systemData, clipboard, {}); + // check "lazy update" toggle button + let signatureOptions = { + deleteOld: moduleElement.find('.' + config.sigTableLazyToggleButtonClass).is(':checked') ? 1 : 0 + }; + + $(this).updateSignatureTableByClipboard(systemData, clipboard, signatureOptions); }); }; + /** + * init callback + * @param moduleElement + * @param mapId + * @param connectionData + */ + let initModule = (moduleElement, mapId, systemData) => { + unlockSignatureTable(true); + }; + /** * get module element * @param parentElement @@ -2326,25 +2379,23 @@ define([ * @returns {*|jQuery|HTMLElement} */ let getModule = function(parentElement, mapId, systemData){ - // create new module container - let moduleElement = $('
      ', { - class: [config.moduleClass, config.systemSigModuleClass].join(' '), - css: {opacity: 0} - }); + let moduleElement = $('
      ').append( + $('
      ', { + class: config.moduleHeadClass + }).append( + $('
      ', { + class: config.moduleHandlerClass + }), + $('
      ', { + text: 'Signatures' + }) + ) + ); moduleElement.data('mapId', mapId); moduleElement.data('systemId', systemData.id); - // headline - let headline = $('
      ', { - text: 'Signatures' - }); - - moduleElement.append(headline); - - $(parentElement).append(moduleElement); - // init dataTables initSignatureDataTable(systemData); @@ -2385,76 +2436,31 @@ define([ }; /** - * main module load function - * @param mapId - * @param systemData + * before module reDraw callback */ - $.fn.drawSignatureTableModule = function(mapId, systemData){ - let parentElement = $(this); + let beforeReDraw = () => { + // disable update + lockSignatureTable(); + }; - // show module - let showModule = function(moduleElement){ - if(moduleElement){ - moduleElement.velocity('transition.slideDownIn', { - duration: Init.animationSpeed.mapModule, - delay: Init.animationSpeed.mapModule, - complete: function(){ - unlockSignatureTable(true); - } - }); - } - }; - - // some custom array functions - let initArrayFunctions = function(){ - /** - * sort array of objects by property name - * @param p - * @returns {Array.} - */ - Array.prototype.sortBy = function(p) { - return this.slice(0).sort(function(a,b) { - return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0; - }); - }; - }; - - // check if module already exists - let moduleElement = parentElement.find('.' + config.systemSigModuleClass); - - if(moduleElement.length > 0){ - // disable update - lockSignatureTable(); - - moduleElement.velocity('transition.slideDownOut', { - duration: Init.animationSpeed.mapModule, - complete: function(tempElement){ - tempElement = $(tempElement); - // Destroying the data tables throws - // save remove of all dataTables - let mapId = tempElement.data('mapId'); - let systemId = tempElement.data('systemId'); - deleteDataTableInstance(mapId, systemId, 'primary'); - deleteDataTableInstance(mapId, systemId, 'secondary'); - - tempElement.remove(); - - moduleElement = getModule(parentElement, mapId, systemData); - // make modules appear "nice" - moduleElement.delay(150); - showModule(moduleElement); - } - }); - }else{ - // init array prototype functions - initArrayFunctions(); - - moduleElement = getModule(parentElement, mapId, systemData); - showModule(moduleElement); - } + /** + * before module destroy callback + */ + let beforeDestroy = (moduleElement) => { + // Destroying the data tables throws + // -> safety remove all dataTables + let mapId = moduleElement.data('mapId'); + let systemId = moduleElement.data('systemId'); + deleteDataTableInstance(mapId, systemId, 'primary'); + deleteDataTableInstance(mapId, systemId, 'secondary'); }; return { + config: config, + getModule: getModule, + initModule: initModule, + beforeReDraw: beforeReDraw, + beforeDestroy: beforeDestroy, getAllSignatureNamesBySystem: getAllSignatureNamesBySystem }; diff --git a/js/app/util.js b/js/app/util.js index 350df026..e5168483 100644 --- a/js/app/util.js +++ b/js/app/util.js @@ -546,6 +546,8 @@ define([ } } + + return element; }; /** @@ -618,6 +620,17 @@ define([ popoverElement = button.data('bs.popover').tip(); popoverElement.velocity('transition.' + easeEffect, velocityOptions); popoverElement.initTooltips(); + + // set click events. This is required to pass data to "beforeunload" events + // -> there is no way to identify the target within that event + popoverElement.on('click', '.btn', function(){ + // character switch detected + $('body').data('characterSwitch', true); + // ... and remove "characterSwitch" data again! after "unload" + setTimeout(function() { + $('body').removeData('characterSwitch'); + }, 500); + }); }else{ popoverElement = button.data('bs.popover').tip(); if(popoverElement.is(':visible')){ @@ -668,39 +681,6 @@ define([ }); }; - /** - * add a wormhole tooltip with wh specific data to elements - * @param tooltipData - * @returns {*} - */ - $.fn.addWormholeInfoTooltip = function(tooltipData){ - return this.each(function() { - let element = $(this); - - requirejs(['text!templates/tooltip/wormhole_info.html', 'mustache'], function (template, Mustache) { - let content = Mustache.render(template, tooltipData); - - element.popover({ - placement: 'top', - html: true, - trigger: 'hover', - content: '', - container: 'body', - title: tooltipData.name + - '' + tooltipData.security + '', - delay: { - show: 250, - hide: 0 - } - }); - - // set new popover content - let popover = element.data('bs.popover'); - popover.options.content = content; - }); - }); - }; - /** * display a custom message (info/warning/error) to a container element * check: $.fn.showFormMessage() for an other way of showing messages @@ -862,6 +842,17 @@ define([ Array.prototype.diff = function(a) { return this.filter(function(i) {return a.indexOf(i) < 0;}); }; + + /** + * sort array of objects by property name + * @param p + * @returns {Array.} + */ + Array.prototype.sortBy = function(p) { + return this.slice(0).sort((a,b) => { + return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0; + }); + }; }; /** @@ -883,7 +874,7 @@ define([ } return flatten; - } ; + }; /** * set default configuration for "Bootbox" dialogs @@ -1081,6 +1072,28 @@ define([ return Init.currentUserData; }; + /** + * get either active characterID or characterId from initial page load + * @returns {number} + */ + let getCurrentCharacterId = () => { + let userData = getCurrentUserData(); + let currentCharacterId = 0; + if( + userData && + userData.character + ){ + currentCharacterId = parseInt( userData.character.id ); + } + + if(!currentCharacterId){ + // no active character... -> get default characterId from initial page load + currentCharacterId = parseInt(document.body.getAttribute('data-character-id')); + } + + return currentCharacterId; + }; + /** * get a unique ID for each tab * -> store ID in session storage @@ -1104,21 +1117,9 @@ define([ // Add custom application headers on "same origin" requests only! // -> Otherwise a "preflight" request is made, which will "probably" fail if(settings.crossDomain === false){ - // Add browser tab information - xhr.setRequestHeader('Pf-Tab-Id', getBrowserTabId()) ; - // add current character data to ANY XHR request (HTTP HEADER) // -> This helps to identify multiple characters on multiple browser tabs - let userData = getCurrentUserData(); - let currentCharacterId = 0; - if( - userData && - userData.character - ){ - currentCharacterId = parseInt( userData.character.id ); - } - - xhr.setRequestHeader('Pf-Character', currentCharacterId); + xhr.setRequestHeader('Pf-Character', getCurrentCharacterId()); } } }); @@ -1278,7 +1279,6 @@ define([ * @returns {*|HTMLElement} */ let getMapModule = function(){ - let mapModule = $('#' + config.mapModuleId); if(mapModule.length === 0){ mapModule = $('
      ', { @@ -1470,13 +1470,11 @@ define([ * @param sec * @returns {string} */ - let getSecurityClassForSystem = function(sec){ + let getSecurityClassForSystem = (sec) => { let secClass = ''; - if( Init.classes.systemSecurity.hasOwnProperty(sec) ){ secClass = Init.classes.systemSecurity[sec]['class']; } - return secClass; }; @@ -1994,7 +1992,7 @@ define([ * set currentSystemData as "global" variable * @param systemData */ - let setCurrentSystemData = function(systemData){ + let setCurrentSystemData = (systemData) => { Init.currentSystemData = systemData; }; @@ -2002,7 +2000,7 @@ define([ * get currentSystemData from "global" variables * @returns {*} */ - let getCurrentSystemData = function(){ + let getCurrentSystemData = () => { return Init.currentSystemData; }; @@ -2060,7 +2058,7 @@ define([ * @param price * @returns {string} */ - let formatPrice = function(price){ + let formatPrice = (price) => { price = Number( price ).toFixed(2); let parts = price.toString().split('.'); @@ -2069,6 +2067,15 @@ define([ return price + ' ISK'; }; + /** + * format mass value + * @param value + * @returns {string} + */ + let formatMassValue = (value) => { + return (parseInt(value) / 1000).toLocaleString() + ' t'; + }; + /** * get localForage instance (singleton) for offline client site storage * @returns {localforage} @@ -2098,7 +2105,7 @@ define([ * @param date * @returns {Date} */ - let createDateAsUTC = function(date){ + let createDateAsUTC = (date) => { return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds())); }; @@ -2107,7 +2114,7 @@ define([ * @param date * @returns {Date} */ - let convertDateToUTC = function(date){ + let convertDateToUTC = (date) => { return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()); }; @@ -2117,7 +2124,7 @@ define([ * @param showSeconds * @returns {string} */ - let convertDateToString = function(date, showSeconds){ + let convertDateToString = (date, showSeconds) => { let dateString = ('0'+ (date.getMonth() + 1 )).slice(-2) + '/' + ('0'+date.getDate()).slice(-2) + '/' + date.getFullYear(); let timeString = ('0' + date.getHours()).slice(-2) + ':' + ('0'+date.getMinutes()).slice(-2); timeString += (showSeconds) ? ':' + ('0'+date.getSeconds()).slice(-2) : ''; @@ -2142,7 +2149,7 @@ define([ * -> www.pathfinder.com/pathfinder/ -> /pathfinder * @returns {string|string} */ - let getDocumentPath = function(){ + let getDocumentPath = () => { let pathname = window.location.pathname; // replace file endings let r = /[^\/]*$/; @@ -2155,7 +2162,7 @@ define([ * @param url * @param params */ - let redirect = function(url, params){ + let redirect = (url, params) => { let currentUrl = document.URL; if(url !== currentUrl){ @@ -2173,7 +2180,7 @@ define([ * send logout request * @param params */ - let logout = function(params){ + let logout = (params) => { let data = {}; if( params && @@ -2197,6 +2204,56 @@ define([ }); }; + /** + * set a cookie + * @param name + * @param value + * @param expire + * @param format + */ + let setCookie = (name, value, expire, format) => { + let d = new Date(); + let time = d.getTime(); + let timeExpire = time * -1; + + if(expire > 0){ + switch(format){ + case 'd': // days + timeExpire = expire * 24 * 60 * 60 * 1000; break; + case 's': // seconds + timeExpire = expire * 1000; break; + + } + } + + d.setTime(time + timeExpire); + let expires = 'expires=' + d.toUTCString(); + let path = 'path=' + getDocumentPath(); + document.cookie = name + '=' + value + '; ' + expires + '; ' + path; + }; + + /** + * get cookie value by name + * @param cname + * @returns {string} + */ + let getCookie = (cname) => { + let name = cname + '='; + let ca = document.cookie.split(';'); + + for(let i = 0; i ').html(this.options.on).addClass(this._onstyle+" "+b),d=a('
      ","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};ze.optgroup=ze.option,ze.tbody=ze.tfoot=ze.colgroup=ze.caption=ze.thead,ze.th=ze.td;var Xe=/<|&#?\w+;/;!function(){var e=ee.createDocumentFragment().appendChild(ee.createElement("div")),t=ee.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),de.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",de.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var Ye=ee.documentElement,Ke=/^key/,Ge=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Qe=/^([^.]*)(?:\.(.+)|)/;pe.event={global:{},add:function(e,t,n,r,o){var a,i,s,l,c,u,d,f,p,h,m,g=Re.get(e);if(g)for(n.handler&&(a=n,n=a.handler,o=a.selector),o&&pe.find.matchesSelector(Ye,o),n.guid||(n.guid=pe.guid++),(l=g.events)||(l=g.events={}),(i=g.handle)||(i=g.handle=function(t){return void 0!==pe&&pe.event.triggered!==t.type?pe.event.dispatch.apply(e,arguments):void 0}),c=(t=(t||"").match(ke)||[""]).length;c--;)s=Qe.exec(t[c])||[],p=m=s[1],h=(s[2]||"").split(".").sort(),p&&(d=pe.event.special[p]||{},p=(o?d.delegateType:d.bindType)||p,d=pe.event.special[p]||{},u=pe.extend({type:p,origType:m,data:r,handler:n,guid:n.guid,selector:o,needsContext:o&&pe.expr.match.needsContext.test(o),namespace:h.join(".")},a),(f=l[p])||(f=l[p]=[],f.delegateCount=0,d.setup&&!1!==d.setup.call(e,r,h,i)||e.addEventListener&&e.addEventListener(p,i)),d.add&&(d.add.call(e,u),u.handler.guid||(u.handler.guid=n.guid)),o?f.splice(f.delegateCount++,0,u):f.push(u),pe.event.global[p]=!0)},remove:function(e,t,n,r,o){var a,i,s,l,c,u,d,f,p,h,m,g=Re.hasData(e)&&Re.get(e);if(g&&(l=g.events)){for(c=(t=(t||"").match(ke)||[""]).length;c--;)if(s=Qe.exec(t[c])||[],p=m=s[1],h=(s[2]||"").split(".").sort(),p){for(d=pe.event.special[p]||{},f=l[p=(r?d.delegateType:d.bindType)||p]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=a=f.length;a--;)u=f[a],!o&&m!==u.origType||n&&n.guid!==u.guid||s&&!s.test(u.namespace)||r&&r!==u.selector&&("**"!==r||!u.selector)||(f.splice(a,1),u.selector&&f.delegateCount--,d.remove&&d.remove.call(e,u));i&&!f.length&&(d.teardown&&!1!==d.teardown.call(e,h,g.handle)||pe.removeEvent(e,p,g.handle),delete l[p])}else for(p in l)pe.event.remove(e,p+t[c],n,r,!0);pe.isEmptyObject(l)&&Re.remove(e,"handle events")}},dispatch:function(e){var t,n,r,o,a,i,s=pe.event.fix(e),l=new Array(arguments.length),c=(Re.get(this,"events")||{})[s.type]||[],u=pe.event.special[s.type]||{};for(l[0]=s,t=1;t=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==e.type||!0!==c.disabled)){for(a=[],i={},n=0;n-1:pe.find(o,this,null,[c]).length),i[o]&&a.push(r);a.length&&s.push({elem:c,handlers:a})}return c=this,l\x20\t\r\n\f]*)[^>]*)\/>/gi,Je=/\s*$/g;pe.extend({htmlPrefilter:function(e){return e.replace(Ze,"<$1>")},clone:function(e,t,n){var r,o,a,i,s=e.cloneNode(!0),l=pe.contains(e.ownerDocument,e);if(!(de.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||pe.isXMLDoc(e)))for(i=v(s),a=v(e),r=0,o=a.length;r0&&y(i,!l&&v(e,"script")),s},cleanData:function(e){for(var t,n,r,o=pe.event.special,a=0;void 0!==(n=e[a]);a++)if(Ee(n)){if(t=n[Re.expando]){if(t.events)for(r in t.events)o[r]?pe.event.remove(n,r):pe.removeEvent(n,r,t.handle);n[Re.expando]=void 0}n[Pe.expando]&&(n[Pe.expando]=void 0)}}}),pe.fn.extend({detach:function(e){return O(this,e,!0)},remove:function(e){return O(this,e)},text:function(e){return Fe(this,function(e){return void 0===e?pe.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return A(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||T(this,e).appendChild(e)})},prepend:function(){return A(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=T(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return A(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return A(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(pe.cleanData(v(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return pe.clone(this,e,t)})},html:function(e){return Fe(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Je.test(e)&&!ze[(qe.exec(e)||["",""])[1].toLowerCase()]){e=pe.htmlPrefilter(e);try{for(;n1)}}),pe.Tween=j,j.prototype={constructor:j,init:function(e,t,n,r,o,a){this.elem=e,this.prop=n,this.easing=o||pe.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=a||(pe.cssNumber[n]?"":"px")},cur:function(){var e=j.propHooks[this.prop];return e&&e.get?e.get(this):j.propHooks._default.get(this)},run:function(e){var t,n=j.propHooks[this.prop];return this.options.duration?this.pos=t=pe.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):j.propHooks._default.set(this),this}},j.prototype.init.prototype=j.prototype,j.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=pe.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){pe.fx.step[e.prop]?pe.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[pe.cssProps[e.prop]]&&!pe.cssHooks[e.prop]?e.elem[e.prop]=e.now:pe.style(e.elem,e.prop,e.now+e.unit)}}},j.propHooks.scrollTop=j.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},pe.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},pe.fx=j.prototype.init,pe.fx.step={};var dt,ft,pt=/^(?:toggle|show|hide)$/,ht=/queueHooks$/;pe.Animation=pe.extend(W,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return h(n.elem,e,Be.exec(t),n),n}]},tweener:function(e,t){pe.isFunction(e)?(t=e,e=["*"]):e=e.match(ke);for(var n,r=0,o=e.length;r1)},removeAttr:function(e){return this.each(function(){pe.removeAttr(this,e)})}}),pe.extend({attr:function(e,t,n){var r,o,a=e.nodeType;if(3!==a&&8!==a&&2!==a)return void 0===e.getAttribute?pe.prop(e,t,n):(1===a&&pe.isXMLDoc(e)||(o=pe.attrHooks[t.toLowerCase()]||(pe.expr.match.bool.test(t)?mt:void 0)),void 0!==n?null===n?void pe.removeAttr(e,t):o&&"set"in o&&void 0!==(r=o.set(e,n,t))?r:(e.setAttribute(t,n+""),n):o&&"get"in o&&null!==(r=o.get(e,t))?r:null==(r=pe.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!de.radioValue&&"radio"===t&&pe.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,o=t&&t.match(ke);if(o&&1===e.nodeType)for(;n=o[r++];)e.removeAttribute(n)}}),mt={set:function(e,t,n){return!1===t?pe.removeAttr(e,n):e.setAttribute(n,n),n}},pe.each(pe.expr.match.bool.source.match(/\w+/g),function(e,t){var n=gt[t]||pe.find.attr;gt[t]=function(e,t,r){var o,a,i=t.toLowerCase();return r||(a=gt[i],gt[i]=o,o=null!=n(e,t,r)?i:null,gt[i]=a),o}});var vt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;pe.fn.extend({prop:function(e,t){return Fe(this,pe.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[pe.propFix[e]||e]})}}),pe.extend({prop:function(e,t,n){var r,o,a=e.nodeType;if(3!==a&&8!==a&&2!==a)return 1===a&&pe.isXMLDoc(e)||(t=pe.propFix[t]||t,o=pe.propHooks[t]),void 0!==n?o&&"set"in o&&void 0!==(r=o.set(e,n,t))?r:e[t]=n:o&&"get"in o&&null!==(r=o.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=pe.find.attr(e,"tabindex");return t?parseInt(t,10):vt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),de.optSelected||(pe.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),pe.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){pe.propFix[this.toLowerCase()]=this}),pe.fn.extend({addClass:function(e){var t,n,r,o,a,i,s,l=0;if(pe.isFunction(e))return this.each(function(t){pe(this).addClass(e.call(this,t,V(this)))});if("string"==typeof e&&e)for(t=e.match(ke)||[];n=this[l++];)if(o=V(n),r=1===n.nodeType&&" "+q(o)+" "){for(i=0;a=t[i++];)r.indexOf(" "+a+" ")<0&&(r+=a+" ");o!==(s=q(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,o,a,i,s,l=0;if(pe.isFunction(e))return this.each(function(t){pe(this).removeClass(e.call(this,t,V(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof e&&e)for(t=e.match(ke)||[];n=this[l++];)if(o=V(n),r=1===n.nodeType&&" "+q(o)+" "){for(i=0;a=t[i++];)for(;r.indexOf(" "+a+" ")>-1;)r=r.replace(" "+a+" "," ");o!==(s=q(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):pe.isFunction(e)?this.each(function(n){pe(this).toggleClass(e.call(this,n,V(this),t),t)}):this.each(function(){var t,r,o,a;if("string"===n)for(r=0,o=pe(this),a=e.match(ke)||[];t=a[r++];)o.hasClass(t)?o.removeClass(t):o.addClass(t);else void 0!==e&&"boolean"!==n||((t=V(this))&&Re.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":Re.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+q(V(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;pe.fn.extend({val:function(e){var t,n,r,o=this[0];return arguments.length?(r=pe.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(null==(o=r?e.call(this,n,pe(this).val()):e)?o="":"number"==typeof o?o+="":pe.isArray(o)&&(o=pe.map(o,function(e){return null==e?"":e+""})),(t=pe.valHooks[this.type]||pe.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,o,"value")||(this.value=o))})):o?(t=pe.valHooks[o.type]||pe.valHooks[o.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(o,"value"))?n:"string"==typeof(n=o.value)?n.replace(bt,""):null==n?"":n:void 0}}),pe.extend({valHooks:{option:{get:function(e){var t=pe.find.attr(e,"value");return null!=t?t:q(pe.text(e))}},select:{get:function(e){var t,n,r,o=e.options,a=e.selectedIndex,i="select-one"===e.type,s=i?null:[],l=i?a+1:o.length;for(r=a<0?l:i?a:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),a}}}}),pe.each(["radio","checkbox"],function(){pe.valHooks[this]={set:function(e,t){if(pe.isArray(t))return e.checked=pe.inArray(pe(e).val(),t)>-1}},de.checkOn||(pe.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var xt=/^(?:focusinfocus|focusoutblur)$/;pe.extend(pe.event,{trigger:function(t,n,r,o){var a,i,s,l,c,u,d,f=[r||ee],p=le.call(t,"type")?t.type:t,h=le.call(t,"namespace")?t.namespace.split("."):[];if(i=s=r=r||ee,3!==r.nodeType&&8!==r.nodeType&&!xt.test(p+pe.event.triggered)&&(p.indexOf(".")>-1&&(h=p.split("."),p=h.shift(),h.sort()),c=p.indexOf(":")<0&&"on"+p,t=t[pe.expando]?t:new pe.Event(p,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=h.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:pe.makeArray(n,[t]),d=pe.event.special[p]||{},o||!d.trigger||!1!==d.trigger.apply(r,n))){if(!o&&!d.noBubble&&!pe.isWindow(r)){for(l=d.delegateType||p,xt.test(l+p)||(i=i.parentNode);i;i=i.parentNode)f.push(i),s=i;s===(r.ownerDocument||ee)&&f.push(s.defaultView||s.parentWindow||e)}for(a=0;(i=f[a++])&&!t.isPropagationStopped();)t.type=a>1?l:d.bindType||p,(u=(Re.get(i,"events")||{})[t.type]&&Re.get(i,"handle"))&&u.apply(i,n),(u=c&&i[c])&&u.apply&&Ee(i)&&(t.result=u.apply(i,n),!1===t.result&&t.preventDefault());return t.type=p,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(f.pop(),n)||!Ee(r)||c&&pe.isFunction(r[p])&&!pe.isWindow(r)&&((s=r[c])&&(r[c]=null),pe.event.triggered=p,r[p](),pe.event.triggered=void 0,s&&(r[c]=s)),t.result}},simulate:function(e,t,n){var r=pe.extend(new pe.Event,n,{type:e,isSimulated:!0});pe.event.trigger(r,null,t)}}),pe.fn.extend({trigger:function(e,t){return this.each(function(){pe.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return pe.event.trigger(e,t,n,!0)}}),pe.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){pe.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),pe.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),de.focusin="onfocusin"in e,de.focusin||pe.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){pe.event.simulate(t,e.target,pe.event.fix(e))};pe.event.special[t]={setup:function(){var r=this.ownerDocument||this,o=Re.access(r,t);o||r.addEventListener(e,n,!0),Re.access(r,t,(o||0)+1)},teardown:function(){var r=this.ownerDocument||this,o=Re.access(r,t)-1;o?Re.access(r,t,o):(r.removeEventListener(e,n,!0),Re.remove(r,t))}}});var wt=e.location,Ct=pe.now(),St=/\?/;pe.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||pe.error("Invalid XML: "+t),n};var Tt=/\[\]$/,Dt=/\r?\n/g,_t=/^(?:submit|button|image|reset|file)$/i,It=/^(?:input|select|textarea|keygen)/i;pe.param=function(e,t){var n,r=[],o=function(e,t){var n=pe.isFunction(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(pe.isArray(e)||e.jquery&&!pe.isPlainObject(e))pe.each(e,function(){o(this.name,this.value)});else for(n in e)z(n,e[n],t,o);return r.join("&")},pe.fn.extend({serialize:function(){return pe.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=pe.prop(this,"elements");return e?pe.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!pe(this).is(":disabled")&&It.test(this.nodeName)&&!_t.test(e)&&(this.checked||!We.test(e))}).map(function(e,t){var n=pe(this).val();return null==n?null:pe.isArray(n)?pe.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var kt=/%20/g,At=/#.*$/,Ot=/([?&])_=[^&]*/,Ft=/^(.*?):[ \t]*([^\r\n]*)$/gm,Et=/^(?:GET|HEAD)$/,Rt=/^\/\//,Pt={},Nt={},Lt="*/".concat("*"),jt=ee.createElement("a");jt.href=wt.href,pe.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:wt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(wt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Lt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":pe.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?K(K(e,pe.ajaxSettings),t):K(pe.ajaxSettings,e)},ajaxPrefilter:X(Pt),ajaxTransport:X(Nt),ajax:function(t,n){function r(t,n,r,s){var c,f,p,x,w,C=n;u||(u=!0,l&&e.clearTimeout(l),o=void 0,i=s||"",S.readyState=t>0?4:0,c=t>=200&&t<300||304===t,r&&(x=G(h,S,r)),x=Q(h,x,S,c),c?(h.ifModified&&((w=S.getResponseHeader("Last-Modified"))&&(pe.lastModified[a]=w),(w=S.getResponseHeader("etag"))&&(pe.etag[a]=w)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=x.state,f=x.data,p=x.error,c=!p)):(p=C,!t&&C||(C="error",t<0&&(t=0))),S.status=t,S.statusText=(n||C)+"",c?v.resolveWith(m,[f,C,S]):v.rejectWith(m,[S,C,p]),S.statusCode(b),b=void 0,d&&g.trigger(c?"ajaxSuccess":"ajaxError",[S,h,c?f:p]),y.fireWith(m,[S,C]),d&&(g.trigger("ajaxComplete",[S,h]),--pe.active||pe.event.trigger("ajaxStop")))}"object"==typeof t&&(n=t,t=void 0),n=n||{};var o,a,i,s,l,c,u,d,f,p,h=pe.ajaxSetup({},n),m=h.context||h,g=h.context&&(m.nodeType||m.jquery)?pe(m):pe.event,v=pe.Deferred(),y=pe.Callbacks("once memory"),b=h.statusCode||{},x={},w={},C="canceled",S={readyState:0,getResponseHeader:function(e){var t;if(u){if(!s)for(s={};t=Ft.exec(i);)s[t[1].toLowerCase()]=t[2];t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return u?i:null},setRequestHeader:function(e,t){return null==u&&(e=w[e.toLowerCase()]=w[e.toLowerCase()]||e,x[e]=t),this},overrideMimeType:function(e){return null==u&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(u)S.always(e[S.status]);else for(t in e)b[t]=[b[t],e[t]];return this},abort:function(e){var t=e||C;return o&&o.abort(t),r(0,t),this}};if(v.promise(S),h.url=((t||h.url||wt.href)+"").replace(Rt,wt.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(ke)||[""],null==h.crossDomain){c=ee.createElement("a");try{c.href=h.url,c.href=c.href,h.crossDomain=jt.protocol+"//"+jt.host!=c.protocol+"//"+c.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=pe.param(h.data,h.traditional)),Y(Pt,h,n,S),u)return S;(d=pe.event&&h.global)&&0==pe.active++&&pe.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Et.test(h.type),a=h.url.replace(At,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(kt,"+")):(p=h.url.slice(a.length),h.data&&(a+=(St.test(a)?"&":"?")+h.data,delete h.data),!1===h.cache&&(a=a.replace(Ot,"$1"),p=(St.test(a)?"&":"?")+"_="+Ct+++p),h.url=a+p),h.ifModified&&(pe.lastModified[a]&&S.setRequestHeader("If-Modified-Since",pe.lastModified[a]),pe.etag[a]&&S.setRequestHeader("If-None-Match",pe.etag[a])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&S.setRequestHeader("Content-Type",h.contentType),S.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+Lt+"; q=0.01":""):h.accepts["*"]);for(f in h.headers)S.setRequestHeader(f,h.headers[f]);if(h.beforeSend&&(!1===h.beforeSend.call(m,S,h)||u))return S.abort();if(C="abort",y.add(h.complete),S.done(h.success),S.fail(h.error),o=Y(Nt,h,n,S)){if(S.readyState=1,d&&g.trigger("ajaxSend",[S,h]),u)return S;h.async&&h.timeout>0&&(l=e.setTimeout(function(){S.abort("timeout")},h.timeout));try{u=!1,o.send(x,r)}catch(e){if(u)throw e;r(-1,e)}}else r(-1,"No Transport");return S},getJSON:function(e,t,n){return pe.get(e,t,n,"json")},getScript:function(e,t){return pe.get(e,void 0,t,"script")}}),pe.each(["get","post"],function(e,t){pe[t]=function(e,n,r,o){return pe.isFunction(n)&&(o=o||r,r=n,n=void 0),pe.ajax(pe.extend({url:e,type:t,dataType:o,data:n,success:r},pe.isPlainObject(e)&&e))}}),pe._evalUrl=function(e){return pe.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},pe.fn.extend({wrapAll:function(e){var t;return this[0]&&(pe.isFunction(e)&&(e=e.call(this[0])),t=pe(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return pe.isFunction(e)?this.each(function(t){pe(this).wrapInner(e.call(this,t))}):this.each(function(){var t=pe(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=pe.isFunction(e);return this.each(function(n){pe(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){pe(this).replaceWith(this.childNodes)}),this}}),pe.expr.pseudos.hidden=function(e){return!pe.expr.pseudos.visible(e)},pe.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},pe.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Bt={0:200,1223:204},Mt=pe.ajaxSettings.xhr();de.cors=!!Mt&&"withCredentials"in Mt,de.ajax=Mt=!!Mt,pe.ajaxTransport(function(t){var n,r;if(de.cors||Mt&&!t.crossDomain)return{send:function(o,a){var i,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(i in t.xhrFields)s[i]=t.xhrFields[i];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||o["X-Requested-With"]||(o["X-Requested-With"]="XMLHttpRequest");for(i in o)s.setRequestHeader(i,o[i]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?a(0,"error"):a(s.status,s.statusText):a(Bt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),pe.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),pe.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return pe.globalEval(e),e}}}),pe.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),pe.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(r,o){t=pe("