diff --git a/bower.json b/bower.json index c61558e9..ccfbe22b 100644 --- a/bower.json +++ b/bower.json @@ -22,7 +22,8 @@ "draggabilly": "~1.1.x", "bowser": "0.7.x", "angular-growl-v2": "~0.7.0", - "angular-local-storage": "~0.0.7" + "angular-local-storage": "~0.0.7", + "humanize-duration": "~2.0.0" }, "dependencies": {}, "private": true diff --git a/build.config.js b/build.config.js index d57ea6f2..515b1868 100644 --- a/build.config.js +++ b/build.config.js @@ -122,7 +122,8 @@ module.exports = { 'vendor/draggabilly/draggabilly.js', 'vendor/bowser/bowser.js', 'vendor/angular-growl-v2/build/angular-growl.js', - 'vendor/angular-local-storage/angular-local-storage.js' + 'vendor/angular-local-storage/angular-local-storage.js', + 'vendor/humanize-duration/humanize-duration.js' ], css: [ diff --git a/src/app/app.js b/src/app/app.js index 3187dd42..ff611409 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -76,8 +76,12 @@ var app = angular.module('baw', 'bawApp.directives.ngAudio', /* our directives.js */ 'bawApp.directives.toggleSwitch', 'bawApp.filters', /* our filters.js */ + + "bawApp.services.core", + 'bawApp.services.queryBuilder', 'bawApp.services', /* our services.js */ 'bawApp.services.unitConverter', + 'audio-control', 'draggabilly', @@ -95,6 +99,7 @@ var app = angular.module('baw', 'bawApp.projects', 'bawApp.recordInformation', 'bawApp.recordings', + 'bawApp.recordings.recentRecordings', 'bawApp.search', 'bawApp.tags', 'bawApp.users', @@ -129,7 +134,8 @@ var app = angular.module('baw', when('/recordings/:recordingId', {templateUrl: '/assets/recording.html', controller: 'RecordingCtrl' }). - when('/listen', {templateUrl: paths.site.files.listen, controller: 'ListenCtrl', title: 'Listen'}). + + when('/listen', {templateUrl: paths.site.files.recordings.recentRecordings, controller: 'RecentRecordingsCtrl', title: 'Listen'}). when('/listen/:recordingId', {templateUrl: paths.site.files.listen, controller: 'ListenCtrl', title: ':recordingId'}). //when('/listen/:recordingId/start=:start/end=:end', {templateUrl: paths.site.files.listen, controller: 'ListenCtrl'}). diff --git a/src/app/recordings/recentRecordings/_recentRecordings.scss b/src/app/recordings/recentRecordings/_recentRecordings.scss new file mode 100644 index 00000000..042c6f57 --- /dev/null +++ b/src/app/recordings/recentRecordings/_recentRecordings.scss @@ -0,0 +1,5 @@ +#recentRecordingsTable { + tr:hover { + cursor: pointer; + } +} \ No newline at end of file diff --git a/src/app/recordings/recentRecordings/recentRecordings.js b/src/app/recordings/recentRecordings/recentRecordings.js new file mode 100644 index 00000000..1b7f11d1 --- /dev/null +++ b/src/app/recordings/recentRecordings/recentRecordings.js @@ -0,0 +1,74 @@ +angular.module("bawApp.recordings.recentRecordings", []) +.controller("RecentRecordingsCtrl", + [ + "$scope", + "$location", + "AudioRecording", + "Site", + "moment", + "conf.paths", + function RecentRecordingsCtrl($scope, $location, AudioRecording, Site, moment, paths) { + $scope.recentRecordings = [ + { + siteId: 1000, + duration: 123456, + uploaded: new Date(), + id: 1234 + }, + { + siteId: 1000, + duration: 123456, + uploaded: new Date(), + id: 1234 + }, + { + siteId: 1000, + duration: 123456, + uploaded: new Date(), + id: 1234 + } + ]; + + function audioRecordingsFormat(wrapper) { + $scope.recentRecordings = wrapper.data; + // format return objects for display + $scope.recentRecordings.forEach(function(value, index) { + value.durationHumanized = moment.duration(value.durationSeconds, "seconds").humanizeDuration(); + value.uploadedHumanized = moment(value.createdAt).fromNow(); + value.listenLink = paths.site.ngRoutes.listen.format({recordingId: value.id}); + }); + + return Site.getSitesByIds(); + } + + function sitesFormat(wrapper) { + var sites = wrapper.reduce(function(state, current) { + state[current.id] = current; + return state; + }, {}); + + $scope.audioRecordings.forEach(function (value) { + value.site = sites[value.siteId]; + }); + } + + AudioRecording + .getRecentRecordings() + .then(audioRecordingsFormat) + .then(function gsbiSucccess(data) { + + }) + .catch(function error(reason) { + console.error(reason); + }); + + + + //Site.getNames(); + + $scope.navigate = function navigate(link) { + $location.url(link); + }; + } + ] +); \ No newline at end of file diff --git a/src/app/recordings/recentRecordings/recentRecordings.tpl.html b/src/app/recordings/recentRecordings/recentRecordings.tpl.html new file mode 100644 index 00000000..7baae8bd --- /dev/null +++ b/src/app/recordings/recentRecordings/recentRecordings.tpl.html @@ -0,0 +1,35 @@ +
+

Recent Audio Recordings

+
+
+ + No recent audio recordings. + + + + + + + + + + + + + + + + + + +
SiteDurationUploaded
{{r.site.name || r.siteId}}{{r.durationHumanized}}{{r.uploadedHumanized}} + + Play + + + Site + +
+
+
+
\ No newline at end of file diff --git a/src/baw.configuration.tpl.js b/src/baw.configuration.tpl.js index dfe8ad34..1ea19a04 100644 --- a/src/baw.configuration.tpl.js +++ b/src/baw.configuration.tpl.js @@ -61,12 +61,14 @@ angular.module('bawApp.configuration', ['url']) project: "/projects/{projectId}", site: { flattened: "/sites/{siteId}", - nested: "/projects/{projectId}/sites/{siteId}" + nested: "/projects/{projectId}/sites/{siteId}", + filter: "/sites/filter" }, audioRecording: { listShort: "/audio_recordings/{recordingId}", show: "/audio_recordings/{recordingId}", - list: "/audio_recordings/" + list: "/audio_recordings/", + filter: "/audio_recordings/filter" }, audioEvent: { list: "/audio_recordings/{recordingId}/audio_events", @@ -123,10 +125,14 @@ angular.module('bawApp.configuration', ['url']) spec: 'assets/bird_walk/bird_walk_spec.json', stats: 'assets/bird_walk/bird_walk_stats.json', images: 'assets/bird_walk/images/' + }, + recordings: { + recentRecordings: 'recordings/recentRecordings/recentRecordings.tpl.html' } }, // routes used by angular ngRoutes: { + recentRecordings: "/listen", listen: "/listen/{recordingId}", library: "/library", libraryItem: "/library/{recordingId}/audio_events/{audioEventId}" diff --git a/src/components/services/lodashService.js b/src/components/services/lodashService.js new file mode 100644 index 00000000..214d7949 --- /dev/null +++ b/src/components/services/lodashService.js @@ -0,0 +1,11 @@ +var bawssc = bawssc || angular.module("bawApp.services.core", []); + +bawssc.provider("_", function() { + + // TODO: is there a better way to load lodash without requiring it be attached to window? + var _ = window._; + + this.$get = [function lodashFactory() { + return _; + }]; +}); \ No newline at end of file diff --git a/src/components/services/momentService.js b/src/components/services/momentService.js new file mode 100644 index 00000000..6a07278b --- /dev/null +++ b/src/components/services/momentService.js @@ -0,0 +1,19 @@ +var bawssc = bawssc || angular.module("bawApp.services.core", []); + +bawssc.provider("moment", function() { + + // TODO: is there a better way to load moment without requiring it be attached to window? + var moment = window.moment; + + // HACK: add real duration formatting onto moment object! + var humanizeDuration = window.humanizeDuration; + this.$get = [function momentFactory() { + moment.humanizeDuration = humanizeDuration; + + moment.duration.fn.humanizeDuration = function(parameters) { + return humanizeDuration(this.asMilliseconds(), parameters); + }; + + return moment; + }]; +}); \ No newline at end of file diff --git a/src/components/services/queryBuilder.js b/src/components/services/queryBuilder.js index b5828c32..b0ce488b 100644 --- a/src/components/services/queryBuilder.js +++ b/src/components/services/queryBuilder.js @@ -262,12 +262,32 @@ qb.factory("QueryBuilder", ["conf.constants", function(constants) { RootQuery.prototype = Object.create(Query.prototype); + /** + * @callback createCallback + * @param {RootQuery} query + * @returns {RootQuery} + */ + + /** + * Create a new query. + * Optional callback function automatically composes/ends the query. + * @param {createCallback=} queryComposer + * @returns {RootQuery} + */ + function create(queryComposer) { + var q = new RootQuery(); + + if (queryComposer) { + var result = queryComposer.call(q, q); + q.compose(result); + } + + return q; + } return { Query: Query, RootQuery: RootQuery, - create: function () { - return new RootQuery(); - } + create: create }; }]); \ No newline at end of file diff --git a/src/components/services/services.js b/src/components/services/services.js index 7a5121c2..598c44e4 100644 --- a/src/components/services/services.js +++ b/src/components/services/services.js @@ -23,23 +23,52 @@ return uri.replace(/(\{([^{}]*)\})/g, ":$2"); } - var bawss = angular.module("bawApp.services", ['ngResource', 'bawApp.configuration']); + var bawss = bawss || angular.module("bawApp.services", ["ngResource", "bawApp.configuration", "bawApp.services.core", "bawApp.services.queryBuilder"]); - bawss.factory('Project', [ '$resource', 'conf.paths', function ($resource, paths) { - return resourcePut($resource, uriConvert(paths.api.routes.projectAbsolute), {projectId: "@projectId"}); + bawss.factory('Project', [ '$resource', "$http", 'conf.paths', "QueryBuilder", function ($resource, $http, paths, QueryBuilder) { + var resource = resourcePut($resource, uriConvert(paths.api.routes.projectAbsolute), {projectId: "@projectId"}); + + return resource; }]); - bawss.factory('Site', [ '$resource', 'conf.paths', function ($resource, paths) { - return resourcePut($resource, uriConvert(paths.api.routes.site.flattenedAbsolute), { siteId: "@siteId"}); + bawss.factory('Site', [ '$resource', "$http", 'conf.paths', "_", "QueryBuilder", function ($resource, $http, paths, _, QueryBuilder) { + var resource = resourcePut($resource, uriConvert(paths.api.routes.site.flattenedAbsolute), { siteId: "@siteId"}); + + + resource.getSitesByIds = function(siteIds) { + var url = paths.api.routes.site.filterAbsolute; + siteIds = _.uniq(siteIds); + var query = QueryBuilder.create(function(q) { + return q.in("id", siteIds); + }); + return $http.post(url, query.toJSON()); + }; + + + return resource; }]); // NOTE: deleted photo resource, API for photos no longer exposed // NOTE: deleted user resource, API for users no longer exposed - bawss.factory('AudioRecording', [ '$resource', 'conf.paths', function ($resource, paths) { - return resourcePut($resource, uriConvert(paths.api.routes.audioRecording.showAbsolute), + 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'}); + + var query = QueryBuilder.create(function(q) { + return q + .sort({orderBy: "createdDate", direction: "desc"}) + .page({page:1, items: 10}) + .project({include: ["id", "siteId", "durationSeconds", "recordedDate", "createdAt"]}); + }); + resource.getRecentRecordings = function() { + var url = paths.api.routes.audioRecording.filterAbsolute; + + return $http.post(url, query.toJSON()); + }; + + return resource; }]); bawss.factory('AudioEvent', [ '$resource', '$url', 'conf.paths', diff --git a/src/index.html b/src/index.html index d7ec541b..f2103446 100644 --- a/src/index.html +++ b/src/index.html @@ -35,7 +35,7 @@