Mercurial > hg > webaudioevaluationtool
changeset 3045:f888168418ad
#126 Complete redesign of discrete interface
author | Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk> |
---|---|
date | Wed, 27 Sep 2017 13:17:11 +0100 |
parents | 2020be3c592c |
children | 702a8d786e1a |
files | interfaces/discrete.css interfaces/discrete.js |
diffstat | 2 files changed, 213 insertions(+), 362 deletions(-) [+] |
line wrap: on
line diff
--- a/interfaces/discrete.css Tue Sep 26 17:07:06 2017 +0100 +++ b/interfaces/discrete.css Wed Sep 27 13:17:11 2017 +0100 @@ -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 Tue Sep 26 17:07:06 2017 +0100 +++ b/interfaces/discrete.js Wed Sep 27 13:17:11 2017 +0100 @@ -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,11 +60,6 @@ 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'; @@ -81,29 +80,21 @@ 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'); @@ -124,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"); } @@ -142,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; } @@ -157,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) { @@ -204,201 +252,102 @@ } } }); - - // 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. @@ -413,85 +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; - var ticks = specification.interfaces.options.concat(interfaceObj.options).find(function (a) { - return (a.type == "show" && a.name == "ticks"); +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"; }); - if (ticks !== undefined) { - ticks = true; - } else { - ticks = false; + 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); } - scales = scales.sort(function (a, b) { - return a.position - b.position; - }); - 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; - } - if (ticks) { - 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'; - }); } function buttonSubmitClick() // TODO: Only when all songs have been played! @@ -514,7 +414,7 @@ } for (var i = 0; i < checks.length; i++) { - var checkState; + var checkState = true; if (checks[i].type == 'check') { switch (checks[i].name) { case 'fragmentPlayed': @@ -537,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; } } @@ -566,22 +465,6 @@ } } -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 pageXMLSave(store, pageSpecification) { // MANDATORY // Saves a specific test page