diff --git a/src/app/annotationLibrary/_annotationLibrary.scss b/src/app/annotationLibrary/_annotationLibrary.scss index c0ab5773..56c468b6 100644 --- a/src/app/annotationLibrary/_annotationLibrary.scss +++ b/src/app/annotationLibrary/_annotationLibrary.scss @@ -57,6 +57,15 @@ $loadGifPath: image-url('load.gif'); .library-single-info { margin: 0 auto 0 auto; + + & .reported { + color: $brand-danger; + margin: 0 $padding-base-horizontal; + } + + & .reported-comment { + color: $gray-lighter; + } } /* Shared spectrogram and grid */ diff --git a/src/app/annotationLibrary/annotationItem.tpl.html b/src/app/annotationLibrary/annotationItem.tpl.html index a4c7b865..9863e3f7 100644 --- a/src/app/annotationLibrary/annotationItem.tpl.html +++ b/src/app/annotationLibrary/annotationItem.tpl.html @@ -44,8 +44,8 @@

Details

-   {{tag.typeOfTag.replace('_',' ')}} @@ -53,11 +53,12 @@

Details

taxonomic {{tag.text}} (ID {{tag.id}})
-   - Reference Tag + Reference Annotation   @@ -76,7 +77,12 @@

Details

  Site: {{annotation.siteName}}
- + +   + Audio recording: {{annotation.audioRecordingId}} + + +   {{annotation.ownerName }} @@ -92,11 +98,99 @@

Details

-

 Comments +

+ +  {{comments.length == 0 ? 'No Comments Yet' : (comments.length == 1 ? '1 Comment' : comments.length + ' Comments') }}

- Coming soon... +
    +
  • + + + + +
    +

    + {{profile.userName}} + now +

    + +
    +
    + + {{error}} +
    +
    + +
    +
    + +
    +
  • +
+
    +
  • + + + + +
    +
    + link + + + + + + + +
    + +

    + {{comment.updater.userName}} + + last change {{formatTimeAgo(comment.updatedAt)}} + +

    +

    + {{comment.comment}} +

    + +
    +
    + + {{error}} +
    + + +
    +
    +
  • +
