Mercurial > hg > webaudioevaluationtool
changeset 3083:2b550d4c7fde
Merge branch 'vnext' into Dev_main
# Conflicts:
# tests/examples/APE_example.xml
# tests/examples/horizontal_example.xml
# tests/examples/mushra_example.xml
# tests/examples/radio_example.xml
author | Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk> |
---|---|
date | Wed, 22 Nov 2017 10:10:44 +0000 |
parents | d25e09e3b8fe (diff) 396b9aac3bb9 (current diff) |
children | 1ff68ea1a4cf |
files | tests/examples/AB_example.xml tests/examples/APE_example.xml tests/examples/horizontal_example.xml tests/examples/mushra_example.xml tests/examples/radio_example.xml |
diffstat | 30 files changed, 1132 insertions(+), 916 deletions(-) [+] |
line wrap: on
line diff
--- a/.gitignore Wed Nov 22 10:08:26 2017 +0000 +++ b/.gitignore Wed Nov 22 10:10:44 2017 +0000 @@ -11,3 +11,6 @@ *.DS_STORE *.swp *.swo +saves/ratings/* +saves/timelines/* +saves/timelines_movement/*
--- a/css/core.css Wed Nov 22 10:08:26 2017 +0000 +++ b/css/core.css Wed Nov 22 10:10:44 2017 +0000 @@ -199,6 +199,7 @@ div.master-volume-holder-inline { width: 100%; padding: 5px; + float: left; } div.master-volume-holder-float { position: absolute;
--- a/index.html Wed Nov 22 10:08:26 2017 +0000 +++ b/index.html Wed Nov 22 10:10:44 2017 +0000 @@ -51,7 +51,7 @@ <button id="popup-previous" class="popupButton">Back</button> </div> <div class="testHalt" style="visibility: hidden"></div> - <div id="footer"><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.2)</a></div> + <div id="footer"><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.3)</a></div> </body> </html>
--- a/interfaces/AB.css Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/AB.css Wed Nov 22 10:10:44 2017 +0000 @@ -24,6 +24,10 @@ height: 40px; font-size: 1.2em; } +div#submit-holder { + width: 100%; + text-align: center; +} div.interface-buttons { height: 40px; }
--- a/interfaces/AB.js Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/AB.js Wed Nov 22 10:10:44 2017 +0000 @@ -75,13 +75,14 @@ var boxes = document.createElement('div'); boxes.id = "box-holders"; + var submitHolder = document.createElement("div"); + submitHolder.id = "submit-holder" var submit = document.createElement('button'); submit.id = "submit"; submit.onclick = buttonSubmitClick; submit.className = "big-button"; - submit.textContent = "submit"; - submit.style.position = "relative"; - submit.style.left = (window.innerWidth - 250) / 2 + 'px'; + submit.textContent = "Submit"; + submitHolder.appendChild(submit); feedbackHolder.appendChild(boxes); @@ -95,7 +96,7 @@ testContent.appendChild(interfaceButtons); testContent.appendChild(outsideRef); testContent.appendChild(feedbackHolder); - testContent.appendChild(submit); + testContent.appendChild(submitHolder); testContent.appendChild(comments); interfaceContext.insertPoint.appendChild(testContent); @@ -395,10 +396,13 @@ function buttonSubmitClick() { var checks = testState.currentStateMap.interfaces[0].options, canContinue = true; - + if (interfaceContext.checkFragmentMinPlays() === false) { - return; -} + return; + } + if (interfaceContext.checkCommentQuestions() === false) { + return; + } for (var i = 0; i < checks.length; i++) { if (checks[i].type == 'check') {
--- a/interfaces/ABX.css Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/ABX.css Wed Nov 22 10:10:44 2017 +0000 @@ -20,9 +20,12 @@ height: 40px; font-size: 1.2em; } +div#submit-holder { + width: 100%; + text-align: center; +} div#box-holders { width: 100%; - text-align: center; } div#playback-holder { float: none;
--- a/interfaces/ABX.js Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/ABX.js Wed Nov 22 10:10:44 2017 +0000 @@ -77,15 +77,15 @@ var boxes = document.createElement('div'); boxes.align = "center"; boxes.id = "box-holders"; - boxes.style.float = "left"; + var submitHolder = document.createElement("div"); + submitHolder.id = "submit-holder" var submit = document.createElement('button'); submit.id = "submit"; submit.onclick = buttonSubmitClick; submit.className = "big-button"; submit.textContent = "submit"; - submit.style.position = "relative"; - submit.style.left = (window.innerWidth - 250) / 2 + 'px'; + submitHolder.appendChild(submit); feedbackHolder.appendChild(boxes); @@ -98,7 +98,7 @@ testContent.appendChild(pagetitle); testContent.appendChild(interfaceButtons); testContent.appendChild(feedbackHolder); - testContent.appendChild(submit); + testContent.appendChild(submitHolder); testContent.appendChild(comments); interfaceContext.insertPoint.appendChild(testContent); @@ -424,9 +424,6 @@ boxW = numObj * 312; diff = window.innerWidth - boxW; } - document.getElementById('box-holders').style.marginLeft = diff / 2 + 'px'; - document.getElementById('box-holders').style.marginRight = diff / 2 + 'px'; - document.getElementById('box-holders').style.width = boxW + 'px'; } function buttonSubmitClick() { @@ -436,6 +433,9 @@ if (interfaceContext.checkFragmentMinPlays() === false) { return; } + if (interfaceContext.checkCommentQuestions() === false) { + return; + } for (var i = 0; i < checks.length; i++) { var checkState = true;
--- a/interfaces/ape.css Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/ape.css Wed Nov 22 10:10:44 2017 +0000 @@ -26,7 +26,6 @@ } div.slider { /* Specify any structure for the slider holder interface */ - background-color: #eee; height: 150px; margin: 5px 50px; -moz-user-select: -moz-none; @@ -61,6 +60,12 @@ -webkit-user-select: none; border: 1px solid black; } +canvas.tick-canvas { + z-index: -1; + position: absolute; + height: 150px; + background-color: #eee; +} div#outside-reference-holder { display: flex; align-content: center;
--- a/interfaces/ape.js Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/ape.js Wed Nov 22 10:10:44 2017 +0000 @@ -21,136 +21,15 @@ testContent.id = 'testContent'; // Bindings for interfaceContext - interfaceContext.checkAllPlayed = function () { - var hasBeenPlayed = audioEngineContext.checkAllPlayed(); - if (hasBeenPlayed.length > 0) // if a fragment has not been played yet - { - var str = ""; - if (hasBeenPlayed.length > 1) { - for (var i = 0; i < hasBeenPlayed.length; i++) { - var ao_id = audioEngineContext.audioObjects[hasBeenPlayed[i]].interfaceDOM.getPresentedId(); - str = str + ao_id; // start from 1 - if (i < hasBeenPlayed.length - 2) { - str += ", "; - } else if (i == hasBeenPlayed.length - 2) { - str += " or "; - } - } - str = 'You have not played fragments ' + str + ' yet. Please listen, rate and comment all samples before submitting.'; - } else { - str = 'You have not played fragment ' + (audioEngineContext.audioObjects[hasBeenPlayed[0]].interfaceDOM.getPresentedId()) + ' yet. Please listen, rate and comment all samples before submitting.'; - } - this.storeErrorNode(str); - interfaceContext.lightbox.post("Message", str); - return false; - } - return true; - }; interfaceContext.checkAllMoved = function () { - var state = true; - var str = 'You have not moved the following sliders. '; - for (var i = 0; i < this.interfaceSliders.length; i++) { - var interfaceTID = []; - for (var j = 0; j < this.interfaceSliders[i].metrics.length; j++) { - var ao_id = this.interfaceSliders[i].sliders[j].getAttribute("trackIndex"); - if (this.interfaceSliders[i].metrics[j].wasMoved === false && audioEngineContext.audioObjects[ao_id].interfaceDOM.canMove()) { - state = false; - interfaceTID.push(j); - } - } - if (interfaceTID.length !== 0) { - var interfaceName = this.interfaceSliders[i].interfaceObject.title; - if (interfaceName === undefined) { - str += 'On axis ' + String(i + 1) + ' you must move '; - } else { - str += 'On axis "' + interfaceName + '" you must move '; - } - if (interfaceTID.length == 1) { - str += 'slider ' + (audioEngineContext.audioObjects[interfaceTID[0]].interfaceDOM.getPresentedId()) + '. '; // start from 1 - } else { - str += 'sliders '; - for (var k = 0; k < interfaceTID.length - 1; k++) { - str += (audioEngineContext.audioObjects[interfaceTID[k]].interfaceDOM.getPresentedId()) + ', '; // start from 1 - } - str += (audioEngineContext.audioObjects[interfaceTID[interfaceTID.length - 1]].interfaceDOM.getPresentedId()) + '. '; - } - } - } - if (state !== true) { - this.storeErrorNode(str); - interfaceContext.lightbox.post("Message", str); - console.log(str); - } - return state; + return module.checkAllMoved(); }; interfaceContext.checkScaleRange = function () { - var audioObjs = audioEngineContext.audioObjects; - var audioHolder = testState.stateMap[testState.stateIndex]; - var interfaceObject = this.interfaceSliders[0].interfaceObject; - var state = true; - var str = ''; - this.interfaceSliders.forEach(function (sliderHolder, i) { - var scales = (function () { - var scaleRange = interfaceObject.options.find(function (a) { - return a.name == "scalerange"; - }); - return { - min: scaleRange.min, - max: scaleRange.max - }; - })(); - var range = sliderHolder.sliders.reduce(function (a, b) { - var v = convSliderPosToRate(b) * 100.0; - return { - min: Math.min(a.min, v), - max: Math.max(a.max, v) - }; - }, { - min: 100, - max: 0 - }); - if (range.min >= scales.min || range.max <= scales.max) { - state = false; - str += 'On axis "' + sliderHolder.interfaceObject.title + '" you have not used the full width of the scale. '; - } - }); - if (state !== true) { - this.storeErrorNode(str); - interfaceContext.lightbox.post("Message", str); - console.log(str); - } - return state; + return module.checkScaleRange(); }; - Interface.prototype.objectSelected = null; - Interface.prototype.objectMoved = false; - Interface.prototype.selectObject = function (object) { - if (this.objectSelected === null) { - this.objectSelected = object; - this.objectMoved = false; - } - }; - Interface.prototype.moveObject = function () { - if (this.objectMoved === false) { - this.objectMoved = true; - } - }; - Interface.prototype.releaseObject = function () { - this.objectSelected = null; - this.objectMoved = false; - }; - Interface.prototype.getSelectedObject = function () { - return this.objectSelected; - }; - Interface.prototype.hasSelectedObjectMoved = function () { - return this.objectMoved; - }; - - // Bindings for slider interfaces - Interface.prototype.interfaceSliders = []; - // Bindings for audioObjects // Create the top div for the Title element @@ -220,12 +99,13 @@ interfaceContext.insertPoint.appendChild(testContent); // Load the full interface + window.module = new ape(); testState.initialise(); testState.advanceState(); - } function loadTest(audioHolderObject) { + module.clear(); var width = window.innerWidth; var height = window.innerHeight; var id = audioHolderObject.id; @@ -246,15 +126,10 @@ document.getElementById("test-title").textContent = audioHolderObject.title; } - // Delete outside reference document.getElementById("outside-reference-holder").innerHTML = ""; var interfaceObj = interfaceContext.getCombinedInterfaces(audioHolderObject); - interfaceObj.forEach(function (interfaceObjectInstance) { - // Create the div box to center align - interfaceContext.interfaceSliders.push(new interfaceSliderHolder(interfaceObjectInstance, audioHolderObject)); - }); interfaceObj.forEach(function (interface) { interface.options.forEach(function (option) { if (option.type == "show") { @@ -298,113 +173,7 @@ var loopPlayback = audioHolderObject.loop; - var currentTestHolder = document.createElement('audioHolder'); - currentTestHolder.id = audioHolderObject.id; - currentTestHolder.repeatCount = audioHolderObject.repeatCount; - - // Find all the audioElements from the audioHolder - $(audioHolderObject.audioElements).each(function (index, element) { - // Find URL of track - // In this jQuery loop, variable 'this' holds the current audioElement. - var audioObject = audioEngineContext.newTrack(element); - // Check if an outside reference - if (element.type == 'outside-reference') { - // Construct outside reference; - var orNode = new outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder")); - audioObject.bindInterface(orNode); - } else { - // Create a slider per track - var sliderNode = new sliderObject(audioObject, interfaceObj, index); - audioObject.bindInterface(sliderNode); - interfaceContext.commentBoxes.createCommentBox(audioObject); - } - }); - - // Initialse the interfaceSlider object metrics - - $('.track-slider').mousedown(function (event) { - interfaceContext.selectObject($(this)[0]); - }); - $('.track-slider').on('touchstart', null, function (event) { - interfaceContext.selectObject($(this)[0]); - }); - - $('.track-slider').mousemove(function (event) { - event.preventDefault(); - }); - - $('.slider').mousemove(function (event) { - event.preventDefault(); - var obj = interfaceContext.getSelectedObject(); - if (obj === null) { - return; - } - var move = event.clientX - 6; - var w = $(event.currentTarget).width(); - move = Math.max(50, move); - move = Math.min(w + 50, move); - $(obj).css("left", move + "px"); - interfaceContext.moveObject(); - }); - - $('.slider').on('touchmove', null, function (event) { - event.preventDefault(); - var obj = interfaceContext.getSelectedObject(); - if (obj === null) { - return; - } - var move = event.originalEvent.targetTouches[0].clientX - 6; - var w = $(event.currentTarget).width(); - move = Math.max(50, move); - move = Math.min(w + 50, move); - $(obj).css("left", move + "px"); - interfaceContext.moveObject(); - }); - - $(document).mouseup(function (event) { - event.preventDefault(); - var obj = interfaceContext.getSelectedObject(); - if (obj === null) { - return; - } - var interfaceID = obj.parentElement.getAttribute("interfaceid"); - var trackID = obj.getAttribute("trackindex"); - var id; - if (interfaceContext.hasSelectedObjectMoved() === true) { - var l = $(obj).css("left"); - id = obj.getAttribute('trackIndex'); - var time = audioEngineContext.timer.getTestTime(); - var rate = convSliderPosToRate(obj); - audioEngineContext.audioObjects[id].metric.moved(time, rate); - interfaceContext.interfaceSliders[interfaceID].metrics[trackID].moved(time, rate); - console.log("slider " + id + " moved to " + rate + ' (' + time + ')'); - obj.setAttribute("slider-value", convSliderPosToRate(obj)); - } else { - id = Number(obj.attributes.trackIndex.value); - //audioEngineContext.metric.sliderPlayed(id); - audioEngineContext.play(id); - } - interfaceContext.releaseObject(); - }); - - $('.slider').on('touchend', null, function (event) { - var obj = interfaceContext.getSelectedObject(); - if (obj === null) { - return; - } - var interfaceID = obj.parentElement.getAttribute("interfaceid"); - var trackID = obj.getAttribute("trackindex"); - if (interfaceContext.hasSelectedObjectMoved() === true) { - var l = $(obj).css("left"); - var id = obj.getAttribute('trackIndex'); - var time = audioEngineContext.timer.getTestTime(); - var rate = convSliderPosToRate(obj); - audioEngineContext.audioObjects[id].metric.moved(time, rate); - interfaceContext.interfaceSliders[interfaceID].metrics[trackID].moved(time, rate); - console.log("slider " + id + " moved to " + rate + ' (' + time + ')'); - } - interfaceContext.releaseObject(); - }); + module.initialisePage(audioHolderObject); var interfaceList = audioHolderObject.interfaces.concat(specification.interfaces); for (var k = 0; k < interfaceList.length; k++) { @@ -445,239 +214,562 @@ }); //testWaitIndicator(); + module.resize(); } -function interfaceSliderHolder(interfaceObject, page) { - this.sliders = []; - this.metrics = []; - this.id = document.getElementsByClassName("sliderCanvasDiv").length; - this.name = interfaceObject.name; - this.interfaceObject = interfaceObject; - this.sliderDOM = document.createElement('div'); - this.sliderDOM.className = 'sliderCanvasDiv'; - this.sliderDOM.id = 'sliderCanvasHolder-' + this.id; - this.imageHolder = (function () { - var imageController = {}; - imageController.root = document.createElement("div"); - imageController.root.className = "imageController"; - imageController.img = document.createElement("img"); - imageController.root.appendChild(imageController.img); - imageController.setImage = function (src) { - imageController.img.src = ""; - if (typeof src !== "string" || src.length === undefined) { +function ape() { + var axis = [] + var DOMRoot = document.getElementById("slider-holder"); + var AOIs = []; + var page = undefined; + + function audioObjectInterface(audioObject, parent) { + // The audioObject communicates with this object + var playing = false; + var sliders = []; + this.enable = function () { + sliders.forEach(function (s) { + s.enable(); + }); + } + + this.updateLoading = function (p) { + sliders.forEach(function (s) { + s.updateLoading(p); + }); + } + + this.startPlayback = function () { + playing = true; + sliders.forEach(function (s) { + s.playing(); + }); + } + + this.stopPlayback = function () { + playing = false; + sliders.forEach(function (s) { + s.stopped(); + }); + } + + this.getValue = function () { + return sliders[0].value; + } + + this.getPresentedId = function () { + return sliders[0].label; + } + + this.canMove = function () { + return true; + } + + this.exportXMLDOM = function (audioObject) { + var elements = []; + sliders.forEach(function (s) { + elements.push(s.exportXMLDOM()); + }); + return elements; + } + + this.error = function () { + sliders.forEach(function (s) { + s.error(); + }); + } + + this.addSlider = function (s) { + sliders.push(s); + } + + this.clicked = function (event) { + if (!playing) { + audioEngineContext.play(audioObject.id); + } else { + audioEngineContext.stop(); + } + playing = !playing; + } + + this.pageXMLSave = function (store) { + var inject = audioObject.storeDOM.getElementsByTagName("metric")[0]; + sliders.forEach(function (s) { + s.pageXMLSave(inject); + }); + } + + } + + function axisObject(interfaceObject, parent) { + + function sliderInterface(AOI, axisInterface) { + var trackObj = document.createElement('div'); + var labelHolder = document.createElement("span"); + var label = ""; + var metric = new metricTracker(this); + var value = Math.random(); + trackObj.align = "center"; + trackObj.className = 'track-slider track-slider-disabled'; + trackObj.appendChild(labelHolder); + axisInterface.sliderRail.appendChild(trackObj); + metric.initialise(this.value); + this.setLabel = function (s) { + label = s; + } + this.resize = function (event) { + var width = $(axisInterface.sliderRail).width(); + var w = Number(value * width); + trackObj.style.left = String(w) + "px"; + } + this.playing = function () { + trackObj.classList.add("track-slider-playing"); + } + this.stopped = function () { + trackObj.classList.remove("track-slider-playing"); + } + this.enable = function () { + trackObj.addEventListener("mousedown", this); + trackObj.addEventListener("mouseup", this); + trackObj.classList.remove("track-slider-disabled"); + labelHolder.textContent = label; + } + this.updateLoading = function (progress) { + labelHolder.textContent = progress + "%"; + } + this.exportXMLDOM = function () { + var node = storage.document.createElement('value'); + node.setAttribute("interface-name", axisInterface.name) + node.textContent = this.value; + return node; + } + this.error = function () { + trackObj.classList.add("error-colour"); + trackObj.removeEventListener("mousedown"); + trackObj.removeEventListener("mouseup"); + } + var timing = undefined; + this.handleEvent = function (e) { + // This is only for the mousedown / touchdown + if (e.preventDefault) { + e.preventDefault(); + } + if (e.type == "mousedown") { + axisInterface.mousedown(this); + } else if (e.type == "mouseup" || e.type == "touchend" || e.type == "touchcancel") { + axisInterface.mouseup(this); + metric.moved(audioEngineContext.timer.getTestTime(), this.value); + console.log("Slider " + label + " on axis " + axisInterface.name + " moved to " + this.value); + } + } + this.clicked = function (e) { + AOI.clicked(); + } + this.pageXMLSave = function (inject) { + var nodes = metric.exportXMLDOM(inject); + nodes.forEach(function (elem) { + var name = elem.getAttribute("name"); + if (name == "elementTracker" || name == "elementTrackerFull" || name == "elementInitialPosition" || name == "elementFlagMoved") { + elem.setAttribute("interface-name", axisInterface.name); + } else { + inject.removeChild(elem); + } + }); + } + this.hasMoved = function () { + return metric.wasMoved; + } + Object.defineProperties(this, { + "DOM": { + "value": trackObj + }, + "value": { + "get": function () { + return value; + }, + "set": function (v) { + if (v >= 0 && v <= 1) { + value = v; + } + this.resize(); + return value; + } + }, + "label": { + "get": function () { + return label; + }, + "set": function () {} + }, + "metric": { + "value": metric + } + }); + } + + function drawTick(position) { + var context = tickCanvas.getContext("2d"), + w = tickCanvas.width, + h = tickCanvas.height; + context.beginPath(); + context.setLineDash([1, 2]); + context.moveTo(position * w, 0); + context.lineTo(position * w, h); + context.closePath(); + context.stroke(); + } + + function clearTicks() { + var c = tickCanvas.getContext("2d"), + w = tickCanvas.width, + h = tickCanvas.height; + c.clearRect(0, 0, w, h); + } + + function createScaleMarkers(interfaceObject, root, w) { + var ticks = interfaceObject.options.findIndex(function (a) { + return (a.type == "show" && a.name == "ticks"); + }); + ticks = (ticks >= 0); + clearTicks(); + interfaceObject.scales.forEach(function (scaleObj) { + var position = Number(scaleObj.position) * 0.01; + var pixelPosition = (position * w) + 50; + var scaleDOM = document.createElement('span'); + scaleDOM.className = "ape-marker-text"; + scaleDOM.textContent = scaleObj.text; + scaleDOM.setAttribute('value', position); + root.appendChild(scaleDOM); + scaleDOM.style.left = Math.floor((pixelPosition - ($(scaleDOM).width() / 2))) + 'px'; + if (ticks) { + drawTick(position); + } + }, this); + } + var sliders = []; + var UI = { + selected: undefined, + startTime: undefined + } + this.name = interfaceObject.name; + var DOMRoot = document.createElement("div"); + parent.getDOMRoot().appendChild(DOMRoot); + DOMRoot.className = "sliderCanvasDiv"; + DOMRoot.id = "sliderCanvasHolder-" + this.name; + var sliders = []; + + var axisTitle = document.createElement("div"); + axisTitle.className = "pageTitle"; + axisTitle.align = "center"; + var titleSpan = document.createElement('span'); + titleSpan.id = "pageTitle-" + this.name; + if (interfaceObject.title !== undefined && typeof interfaceObject.title == "string") { + titleSpan.textContent = interfaceObject.title; + } else { + titleSpan.textContent = "Axis " + String(this.id + 1); + } + axisTitle.appendChild(titleSpan); + DOMRoot.appendChild(axisTitle); + + var imageHolder = (function () { + var imageController = {}; + imageController.root = document.createElement("div"); + imageController.root.className = "imageController"; + imageController.img = document.createElement("img"); + imageController.root.appendChild(imageController.img); + imageController.setImage = function (src) { + imageController.img.src = ""; + if (typeof src !== "string" || src.length === undefined) { + return; + } + imageController.img.src = src; + }; + return imageController; + })(); + if (interfaceObject.image !== undefined || page.audioElements.some(function (a) { + return a.image !== undefined; + })) { + DOMRoot.appendChild(imageHolder.root); + imageHolder.setImage(interfaceObject.image); + } + + // Now create the slider box to hold the fragment sliders + var sliderRail = document.createElement("div"); + sliderRail.id = "sliderrail-" + this.name; + sliderRail.className = "slider"; + sliderRail.align = "left"; + DOMRoot.appendChild(sliderRail); + + // Canvas for the markers + var tickCanvas = document.createElement("canvas"); + tickCanvas.id = "ticks-" + this.name; + tickCanvas.className = "tick-canvas"; + tickCanvas.height = 150; + tickCanvas.width = $(sliderRail).width() - 100; + tickCanvas.style.width = ($(sliderRail).width() - 100) + "px"; + sliderRail.appendChild(tickCanvas); + + // Create the div to hold any scale objects + var scale = document.createElement("div"); + scale.className = "sliderScale"; + scale.id = "slider-scale-holder-" + this.name; + scale.slign = "left"; + DOMRoot.appendChild(scale); + createScaleMarkers(interfaceObject, scale, $(sliderRail).width()); + + this.resize = function (event) { + var w = $(sliderRail).width(); + var marginsize = 50; + sliders.forEach(function (s) { + s.resize(); + }); + scale.innerHTML = ""; + tickCanvas.width = $(sliderRail).width(); + tickCanvas.style.width = tickCanvas.width + "px"; + createScaleMarkers(interfaceObject, scale, $(sliderRail).width()); + } + this.playing = function (id) { + var node = audioEngineContext.audioObjects.find(function (a) { + return a.id == id; + }); + if (node === undefined) { + this.imageHolder.setImage(interfaceObject.image || ""); return; } - imageController.img.src = src; - }; - return imageController; - })(); + var imgurl = node.specification.image || interfaceObject.image || ""; + this.imageHolder.setImage(imgurl); + } + this.stopped = function () { + var imgurl = interfaceObject.image || ""; + this.imageHolder.setImage(imgurl); + } + this.addSlider = function (aoi) { + var node = new sliderInterface(aoi, this); + sliders.push(node); + return node; + } + this.mousedown = function (sliderUI) { + UI.selected = sliderUI; + UI.startTime = new Date(); + } + this.mouseup = function (event) { + var delta = new Date() - UI.startTime; + if (delta < 200) { + UI.selected.clicked(); + } else if (event.type == "touchend" || event.type == "touchcancel") { + UI.selected.handleEvent(event); + } + UI.selected = undefined; + UI.startTime = undefined; + } + this.handleEvent = function (event) { + function getTargetSlider(target) { + return sliders.find(function (a) { + return a.DOM == target; + }); + } + var time = audioEngineContext.timer.getTestTime(); + if (event.preventDefault) { + event.preventDefault(); + } + if (event.type == "touchstart") { + var selected = getTargetSlider(event.target); + if (typeof selected != "object") { + return; + } + UI.startTime = new Date(); + UI.selected = selected; + } + if (UI.selected === undefined) { + return; + } + if (event.type == "mousemove") { + var move = event.clientX - 6; + var w = $(sliderRail).width(); + move = Math.max(50, move); + move = Math.min(w, move); + UI.selected.value = (move / w); + } else if (event.type == "touchmove") { + if (UI.selected == getTargetSlider(event.target)) { + var move; + if (event.targetTouches) { + move = event.targetTouches[0].clientX - 6; + } else if (event.originalEvent.targetTouches) { + move = event.originalEvent.targetTouches[0].clientX - 6; + } else { + return; + } + var w = $(event.currentTarget).width(); + move = Math.max(50, move); + move = Math.min(w, move); + UI.selected.value = (move / w); + } + } else if (event.type == "touchend" || event.type == "touchcancel") { + if (UI.selected == getTargetSlider(event.target)) { + this.mouseup(event); + } + } + } + this.checkAllMoved = function () { + var notMoved = sliders.filter(function (s) { + return !s.hasMoved(); + }); + if (notMoved.length !== 0) { + var ls = []; + notMoved.forEach(function (s) { + ls.push(s.label); + }) + var str = "On axis \"" + interfaceObject.title + "\", "; + if (ls.length == 1) { + str += "slider " + ls[0]; + } else { + str += "sliders " + [ls.slice(0, ls.length - 1).join(", ")].concat(ls[ls.length - 1]).join(" and "); + } + str += "."; + return str; + } else { + return ""; + } + } + this.checkScaleRange = function () { + var scaleRange = interfaceObject.options.find(function (a) { + return a.name == "scalerange"; + }); + if (scaleRange === undefined) { + return ""; + } + var scales = { + min: scaleRange.min, + max: scaleRange.max + }; + var maxSlider = sliders.reduce(function (a, b) { + return Math.max(a, b.value); + }, 0); + var minSlider = sliders.reduce(function (a, b) { + return Math.min(a, b.value); + }, 100); + if (minSlider >= scales.min || maxSlider <= scales.max) { + return "On axis \"" + interfaceObject.title + "\", you have not used the required width of the scales"; + } + return ""; + } + sliderRail.addEventListener("mousemove", this); + sliderRail.addEventListener("touchstart", this); + sliderRail.addEventListener("touchmove", this); + sliderRail.addEventListener("touchend", this); + sliderRail.addEventListener("touchcancel", this); + Object.defineProperties(this, { + "sliderRail": { + "value": sliderRail + } + }); + } + this.getDOMRoot = function () { + return DOMRoot; + } + this.getPage = function () { + return page; + } + this.clear = function () { + page = undefined; + axis = []; + AOIs = []; + DOMRoot.innerHTML = ""; + } + this.initialisePage = function (page_init) { + this.clear(); + page = page_init; + var randomiseAxisOrder; + if (page.randomiseAxisOrder !== undefined) { + randomiseAxisOrder = page.randomiseAxisOrder; + } else { + randomiseAxisOrder = page.parent.randomiseAxisOrder; + } + var commentBoxes = false; + // Create each of the interface axis + if (randomiseAxisOrder) { + page.interfaces = randomiseOrder(page.interfaces); + } + var interfaceObj = interfaceContext.getCombinedInterfaces(page); + interfaceObj.forEach(function (i) { + var node = new axisObject(i, this); + axis.push(node); + i.options.forEach(function (o) { + if (o.type == "show" && o.name == "comments") { + commentBoxes = true; + } + }); + }, this); - var pagetitle = document.createElement('div'); - pagetitle.className = "pageTitle"; - pagetitle.align = "center"; - var titleSpan = document.createElement('span'); - titleSpan.id = "pageTitle-" + this.id; - if (interfaceObject.title !== undefined && typeof interfaceObject.title == "string") { - titleSpan.textContent = interfaceObject.title; - } else { - titleSpan.textContent = "Axis " + String(this.id + 1); + // Create the audioObject interface objects for each aO. + page.audioElements.forEach(function (element, index) { + var audioObject = audioEngineContext.newTrack(element); + if (element.type == 'outside-reference') { + // Construct outside reference; + var orNode = new outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder")); + audioObject.bindInterface(orNode); + } else { + var aoi = new audioObjectInterface(audioObject, this); + AOIs.push(aoi); + var label = interfaceContext.getLabel(page.label, index, page.labelStart); + axis.forEach(function (a) { + var node = a.addSlider(aoi); + node.setLabel(label); + aoi.addSlider(node); + }); + audioObject.bindInterface(aoi); + if (commentBoxes) { + interfaceContext.commentBoxes.createCommentBox(audioObject); + } + } + }); } - pagetitle.appendChild(titleSpan); - this.sliderDOM.appendChild(pagetitle); - - if (interfaceObject.image !== undefined || page.audioElements.some(function (a) { - return a.image !== undefined; - })) { - this.sliderDOM.appendChild(this.imageHolder.root); - this.imageHolder.setImage(interfaceObject.image); + this.checkAllMoved = function () { + var str = "You have not moved the following sliders. " + var cont = true; + axis.forEach(function (a) { + var msg = a.checkAllMoved(); + if (msg.length > 0) { + cont = false; + str += msg; + } + }); + if (!cont) { + interfaceContext.lightbox.post("Error", str); + interfaceContext.storeErrorNode(str); + console.log(str); + } + return cont; } - // Create the slider box to hold the slider elements - this.canvas = document.createElement('div'); - if (this.name !== undefined) - this.canvas.id = 'slider-' + this.name; - else - this.canvas.id = 'slider-' + this.id; - this.canvas.setAttribute("interfaceid", this.id); - this.canvas.className = 'slider'; - this.canvas.align = "left"; - this.canvas.addEventListener('dragover', function (event) { - event.preventDefault(); - event.dataTransfer.effectAllowed = 'none'; - event.dataTransfer.dropEffect = 'copy'; - return false; - }, false); - this.sliderDOM.appendChild(this.canvas); - - // Create the div to hold any scale objects - this.scale = document.createElement('div'); - this.scale.className = 'sliderScale'; - this.scale.id = 'sliderScaleHolder-' + this.id; - this.scale.align = 'left'; - this.sliderDOM.appendChild(this.scale); - var positionScale = this.canvas.style.width.substr(0, this.canvas.style.width.length - 2); - var offset = 50; - var dest = document.getElementById("slider-holder").appendChild(this.sliderDOM); - interfaceObject.scales.forEach(function (scaleObj) { - var position = Number(scaleObj.position) * 0.01; - var pixelPosition = (position * $(this.canvas).width()) + offset; - var scaleDOM = document.createElement('span'); - scaleDOM.className = "ape-marker-text"; - scaleDOM.textContent = scaleObj.text; - scaleDOM.setAttribute('value', position); - this.scale.appendChild(scaleDOM); - scaleDOM.style.left = Math.floor((pixelPosition - ($(scaleDOM).width() / 2))) + 'px'; - }, this); - - this.createSliderObject = function (audioObject, label) { - var trackObj = document.createElement('div'); - trackObj.align = "center"; - trackObj.className = 'track-slider track-slider-disabled track-slider-' + audioObject.id; - trackObj.id = 'track-slider-' + this.id + '-' + audioObject.id; - trackObj.setAttribute('trackIndex', audioObject.id); - if (this.name !== undefined) { - trackObj.setAttribute('interface-name', this.name); - } else { - trackObj.setAttribute('interface-name', this.id); + this.checkScaleRange = function () { + var str = ""; + var cont = true; + axis.forEach(function (a) { + var msg = a.checkScaleRange(); + if (msg.length > 0) { + cont = false; + str += msg; + } + }); + if (!cont) { + interfaceContext.lightbox.post("Error", str); + interfaceContext.storeErrorNode(str); + console.log(str); } - var offset = 50; - // Distribute it randomnly - var w = window.innerWidth - (offset + 8) * 2; - w = Math.random() * w; - w = Math.floor(w + (offset + 8)); - trackObj.style.left = w + 'px'; - this.canvas.appendChild(trackObj); - this.sliders.push(trackObj); - this.metrics.push(new metricTracker(this)); - var labelHolder = document.createElement("span"); - labelHolder.textContent = label; - trackObj.appendChild(labelHolder); - var rate = convSliderPosToRate(trackObj); - this.metrics[this.metrics.length - 1].initialise(rate); - trackObj.setAttribute("slider-value", rate); - return trackObj; - }; - - this.resize = function (event) { - var sliderDiv = this.canvas; - var sliderScaleDiv = this.scale; - var width = $(sliderDiv).width(); - var marginsize = 50; - // Move sliders into new position - this.sliders.forEach(function (slider, index) { - var pix = Number(slider.getAttribute("slider-value")) * width; - slider.style.left = (pix + marginsize) + 'px'; - }); - - // Move scale labels - for (var index = 0; index < this.scale.children.length; index++) { - var scaleObj = this.scale.children[index]; - var position = Number(scaleObj.attributes.value.value); - var pixelPosition = (position * width) + marginsize; - scaleObj.style.left = Math.floor((pixelPosition - ($(scaleObj).width() / 2))) + 'px'; - } - }; - - this.playing = function (id) { - var node = audioEngineContext.audioObjects.find(function (a) { - return a.id == id; - }); - if (node === undefined) { - this.imageHolder.setImage(interfaceObject.image || ""); - return; - } - var imgurl = node.specification.image || interfaceObject.image || ""; - this.imageHolder.setImage(imgurl); + return cont; } -} - -function sliderObject(audioObject, interfaceObjects, index) { - // Create a new slider object; - this.parent = audioObject; - this.trackSliderObjects = []; - this.label = interfaceContext.getLabel(audioObject.specification.parent.label, index, audioObject.specification.parent.labelStart); - this.playing = false; - for (var i = 0; i < interfaceContext.interfaceSliders.length; i++) { - var trackObj = interfaceContext.interfaceSliders[i].createSliderObject(audioObject, this.label); - this.trackSliderObjects.push(trackObj); - } - - // Onclick, switch playback to that track - - this.enable = function () { - if (this.parent.state == 1) { - $(this.trackSliderObjects).each(function (i, trackObj) { - $(trackObj).removeClass('track-slider-disabled'); + this.pageXMLSave = function (store, pageSpecification) { + if (axis.length > 1) { + AOIs.forEach(function (ao) { + ao.pageXMLSave(store); }); } - }; - this.updateLoading = function (progress) { - if (progress != 100) { - progress = String(progress); - progress = progress.split('.')[0]; - this.trackSliderObjects[0].children[0].textContent = progress + '%'; - } else { - this.trackSliderObjects[0].children[0].textContent = this.label; - } - }; - this.startPlayback = function () { - $('.track-slider').removeClass('track-slider-playing'); - var name = ".track-slider-" + this.parent.id; - $(name).addClass('track-slider-playing'); - interfaceContext.commentBoxes.highlightById(audioObject.id); - $('.outside-reference').removeClass('track-slider-playing'); - this.playing = true; - - if (this.parent.specification.parent.playOne || specification.playOne) { - $('.track-slider').addClass('track-slider-disabled'); - $('.outside-reference').addClass('track-slider-disabled'); - } - interfaceContext.interfaceSliders.forEach(function (ts) { - ts.playing(this.parent.id); - }, this); - }; - this.stopPlayback = function () { - if (this.playing) { - this.playing = false; - var name = ".track-slider-" + this.parent.id; - $(name).removeClass('track-slider-playing'); - $('.track-slider').removeClass('track-slider-disabled'); - $('.outside-reference').removeClass('track-slider-disabled'); - var box = interfaceContext.commentBoxes.boxes.find(function (a) { - return a.id === audioObject.id; - }); - if (box) { - box.highlight(false); - } - } - }; - this.exportXMLDOM = function (audioObject) { - // Called by the audioObject holding this element. Must be present - var obj = []; - $(this.trackSliderObjects).each(function (i, trackObj) { - var node = storage.document.createElement('value'); - if (trackObj.getAttribute("interface-name") !== "null") { - node.setAttribute("interface-name", trackObj.getAttribute("interface-name")); - } - node.textContent = convSliderPosToRate(trackObj); - obj.push(node); + } + this.resize = function (event) { + axis.forEach(function (a) { + a.resize(event); }); - - return obj; - }; - this.getValue = function () { - return convSliderPosToRate(this.trackSliderObjects[0]); - }; - this.getPresentedId = function () { - return this.label; - }; - this.canMove = function () { - return true; - }; - this.error = function () { - // audioObject has an error!! - this.playback.textContent = "Error"; - $(this.playback).addClass("error-colour"); - }; + } } function outsideReferenceDOM(audioObject, index, inject) { @@ -753,6 +845,9 @@ if (interfaceContext.checkFragmentMinPlays() === false) { return; } + if (interfaceContext.checkCommentQuestions() === false) { + return; + } for (var i = 0; i < checks.length; i++) { var checkState = true; @@ -820,9 +915,7 @@ // MANDATORY FUNCTION // Resize the slider objects - for (var i = 0; i < interfaceContext.interfaceSliders.length; i++) { - interfaceContext.interfaceSliders[i].resize(event); - } + window.module.resize(event); } function pageXMLSave(store, pageSpecification) { @@ -833,32 +926,5 @@ // pageSpecification is the current page node configuration // To create new XML nodes, use storage.document.createElement(); - if (interfaceContext.interfaceSliders.length == 1) { - // If there is only one axis, there only needs to be one metric return - return; - } - var audioelements = store.getElementsByTagName("audioelement"); - for (var i = 0; i < audioelements.length; i++) { - // Have to append the metric specific nodes - if (pageSpecification.outsideReference === undefined || pageSpecification.outsideReference.id != audioelements[i].id) { - var inject = audioelements[i].getElementsByTagName("metric"); - if (inject.length === 0) { - inject = storage.document.createElement("metric"); - } else { - inject = inject[0]; - } - for (var k = 0; k < interfaceContext.interfaceSliders.length; k++) { - var mrnodes = interfaceContext.interfaceSliders[k].metrics[i].exportXMLDOM(inject); - for (var j = 0; j < mrnodes.length; j++) { - var name = mrnodes[j].getAttribute("name"); - if (name == "elementTracker" || name == "elementTrackerFull" || name == "elementInitialPosition" || name == "elementFlagMoved") { - if (interfaceContext.interfaceSliders[k].name !== null) { - mrnodes[j].setAttribute("interface-name", interfaceContext.interfaceSliders[k].name); - } - mrnodes[j].setAttribute("interface-id", k); - } - } - } - } - } + module.pageXMLSave(store, pageSpecification); }
--- a/interfaces/discrete.css Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/discrete.css Wed Nov 22 10:10:44 2017 +0000 @@ -20,79 +20,47 @@ min-width: 20px; background-color: #ddd } -div#slider-holder { - height: inherit; - position: absolute; - left: 0px; - z-index: 3; - margin-top: 25px; +div#slider-box { + width: 75%; + height: auto; + margin: auto; + padding-bottom: 20px; } -div#scale-holder { - position: absolute; - left: 0px; - z-index: 2; +div#slider-grid { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; } div#scale-text-holder { - position: relative; - float: left; + display: grid; + grid-template-rows: 1fr; + min-height: 25px; + text-align: center; +} +div.discrete-row { + display: grid; + grid-template-rows: 1fr; + padding: 10px; + border: 1px solid black; + height: 50px; + line-height: 50px; +} +button.discrete-button { + width: 100px; +} +div.discrete-label { + width: 100px; + text-align: center; } div.scale-text { - position: absolute; - font-size: 1.2em; -} -canvas#scale-canvas { - position: relative; - float: left; -} -div.track-slider { - float: left; - height: 30px; - border: solid; - border-width: 1px; - border-color: black; - padding: 2px; - margin-left: 94px; - margin-bottom: 30px; -} -div.track-slider-range { - float: left; - height: 100%; - margin: 0px 50px; position: relative; } -div.track-slider-title { - float: left; - padding-top: 5px; - width: 100px; +div.scale-text > span { + position: absolute; + bottom: 0; + width: 100%; + left: 0; } -button.track-slider-button { - float: left; - width: 100px; - height: 30px; +div.discrete-row-playing { + background-color: rgba(255, 201, 201, 0.5); } -input.track-radio { - position: absolute; - margin: 9px 0px; -} -div#outside-reference-holder { - display: flex; - align-content: center; - justify-content: center; - margin-bottom: 5px; -} -button.outside-reference { - position: inherit; - margin: 0px 5px; -} -div.track-slider-playing { - background-color: #FFDDDD; -} -div#page-count { - float: left; - margin: 0px 5px; -} -div#master-volume-holder { - position: absolute; - top: 10px; - left: 120px; -}
--- a/interfaces/discrete.js Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/discrete.js Wed Nov 22 10:10:44 2017 +0000 @@ -1,4 +1,8 @@ -/* globals interfaceContext, document, window, $, specification, audioEngineContext, console, window, testState, storage */ +/** + * WAET Blank Template + * Use this to start building your custom interface + */ + // Once this is loaded and parsed, begin execution loadInterface(); @@ -56,44 +60,41 @@ console.log('Stopped at ' + time); // DEBUG/SAFETY } }; - - // Create outside reference holder - var outsideRef = document.createElement("div"); - outsideRef.id = "outside-reference-holder"; - // Create Submit (save) button var submit = document.createElement("button"); submit.innerHTML = 'Next'; submit.onclick = buttonSubmitClick; submit.id = 'submit-button'; submit.style.float = 'left'; + + // Create the sort button + var sort = document.createElement("button"); + sort.id = "sort-fragments"; + sort.textContent = "Sort"; + sort.style.display = "inline-block"; + sort.style.visibility = "hidden"; + sort.onclick = buttonSortFragmentClick; + // Append the interface buttons into the interfaceButtons object. interfaceButtons.appendChild(playback); interfaceButtons.appendChild(submit); + interfaceButtons.appendChild(sort); - // Create a slider box - var sliderBox = document.createElement('div'); - sliderBox.style.width = "100%"; - sliderBox.style.height = window.innerHeight - 200 + 12 + 'px'; - sliderBox.style.marginBottom = '10px'; - sliderBox.id = 'slider'; - var scaleHolder = document.createElement('div'); - scaleHolder.id = "scale-holder"; - scaleHolder.style.marginLeft = "107px"; - sliderBox.appendChild(scaleHolder); + + // Create outside reference holder + var outsideRef = document.createElement("div"); + outsideRef.id = "outside-reference-holder"; + + // Create a holder for the slider rows + var sliderBox = document.createElement("div"); + sliderBox.id = 'slider-box'; + var sliderGrid = document.createElement("div"); + sliderGrid.id = "slider-grid"; + sliderBox.appendChild(sliderGrid); var scaleText = document.createElement('div'); scaleText.id = "scale-text-holder"; - scaleText.style.height = "25px"; - scaleText.style.width = "100%"; - scaleHolder.appendChild(scaleText); - var scaleCanvas = document.createElement('canvas'); - scaleCanvas.id = "scale-canvas"; - scaleCanvas.style.marginLeft = "150px"; - scaleHolder.appendChild(scaleCanvas); - var sliderObjectHolder = document.createElement('div'); - sliderObjectHolder.id = 'slider-holder'; - sliderObjectHolder.align = "center"; - sliderBox.appendChild(sliderObjectHolder); + sliderGrid.appendChild(scaleText); + // Global parent for the comment boxes on the page var feedbackHolder = document.createElement('div'); @@ -114,15 +115,20 @@ // Load the full interface testState.initialise(); testState.advanceState(); -} +}; function loadTest(page) { // Called each time a new test page is to be build. The page specification node is the only item passed in - var id = page.id; var feedbackHolder = document.getElementById('feedbackHolder'); + var sliderBox = document.getElementById('slider-box'); + var sliderGrid = document.getElementById("slider-grid"); + var scaleTextHolder = document.getElementById("scale-text-holder"); + var interfaceObj = interfaceContext.getCombinedInterfaces(page); + var commentBoxPrefix = "Comment on track"; + var loopPlayback = page.loop; feedbackHolder.innerHTML = ""; - var interfaceObj = interfaceContext.getCombinedInterfaces(page); + if (interfaceObj.length > 1) { console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node"); } @@ -132,7 +138,7 @@ if (typeof page.title == "string" && page.title.length > 0) { document.getElementById("test-title").textContent = page.title; } - + // Set the axis title if (interfaceObj.title !== null) { document.getElementById("pageTitle").textContent = interfaceObj.title; } @@ -147,15 +153,67 @@ // Delete outside reference document.getElementById("outside-reference-holder").innerHTML = ""; - var sliderBox = document.getElementById('slider-holder'); - sliderBox.innerHTML = ""; - - var commentBoxPrefix = "Comment on track"; + // Get the comment box prefix if (interfaceObj.commentBoxPrefix !== undefined) { commentBoxPrefix = interfaceObj.commentBoxPrefix; } - var loopPlayback = page.loop; + // Populate the comment questions + $(page.commentQuestions).each(function (index, element) { + var node = interfaceContext.createCommentQuestion(element); + feedbackHolder.appendChild(node.holder); + }); + + // Configure the grid + var numRows = page.audioElements.filter(function (a) { + return (a.type !== "outside-reference"); + }).length; + var numColumns = page.interfaces[0].scales.length; + sliderGrid.style.gridTemplateRows = "50px repeat(" + numRows + ", 72px)"; + scaleTextHolder.style.gridTemplateColumns = "100px repeat(" + numColumns + ", 1fr) 100px"; + page.interfaces[0].scales.sort(function (a, b) { + if (a.position > b.position) { + return 1; + } else if (a.position < b.position) { + return -1; + } + return 0; + }).forEach(function (a, i) { + var h = document.createElement("div"); + var text = document.createElement("span"); + h.className = "scale-text"; + h.style.gridColumn = String(i + 2) + "/" + String(i + 3); + text.textContent = a.text; + h.appendChild(text); + scaleTextHolder.appendChild(h); + }) + + // Find all the audioElements from the audioHolder + var index = 0; + var labelType = page.label; + if (labelType == "default") { + labelType = "number"; + } + $(page.audioElements).each(function (pageIndex, element) { + // Find URL of track + // In this jQuery loop, variable 'this' holds the current audioElement. + + var audioObject = audioEngineContext.newTrack(element); + if (element.type == 'outside-reference') { + // Construct outside reference; + var orNode = new interfaceContext.outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder")); + audioObject.bindInterface(orNode); + } else { + // Create a slider per track + var label = interfaceContext.getLabel(labelType, index, page.labelStart); + var sliderObj = new discreteObject(audioObject, label); + sliderGrid.appendChild(sliderObj.DOMRoot); + audioObject.bindInterface(sliderObj); + interfaceContext.commentBoxes.createCommentBox(audioObject); + index += 1; + } + + }); interfaceObj.options.forEach(function (option) { if (option.type == "show") { switch (option.name) { @@ -187,204 +245,109 @@ case "comments": interfaceContext.commentBoxes.showCommentBoxes(feedbackHolder, true); break; + case "fragmentSort": + var button = document.getElementById('sort-fragments'); + button.style.visibility = "visible"; + break; } } }); - - // Find all the audioElements from the audioHolder - var index = 0; - var interfaceScales = page.interfaces[0].scales; - var labelType = page.label; - if (labelType == "default") { - labelType = "number"; - } - $(page.audioElements).each(function (pageIndex, element) { - // Find URL of track - // In this jQuery loop, variable 'this' holds the current audioElement. - - var audioObject = audioEngineContext.newTrack(element); - if (element.type == 'outside-reference') { - // Construct outside reference; - var orNode = new interfaceContext.outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder")); - audioObject.bindInterface(orNode); - } else { - // Create a slider per track - var label = interfaceContext.getLabel(labelType, index, page.labelStart); - var sliderObj = new discreteObject(audioObject, label, interfaceScales); - sliderBox.appendChild(sliderObj.holder); - audioObject.bindInterface(sliderObj); - interfaceContext.commentBoxes.createCommentBox(audioObject); - index += 1; - } - - }); - - $(page.commentQuestions).each(function (index, element) { - var node = interfaceContext.createCommentQuestion(element); - feedbackHolder.appendChild(node.holder); - }); - // Auto-align resizeWindow(null); } -function discreteObject(audioObject, label, interfaceScales) { +function discreteObject(audioObject, label) { // An example node, you can make this however you want for each audioElement. // However, every audioObject (audioEngineContext.audioObject) MUST have an interface object with the following // You attach them by calling audioObject.bindInterface( ) - if (interfaceScales === null || interfaceScales.length === 0) { - console.log("WARNING: The discrete radio's are built depending on the number of scale points specified! Ensure you have some specified. Defaulting to 5 for now!"); - var numOptions = 5; - } - this.parent = audioObject; + var playing = false; - this.holder = document.createElement('div'); - this.title = document.createElement('div'); - this.discreteHolder = document.createElement('div'); - this.discretes = []; - this.play = document.createElement('button'); - - this.holder.className = 'track-slider'; - this.holder.style.width = window.innerWidth - 200 + 'px'; - this.holder.appendChild(this.title); - this.holder.appendChild(this.discreteHolder); - this.holder.appendChild(this.play); - this.holder.setAttribute('trackIndex', audioObject.id); - this.title.textContent = label; - this.title.className = 'track-slider-title'; - - this.discreteHolder.className = "track-slider-range"; - this.discreteHolder.style.width = window.innerWidth - 500 + 'px'; - this.radioClicked = function (event) { - var time = audioEngineContext.timer.getTestTime(); - if (audioEngineContext.status === 0) { - event.currentTarget.checked = false; - return; - } - var id = this.parent.id; - var position = this.getValue(); - this.parent.metric.moved(time, position); - console.log('slider ' + id + ' moved to ' + position + ' (' + time + ')'); - - }; - this.handleEvent = function (event) { - if (event.currentTarget.getAttribute("name") === this.parent.specification.id) { - this.radioClicked(event); + function buttonClicked(event) { + if (!playing) { + audioEngineContext.play(audioObject.id); + } else { + audioEngineContext.stop(); } }; - for (var i = 0; i < interfaceScales.length; i++) { - var node = document.createElement('input'); - node.setAttribute('type', 'radio'); - node.className = 'track-radio'; - node.disabled = true; - node.setAttribute('position', interfaceScales[i].position); - node.setAttribute('name', audioObject.specification.id); - node.setAttribute('id', audioObject.specification.id + '-' + String(i)); - this.discretes.push(node); - this.discreteHolder.appendChild(node); - node.addEventListener("click", this); + + function radioSelected(event) { + var time = audioEngineContext.timer.getTestTime(); + audioObject.metric.moved(time, event.currentTarget.value); + console.log("slider " + audioObject.id + " moved to " + event.currentTarget.value + "(" + time + ")"); + }; + + var root = document.createElement("div"), + labelHolder = document.createElement("div"), + button = document.createElement("button"); + root.className = "discrete-row"; + labelHolder.className = "discrete-label"; + button.className = "discrete-button"; + root.appendChild(labelHolder); + + var labelSpan = document.createElement("span"); + labelHolder.appendChild(labelSpan); + labelSpan.textContent = label; + button.textContent = "Listen"; + button.disabled = "true"; + button.addEventListener("click", this); + + var numScales = audioObject.specification.parent.interfaces[0].scales.length; + root.style.gridTemplateColumns = "100px repeat(" + numScales + ", 1fr) 100px"; + for (var n = 0; n < numScales; n++) { + var input = document.createElement("input"); + input.type = "radio"; + input.disabled = "true"; + input.value = n / (numScales - 1); + input.addEventListener("click", this); + input.name = audioObject.specification.id; + root.appendChild(input); } - - this.play.className = 'track-slider-button'; - this.play.textContent = "Loading..."; - this.play.value = audioObject.id; - this.play.disabled = true; - this.play.setAttribute("playstate", "ready"); - this.play.onclick = function (event) { - var id = Number(event.currentTarget.value); - //audioEngineContext.metric.sliderPlayed(id); - if (event.currentTarget.getAttribute("playstate") == "ready") - audioEngineContext.play(id); - else if (event.currentTarget.getAttribute("playstate") == "playing") - audioEngineContext.stop(); - }; - this.resize = function (event) { - this.holder.style.width = window.innerWidth - 200 + 'px'; - this.discreteHolder.style.width = window.innerWidth - 500 + 'px'; - //text.style.left = (posPix+150-($(text).width()/2)) +'px'; - for (var i = 0; i < this.discretes.length; i++) { - var width = $(this.discreteHolder).width() - 20; - var node = this.discretes[i]; - var nodeW = $(node).width(); - var position = node.getAttribute('position'); - var posPix = Math.round(width * (position / 100.0)); - node.style.left = (posPix + 10 - (nodeW / 2)) + 'px'; + root.appendChild(button); + this.handleEvent = function (event) { + if (event.currentTarget === button) { + buttonClicked(event); + } else if (event.currentTarget.type === "radio") { + radioSelected(event); } - }; + } this.enable = function () { // This is used to tell the interface object that playback of this node is ready - this.play.disabled = false; - this.play.textContent = "Play"; - $(this.slider).removeClass('track-slider-disabled'); - this.discretes.forEach(function (elem) { - elem.disabled = false; - }); + button.disabled = ""; + var a = root.querySelectorAll("input[type=\"radio\"]"); + for (var n = 0; n < a.length; n++) { + a[n].disabled = false; + } + button.textContent = "Listen"; }; this.updateLoading = function (progress) { // progress is a value from 0 to 100 indicating the current download state of media files - if (progress != 100) { - progress = String(progress); - progress = progress.split('.')[0]; - this.play.textContent = progress + '%'; - } else { - this.play.textContent = "Play"; - } + button.textContent = progress + "%"; }; - this.startPlayback = function () { - // Called by audioObject when playback begins - this.play.setAttribute("playstate", "playing"); - $(".track-slider").removeClass('track-slider-playing'); - $(this.holder).addClass('track-slider-playing'); - var outsideReference = document.getElementById('outside-reference'); - this.play.textContent = "Listening"; - if (outsideReference !== null) { - $(outsideReference).removeClass('track-slider-playing'); - } - if (this.parent.specification.parent.playOne || specification.playOne) { - $('.track-slider-button').text = "Wait"; - $('.track-slider-button').attr("disabled", "true"); - } - interfaceContext.commentBoxes.highlightById(audioObject.id); - if (audioObject.specification.image !== undefined) { - interfaceContext.imageHolder.setImage(audioObject.specification.image); - } + // Called when playback has begun + playing = true; + $(root).addClass("discrete-row-playing"); + button.textContent = "Stop"; }; this.stopPlayback = function () { - // Called by audioObject when playback stops - if (this.play.getAttribute("playstate") == "playing") { - this.play.setAttribute("playstate", "ready"); - $(this.holder).removeClass('track-slider-playing'); - $('.track-slider-button').text = "Play"; - this.play.textContent = "Play"; - $('.track-slider-button').removeAttr("disabled"); - var box = interfaceContext.commentBoxes.boxes.find(function (a) { - return a.id === audioObject.id; - }); - if (box) { - box.highlight(false); - } - if (audioObject.specification.parent.interfaces[0].image !== undefined) { - interfaceContext.imageHolder.setImage(audioObject.specification.parent.interfaces[0].image); - } else { - interfaceContext.imageHolder.setImage(""); + // Called when playback has stopped. This gets called even if playback never started! + playing = false; + $(root).removeClass("discrete-row-playing"); + button.textContent = "Listen"; + }; + this.getValue = function () { + // Return the current value of the object. If there is no value, return 0 + var a = root.querySelectorAll("input[type=\"radio\"]"); + for (var n = 0; n < a.length; n++) { + if (a[n].checked) { + return Number(a[n].value); } } - }; - - this.getValue = function () { - // Return the current value of the object. If there is no value, return -1 - var checkedElement = this.discretes.find(function (elem) { - return elem.checked; - }); - if (checkedElement === undefined) { - return -1; - } - return checkedElement.getAttribute("position") / 100.0; + return -1; }; this.getPresentedId = function () { // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale - return this.title.textContent; + return label; }; this.canMove = function () { // Return either true or false if the interface object can be moved. AB / Reference cannot, whilst sliders can and therefore have a continuous scale. @@ -399,75 +362,36 @@ var node = storage.document.createElement('value'); node.textContent = this.getValue(); return node; + }; this.error = function () { - // audioObject has an error!! - this.playback.textContent = "Error"; - $(this.playback).addClass("error-colour"); + // If there is an error with the audioObject, this will be called to indicate a failure }; -} + Object.defineProperties(this, { + "DOMRoot": { + "value": root + } + }); +}; function resizeWindow(event) { // Called on every window resize event, use this to scale your page properly - var numObj = document.getElementsByClassName('track-slider').length; - var totalHeight = (numObj * 66) - 30; - document.getElementById('scale-holder').style.width = window.innerWidth - 220 + 'px'; - // Cheers edge for making me delete a canvas every resize. - var canvas = document.getElementById('scale-canvas'); - var new_canvas = document.createElement("canvas"); - new_canvas.id = 'scale-canvas'; - new_canvas.style.marginLeft = "150px"; - canvas.parentElement.appendChild(new_canvas); - canvas.parentElement.removeChild(canvas); - new_canvas.width = window.innerWidth - 520; - new_canvas.height = totalHeight; - for (var i in audioEngineContext.audioObjects) { - if (audioEngineContext.audioObjects[i].specification.type != 'outside-reference') { - audioEngineContext.audioObjects[i].interfaceDOM.resize(event); - } - } - document.getElementById('slider-holder').style.height = totalHeight + 'px'; - document.getElementById('slider').style.height = totalHeight + 70 + 'px'; - drawScale(); } -function drawScale() { - var interfaceObj = testState.currentStateMap.interfaces[0]; - var scales = testState.currentStateMap.interfaces[0].scales; - scales = scales.sort(function (a, b) { - return a.position - b.position; +function buttonSortFragmentClick() { + var sortIndex = interfaceContext.sortFragmentsByScore(); + var sliderBox = document.getElementById("slider-holder"); + var nodes = audioEngineContext.audioObjects.filter(function (ao) { + return ao.specification.type !== "outside-reference"; }); - var canvas = document.getElementById('scale-canvas'); - var ctx = canvas.getContext("2d"); - var height = canvas.height; - var width = canvas.width; - var textHolder = document.getElementById('scale-text-holder'); - textHolder.innerHTML = ""; - ctx.fillStyle = "#000000"; - ctx.setLineDash([1, 4]); - scales.forEach(function (scale) { - var posPercent = scale.position / 100.0; - var posPix = Math.round(width * posPercent); - if (posPix <= 0) { - posPix = 1; - } - if (posPix >= width) { - posPix = width - 1; - } - ctx.moveTo(posPix, 0); - ctx.lineTo(posPix, height); - ctx.stroke(); - - var text = document.createElement('div'); - text.align = "center"; - var textC = document.createElement('span'); - textC.textContent = scale.text; - text.appendChild(textC); - text.className = "scale-text"; - textHolder.appendChild(text); - text.style.width = $(text.children[0]).width() + 'px'; - text.style.left = (posPix + 150 - ($(text).width() / 2)) + 'px'; + var i; + nodes.forEach(function (ao) { + sliderBox.removeChild(ao.interfaceDOM.holder); }); + for (i = 0; i < nodes.length; i++) { + var j = sortIndex[i]; + sliderBox.appendChild(nodes[j].interfaceDOM.holder); + } } function buttonSubmitClick() // TODO: Only when all songs have been played! @@ -485,9 +409,12 @@ if (interfaceContext.checkFragmentMinPlays() === false) { return; } + if (interfaceContext.checkCommentQuestions() === false) { + return; + } for (var i = 0; i < checks.length; i++) { - var checkState; + var checkState = true; if (checks[i].type == 'check') { switch (checks[i].name) { case 'fragmentPlayed': @@ -510,16 +437,15 @@ case 'scalerange': // Check the scale has been used effectively checkState = interfaceContext.checkScaleRange(checks[i].errorMessage); + break; default: console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface"); break; } - if (checkState === false) { - canContinue = false; - } } - if (!canContinue) { + if (checkState === false) { + canContinue = false; break; } }
--- a/interfaces/horizontal-sliders.js Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/horizontal-sliders.js Wed Nov 22 10:10:44 2017 +0000 @@ -62,9 +62,20 @@ submit.onclick = buttonSubmitClick; submit.id = 'submit-button'; submit.style.float = 'left'; + + // Create the sort button + var sort = document.createElement("button"); + sort.id = "sort-fragments"; + sort.textContent = "Sort"; + sort.style.display = "inline-block"; + sort.style.visibility = "hidden"; + sort.onclick = buttonSortFragmentClick; + // Append the interface buttons into the interfaceButtons object. interfaceButtons.appendChild(playback); interfaceButtons.appendChild(submit); + interfaceButtons.appendChild(sort); + // Create outside reference holder var outsideRef = document.createElement("div"); @@ -226,6 +237,10 @@ case "comments": interfaceContext.commentBoxes.showCommentBoxes(feedbackHolder, true); break; + case "fragmentSort": + var button = document.getElementById('sort-fragments'); + button.style.visibility = "visible"; + break; } } }); @@ -383,6 +398,14 @@ function drawScale() { var interfaceObj = testState.currentStateMap.interfaces[0]; var scales = testState.currentStateMap.interfaces[0].scales; + var ticks = specification.interfaces.options.concat(interfaceObj.options).find(function (a) { + return (a.type == "show" && a.name == "ticks"); + }); + if (ticks !== undefined) { + ticks = true; + } else { + ticks = false; + } scales = scales.sort(function (a, b) { return a.position - b.position; }); @@ -403,9 +426,11 @@ if (posPix >= width) { posPix = width - 1; } - ctx.moveTo(posPix, 0); - ctx.lineTo(posPix, height); - ctx.stroke(); + if (ticks) { + ctx.moveTo(posPix, 0); + ctx.lineTo(posPix, height); + ctx.stroke(); + } var text = document.createElement('div'); text.align = "center"; @@ -419,6 +444,22 @@ }); } +function buttonSortFragmentClick() { + var sortIndex = interfaceContext.sortFragmentsByScore(); + var sliderBox = document.getElementById("slider-holder"); + var nodes = audioEngineContext.audioObjects.filter(function (ao) { + return ao.specification.type !== "outside-reference"; + }); + var i; + nodes.forEach(function (ao) { + sliderBox.removeChild(ao.interfaceDOM.holder); + }); + for (i = 0; i < nodes.length; i++) { + var j = sortIndex[i]; + sliderBox.appendChild(nodes[j].interfaceDOM.holder); + } +} + function buttonSubmitClick() // TODO: Only when all songs have been played! { var checks = testState.currentStateMap.interfaces[0].options, @@ -434,6 +475,9 @@ if (interfaceContext.checkFragmentMinPlays() === false) { return; } + if (interfaceContext.checkCommentQuestions() === false) { + return; + } for (var i = 0; i < checks.length; i++) { var checkState = true;
--- a/interfaces/mushra.css Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/mushra.css Wed Nov 22 10:10:44 2017 +0000 @@ -39,6 +39,8 @@ } div.scale-text { position: absolute; + text-align: right; + min-width: 100px; } canvas#scale-canvas { position: relative;
--- a/interfaces/mushra.js Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/mushra.js Wed Nov 22 10:10:44 2017 +0000 @@ -67,9 +67,20 @@ submit.onclick = buttonSubmitClick; submit.id = 'submit-button'; submit.style.display = 'inline-block'; + + // Create the sort button + var sort = document.createElement("button"); + sort.id = "sort-fragments"; + sort.textContent = "Sort"; + sort.style.display = "inline-block"; + sort.style.visibility = "hidden"; + sort.onclick = buttonSortFragmentClick; + // Append the interface buttons into the interfaceButtons object. interfaceButtons.appendChild(playback); interfaceButtons.appendChild(submit); + interfaceButtons.appendChild(sort); + // Create outside reference holder var outsideRef = document.createElement("div"); @@ -206,6 +217,8 @@ var interfaceOptions = interfaceObj.options; + var sortButton = document.getElementById("sort-fragments"); + sortButton.style.visibility = "hidden"; interfaceOptions.forEach(function (option) { if (option.type == "show") { switch (option.name) { @@ -239,31 +252,8 @@ interfaceContext.commentBoxes.showCommentBoxes(feedbackHolder, true); break; case "fragmentSort": - var button = document.getElementById('sort'); - if (button === null) { - button = document.createElement("button"); - button.id = 'sort'; - button.textContent = "Sort"; - button.style.display = 'inline-block'; - var container = document.getElementById("interface-buttons"); - var neighbour = container.lastElementChild; - container.appendChild(button); - button.onclick = function () { - var sortIndex = interfaceContext.sortFragmentsByScore(); - var sliderBox = document.getElementById("slider-holder"); - var nodes = audioEngineContext.audioObjects.filter(function (ao) { - return ao.specification.type !== "outside-reference"; - }); - var i; - nodes.forEach(function (ao) { - sliderBox.removeChild(ao.interfaceDOM.holder); - }); - for (i = 0; i < nodes.length; i++) { - var j = sortIndex[i]; - sliderBox.appendChild(nodes[j].interfaceDOM.holder); - } - }; - } + var button = document.getElementById('sort-fragments'); + button.style.visibility = "visible"; break; } } @@ -480,6 +470,14 @@ function drawScale() { var interfaceObj = testState.currentStateMap.interfaces[0]; var scales = testState.currentStateMap.interfaces[0].scales; + var ticks = specification.interfaces.options.concat(interfaceObj.options).find(function (a) { + return (a.type == "show" && a.name == "ticks"); + }); + if (ticks !== undefined) { + ticks = true; + } else { + ticks = false; + } scales = scales.sort(function (a, b) { return a.position - b.position; }); @@ -495,11 +493,13 @@ scales.forEach(function (scale) { var posPercent = scale.position / 100.0; var posPix = (1 - posPercent) * (draw_heights[1] - draw_heights[0]) + draw_heights[0]; - ctx.fillStyle = "#000000"; - ctx.setLineDash([1, 2]); - ctx.moveTo(0, posPix); - ctx.lineTo(width, posPix); - ctx.stroke(); + if (ticks) { + ctx.fillStyle = "#000000"; + ctx.setLineDash([1, 2]); + ctx.moveTo(0, posPix); + ctx.lineTo(width, posPix); + ctx.stroke(); + } var text = document.createElement('div'); text.align = "right"; var textC = document.createElement('span'); @@ -508,11 +508,26 @@ text.className = "scale-text"; textHolder.appendChild(text); text.style.top = (posPix - 9) + 'px'; - text.style.left = 100 - ($(text).width() + 3) + 'px'; lastHeight = posPix; }); } +function buttonSortFragmentClick() { + var sortIndex = interfaceContext.sortFragmentsByScore(); + var sliderBox = document.getElementById("slider-holder"); + var nodes = audioEngineContext.audioObjects.filter(function (ao) { + return ao.specification.type !== "outside-reference"; + }); + var i; + nodes.forEach(function (ao) { + sliderBox.removeChild(ao.interfaceDOM.holder); + }); + for (i = 0; i < nodes.length; i++) { + var j = sortIndex[i]; + sliderBox.appendChild(nodes[j].interfaceDOM.holder); + } +} + function buttonSubmitClick() // TODO: Only when all songs have been played! { var checks = testState.currentStateMap.interfaces[0].options, @@ -528,6 +543,9 @@ if (interfaceContext.checkFragmentMinPlays() === false) { return; } + if (interfaceContext.checkCommentQuestions() === false) { + return; + } for (var i = 0; i < checks.length; i++) { var checkState = true;
--- a/interfaces/timeline.js Wed Nov 22 10:08:26 2017 +0000 +++ b/interfaces/timeline.js Wed Nov 22 10:10:44 2017 +0000 @@ -505,6 +505,9 @@ if (interfaceContext.checkFragmentMinPlays() === false) { return; } + if (interfaceContext.checkCommentQuestions() === false) { + return; + } for (var i = 0; i < checks.length; i++) { var checkState = true; if (checks[i].type == 'check') {
--- a/js/core.js Wed Nov 22 10:08:26 2017 +0000 +++ b/js/core.js Wed Nov 22 10:10:44 2017 +0000 @@ -2220,10 +2220,19 @@ }; this.moved = function (time, position) { + var last; if (time > 0) { this.wasMoved = true; } - this.movementTracker[this.movementTracker.length] = [time, position]; + // Get the last entry + if (this.movementTracker.length > 0) { + last = this.movementTracker[this.movementTracker.length - 1]; + } else { + last = -1; + } + if (position != last[1]) { + this.movementTracker[this.movementTracker.length] = [time, position]; + } }; this.startListening = function (time) { @@ -2711,6 +2720,12 @@ this.textArea.style.width = boxwidth - 6 + "px"; }; this.resize(); + this.check = function () { + if (this.specification.mandatory && this.textArea.value.length == 0) { + return false; + } + return true; + } }; this.radioBox = function (commentQuestion) { @@ -2785,6 +2800,15 @@ } this.holder.style.width = boxwidth + "px"; }; + this.check = function () { + var anyChecked = this.options.some(function (a) { + return a.checked; + }); + if (this.specification.mandatory && anyChecked == false) { + return false; + } + return true; + } this.resize(); }; @@ -2851,6 +2875,15 @@ } this.holder.style.width = boxwidth + "px"; }; + this.check = function () { + var anyChecked = this.options.some(function (a) { + return a.checked; + }); + if (this.specification.mandatory && anyChecked == false) { + return false; + } + return true; + }; this.resize(); }; @@ -2908,6 +2941,9 @@ this.holder.style.width = boxwidth + "px"; this.slider.style.width = boxwidth - 24 + "px"; }; + this.check = function () { + return true; + } this.resize(); }; @@ -2930,6 +2966,19 @@ this.commentQuestions = []; }; + this.checkCommentQuestions = function () { + var errored = this.commentQuestions.reduce(function (a, cq) { + if (cq.check() == false) { + a.push(cq); + } + return a; + }, []); + if (errored.length == 0) { + return true; + } + interfaceContext.lightbox.post("Message", "Not all the mandatory comment boxes below have been filled."); + } + this.outsideReferenceDOM = function (audioObject, index, inject) { this.parent = audioObject; this.outsideReferenceHolder = document.createElement('button');
--- a/js/specification.js Wed Nov 22 10:08:26 2017 +0000 +++ b/js/specification.js Wed Nov 22 10:10:44 2017 +0000 @@ -19,6 +19,7 @@ this.playOne = undefined; this.minNumberPlays = undefined; this.maxNumberPlays = undefined; + this.randomiseAxisOrder = undefined; // nodes this.metrics = new metricNode(); @@ -222,6 +223,7 @@ this.location = undefined; this.options = []; this.parent = undefined; + this.showBackButton = true; this.specification = specification; this.addOption = function () { @@ -409,6 +411,12 @@ } else if (this.location == 'after') { this.location = 'post'; } + this.showBackButton = xml.getAttribute("showBackButton"); + if (this.showBackButton == "false") { + this.showBackButton = false; + } else { + this.showBackButton = true; + } var child = xml.firstElementChild; while (child) { var node = new this.OptionNode(this.specification); @@ -424,6 +432,7 @@ this.encode = function (doc) { var node = doc.createElement('survey'); node.setAttribute('location', this.location); + node.setAttribute('showBackButton', this.showBackButton); for (var i = 0; i < this.options.length; i++) { node.appendChild(this.options[i].exportXML(doc)); } @@ -572,6 +581,7 @@ this.commentBoxPrefix = "Comment on track"; this.minNumberPlays = undefined; this.maxNumberPlays = undefined; + this.randomiseAxisOrder = undefined; this.audioElements = []; this.commentQuestions = []; this.schema = schemaRoot.querySelector("[name=page]"); @@ -696,8 +706,12 @@ AHNode.appendChild(this.audioElements[i].encode(root)); } // Create <CommentQuestion> - for (i = 0; i < this.commentQuestions.length; i++) { - AHNode.appendChild(this.commentQuestions[i].encode(root)); + if (this.commentQuestions.length > 0) { + var node = root.createElement("commentquestions"); + for (i = 0; i < this.commentQuestions.length; i++) { + node.appendChild(this.commentQuestions[i].encode(root)); + } + AHNode.appendChild(node); } AHNode.appendChild(this.preTest.encode(root)); @@ -710,10 +724,12 @@ this.name = undefined; this.type = undefined; this.statement = undefined; + this.mandatory = undefined; this.schema = schemaRoot.querySelector('[name=commentquestion]'); this.decode = function (parent, xml) { this.id = xml.id; this.name = xml.getAttribute('name'); + this.mandatory = xml.getAttribute("mandatory") == "true"; if (this.name === null) { this.name = undefined; } @@ -794,6 +810,7 @@ throw ("Unknown type " + this.type); } node.id = this.id; + node.setAttribute("mandatory", this.mandatory); node.setAttribute("type", this.type); if (this.name !== undefined) { node.setAttribute("name", this.name);
--- a/php/requestKey.php Wed Nov 22 10:08:26 2017 +0000 +++ b/php/requestKey.php Wed Nov 22 10:10:44 2017 +0000 @@ -11,6 +11,10 @@ return $randomString; } +if (!file_exists("../saves")) { + mkdir("../saves"); +} + // Request a new session key from the server header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); header("Cache-Control: post-check=0, pre-check=0", false);
--- a/php/save.php Wed Nov 22 10:08:26 2017 +0000 +++ b/php/save.php Wed Nov 22 10:10:44 2017 +0000 @@ -33,7 +33,17 @@ } $postText = file_get_contents('php://input'); $file_key = $_GET['key']; -$filename = '../saves/'.$saveFilenamePrefix.$file_key.".xml"; + +$update = false; +if (isset($_GET["update"])) { + $update = $_GET["update"] == "update"; +} + +if ($update) { + $filename = '../saves/update-'.$saveFilenamePrefix.$file_key.".xml"; +} else { + $filename = '../saves/'.$saveFilenamePrefix.$file_key.".xml"; +} if (!file_exists($filename)) { die('<response state="error"><message>Could not find save</message></response>'); @@ -132,4 +142,8 @@ // Return XML confirmation data $xml = '<response state="OK"><message>OK</message><file bytes="'.$wbytes.'">"'.$filename.'"</file></response>'; echo $xml; + +if (!$update) { + unlink('../saves/update-'.$saveFilenamePrefix.$file_key.".xml"); +} ?>
--- a/python/generate_report.py Wed Nov 22 10:08:26 2017 +0000 +++ b/python/generate_report.py Wed Nov 22 10:10:44 2017 +0000 @@ -232,17 +232,18 @@ # number of comments (interesting if comments not mandatory) for audioelement in audioelements: - response = audioelement.find("./comment/response") - was_played = audioelement.find("./metric/metricresult/[@name='elementFlagListenedTo']") - was_moved = audioelement.find("./metric/metricresult/[@name='elementFlagMoved']") - if response is not None and response.text is not None and len(response.text) > 1: - number_of_comments += 1 - else: - number_of_missing_comments += 1 - if was_played is not None and was_played.text == 'false': - not_played.append(audioelement.get('name')) - if was_moved is not None and was_moved.text == 'false': - not_moved.append(audioelement.get('name')) + if audioelement.get("type") != "outside-reference": + response = audioelement.find("./comment/response") + was_played = audioelement.find("./metric/metricresult/[@name='elementFlagListenedTo']") + was_moved = audioelement.find("./metric/metricresult/[@name='elementFlagMoved']") + if response is not None and response.text is not None and len(response.text) > 1: + number_of_comments += 1 + else: + number_of_missing_comments += 1 + if was_played is not None and was_played.text == 'false': + not_played.append(audioelement.get('name')) + if was_moved is not None and was_moved.text == 'false': + not_moved.append(audioelement.get('name')) # update global counters total_empty_comments += number_of_missing_comments
--- a/python/pythonServer.py Wed Nov 22 10:08:26 2017 +0000 +++ b/python/pythonServer.py Wed Nov 22 10:10:44 2017 +0000 @@ -13,6 +13,7 @@ import copy import string import random +import errno if sys.version_info[0] == 2: # Version 2.x @@ -28,6 +29,12 @@ scriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) # script directory os.chdir(scriptdir) # does this work? +try: + os.makedirs("../saves") +except OSError as e: + if e.errno != errno.EEXIST: + raise + PSEUDO_PATH = '../tests/' pseudo_files = [] pseudo_index = 0 @@ -62,16 +69,17 @@ st = s.path.rsplit(',') lenSt = len(st) fmt = st[lenSt-1].rsplit('.') + fmt = fmt[len(fmt)-1] fpath = "../"+urllib2.unquote(s.path) size = os.path.getsize(fpath) - fileDump = open(fpath) + fileDump = open(fpath, mode='rb') s.send_response(200) - if (fmt[1] == 'html'): + if (fmt == 'html'): s.send_header("Content-type", 'text/html') - elif (fmt[1] == 'css'): + elif (fmt == 'css'): s.send_header("Content-type", 'text/css') - elif (fmt[1] == 'js'): + elif (fmt == 'js'): s.send_header("Content-type", 'application/javascript') else: s.send_header("Content-type", 'application/octet-stream') @@ -146,12 +154,15 @@ global curSaveIndex options = self.path.rsplit('?') options = options[1].rsplit('&') + update = False for option in options: optionPair = option.rsplit('=') if optionPair[0] == "key": key = optionPair[1] elif optionPair[0] == "saveFilenamePrefix": prefix = optionPair[1] + elif optionPair[0] == "state": + update = optionPair[1] == "update" if key == None: self.send_response(404) return @@ -161,6 +172,8 @@ postVars = self.rfile.read(varLen) print("Saving file key "+key) filename = prefix+'-'+key+'.xml' + if update: + filename = "update-"+filename file = open('../saves/'+filename,'wb') file.write(postVars) file.close() @@ -181,6 +194,9 @@ self.wfile.write(bytes(reply, "utf-8")) curSaveIndex += 1 curFileName = 'test-'+str(curSaveIndex)+'.xml' + if update == False: + if(os.path.isfile("../saves/update-"+filename)): + os.remove("../saves/update-"+filename) def testSave(self): self.send_response(200) @@ -209,7 +225,6 @@ self.wfile.write(message) elif sys.version_info[0] == 3: self.wfile.write(bytes(message, "utf-8")) - def poolXML(s): pool = ET.parse('../tests/pool.xml')
--- a/python/timeline_view_movement.py Wed Nov 22 10:08:26 2017 +0000 +++ b/python/timeline_view_movement.py Wed Nov 22 10:10:44 2017 +0000 @@ -74,6 +74,10 @@ if page_name is None: # ignore 'empty' audio_holders print("Skipping empty page name from "+subject_id+".") break + + if page.get("state") != "complete": + print("Skipping non-completed page "+page_name+" from "+subject_id+".") + break # subtract total page length from subsequent page event times page_time_temp = page.find("./metric/metricresult/[@id='testTime']") @@ -108,11 +112,20 @@ if audioelement is not None: # Check it exists audio_id = str(audioelement.get('ref')) - # break if no initial position or move events registered + # break if outside-reference + if audioelement.get("type") == "outside-reference": + break; + + # break if no initial position.... initial_position_temp = audioelement.find("./metric/metricresult/[@name='elementInitialPosition']") if initial_position_temp is None: print("Skipping "+page_name+" from "+subject_id+": does not have initial positions specified.") break + # ... or move events registered + movements = audioelement.find("./metric/metricresult[@name='elementTrackerFull']") + if movements is None: + print("Skipping "+page_name+" from "+subject_id+": does not have trackers.") + break # get move events, initial and eventual position initial_position = float(initial_position_temp.text) @@ -299,13 +312,20 @@ interfaces = page_setup.findall("./interface") interface_title = interfaces[0].find("./title") scales = interfaces[0].findall("./scales") # get first interface by default - scalelabels = scales[0].findall("./scalelabel") # get first scale by default - + labelpos = [] # array of scalelabel positions labelstr = [] # array of strings at labels - for scalelabel in scalelabels: - labelpos.append(float(scalelabel.get('position'))/100.0) - labelstr.append(scalelabel.text) + + # No scales given. Use normal floats + if len(scales) is 0: + labelpos = [0.0, 1.0] + labelstr = ["0", "100"] + else: + scalelabels = scales[0].findall("./scalelabel") # get first scale by default + + for scalelabel in scalelabels: + labelpos.append(float(scalelabel.get('position'))/100.0) + labelstr.append(scalelabel.text) # use interface name as Y axis label if interface_title is not None:
--- a/test.html Wed Nov 22 10:08:26 2017 +0000 +++ b/test.html Wed Nov 22 10:10:44 2017 +0000 @@ -37,7 +37,7 @@ <button id="popup-previous" class="popupButton">Back</button> </div> <div class="testHalt" style="visibility: hidden"></div> - <div id="footer"><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.2)</a></div> + <div id="footer"><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.3)</a></div> </body> </html>
--- a/test_create.html Wed Nov 22 10:08:26 2017 +0000 +++ b/test_create.html Wed Nov 22 10:10:44 2017 +0000 @@ -142,6 +142,12 @@ </div> <div id="globalpresurvey" class="node" ng-controller="survey" ng-init="survey = specification.preTest"> <h2>Pre Test Survey</h2> + <div class="attributes"> + <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Allow users to go both back and forward in the test"> + <span>Show back button: </span> + <input type="checkbox" ng-model="survey.showBackButton" /> + </div> + </div> <button type="button" class="btn btn-success" ng-click="addSurveyEntry()">Add Entry</button> <div class="node surveyentry" ng-repeat="opt in survey.options" ng-controller="surveyOption"> <h3>Survey Entry</h3> @@ -273,6 +279,12 @@ </div> <div id="globalpostsurvey" class="node" ng-controller="survey" ng-init="survey = specification.postTest"> <h2>Post Test Survey</h2> + <div class="attributes"> + <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Allow users to go both back and forward in the test"> + <span>Show back button: </span> + <input type="checkbox" ng-model="survey.showBackButton" /> + </div> + </div> <button type="button" class="btn btn-success" ng-click="addSurveyEntry()">Add Entry</button> <div class="node surveyentry" ng-repeat="opt in survey.options" ng-controller="surveyOption"> <h3>Survey Entry</h3> @@ -446,6 +458,10 @@ <span>Show Fragment Comments: </span> <input type="checkbox" ng-click="enableInterfaceOption($event)" /> </div> + <div class="attribute" name="ticks" type="show" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Show tick marks for each scale label"> + <span>Show Scale Ticks: </span> + <input type="checkbox" ng-click="enableInterfaceOption($event)" /> + </div> </div> </div> </div> @@ -550,6 +566,12 @@ </div> <div class="node" ng-controller="survey" ng-init="survey = page.preTest"> <h2>Pre Page Survey</h2> + <div class="attributes"> + <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Allow users to go both back and forward in the test"> + <span>Show back button: </span> + <input type="checkbox" ng-model="survey.showBackButton" /> + </div> + </div> <button type="button" class="btn btn-success" ng-click="addSurveyEntry()">Add Entry</button> <div class="node surveyentry" ng-repeat="opt in survey.options" ng-controller="surveyOption"> <h3>Survey Entry</h3> @@ -681,6 +703,12 @@ </div> <div class="node" ng-controller="survey" ng-init="survey = page.postTest"> <h2>Post Page Survey</h2> + <div class="attributes"> + <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Allow users to go both back and forward in the test"> + <span>Show back button: </span> + <input type="checkbox" ng-model="survey.showBackButton" /> + </div> + </div> <button type="button" class="btn btn-success" ng-click="addSurveyEntry()">Add Entry</button> <div class="node surveyentry" ng-repeat="opt in survey.options" ng-controller="surveyOption"> <h3>Survey Entry</h3> @@ -856,6 +884,10 @@ <span>Show Fragment Comments: </span> <input type="checkbox" ng-click="enableInterfaceOption($event)" /> </div> + <div class="attribute" name="ticks" type="show" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Show tick marks for each scale label"> + <span>Show Scale Ticks: </span> + <input type="checkbox" ng-click="enableInterfaceOption($event)" /> + </div> </div> </div> <div class="node"> @@ -909,6 +941,15 @@ <button type="button" class="btn btn-danger" ng-click="removeCommentQuestion(cq)">Remove Comment Question</button> <div class="attributes"> <div class="attribute"> + <span>Type:</span> + <select ng-model="cq.type"> + <option value="question">Question</option> + <option value="checkbox">Checkbox</option> + <option value="radio">Radio</option> + <option value="slider">Slider</option> + </select> + </div> + <div class="attribute"> <span>Unique ID:</span> <input type="text" ng-model="cq.id" required/> </div> @@ -916,6 +957,10 @@ <span>Common Name:</span> <input type="text" ng-model="cq.name" /> </div> + <div class="attribute"> + <span>Mandatory:</span> + <input type="checkbox" ng-model="cq.mandatory" /> + </div> <div class="attribute" ng-show="cq.type == 'slider'"> <span>Minimum:</span> <input type="number" ng-model="cq.min" />
--- a/tests/examples/AB_example.xml Wed Nov 22 10:08:26 2017 +0000 +++ b/tests/examples/AB_example.xml Wed Nov 22 10:10:44 2017 +0000 @@ -45,10 +45,6 @@ <metricenable>elementListenTracker</metricenable> </metric> <interface> - <interfaceoption type="check" name="fragmentMoved" /> - <interfaceoption type="check" name="scalerange" min="25" max="75"> - <errormessage>Test Error Message</errormessage> - </interfaceoption> <interfaceoption type="show" name='playhead' /> <interfaceoption type="show" name="page-count" /> <interfaceoption type="show" name='volume' />
--- a/tests/examples/APE_example.xml Wed Nov 22 10:08:26 2017 +0000 +++ b/tests/examples/APE_example.xml Wed Nov 22 10:10:44 2017 +0000 @@ -123,7 +123,7 @@ <option name="Good"></option> <option name="Great">Great</option> </commentradio> - <commentcheckbox id="character" type="checkbox"> + <commentcheckbox id="character"> <statement>Please describe the overall character</statement> <option name="funky">Funky</option> <option name="mellow">Mellow</option>
--- a/tests/examples/horizontal_example.xml Wed Nov 22 10:08:26 2017 +0000 +++ b/tests/examples/horizontal_example.xml Wed Nov 22 10:10:44 2017 +0000 @@ -17,6 +17,7 @@ <interfaceoption type="show" name="page-count" /> <interfaceoption type="show" name="volume" /> <interfaceoption type="show" name="comments" /> + <interfaceoption type="show" name="ticks" /> </interface> </setup> <page id='test-0' hostURL="media/example/" randomiseOrder='true' repeatCount='0' loop='true' loudness="-12">
--- a/tests/examples/mushra_example.xml Wed Nov 22 10:08:26 2017 +0000 +++ b/tests/examples/mushra_example.xml Wed Nov 22 10:10:44 2017 +0000 @@ -112,24 +112,24 @@ <audioelement url="5.wav" gain="0.0" id="track-10" /> <audioelement url="1.wav" gain="0.0" id="track-11" type="outside-reference" /> <commentquestions> - <commentquestion id='mixingExperience' type="question"> + <commentquestion id='mixingExperience'> <statement>What is your general experience with numbers?</statement> </commentquestion> - <commentquestion id="preference" type="radio"> + <commentradio id="preference"> <statement>Please enter your overall preference</statement> <option name="worst">Very Bad</option> <option name="bad"></option> <option name="OK">OK</option> <option name="Good"></option> <option name="Great">Great</option> - </commentquestion> - <commentquestion id="character" type="checkbox"> + </commentradio> + <commentcheckbox id="character"> <statement>Please describe the overall character</statement> <option name="funky">Funky</option> <option name="mellow">Mellow</option> <option name="laidback">Laid back</option> <option name="heavy">Heavy</option> - </commentquestion> + </commentcheckbox> </commentquestions> <survey location="before"> <surveyentry type="statement" id="test-1-intro">
--- a/tests/examples/radio_example.xml Wed Nov 22 10:08:26 2017 +0000 +++ b/tests/examples/radio_example.xml Wed Nov 22 10:10:44 2017 +0000 @@ -29,9 +29,9 @@ <scalelabel position="100">(5) Inaudible</scalelabel> </scales> </interface> - <audioelement url="0.wav" id="track-1" alwaysInclude="true" /> - <audioelement url="1.wav" id="track-2" /> - <audioelement url="3.wav" id="track-4" /> - <audioelement url="3.wav" id="track-5" /> + <audioelement url="0.wav" id="track-0" alwaysInclude="true" /> + <audioelement url="1.wav" id="track-1" /> + <audioelement url="2.wav" id="track-2" /> + <audioelement url="3.wav" id="track-3" /> </page> </waet>
--- a/xml/test-schema.xsd Wed Nov 22 10:08:26 2017 +0000 +++ b/xml/test-schema.xsd Wed Nov 22 10:10:44 2017 +0000 @@ -67,6 +67,7 @@ </xs:restriction> </xs:simpleType> </xs:attribute> + <xs:attribute name="randomiseAxisOrder" type="xs:boolean" default="false" /> <xs:attribute ref="preSilence" /> <xs:attribute ref="postSilence" /> <xs:attribute ref="playOne" /> @@ -105,6 +106,7 @@ </xs:simpleType> </xs:attribute> <xs:attribute name="labelStart" type="xs:string" use="optional" default="" /> + <xs:attribute name="randomiseAxisOrder" type="xs:boolean" use="optional" /> <xs:attribute ref="poolSize" /> <xs:attribute ref="alwaysInclude" /> <xs:attribute name="position" use="optional" type="xs:nonNegativeInteger" /> @@ -277,6 +279,7 @@ </xs:sequence> <xs:attribute ref="id" use="optional" /> <xs:attribute ref="name" use="optional" /> + <xs:attribute ref="mandatory" use="optional" /> </xs:complexType> </xs:element> @@ -296,6 +299,7 @@ </xs:sequence> <xs:attribute ref="id" use="optional" /> <xs:attribute ref="name" use="optional" /> + <xs:attribute ref="mandatory" use="optional" /> </xs:complexType> </xs:element> @@ -306,6 +310,7 @@ </xs:sequence> <xs:attribute ref="id" use="optional" /> <xs:attribute ref="name" use="optional" /> + <xs:attribute ref="mandatory" use="optional" /> </xs:complexType> </xs:element> @@ -322,6 +327,7 @@ <xs:attribute name="max" type="xs:decimal" use="required" /> <xs:attribute name="step" type="xs:decimal" use="optional" default="1" /> <xs:attribute name="value" type="xs:decimal" use="optional" /> + <xs:attribute ref="mandatory" use="optional" /> </xs:complexType> </xs:element> @@ -547,6 +553,7 @@ </xs:restriction> </xs:simpleType> </xs:attribute> + <xs:attribute name="showBackButton" type="xs:boolean" default="true" /> </xs:complexType> </xs:element>