diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java index 39547b83726e74fdc8681a0c7ab2b2dbf87afd6c..3fafdd4b752c4211196a323198be3356283055be 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java @@ -1,6 +1,7 @@ package de.vipra.rest.resource; import java.io.IOException; +import java.util.Date; import java.util.List; import javax.servlet.ServletContext; @@ -67,7 +68,8 @@ public class ArticleResource { public Response getArticles(@QueryParam("topicModel") final String topicModel, @QueryParam("skip") final Integer skip, @QueryParam("limit") final Integer limit, @QueryParam("sort") @DefaultValue("date") final String sortBy, @QueryParam("fields") final String fields, @QueryParam("word") final String word, @QueryParam("entity") final String entity, - @QueryParam("excerpt") final String excerpt, @QueryParam("char") final String startChar, @QueryParam("contains") final String contains) { + @QueryParam("excerpt") final String excerpt, @QueryParam("char") final String startChar, @QueryParam("contains") final String contains, + @QueryParam("from") final Long fromDate, @QueryParam("to") final Long toDate) { final ResponseWrapper<List<ArticleFull>> res = new ResponseWrapper<>(); if (topicModel == null || topicModel.trim().isEmpty()) { @@ -104,6 +106,12 @@ public class ArticleResource { if (contains != null && !contains.isEmpty()) query.contains("title", contains, true); + if (fromDate != null) + query.gte("date", new Date(fromDate)); + + if (toDate != null) + query.lte("date", new Date(toDate)); + final List<ArticleFull> articles = dbArticles.getMultiple(query); if ((skip != null && skip > 0) || (limit != null && limit > 0)) diff --git a/vipra-ui/app/html/articles/index.html b/vipra-ui/app/html/articles/index.html index 2037f1dfd28452ecff1125f5f20dd54a8a79bef1..91193ff1220c473ace98dbf2935c35de8071e278 100644 --- a/vipra-ui/app/html/articles/index.html +++ b/vipra-ui/app/html/articles/index.html @@ -70,6 +70,21 @@ </span> </div> </div> + <div class="form-group"> + <label>From/to date</label> + <div class="input-group date" id="fromDate" bs-datetimepicker ng-model="articlesIndexModels.fromDate"> + <input type="text" class="form-control" placeholder="From date"> + <span class="input-group-addon"> + <span class="glyphicon glyphicon-calendar"></span> + </span> + </div> + <div class="input-group date" id="toDate" bs-datetimepicker ng-model="articlesIndexModels.toDate"> + <input type="text" class="form-control" placeholder="To date"> + <span class="input-group-addon"> + <span class="glyphicon glyphicon-calendar"></span> + </span> + </div> + </div> </div> </div> </div> diff --git a/vipra-ui/app/html/directives/article-link.html b/vipra-ui/app/html/directives/article-link.html index e62bd916ba357df1b280283cfbaeca58b85710b7..952d2991b5e61401bdb68726e4220a81956b1758 100644 --- a/vipra-ui/app/html/directives/article-link.html +++ b/vipra-ui/app/html/directives/article-link.html @@ -1,6 +1,10 @@ <div class="link-wrapper"> <span class="menu-padding ellipsis"> <div class="pull-right article-dropdown"> + <div class="date-compact"> + <small class="text-muted" ng-bind-template="{{::$root.Vipra.formatDate(article.date)}}"></small> + <small class="text-muted" ng-bind-template="{{::$root.Vipra.formatTime(article.date)}}"></small> + </div> <span class="badge" ng-bind="::article.topicsCount" ng-attr-title="{{::article.topicsCount}} topic(s)" ng-if="::showBadge"></span> <i class="fa text-muted pointer" ng-class="{'fa-chevron-down':!detailsShown,'fa-chevron-up':detailsShown}" ng-click="toggleDetails()" ng-if="::showDetails" analytics-on analytics-event="Article details" analytics-category="Article actions"></i> </div> @@ -13,7 +17,9 @@ <div ng-if="detailsShown" class="details"> <span ng-bind="::articleDetails.text"></span> <div> - <a ui-sref="topics.show({id:topic.topic.id})" class="badge topic-badge text-outline" ng-bind="::topic.topic.name" ng-style="{'background':topic.topic.color}" ng-attr-title="{{::topic.topic.name}}" ng-repeat="topic in articleDetails.topics"></a> + <a class="badge topic-badge text-outline" ng-style="{'background':topic.topic.color}" ng-repeat="topic in articleDetails.topics"> + <topic-link topic="::topic.topic" badge="false" /> + </a> </div> </div> </div> \ No newline at end of file diff --git a/vipra-ui/app/html/directives/article-menu.html b/vipra-ui/app/html/directives/article-menu.html index 1a2ed5613f9d460782d09d0d079cf39508f42f4c..09815f09a46b98ed3d312bb0bf6001545029f853 100644 --- a/vipra-ui/app/html/directives/article-menu.html +++ b/vipra-ui/app/html/directives/article-menu.html @@ -5,6 +5,6 @@ <ul class="dropdown-menu" ng-class="{'dropdown-menu-right':dropdownRight}"> <li><a ui-sref="articles.show({id:article.id})">Show</a></li> <li role="separator" class="divider"></li> - <li><a ui-sref="network({type:'topics',id:article.id})">Network</a></li> + <li><a ui-sref="network({type:'articles',id:article.id})">Network</a></li> </ul> </div> \ No newline at end of file diff --git a/vipra-ui/app/html/directives/char-selector.html b/vipra-ui/app/html/directives/char-selector.html index 7747f99edcecdf38489776e512b28db21bf7663b..0b173e190aa5864855efa13ad075b1e241bf9e84 100644 --- a/vipra-ui/app/html/directives/char-selector.html +++ b/vipra-ui/app/html/directives/char-selector.html @@ -1,29 +1,36 @@ -<select class="form-control"> - <option value="" analytics-on analytics-event="Char selector ()" analytics-category="Filter actions">All</option> - <option value="A" analytics-on analytics-event="Char selector (A)" analytics-category="Filter actions">A</option> - <option value="B" analytics-on analytics-event="Char selector (B)" analytics-category="Filter actions">B</option> - <option value="C" analytics-on analytics-event="Char selector (C)" analytics-category="Filter actions">C</option> - <option value="D" analytics-on analytics-event="Char selector (D)" analytics-category="Filter actions">D</option> - <option value="E" analytics-on analytics-event="Char selector (E)" analytics-category="Filter actions">E</option> - <option value="F" analytics-on analytics-event="Char selector (F)" analytics-category="Filter actions">F</option> - <option value="G" analytics-on analytics-event="Char selector (G)" analytics-category="Filter actions">G</option> - <option value="H" analytics-on analytics-event="Char selector (H)" analytics-category="Filter actions">H</option> - <option value="I" analytics-on analytics-event="Char selector (I)" analytics-category="Filter actions">I</option> - <option value="J" analytics-on analytics-event="Char selector (J)" analytics-category="Filter actions">J</option> - <option value="K" analytics-on analytics-event="Char selector (K)" analytics-category="Filter actions">K</option> - <option value="L" analytics-on analytics-event="Char selector (L)" analytics-category="Filter actions">L</option> - <option value="M" analytics-on analytics-event="Char selector (M)" analytics-category="Filter actions">M</option> - <option value="N" analytics-on analytics-event="Char selector (N)" analytics-category="Filter actions">N</option> - <option value="O" analytics-on analytics-event="Char selector (O)" analytics-category="Filter actions">O</option> - <option value="P" analytics-on analytics-event="Char selector (P)" analytics-category="Filter actions">P</option> - <option value="Q" analytics-on analytics-event="Char selector (Q)" analytics-category="Filter actions">Q</option> - <option value="R" analytics-on analytics-event="Char selector (R)" analytics-category="Filter actions">R</option> - <option value="S" analytics-on analytics-event="Char selector (S)" analytics-category="Filter actions">S</option> - <option value="T" analytics-on analytics-event="Char selector (T)" analytics-category="Filter actions">T</option> - <option value="U" analytics-on analytics-event="Char selector (U)" analytics-category="Filter actions">U</option> - <option value="V" analytics-on analytics-event="Char selector (V)" analytics-category="Filter actions">V</option> - <option value="W" analytics-on analytics-event="Char selector (W)" analytics-category="Filter actions">W</option> - <option value="X" analytics-on analytics-event="Char selector (X)" analytics-category="Filter actions">X</option> - <option value="Y" analytics-on analytics-event="Char selector (Y)" analytics-category="Filter actions">Y</option> - <option value="Z" analytics-on analytics-event="Char selector (Z)" analytics-category="Filter actions">Z</option> -</select> \ No newline at end of file +<div class="input-group"> + <select class="form-control" ng-model="ngModel"> + <option value="" analytics-on analytics-event="Char selector ()" analytics-category="Filter actions">All</option> + <option value="A" analytics-on analytics-event="Char selector (A)" analytics-category="Filter actions">A</option> + <option value="B" analytics-on analytics-event="Char selector (B)" analytics-category="Filter actions">B</option> + <option value="C" analytics-on analytics-event="Char selector (C)" analytics-category="Filter actions">C</option> + <option value="D" analytics-on analytics-event="Char selector (D)" analytics-category="Filter actions">D</option> + <option value="E" analytics-on analytics-event="Char selector (E)" analytics-category="Filter actions">E</option> + <option value="F" analytics-on analytics-event="Char selector (F)" analytics-category="Filter actions">F</option> + <option value="G" analytics-on analytics-event="Char selector (G)" analytics-category="Filter actions">G</option> + <option value="H" analytics-on analytics-event="Char selector (H)" analytics-category="Filter actions">H</option> + <option value="I" analytics-on analytics-event="Char selector (I)" analytics-category="Filter actions">I</option> + <option value="J" analytics-on analytics-event="Char selector (J)" analytics-category="Filter actions">J</option> + <option value="K" analytics-on analytics-event="Char selector (K)" analytics-category="Filter actions">K</option> + <option value="L" analytics-on analytics-event="Char selector (L)" analytics-category="Filter actions">L</option> + <option value="M" analytics-on analytics-event="Char selector (M)" analytics-category="Filter actions">M</option> + <option value="N" analytics-on analytics-event="Char selector (N)" analytics-category="Filter actions">N</option> + <option value="O" analytics-on analytics-event="Char selector (O)" analytics-category="Filter actions">O</option> + <option value="P" analytics-on analytics-event="Char selector (P)" analytics-category="Filter actions">P</option> + <option value="Q" analytics-on analytics-event="Char selector (Q)" analytics-category="Filter actions">Q</option> + <option value="R" analytics-on analytics-event="Char selector (R)" analytics-category="Filter actions">R</option> + <option value="S" analytics-on analytics-event="Char selector (S)" analytics-category="Filter actions">S</option> + <option value="T" analytics-on analytics-event="Char selector (T)" analytics-category="Filter actions">T</option> + <option value="U" analytics-on analytics-event="Char selector (U)" analytics-category="Filter actions">U</option> + <option value="V" analytics-on analytics-event="Char selector (V)" analytics-category="Filter actions">V</option> + <option value="W" analytics-on analytics-event="Char selector (W)" analytics-category="Filter actions">W</option> + <option value="X" analytics-on analytics-event="Char selector (X)" analytics-category="Filter actions">X</option> + <option value="Y" analytics-on analytics-event="Char selector (Y)" analytics-category="Filter actions">Y</option> + <option value="Z" analytics-on analytics-event="Char selector (Z)" analytics-category="Filter actions">Z</option> + </select> + <span class="input-group-btn"> + <button class="btn btn-default" type="button" ng-click="ngModel=''"> + <span class="glyphicon glyphicon-remove text-danger"></span> + </button> + </span> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/directives/pagination-full.html b/vipra-ui/app/html/directives/pagination-full.html index c206067bc1b9845808ffa04d42266adccc64547b..7564a87c025a491eaf2e554a3a882ba83cd4b220 100644 --- a/vipra-ui/app/html/directives/pagination-full.html +++ b/vipra-ui/app/html/directives/pagination-full.html @@ -27,9 +27,9 @@ </div> </div> - <div class="btn-group btn-group-justified"> + <div class="btn-group btn-group-justified button-pagination"> <div class="btn-group" ng-repeat="p in pages"> - <button class="btn btn-default" ng-class="{active:page==p}" ng-attr-title="{{'Page ' + p}}" ng-click="changePage(p)" ng-disabled="page==p" analytics-on analytics-event="Paginator (i)" analytics-category="Filter actions" ng-bind="p"></button> + <button class="btn btn-default" ng-class="{current:page==p}" ng-attr-title="{{'Page ' + p}}" ng-click="changePage(p)" analytics-on analytics-event="Paginator (i)" analytics-category="Filter actions" ng-bind="p"></button> </div> </div> </div> \ No newline at end of file diff --git a/vipra-ui/app/html/network.html b/vipra-ui/app/html/network.html index 79fab49ca641c8414241be772a63f5630139f978..d410777f47cd522ce4b3a0fe7245b00db93e4944 100644 --- a/vipra-ui/app/html/network.html +++ b/vipra-ui/app/html/network.html @@ -19,6 +19,11 @@ <input type="checkbox" id="showWords" ng-model="shown.words"> <label for="showWords" analytics-on analytics-event="Network Type (Words)" analytics-category="Filter actions">Words</label> </div> + <hr> + <div class="checkbox"> + <input type="checkbox" id="highlightEnabled" ng-model="highlightEnabled"> + <label for="highlightEnabled" analytics-on analytics-event="Network Highlight Enable" analytics-category="Network actions">Neighborhood highlight</label> + </div> <div style="display:none"> Window <window-dropdown ng-model="selectedWindow" windows="windows" /> @@ -45,6 +50,17 @@ </tr> </table> </div> + <div> + <table> + <tr> + <td> + <button class="btn btn-default" ng-click="togglePhysics()" analytics-on analytics-event="Network Physics Toggle" analytics-category="Network actions"> + <span class="fa" ng-class="{'fa-pause':physicsEnabled, 'fa-play':!physicsEnabled}"></span> Physics + </button> + </td> + </tr> + </table> + </div> <div> <button class="btn btn-default btn-block" ng-click="completeNetwork()" analytics-on analytics-event="Network Complete Network" analytics-category="Network actions"> Load missing relations diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js index 1fc95a68654349207aed4a66e162111aa1335331..c73784eed3a9bc1763bbc267a04bfdb19f795957 100644 --- a/vipra-ui/app/js/app.js +++ b/vipra-ui/app/js/app.js @@ -16,14 +16,15 @@ 'nya.bootstrap.select', 'angulartics', 'angulartics.google.analytics', + 'angular-loading-bar', 'vipra.controllers', 'vipra.directives', 'vipra.factories', 'vipra.templates' ]); - app.config(['$locationProvider', '$stateProvider', '$urlRouterProvider', '$httpProvider', - function($locationProvider, $stateProvider, $urlRouterProvider, $httpProvider) { + app.config(['$locationProvider', '$stateProvider', '$urlRouterProvider', '$httpProvider', 'cfpLoadingBarProvider', + function($locationProvider, $stateProvider, $urlRouterProvider, $httpProvider, cfpLoadingBarProvider) { $locationProvider.html5Mode(true); $urlRouterProvider.otherwise('/'); @@ -211,6 +212,8 @@ fadeAfter: 10000 }); + // disable loading bar spinner + cfpLoadingBarProvider.includeSpinner = false; } ]); diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index 4c04181b15aea244cde5cc4dbaaeee47d877936d..1d6d78582108d11935e774ccbf6b8ebf2af61839 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -90,7 +90,6 @@ }; $scope.checkTopicModel = function(state, callback) { - if($state.current.name === 'index' || $state.current.name !== state) return; if($scope.rootModels.topicModel) callback(); else { @@ -289,10 +288,16 @@ /** * Network controller */ - app.controller('NetworkController', ['$scope', '$state', '$stateParams', '$timeout', 'ArticleFactory', 'TopicFactory', 'WordFactory', - function($scope, $state, $stateParams, $timeout, ArticleFactory, TopicFactory, WordFactory) { + app.controller('NetworkController', ['$scope', '$state', '$stateParams', '$q', '$window', '$timeout', 'ArticleFactory', 'TopicFactory', 'WordFactory', + function($scope, $state, $stateParams, $q, $window, $timeout, ArticleFactory, TopicFactory, WordFactory) { $scope.rootModels.title = 'Network'; + $scope.physicsEnabled = true; + $scope.highlightEnabled = true; + + var level2Color = 'rgba(130,130,130,0.8)'; + var level3Color = 'rgba(220,220,220,0.4)'; + var id = 0, ids = {}, edges = {}; @@ -330,6 +335,10 @@ barnesHut: { springConstant: 0.008, gravitationalConstant: -3500 + }, + timestep: 0.4, + stabilization: { + iterations: 100 } }, interaction: { @@ -349,8 +358,6 @@ ids[dbid] = ++id; return { id: id, - x: x, - y: y, title: title, label: title.multiline(5).ellipsize(50), type: type, @@ -360,6 +367,7 @@ loader: loader, borderWidth: 1, origColor: { + border: '#000', background: color, highlight: { background: color, @@ -460,12 +468,17 @@ }; // on node select + var nodeLoader = null; $scope.select = function(props) { var node = $scope.nodes.get(props.nodes[0]); if (node) { - node.loader(node, props); - node.loaded = true; - $scope.nodes.update(node); + nodeLoader = node.loader(node, props); + nodeLoader.then(function() { + node.loaded = true; + $scope.nodes.update(node); + if($scope.searchNodes) + $scope.search(); + }); } }; @@ -476,6 +489,7 @@ }; $scope.loadArticle = function(node, props) { + var defer = $q.defer(); ArticleFactory.get({ id: node.dbid, fields: '_all' @@ -493,55 +507,91 @@ articles.push(data.similarArticles[i].article); constructor(articles, articleNode, node, props); } + defer.resolve(); }); + return defer.promise; }; $scope.loadTopic = function(node, props) { + var defers = []; + var d1 = $q.defer(); + d1.resolve(); + defers.push(d1.promise); if ($scope.shown.articles) { + var d2 = $q.defer(); + defers.push(d2.promise); TopicFactory.articles({ id: node.dbid, limit: 50 }, function(data) { constructor(data, articleNode, node, props); + d2.resolve(); }); } if ($scope.shown.words) { + var d3 = $q.defer(); + defers.push(d3.promise); TopicFactory.get({ id: node.dbid, limit: 50 }, function(data) { constructor(data.words, wordNode, node, props); + d3.resolve(); }); } + var defer = $q.defer(); + $q.all([]).then(function() { + defer.resolve(); + }); + return defer.promise; }; $scope.loadWord = function(node, props) { + var defers = []; + var d1 = $q.defer(); + d1.resolve(); + defers.push(d1.promise); if ($scope.shown.articles) { + var d2 = $q.defer(); + defers.push(d2.promise); ArticleFactory.query({ word: node.dbid, topicModel: $scope.rootModels.topicModel.id, limit: 50 }, function(data) { constructor(data, articleNode, node, props); + d2.resolve(); }); } if ($scope.shown.topics) { + var d3 = $q.defer(); + defers.push(d3.promise); TopicFactory.query({ word: node.dbid, topicModel: $scope.rootModels.topicModel.id, limit: 50 }, function(data) { constructor(data, topicNode, node, props); + d3.resolve(); }); } + var defer = $q.defer(); + $q.all([]).then(function() { + defer.resolve(); + }); + return defer.promise; }; // on node open $scope.open = function(props) { + if(!props.nodes.length) return; var node = $scope.nodes.get(props.nodes[0]); - $state.transitionTo(node.show, { + + $window.open($state.href(node.show, { id: node.dbid - }); + }, { + absolute: true + }), '_blank'); }; $scope.reset = function() { @@ -569,6 +619,10 @@ }; $scope.$watch('searchNodes', function() { + $scope.search(); + }); + + $scope.search = function() { var nodes = $scope.nodes.get(); var i; var updates = []; @@ -585,11 +639,9 @@ } else { updates.push({ id: nodes[i].id, - color: { - background: '#eee' - }, + color: level3Color, font: { - color: '#ccc' + color: level3Color } }); } @@ -607,14 +659,126 @@ } $scope.data.nodes.update(updates); + }; + + var highlightActive = false; + var lastParams = null; + $scope.neighbourhoodHighlight = function(params, force) { + if(typeof force === 'undefined' && !$scope.highlightEnabled) return; + if(!highlightActive && $scope.searchNodes) return; + var allNodes = $scope.nodes.get({returnType:"Object"}); + var nodeId; + + if (params && params.nodes.length > 0) { + // if something is selected + highlightActive = true; + var i, j; + var selectedNode = params.nodes[0]; + var degrees = 2; + + // mark all nodes as hard to read. + for (nodeId in allNodes) { + allNodes[nodeId].color = level3Color; + if (allNodes[nodeId].hiddenLabel === undefined) { + allNodes[nodeId].hiddenLabel = allNodes[nodeId].label; + allNodes[nodeId].label = undefined; + } + } + var connectedNodes = $scope.graph.getConnectedNodes(selectedNode); + var allConnectedNodes = []; + + // get the second degree nodes + for (i = 1; i < degrees; i++) + for (j = 0; j < connectedNodes.length; j++) + allConnectedNodes = allConnectedNodes.concat($scope.graph.getConnectedNodes(connectedNodes[j])); + + // all second degree nodes get a different color and their label back + for (i = 0; i < allConnectedNodes.length; i++) { + allNodes[allConnectedNodes[i]].color = level2Color; + if (allNodes[allConnectedNodes[i]].hiddenLabel !== undefined) { + allNodes[allConnectedNodes[i]].label = allNodes[allConnectedNodes[i]].hiddenLabel; + allNodes[allConnectedNodes[i]].hiddenLabel = undefined; + } + } + + // all first degree nodes get their own color and their label back + for (i = 0; i < connectedNodes.length; i++) { + allNodes[connectedNodes[i]].color = allNodes[connectedNodes[i]].origColor; + if (allNodes[connectedNodes[i]].hiddenLabel !== undefined) { + allNodes[connectedNodes[i]].label = allNodes[connectedNodes[i]].hiddenLabel; + allNodes[connectedNodes[i]].hiddenLabel = undefined; + } + } + + // the main node gets its own color and its label back. + allNodes[selectedNode].color = allNodes[selectedNode].origColor; + if (allNodes[selectedNode].hiddenLabel !== undefined) { + allNodes[selectedNode].label = allNodes[selectedNode].hiddenLabel; + allNodes[selectedNode].hiddenLabel = undefined; + } + } else { + // nothing is selected + highlightActive = false; + for (nodeId in allNodes) { + allNodes[nodeId].color = allNodes[nodeId].origColor; + if (allNodes[nodeId].hiddenLabel !== undefined) { + allNodes[nodeId].label = allNodes[nodeId].hiddenLabel; + allNodes[nodeId].hiddenLabel = undefined; + } + } + } + + // transform the object into an array + var updateArray = []; + for (nodeId in allNodes) + if (allNodes.hasOwnProperty(nodeId)) + updateArray.push(allNodes[nodeId]); + $scope.nodes.update(updateArray); + + if(!highlightActive && $scope.searchNodes) + $scope.search(); + }; + + $scope.$watch('highlightEnabled', function() { + if(!$scope.highlightEnabled && highlightActive) { + $scope.neighbourhoodHighlight(null, true); + } else if($scope.highlightEnabled && !highlightActive && lastParams) { + $scope.neighbourhoodHighlight(lastParams); + } }); + $scope.togglePhysics = function(t) { + if((typeof t !== 'undefined' && !t) || $scope.physicsEnabled) { + $scope.graph.stopSimulation(); + } else { + $scope.graph.startSimulation(); + } + $scope.physicsEnabled = typeof t !== 'undefined' ? t : !$scope.physicsEnabled; + }; + // create graph var container = document.getElementById("visgraph"); $scope.graph = new vis.Network(container, $scope.data, $scope.options); $scope.graph.on('selectNode', $scope.select); $scope.graph.on('deselectNode', $scope.deselect); $scope.graph.on('doubleClick', $scope.open); + $scope.graph.on('click', function(params) { + lastParams = params; + if(params.nodes.length > 0) { + nodeLoader.then(function() { + $scope.neighbourhoodHighlight(params); + }); + } else { + $scope.neighbourhoodHighlight(params); + } + }); + $scope.graph.on('dragging', function() { + if(!$scope.physicsEnabled) { + $scope.$apply(function() { + $scope.physicsEnabled = true; + }); + } + }); if($stateParams.type) { // if the topic model is not set, the page was refreshed @@ -972,13 +1136,16 @@ $scope.reloadArticles = function() { $scope.loadingArticles = true; + ArticleFactory.query({ skip: ($scope.articlesIndexModels.page - 1) * $scope.articlesIndexModels.limit, limit: $scope.articlesIndexModels.limit, sort: ($scope.articlesIndexModels.sortdir ? '' : '-') + $scope.articlesIndexModels.sortkey, topicModel: $scope.rootModels.topicModel.id, char: $scope.articlesIndexModels.startChar, - contains: $scope.articlesIndexModels.contains + contains: $scope.articlesIndexModels.contains, + from: $scope.articlesIndexModels.fromDate ? $scope.articlesIndexModels.fromDate.getTime() : null, + to: $scope.articlesIndexModels.toDate ? $scope.articlesIndexModels.toDate.getTime() : null }, function(data, headers) { $scope.articles = data; $scope.articlesTotal = headers("V-Total"); @@ -989,7 +1156,7 @@ }); }; - $scope.$watchGroup(['articlesIndexModels.page', 'articlesIndexModels.sortkey', 'articlesIndexModels.sortdir'], function() { + $scope.$watchGroup(['articlesIndexModels.page', 'articlesIndexModels.sortkey', 'articlesIndexModels.sortdir', 'articlesIndexModels.fromDate', 'articlesIndexModels.toDate'], function() { $scope.reloadArticles(); }); @@ -1021,6 +1188,7 @@ id: $stateParams.id }, function(data) { $scope.article = data; + $scope.article.text = $scope.prepareText($scope.article.text); $scope.articleDate = Vipra.formatDate($scope.article.date); $scope.articleCreated = Vipra.formatDateTime($scope.article.created); $scope.articleModified = Vipra.formatDateTime($scope.article.modified); @@ -1114,6 +1282,11 @@ $scope.entities = $scope.allEntities; }; + $scope.prepareText = function(text) { + var base = $state.href('entities', {}, {absolute: true}); + return text.replace(/href="[^>]+>([^<]+)/g, 'href="' + base + '/$1">$1'); + }; + var topicShareChartElement = $('#topic-share'); $scope.highlightSlice = function(id, toggle) { var highcharts = topicShareChartElement.highcharts(); diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index 1c1aec18a9b004241ff60123e1fe69ef20d62987..90613587bcc59b3b51eb037e3ac3c91d41779bff 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -258,7 +258,7 @@ }); $elem.on('dp.change', function(e) { $scope.$apply(function() { - $scope.ngModel = e.date.toDate(); + $scope.ngModel = e.date ? e.date.toDate() : null; }); }); } diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js index a3df6c4ee795a6d38710bea35d0eb77ba2a2dd4f..8a33c7a25a5712cae94c396a2824f492bf29f27a 100644 --- a/vipra-ui/app/js/helpers.js +++ b/vipra-ui/app/js/helpers.js @@ -17,6 +17,10 @@ return new Date(date).toLocaleDateString(); }; + Vipra.formatTime = function(date) { + return new Date(date).toLocaleTimeString(); + }; + Vipra.formatDateTime = function(date) { return new Date(date).toLocaleString(); }; diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index 3638b6066601bd9d5cd8e07fa3efd25870a6d46e..1adb2ddb803705549ab31f4aa2522a3b3096dbd8 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -84,11 +84,8 @@ a:hover { .graph-item { position: absolute; padding: @graph-spacing*2; - background: rgba(128, 128, 128, 0.1); + background: #f5f5f5; border-radius: 5px; - &:hover { - background: rgb(242, 242, 242); - } &.top-left { top: 10px; left: 10px; @@ -139,6 +136,10 @@ a:hover { position: relative; z-index: 2; } + hr { + margin: 5px 0; + border-color: #ccc; + } } td { overflow: hidden; @@ -796,6 +797,10 @@ entity-menu { padding: 0 10px; opacity: 0.8; } + label { + color: white; + font-weight: normal; + } } .latest-items { @@ -966,10 +971,14 @@ entity-menu { .topic-badge { margin-right: 3px; -} - -.text-outline { - text-shadow: 1px 1px 1px #888, 1px -1px 1px #888, -1px 1px 1px #888, -1px -1px 1px #888; + .topic-link { + color: white; + text-decoration: none; + text-shadow: 1px 1px 1px #888, 1px -1px 1px #888, -1px 1px 1px #888, -1px -1px 1px #888; + .title { + padding-right: 2px; + } + } } .sidebar-affix { @@ -995,6 +1004,27 @@ entity-menu { text-decoration: underline; } +.button-pagination { + margin: 5px 0 0 0; + .current, + .current:focus + .current:hover, + .current:active { + background-color: #337ab7; + border-color: #337ab7; + color: #fff; + cursor: default; + } +} + +.date-compact { + display: inline-block; + line-height: .9; + vertical-align: middle; + text-align: right; + margin-top: -2px; +} + [ng\:cloak], [ng-cloak], .ng-cloak { display: none !important; } @@ -1046,24 +1076,12 @@ entity-menu { } } -@media (min-width: 1200px) { - .sidebar-affix { - width: 262.5px; - } -} - -@media (max-width: 1050px) { +@media screen and (max-width: 900px) { .search-form { display: none; } } -@media (min-width: 992px) { - .sidebar-affix { - width: 212.5px; - } -} - @media (max-width: 920px) { .navbar-header { float: none; @@ -1100,9 +1118,20 @@ entity-menu { .collapsing { overflow: hidden!important; } + .date-compact { + small { + display: block; + } + } } -@media screen and (max-width: 900px) { +@media (min-width: 992px) { + .sidebar-affix { + width: 212.5px; + } +} + +@media (max-width: 1050px) { .search-form { display: none; } diff --git a/vipra-ui/bower.json b/vipra-ui/bower.json index ce397d0fa02cc04ae742c9cdbce2128922149401..a84e1b265555a9a383c0d6cc9791944615b32c15 100644 --- a/vipra-ui/bower.json +++ b/vipra-ui/bower.json @@ -34,6 +34,7 @@ "bootbox.js": "bootbox#^4.x", "angular-hotkeys": "chieffancypants/angular-hotkeys#^1.x", "eonasdan-bootstrap-datetimepicker": "^4.x", - "angulartics-google-analytics": "^0.1.4" + "angulartics-google-analytics": "^0.1.4", + "angular-loading-bar": "^0.9.0" } } diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js index db2b270609ec12149f367843095130a826452bc6..abee6c086adfaedaf43631473dd1184f7c5b8631 100644 --- a/vipra-ui/gulpfile.js +++ b/vipra-ui/gulpfile.js @@ -34,7 +34,8 @@ var assets = { 'bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js', 'bower_components/angulartics/dist/angulartics.min.js', 'bower_components/angulartics-google-analytics/dist/angulartics-google-analytics.min.js', - 'bower_components/jquery-layout/source/stable/jquery.layout.min.js' + 'bower_components/jquery-layout/source/stable/jquery.layout.min.js', + 'bower_components/angular-loading-bar/build/loading-bar.min.js' ], css: [ 'bower_components/bootstrap/dist/css/bootstrap.min.css', @@ -43,7 +44,8 @@ var assets = { 'bower_components/nya-bootstrap-select/dist/css/nya-bs-select.min.css', 'bower_components/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css', 'bower_components/angular-hotkeys/build/hotkeys.min.css', - 'bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css' + 'bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css', + 'bower_components/angular-loading-bar/build/loading-bar.min.css' ], fonts: [ 'bower_components/bootstrap/dist/fonts/*',