diff --git a/.travis.yml b/.travis.yml index 2d7284e2c..8b98109e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ branches: only: - master - develop + - feature/6.1.0-fixes services: - postgresql addons: diff --git a/build.gradle b/build.gradle index 437fcdfe4..62bdbac1d 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ plugins { id "com.virgo47.ClasspathJar" version "1.0.0" } -version "6.0.2" +version "6.1.0" group "au.org.ala" description "Digivol application" diff --git a/grails-app/assets/audio/klankbeeld_suburb-sunday-713am.wav b/grails-app/assets/audio/klankbeeld_suburb-sunday-713am.wav new file mode 100644 index 000000000..0bc9b5d2e Binary files /dev/null and b/grails-app/assets/audio/klankbeeld_suburb-sunday-713am.wav differ diff --git a/grails-app/assets/images/icon_pause3.png b/grails-app/assets/images/icon_pause3.png new file mode 100644 index 000000000..5e8ed0fec Binary files /dev/null and b/grails-app/assets/images/icon_pause3.png differ diff --git a/grails-app/assets/images/icon_play3.png b/grails-app/assets/images/icon_play3.png new file mode 100644 index 000000000..7bf54ea98 Binary files /dev/null and b/grails-app/assets/images/icon_play3.png differ diff --git a/grails-app/assets/images/icons-audio-52.png b/grails-app/assets/images/icons-audio-52.png new file mode 100644 index 000000000..7d8e710c0 Binary files /dev/null and b/grails-app/assets/images/icons-audio-52.png differ diff --git a/grails-app/assets/javascripts/audio-template-config.js b/grails-app/assets/javascripts/audio-template-config.js new file mode 100644 index 000000000..3020da6c9 --- /dev/null +++ b/grails-app/assets/javascripts/audio-template-config.js @@ -0,0 +1,7 @@ +//= require angular/ng-file-upload +//= require underscore +//= require compile/csv/csv.js +//= require compile/soundmanager2/soundmanager2.js +//= require inline-audio-player.js +//=require_self + diff --git a/grails-app/assets/javascripts/digivol-stageImage.js b/grails-app/assets/javascripts/digivol-stageImage.js index 8df802d85..2237da9a9 100644 --- a/grails-app/assets/javascripts/digivol-stageImage.js +++ b/grails-app/assets/javascripts/digivol-stageImage.js @@ -132,13 +132,16 @@ function digivolStageFiles (config, self) { updateStagedImageDisplay(); } + var imageFileTypes = ['image/jpeg', 'image/png', 'image/gif', 'text/plain']; + var audioFileTypes = ['audio/aac', 'audio/wav', 'audio/mpeg', 'audio/x-m4a', 'audio/ogg', 'audio/vnd.dlna.adts'] + var r = new Resumable({ - target: config.uploadFileUrl, + target: (config.isAudioProject) ? config.uploadAudioUrl : config.uploadFileUrl, query: query, chunkRetryInterval: 1000, withCredentials: true, xhrTimeout: 30000, - fileType: ['image/jpeg', 'image/png', 'image/gif', 'text/plain'], + fileType: (config.isAudioProject) ? audioFileTypes : imageFileTypes, fileTypeErrorCallback: function (file, errorCount) { console.log("fileTypeErrorCallback", file, errorCount); }, diff --git a/grails-app/assets/javascripts/digivol-stats.js b/grails-app/assets/javascripts/digivol-stats.js index 323d9e5ab..8f32b2b8d 100644 --- a/grails-app/assets/javascripts/digivol-stats.js +++ b/grails-app/assets/javascripts/digivol-stats.js @@ -4,68 +4,111 @@ //=require_self function digivolStats(config) { - var stats = angular.module('stats', ['digivol']); + var stats = angular.module('stats', ['digivol']); - stats.controller('StatsCtrl', [ - '$scope', '$http', '$log', - function ($scope, $http, $log) { - $scope.loading = true; + stats.controller('StatsCtrl', [ + '$scope', '$http', '$log', + function ($scope, $http, $log) { + $scope.lbLoading = true; + $scope.conLoading = true; - $scope.transcriberCount = null; - $scope.completedTasks = null; - $scope.totalTasks = null; + $scope.transcriberCount = null; + $scope.completedTasks = null; + $scope.totalTasks = null; - $scope.daily = { userId: -1, email: '', name: '', score: null }; - $scope.weekly = { userId: -1, email: '', name: '', score: null }; - $scope.monthly = { userId: -1, email: '', name: '', score: null }; - $scope.alltime = { userId: -1, email: '', name: '', score: null }; + $scope.daily = {userId: -1, email: '', name: '', score: null}; + $scope.weekly = {userId: -1, email: '', name: '', score: null}; + $scope.monthly = {userId: -1, email: '', name: '', score: null}; + $scope.alltime = {userId: -1, email: '', name: '', score: null}; - $scope.contributors = []; + $scope.contributors = []; - $scope.avatarUrl = function (user) { - var email = user.email || ""; - return "//www.gravatar.com/avatar/" + email + "?s=40&d=mm" - }; + $scope.avatarUrl = function (user) { + var email = user.email || ""; + return "//www.gravatar.com/avatar/" + email + "?s=40&d=mm" + }; - $scope.userProfileUrl = function(user) { - var id = user.userId || ""; - return config.userProfileUrl.replace("-1", id); - }; + $scope.userProfileUrl = function (user) { + var id = user.userId || ""; + return config.userProfileUrl.replace("-1", id); + }; - $scope.projectUrl = function(project) { - var id = project.projectId || ""; - return config.projectUrl.replace('-1', id); - }; + $scope.projectUrl = function (project) { + var id = project.projectId || ""; + return config.projectUrl.replace('-1', id); + }; - $scope.additionalTranscribedThumbs = function(contrib) { - return Math.max(contrib.transcribedItems - 5, 0); - }; + $scope.additionalTranscribedThumbs = function (contrib) { + return Math.max(contrib.transcribedItems - 5, 0); + }; + $scope.taskSummaryUrl = function (thumb) { + var id = thumb.id || ""; + return config.taskSummaryUrl.replace('-1', id); + }; - $scope.taskSummaryUrl = function(thumb) { - var id = thumb.id || ""; - return config.taskSummaryUrl.replace('-1', id); - }; + var tags = ''; + if (config.tags !== 'null') { + tags = config.tags + } - var p = $http.get(config.statsUrl, { - params: { - institutionId: config.institutionId, - projectId: config.projectId, - projectType: config.projectType, - tags: config.tags, - maxContributors: config.maxContributors, - disableStats: config.disableStats, - disableHonourBoard: config.disableHonourBoard + var p = $http.get(config.statsUrl, { + params: { + institutionId: config.institutionId, + projectId: config.projectId, + projectType: config.projectType, + tags: tags, + maxContributors: config.maxContributors, + disableStats: config.disableStats, + disableHonourBoard: config.disableHonourBoard + } + }); + p.then(function (resp) { + angular.extend($scope, resp.data); + $scope.lbLoading = false; + }, + function (resp) { + $log.error("Got error response for leaderboard", resp); + }); + + //console.log("Disable contributors: " + config.disableContribution); + if (config.disableContribution === false) { + console.log("Getting contributors"); + var c = $http.get(config.contributorsUrl, { + params: { + institutionId: config.institutionId, + projectId: config.projectId, + projectType: config.projectType, + tags: config.tags, + maxContributors: config.maxContributors + } + }); + c.then(function (resp) { + angular.extend($scope, resp.data); + $scope.conLoading = false; + }, + function (resp) { + $log.error("Got error response for contributors", resp); + }); + } else if (config.disableForumActivity === false) { + // Can't do contribution and forum activity together (forum activity is included in contribution) + console.log("Getting forum activity"); + var f = $http.get(config.forumActivityUrl, { + params: { + institutionId: config.institutionId, + projectId: config.projectId, + maxPosts: config.maxContributors + } + }); + f.then(function (resp) { + angular.extend($scope, resp.data); + $scope.conLoading = false; + }, + function (resp) { + $log.error("Got error response for forum activity", resp); + }); + } } - }); - p.then(function (resp) { - angular.extend($scope, resp.data); - $scope.loading = false; - }, - function (resp) { - $log.error("Got error response for leaderboard", resp); - }); - } - ]); + ]); } diff --git a/grails-app/assets/javascripts/inline-audio-player.js b/grails-app/assets/javascripts/inline-audio-player.js new file mode 100644 index 000000000..ed32cd68c --- /dev/null +++ b/grails-app/assets/javascripts/inline-audio-player.js @@ -0,0 +1,270 @@ +/** + * + * SoundManager 2: Play MP3 links "in-place" + * ---------------------------------------------- + * + * http://schillmania.com/projects/soundmanager2/ + * + * A simple demo making MP3s playable "inline" + * and easily styled/customizable via CSS. + * + * Requires SoundManager 2 Javascript API. + * + */ + +(function() { + + /* global soundManager, document, window, navigator, event */ + + 'use strict'; + + function InlinePlayer() { + var self = this; + var pl = this; + var sm = soundManager; // soundManager instance + var isIE = (navigator.userAgent.match(/msie/i)); + this.playableClass = 'inline-playable'; // CSS class for forcing a link to be playable (eg. doesn't have .MP3 in it) + this.excludeClass = 'inline-exclude'; // CSS class for ignoring MP3 links + this.links = []; + this.sounds = []; + this.soundsByURL = []; + this.indexByURL = []; + this.lastSound = null; + this.soundCount = 0; + + this.config = { + playNext: false, // stop after one sound, or play through list until end + autoPlay: false // start playing the first sound right away + }; + + this.css = { + // CSS class names appended to link during various states + sDefault: 'sm2_link', // default state + sLoading: 'sm2_loading', + sPlaying: 'sm2_playing', + sPaused: 'sm2_paused' + }; + + this.addEventHandler = (typeof window.addEventListener !== 'undefined' ? function(o, evtName, evtHandler) { + return o.addEventListener(evtName, evtHandler, false); + } : function(o, evtName, evtHandler) { + o.attachEvent('on' + evtName, evtHandler); + }); + + this.removeEventHandler = (typeof window.removeEventListener !== 'undefined' ? function(o, evtName, evtHandler) { + return o.removeEventListener(evtName, evtHandler, false); + } : function(o, evtName, evtHandler) { + return o.detachEvent('on' + evtName, evtHandler); + }); + + this.classContains = function(o, cStr) { + return (typeof o.className !== 'undefined' ? o.className.match(new RegExp('(\\s|^)' + cStr + '(\\s|$)')) : false); + }; + + this.addClass = function(o, cStr) { + if (!o || !cStr || self.classContains(o, cStr)) return; + o.className = (o.className ? o.className + ' ' : '') + cStr; + }; + + this.removeClass = function(o, cStr) { + if (!o || !cStr || !self.classContains(o, cStr)) return; + o.className = o.className.replace(new RegExp('( ' + cStr + ')|(' + cStr + ')', 'g'), ''); + }; + + this.getSoundByURL = function(sURL) { + return (typeof self.soundsByURL[sURL] !== 'undefined' ? self.soundsByURL[sURL] : null); + }; + + this.isChildOfNode = function(o, sNodeName) { + if (!o || !o.parentNode) { + return false; + } + sNodeName = sNodeName.toLowerCase(); + do { + o = o.parentNode; + } while (o && o.parentNode && o.nodeName.toLowerCase() !== sNodeName); + return (o.nodeName.toLowerCase() === sNodeName ? o : null); + }; + + this.events = { + + // handlers for sound events as they're started/stopped/played + + play: function() { + pl.removeClass(this._data.oLink, this._data.className); + this._data.className = pl.css.sPlaying; + pl.addClass(this._data.oLink, this._data.className); + }, + + stop: function() { + pl.removeClass(this._data.oLink, this._data.className); + this._data.className = ''; + }, + + pause: function() { + pl.removeClass(this._data.oLink, this._data.className); + this._data.className = pl.css.sPaused; + pl.addClass(this._data.oLink, this._data.className); + }, + + resume: function() { + pl.removeClass(this._data.oLink, this._data.className); + this._data.className = pl.css.sPlaying; + pl.addClass(this._data.oLink, this._data.className); + }, + + finish: function() { + pl.removeClass(this._data.oLink, this._data.className); + this._data.className = ''; + if (pl.config.playNext) { + var nextLink = (pl.indexByURL[this._data.oLink.href] + 1); + if (nextLink < pl.links.length) { + pl.handleClick({ target: pl.links[nextLink] }); + } + } + } + + }; + + this.stopEvent = function(e) { + if (typeof e !== 'undefined' && typeof e.preventDefault !== 'undefined') { + e.preventDefault(); + } else if (typeof event !== 'undefined' && typeof event.returnValue !== 'undefined') { + event.returnValue = false; + } + return false; + }; + + this.getTheDamnLink = (isIE) ? function(e) { + // I really didn't want to have to do this. + return (e && e.target ? e.target : window.event.srcElement); + } : function(e) { + return e.target; + }; + + this.handleClick = function(e) { + // a sound link was clicked + if (typeof e.button !== 'undefined' && e.button > 1) { + // ignore right-click + return true; + } + var o = self.getTheDamnLink(e); + if (o.nodeName.toLowerCase() !== 'a') { + o = self.isChildOfNode(o, 'a'); + if (!o) return true; + } + if (!o.href || (!sm.canPlayLink(o) && !self.classContains(o, self.playableClass)) || self.classContains(o, self.excludeClass)) { + return true; // pass-thru for non-MP3/non-links + } + var soundURL = (o.href); + var thisSound = self.getSoundByURL(soundURL); + if (thisSound) { + // already exists + if (thisSound === self.lastSound) { + // and was playing (or paused) + thisSound.togglePause(); + } else { + // different sound + sm._writeDebug('sound different than last sound: ' + self.lastSound.id); + if (self.lastSound) { + self.stopSound(self.lastSound); + } + thisSound.togglePause(); // start playing current + } + } else { + // stop last sound + if (self.lastSound) { + self.stopSound(self.lastSound); + } + // create sound + thisSound = sm.createSound({ + id: 'inlineMP3Sound' + (self.soundCount++), + url: soundURL, + onplay: self.events.play, + onstop: self.events.stop, + onpause: self.events.pause, + onresume: self.events.resume, + onfinish: self.events.finish, + type: (o.type || null) + }); + // tack on some custom data + thisSound._data = { + oLink: o, // DOM node for reference within SM2 object event handlers + className: self.css.sPlaying + }; + self.soundsByURL[soundURL] = thisSound; + self.sounds.push(thisSound); + thisSound.play(); + } + + self.lastSound = thisSound; // reference for next call + + if (typeof e !== 'undefined' && typeof e.preventDefault !== 'undefined') { + e.preventDefault(); + } else { + event.returnValue = false; + } + return false; + }; + + this.stopSound = function(oSound) { + soundManager.stop(oSound.id); + soundManager.unload(oSound.id); + }; + + this.stopAll = function() { + soundManager.stopAll(); + } + + this.init = function() { + sm._writeDebug('inlinePlayer.init()'); + var oLinks = document.getElementsByTagName('a'); + // grab all links, look for .mp3 + var foundItems = 0; + for (var i = 0, j = oLinks.length; i < j; i++) { + if ((sm.canPlayLink(oLinks[i]) || self.classContains(oLinks[i], self.playableClass)) && !self.classContains(oLinks[i], self.excludeClass)) { + self.addClass(oLinks[i], self.css.sDefault); // add default CSS decoration + self.links[foundItems] = (oLinks[i]); + self.indexByURL[oLinks[i].href] = foundItems; // hack for indexing + foundItems++; + } + } + if (foundItems > 0) { + self.addEventHandler(document, 'click', self.handleClick); + if (self.config.autoPlay) { + self.handleClick({ target: self.links[0], preventDefault: function() {} }); + } + } + sm._writeDebug('inlinePlayer.init(): Found ' + foundItems + ' relevant items.'); + }; + + this.init(); + + } + + // var inlinePlayer = null; + +// in the event any external script references this... + window.inlinePlayer = null; + + soundManager.setup({ + // disable or enable debug output + debugMode: true, + // use HTML5 audio for MP3/MP4, if available + preferFlash: false, + useFlashBlock: true, + // path to directory containing SM2 SWF + url: '', + // optional: enable MPEG-4/AAC support (requires flash 9) + flashVersion: 9 + }); + + soundManager.onready(function() { + // soundManager.createSound() etc. may now be called + window.inlinePlayer = new InlinePlayer(); + // console.log("Initialising inline player"); + // console.log(window.inlinePlayer); + // console.log("Inline player initialised"); + }); + +}()); \ No newline at end of file diff --git a/grails-app/assets/javascripts/transcribe/audiotranscribe.js b/grails-app/assets/javascripts/transcribe/audiotranscribe.js new file mode 100644 index 000000000..00e48be6f --- /dev/null +++ b/grails-app/assets/javascripts/transcribe/audiotranscribe.js @@ -0,0 +1,589 @@ +//= encoding UTF-8 +// assume jquery +// assume underscore +// assume bootbox +//= require mustache +//= require dotdotdot +//= require transitionend +//= require marked +//= require compile/soundmanager2/soundmanager2.js +//= require inline-audio-player.js +//= require_self +function wildlifespotter(wsParams, imagePrefix, recordValues, placeholders) { + jQuery(function ($) { + + var filterText; + var categoryFilters = {}; + var $searchInput = $('#ct-search-input'); + var $filterLinks = $('.category-filter a'); + var selectedIndicies = {}; + var AUDIO_DETAIL = 'audio-sample-detail'; + var AUDIO_LIST = 'audio-sample-list'; + + // selection of badge thumbnail + $('#ct-container').on('click', '.bvpBadgeMain', function () { + registerSelectionClick($(this)); + }); + + $('#ct-container').on('click', '.audio-badge', function () { + registerSelectionClick($(this)); + }); + + $('#ct-container').on('click', '.ws-selected', function () { + registerSelectionClick($(this)); + }); + + // selection of detail checkmark + $('#detail-template').on('click', '.ws-selector', function () { + registerSelectionClick($(this)); + }); + + function registerSelectionClick($e) { + //var $this = e; + var index = $e.closest('[data-item-index]').data('item-index'); + toggleIndex(index); + } + + $('#ct-container').on('click', '.animalDelete', function () { + var $this = $(this); + var index = $this.closest('[data-item-index]').data('item-index'); + deselectIndex(index); + }); + + function toggleIndex(index) { + if (selectedIndicies.hasOwnProperty(index)) { + deselectIndex(index); + } else { + selectIndex(index); + } + } + + function selectIndex(index) { + selectedIndicies[index] = {count: 1, notes: '', editorOpen: false}; + syncSelections(); + } + + function deselectIndex(index) { + delete selectedIndicies[index]; + syncSelections(); + } + + function syncSelections() { + var usKeys = _.chain(selectedIndicies).keys().filter(function (idx) { + return selectedIndicies[idx].count > 0; + }); + var dataItemIndexes = usKeys.map(function (v, i, l) { + return "[data-item-index='" + v + "']" + }); + var wsSelectionIndicator = dataItemIndexes.map(function (v, i, l) { + return v + " .ws-selected"; + }); + var wsSelectorIndicator = dataItemIndexes.map(function (v, i, l) { + return v + " .ws-selector"; + }); + $(wsSelectionIndicator.value().join(", ")).addClass('selected'); + $(wsSelectorIndicator.value().join(", ")).attr('aria-selected', 'true'); + $('[data-item-index]:not(' + dataItemIndexes.value().join(',') + ') .ws-selected').removeClass('selected').attr('aria-selected', 'false'); + $('[data-item-index]:not(' + dataItemIndexes.value().join(',') + ') .ws-selector').attr('aria-selected', 'false'); + var length = usKeys.value().length; + if (length == 0) { + hideSelectionPanel(); + } else { + showSelectionPanel(); + } + generateFormFields(); + } + + function hideSelectionPanel() { + var parent = $('#classification-status-no-animals-selected'); + var other = $('#classification-status-animals-selected'); + parent.show(); + other.hide(); + } + + function showSelectionPanel() { + var parent = $('#classification-status-animals-selected'); + var other = $('#classification-status-no-animals-selected'); + + var templateObj = { + selectedAnimals: _.chain(selectedIndicies).keys().map(function (v, i) { + + return { + index: v, + name: wsParams.animals[v].vernacularName, + scientificName: wsParams.animals[v].scientificName, + options: _([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).map(function (opt, i) { + return { + val: opt, + selected: selectedIndicies[v].count == opt ? 'selected' : '', + isSelected: selectedIndicies[v].count == opt ? 'true' : 'false' + }; + }), + comment: selectedIndicies[v].comment + }; + }).sortBy(function (o) { + return o.index; + }).value() + }; + mu.replaceTemplate(parent, 'status-detail-list-template', templateObj); + + parent.show(); + other.hide(); + } + + // Comments + $('#ct-container').on('change keyup paste input propertychange', '.editClassificationComments textarea', function () { + var $this = $(this); + var idx = $this.closest('[data-item-index]').data('item-index'); + var comment = $this.val(); + selectedIndicies[idx].comment = comment; + $('[data-item-index="' + idx + '"] .classificationComments').text(comment); + generateFormFields(); + }); + + // $('#ct-container').on('change', 'select.numAnimals', function () { + // var $this = $(this); + // var idx = $this.closest('[data-item-index]').data('item-index'); + // var count = $this.val(); + // selectedIndicies[idx].count = parseInt(count); + // generateFormFields(); + // }); + + $('#ct-container').on('click', '.editCommentButton', function () { + var $this = $(this); + var idx = $this.closest('[data-item-index]').data('item-index'); + $('[data-item-index="' + idx + '"] .editClassificationComments').show(); + $('[data-item-index="' + idx + '"] .classificationComments').hide(); + $('[data-item-index="' + idx + '"] .saveCommentButton').show(); + $('[data-item-index="' + idx + '"] .editCommentButton').hide(); + selectedIndicies[idx].editorOpen = true; + }); + + $('#ct-container').on('click', '.saveCommentButton', function () { + var $this = $(this); + var idx = $this.closest('[data-item-index]').data('item-index'); + $('[data-item-index="' + idx + '"] .classificationComments').show(); + $('[data-item-index="' + idx + '"] .editClassificationComments').hide(); + $('[data-item-index="' + idx + '"] .editCommentButton').show(); + $('[data-item-index="' + idx + '"] .saveCommentButton').hide(); + selectedIndicies[idx].editorOpen = false; + }); + + // animal filtration + function filterAnimals() { + var animals = wsParams.animals; + + var elements = $('#ct-animals-present').find('[data-item-index]'); + var parts = _.partition(elements, function (e, i) { + var $e = $(e); + var index = $e.data('item-index'); + var animal = animals[index]; + var matchesVernacularName = filterText ? animal.vernacularName.toLocaleLowerCase().indexOf(filterText) !== -1 : true; + var matchesScientificName = filterText ? animal.scientificName.toLocaleLowerCase().indexOf(filterText) !== -1 : true; + var matchesFilters = true; + for (var cat in categoryFilters) { + if (animal.categories[cat] !== categoryFilters[cat]) matchesFilters = false; + } + return (matchesScientificName || matchesVernacularName) && matchesFilters; + }); + $(parts[0]).show(100); + $(parts[1]).hide(100); + } + + $filterLinks.on('click', function () { + var $this = $(this); + var categories = wsParams.categories; + var catIdx = $this.data('cat-idx'); + var entryIdx = $this.data('entry-idx'); + var cat = categories[catIdx]; + var ent = cat.entries[entryIdx]; + + categoryFilters[cat.name] = ent.name; + filterAnimals(); + ensureFilterSummary(); + }); + + $searchInput.on('change keyup paste input propertychange', function () { + filterText = $searchInput.val().toLocaleLowerCase(); + filterAnimals(); + }); + + function ensureFilterSummary() { + var other = $('#ct-animals-no-filter'); + var parent = $('#ct-animals-filter'); + var categories = wsParams.categories; + var model = {categories: []}; + for (var i = 0; i < wsParams.categories.length; ++i) { + var cat = categories[i]; + var selected = categoryFilters[cat.name]; + if (!selected) selected = 'All'; + model.categories.push({name: cat.name, value: selected}); + } + parent.empty(); + mu.appendTemplate(parent, 'filter-summary', model); + other.hide(); + parent.show(); + } + + function setFilterText(text) { + filterText = text; + $('#ct-search-input').val(text); + } + + // clear all filters + $('#ct-animals-filter').on('click', 'a.clearall', function () { + for (var cat in categoryFilters) { + delete categoryFilters[cat]; + } + setFilterText(''); + var other = $('#ct-animals-no-filter'); + var parent = $('#ct-animals-filter'); + other.show(); + parent.hide(); + filterAnimals(); + }); + + // Show info pane + + // Show detail + function showDetail(index) { + var $container = $('#ws-dynamic-container'); + $container.empty(); + + var animals = wsParams.animals; + var animal = animals[index]; + var description = animal.description != null ? marked(animal.description) : ""; + var templateObj = { + animal: { + selected: selectedIndicies.hasOwnProperty(index) && selectedIndicies[index].count > 0 ? 'selected' : '', + vernacularName: animal.vernacularName, + scientificName: animal.scientificName, + description: description, + images: _.map(animal.images, function (v, i, l) { + return { + idx: i, + hash: v.hash, + active: i == 0 ? 'active' : '' + }; + }), + audio: _.map(animal.audio, function (v, i, l) { + return { + idx: i, + hash: v.hash, + ext: v.ext + } + }) + }, + itemIndex: index + }; + + var detail = mu.appendTemplate($container, 'detail-template', templateObj); + //var carousel = $('#ct-full-image-carousel'); + var carousel = detail.find('.carousel'); + carousel.carousel({interval: false}); + detail.find('[title]').tooltip(); + + switchCtPage('#ws-dynamic-container'); + + addSampleWaveforms(AUDIO_DETAIL); + } + + function hideDetail() { + var $container = $('#ws-dynamic-container'); + $container.empty(); + switchCtPage('#ct-animals-present'); + destoryDetailSurfers(AUDIO_DETAIL); + } + + $('#ct-container').on('click', '.ws-info', function (e) { + e.stopPropagation(); + var $this = $(this); + // var idxElem = $this.closest('[data-item-index]'); + // var idx = idxElem.data('item-index'); + // stopAudio(); + // showDetail(idx); + infoButton($this); + }); + + $('#ct-container').on('click', '.audio-info', function (e) { + e.stopPropagation(); + var $this = $(this); + // var idxElem = $this.closest('[data-item-index]'); + // var idx = idxElem.data('item-index'); + // stopAudio(); + // showDetail(idx); + infoButton($this); + }); + + function infoButton(e) { + var $this = $(e); + var idxElem = $this.closest('[data-item-index]'); + var idx = idxElem.data('item-index'); + stopAudio(); + showDetail(idx); + } + + $('#ct-container').on('click', '.ws-full-image-carousel-close', function (e) { + e.stopPropagation(); + var $this = $(this); + stopAudio(); + hideDetail(); + }); + + function togglePlayButton(e, forceStopped) { + if (forceStopped === undefined || forceStopped === null) forceStopped = false; + var $this = $(e); + var pause = ''; + var play = ''; + if (forceStopped) { + $this.empty(); + $this.append(play); + } else { + if ($this.html() === play) { + $this.empty(); + $this.append(pause); + } else { + $this.empty(); + $this.append(play); + } + } + } + + var $ctq = $('#camera-trap-questions'); + var transitionendname = transitionEnd($ctq).whichTransitionEnd(); + + function switchCtPage(to) { + var $ctq = $('#camera-trap-questions'); + // kill any existing transition + $ctq.find('.ct-item.fading').removeClass('fading'); + var otherActives = $ctq.find('.ct-item.active') + .not(to); + otherActives.removeClass('active'); + if (transitionendname) otherActives.addClass('fading'); + $(to).addClass('active'); + + // var $ctqn = $('#ct-questions-nav'); + // $ctqn.find('button.active').removeClass('active'); + // $ctqn.find('button[data-target="'+to+'"]').addClass('active'); + + var summary = to == '#ct-animals-summary'; + $('#btnNext').toggleClass('hidden', summary); + // $('.bvp-submit-button').toggleClass('hidden', !summary); + } + + var wavesurfers = []; + var detailSurfers = []; + function addSampleWaveforms(selector) { + //console.log('adding sample waves3'); + var surferList = [].map.call(document.querySelectorAll("div." + selector), function (element) { + //console.log("Adding wave"); + return { + wave: WaveSurfer.create({ + container: element, + backgroundColor: 'white', + waveColor: '#a1a1a1', + progressColor: '#d5502a', + hideScrollbar: true, + height: 40, + barMinHeight: 30, + barHeight: 35, + normalize: true + }), + url: element.getAttribute('data-audio-file'), + playLink: element.getAttribute('data-play-link'), + selector: selector + }; + }); + + surferList.forEach(function (item, index) { + item.wave.load(item.url); + var button = document.querySelector('.' + selector + '-play[data-action-play="' + item.playLink + '"]'); + button.addEventListener('click', item.wave.playPause.bind(item.wave)); + button.addEventListener('click', function(e) { + var $this = $(this); + togglePlayButton($this); + }); + item.wave.on('finish', function () { + console.log('audio finished'); + togglePlayButton(button, true); + }); + item.button = button; + + addSurferToList(item, selector); + }); + } + + function addSurferToList(item, selector) { + var exists = false; + wavesurfers.forEach(function (existingItem, index) { + if (item.selector === existingItem.selector && item.playLink === existingItem.playLink) { + exists = true; + } + }); + if (!exists) wavesurfers.push(item); + } + + function destoryDetailSurfers(selector) { + wavesurfers.forEach(function (item, index) { + if (item.selector === selector) { + item.wave.destroy(); + wavesurfers.splice(index, 1); + } + }); + } + + function stopAudio() { + wavesurfers.forEach(function (item, index) { + console.log("Wavefile: index: [" + index + "], selector: [" + item.selector + "], link: [" + item.playLink + "]"); + console.log(item.wave.isPlaying()); + // console.log(item); + if (item.wave.isPlaying()) { + item.wave.stop(); + togglePlayButton(item.button, true); + } + }); + + // console.log("stopping inline player"); + // console.log(window.inlinePlayer); + if (window.inlinePlayer !== undefined && window.inlinePlayer !== null) { + window.inlinePlayer.stopAll(); + } + } + + // Cycling Thumbnails + function cycleImages() { + $('#ct-animals-list .cycler').filter(function (i) { + return $("img", this).length > 1 + }).each(function (e) { + var $this = $(this); + var $active = $this.find('.active'); + var nextImage = $active.next('img'); + var $next = (nextImage.length > 0) ? nextImage : $this.find('img:first'); + $active.removeClass('active'); + $next.addClass('active'); + }); + } + + setInterval(cycleImages, 7000); + + // Sync save + function syncSelectionState() { + generateFormFields(); + } + + function generateFormFields() { + var $ctFields = $('#ct-fields'); + $ctFields.empty(); + if (_.keys(selectedIndicies).length > 0) { + $('input[name=recordValues\\.0\\.noAudibleAnimal]').removeAttr('checked'); + $('input[name=recordValues\\.0\\.problemWithAudio]').removeAttr('checked'); + if (recordValues && recordValues[0]) { + delete recordValues[0].noAudibleAnimal; + delete recordValues[0].problemWithAudio; + } + } else { + if (recordValues && recordValues[0]) { + delete recordValues[0].vernacularName; + delete recordValues[0].scientificName; + //delete recordValues[0].individualCount; + } + } + var i = 0; + _.each(selectedIndicies, function (value, key, list) { + mu.appendTemplate($ctFields, 'input-template', { + id: 'recordValues.' + i + '.vernacularName', + value: wsParams.animals[key].vernacularName + }); + mu.appendTemplate($ctFields, 'input-template', { + id: 'recordValues.' + i + '.scientificName', + value: wsParams.animals[key].scientificName + }); + // mu.appendTemplate($ctFields, 'input-template', { + // id: 'recordValues.' + i + '.individualCount', + // value: value.count + // }); + mu.appendTemplate($ctFields, 'input-template', { + id: 'recordValues.' + i + '.comment', + value: value.comment + }); + ++i; + }); + } + + function syncRecordValues() { + var animals = wsParams.animals; + _.chain(recordValues).keys().sort().each(function (e, i, l) { + + var key = parseInt(e); + if (isNaN(key)) { + return; + } + + var v = recordValues[e]; + + var index = _.findIndex(animals, function (a) { + return a.vernacularName === v.vernacularName || a.scientificName === v.scientificName; + }); + //console.log("Any matching animals: ["+ index +"]"); + + if (index >= 0) { + selectedIndicies[index] = { + comment: v.comment, + count: 1, // Hard coded at this time, only one animal is assumed. + editorOpen: false + }; + //console.log(selectedIndicies); + } + }); + } + + transcribeValidation.addCustomValidator(function (errorList) { + var q1 = $('input[name=recordValues\\.0\\.noAudibleAnimal]:checked').val(); + var q2 = $('input[name=recordValues\\.0\\.problemWithAudio]:checked').val(); + var q3 = _.keys(selectedIndicies).length > 0; + if (!q1 && !q2 && !q3) { + + errorList.push({ + element: null, + message: "You must either indicate that there are no animals, there's a problem with the audio file or select at least one animal before you can submit", + type: "Error" + }); + } + }); + + transcribeValidation.setErrorRenderFunctions(function (errorList) {}, + function () {}); + + submitRequiresConfirmation = true; + postValidationFunction = function (validationResults) { + console.log("I'm in the audio validation."); + if (validationResults.errorList.length > 0) bootbox.alert("

Invalid selection