diff --git a/Gruntfile.js b/Gruntfile.js index d3d5398d..1d7824ca 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,7 +16,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-uglify'); //grunt.loadNpmTasks('grunt-conventional-changelog'); - grunt.loadNpmTasks('grunt-conventional-changelog'); + grunt.loadNpmTasks('grunt-changelog'); grunt.loadNpmTasks('grunt-bump'); grunt.loadNpmTasks('grunt-sass'); grunt.loadNpmTasks('grunt-karma'); @@ -112,8 +112,8 @@ module.exports = function (grunt) { */ changelog: { options: { - after: "2013-09-05T10:18:39.4492679+10:00", - before: "today", + //after: "2013-09-05T10:18:39.4492679+10:00", + //before: "now", dest: 'CHANGELOG.md', template: 'changelog.tpl' } diff --git a/bower.json b/bower.json index 44a27783..4a2ce596 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "ng-boilerplate", - "version": "0.0.8", + "version": "0.0.9", "devDependencies": { "angular": "1.2.2", "angular-mocks": "~1.2.0", diff --git a/package.json b/package.json index f34378e0..fea9bf50 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "QUT Bioacoustics", "name": "baw-client", - "version": "0.0.8", + "version": "0.0.9", "description": "The AngularJS client for the QUT Bioacoustics server", "licenses": { "type": "Apache", diff --git a/src/app/app.js b/src/app/app.js index 11f0ae08..258e2112 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -155,6 +155,11 @@ var app = angular.module('baw', // http default configuration $httpProvider.defaults.withCredentials = true; + + // the default accept type is ` "application/json, text/plain, */*" ` + // for angular. This causes rails to do stupid shit for things like 403s... with old header it gives a 302 + // and redirects to HTML page. WTF. + $httpProvider.defaults.headers['common']['Accept'] = 'application/json'; }]) diff --git a/src/app/listen/_listen.scss b/src/app/listen/_listen.scss index 6056912c..4a227971 100644 --- a/src/app/listen/_listen.scss +++ b/src/app/listen/_listen.scss @@ -1,8 +1,24 @@ + +.noPermissions { + @extend .alert; + @extend .alert-warning; +} + +.project-names>span:not(:last-child) { + word-spacing : -0.3em; + + & * { + word-spacing: normal; + } +} + #chunkInfo>span:nth-child(2) { text-align: right; input[type="range"] { width: 300px; + + } &>span { @@ -11,6 +27,19 @@ } } +.hide-thumb { + + &::-ms-thumb { + opacity: 0; + } + &::-moz-range-thumb { + opacity: 0; + } + &::-webkit-slider-thumb { + opacity: 0; + } + +} .position { & span { diff --git a/src/app/listen/listen.js b/src/app/listen/listen.js index 7995d079..367c2c0b 100644 --- a/src/app/listen/listen.js +++ b/src/app/listen/listen.js @@ -5,6 +5,7 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) '$location', '$routeParams', '$route', + '$q', 'conf.paths', 'conf.constants', '$url', @@ -13,6 +14,8 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) 'AudioEvent', 'Tag', 'Taggings', + 'Site', + 'Project', /** * The listen controller. * @param $scope @@ -28,10 +31,14 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) * @param $url * @param AudioRecording * @param Taggings + * @param $q + * @param Site + * @param Project */ function ListenCtrl( - $scope, $resource, $location, $routeParams, $route, paths, constants, $url, - AudioRecording, Media, AudioEvent, Tag, Taggings) { + $scope, $resource, $location, $routeParams, $route, $q, paths, constants, $url, + AudioRecording, Media, AudioEvent, Tag, Taggings, Site, Project) { + var CHUNK_DURATION_SECONDS = constants.listen.chunkDurationSeconds; function getMediaParameters(format) { @@ -72,11 +79,15 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) // set up some dummy objects for use later + $scope.jumpToHide = true; $scope.model = { audioElement: {}, audioEvents: [], media: null, - selectedAudioEvent: null + selectedAudioEvent: null, + audioRecording: null, + projects: [], + site: null }; var formatPaths = function () { @@ -144,20 +155,102 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) console.error("retrieval of media json failed"); }); - $scope.model.audioRecording = AudioRecording.get({recordingId: recordingId}, {}, - function audioRecordingGetSuccess() { - // no-op - // if an audioRecording 'model' is ever created, this is where we would transform the returned data - // set up jumpto vars - var maxMinutes = Math.floor(parseFloat($scope.model.audioRecording.durationSeconds) / 60); - $scope.jumpToMax = maxMinutes; - $scope.jumpToMinute = Math.floor( parseFloat($routeParams.start) / 60); - }, - function audioRecordingGetFailure() { - console.error("retrieval of audioRecording json failed"); + var getAudioRecording = function getAudioRecording(recordingId) { + var deferred = $q.defer(); + + AudioRecording.get({recordingId: recordingId}, {}, + function audioRecordingGetSuccess(value) { + // if an audioRecording 'model' is ever created, this is where we would transform the returned data + $scope.model.audioRecording = value; + + var result = {audioRecording: value}; + + // set up jumpto vars + var maxMinutes = Math.floor(parseFloat($scope.model.audioRecording.durationSeconds) / 60); + $scope.jumpToMax = maxMinutes; + $scope.jumpToMinute = Math.floor( parseFloat($routeParams.start) / 60); + $scope.jumpToHide = false; + + deferred.resolve(result); + }, + function audioRecordingGetFailure() { + deferred.reject("retrieval of audioRecording json failed"); + }); + + return deferred.promise; + }; + + var getSite = function getSite(result) { + var siteDeferred = $q.defer(); + // get site + Site.get({siteId: result.audioRecording.siteId}, {}, function getSiteSuccess(value) { + + value.link = paths.api.routes.siteAbsolute.format({"siteId": value.id}); + + $scope.model.site = value; + result.site = value; + siteDeferred.resolve(result); + }, function getSiteError() { + siteDeferred.reject("retrieval of site json failed"); }); + return siteDeferred.promise; + }; + + var getProjects = function getProjects(result) { + var projectPromises = []; + + $scope.model.projects = $scope.model.projects || []; + result.projects = result.projects || []; + + result.site.projectIds.forEach(function (id, index) { + var projectDeferred = $q.defer(); + // get project + Project.get({projectId: id}, {}, function getProjectSuccess(value) { + + value.link = paths.api.routes.projectAbsolute.format({"projectId": value.id}); + + $scope.model.projects[index] = value; + result.projects[index] = value; + projectDeferred.resolve(result); + }, function getProjectError(error) { + if (error.status === 403) { + console.warn("The project %s does not give permissions to current user to access it's content. There are %s projects.", id, result.site.projectIds.length); + // populate field anyway, not really sure what to do here, temp value added + var denied = { + id: id, + permissions: "access denied" + }; + + denied.link = paths.api.routes.projectAbsolute.format({"projectId": denied.id}); + + $scope.model.projects[index] = denied; + result.projects[index] = denied; + + // we don't mind that this "error" has occurred - there should be at least one project + // that did resolve. Resolve promise anyway + projectDeferred.resolve(result); + } + else { + projectDeferred.reject("retrieval of project json failed"); + } + }); + + projectPromises[index] = projectDeferred.promise; + }); + + return $q.all(projectPromises); + }; + getAudioRecording(recordingId).then(getSite).then(getProjects).then(function success(result) { + console.info("Metadata Promise chain success", result); + }, function error(err) { + console.error("An error occurred downloading metadata for this chunk:" + err, err); + }, function notify() { + // TODO: remove dodgy scope closure from promise functions, and update values here incrementally! + console.debug("All promises notify", arguments); + }); + AudioEvent.query( { @@ -185,7 +278,7 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) $scope.tags.push(baw.Tag.make(value)); }); - $scope.model.audioEvents.forEach(function(value){ + $scope.model.audioEvents.forEach(function (value) { Tag.resolveAll(value.tags, $scope.tags); }); }, @@ -255,11 +348,16 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) var offset = base.add({seconds: $scope.model.media.startOffset}); return offset; }; - + + $scope.previousEnabled = false; $scope.nextEnabled = false; - $scope.createNavigationHref = function (linkType, stepBy) { + // skip if resources not available + if (!$scope.model.audioRecording) { + return "#"; + } + if (!angular.isNumber(stepBy)) { stepBy = CHUNK_DURATION_SECONDS; } @@ -268,13 +366,13 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) if (linkType === "previous") { var lowerBound = ($routeParams.start - stepBy); - + if ($routeParams.start > 0) { $scope.previousEnabled = true; } else { - $scope.previousEnabled = false; + $scope.previousEnabled = false; } - + if (lowerBound === 0) { baseLink.end = lowerBound + stepBy; } @@ -292,24 +390,24 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) return uriPrev; } - else if (linkType === "next") { - + else if (linkType === "next") { + var maxEnd = Math.floor($scope.model.audioRecording.durationSeconds); - + var uriNext = $url.formatUri( paths.site.ngRoutes.listen, { recordingId: recordingId, start: ($routeParams.start + stepBy), - end: (($routeParams.end + stepBy < maxEnd) ? $routeParams.end + stepBy : maxEnd) + end: (($routeParams.end + stepBy < maxEnd) ? $routeParams.end + stepBy : maxEnd) }); - + if ($routeParams.end < $scope.model.audioRecording.durationSeconds - constants.listen.minAudioDurationSeconds) { $scope.nextEnabled = true; } else { - $scope.nextEnabled = false; + $scope.nextEnabled = false; } - + return uriNext; } @@ -341,8 +439,11 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) $scope.model.audioEvents.forEach(function (value, key) { value.selected = false; }); + + $scope.model.selectedAudioEvent = null; }; + $scope.singleEditDisabled = function () { return ($scope.model.selectedAudioEvent === null || $scope.model.selectedAudioEvent.id === undefined); }; @@ -366,7 +467,6 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) tagTemplateUrl: "/templates/tags.html" }; - $scope.$on('decipher.tags.initialized', function (event) { event.stopPropagation(); console.debug('decipher.tags.initialized', arguments); @@ -436,7 +536,7 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) // assumes tags array is kept in sync //delete $scope.model.selectedAudioEvent.tags[index]; - console.debug("Tag removal success", removedTag.tag.text ); + console.debug("Tag removal success", removedTag.tag.text); }, function error(response) { console.error("Tagging creation failed", response); diff --git a/src/app/listen/listen.tpl.html b/src/app/listen/listen.tpl.html index 7b926946..8cae18f0 100644 --- a/src/app/listen/listen.tpl.html +++ b/src/app/listen/listen.tpl.html @@ -1,17 +1,27 @@ -