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 d25cb8d0b7ede3ea6874b37be48a8ab487c21073..acc0312b59c27e5c41eafccbcd882da3863570f7 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 @@ -52,8 +52,7 @@ public class ArticleResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response getArticles(@QueryParam("skip") final Integer skip, @QueryParam("limit") final Integer limit, - @QueryParam("sort") @DefaultValue("date") final String sortBy, @QueryParam("fields") final String fields, - @QueryParam("query") final String query) { + @QueryParam("sort") @DefaultValue("date") final String sortBy, @QueryParam("fields") final String fields) { final ResponseWrapper<List<ArticleFull>> res = new ResponseWrapper<>(); if (res.hasErrors()) @@ -106,6 +105,16 @@ public class ArticleResource { } } + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("{id}/similar") + public Response getSimilar(@PathParam("id") final String id, @QueryParam("skip") final Integer skip, + @QueryParam("limit") final Integer limit, @QueryParam("sort") @DefaultValue("date") final String sortBy, + @QueryParam("fields") final String fields) { + // TODO implement + return null; + } + @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) diff --git a/vipra-ui/app/html/about.html b/vipra-ui/app/html/about.html index 15ba8404ab54343e0e96987d05728e16dacb07ab..bc1175ccd739d7b1d4ef483d20d2c69923e313b8 100644 --- a/vipra-ui/app/html/about.html +++ b/vipra-ui/app/html/about.html @@ -1,4 +1,4 @@ -<div ng-cloak ng-hide="$state.current.name !== 'about'"> +<div class="container" ng-cloak ng-hide="$state.current.name !== 'about'"> <div class="page-header"> <h1>About</h1> </div> diff --git a/vipra-ui/app/html/articles/index.html b/vipra-ui/app/html/articles/index.html index 85f4e3b1447fe2dc409edd3dda43a48e2029fb3d..32d5a302335f71278510da252b7414744139d990 100644 --- a/vipra-ui/app/html/articles/index.html +++ b/vipra-ui/app/html/articles/index.html @@ -1,4 +1,4 @@ -<div ng-cloak ng-hide="$state.current.name !== 'articles'"> +<div class="container" ng-cloak ng-hide="$state.current.name !== 'articles'"> <div class="text-muted"> Found <ng-pluralize count="articlesTotal||0" when="{0:'no articles',1:'1 article',other:'{} articles'}"></ng-pluralize> in the database. diff --git a/vipra-ui/app/html/articles/show.html b/vipra-ui/app/html/articles/show.html index d450d474e51a735a71f31c50764c7b86166fa32f..8f36ebc0ffbb625f7ab2c8bf6147da34546b863b 100644 --- a/vipra-ui/app/html/articles/show.html +++ b/vipra-ui/app/html/articles/show.html @@ -1,4 +1,4 @@ -<div ng-cloak ng-hide="$state.current.name !== 'articles.show'"> +<div class="container" ng-cloak ng-hide="$state.current.name !== 'articles.show'"> <div class="page-header"> <h1 ng-bind="::article.title"></h1> <table class="item-actions"> @@ -16,8 +16,8 @@ </tr> </table> </div> - <h3>Info <hide-link target="#info"/></h3> - <div class="row" id="info"> + <h3>Info</h3> + <div class="row"> <div class="col-md-12"> <table class="table table-bordered table-condensed table-fixed table-infos"> <tbody> @@ -54,8 +54,8 @@ </table> </div> </div> - <h3>Topics <hide-link target="#topics"/></h3> - <div class="row" id="topics"> + <h3>Topics</h3> + <div class="row"> <div class="col-md-5"> <table class="table table-morecondensed" ng-show="article.topics.length > 0"> <tr> diff --git a/vipra-ui/app/html/articles/similar.html b/vipra-ui/app/html/articles/similar.html index dc2fb0618aa6e5cb7ffdbedf895d23cbbd63a2cc..a8d167b5e0d5e71a76f4f70851af6a673712029f 100644 --- a/vipra-ui/app/html/articles/similar.html +++ b/vipra-ui/app/html/articles/similar.html @@ -1,14 +1,16 @@ -<div ng-cloak ng-hide="$state.current.name !== 'articles.show.similar'"> +<div class="container" ng-cloak ng-hide="$state.current.name !== 'articles.show.similar'"> <div class="page-header"> <h1 ng-bind="::article.title"></h1> <table class="item-actions"> <tr> <td> - <a class="btn btn-default" ui-sref="articles.show({id:article.id})"> - Back - </a> + <a class="btn btn-default" ui-sref="^">Back</a> </td> </tr> </table> </div> + <h3>Similar articles</h3> + <div class="row"> + + </div> </div> \ No newline at end of file diff --git a/vipra-ui/app/html/directives/checkbox.html b/vipra-ui/app/html/directives/checkbox.html deleted file mode 100644 index b951ed8d7d15c6926e025ea7ed839e7b5213f2f1..0000000000000000000000000000000000000000 --- a/vipra-ui/app/html/directives/checkbox.html +++ /dev/null @@ -1,2 +0,0 @@ -<input type="checkbox" ng-model="ngModel" style="display:none"> -<span class="glyphicon" ng-class="{'glyphicon-unchecked':!ngModel,'glyphicon-check':ngModel}" ng-click="ngModel=!ngModel"></span> diff --git a/vipra-ui/app/html/explorer.html b/vipra-ui/app/html/explorer.html new file mode 100644 index 0000000000000000000000000000000000000000..961e0f9d437afb9f7d99e5d334a393da120e53c3 --- /dev/null +++ b/vipra-ui/app/html/explorer.html @@ -0,0 +1,21 @@ +<div class="fullsize navpadding explorer" ng-cloak ng-hide="$state.current.name !== 'explorer'"> + <div class="sidebar"> + <button class="btn btn-sm btn-default" ng-click="checkTopics(true)">All</button> + <button class="btn btn-sm btn-default" ng-click="checkTopics(false)">None</button> + <button class="btn btn-sm btn-default" ng-click="checkTopics()">Toggle</button> + <ul class="list-unstyled topic-choice"> + <li ng-repeat="topic in topics"> + <div class="checkbox checkbox-condensed" ng-attr-title="{{::topic.name}}" ng-class="{selected:opts.selectedTopics[topic.id]}"> + <input type="checkbox" ng-model="opts.selectedTopics[topic.id]" ng-attr-id="{{::topic.id}}" ng-model-store="selectedTopics-{{::topic.id}}"> + <label class="check" ng-attr-for="{{::topic.id}}"> + <span class="ellipsis" ng-bind="::topic.name"></span> + </label> + <span class="colorbox" style="background:rgb({{::topic.color.join()}})"></span> + </div> + </li> + </ul> + </div> + <div class="chart"> + + </div> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/index.html b/vipra-ui/app/html/index.html index c95c3c01c88287372cdfb965b85a984281772b44..260825fac09599e9678a420d1a014b80e9eea49d 100644 --- a/vipra-ui/app/html/index.html +++ b/vipra-ui/app/html/index.html @@ -1,4 +1,4 @@ -<div ng-cloak ng-hide="$state.current.name !== 'index'"> +<div class="container" ng-cloak ng-hide="$state.current.name !== 'index'"> <div class="row" ng-hide="search"> <div class="col-md-12 text-center"> <svg class="logo hover heading" viewBox="0 0 200 120"> diff --git a/vipra-ui/app/html/topics/articles.html b/vipra-ui/app/html/topics/articles.html index a165d374ade675cc7087b9b91d80be9f6c20507c..a49f2a6e0780007561b08a8fa5d9b226dfb1a327 100644 --- a/vipra-ui/app/html/topics/articles.html +++ b/vipra-ui/app/html/topics/articles.html @@ -1,4 +1,4 @@ -<div ng-cloak ng-hide="$state.current.name !== 'topics.show.articles'"> +<div class="container" ng-cloak ng-hide="$state.current.name !== 'topics.show.articles'"> <div class="page-header"> <h1 ng-bind-template="Articles for topic '{{::topic.name}}'"></h1> <table class="item-actions"> diff --git a/vipra-ui/app/html/topics/index.html b/vipra-ui/app/html/topics/index.html index c181b8328d97b3c75929bf631a4f8e113204070a..ea87c18abf792c9436fc20672e49798d4f486346 100644 --- a/vipra-ui/app/html/topics/index.html +++ b/vipra-ui/app/html/topics/index.html @@ -1,4 +1,4 @@ -<div ng-cloak ng-hide="$state.current.name !== 'topics'"> +<div class="container" ng-cloak ng-hide="$state.current.name !== 'topics'"> <div class="text-muted"> Found <ng-pluralize count="topicsTotal||0" when="{0:'no topics',1:'1 topic',other:'{} topics'}"></ng-pluralize> in the database. diff --git a/vipra-ui/app/html/topics/show.html b/vipra-ui/app/html/topics/show.html index 292542a1cf3a4962482b16fb73216069ed77654c..6dd31fad7b18a8ce006f64d88a0e3fa1ed7c7ced 100644 --- a/vipra-ui/app/html/topics/show.html +++ b/vipra-ui/app/html/topics/show.html @@ -1,4 +1,4 @@ -<div ng-cloak ng-hide="$state.current.name !== 'topics.show'"> +<div class="container" ng-cloak ng-hide="$state.current.name !== 'topics.show'"> <div class="page-header"> <h1> <div ng-bind="topic.name" ng-hide="isRename"></div> @@ -33,8 +33,8 @@ </tr> </table> </div> - <h3>Info <hide-link target="#info"/></h3> - <div class="row" id="info"> + <h3>Info</h3> + <div class="row"> <div class="col-md-12"> <table class="table table-bordered table-condensed table-fixed table-infos"> <tbody> @@ -55,22 +55,22 @@ </div> </div> <div ng-if="!topic.dynamic"> - <h3>Words <hide-link target="#words"/></h3> - <div class="row" id="words"> + <h3>Words</h3> + <div class="row"> <div class="col-md-12"> <table class="table table-bordered table-condensed table-fixed"> <thead> <tr> - <th sort-by="id" sort-type="wordSort" sort-reverse="wordSortRev"> + <th sort-by="id" ng-model="opts.wordSort" sort-reverse="opts.wordSortRev"> Word </th> - <th sort-by="likeliness" sort-type="wordSort" sort-reverse="wordSortRev"> + <th sort-by="likeliness" ng-model="opts.wordSort" sort-reverse="opts.wordSortRev"> Likeliness </th> </tr> </thead> <tbody> - <tr ng-repeat="word in topic.words | orderBy:wordSort:wordSortRev"> + <tr ng-repeat="word in topic.words | orderBy:opts.wordSort:opts.wordSortRev"> <td> <a ui-sref="words.show({id:word.id})" ng-bind="word.id"></a> </td> @@ -82,27 +82,25 @@ </div> </div> <div ng-if="topic.dynamic"> - <h3>Relevance over time <hide-link target="#relevance"/></h3> - <div id="relevance"> - <div class="row"> - <div class="col-md-12"> - <div class="well well-sm"> - <label class="radio-inline"> - <input type="radio" name="seqstyle" ng-model="seqstyle" value="absolute" ng-model-store="seqStyle" ng-model-default="'absolute'">Absolute - </label> - <label class="radio-inline"> - <input type="radio" name="seqstyle" ng-model="seqstyle" value="relative" ng-model-store="seqStyle">Relative - </label> - </div> + <h3>Relevance over time</h3> + <div class="row"> + <div class="col-md-12"> + <div class="well well-sm"> + <label class="radio-inline"> + <input type="radio" ng-model="opts.seqstyle" value="absolute" ng-model-store="seqStyle" ng-model-default="'absolute'" ng-change="changeSeqStyle()">Absolute + </label> + <label class="radio-inline"> + <input type="radio" ng-model="opts.seqstyle" value="relative" ng-model-store="seqStyle" ng-change="changeSeqStyle()">Relative + </label> </div> </div> - <br> - <div class="row"> - <div class="col-md-12"> - <div class="area-chart" id="topic-seq" highcharts="topicSeq"></div> - </div> + </div> + <br> + <div class="row"> + <div class="col-md-12"> + <div class="area-chart" id="topic-seq" highcharts="topicSeq"></div> </div> - </div> - </div> + </div> + </div> </div> <div ng-cloak ui-view></div> diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html index 666f2d800f038fddf0dcca03b9398098f633b6c8..002b44c155d9b120542d7e82ebe174b131e0ac72 100644 --- a/vipra-ui/app/index.html +++ b/vipra-ui/app/index.html @@ -34,7 +34,7 @@ </head> <body> - <nav class="navbar navbar-default navbar-static-top navbar-breadcrumbs"> + <nav class="navbar navbar-default navbar-static-top"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> @@ -56,10 +56,13 @@ <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="vipra-navbar-collapse-1"> <ul class="nav navbar-nav"> - <li ng-class="{active:$state.includes('articles')}"> + <li ui-sref-active="active"> + <a ui-sref="explorer">Explorer</a> + </li> + <li ui-sref-active="active"> <a ui-sref="articles">Articles</a> </li> - <li ng-class="{active:$state.includes('topics')}"> + <li ui-sref-active="active"> <a ui-sref="topics">Topics</a> </li> </ul> @@ -75,8 +78,7 @@ </div> <!-- /.container-fluid --> </nav> - <div ncy-breadcrumb ng-hide="$state.current.name === 'index'"></div> - <div class="container" ui-view ng-cloak></div> + <div class="main" ui-view ng-cloak></div> </body> </html> diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js index fe5fbd1c5e6ea5f7426b62594ef2b970b6ba7101..515882f4e578d6bc16fc36a1554b5a5da97d825b 100644 --- a/vipra-ui/app/js/app.js +++ b/vipra-ui/app/js/app.js @@ -2,7 +2,7 @@ * Vipra Application * Main application file ******************************************************************************/ -/* globals angular, console, NProgress */ +/* globals angular */ (function() { "use strict"; @@ -10,10 +10,8 @@ var app = angular.module('vipra.app', [ 'ngResource', 'ngSanitize', - 'ngWebSocket', 'ui.router', 'nya.bootstrap.select', - 'ncy-angular-breadcrumb', 'vipra.controllers', 'vipra.directives', 'vipra.factories', @@ -30,28 +28,25 @@ $stateProvider.state('index', { url: '/', templateUrl: 'html/index.html', - controller: 'IndexController', - ncyBreadcrumb: { - label: 'Start' - } + controller: 'IndexController' }); $stateProvider.state('about', { url: '/about', templateUrl: 'html/about.html', - controller: 'AboutController', - ncyBreadcrumb: { - label: 'About' - } + controller: 'AboutController' }); $stateProvider.state('network', { url: '/network/:type/:id', templateUrl: 'html/network.html', - controller: 'NetworkController', - ncyBreadcrumb: { - label: 'Network' - } + controller: 'NetworkController' + }); + + $stateProvider.state('explorer', { + url: '/explorer', + templateUrl: 'html/explorer.html', + controller: 'ExplorerController' }); // states: articles @@ -59,28 +54,19 @@ $stateProvider.state('articles', { url: '/articles', templateUrl: 'html/articles/index.html', - controller: 'ArticlesIndexController', - ncyBreadcrumb: { - label: 'Articles' - } + controller: 'ArticlesIndexController' }); $stateProvider.state('articles.show', { url: '/:id', templateUrl: 'html/articles/show.html', - controller: 'ArticlesShowController', - ncyBreadcrumb: { - label: '{{article.title.ellipsize(30)}}' - } + controller: 'ArticlesShowController' }); $stateProvider.state('articles.show.similar', { url: '/similar', templateUrl: 'html/articles/similar.html', - controller: 'ArticlesSimilarController', - ncyBreadcrumb: { - label: 'Similar' - } + controller: 'ArticlesSimilarController' }); // states: topics @@ -88,34 +74,23 @@ $stateProvider.state('topics', { url: '/topics', templateUrl: 'html/topics/index.html', - controller: 'TopicsIndexController', - ncyBreadcrumb: { - label: 'Topics' - } + controller: 'TopicsIndexController' }); $stateProvider.state('topics.show', { url: '/:id', templateUrl: 'html/topics/show.html', - controller: 'TopicsShowController', - ncyBreadcrumb: { - label: '{{topic.name}}' - } + controller: 'TopicsShowController' }); $stateProvider.state('topics.show.articles', { url: '/articles', templateUrl: 'html/topics/articles.html', - controller: 'TopicsArticlesController', - ncyBreadcrumb: { - label: 'Articles' - } + controller: 'TopicsArticlesController' }); }]); - var loadingTimeout; - app.config(['$httpProvider', function($httpProvider) { $httpProvider.interceptors.push(function($q, $injector, $rootScope) { @@ -123,19 +98,12 @@ $rootScope.loading.requests = ++$rootScope.loading.requests || 1; $rootScope.loading[config.method] = ++$rootScope.loading[config.method] || 1; $rootScope.loading.any = true; - - clearTimeout(loadingTimeout); }; var requestDecrement = function(config) { $rootScope.loading.requests = Math.max(--$rootScope.loading.requests, 0); $rootScope.loading[config.method] = Math.max(--$rootScope.loading[config.method], 0); $rootScope.loading.any = $rootScope.loading.requests > 0; - - loadingTimeout = setTimeout(function() { - console.log('loading done'); - NProgress.done(); - }, 100); }; return { @@ -167,16 +135,6 @@ $rootScope.loading = {}; - $rootScope.$on('$stateChangeStart', function(event, toState) { - console.log('changing state: ' + toState.name); - NProgress.start(); - - loadingTimeout = setTimeout(function() { - console.log('loading done'); - NProgress.done(); - }, 100); - }); - }]); })(); diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index 0658190d7980963ce5c09151d6f80b63ca14010c..bf42c96a245e1d81dd22bd94502f1282ccba027d 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -290,6 +290,36 @@ } ]); + app.controller('ExplorerController', ['$scope', 'TopicFactory', + function($scope, TopicFactory) { + + $scope.opts = {}; + + TopicFactory.query({}, function(data) { + $scope.topics = data; + var colors = Vipra.generateColors($scope.topics.length); + for (var i = 0; i < $scope.topics.length; i++) + $scope.topics[i].color = colors[i]; + }, function(err) { + $scope.errors = err; + }); + + $scope.checkTopics = function(to) { + var toggle = typeof to === 'undefined'; + if(!$scope.opts.selectedTopics) + $scope.opts.selectedTopics = {}; + for(var i = 0, t; i < $scope.topics.length; i++) { + t = $scope.topics[i]; + if(toggle) + $scope.opts.selectedTopics[t.id] = !$scope.opts.selectedTopics[t.id]; + else + $scope.opts.selectedTopics[t.id] = to; + } + }; + + } + ]); + /**************************************************************************** * Article Controllers ****************************************************************************/ @@ -371,6 +401,23 @@ } ]); + /** + * Article Similar route + */ + app.controller('ArticlesSimilarController', ['$scope', '$stateParams', 'ArticleFactory', + function($scope, $stateParams, ArticleFactory) { + + ArticleFactory.get({ + id: $stateParams.id + }, function(data) { + $scope.articles = data; + }, function(err) { + $scope.errors = err; + }); + + } + ]); + /**************************************************************************** * Topic Controllers ****************************************************************************/ @@ -409,8 +456,10 @@ app.controller('TopicsShowController', ['$scope', '$stateParams', '$timeout', 'TopicFactory', function($scope, $stateParams, $timeout, TopicFactory) { - $scope.wordSort = $scope.wordSort || 'likeliness'; - $scope.wordSortRev = typeof $scope.wordSortRev === 'undefined' ? true : $scope.wordSortRev; + $scope.opts = { + wordSort: $scope.wordSort || 'likeliness', + wordSortRev: typeof $scope.wordSortRev === 'undefined' ? true : $scope.wordSortRev + }; TopicFactory.get({ id: $stateParams.id @@ -418,36 +467,6 @@ $scope.topic = data; $scope.topicCreated = Vipra.formatDateTime($scope.topic.created); $scope.topicModified = Vipra.formatDateTime($scope.topic.modified); - - if($scope.topic.dynamic) { - var relevances = []; - for(var i = 0, s; i < $scope.topic.sequences.length; i++) { - s = $scope.topic.sequences[i]; - relevances.push([new Date(s.window.startDate).getTime(), s.relevance]); - } - $scope.topicSeq = { - chart: { - type: 'areaspline', - zoomType: 'x', - spacingLeft: 0, - spacingRight: 0 - }, - title: { text: '' }, - xAxis: { - type: 'datetime', - title: { text: '' } - }, - yAxis: { title: { text: 'Relevance' } }, - tooltip: { - headerFormat: '', - pointFormat: '{point.x:%Y}: {point.y:.4f}' - }, - legend: { enabled: false }, - credits: { enabled: false }, - plotOptions: { areaspline: { fillOpacity: 0.5 } }, - series: [ { name: $scope.topic.name, data: relevances } ] - }; - } }, function(err) { $scope.errors = err; }); @@ -466,8 +485,6 @@ TopicFactory.update({ id: $scope.topic.id }, $scope.topic, function(data) { $scope.topic = data; $scope.isRename = false; - }, function(errors) { - $scope.renameErrors = Vipra.getErrors(errors); }, function(err) { $scope.errors = err; }); @@ -483,6 +500,60 @@ $event.preventDefault(); } }; + + $scope.$watch('opts.seqstyle', function(style) { + if (!style) return; + console.log('redraw relevance chart'); + var min = 0, + max = 0, + relevances = []; + // create series + for (var i = 0, sequence, relevance; i < $scope.topic.sequences.length; i++) { + sequence = $scope.topic.sequences[i]; + relevance = $scope.opts.seqstyle === 'relative' ? sequence.relevanceChange : sequence.relevance; + relevances.push([new Date(sequence.window.startDate).getTime(), relevance]); + if (relevances[i][1] > relevances[max][1]) + max = i; + else if (relevances[i][1] < relevances[min][1]) + min = i; + } + + // mark maximum and minimum value + relevances[max] = { + x: relevances[max][0], + y: relevances[max][1], + marker: { symbol: 'triangle', fillColor: '#f00' } + }; + relevances[min] = { + x: relevances[min][0], + y: relevances[min][1], + marker: { symbol: 'triangle-down', fillColor: '#f00' } + }; + + // highcharts configuration + $scope.topicSeq = { + chart: { + type: 'areaspline', + zoomType: 'x', + spacingLeft: 0, + spacingRight: 0 + }, + title: { text: '' }, + xAxis: { + type: 'datetime', + title: { text: '' } + }, + yAxis: { title: { text: 'Relevance' } }, + tooltip: { + headerFormat: '', + pointFormat: '{point.x:%Y}: {point.y:.4f}' + }, + legend: { enabled: false }, + credits: { enabled: false }, + plotOptions: { areaspline: { fillOpacity: 0.5 } }, + series: [{ name: $scope.topic.name, data: relevances }] + }; + }); } ]); diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index 23f4deaeb77601021f90269cf859b063161b01f1..d790879ba8a00a2fbd85f584fb80f8ac96b4fbf4 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -65,43 +65,6 @@ }; }); - app.directive('hideLink', ['$state', 'Store', function($state, Store) { - return { - scope: { - target: '@', - store: '@' - }, - replace: true, - template: '<a class="hide-link" href="#" ng-bind="text" ng-click="change($event)"></a>', - link: function($scope) { - var target = $($scope.target); - if (!target.length) return; - var store = $scope.store !== 'false'; - var name = 'hidelink-' + $scope.target; - if (store) { - var visible = Store(name); - if (visible !== null && typeof visible !== 'undefined') - target.toggle(visible); - } - - var setText = function(b) { - $scope.text = b ? 'hide' : 'show'; - }; - - $scope.change = function(e) { - e.preventDefault(); - var visible = !target.is(':visible'); - setText(visible); - target.slideToggle(); - if (store) - Store(name, visible); - }; - - setText(target.is(':visible')); - } - }; - }]); - app.directive('toggleLink', function() { return { link: function($scope, $elem, $attrs) { @@ -117,11 +80,16 @@ restrict: 'A', require: 'ngModel', link: function($scope, $elem, $attrs, $ctrl) { - if (!$attrs.ngModelStore) { - console.log("no store key given"); + if (!$attrs.ngModel) { + console.log('no model given'); + return; + } + if (typeof $attrs.ngModelStore === 'undefined') { + console.log('no store key given'); return; } - var value = Store($attrs.ngModelStore); + var key = $attrs.ngModelStore; + var value = Store(key); if (value !== null) { $ctrl.$setViewValue(value); $ctrl.$render(); @@ -129,8 +97,8 @@ $ctrl.$setViewValue($scope.$eval($attrs.ngModelDefault)); $ctrl.$render(); } - $ctrl.$viewChangeListeners.push(function() { - Store($attrs.ngModelStore, $ctrl.$viewValue); + $scope.$watch($attrs.ngModel, function() { + Store(key, $ctrl.$viewValue); }); } }; @@ -179,27 +147,17 @@ }; }); - app.directive('bsCheckbox', function() { - return { - scope: { - ngModel: '=' - }, - restrict: 'E', - templateUrl: 'html/directives/checkbox.html' - }; - }); - app.directive('sortBy', ['Store', function(Store) { return { scope: { sortBy: '@', - sortType: '=', + ngModel: '=', sortReverse: '=', - storeKey: '@sortType' + storeKey: '@ngModel' }, 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>', + template: '<span ng-click="click()"><ng-transclude/><span ng-show="ngModel == 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'); @@ -208,14 +166,14 @@ var value = Store($scope.storeKey); if (value) { value = value.split(','); - $scope.sortType = value[0]; + $scope.ngModel = value[0]; $scope.sortReverse = value[1] === 'true'; } $scope.sortBy = $attrs.sortBy; $scope.click = function() { - $scope.sortType = $scope.sortBy; + $scope.ngModel = $scope.sortBy; $scope.sortReverse = !$scope.sortReverse; - Store($scope.storeKey, $scope.sortType + ',' + $scope.sortReverse); + Store($scope.storeKey, $scope.ngModel + ',' + $scope.sortReverse); }; } }; diff --git a/vipra-ui/app/js/factories.js b/vipra-ui/app/js/factories.js index 0e754c75690eeb63b5369dd14174f1fa182e3177..b745096610a111a512ce565689efbb72070ce12d 100644 --- a/vipra-ui/app/js/factories.js +++ b/vipra-ui/app/js/factories.js @@ -10,7 +10,9 @@ var app = angular.module('vipra.factories', []); app.factory('ArticleFactory', ['$resource', function($resource) { - return $resource(Vipra.config.restUrl + '/articles/:id'); + return $resource(Vipra.config.restUrl + '/articles/:id', {}, { + similar: { isArray: true, url: Vipra.config.restUrl + '/articles/:id/similar' } + }); }]); app.factory('TopicFactory', ['$resource', function($resource) { @@ -103,25 +105,4 @@ }; }]); - app.service('WebSocketService', ['$websocket', '$state', function($websocket, $state) { - var socket = $websocket(Vipra.config.websocketUrl), - callback = null; - - socket.onMessage(function(message) { - var data = JSON.parse(message.data); - if (callback) - callback(data); - }); - - this.send = function(type, data) { - socket.send(JSON.stringify({ type: type, data: JSON.stringify(data) })); - }; - - this.receive = function(fn) { - callback = fn; - }; - - this.send(1, { state: $state.current.name }); - }]); - })(); diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js index b8362ea044ce896522db612c6c6b92932c7c69bc..0898a924c40c7fe542c34652a2c0ee64bcd96996 100644 --- a/vipra-ui/app/js/helpers.js +++ b/vipra-ui/app/js/helpers.js @@ -36,13 +36,96 @@ return 'id' + Math.random().toString(36).substring(7); }; - Vipra.getErrors = function(errors) { - var html = []; - if (errors && errors.length) { - for (var i = 0; i < errors.length; i++) - html.push('<strong>' + errors[i].title + '</strong>: ' + errors[i].detail); + /** + * taken from + * https://jsfiddle.net/aldass/9f0yadfy/ + */ + Vipra.generateColors = function(t) { + t = parseInt(t); + if (t < 2) + throw new Error("'t' must be greater than 1."); + + // distribute the colors evenly on + // the hue range (the 'H' in HSV) + var i = 360 / (t - 1); + + // hold the generated colors + var r = []; + var sv = 70; + for (var x = 0; x < t; x++) { + // alternate the s, v for more + // contrast between the colors. + sv = sv > 90 ? 70 : sv + 10; + r.push(Vipra.hsvToRgb(i * x, sv, sv)); } - return html.join('<br>'); + return r; + }; + + /** + * HSV to RGB color conversion + * + * H runs from 0 to 360 degrees + * S and V run from 0 to 100 + * + * Ported from the excellent java algorithm by Eugene Vishnevsky at: + * http://www.cs.rit.edu/~ncs/color/t_convert.html + */ + Vipra.hsvToRgb = function(h, s, v) { + var r, g, b; + var i; + var f, p, q, t; + + h = Math.max(0, Math.min(360, h)); + s = Math.max(0, Math.min(100, s)); + v = Math.max(0, Math.min(100, v)); + s /= 100; + v /= 100; + + if (s === 0) { + r = g = b = v; + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + } + + h /= 60; + i = Math.floor(h); + f = h - i; + p = v * (1 - s); + q = v * (1 - s * f); + t = v * (1 - s * (1 - f)); + + switch (i) { + case 0: + r = v; + g = t; + b = p; + break; + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + default: + r = v; + g = p; + b = q; + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; }; /** diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index 9c8729b4ab4187641c7c46c7c384aa77b5440e4c..3613090040c9e81e772571fb81fba09265b60ceb 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -106,7 +106,7 @@ ul.dashed { .graph-legend { position: absolute; - top: 97px; + top: 60px; left: 10px; font-weight: bold; padding: 10px; @@ -132,10 +132,6 @@ ul.dashed { display: block; } -.hide-link { - font-size: 12px; -} - .noselect { -webkit-touch-callout: none; -webkit-user-select: none; @@ -154,7 +150,7 @@ ul.dashed { } .navpadding { - padding-top: 87px; + padding-top: 51px; } .form-control-inline { @@ -225,10 +221,6 @@ ul.dashed { margin-top: -2px; } -.navbar-breadcrumbs { - margin-bottom: 0; -} - .topic-share { width: 75px; } @@ -247,10 +239,6 @@ revolve-select, [revolve-select] { display: none; } -bs-checkbox { - cursor: pointer; -} - .logo { &.hover { .logo-shape { @@ -270,6 +258,78 @@ bs-checkbox { } } +.vis-network { + outline: none; +} + +.fullheight { + height: 100%; + + .row > * { + height: 100%; + } +} + +.checkbox-condensed { + margin-top: 0; + margin-bottom: 0; +} + +.ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; + width: 100%; + vertical-align: middle; +} + +.explorer { + display: flex; + flex-direction: row; + + .sidebar { + flex: 0 0 250px; + width: 250px; + background: #f9f9f9; + height: 100%; + padding: 5px 0 5px 5px; + } + + .chart { + flex: 1 0 0; + } + + .topic-choice { + padding-top: 5px; + + label { + padding-right: 15px; + } + } + + .colorbox { + display: inline-block; + visibility: hidden; + width: 10px; + height: 20px; + vertical-align: middle; + position: absolute; + right: 0; + top: 0; + } + + .checkbox:hover .colorbox, + .selected .colorbox { + visibility: visible; + } +} + +input[type=checkbox], +.checkbox label::before { + cursor: pointer; +} + @-moz-keyframes spin { 100% { -moz-transform: rotateY(360deg); } } @-webkit-keyframes spin { 100% { -webkit-transform: rotateY(360deg); } } @keyframes spin { 100% { -webkit-transform: rotateY(360deg); transform:rotateY(360deg); } } diff --git a/vipra-ui/bower.json b/vipra-ui/bower.json index d1495f65d49fb7395fcbf9fad62dcccbbcf10930..c484899bfc46e5503535d8c4ab4646278594e2e6 100644 --- a/vipra-ui/bower.json +++ b/vipra-ui/bower.json @@ -17,18 +17,17 @@ "tests" ], "dependencies": { - "bootstrap": "~3.3.6", - "jquery": "^2.2.0", - "angular": "^1.5.0", - "angular-resource": "^1.5.0", + "bootstrap": "~3.x", + "jquery": "^2.x", + "angular": "^1.x", + "angular-resource": "^1.x", "angular-ui-router": "^0.2.17", - "angular-sanitize": "^1.5.0", - "highcharts": "^4.2.2", - "vis": "https://github.com/almende/vis.git#^4.14.0", - "angular-websocket": "^1.0.14", - "moment": "^2.11.2", - "nya-bootstrap-select": "^2.1.3", - "nprogress": "^0.2.0", - "angular-breadcrumb": "^0.4.1" + "angular-sanitize": "^1.x", + "highcharts": "^4.x", + "vis": "^4.x", + "moment": "^2.x", + "nya-bootstrap-select": "^2.x", + "font-awesome": "^4.x", + "awesome-bootstrap-checkbox": "^0.3.7" } } diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js index 56965ffd942a7b332f016a201512e6c25a05c3fd..3cfe64cac2daa164bce78064dfa23023e01ff701 100644 --- a/vipra-ui/gulpfile.js +++ b/vipra-ui/gulpfile.js @@ -17,23 +17,22 @@ 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-websocket/angular-websocket.min.js', - 'bower_components/angular-breadcrumb/dist/angular-breadcrumb.min.js', 'bower_components/bootstrap/dist/js/bootstrap.min.js', 'bower_components/highcharts/highcharts.js', 'bower_components/vis/dist/vis.min.js', - 'bower_components/nprogress/nprogress.js', 'bower_components/moment/min/moment.min.js', 'bower_components/nya-bootstrap-select/dist/js/nya-bs-select.min.js' ], css: [ 'bower_components/bootstrap/dist/css/bootstrap.min.css', + 'bower_components/font-awesome/css/font-awesome.min.css', 'bower_components/vis/dist/vis.min.css', - 'bower_components/nprogress/nprogress.css', - 'bower_components/nya-bootstrap-select/dist/css/nya-bs-select.min.css' + 'bower_components/nya-bootstrap-select/dist/css/nya-bs-select.min.css', + 'bower_components/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css' ], fonts: [ - 'bower_components/bootstrap/dist/fonts/*' + 'bower_components/bootstrap/dist/fonts/*', + 'bower_components/font-awesome/fonts/*' ], img: [] }; @@ -50,11 +49,11 @@ gulp.task('less', function() { gulp.task('js', function() { gulp.src('app/js/**/*.js') - .pipe(sourcemaps.init()) + //.pipe(sourcemaps.init()) .pipe(concat('app.js')) .pipe(ngannotate()) - .pipe(uglify()) - .pipe(sourcemaps.write('.')) + //.pipe(uglify()) + //.pipe(sourcemaps.write('.')) .pipe(gulp.dest('public/js')); });