diff --git a/src/app/citizenScience/bristlebird/bristlebird.js b/src/app/citizenScience/bristlebird/bristlebird.js
index 1943518a..c3bc760c 100644
--- a/src/app/citizenScience/bristlebird/bristlebird.js
+++ b/src/app/citizenScience/bristlebird/bristlebird.js
@@ -22,11 +22,13 @@ class BristlebirdController {
$location,
CitizenScienceCommon,
CsSamples,
- CsLabels,
SampleLabels,
backgroundImage,
paths,
- Question) {
+ Question,
+ $routeParams,
+ StudyService
+ ) {
var self = this;
@@ -34,15 +36,7 @@ class BristlebirdController {
* The name of the css project as it appears in the dataset definition
* @type {string}
*/
- $scope.csProject = "ebb";
-
- /**
- * The duration of each audio sample
- * currently all samples will be the same duration (not set per sample in the dataset)
- * @type {number}
- */
- self.sampleDuration = 25;
-
+ $scope.csProject = $routeParams.studyName;
/**
* The current sample object, including sample id
@@ -55,8 +49,6 @@ class BristlebirdController {
// to be populated after getting samples from dataset
$scope.media = null;
-
- // load populate the onboarding steps as they are loaded.
$scope.onboarding = {};
$scope.onboarding.steps = [
@@ -94,37 +86,7 @@ class BristlebirdController {
};
-
- /**
- * Labels that the user can select.
- * applies one or more tags which are not shown to the user.
- * example response from server
- * [{
- * "tags": ["ebb", "type1"],
- * "name": "Eastern Bristlebird",
- * "examples": [{
- * "annotationId": 124730
- * }, {
- * "annotationId": 124727
- * }, {
- * "annotationId": 98378
- * }]
- * },
- * {
- * "tags": ["ground_parrot", "type1"],
- * "name": "Ground Parrot",
- * "examples": [{
- * "annotationId": 124622
- * }]
- * },
- * {
- * "tags": ["quoll", "type1"],
- * "label": "Spotted Quoll",
- * "examples": []
- * }];
- */
-
- $scope.labels = [];
+ $scope.questionData = {};
// the model passed to ngAudio
$scope.audioElementModel = CitizenScienceCommon.getAudioModel();
@@ -132,14 +94,28 @@ class BristlebirdController {
this.showAudio = CitizenScienceCommon.bindShowAudio($scope);
//TODO: replace hardcoded value with routed study id
- $scope.study_id = 1;
- Question.questions($scope.study_id).then(x => {
- console.log("questions loaded", x);
- //TODO: update to allow multiple questions
- $scope.labels = x.data.data[0].questionData.labels;
- SampleLabels.init(x.data.data[0].id, $scope.study_id);
+
+ StudyService.studyByName($routeParams.studyName).then(x => {
+ var studies = x.data.data;
+ if (studies.length === 0) {
+ console.warn("No study " + $routeParams.studyName + " exists");
+ return;
+ } else if (studies.length > 1) {
+ console.warn("More than one study found. Using the first one");
+ }
+ $scope.study = studies[0];
+ $scope.study_id = $scope.study.id;
+
+ Question.questions($scope.study_id).then(x => {
+ console.log("questions loaded", x);
+ //TODO: update to allow multiple questions
+ $scope.questionData = x.data.data[0].questionData;
+
+ SampleLabels.init(x.data.data[0], $scope.study_id);
+ });
});
+
//SampleLabels.init($scope.csProject, $scope.samples, $scope.labels);
/**
@@ -194,8 +170,7 @@ angular
"bawApp.components.citizenScienceThumbLabels",
"bawApp.components.onboarding",
"bawApp.components.background",
- "bawApp.citizenScience.csSamples",
- "bawApp.citizenScience.csLabels"
+ "bawApp.citizenScience.csSamples"
])
.controller(
"BristlebirdController",
@@ -205,11 +180,12 @@ angular
"$location",
"CitizenScienceCommon",
"CsSamples",
- "CsLabels",
"SampleLabels",
"backgroundImage",
"conf.paths",
"Question",
+ "$routeParams",
+ "Study",
BristlebirdController
])
.controller(
diff --git a/src/app/citizenScience/bristlebird/listen.tpl.html b/src/app/citizenScience/bristlebird/listen.tpl.html
index 0e8aa2e7..dc0dc3a2 100644
--- a/src/app/citizenScience/bristlebird/listen.tpl.html
+++ b/src/app/citizenScience/bristlebird/listen.tpl.html
@@ -47,22 +47,11 @@
Eastern Bristlebird Search
-
-
-
-
-
+
+
+
+
+
diff --git a/src/app/citizenScience/labels/citizenScienceLabels.js b/src/app/citizenScience/labels/citizenScienceLabels.js
index e6ad44c8..d952321d 100644
--- a/src/app/citizenScience/labels/citizenScienceLabels.js
+++ b/src/app/citizenScience/labels/citizenScienceLabels.js
@@ -1,73 +1,73 @@
-var csLabels = angular.module("bawApp.citizenScience.csLabels", ["bawApp.citizenScience.common"]);
-
-
-/**
- * Manages the data for labels that will be applied to cs samples
- */
-csLabels.factory("CsLabels", [
- "CitizenScienceCommon",
- "$http",
- function CsLabels(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 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;
- });
- },
-
- };
-
- return self.publicFunctions;
-
- }]);
-
-
+// var csLabels = angular.module("bawApp.citizenScience.csLabels", ["bawApp.citizenScience.common"]);
+//
+//
+// /**
+// * Manages the data for labels that will be applied to cs samples
+// */
+// csLabels.factory("CsLabels", [
+// "CitizenScienceCommon",
+// "$http",
+// function CsLabels(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 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;
+// });
+// },
+//
+// };
+//
+// return self.publicFunctions;
+//
+// }]);
+//
+//
diff --git a/src/app/citizenScience/responses/citizenScienceSampleLabels.js b/src/app/citizenScience/responses/citizenScienceSampleLabels.js
index af33946c..b02049a1 100644
--- a/src/app/citizenScience/responses/citizenScienceSampleLabels.js
+++ b/src/app/citizenScience/responses/citizenScienceSampleLabels.js
@@ -21,6 +21,8 @@ sampleLabels.factory("SampleLabels", [
// the data for questionResponses. Each question will have a unique key
self.data = {};
self.hasResponse = false;
+ self.allowEmpty = true;
+ self.allowMulti = true;
@@ -29,15 +31,29 @@ sampleLabels.factory("SampleLabels", [
* @param questionid int
* @param studyId int
*/
- self.init = function (questionId = false, studyId = false) {
+ self.init = function (question = false, studyId = false) {
if (studyId !== false) {
self.data.studyId = studyId;
}
- if (questionId !== false) {
- self.data.questionId = questionId;
+ if (question !== false) {
+ self.data.questionId = question.id;
+
+ if (question.data.hasOwnProperty("allowEmpty")) {
+ self.allowEmpty = question.data.allowEmpty;
+ }
+
+ if (question.data.hasOwnProperty("allowMulti")) {
+ self.allowMulti = question.data.allowMulti;
+ }
+ if (question.data.labels.length === 1) {
+ // for binary yes/no there is only one label, therefore no multi select
+ self.allowMulti = false;
+ }
+
}
+
return self.data;
};
@@ -70,6 +86,9 @@ sampleLabels.factory("SampleLabels", [
if (labelId !== undefined) {
self.hasResponse = true;
if (value) {
+ if (!self.allowMulti) {
+ self.data.labels.clear();
+ }
self.data.labels.add(labelId);
} else {
self.data.labels.delete(labelId);
diff --git a/src/app/citizenScience/responses/responses.js b/src/app/citizenScience/responses/responses.js
index e6f84bf4..92bd9692 100644
--- a/src/app/citizenScience/responses/responses.js
+++ b/src/app/citizenScience/responses/responses.js
@@ -1,37 +1,14 @@
+/* This controls the screen where all responses are listed for admin */
class ResponsesController {
constructor($scope,
SampleLabels,
- CsLabels) {
+ Question) {
var self = this;
- SampleLabels.init("ebb");
-
- self.labels = false;
-
- CsLabels.getLabels("ebb").then(labels => {
- $scope.responseData = SampleLabels.getData(labels);
- self.labels = labels;
- });
-
-
-
-
- $scope.$watch("responseData", function (newVal, oldVal) {
- $scope.responseDataString = JSON.stringify(newVal, null, 4);
- },true);
-
- $scope.deleteResponses = function () {
- if (confirm("Are you sure you want to delete all the responses?")) {
- SampleLabels.clearLabels();
- CsLabels.getLabels("ebb").then(labels => {
- $scope.responseData = SampleLabels.getData(labels);
- self.labels = labels;
- });
- }
- };
-
+ // todo: display table of responses.
+ console.log(self);
}
@@ -40,14 +17,13 @@ class ResponsesController {
angular
.module("bawApp.citizenScience.responses", [
- "bawApp.citizenScience.sampleLabels",
- "bawApp.citizenScience.csLabels"
+ "bawApp.citizenScience.sampleLabels"
])
.controller(
"ResponsesController",
[
"$scope",
"SampleLabels",
- "CsLabels",
+ "Question",
ResponsesController
]);
\ No newline at end of file
diff --git a/src/app/citizenScience/thumbLabels/labels.js b/src/app/citizenScience/thumbLabels/labels.js
index 31244d0f..9288f1b9 100644
--- a/src/app/citizenScience/thumbLabels/labels.js
+++ b/src/app/citizenScience/thumbLabels/labels.js
@@ -37,9 +37,13 @@ angular.module("bawApp.components.citizenScienceThumbLabels",
$scope.examplesPosition = "0px";
$scope.$watch(function () {
- return self.labels;
+ return self.questionData;
}, function (newVal, oldVal) {
- self.fetchAnnotationData(newVal);
+ if (newVal !== null && typeof newVal === "object") {
+ if (newVal.hasOwnProperty("labels")) {
+ self.fetchAnnotationData();
+ }
+ }
});
/**
@@ -48,9 +52,10 @@ angular.module("bawApp.components.citizenScienceThumbLabels",
* full "anotation" object that contains the AudioEvent model as well Media model
* @param labels Object
*/
- self.fetchAnnotationData = function (labels) {
+ self.fetchAnnotationData = function () {
// transform labels structure into a single array of annotationsIds
+ var labels = self.questionData.labels;
var annotationIds = [].concat.apply([], labels.map(l => l.examples)).map(e => e.annotationId);
if (annotationIds.length === 0) {
@@ -92,7 +97,7 @@ angular.module("bawApp.components.citizenScienceThumbLabels",
// add annotations back into labels object
response.annotations.forEach(function (annotation) {
- self.labels.forEach(function (l) {
+ self.questionData.labels.forEach(function (l) {
l.examples.forEach(function (e) {
if (e.annotationId === annotation.id) {
e.annotation = annotation;
@@ -101,7 +106,6 @@ angular.module("bawApp.components.citizenScienceThumbLabels",
});
});
- console.log(self.labels);
}, function (httpResponse) {
console.error("Failed to load citizen science example item response.", httpResponse);
@@ -109,6 +113,6 @@ angular.module("bawApp.components.citizenScienceThumbLabels",
};
}],
bindings: {
- labels: "=",
+ questionData: "=",
}
});
\ No newline at end of file
diff --git a/src/app/citizenScience/thumbLabels/labels.tpl.html b/src/app/citizenScience/thumbLabels/labels.tpl.html
index c28fc77f..980d678f 100644
--- a/src/app/citizenScience/thumbLabels/labels.tpl.html
+++ b/src/app/citizenScience/thumbLabels/labels.tpl.html
@@ -1,13 +1,16 @@
-
\ No newline at end of file
diff --git a/src/app/citizenScience/yesnoLabels/_yesnoLabels.scss b/src/app/citizenScience/yesnoLabels/_yesnoLabels.scss
new file mode 100644
index 00000000..be3a9152
--- /dev/null
+++ b/src/app/citizenScience/yesnoLabels/_yesnoLabels.scss
@@ -0,0 +1,141 @@
+$thumb-height: 100;
+
+$triangle: '';
+
+.thumb-labels-container {
+ text-align: center;
+ margin-top: 10px;
+ background: rgba(0, 0, 0, 0.3);
+ position: relative;
+
+ /* flexbox layout of thumbs */
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ .citizen-science-thumb-label {
+
+ position: relative;
+ z-index: 51;
+
+ &.active {
+
+ /* make room under the row for the info */
+ padding-bottom: 400px;
+
+ .citizen-science-thumb {
+
+ outline: rgba(255, 255, 255, 0.5) solid 2px;
+
+ &:after {
+ content: url("data:image/svg+xml;utf8,#{$triangle}");
+ position: absolute;
+ bottom: -15px;
+ z-index: 52;
+ left: 0;
+ right: 0;
+ opacity: 0.9
+ }
+
+ }
+
+ }
+
+ .citizen-science-thumb {
+ position: relative;
+ height: $thumb-height + px;
+ margin: 5px;
+
+ img {
+ height: 100%;
+ }
+
+ .thumb-tick {
+ position: absolute;
+ top:5px;
+ right:8px;
+ color: #fff;
+ text-shadow: 1px 1px 2px #000;
+ }
+
+ }
+
+ citizen-science-label-examples {
+
+ position: absolute;
+ z-index: 50;
+ box-shadow: 5px 5px 10px 10px rgba(0, 0, 0, 0.3);
+ border-radius: 5px;
+ background: rgba(0, 0, 0, 0.9);
+ padding: 5px;
+ top: ($thumb-height + 10) + px;
+
+ .citizen-science-thumb-example {
+
+ margin-top: 10px;
+ display: flex;
+ justify-content: space-evenly;
+
+ .img {
+ flex-grow:1;
+ background-size: cover;
+ background-position: center center;
+ box-shadow: 0px 0px 1px 10px rgba(0, 0, 0, 0.5) inset;
+ max-width: 60%;
+ min-width: 30%;
+ }
+
+ @media (max-width: 520px) {
+ .img {
+ display: none;
+ }
+ }
+
+ annotation-item {
+
+ padding-left: 20px;
+ padding-right: 10px;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ .label-examples-annotations {
+
+ h3 {
+ text-align: center;
+ font-size: 1.2em;
+ max-width: 350px;
+ padding-left: 50px;
+ padding-right: 50px;
+ position: relative;
+ margin-left: auto;
+ margin-right: auto;
+
+ span {
+ max-width: calc(100% - 100px);
+ }
+
+ .arrow {
+ font-size: 1.4em;
+ position: absolute;
+
+ &.prev {
+ left: 0px;
+ }
+
+ &.next {
+ right: 0px;
+ }
+
+ }
+
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/app/citizenScience/yesnoLabels/label.js b/src/app/citizenScience/yesnoLabels/label.js
new file mode 100644
index 00000000..d23b04cd
--- /dev/null
+++ b/src/app/citizenScience/yesnoLabels/label.js
@@ -0,0 +1,56 @@
+angular.module("bawApp.components.citizenScienceThumbLabels.label",
+ [
+ "bawApp.components.citizenScienceThumbLabels.examples",
+ "bawApp.citizenScience.sampleLabels"
+ ])
+ .component("citizenScienceLabel", {
+ templateUrl: "citizenScience/thumbLabels/label.tpl.html",
+ controller: [
+ "$scope",
+ "SampleLabels",
+ function ($scope, SampleLabels) {
+
+ /**
+ * A label is "selected" if it has been applied to the current sample
+ * A label is "active" if it has been clicked to show details
+ */
+
+ var self = this;
+
+ $scope.isSelected = function() {
+ return SampleLabels.getValue(self.label.id);
+ };
+
+ $scope.isShowingDetails = function () {
+ return self.currentDetailsLabelId.value === self.label.id;
+ };
+
+ /**
+ * toggles whether the details pane is showing for the current label
+ */
+ $scope.toggleShowDetails = function () {
+ if ($scope.isShowingDetails()) {
+ self.currentDetailsLabelId.value = -1;
+ } else {
+ self.currentDetailsLabelId.value = self.label.id;
+ }
+ console.log("showing details for label:", self.currentDetailsLabelId.value);
+
+ };
+
+ /**
+ * callback when this label is either attached or detached from the current sample
+ * @param isSelected Boolean
+ */
+ self.onToggleSelected = function (isSelected) {
+ SampleLabels.setValue(self.label.id, isSelected);
+ };
+
+ }],
+ bindings: {
+
+ label: "=",
+ currentDetailsLabelId: "="
+
+ }
+ });
\ No newline at end of file
diff --git a/src/app/citizenScience/yesnoLabels/labels.js b/src/app/citizenScience/yesnoLabels/labels.js
new file mode 100644
index 00000000..01b94b0d
--- /dev/null
+++ b/src/app/citizenScience/yesnoLabels/labels.js
@@ -0,0 +1,24 @@
+angular.module("bawApp.components.citizenScienceThumbLabels",
+ [
+ "bawApp.components.citizenScienceThumbLabels.label"
+ ])
+ .component("citizenScienceLabels", {
+ templateUrl: "citizenScience/yesnoLabels/labels.tpl.html",
+ controller: [
+ "$scope",
+ "$http",
+ "CitizenScienceCommon",
+ "annotationLibraryCommon",
+ "baw.models.AudioEvent",
+ "$q",
+ function ($scope) {
+
+ var self = this;
+ // yesno questions only have one label
+ $scope.label = questionData.labels[0].name
+
+ }],
+ bindings: {
+ questionData: "=",
+ }
+ });
\ No newline at end of file
diff --git a/src/app/citizenScience/yesnoLabels/labels.tpl.html b/src/app/citizenScience/yesnoLabels/labels.tpl.html
new file mode 100644
index 00000000..4512152b
--- /dev/null
+++ b/src/app/citizenScience/yesnoLabels/labels.tpl.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![{{$ctrl.label.name}}]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/baw.paths.nobuild.js b/src/baw.paths.nobuild.js
index be77019b..cf63b605 100644
--- a/src/baw.paths.nobuild.js
+++ b/src/baw.paths.nobuild.js
@@ -92,6 +92,11 @@ module.exports = function (environment) {
"show": "/responses/{responseId}",
"create": "/responses"
},
+ "study": {
+ "list": "/studies",
+ "show": "/studies/{studyId}",
+ "filter": "/studies/filter"
+ },
},
"links": {
"projects": "/projects",
@@ -191,9 +196,9 @@ module.exports = function (environment) {
"libraryItem": "/library/{recordingId}/audio_events/{audioEventId}",
"visualize": "/visualize",
"citizenScience": {
- "listenId":"/citsci/bristlebird/listen/{sampleNum}",
- "listen":"/citsci/bristlebird/listen",
- "responses": "/citsci/bristlebird/responses"
+ "listenId":"/citsci/{studyName}/listen/{sampleNum}",
+ "listen":"/citsci/{studyName}/listen",
+ "responses": "/citsci/{studyName}/responses"
},
"demo": {
"d3": "/demo/d3",
diff --git a/src/components/models/models.js b/src/components/models/models.js
index 0169e4ea..c115aa4f 100644
--- a/src/components/models/models.js
+++ b/src/components/models/models.js
@@ -27,7 +27,8 @@ angular.module(
"bawApp.models.datasetItem",
"bawApp.models.progressEvent",
"bawApp.models.question",
- "bawApp.models.questionResponse"
+ "bawApp.models.questionResponse",
+ "bawApp.models.study"
]);
diff --git a/src/components/models/study.js b/src/components/models/study.js
new file mode 100644
index 00000000..f75dd869
--- /dev/null
+++ b/src/components/models/study.js
@@ -0,0 +1,17 @@
+angular
+ .module("bawApp.models.study", [])
+ .constant("baw.models.study.defaultDatasetId", 1)
+ .factory("baw.models.study", [
+ "baw.models.ApiBase",
+ function (ApiBase) {
+
+ class Study extends ApiBase {
+ constructor(resource) {
+ super(resource);
+ this.customSettings = this.customSettings || null;
+ }
+
+ }
+
+ return Study;
+ }]);
diff --git a/src/components/services/question.js b/src/components/services/question.js
index ae67604d..f4255787 100644
--- a/src/components/services/question.js
+++ b/src/components/services/question.js
@@ -24,7 +24,7 @@ angular
};
resource.question = function getQuestion(questionId) {
- var url = $url.formatUri(paths.api.routes.datasetItem.showAbsolute, {questionId: questionId});
+ var url = $url.formatUri(paths.api.routes.question.showAbsolute, {questionId: questionId});
return $http.get(url).then(x => QuestionModel.makeFromApi(x));
};
diff --git a/src/components/services/services.js b/src/components/services/services.js
index 0ee5eb57..e7f97aff 100644
--- a/src/components/services/services.js
+++ b/src/components/services/services.js
@@ -38,6 +38,7 @@ angular.module(
"bawApp.services.progressEvent",
"bawApp.services.question",
"bawApp.services.questionResponse",
+ "bawApp.services.study"
]);
diff --git a/src/components/services/study.js b/src/components/services/study.js
new file mode 100644
index 00000000..c2291727
--- /dev/null
+++ b/src/components/services/study.js
@@ -0,0 +1,33 @@
+angular
+ .module("bawApp.services.study", [])
+ .factory(
+ "Study",
+ [
+ "$resource",
+ "$http",
+ "bawResource",
+ "$url",
+ "conf.paths",
+ "baw.models.study",
+ function ($resource, $http, bawResource, $url, paths, StudyModel) {
+
+ var resource = bawResource(
+ paths.api.routes.study.list,
+ {studyId: "@studyId"},
+ {});
+
+
+ resource.study = function getStudy(studyId) {
+ var url = $url.formatUri(paths.api.routes.study.showAbsolute, {studyId: studyId});
+ return $http.get(url).then(x => StudyModel.makeFromApi(x));
+ };
+
+ resource.studyByName = function getStudyByName(studyName) {
+ var url = $url.formatUri(paths.api.routes.study.filterAbsolute, {filter_name: studyName});
+ return $http.get(url).then(x => StudyModel.makeFromApi(x));
+ };
+
+ return resource;
+ }
+ ]
+ );
\ No newline at end of file