diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..7e54495609aa7ca7c3411e8527e7d7f3ec686e21 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/vipra-backend/pom.xml b/vipra-backend/pom.xml index 6bc93255b326a84642fbe3945b46005cf3e7d7d4..73c6ff9eda36c317668dfffb5bac585ca3502218 100644 --- a/vipra-backend/pom.xml +++ b/vipra-backend/pom.xml @@ -15,7 +15,7 @@ <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <maven.build.timestamp.format>yyMMdd_HHmm</maven.build.timestamp.format> - <buildDate>${maven.build.timestamp}</buildDate> + <buildDate>${maven.build.timestamp}</buildDate> <jerseyVersion>2.22.1</jerseyVersion> <jettyVersion>9.3.6.v20151106</jettyVersion> <servletVersion>3.1.0</servletVersion> @@ -135,6 +135,11 @@ <git-SHA-1>${buildNumber}</git-SHA-1> </manifestEntries> </archive> + <webResources> + <resource> + <directory>src/main/public</directory> + </resource> + </webResources> </configuration> </plugin> diff --git a/vipra-ui/app/html/explorer.html b/vipra-ui/app/html/explorer.html index d41fb5268facc705106dd1d7b83c6cf0eb024218..0ddaf29a78e5057110c2d826d367f67812013ba5 100644 --- a/vipra-ui/app/html/explorer.html +++ b/vipra-ui/app/html/explorer.html @@ -12,14 +12,18 @@ <a class="btn btn-sm btn-default" ng-model="opts.sorttopics" bs-radio="'fallingRelevance'" title="Sort by falling relevance">↘</a> <a class="btn btn-sm btn-default" ng-model="opts.sorttopics" bs-radio="'risingRelevance'" title="Sort by rising relevance">↗</a> <a class="btn btn-sm btn-default" ng-model="opts.sorttopics" bs-radio="'risingDecayRelevance'" title="Sort by rising relevance with decay">↝</a> + <a class="btn btn-sm btn-link" ng-click="opts.sortdir=!opts.sortdir"> + <i class="fa" ng-class="{'fa-sort-amount-desc':opts.sortdir,'fa-sort-amount-asc':!opts.sortdir}"></i> + </a> </div> <div class="btn-group btn-group-justified"> <input type="text" class="form-control" ng-model="search.$" placeholder="Filter"> <span class="glyphicon glyphicon-remove-circle searchclear" ng-click="search=''"></span> </div> <ul class="list-unstyled topic-choice"> - <li ng-repeat="topic in topics | orderBy:opts.sorttopics | filter:search"> - <div class="checkbox checkbox-condensed" ng-class="{selected:opts.selectedTopics[topic.id]}" bs-popover popover-title="{{::topic.name}}" popover-html="{{::topicPopover(topic)}}"> + <li ng-repeat="topic in topics | orderBy:opts.sorttopics:opts.sortdir | filter:search"> + <div class="checkbox checkbox-condensed" ng-class="{selected:opts.selectedTopics[topic.id]}" bs-popover popover-title="{{::topic.name}}" popover-template="partials/topic-popover.html"> + <span class="valuebar" ng-style="{width:topicCurrValue(topic)}"></span> <input type="checkbox" ng-model="opts.selectedTopics[topic.id]" ng-attr-id="{{::topic.id}}"> <label class="check" ng-attr-for="{{::topic.id}}"> <span class="ellipsis" ng-bind="::topic.name"></span> @@ -55,4 +59,4 @@ <div class="message text-muted" ng-show="!topicsSelected">No topic selected</div> </div> </div> -</div> \ No newline at end of file +</div> diff --git a/vipra-ui/app/html/partials/topic-popover.html b/vipra-ui/app/html/partials/topic-popover.html new file mode 100644 index 0000000000000000000000000000000000000000..a1708a91869d7242cd1ef896fa40bd81b0b48746 --- /dev/null +++ b/vipra-ui/app/html/partials/topic-popover.html @@ -0,0 +1,18 @@ +<table class="table table-bordered table-condensed table-nomargin"> + <tbody> + <tr> + <th class="text-center">μ</th> + <th class="text-center">σ</th> + <th class="text-center">↘</th> + <th class="text-center">↗</th> + <th class="text-center">↝</th> + </tr> + <tr> + <td class="text-center" ng-bind-template="{{::topic.avgRelevance.toFixed(2)}}" ng-class="{'active text-primary':opts.sorttopics==='avgRelevance'}"></td> + <td class="text-center" ng-bind-template="{{::topic.varRelevance.toFixed(2)}}" ng-class="{'active text-primary':opts.sorttopics==='varRelevance'}"></td> + <td class="text-center" ng-bind-template="{{::topic.fallingRelevance.toFixed(2)}}" ng-class="{'active text-primary':opts.sorttopics==='fallingRelevance'}"></td> + <td class="text-center" ng-bind-template="{{::topic.risingRelevance.toFixed(2)}}" ng-class="{'active text-primary':opts.sorttopics==='risingRelevance'}"></td> + <td class="text-center" ng-bind-template="{{::topic.risingDecayRelevance.toFixed(2)}}" ng-class="{'active text-primary':opts.sorttopics==='risingDecayRelevance'}"></td> + </tr> + </tbody> +</table> diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js index 35b6b0e1b9428b47315a3fc535caaf5cf3871108..6eb5c2612d75fbd184bb0e37642277f0d4e993ef 100644 --- a/vipra-ui/app/js/app.js +++ b/vipra-ui/app/js/app.js @@ -3,7 +3,7 @@ * Main application file ******************************************************************************/ /* globals angular */ -(function() { +(function () { "use strict"; @@ -19,7 +19,7 @@ 'vipra.templates' ]); - app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { + app.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise('/'); @@ -85,38 +85,38 @@ }]); - app.config(['$httpProvider', function($httpProvider) { + app.config(['$httpProvider', function ($httpProvider) { - $httpProvider.interceptors.push(function($q, $injector, $rootScope) { - var requestIncrement = function(config) { + $httpProvider.interceptors.push(function ($q, $injector, $rootScope) { + var requestIncrement = function (config) { $rootScope.loading.requests = ++$rootScope.loading.requests || 1; $rootScope.loading[config.method] = ++$rootScope.loading[config.method] || 1; $rootScope.loading.any = true; }; - var requestDecrement = function(config) { + 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; }; return { - request: function(config) { + request: function (config) { requestIncrement(config); return config; }, - requestError: function(rejection) { + requestError: function (rejection) { requestDecrement(rejection.config); return $q.reject(rejection); }, - response: function(response) { + response: function (response) { requestDecrement(response.config); return response; }, - responseError: function(rejection) { + responseError: function (rejection) { requestDecrement(rejection.config); return $q.reject(rejection); } @@ -125,7 +125,7 @@ }]); - app.run(['$rootScope', function($rootScope) { + app.run(['$rootScope', function ($rootScope) { $rootScope.loading = {}; diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index a7825d391213d1cf126f9d4447d9d95d3cb62b64..c434d3622062edae6eaaa934209f1aa009f76860 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -295,8 +295,15 @@ } ]); - app.controller('ExplorerController', ['$scope', 'TopicFactory', - function($scope, TopicFactory) { + app.controller('ExplorerController', ['$scope', '$templateCache', '$compile', 'TopicFactory', + function($scope, $templateCache, $compile, TopicFactory) { + + var avgMax = 0, + varMax = 0, + fallingMax = 0, + risingMax = 0, + risingDecayMax = 0, + risingDecayMin = 0; $scope.opts = { sorttopics: 'name', @@ -310,8 +317,18 @@ }, function(data) { $scope.topics = data; var colors = randomColor({ count: $scope.topics.length, seed: 1 }); - for (var i = 0; i < $scope.topics.length; i++) - $scope.topics[i].color = colors[i]; + for (var i = 0, t; i < $scope.topics.length; i++) { + t = $scope.topics[i]; + t.color = colors[i]; + avgMax = Math.max(t.avgRelevance, avgMax); + varMax = Math.max(t.varRelevance, varMax); + fallingMax = Math.max(t.fallingRelevance, fallingMax); + risingMax = Math.max(t.risingRelevance, risingMax); + risingDecayMax = Math.max(t.risingDecayRelevance, risingDecayMax); + risingDecayMin = Math.max(t.risingDecayRelevance, risingDecayMin); + } + risingDecayMin = Math.abs(risingDecayMin); + risingDecayMax = risingDecayMin + Math.abs(risingDecayMax); }, function(err) { $scope.errors = err; }); @@ -371,13 +388,26 @@ }); }; - $scope.topicPopover = function(topic) { - return "<table class=\"table table-bordered table-condensed table-nomargin\"><tbody><tr><td>μ</td><td>σ</td><td>↘</td><td>↗</td><td>↝</td></tr><tr><td>" + - topic.avgRelevance.toFixed(2) + "</td><td>" + - topic.varRelevance.toFixed(2) + "</td><td>" + - topic.fallingRelevance.toFixed(2) + "</td><td>" + - topic.risingRelevance.toFixed(2) + "</td><td>" + - topic.risingDecayRelevance.toFixed(2) + "</td></tr></tbody></table>"; + $scope.topicCurrValue = function(topic) { + var percent = 0; + switch ($scope.opts.sorttopics) { + case 'avgRelevance': + percent = topic.avgRelevance / avgMax; + break; + case 'varRelevance': + percent = topic.varRelevance / varMax; + break; + case 'fallingRelevance': + percent = topic.fallingRelevance / fallingMax; + break; + case 'risingRelevance': + percent = topic.risingRelevance / risingMax; + break; + case 'risingDecayRelevance': + percent = (topic.risingDecayRelevance + risingDecayMin) / risingDecayMax; + break; + } + return (percent * 100) + '%'; }; $scope.$watchCollection('opts.selectedTopics', $scope.redrawGraph); diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index c7663debe92f0b03303f8c9b2955717e6e071c6a..06abf12bfaea98d7f6b88bf68ef1a195ed9b389f 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -120,23 +120,30 @@ }; }); - app.directive('bsPopover', function () { - return { - restrict: 'A', - link: function ($scope, $elem, $attrs) { - $scope.label = $attrs.popoverLabel; - $($elem).popover({ - animation: ($attrs.popoverAnimation === 'true') || true, - container: $attrs.popoverContainer || 'body', - content: $attrs.popoverHtml, - delay: parseInt($attrs.popoverDelay || 1000, 10), - html: $attrs.popoverHtml || true, - placement: $attrs.popoverPlacement || 'right', - title: $attrs.popoverTitle, - trigger: $attrs.popoverTrigger || 'hover' - }); - } - }; - }); + app.directive('bsPopover', ['$templateCache', '$compile', + function($templateCache, $compile) { + return { + restrict: 'A', + link: function($scope, $elem, $attrs) { + var content = $attrs.popoverHtml; + if ($attrs.popoverTemplate) { + var template = $templateCache.get($attrs.popoverTemplate); + content = $compile(template)($scope); + } + $scope.label = $attrs.popoverLabel; + $elem.popover({ + animation: ($attrs.popoverAnimation === 'true') || true, + container: $attrs.popoverContainer || 'body', + content: content, + delay: parseInt($attrs.popoverDelay || 1000, 10), + html: $attrs.popoverHtml || true, + placement: $attrs.popoverPlacement || 'right', + title: $attrs.popoverTitle, + trigger: $attrs.popoverTrigger || 'hover' + }); + } + }; + } + ]); })(); diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js index b0e780f9b03cf87783047edf359cf7a1db246eb8..b78028fc74a550dea22afc38a8b6f552fa2488f3 100644 --- a/vipra-ui/app/js/helpers.js +++ b/vipra-ui/app/js/helpers.js @@ -2,7 +2,7 @@ * Vipra Application * Helpers & Polyfills ******************************************************************************/ -/* globals Vipra */ +/* globals Vipra, $ */ (function() { "use strict"; diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index a87797aa86ad2c6a2219d0985ccdd622e56fe0cd..b987219ad9a9b08f8695ca34eeef85883cfe02ee 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -1,5 +1,4 @@ @basecolor: #007aa3; - html { position: relative; min-height: 100%; @@ -10,7 +9,7 @@ body { } a:hover { - cursor:pointer; + cursor: pointer; } .heading { @@ -20,12 +19,10 @@ a:hover { .search-results { padding: 15px; - .search-result { - &:not(:last-child) { + &:not(: last-child) { margin-bottom: 20px; } - a { font-size: 1.5rem; } @@ -44,10 +41,8 @@ a:hover { } } } - .navbar-brand { padding: 10px; - svg { height: 30px; } @@ -73,21 +68,17 @@ a:hover { left: 10px; font-weight: bold; padding: 10px; - background: rgba(255,255,255,0.75); + background: rgba(255, 255, 255, 0.75); border-radius: 5px; - label { margin: 0; } - label + label { padding-left: 5px; } - .checkbox:first-child { margin-top: 0; } - .checkbox:last-child { margin-bottom: 0; } @@ -119,7 +110,6 @@ a:hover { left: 0; right: 0; bottom: 0; - &.navpadding { top: 51px; } @@ -138,7 +128,6 @@ a:hover { td { vertical-align: middle; } - td + td { padding-left: 10px; } @@ -159,11 +148,9 @@ a:hover { .nya-bs-condensed { width: auto !important; margin-top: -2px; - .dropdown-toggle { padding: 0px 25px 0px 12px; } - .dropdown-menu li a { padding: 2px 12px; } @@ -178,16 +165,16 @@ a:hover { .logo-shape { transition: all .8s; &:hover { - transition: all .1s; opacity:.1; + transition: all .1s; + opacity: .1; } } } - &.animate { .logo-shape { - -webkit-animation:fadeRotate 2s ease-in-out infinite; - -moz-animation:fadeRotate 2s ease-in-out infinite; - animation:fadeRotate 2s ease-in-out infinite; + -webkit-animation: fadeRotate 2s ease-in-out infinite; + -moz-animation: fadeRotate 2s ease-in-out infinite; + animation: fadeRotate 2s ease-in-out infinite; } } } @@ -213,14 +200,28 @@ a:hover { .colorbox { display: inline-block; width: 10px; - height: 20px; + height: 100%; vertical-align: middle; float: right; + border-radius: 3px; +} + +.valuebar { + @valuebg: #e3e3e3; + display: inline-block; + position: absolute; + top: 0; + left: 0; + height: 100%; + background: @valuebg; + *:hover > & { + background: darken(@valuebg, 5%); + } } .explorer { @sidebar-width: 300px; - + @sidebar-padding: 5px; .sidebar { position: absolute; top: 0; @@ -229,13 +230,11 @@ a:hover { width: @sidebar-width; background: #f9f9f9; height: 100%; - padding: 5px; - + padding: @sidebar-padding; > * + * { margin-top: 5px; } } - .center { position: absolute; top: 0; @@ -243,40 +242,40 @@ a:hover { right: 0; bottom: 0; } - .chart { padding: 5px; } - .topbar { background: #f9f9f9; width: 100%; padding: 5px; vertical-align: middle; } - .sequence { flex: 1 0 0; } - .topic-choice { position: absolute; top: 110px; bottom: 0; overflow-y: auto; - + width: @sidebar-width - (2 * @sidebar-padding); + margin-bottom: 0; label { padding-right: 15px; + &:before, + &:after { + margin-top: 2px; + margin-left: -18px; + } } } - .colorbox { position: absolute; right: 0; top: 0; visibility: hidden; } - .checkbox:hover .colorbox, .selected .colorbox { visibility: visible; @@ -338,10 +337,57 @@ input[type=checkbox], height: 100%; } -@-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); } } +@-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); + } +} -@-moz-keyframes fadeRotate { 0% { opacity: 1; } 35% { opacity: .1; } 70% { opacity: 1; } } -@-webkit-keyframes fadeRotate { 0% { opacity: 1; } 35% { opacity: .1; } 70% { opacity: 1; } } -@keyframes fadeRotate { 0% { opacity: 1; } 35% { opacity: .1; } 70% { opacity: 1; } } \ No newline at end of file +@-moz-keyframes fadeRotate { + 0% { + opacity: 1; + } + 35% { + opacity: .1; + } + 70% { + opacity: 1; + } +} + +@-webkit-keyframes fadeRotate { + 0% { + opacity: 1; + } + 35% { + opacity: .1; + } + 70% { + opacity: 1; + } +} + +@keyframes fadeRotate { + 0% { + opacity: 1; + } + 35% { + opacity: .1; + } + 70% { + opacity: 1; + } +} diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js index a6d5e5c8bab86626b58ff6d60c62e6fe782e259f..95593c216fe5901672b570c3fbbe2b4a2268de3e 100644 --- a/vipra-ui/gulpfile.js +++ b/vipra-ui/gulpfile.js @@ -1,14 +1,14 @@ /* jshint ignore: start */ var gulp = require('gulp'), - less = require('gulp-less'), - concat = require('gulp-concat'), - uglify = require('gulp-uglify'), - cssnano = require('gulp-cssnano'), - webserver = require('gulp-webserver'), - sourcemaps = require('gulp-sourcemaps'), - ngannotate = require('gulp-ng-annotate'), - templatecache = require('gulp-angular-templatecache'); + less = require('gulp-less'), + concat = require('gulp-concat'), + uglify = require('gulp-uglify'), + cleancss = require('gulp-clean-css'), + webserver = require('gulp-webserver'), + sourcemaps = require('gulp-sourcemaps'), + ngannotate = require('gulp-ng-annotate'), + templatecache = require('gulp-angular-templatecache'); var assets = { js: [ @@ -41,60 +41,60 @@ var assets = { gulp.task('less', function() { gulp.src('app/less/**/*.less') - .pipe(sourcemaps.init()) - .pipe(concat('app.css')) - .pipe(less()) - .pipe(cssnano()) - .pipe(sourcemaps.write('.')) - .pipe(gulp.dest('public/css')); + .pipe(sourcemaps.init()) + .pipe(concat('app.css')) + .pipe(less()) + .pipe(cleancss()) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('public/css')); }); gulp.task('js', function() { gulp.src('app/js/**/*.js') - //.pipe(sourcemaps.init()) - .pipe(concat('app.js')) - .pipe(ngannotate()) - //.pipe(uglify()) - //.pipe(sourcemaps.write('.')) - .pipe(gulp.dest('public/js')); + .pipe(sourcemaps.init()) + .pipe(concat('app.js')) + .pipe(ngannotate()) + //.pipe(uglify()) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('public/js')); }); gulp.task('html', function() { gulp.src('app/index.html') - .pipe(gulp.dest('public')); + .pipe(gulp.dest('public')); gulp.src(['app/html/**/*.html']) - .pipe(gulp.dest('public/html')); + .pipe(gulp.dest('public/html')); gulp.src(['app/html/**/*.html']) - .pipe(templatecache({ - module: 'vipra.templates', - standalone: true - })) - .pipe(ngannotate()) - .pipe(uglify()) - .pipe(gulp.dest('public/js')); + .pipe(templatecache({ + module: 'vipra.templates', + standalone: true + })) + .pipe(ngannotate()) + .pipe(uglify()) + .pipe(gulp.dest('public/js')); }); gulp.task('img', function() { gulp.src('app/img/**/*.*') - .pipe(gulp.dest('public/img')); + .pipe(gulp.dest('public/img')); }); gulp.task('public', function() { gulp.src('app/public/**/*') - .pipe(gulp.dest('public')); + .pipe(gulp.dest('public')); }); gulp.task('assets', function() { gulp.src(assets.js) - .pipe(concat('vendor.js')) - .pipe(gulp.dest('public/js')); + .pipe(concat('vendor.js')) + .pipe(gulp.dest('public/js')); gulp.src(assets.css) - .pipe(concat('vendor.css')) - .pipe(gulp.dest('public/css')); + .pipe(concat('vendor.css')) + .pipe(gulp.dest('public/css')); gulp.src(assets.fonts) - .pipe(gulp.dest('public/fonts')); + .pipe(gulp.dest('public/fonts')); gulp.src(assets.img) - .pipe(gulp.dest('public/img')); + .pipe(gulp.dest('public/img')); }); gulp.task('build', ['less', 'js', 'html', 'img', 'public', 'assets']); @@ -108,9 +108,9 @@ gulp.task('watch', function() { gulp.task('server', function() { gulp.src('public') - .pipe(webserver({ - open: true, - port: 4200, - fallback: 'index.html' - })); -}); \ No newline at end of file + .pipe(webserver({ + open: true, + port: 4200, + fallback: 'index.html' + })); +}); diff --git a/vipra-ui/package.json b/vipra-ui/package.json index dd09ca97c891388885061a2939128b7b6cb5d972..5256a0e0dd8a9a4cb40189ce9ab7a4455b9bc315 100644 --- a/vipra-ui/package.json +++ b/vipra-ui/package.json @@ -8,6 +8,7 @@ "bower": "^1.7.7", "gulp": "^3.9.1", "gulp-angular-templatecache": "^1.8.0", + "gulp-clean-css": "^2.0.3", "gulp-concat": "^2.6.0", "gulp-cssnano": "^2.1.0", "gulp-include": "^2.1.0",