diff --git a/vipra-ui/app/html/articles/show.html b/vipra-ui/app/html/articles/show.html index 46003bd8b0f80cf99520cbec1bd37701c2cc9b01..e5f4708a6a06468058c3116f9cf00228422ff6ec 100644 --- a/vipra-ui/app/html/articles/show.html +++ b/vipra-ui/app/html/articles/show.html @@ -1,4 +1,6 @@ -<h1 ng-bind="::article.title"></h1> +<div class="page-header"> + <h1 ng-bind="::article.title"></h1> +</div> <h3>Info <hide-link target="#info"/></h3> @@ -41,15 +43,28 @@ </div> </div> -<h3>Topics <hide-link target="#topics"/></h3> +<h3>Topics <span ng-bind-template="({{article.topics.length}})"></span> <hide-link target="#topics"/></h3> <div class="row" id="topics"> - <div class="col-md-4 topic-links text-right"> - <topic-link topic="topic.topic" ng-repeat="topic in article.topics"> - <span class="label label-default" ng-bind-template="{{topic.share}}%"></span> - </topic-link> + <div class="col-md-5 topic-links"> + <table class="table table-morecondensed"> + <tr> + <th sort-by="topic.topic.name" sort-type="topicSort" sort-reverse="topicSortRev"> + Name + </th> + <th sort-by="topic.share" sort-type="topicSort" sort-reverse="topicSortRev"> + Share + </th> + </tr> + <tr ng-repeat="topic in article.topics | orderBy:topicSort:topicSortRev"> + <td> + <topic-link topic="topic.topic"/> + </td> + <td class="text-right" ng-bind-template="{{topic.share}}%"></td> + </tr> + </table> </div> - <div class="col-md-offset-1 col-md-6"> + <div class="col-md-7"> <div class="pie-chart" id="topic-share" highcharts="topicShare"></div> </div> </div> diff --git a/vipra-ui/app/html/directives/dropdown.html b/vipra-ui/app/html/directives/dropdown.html new file mode 100644 index 0000000000000000000000000000000000000000..1da0a98057040bb994b498560bc7b3b177e9f2e2 --- /dev/null +++ b/vipra-ui/app/html/directives/dropdown.html @@ -0,0 +1,7 @@ +<div class="dropdown"> + <button class="btn btn-default dropdown-toggle" type="button" ng-attr-id="{{dropdownId}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> + <span ng-bind="label"></span> + <span class="caret"></span> + </button> + <ul ng-attr-class="{{'dropdown-menu ' + align}}" ng-attr-aria-labelledby="{{dropdownId}}" ng-transclude></ul> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/network.html b/vipra-ui/app/html/network.html index 4346bb6018cdf12a46c5ced38da41badb31d937f..5aa49fbe3e115f764e263a51446bc3ee9ec0e822 100644 --- a/vipra-ui/app/html/network.html +++ b/vipra-ui/app/html/network.html @@ -1,7 +1,14 @@ -<div class="fullsize navpadding" vis-network vis-type="type" vis-colors="colors" vis-shown="shown" ng-model="model"> +<div class="fullsize navpadding"> <div class="graph-legend overlay"> - <span style="color:{{colors.articles}}"><input type="checkbox" ng-model="shown.articles"> Articles</span><br> - <span style="color:{{colors.topics}}"><input type="checkbox" ng-model="shown.topics"> Topics</span><br> - <span style="color:{{colors.words}}"><input type="checkbox" ng-model="shown.words"> Words</span><br> + <label style="color:{{colors.articles}}"> + <input type="checkbox" ng-model="shown.articles" store-value="showArticles"> Articles + </label> + <label style="color:{{colors.topics}}"> + <input type="checkbox" ng-model="shown.topics" store-value="showTopics"> Topics + </label> + <label style="color:{{colors.words}}"> + <input type="checkbox" ng-model="shown.words" store-value="showWords"> Words + </label> </div> + <div class="fullsize navpadding" id="visgraph"></div> </div> \ No newline at end of file diff --git a/vipra-ui/app/html/topics/show.html b/vipra-ui/app/html/topics/show.html index 3f11638f931fdedfb5caa7f57bd70abf7297504c..753ba0cb6fb51391e2ce0ab4f73a108cddab9836 100644 --- a/vipra-ui/app/html/topics/show.html +++ b/vipra-ui/app/html/topics/show.html @@ -1,45 +1,78 @@ -<h1 ng-bind="::topic.name"></h1> +<div class="page-header"> + <h1> + <div ng-bind="topic.name" ng-hide="isRename"></div> + <div class="input-group input-group-lg" ng-show="isRename"> + <input type="text" class="form-control" ng-model="topic.name" id="topicName" ng-keyup="keyup($event)"> + <div class="input-group-btn"> + <button class="btn btn-success" ng-click="endRename(true)"> + <span class="glyphicon glyphicon-ok"></span> + </button> + <button class="btn btn-danger" ng-click="endRename(false)"> + <span class="glyphicon glyphicon-remove"></span> + </button> + </div> + </div> + </h1> + + <bs-dropdown label="Actions"> + <li><a ng-click="startRename()">Rename</a></li> + </bs-dropdown> +</div> -<table class="table table-bordered table-condensed"> - <tbody> - <tr> - <th>ID</th> - <td ng-bind="::topic.id"></td> - </tr> - <tr> - <th>Index</th> - <td ng-bind="::topic.index"></td> - </tr> - <tr> - <th>Created</th> - <td ng-bind="::topic.created"></td> - </tr> - <tr> - <th>Last modified</th> - <td ng-bind="::topic.modified"></td> - </tr> - <tr> - <th>Links</th> - <td> - <a ui-sref="network({type:'topics', id:topic.id})">Network graph</a> - </td> - </tr> - </tbody> -</table> +<h3>Info <hide-link target="#info"/></h3> + +<div class="row" id="info"> + <div class="col-md-12"> + <table class="table table-bordered table-condensed"> + <tbody> + <tr> + <th>ID</th> + <td ng-bind="::topic.id"></td> + </tr> + <tr> + <th>Index</th> + <td ng-bind="::topic.index"></td> + </tr> + <tr> + <th>Created</th> + <td ng-bind="::topic.created"></td> + </tr> + <tr> + <th>Last modified</th> + <td ng-bind="::topic.modified"></td> + </tr> + <tr> + <th>Links</th> + <td> + <a ui-sref="network({type:'topics', id:topic.id})">Network graph</a> + </td> + </tr> + </tbody> + </table> + </div> +</div> <h3>Words <hide-link target="#words"/></h3> -<table class="table table-bordered table-condensed" id="words"> - <thead> - <tr> - <th>Word</th> - <th>Likeliness</th> - </tr> - </thead> - <tbody> - <tr ng-repeat="word in ::topic.words"> - <td><a ui-sref="words.show({id:word.id})" ng-bind="word.id"></a></td> - <td ng-bind="word.likeliness"></td> - </tr> - </tbody> -</table> \ No newline at end of file +<div class="row" id="words"> + <div class="col-md-12"> + <table class="table table-bordered table-condensed"> + <thead> + <tr> + <th sort-by="id" sort-type="wordSort" sort-reverse="wordSortRev"> + Word + </th> + <th sort-by="likeliness" sort-type="wordSort" sort-reverse="wordSortRev"> + Likeliness + </th> + </tr> + </thead> + <tbody> + <tr ng-repeat="word in topic.words | orderBy:wordSort:wordSortRev"> + <td><a ui-sref="words.show({id:word.id})" ng-bind="word.id"></a></td> + <td ng-bind="word.likeliness"></td> + </tr> + </tbody> + </table> + </div> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/words/show.html b/vipra-ui/app/html/words/show.html index 1ab0373b9d80526fe1895bf5c55481f6a77c62a6..749c267fd716f73df8c5baf240b26c1cc5db0a79 100644 --- a/vipra-ui/app/html/words/show.html +++ b/vipra-ui/app/html/words/show.html @@ -1,18 +1,30 @@ -<h1 ng-bind="::word.id"></h1> +<div class="page-header"> + <h1 ng-bind="::word.id"></h1> +</div> -<table class="table table-bordered table-condensed"> - <tbody> - <tr> - <th>Created</th> - <td ng-bind="::word.created"></td> - </tr> - </tbody> -</table> +<h3>Info <hide-link target="#info"/></h3> -<h3>Topics</h3> +<div class="row" id="info"> + <div class="col-md-12"> + <table class="table table-bordered table-condensed"> + <tbody> + <tr> + <th>Created</th> + <td ng-bind="::word.created"></td> + </tr> + </tbody> + </table> + </div> +</div> -<ul class="list-unstyled"> - <li ng-repeat="topic in ::word.topics"> - <topic-link topic="topic"/> - </li> -</ul> \ No newline at end of file +<h3>Topics <hide-link target="#topics"/></h3> + +<div class="row" id="topics"> + <div class="col-md-12"> + <ul class="list-unstyled"> + <li ng-repeat="topic in ::word.topics"> + <topic-link topic="topic"/> + </li> + </ul> + </div> +</div> \ No newline at end of file diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js index 6b6456a299d79b789abb2e9bf0a08a4144c29620..be65d8ccde29f2e537f8b56e32c985c8970e7565 100644 --- a/vipra-ui/app/js/app.js +++ b/vipra-ui/app/js/app.js @@ -8,12 +8,10 @@ 'ngResource', 'ngSanitize', 'ui.router', - 'ui.bootstrap', 'vipra.controllers', 'vipra.directives', 'vipra.factories', 'vipra.filters', - 'vipra.services', 'vipra.templates' ]); diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index ff261ec5c5a061bb6012ad866c40b52ed3e5b187..f8a5cd6b21256f0b91efcf5d6f3ace84c1d80374 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -14,6 +14,9 @@ pageSize = 100, paginationPadding = 4; + /** + * Index controller + */ app.controller('IndexController', ['$scope', '$location', 'ArticleFactory', 'TopicFactory', 'WordFactory', 'SearchFactory', function($scope, $location, ArticleFactory, TopicFactory, WordFactory, SearchFactory) { @@ -48,19 +51,46 @@ }]); - app.controller('NetworkController', ['$scope', '$state', '$stateParams', 'ArticleFactory', 'TopicFactory', - function($scope, $state, $stateParams, ArticleFactory, TopicFactory) { + /** + * Network controller + */ + app.controller('NetworkController', ['$scope', '$state', '$stateParams', '$timeout', 'Store', 'ArticleFactory', 'TopicFactory', 'WordFactory', + function($scope, $state, $stateParams, $timeout, Store, ArticleFactory, TopicFactory, WordFactory) { + + var id = 0, + ids = {}, + container = $("#visgraph")[0], + edges = {}; - $scope.type = $stateParams.type; $scope.colors = { articles: '#BBC9D2', topics: '#DBB234', words: '#547C65' }; - $scope.shown = { - articles: true, - topics: true, - words: true + $scope.nodes = new vis.DataSet(); + $scope.edges = new vis.DataSet(); + $scope.data = { + nodes: $scope.nodes, + edges: $scope.edges + }; + $scope.options = { + nodes: { + font: { size: 14 }, + shape: 'dot', + borderWidth: 0 + }, + edges: { + color: { + highlight: '#f00' + } + }, + layout: { randomSeed: 1 }, + physics: { + barnesHut: { + springConstant: 0.008, + gravitationalConstant: -3500 + } + } }; var factory; @@ -68,21 +98,155 @@ factory = ArticleFactory; else if($stateParams.type === 'topics') factory = TopicFactory; + else if($stateParams.type === 'words') + factory = WordFactory; else { console.log('unknown network type'); return; } + // get root node factory.get({id: $stateParams.id}, function(response) { - $scope.model = response.data; + // add root node + if($stateParams.type === 'articles') + $scope.nodes.add([articleNode(response.data)]); + else if($stateParams.type === 'topics') + $scope.nodes.add([topicNode(response.data)]); + else if($stateParams.type === 'words') + $scope.nodes.add([wordNode(response.data)]); + ids[response.data.id] = id; + + // create graph + $scope.graph = new vis.Network(container, $scope.data, $scope.options); + $scope.graph.on('selectNode', $scope.select); + $scope.graph.on('doubleClick', $scope.open); }); + var newNode = function(title, type, show, dbid, color, shape) { + ids[dbid] = ++id; + return { + id: id, + title: title, + label: title.multiline(5), + type: type, + show: show, + dbid: dbid, + shape: shape || 'dot', + color: { + background: color, + highlight: { background: color } + } + }; + }; + + var topicNode = function(topic) { + topic = topic.topic || topic; + return newNode(topic.name, 'topic', 'topics.show', topic.id, $scope.colors.topics, 'triangle'); + }; + + var articleNode = function(article) { + return newNode(article.title, 'article', 'articles.show', article.id, $scope.colors.articles, 'square'); + }; + + var wordNode = function(word) { + return newNode(word.id, 'word', 'words.show', word.id, $scope.colors.words); + }; + + var edgeExists = function(idA, idB) { + if(idB < idA) { + var tmp = idA; + idA = idB; + idB = tmp; + } + return edges.hasOwnProperty(idA + '-' + idB); + }; + + var addEdge = function(idA, idB) { + if(idB < idA) { + var tmp = idA; + idA = idB; + idB = tmp; + } + edges[idA + '-' + idB] = 1; + }; + + // construct new nodes + var constructor = function(result, node, key, nodeFunction) { + if(result.data && (!key || result.data[key])) { + var data = key ? result.data[key] : result.data, + newNodes = [], + newEdges = []; + for(var i = 0; i < data.length; i++) { + var current = data[i]; + if(ids.hasOwnProperty(current.id)) { + if(edgeExists(ids[current.id], node.id)) + continue; + newEdges.push({from:ids[current.id], to:node.id}); + addEdge(ids[current.id], node.id); + } else { + newNodes.push(nodeFunction(current)); + newEdges.push({from:id, to:node.id}); + addEdge(id, node.id); + } + } + if(newNodes.length) + $scope.nodes.add(newNodes); + if(newEdges.length) + $scope.edges.add(newEdges); + } + }; + + // on node select + var selectTimeout; + $scope.select = function(props) { + $timeout.cancel(selectTimeout); + selectTimeout = $timeout(function() { + var node = $scope.nodes.get(props.nodes[0]); + if(node) { + if(node.type === 'article' && $scope.shown.topics) { + // node is article, load article to get topics + ArticleFactory.get({id:node.dbid}, function(res) { + for(var i = 0; i < res.data.topics.length; i++) + res.data.topics[i] = res.data.topics[i].topic; + constructor(res, node, 'topics', topicNode); + }); + } else if(node.type === 'topic') { + // node is topic, load topic to get words and articles + if($scope.shown.words) + TopicFactory.get({id:node.dbid}, function(res) { + constructor(res, node, 'words', wordNode); + }); + if($scope.shown.articles) + TopicFactory.articles({id:node.dbid}, function(res) { + constructor(res, node, null, articleNode); + }); + } else if(node.type === 'word' && $scope.shown.topics) { + // node is word, load word to get topics + WordFactory.get({id:node.dbid}, function(res) { + constructor(res, node, 'topics', topicNode); + }); + } + $scope.nodes.update(node); + } + }, 500); + }; + + // on node open + $scope.open = function(props) { + $timeout.cancel(selectTimeout); + var node = $scope.nodes.get(props.nodes[0]); + $state.transitionTo(node.show, {id:node.dbid}); + }; + }]); - /* + /**************************************************************************** * Article Controllers - */ + ****************************************************************************/ + /** + * Article Index route + */ app.controller('ArticlesIndexController', ['$scope', '$state', '$stateParams', 'ArticleFactory', 'Store', function($scope, $state, $stateParams, ArticleFactory, Store) { @@ -112,6 +276,9 @@ }]); + /** + * Article Show route + */ app.controller('ArticlesShowController', ['$scope', '$stateParams', 'ArticleFactory', function($scope, $stateParams, ArticleFactory, testService) { @@ -123,6 +290,8 @@ $scope.article.modified = formatDateTime($scope.article.modified); $scope.articleMeta = response.meta; $scope.queryTime = response.$queryTime; + $scope.topicSort = $scope.topicSort || 'topic.share'; + $scope.topicSortRev = typeof $scope.topicSortRev === 'undefined' ? false : $scope.topicSortRev; // calculate percentage share var topicShareSeries = [], @@ -155,10 +324,13 @@ }]); - /* + /**************************************************************************** * Topic Controllers - */ + ****************************************************************************/ + /** + * Topic Index route + */ app.controller('TopicsIndexController', ['$scope', '$stateParams', 'Store', 'TopicFactory', function($scope, $stateParams, Store, TopicFactory) { @@ -188,8 +360,11 @@ }]); - app.controller('TopicsShowController', ['$scope', '$stateParams', 'TopicFactory', - function($scope, $stateParams, TopicFactory) { + /** + * Topic Show route + */ + app.controller('TopicsShowController', ['$scope', '$stateParams', '$timeout', 'TopicFactory', + function($scope, $stateParams, $timeout, TopicFactory) { TopicFactory.get({id: $stateParams.id}, function(response) { $scope.topic = response.data; @@ -197,14 +372,43 @@ $scope.topic.modified = formatDateTime($scope.topic.modified); $scope.topicMeta = response.meta; $scope.queryTime = response.$queryTime; + $scope.wordSort = $scope.wordSort || 'likeliness'; + $scope.wordSortRev = typeof $scope.wordSortRev === 'undefined' ? true : $scope.wordSortRev; + + $scope.startRename = function() { + $scope.origName = $scope.topic.name; + $scope.isRename = true; + $timeout(function() { + $('#topicName').select(); + }, 0); + }; + + $scope.endRename = function(save) { + $scope.isRename = false; + if(save) { + // TODO implement + } else { + $scope.topic.name = $scope.origName; + } + }; + + $scope.keyup = function($event) { + if($event.which === 13 || $event.which === 27) { + $scope.endRename($event.which === 13); + $event.preventDefault(); + } + }; }); }]); - /* + /**************************************************************************** * Word Controllers - */ + ****************************************************************************/ + /** + * Word Index route + */ app.controller('WordsIndexController', ['$scope', '$state', '$stateParams', 'Store', 'WordFactory', function($scope, $state, $stateParams, Store, WordFactory) { @@ -235,6 +439,9 @@ }]); + /** + * Word Show route + */ app.controller('WordsShowController', ['$scope', '$stateParams', 'WordFactory', function($scope, $stateParams, WordFactory) { @@ -247,10 +454,13 @@ }]); - /* + /**************************************************************************** * Directive Controllers - */ + ****************************************************************************/ + /** + * Pagination + */ app.controller('PaginationController', ['$scope', function($scope) { diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index cd9abc0bcb3892d4a52af4daddabec8f3f433223..93059f3c3aea253225ed88aff6e02bdfe8ab90a4 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -57,176 +57,6 @@ }; }); - app.directive('visNetwork', ['$state', '$timeout', 'ArticleFactory', 'TopicFactory', 'WordFactory', - function($state, $timeout, ArticleFactory, TopicFactory, WordFactory) { - - return { - scope: { - ngModel: '=', - visType: '=', - visData: '=', - visColors: '=', - visShown: '=' - }, - transclude: true, - template: '<ng-transclude/><div class="fullsize navpadding" id="visgraph"></div>', - link: function($scope, $element) { - var id = 0, - ids = {}, - container = $element.find("#visgraph")[0]; - - $scope.nodes = new vis.DataSet(); - $scope.edges = new vis.DataSet(); - $scope.data = { - nodes: $scope.nodes, - edges: $scope.edges - }; - - $scope.options = { - nodes: { - font: { size: 14 }, - shape: 'dot', - borderWidth: 0 - }, - edges: { - color: { - highlight: '#f00' - } - }, - layout: { randomSeed: 1 }, - physics: { - barnesHut: { - springConstant: 0.008, - gravitationalConstant: -3500 - } - } - }; - - var newNode = function(title, type, show, dbid, color, shape) { - ids[dbid] = ++id; - return { - id: id, - title: title, - label: title, - type: type, - show: show, - dbid: dbid, - shape: shape || 'dot', - color: { - background: color, - highlight: { background: color } - } - }; - }; - - var topicNode = function(topic) { - topic = topic.topic || topic; - return newNode(topic.name, 'topic', 'topics.show', topic.id, $scope.visColors.topics, 'triangle'); - }; - - var articleNode = function(article) { - return newNode(article.title, 'article', 'articles.show', article.id, $scope.visColors.articles, 'square'); - }; - - var wordNode = function(word) { - return newNode(word.id, 'word', 'words.show', word.id, $scope.visColors.words); - }; - - // construct new nodes - var constructor = function(result, node, key, nodeFunction) { - if(result.data && (!key || result.data[key])) { - var data = key ? result.data[key] : result.data, - newNodes = [], - newEdges = []; - for(var i = 0; i < data.length; i++) { - var current = data[i]; - if(ids.hasOwnProperty(current.id)) { - if(!$scope.nodes.get(ids[current.id]).loaded) - newEdges.push({from:ids[current.id], to:node.id}); - } else { - newNodes.push(nodeFunction(current)); - newEdges.push({from:id, to:node.id}); - } - } - if(newNodes.length) $scope.nodes.add(newNodes); - if(newEdges.length) $scope.edges.add(newEdges); - } - }; - - // on node select - var selectTimeout; - $scope.select = function(props) { - $timeout.cancel(selectTimeout); - selectTimeout = $timeout(function() { - var node = $scope.nodes.get(props.nodes[0]); - if(node && !node.loaded) { - var loaded = false; - if(node.type === 'article' && $scope.visShown.topics) { - // node is article, load article to get topics - ArticleFactory.get({id:node.dbid}, function(res) { - for(var i = 0; i < res.data.topics.length; i++) - res.data.topics[i] = res.data.topics[i].topic; - constructor(res, node, 'topics', topicNode); - }); - loaded = true; - } else if(node.type === 'topic') { - // node is topic, load topic to get words and articles - if($scope.visShown.words) { - TopicFactory.get({id:node.dbid}, function(res) { - constructor(res, node, 'words', wordNode); - }); - loaded = true; - } - if($scope.visShown.articles) { - TopicFactory.articles({id:node.dbid}, function(res) { - constructor(res, node, null, articleNode); - }); - loaded = true; - } - } else if(node.type === 'word' && $scope.visShown.topics) { - // node is word, load word to get topics - WordFactory.get({id:node.dbid}, function(res) { - constructor(res, node, 'topics', topicNode); - }); - loaded = true; - } - if(loaded) { - node.loaded = true; - $scope.nodes.update(node); - } - } - }, 500); - }; - - // on node open - $scope.open = function(props) { - $timeout.cancel(selectTimeout); - var node = $scope.nodes.get(props.nodes[0]); - $state.transitionTo(node.show, {id:node.dbid}); - }; - - // watch for changes to model - $scope.$watch('ngModel', function(newVal, oldVal) { - if(!newVal) return; - - // add root node - if($scope.visType === 'articles') - $scope.nodes.add([articleNode($scope.ngModel)]); - else if($scope.visType === 'topics') - $scope.nodes.add([topicNode($scope.ngModel)]); - else if($scope.visType === 'words') - $scope.nodes.add([wordNode($scope.ngModel)]); - ids[$scope.ngModel.id] = id; - - // create graph - $scope.visGraph = new vis.Network(container, $scope.data, $scope.options); - $scope.visGraph.on('selectNode', $scope.select); - $scope.visGraph.on('doubleClick', $scope.open); - }); - } - }; - }]); - app.directive('highcharts', function() { return { scope: { @@ -289,4 +119,72 @@ } }); + app.directive('storeValue', ['Store', function(Store) { + return { + restrict: 'A', + require: 'ngModel', + link: function($scope, $elem, $attrs, $ctrl) { + if(!$attrs.storeValue) { + console.log("no store key given"); + return; + } + var value = Store($attrs.storeValue); + if(typeof value !== 'undefined') { + $ctrl.$setViewValue(value); + $ctrl.$render(); + } + $ctrl.$viewChangeListeners.push(function() { + Store($attrs.storeValue, $ctrl.$viewValue); + }); + } + }; + }]); + + app.directive('bsDropdown', function() { + return { + templateUrl: 'html/directives/dropdown.html', + transclude: true, + replace: true, + link: function($scope, $elem, $attrs) { + $scope.dropdownId = randomId(); + $scope.label = $attrs.label; + $scope.align = 'dropdown-menu-left'; + if($attrs.align === 'right') + $scope.align = 'dropdown-menu-right'; + } + }; + }); + + app.directive('sortBy', ['Store', function(Store) { + return { + scope: { + sortBy: '@', + sortType: '=', + sortReverse: '=', + storeKey: '@sortType' + }, + restrict: 'A', + transclude: true, + template: '<span ng-click="click()"><ng-transclude/><span ng-show="sortType == sortBy" class="caret" ng-class="{\'caret-up\':sortReverse}"></span></span>', + link: function($scope, $elem, $attrs) { + if(!$attrs.sortBy) { + console.log('no sort by key given'); + return; + } + var value = Store($scope.storeKey); + if(value) { + value = value.split(','); + $scope.sortType = value[0]; + $scope.sortReverse = value[1] === 'true'; + } + $scope.sortBy = $attrs.sortBy; + $scope.click = function() { + $scope.sortType = $scope.sortBy; + $scope.sortReverse = !$scope.sortReverse; + Store($scope.storeKey, $scope.sortType + ',' + $scope.sortReverse); + }; + } + }; + }]); + })(); \ No newline at end of file diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js index dacff035c3e805cb785d4e5b980162a1cc93e37f..31c3ef07289f5754dae26222e3726269e171fead 100644 --- a/vipra-ui/app/js/helpers.js +++ b/vipra-ui/app/js/helpers.js @@ -23,6 +23,10 @@ return '<span class="initial">' + text.substring(0, 1) + "</span>" + text.substring(1); }; + window.randomId = function() { + return 'id' + Math.random().toString(36).substring(7); + }; + String.prototype.ellipsize = function(max) { max = max || 20; if(this.length > max) { @@ -31,6 +35,15 @@ return this; }; + String.prototype.multiline = function(max) { + return this.split(new RegExp("((?:\\w+ ){" + max + "})", "g")).filter(Boolean).join("\n"); + }; + + if(typeof String.prototype.startsWith === 'undefined') + String.prototype.startsWith = function(start) { + return this.lastIndexOf(start, 0) === 0; + }; + window.console = window.console || { log: function () {} }; diff --git a/vipra-ui/app/js/services.js b/vipra-ui/app/js/services.js deleted file mode 100644 index 5f8e95703ea98dc8ae766127dd7a21420231b528..0000000000000000000000000000000000000000 --- a/vipra-ui/app/js/services.js +++ /dev/null @@ -1,9 +0,0 @@ -/****************************************************************************** - * Vipra Application - * Services - ******************************************************************************/ -(function() { - - var app = angular.module('vipra.services', []); - -})(); \ No newline at end of file diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index 08c38cef3d7ed8923523c7008f540092da911d8e..a1be97047b06d257b015252a398026ede6957952 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -129,8 +129,14 @@ ul.dashed { left: 10px; font-weight: bold; padding: 10px; - border: 1px solid #aaa; - border-radius: 3px; + + label { + margin: 0; + } + + label + label { + padding-left: 5px; + } } .initial { @@ -167,7 +173,6 @@ ul.dashed { .navpadding { padding-top: 50px; - padding-bottom: 50px; } .form-control-inline { @@ -179,6 +184,36 @@ ul.dashed { z-index: 9999; } +.item-actions { + padding: 5px 0 0 10px; +} + +.row { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.caret.caret-up { + content: ""; + border-top: 0; + border-bottom: 4px dashed; +} + +.table.table-morecondensed > tbody > tr > td { + padding: 0; +} + +.page-header h1 { + min-height: 48px; +} + +[sort-by] { + .noselect; + cursor: pointer; +} + #nprogress .spinner { display: none; } diff --git a/vipra-ui/bower.json b/vipra-ui/bower.json index 1efe46c3299924589c13c787c661a50ef095b070..da39b5d3e8c1ad9b8d75a488378541f38dae2b43 100644 --- a/vipra-ui/bower.json +++ b/vipra-ui/bower.json @@ -23,7 +23,6 @@ "angular-resource": "^1.5.0", "angular-ui-router": "^0.2.17", "angular-sanitize": "^1.5.0", - "angular-bootstrap": "^1.1.2", "highcharts": "^4.2.2", "nprogress": "^0.2.0", "vis": "https://github.com/almende/vis.git#^4.14.0" diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js index 7b4035879b6efa6731e3da89373b5738a12d6472..3b4c63e2d9db7abc4027702cb6fcb52246a53747 100644 --- a/vipra-ui/gulpfile.js +++ b/vipra-ui/gulpfile.js @@ -14,7 +14,6 @@ var assets = { 'bower_components/angular-resource/angular-resource.min.js', 'bower_components/angular-sanitize/angular-sanitize.min.js', 'bower_components/angular-ui-router/release/angular-ui-router.min.js', - 'bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js', 'bower_components/bootstrap/dist/js/bootstrap.min.js', 'bower_components/highcharts/highcharts.js', 'bower_components/vis/dist/vis.min.js',