From 1599960a1ece4c8228f913980df05534a7f51550 Mon Sep 17 00:00:00 2001 From: Philip Eichinski Date: Tue, 31 Jul 2018 14:06:12 +1000 Subject: [PATCH] feat(datasets): Started work to use dataset items from the server for cs samples --- .../citizenScience/bristlebird/bristlebird.js | 12 +- .../citizenScience/citizenScienceCommon.js | 4 +- .../datasetProgress/datasetProgress.js | 121 ++++++++------ .../dummyApi/citizenScienceApiMock.js | 155 ------------------ .../dummyApi/citizenScienceSamples.js | 140 ++++++++++++++++ src/baw.paths.nobuild.js | 4 + src/components/models/datasetItem.js | 16 ++ src/components/models/models.js | 3 +- src/components/services/datasetItem.js | 27 +++ src/components/services/services.js | 3 +- 10 files changed, 269 insertions(+), 216 deletions(-) delete mode 100644 src/app/citizenScience/dummyApi/citizenScienceApiMock.js create mode 100644 src/app/citizenScience/dummyApi/citizenScienceSamples.js create mode 100644 src/components/models/datasetItem.js create mode 100644 src/components/services/datasetItem.js diff --git a/src/app/citizenScience/bristlebird/bristlebird.js b/src/app/citizenScience/bristlebird/bristlebird.js index 6c3f119a..812aefa1 100644 --- a/src/app/citizenScience/bristlebird/bristlebird.js +++ b/src/app/citizenScience/bristlebird/bristlebird.js @@ -17,7 +17,7 @@ class BristlebirdController { ngAudioEvents, $location, CitizenScienceCommon, - CsApi, + CsSamples, SampleLabels, backgroundImage, paths) { @@ -100,7 +100,7 @@ class BristlebirdController { this.showAudio = CitizenScienceCommon.bindShowAudio($scope); - CsApi.getLabels($scope.csProject).then(function (labels) { + CsSamples.getLabels($scope.csProject).then(function (labels) { $scope.labels = labels; }); @@ -109,7 +109,7 @@ class BristlebirdController { /** * Retrieve settings about this citizen science project */ - CsApi.getSettings($scope.csProject).then( + CsSamples.getSettings($scope.csProject).then( function (settings) { $scope.settings = settings; if ($scope.settings.hasOwnProperty("sampleDuration")) { @@ -123,7 +123,7 @@ class BristlebirdController { */ $scope.$watch("currentSample", function () { if ($scope.currentSample.id !== undefined) { - self.showAudio($scope.currentSample.recordingId, $scope.currentSample.startOffset, self.sampleDuration); + self.showAudio($scope.currentSample.audioRecordingId, $scope.currentSample.startTimeSeconds, $scope.currentSample.endTimeSeconds); // for now, we cycle through backgrounds arbitrarily, based on the id of the sample number // todo: store background images as part of the dataset or cs project var backgroundPath = self.backgroundPaths[parseInt($scope.currentSample.id) % (self.backgroundPaths.length - 1)]; @@ -166,7 +166,7 @@ angular "bawApp.components.citizenScienceThumbLabels", "bawApp.components.onboarding", "bawApp.components.background", - "bawApp.citizenScience.csApiMock" + "bawApp.citizenScience.csSamples" ]) .controller( "BristlebirdController", @@ -175,7 +175,7 @@ angular "ngAudioEvents", "$location", "CitizenScienceCommon", - "CsApi", + "CsSamples", "SampleLabels", "backgroundImage", "conf.paths", diff --git a/src/app/citizenScience/citizenScienceCommon.js b/src/app/citizenScience/citizenScienceCommon.js index 3c43c29b..c052ac77 100644 --- a/src/app/citizenScience/citizenScienceCommon.js +++ b/src/app/citizenScience/citizenScienceCommon.js @@ -109,12 +109,12 @@ citizenScienceCommon.factory("CitizenScienceCommon", [ */ bindShowAudio: function ($scope) { - var showAudio = function (recordingId, startOffset, duration) { + var showAudio = function (recordingId, startOffset, endOffset) { var mediaParams = { recordingId: recordingId, startOffset: startOffset, - endOffset: startOffset + duration, + endOffset: endOffset, format: "json" }; diff --git a/src/app/citizenScience/datasetProgress/datasetProgress.js b/src/app/citizenScience/datasetProgress/datasetProgress.js index de9a8f70..6532ce84 100644 --- a/src/app/citizenScience/datasetProgress/datasetProgress.js +++ b/src/app/citizenScience/datasetProgress/datasetProgress.js @@ -1,65 +1,84 @@ -angular.module("bawApp.components.progress", ["bawApp.citizenScience.csApiMock"]) +angular.module("bawApp.components.progress", ["bawApp.citizenScience.csSamples"]) .component("datasetProgress", { templateUrl: "citizenScience/datasetProgress/datasetProgress.tpl.html", - controller: ["$scope", "$routeParams", "$url", "conf.paths", "CsApi", "SampleLabels", - function ($scope, $routeParams, $url, paths, CsApi, SampleLabels) { + controller: ["$scope", "$routeParams", "$url", "conf.paths", "CsSamples", "SampleLabels", + function ($scope, $routeParams, $url, paths, CsSamples, SampleLabels) { - var self = this; - $scope.selectItem = function (itemId) { - console.log("selecting item", itemId); - CsApi.getSample(itemId).then(function (apiResponse) { - self.currentSample = apiResponse; - SampleLabels.registerCurrentSampleId(self.currentSample.id); - $scope.totalSamplesViewed = SampleLabels.getNumSamplesViewed(); - console.log("setting selected to ", itemId); - }); - }; + var self = this; - $scope.selectItem($routeParams.sampleNum); - /** - * Load the new sample whenever the route params change. - */ - $scope.$watch( - function () { - return $routeParams.sampleNum; - }, function (newVal, oldVal) { - console.log("route params changed from ", oldVal, "to", newVal); + $scope.selectItem = function (itemId) { + console.log("selecting item", itemId); + self.currentSample = CsSamples.getSample(itemId); + if (self.currentSample) { + SampleLabels.registerCurrentSampleId(self.currentSample.id); + $scope.totalSamplesViewed = SampleLabels.getNumSamplesViewed(); + console.log("setting selected to ", itemId); + } + }; - // call the api to get the sample based on the route params - $scope.selectItem($routeParams.sampleNum); + // don't do this yet, because we'll watch for ready + //$scope.selectItem($routeParams.sampleNum); - }); + /** + * Load the new sample whenever the route params change. + */ + $scope.$watch( + function () { + return $routeParams.sampleNum; + }, function (newVal, oldVal) { + console.log("route params changed from ", oldVal, "to", newVal); - //TODO: this gets called constantly while the audio is playing - /** - * returns a link for routing based on the id for the next and - * previous samples. That id is returned in the request for metadata of - * the current sample (contained in the route). - * @returns string - */ - $scope.previousLink = function () { - if (self.currentSample.previousSampleId) { - return $url.formatUri(paths.site.ngRoutes.citizenScience.listen, {sampleNum:self.currentSample.previousSampleId}); - } else { - return ""; - } - }; - $scope.nextLink = function () { - if (self.currentSample.nextSampleId) { - return $url.formatUri(paths.site.ngRoutes.citizenScience.listen, {sampleNum:self.currentSample.nextSampleId}); - } else { - return ""; - } - }; + // call the api to get the sample based on the route params + $scope.selectItem($routeParams.sampleNum); - // reverse binding of this functions to make - // them accessible to the parent controller for autoplay - self.nextLink = $scope.nextLink; + }); - self.progressNav = true; + /** + * Load sample when the list of samples is ready + */ - }], + $scope.$watch( + function () { + return CsSamples.isReady; + }, + function (newVal, oldVal) { + if (newVal) { + $scope.selectItem($routeParams.sampleNum); + } + }, + true); + + + //TODO: this gets called constantly while the audio is playing + /** + * returns a link for routing based on the id for the next and + * previous samples. That id is returned in the request for metadata of + * the current sample (contained in the route). + * @returns string + */ + $scope.previousLink = function () { + if (self.currentSample.previousSampleId) { + return $url.formatUri(paths.site.ngRoutes.citizenScience.listen, {sampleNum: self.currentSample.previousSampleId}); + } else { + return ""; + } + }; + $scope.nextLink = function () { + if (self.currentSample.nextSampleId) { + return $url.formatUri(paths.site.ngRoutes.citizenScience.listen, {sampleNum: self.currentSample.nextSampleId}); + } else { + return ""; + } + }; + + // reverse binding of this functions to make + // them accessible to the parent controller for autoplay + self.nextLink = $scope.nextLink; + + self.progressNav = true; + + }], bindings: { audioElementModel: "=", currentSample: "=", diff --git a/src/app/citizenScience/dummyApi/citizenScienceApiMock.js b/src/app/citizenScience/dummyApi/citizenScienceApiMock.js deleted file mode 100644 index fa2c2394..00000000 --- a/src/app/citizenScience/dummyApi/citizenScienceApiMock.js +++ /dev/null @@ -1,155 +0,0 @@ -var csApiMock = angular.module("bawApp.citizenScience.csApiMock", ["bawApp.citizenScience.common"]); - - -/** - * Mocks the api responses for citizenScienceProjects that - * will eventually go on the server - */ - - -csApiMock.factory("CsApi", [ - "CitizenScienceCommon", - "$http", - function CsApi(CitizenScienceCommon, $http) { - - var self = this; - self.useLocalData = true; - self.sheets_api_url = "http://" + window.location.hostname + ":8081"; - self.local_api_url = "/public/citizen_science"; - - /** - * Constructs a url for the request by concatenating the arguments, joined by "/" - * and appending to the relevant baseURL. Allows experimenting with different sources - * for the data without changing everything - * @returns {string|*} - */ - self.apiUrl = function () { - // convert to array - var base_url, url; - if (self.useLocalData) { - base_url = self.local_api_url; - } else { - base_url = self.sheets_api_url; - } - var args = Array.prototype.slice.call(arguments); - - url = [base_url].concat(args).join("/"); - - if (self.useLocalData) { - url = url + ".json"; - } - - return url; - }; - - self.publicFunctions = { - - /** - * Gets the media data for the specified sample - * For now, (because the API is not actually functional), we request all samples as a big - * json dump, then filter to get the specified sample. - * - * Inject the previous and next sample id into the sample we will return, which is what the - * Api will probably do for us in the future. - * - * @param datasetItemId - */ - getSample : function (datasetItemId) { - - var url = self.apiUrl( - "samples", - "ebb", - "phil"); - //TODO: error handling - return $http.get(url).then(function (response) { - - // mock version returns all samples. Then we search then here to get the right one - var itemNum = response.data.findIndex(item => item.id === datasetItemId); - - if (itemNum === -1) { - return {}; - } - - var item = response.data[itemNum]; - if (itemNum > 0) { - item.previousSampleId = response.data[itemNum - 1].id; - } else { - item.previousSampleId = null; - } - if (itemNum < response.data.length-1) { - item.nextSampleId = response.data[itemNum + 1].id; - } else { - item.nextSampleId = null; - } - - return item; - }); - }, - /** - * Gets the identifier for the next sample - * to be used for navigation. - * @param datasetItemId int - */ - getNextSample : function (datasetItemId) { - var url = self.apiUrl( - "nextSample", - "ebb"); - return $http.get(url).then(function (response) { - return response.data; - }); - - }, - /** - * Gets the identifier for the previous sample, - * to be used for navigation - * @param datasetItemId int - */ - getPrevousSample : function (datasetItemId) { - var url = self.apiUrl( - "previousSample", - "ebb"); - return $http.get(url).then(function (response) { - return response.data; - }); - - }, - - /** - * Gets all labels associated with the specified citizen science project - * @param project string - */ - getLabels: function (project) { - var response = $http.get(self.apiUrl( - "labels", - project - )); - - return response.then(function (response) { - var labels = []; - if (Array.isArray(response.data)) { - labels = response.data; - } - - return labels; - }); - }, - - /** - * Gets all settings associated with the specified citizen science project - * @param project string - * @returns {HttpPromise} - */ - getSettings: function (project) { - return $http.get(self.apiUrl( - "settings", - project - )); - } - - }; - - return self.publicFunctions; - - }]); - - diff --git a/src/app/citizenScience/dummyApi/citizenScienceSamples.js b/src/app/citizenScience/dummyApi/citizenScienceSamples.js new file mode 100644 index 00000000..f41c8ea1 --- /dev/null +++ b/src/app/citizenScience/dummyApi/citizenScienceSamples.js @@ -0,0 +1,140 @@ +var csSamples = angular.module("bawApp.citizenScience.csSamples", ["bawApp.citizenScience.common"]); + + +/** + * Manages the queue of dataset items that will be shown to citizen science users + */ +csSamples.factory("CsSamples", [ + "CitizenScienceCommon", + "$http", + "DatasetItem", + function CsSamples(CitizenScienceCommon, $http, DatasetItem) { + + var self = this; + self.useLocalData = true; + self.sheets_api_url = "http://" + window.location.hostname + ":8081"; + self.local_api_url = "/public/citizen_science"; + + /** + * Constructs a url for the request by concatenating the arguments, joined by "/" + * and appending to the relevant baseURL. Allows experimenting with different sources + * for the data without changing everything + * @returns {string|*} + */ + self.apiUrl = function () { + // convert to array + var base_url, url; + if (self.useLocalData) { + base_url = self.local_api_url; + } else { + base_url = self.sheets_api_url; + } + var args = Array.prototype.slice.call(arguments); + + url = [base_url].concat(args).join("/"); + + if (self.useLocalData) { + url = url + ".json"; + } + + return url; + }; + + + // will store a list of dataset items + self.items = []; + + // Populate self.items with dataset items + DatasetItem.datasetItems(3).then(x => { + console.log("page of items loaded", x); + self.items = self.items.concat(x.data.data); + self.publicFunctions.isReady = true; + }); + + + /** + * Adds previous and next items to the current dataset item + * so that back and forward links can be easily added. + * @param itemNum + * @returns {*} + */ + self.setupSample = function (itemNum) { + + if (itemNum <= -1) { + return false; + } + + var item = self.items[itemNum]; + + if (itemNum > 0) { + item.previousSampleId = self.items[itemNum - 1].id; + } else { + item.previousSampleId = null; + } + if (itemNum < self.items.length-1) { + item.nextSampleId = self.items[itemNum + 1].id; + } else { + item.nextSampleId = null; + } + + return item; + + }; + + self.publicFunctions = { + + /** + * Gets the media data for the specified sample + * + * @param datasetItemId + */ + getSample : function (datasetItemId) { + + // find the sample within the current list of samples + var itemNum = self.items.findIndex(item => item.id === parseInt(datasetItemId)); + return self.setupSample(itemNum); + + }, + + + /** + * Gets all labels associated with the specified citizen science project + * @param project string + */ + getLabels: function (project) { + var response = $http.get(self.apiUrl( + "labels", + project + )); + + return response.then(function (response) { + var labels = []; + if (Array.isArray(response.data)) { + labels = response.data; + } + + return labels; + }); + }, + + /** + * Gets all settings associated with the specified citizen science project + * @param project string + * @returns {HttpPromise} + */ + getSettings: function (project) { + return $http.get(self.apiUrl( + "settings", + project + )); + }, + + isReady: false + + }; + + return self.publicFunctions; + + }]); + + diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js index 2a5348c8..ef82df42 100644 --- a/src/baw.paths.nobuild.js +++ b/src/baw.paths.nobuild.js @@ -72,6 +72,10 @@ module.exports = function (environment) { "savedSearches": { "list": "/saved_searches", "show": "/saved_searches/{savedSearchId}" + }, + "datasetItem": { + "list": "/datasets/{datasetId}/items", + "show": "/datasets/{datasetId}/items/{datasetItemId}" } }, "links": { diff --git a/src/components/models/datasetItem.js b/src/components/models/datasetItem.js new file mode 100644 index 00000000..5aa94997 --- /dev/null +++ b/src/components/models/datasetItem.js @@ -0,0 +1,16 @@ +angular + .module("bawApp.models.datasetItem", []) + .factory("baw.models.datasetItem", [ + "baw.models.ApiBase", + function (ApiBase) { + + class DatasetItem extends ApiBase { + constructor(resource) { + super(resource); + this.customSettings = this.customSettings || null; + } + + } + + return DatasetItem; + }]); diff --git a/src/components/models/models.js b/src/components/models/models.js index 9dc3d47f..588e4bf7 100644 --- a/src/components/models/models.js +++ b/src/components/models/models.js @@ -23,7 +23,8 @@ angular.module( //"bawApp.models.birdWalkService", //"bawApp.models.breadcrumbs", "bawApp.models.userProfile", - //"bawApp.models.authenticator" + //"bawApp.models.authenticator", + "bawApp.models.datasetItem" ]); diff --git a/src/components/services/datasetItem.js b/src/components/services/datasetItem.js new file mode 100644 index 00000000..ed6c474e --- /dev/null +++ b/src/components/services/datasetItem.js @@ -0,0 +1,27 @@ +angular + .module("bawApp.services.datasetItem", []) + .factory( + "DatasetItem", + [ + "$resource", + "$http", + "bawResource", + "$url", + "conf.paths", + "baw.models.datasetItem", + function ($resource, $http, bawResource, $url, paths, DatasetItemModel) { + + var resource = bawResource( + paths.api.routes.datasetItem.showAbsolute, + {datasetId: "@datasetId", datasetItemId: "@datasetItemId"}, + {}); + + resource.datasetItems = function getDatasetItems(dataset_id) { + var url = $url.formatUri(paths.api.routes.datasetItem.listAbsolute, {datasetId: dataset_id}); + return $http.get(url).then(x => DatasetItemModel.makeFromApi(x)); + }; + + return resource; + } + ] + ); \ No newline at end of file diff --git a/src/components/services/services.js b/src/components/services/services.js index 5c17017e..cc105d60 100644 --- a/src/components/services/services.js +++ b/src/components/services/services.js @@ -33,7 +33,8 @@ angular.module( "bawApp.services.birdWalkService", "bawApp.services.breadcrumbs", "bawApp.services.userProfile", - "bawApp.services.authenticator" + "bawApp.services.authenticator", + "bawApp.services.datasetItem" ]);