+
diff --git a/src/app/annotationLibrary/annotationLibrary.js b/src/app/annotationLibrary/annotationLibrary.js index ba90da59..43770a72 100644 --- a/src/app/annotationLibrary/annotationLibrary.js +++ b/src/app/annotationLibrary/annotationLibrary.js @@ -9,20 +9,25 @@ baw.annotationLibrary.addCalculatedProperties = function addCalculatedProperties audioEvent.calcOffsetStart = Math.floor(audioEvent.startTimeSeconds / 30) * 30; audioEvent.calcOffsetEnd = (Math.floor(audioEvent.startTimeSeconds / 30) * 30) + 30; + var roundedDuration = Math.round10(audioEvent.annotationDuration, -3); + var roundedFreqLow = Math.round(audioEvent.lowFrequencyHertz); + var roundedFreqHigh = Math.round(audioEvent.highFrequencyHertz); + audioEvent.urls = { site: $url.formatUri(paths.api.links.siteAbsolute, {projectId: audioEvent.projects[0].id, siteId: audioEvent.siteId}), user: $url.formatUri(paths.api.links.userAccountsAbsolute, {userId: audioEvent.ownerId}), - tagSearch: $url.formatUri(paths.site.ngRoutes.library, {tagsPartial: audioEvent.priorityTag.text}), - similar: $url.formatUri(paths.site.ngRoutes.library, + tagSearch: $url.formatUri(paths.site.ngRoutes.libraryAbsolute, {tagsPartial: audioEvent.priorityTag.text}), + isReference: $url.formatUri(paths.site.ngRoutes.libraryAbsolute, {reference: audioEvent.isReference}), + similar: $url.formatUri(paths.site.ngRoutes.libraryAbsolute, { - annotationDuration: Math.round10(audioEvent.annotationDuration, -3), - freqMin: Math.round(audioEvent.lowFrequencyHertz), - freqMax: Math.round(audioEvent.highFrequencyHertz) + annotationDuration: roundedDuration, + freqMin: roundedFreqLow, + freqMax: roundedFreqHigh }), singleItem: $url.formatUri(paths.site.ngRoutes.libraryItem, { recordingId: audioEvent.audioRecordingId, - audioEventId: audioEvent.audioEventId + audioEventId: audioEvent.audioEventId }), listen: $url.formatUri(paths.site.ngRoutes.listen, { @@ -38,6 +43,25 @@ baw.annotationLibrary.addCalculatedProperties = function addCalculatedProperties }) }; + audioEvent.tags.forEach( + function (currentvalue, index, array) { + currentvalue.similarPartial = $url.formatUri(paths.site.ngRoutes.libraryAbsolute, + { + tagsPartial: currentvalue.text, + annotationDuration: roundedDuration, + freqMin: roundedFreqLow, + freqMax: roundedFreqHigh + }); + } + ); + + audioEvent.projects.forEach( + function (currentvalue, index, array) { + currentvalue.link = $url.formatUri(paths.api.links.projectAbsolute, {projectId: currentvalue.id}); + } + ); + + return audioEvent; }; @@ -155,7 +179,7 @@ angular.module('bawApp.annotationLibrary', ['bawApp.configuration']) $scope.createFilterUrl = function createFilterUrl(paramObj) { - return $url.formatUri(paths.site.ngRoutes.libraryAbsolute,paramObj); + return $url.formatUri(paths.site.ngRoutes.libraryAbsolute, paramObj); }; function getEmptyFilterSettings() { @@ -191,7 +215,7 @@ angular.module('bawApp.annotationLibrary', ['bawApp.configuration']) $scope.filterSettings[currentvalue] = isVoid ? null : Number(stringValue); } ); - + // disable other options for reference filter $scope.filterSettings.reference = 'true'; } @@ -289,15 +313,38 @@ angular.module('bawApp.annotationLibrary', ['bawApp.configuration']) }]) .controller('AnnotationItemCtrl', ['$scope', '$location', '$resource', '$routeParams', '$url', - 'conf.paths', 'conf.constants', 'bawApp.unitConverter', - 'AudioEvent', 'Tag', 'Media', 'baw.models.Media', - function ($scope, $location, $resource, $routeParams, $url, paths, constants, unitConverter, AudioEvent, Tag, MediaService, Media) { + 'conf.paths', 'conf.constants', 'bawApp.unitConverter', 'moment', + 'AudioEvent', 'Tag', + 'Media', 'baw.models.Media', 'UserProfileEvents', 'UserProfile', 'AudioEventComment', + function ($scope, $location, $resource, $routeParams, $url, + paths, constants, unitConverter, moment, + AudioEvent, Tag, + MediaService, Media, UserProfileEvents, UserProfile, AudioEventComment) { + + $scope.$on(UserProfileEvents.loaded, profileLoaded); + if (UserProfile.profile && UserProfile.profile.preferences) { + profileLoaded(null, UserProfile); + } + var parameters = { audioEventId: $routeParams.audioEventId, recordingId: $routeParams.recordingId }; + // new comment text and errors + $scope.newComment = { + // bind to new comment textarea + text: '', + errors: [] + }; + + $scope.editComment = { + id: null, + text: null, + errors: [] + }; + AudioEvent.get(parameters, function annotationShowSuccess(audioEventValue, responseHeaders) { @@ -310,6 +357,9 @@ angular.module('bawApp.annotationLibrary', ['bawApp.configuration']) $scope.annotation.gridConfig = {}; + // comments + reloadComments(); + // paging if ($scope.annotation.paging.nextEvent.hasOwnProperty('audioEventId')) { $scope.annotation.paging.nextEvent.link = @@ -337,11 +387,126 @@ angular.module('bawApp.annotationLibrary', ['bawApp.configuration']) }); $scope.createFilterUrl = function createFilterUrl(paramObj) { - return $url.formatUri(paths.site.ngRoutes.libraryAbsolute,paramObj); + return $url.formatUri(paths.site.ngRoutes.libraryAbsolute, paramObj); }; - $scope.createProjectUrl = function createProjectUrl(projectId){ + $scope.createProjectUrl = function createProjectUrl(projectId) { return $url.formatUri(paths.api.links.projectAbsolute, {projectId: projectId}); }; + $scope.createCommentLinkUrl = function createCommentLinkUrl(audioEventCommentId) { + return '/library/' + $routeParams.recordingId + '/audio_events/' + + $routeParams.audioEventId + '#' + audioEventCommentId + }; + + $scope.formatTimeAgo = function formatTimeAgo(date) { + if (date) { + return moment(date).fromNow(); + } + else { + return "unknown"; + } + }; + + $scope.createComment = function createComment() { + if($scope.createCommentForm.$valid) { + AudioEventComment.save( + {audioEventId: $routeParams.audioEventId}, // parameters + {comment: $scope.newComment.text}, // post data + function createCommentSuccess(value, responseHeaders) { + console.log('create success', arguments); + $scope.newComment.errors = []; + $scope.newComment.text = ''; + reloadComments(); + }, + function createCommentError(httpResponse) { + console.log('create failure', arguments); + $scope.newComment.errors = httpResponse.data.comment; + }); + } + }; + + $scope.deleteComment = function deleteComment(commentText, audioEventCommentId) { + var isConfirmed = confirm('Are you sure you want to delete this comment? "' + commentText + '"'); + if (isConfirmed === true) { + AudioEventComment.delete( + { + audioEventId: $routeParams.audioEventId, + audioEventCommentId: audioEventCommentId + }, // parameters + null, // post data + function deleteCommentSuccess(value, responseHeaders) { + console.log('delete success', arguments); + $scope.newComment.errors = []; + $scope.newComment.text = ''; + reloadComments(); + }, + function deleteCommentError(httpResponse) { + console.log('delete failure', arguments); + $scope.newComment.errors = httpResponse.data.comment; + }); + } + }; + + function updateCommentBase(id, body) { + AudioEventComment.update( + // url parameters + { + audioEventId: $routeParams.audioEventId, + audioEventCommentId: id + }, + // body + body, + function updateCommentSuccess(value, responseHeaders) { + console.log('update success', arguments); + $scope.editComment.errors = []; + $scope.editComment.text = null; + $scope.editComment.id = null; + reloadComments(); + }, + function updateCommentError(httpResponse) { + console.log('update failure', arguments); + $scope.editComment.errors = httpResponse.data.comment; + }); + } + + $scope.updateComment = function updateComment(comment, updateForm) { + if(updateForm.$valid) { + updateCommentBase(comment.id, { + comment: comment.comment + }); + comment.editing = false; + } + }; + + $scope.editComment = function editComment(comment) { + comment.editing = true; + }; + + $scope.reportComment = function reportComment(comment) { + comment.flag = "report"; + updateCommentBase(comment.id, { + flag: comment.flag + }); + + }; + + function profileLoaded(event, userProfile) { + $scope.profile = userProfile.profile; + } + + function reloadComments() { + // get array of comment for the current audio event + AudioEventComment.query( + {audioEventId: $routeParams.audioEventId}, + function audioEventCommentSuccess(value, responseHeaders) { + $scope.comments = value.data; + + $scope.comments.forEach(function(value) { + + }); + }); + + } + }]); \ No newline at end of file diff --git a/src/app/annotationLibrary/comments/_comments.scss b/src/app/annotationLibrary/comments/_comments.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/annotationLibrary/comments/comments.js b/src/app/annotationLibrary/comments/comments.js new file mode 100644 index 00000000..015e0ff9 --- /dev/null +++ b/src/app/annotationLibrary/comments/comments.js @@ -0,0 +1,11 @@ +var bawCs = angular + .module('bawApp.annotationLibrary.comments', ['bawApp.configuration']) + .directive('comments', ['conf.paths', function (paths) { + var commentDefinition = { + restrict: 'E', + templateUrl: paths.site.files.annotationComments, + link: function postLink(scope, $element, attributes) { + } + }; + return commentDefinition; + }]); \ No newline at end of file diff --git a/src/app/annotationLibrary/comments/comments.tpl.html b/src/app/annotationLibrary/comments/comments.tpl.html new file mode 100644 index 00000000..e69de29b diff --git a/src/app/listen/listen.js b/src/app/listen/listen.js index 782d9e3b..6150ab74 100644 --- a/src/app/listen/listen.js +++ b/src/app/listen/listen.js @@ -46,6 +46,7 @@ angular.module('bawApp.listen', ['decipher.tags', 'ui.bootstrap.typeahead']) * @param UserProfile * @param Bookmark * @param UserProfileEvents + * @param moment */ function ListenCtrl( $scope, $resource, $location, $routeParams, $route, $q, paths, constants, $url, ngAudioEvents, diff --git a/src/baw.configuration.tpl.js b/src/baw.configuration.tpl.js index a5914432..fb520e03 100644 --- a/src/baw.configuration.tpl.js +++ b/src/baw.configuration.tpl.js @@ -56,6 +56,9 @@ angular.module('bawApp.configuration', ['url']) profile: "/my_account", settings: "/my_account/prefs" }, + audioEventComment: { + show: '/audio_events/{audioEventId}/comments/{audioEventCommentId}' + }, bookmark: { show: "/bookmarks/{bookmarkId}" } @@ -83,6 +86,7 @@ angular.module('bawApp.configuration', ['url']) listen: 'listen/listen.tpl.html', annotationViewer: 'annotationViewer/annotationViewer.tpl.html', gridLines: 'annotationViewer/gridLines/gridLines.tpl.html', + annotationComments: 'annotationLibrary/comments/comments.tpl.html', library: { list: 'annotationLibrary/annotationLibrary.tpl.html', item: 'annotationLibrary/annotationItem.tpl.html' diff --git a/src/components/services/bawResource.js b/src/components/services/bawResource.js index cfdd5394..0be93e0d 100644 --- a/src/components/services/bawResource.js +++ b/src/components/services/bawResource.js @@ -25,6 +25,7 @@ angular.module("bawApp.services.resource", ["ngResource"]) var a = actions || {}; a.update = a.update || { method: 'PUT' }; + a.query = {method: "GET", isArray: false}; var resource = $resource(convertedPath, paramDefaults, a); resource.modifiedPath = convertedPath; diff --git a/src/components/services/bawResource.spec.js b/src/components/services/bawResource.spec.js index 1a61a57b..325ab077 100644 --- a/src/components/services/bawResource.spec.js +++ b/src/components/services/bawResource.spec.js @@ -1,17 +1,22 @@ describe("The bawResource service", function () { - var Bookmark; + var $httpBackend, bawResource, $rootScope; beforeEach(module('bawApp.services')); - beforeEach(inject(["Bookmark", function (providedBookmark) { - Bookmark = providedBookmark; + beforeEach(inject(["$injector", "bawResource", "$rootScope", function ($injector, providedBawResource, _$rootScope) { + $httpBackend = $injector.get('$httpBackend'); + + $httpBackend.when("GET", "/test").respond({data:[], meta:{}}); + + bawResource = providedBawResource; + $rootScope = _$rootScope; }])); it("should return a resource constructor that includes update/put", function () { - expect(Bookmark).toImplement({ + expect(bawResource("/test")).toImplement({ "get": null, "save": null, "query": null, @@ -21,4 +26,24 @@ describe("The bawResource service", function () { "modifiedPath": null }); }); + + it("should override $resource's query", function(done) { + + // make "new" resource + var testResource = bawResource("/test"); + + var pass; + var result = testResource + .query().$promise + .then(function() { + pass = true; + }, function() { + pass = false; + }); + $httpBackend.flush(); + + expect(pass).toBeTrue(); + + done(); + }); }); \ No newline at end of file diff --git a/src/components/services/services.js b/src/components/services/services.js index e6428ec4..85c8d74b 100644 --- a/src/components/services/services.js +++ b/src/components/services/services.js @@ -92,7 +92,17 @@ // NOTE: deleted user resource, API for users no longer exposed - bawss.factory('AudioRecording', [ '$resource', '$http', 'conf.paths', 'QueryBuilder', function ($resource, $http, paths, QueryBuilder) { + + bawss.factory('AudioEventComment', [ "bawResource", 'conf.paths', function (bawResource, paths) { + return bawResource( + paths.api.routes.audioEventComment.showAbsolute, + {audioEventId: "@audioEventId", audioEventCommentId: '@audioEventCommentId'}); + }]); + + bawss.factory( + 'AudioRecording', + [ '$resource', '$http', 'conf.paths', 'QueryBuilder', + function ($resource, $http, paths, QueryBuilder) { var resource = resourcePut($resource, uriConvert(paths.api.routes.audioRecording.showAbsolute), {projectId: "@projectId", siteId: "@siteId", recordingId: '@recordingId'});