UNPKG

ui-mturk-hit

Version:

A custom element for Amazon Mechanical Turk HITs

812 lines (689 loc) 32.5 kB
'use strict' import template from "./view.js" class MTurkHITViewController extends HTMLElement { static get observedAttributes(){ return ['value', "ribbon", "ribbon-color"]; } constructor(model){ super(); this.DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.000"; this.state = {}; this.state.connected = false; this.state.flipping = false; this.state.scale = 1; this.state.rotateY = 0; this.state.front = true; this.state.back = !this.state.front; this.state.status = null; this.state.editButtonEnabled = true this.state.resultsButtonEnabled = true this.state.previewButtonEnabled = true this.state.deleteButtonEnabled = true this.state.emailButtonEnabled = true this.state.mturkButtonEnabled = true this.state.maxButtonWaitFeedback = 5000; this.flippable = true; this.icons = {}; this.icons.publishIcon = "fa-cloud"; this.icons.stopIcon = "fa-stop"; this.icons.resumeIcon = "fa-play"; this.icons.resultsIcon = "fa-chart-pie"; this.icons.resultsIconAlt = "fa-chart-bar"; this.icons.editIcon = "fa-pencil"; this.icons.previewIcon = "fa-eye"; this.icons.deleteIcon = "fa-trash-alt"; this.icons.amazonIcon = "fa-amazon"; this.icons.emailIcon = "fa-envolope"; this.colors = {}; this.colors.blue = "#60b1d5"; this.colors.green = "#54cf84"; this.colors.orange = "#fd8b25"; //Keeps reference of events with bindings (so we can remove them) //see: https://stackoverflow.com/questions/11565471/removing-event-listener-which-was-added-with-bind this.event = {}; this.view = {}; this.model = model || {}; this._setModelDefaults(); this.shadowRoot = this.attachShadow({mode: 'open'}); this.shadowRoot.appendChild(template.content.cloneNode(true)); } //Fires when the element is inserted into the DOM. It's a good place to set //the initial role, tabindex, internal state, and install event listeners. // //NOTE: A user may set a property on an instance of an element, before its //prototype has been connected to this class. The _upgradeProperty() method //will check for any instance properties and run them through the proper //class setters. connectedCallback() { //Wire views here this.view.container = this.shadowRoot.querySelector('#container'); this.view.front = this.shadowRoot.querySelector('#front'); this.view.back = this.shadowRoot.querySelector('#back'); this.view.HITStatusIcon = this.shadowRoot.querySelector('#HITStatusIcon'); this.view.infoButtonContainer = this.shadowRoot.querySelector('#infoButtonContainer'); this.view.infoButtonIcon = this.shadowRoot.querySelector('#infoButtonIcon'); this.view.progressBar = this.shadowRoot.querySelector('#progressBar'); this.view.editHITButton = this.shadowRoot.querySelector('#editHITButton'); this.view.editHITIcon = this.shadowRoot.querySelector('#editHITIcon'); this.view.HITResultsButton = this.shadowRoot.querySelector('#HITResultsButton'); this.view.HITResultsIcon = this.shadowRoot.querySelector('#HITResultsIcon'); this.view.HITPreviewButton = this.shadowRoot.querySelector('#HITPreviewButton'); this.view.HITPreviewIcon = this.shadowRoot.querySelector('#HITPreviewIcon'); this.view.deleteHITButton = this.shadowRoot.querySelector('#deleteHITButton'); this.view.deleteHITIcon = this.shadowRoot.querySelector('#deleteHITIcon'); this.view.detailsTable = this.shadowRoot.querySelector('#detailsTable'); this.view.HITStatusLabel = this.shadowRoot.querySelector('#HITStatusLabel'); this.view.editHITLabel = this.shadowRoot.querySelector('#editHITLabel'); this.view.HITResultsLabel = this.shadowRoot.querySelector('#HITResultsLabel'); this.view.HITLinkLabel = this.shadowRoot.querySelector('#HITLinkLabel'); this.view.deleteHITLabel = this.shadowRoot.querySelector('#deleteHITLabel'); this.view.title = this.shadowRoot.querySelector('#title'); this.view.HITStatus = this.shadowRoot.querySelector('#HITStatus'); this.view.HITEmail = this.shadowRoot.querySelector("#HITEmail"); this.view.HITEmailIcon = this.shadowRoot.querySelector("#HITEmailIcon"); this.view.HITMturk = this.shadowRoot.querySelector("#HITMturk"); this.view.HITMturkIcon = this.shadowRoot.querySelector("#HITMturkIcon"); this.view.description = this.shadowRoot.querySelector('#description'); this.view.numberOfAssignmentsCompleted = this.shadowRoot.querySelector('#numberOfAssignmentsCompleted'); this.view.maxAssignments = this.shadowRoot.querySelector('#maxAssignments'); this.view.creationTime = this.shadowRoot.querySelector('#creationTime'); this.view.expiration = this.shadowRoot.querySelector('#expiration'); this.view.expirationLabel = this.shadowRoot.querySelector('#expirationLabel'); this.view.assignmentDurationInSeconds = this.shadowRoot.querySelector('#assignmentDurationInSeconds'); this.view.formattedPrice = this.shadowRoot.querySelector('#formattedPrice'); this.view.cornerRibbonContainer = this.shadowRoot.querySelector('#cornerRibbonContainer'); this.view.cornerRibbon = this.shadowRoot.querySelector('#cornerRibbon'); this.view.cornerRibbonText = this.shadowRoot.querySelector('#cornerRibbonText'); this.view.cornerRibbonBack = this.shadowRoot.querySelector('#cornerRibbonBack'); //Reference events with bindings this.event.click = this._onClick.bind(this); this.event.hover = this._onHover.bind(this); this.view.container.addEventListener('click', this.event.click); this.view.HITStatus.addEventListener('click', this.event.click); this.view.editHITButton.addEventListener('click', this.event.click); this.view.HITResultsButton.addEventListener('click', this.event.click); this.view.HITPreviewButton.addEventListener('click', this.event.click); this.view.deleteHITButton.addEventListener('click', this.event.click); this.view.infoButtonIcon.addEventListener('click', this.event.click); this.view.infoButtonContainer.addEventListener('click', this.event.click); this.view.HITStatusIcon.addEventListener('mouseover', this.event.hover); this.view.editHITIcon.addEventListener('mouseover', this.event.hover); this.view.HITResultsIcon.addEventListener('mouseover', this.event.hover); this.view.HITPreviewIcon.addEventListener('mouseover', this.event.hover); this.view.deleteHITIcon.addEventListener('mouseover', this.event.hover); this.view.HITStatusIcon.addEventListener('mouseout', this.event.hover); this.view.editHITIcon.addEventListener('mouseout', this.event.hover); this.view.HITResultsIcon.addEventListener('mouseout', this.event.hover); this.view.HITPreviewIcon.addEventListener('mouseout', this.event.hover); this.view.deleteHITIcon.addEventListener('mouseout', this.event.hover); this.state.connected = true; this.showRibbon = false; this._updateView(); } adoptedCallback(){ console.log('adoptedCallback'); } attributeChangedCallback(attrName, oldVal, newVal) { switch(attrName){ case 'value': newVal = JSON.parse(newVal); let validObject = this._lowerCaseProperties(newVal); validObject.reward = this._lowerCaseProperties(validObject.reward); if(validObject !== this.value){ this.value = validObject; } break; case "ribbon": //Make sure it is proper JS object (property names are not capitalized) newVal = JSON.parse(newVal); if(newVal !== this.ribbon){ this.ribbon = newVal; } break; default: console.warn(`Attribute ${attrName} is not handled, you should probably do that`); } } _setModelDefaults(){ this.model.numberOfAssignmentsCompleted = this.model.numberOfAssignmentsCompleted || 0; this.model.type = "ui-mturk-hit"; } _lowerCaseProperties(obj){ let validObject = {}; Object.entries(obj).forEach(keyValue => { const key = keyValue[0]; const value = keyValue[1]; let lowerCaseKey = key; const shouldBeMiniscule = !key.includes("HIT") if(shouldBeMiniscule){ lowerCaseKey = key.charAt(0).toLowerCase() + key.slice(1); } validObject[lowerCaseKey] = value; }) return validObject; } get shadowRoot(){return this._shadowRoot;} set shadowRoot(value){ this._shadowRoot = value} get value(){ return this.model; } set value(value){ //Check if attribute matches property value, Sync the property with the //attribute if they do not, skip this step if already sync if(this.getAttribute('value') !== JSON.stringify(value)){ //By setting the attribute, the attributeChangedCallback() function is //called, which inturn calls this setter again. this.setAttribute('value', JSON.stringify(value)); //attributeChangeCallback() implicitly called return; } this.model = value; this._setModelDefaults(); this._updateView(); } get hasResults(){return this.model.numberOfAssignmentsCompleted > 0; } get hasNoResults(){return !this.hasResults; } get ribbon(){ return this._ribbon; } set ribbon(value){ //Check if attribute matches property value, Sync the property with the //attribute if they do not, skip this step if already sync if(this.getAttribute('ribbon') !== JSON.stringify(value)){ //By setting the attribute, the attributeChangedCallback() function is //called, which inturn calls this setter again. this.setAttribute('ribbon', JSON.stringify(value)); //attributeChangeCallback() implicitly called return; } this._ribbon = value; this._updateView(this.view.cornerRibbonContainer); } get showRibbon(){ return this.view.cornerRibbonContainer.hidden; } set showRibbon(value){ if(value){ this.view.cornerRibbonContainer.removeAttribute("hidden"); } else{ this.view.cornerRibbonContainer.setAttribute("hidden", true); } } _fadeOut(element){ let opacity = 1; function fadeOut(){ if(opacity > 0){ opacity -= 0.075; element.style.opacity = opacity; window.requestAnimationFrame(fadeOut); } } window.requestAnimationFrame(fadeOut); } _fadeIn(element){ let opacity = 0; function fadeIn(){ if(opacity < 1){ opacity += 0.075; element.style.opacity = opacity; window.requestAnimationFrame(fadeIn); } } window.requestAnimationFrame(fadeIn); } _onHover(e){ const isMouseOut = e.type === "mouseout"; const isMouseEnter = e.type === "mouseenter"; switch(e.target){ case this.view.HITStatusIcon: isMouseOut? this._fadeOut(this.view.HITStatusLabel) : this._fadeIn(this.view.HITStatusLabel); break; case this.view.editHITIcon: if(!this.state.editButtonEnabled) return isMouseOut? this._fadeOut(this.view.editHITLabel) : this._fadeIn(this.view.editHITLabel); break; case this.view.HITResultsIcon: if(!this.state.resultsButtonEnabled) return isMouseOut? this._fadeOut(this.view.HITResultsLabel) : this._fadeIn(this.view.HITResultsLabel); break; case this.view.HITPreviewIcon: if(!this.state.previewButtonEnabled) return isMouseOut? this._fadeOut(this.view.HITLinkLabel) : this._fadeIn(this.view.HITLinkLabel); break; case this.view.deleteHITIcon: if(!this.state.deleteButtonEnabled) return isMouseOut? this._fadeOut(this.view.deleteHITLabel) : this._fadeIn(this.view.deleteHITLabel); break; } } //When dealing with the info button, the event handler should be attached to the info button container, not the button itself (container is 44px) _onClick(e){ e.stopPropagation(); let event; const target = e.target; switch(target){ case this.view.infoButtonContainer: case this.view.infoButtonIcon: this.flip(); break; case this.view.HITStatus: case this.view.HITStatusIcon: this.view.HITStatusIcon.classList.add("fa-spin","fa-spinner-third"); this.view.HITStatusIcon.classList.remove(this.icons.stopIcon); this.view.HITStatusIcon.classList.remove(this.icons.resumeIcon); this.view.HITStatusIcon.classList.remove(this.icons.resultsIcon); this.view.HITStatusIcon.classList.remove(this.icons.publishIcon); this.view.HITStatusIcon.classList.remove(this.icons.deleteIcon); switch(this.model.HITStatus){ case "Unassignable": case "Assignable": this.dispatchEvent(new CustomEvent("pause", {detail: this.model})); break; case "Reviewable": const now = moment.utc(); const expiration = moment(this.model.expiration, "YYYY-MM-DDTHH:mm:ss"); const isExpired = expiration.isBefore(now); const isComplete = this.model.numberOfAssignmentsCompleted === this.model.maxAssignments; if(isComplete){ this.dispatchEvent(new CustomEvent("results", {detail: this.model})); } else { this.dispatchEvent(new CustomEvent("resume", {detail: this.model})); } break; default: //draft this.view.HITStatus.style.visibility = "hidden"; this.view.HITEmail.style.backgroundColor = this.colors.blue; this.view.HITMturk.style.backgroundColor = this.colors.blue; this.view.HITEmail.style.visibility = "initial"; this.view.HITMturk.style.visibility = "initial"; this.dispatchEvent(new CustomEvent("publish", {detail: this.model})); break; } break; case this.view.HITEmail: case this.view.HITEmailIcon: if(!this.state.emailButtonEnabled) return this.view.HITEmailIcon.classList.remove("fab"); this.view.HITEmailIcon.classList.remove("fa-amazon"); this.view.HITEmailIcon.classList.add("far", "fa-spin","fa-spinner-third"); this.dispatchEvent(new CustomEvent("publish-email", {detail: this.model})); this.resetIcon(this.view.HITEmailIcon, this.maxButtonWaitFeedback); break; case this.view.HITMturk: case this.view.HITMturkIcon: if(!this.state.mturkButtonEnabled) return this.view.HITMturkIcon.classList.remove("fab"); this.view.HITMturkIcon.classList.remove("fa-amazon"); this.view.HITMturkIcon.classList.add("far", "fa-spin","fa-spinner-third"); this.dispatchEvent(new CustomEvent("publish-mturk", {detail: this.model})); this.resetIcon(this.view.HITMturkIcon, this.maxButtonWaitFeedback); break; case this.view.editHITButton: case this.view.editHITIcon: if(!this.state.editButtonEnabled) return this.view.editHITIcon.classList.add("fa-spin","fa-spinner-third"); this.dispatchEvent(new CustomEvent("edit", {detail: this.model})); this.resetIcon(this.view.editHITIcon, this.maxButtonWaitFeedback); break; case this.view.HITResultsButton: case this.view.HITResultsIcon: if(!this.state.resultsButtonEnabled) return this.view.HITResultsIcon.classList.add("fa-spin","fa-spinner-third"); event = "results"; this.dispatchEvent(new CustomEvent("results", {detail: this.model})); this.resetIcon(this.view.HITResultsIcon, this.maxButtonWaitFeedback); break; case this.view.HITPreviewButton: case this.view.HITPreviewIcon: if(!this.state.previewButtonEnabled) return this.view.HITPreviewIcon.classList.add("fa-spin","fa-spinner-third"); this.dispatchEvent(new CustomEvent("preview", {detail: this.model})); this.resetIcon(this.view.HITPreviewIcon, this.maxButtonWaitFeedback); break; case this.view.deleteHITButton: case this.view.deleteHITIcon: if(!this.state.deleteButtonEnabled) return this.view.deleteHITIcon.classList.remove(this.icons.deleteIcon); this.view.deleteHITIcon.classList.add("fa-spin","fa-spinner-third"); this.dispatchEvent(new CustomEvent("delete", {detail: this.model})); this.resetIcon(this.view.deleteHITIcon, this.maxButtonWaitFeedback); break; } } resetIcon(icon){ const timeout = setTimeout(() => { //Special cases switch(icon){ case this.view.HITStatusIcon: break; case this.view.deleteHITIcon: icon.classList.remove("far","fa-spin","fa-spinner-third"); this.view.deleteHITIcon.classList.add(this.icons.deleteIcon); break; case this.view.HITMturkIcon: icon.classList.remove("far","fa-spin","fa-spinner-third"); break; default: icon.classList.remove("fa-spin","fa-spinner-third"); break; } //this.view.HITEmail.style.visibility = "hidden"; //this.view.HITMturk.style.visibility = "hidden"; //this.view.HITStatus.style.visibility = "initial"; this._updateView(); clearTimeout(timeout); }, this.state.maxButtonWaitFeedback); } _enableButton(button, color, shouldEnable=true){ const disabledColor = "gray"; button.style.backgroundColor = shouldEnable? color : disabledColor; button.style.cursor = shouldEnable? "pointer" : "default"; switch(button){ case this.view.HITResultsButton: this.state.resultsButtonEnabled = shouldEnable; break; case this.view.editHITButton: this.state.editButtonEnabled = shouldEnable; break; case this.view.deleteHITButton: this.state.deleteButtonEnabled = shouldEnable; break; case this.view.HITPreviewButton: this.state.previewButtonEnabled = shouldEnable; break; case this.view.HITEmail: this.state.emailButtonEnabled = shouldEnable; break; case this.view.HITMturk: this.state.mturkButtonEnabled = shouldEnable; break; } } _updateHITStatusView(){ this.view.HITStatus.hidden = false; this.view.HITStatusIcon.classList.remove("fa-spin", "fa-spinner-third"); this.view.HITStatusIcon.classList.remove(this.icons.publishIcon); this.view.HITStatusIcon.classList.remove(this.icons.resumeIcon); this.view.HITStatusIcon.classList.remove(this.icons.resultsIcon); this.view.HITStatusIcon.classList.remove(this.icons.stopIcon); const isComplete = this.model.numberOfAssignmentsCompleted === this.model.maxAssignments; const isIncomplete = !isComplete; switch(this.model.HITStatus){ //ONGOING case "Assignable": //HIT can be accepted by a Worker case "Unassignable": //HIT has been accepted by a Worker and is being worked on and therefore cannot be accepted by another Worker this.view.HITStatusLabel.innerHTML = "stop"; this.view.HITStatusIcon.classList.add(this.icons.stopIcon); this.view.HITStatusIcon.style.paddingLeft = "0"; this.view.HITStatus.style.backgroundColor = this.colors.blue; this.view.progressBar.style.backgroundColor = this.colors.blue; this._enableButton(this.view.HITResultsButton, this.colors.blue, this.hasResults); this._enableButton(this.view.editHITButton, this.colors.blue, this.hasNoResults); this._enableButton(this.view.deleteHITButton, this.colors.blue); this._enableButton(this.view.HITPreviewButton, this.colors.blue); this.view.HITResultsButton.style.display = "initial"; this.view.HITPreviewButton.style.display = "initial"; if(this.ribbon){ this.cornerRibbon(this.ribbon.text, this.ribbon.color || this.colors.blue) } if(this.state.front){ this.view.infoButtonIcon.style.color = this.colors.blue; } break; case "Reviewable": // a Worker has submitted answers to a HIT and the HIT is available for review const now = moment(); const expiration = moment(this.model.expiration, "YYYY-MM-DDTHH:mm:ss"); const isExpired = expiration.isBefore(now); const isNotExpired = !isExpired; if(isComplete){ //COMPLETED this.view.HITStatusLabel.innerHTML = "results"; this.view.HITStatusIcon.classList.add(this.icons.resultsIcon); this.view.HITStatusIcon.style.paddingLeft = "0"; this.view.HITStatus.style.backgroundColor = this.colors.blue; this.view.progressBar.style.backgroundColor = this.colors.blue; this._enableButton(this.view.editHITButton, null, false) this._enableButton(this.view.HITResultsButton, this.colors.blue) this._enableButton(this.view.deleteHITButton, this.colors.blue); this._enableButton(this.view.HITPreviewButton, this.colors.blue); if(this.ribbon){ this.cornerRibbon(this.ribbon.text, this.ribbon.color || this.colors.blue) } if(this.state.front){ this.view.infoButtonIcon.style.color = this.colors.blue; } } else if(isIncomplete && isExpired){ this.view.HITStatus.hidden = true; this.view.HITStatusLabel.innerHTML = "resume"; this.view.HITStatusIcon.classList.add(this.icons.resumeIcon); this.view.HITStatusIcon.style.paddingLeft = "0.25em"; this.view.HITStatus.style.backgroundColor = this.colors.blue; this.view.progressBar.style.backgroundColor = this.colors.blue; this._enableButton(this.view.HITResultsButton, this.colors.blue); this._enableButton(this.view.deleteHITButton, this.colors.blue); this._enableButton(this.view.HITPreviewButton, this.colors.blue); this._enableButton(this.view.editHITButton, this.colors.blue, false); this.view.HITResultsButton.style.display = "initial"; this.view.HITPreviewButton.style.display = "initial"; if(this.ribbon){ this.cornerRibbon(this.ribbon.text, this.ribbon.color || this.colors.blue) } if(this.state.front){ this.view.infoButtonIcon.style.color = this.colors.blue; } } else if(isIncomplete){ this.view.HITStatus.hidden = true; this.view.HITStatusLabel.innerHTML = "resume"; this.view.HITStatusIcon.style.paddingLeft = "0.25em"; this.view.HITStatusIcon.classList.add(this.icons.resumeIcon); this.view.HITStatus.style.backgroundColor = this.colors.blue; this.view.progressBar.style.backgroundColor = this.colors.blue; this._enableButton(this.view.HITResultsButton, this.colors.blue) this._enableButton(this.view.deleteHITButton, this.colors.blue); this._enableButton(this.view.HITPreviewButton, this.colors.blue); this._enableButton(this.view.editHITButton, this.colors.blue, false); this.view.HITResultsButton.style.display = "initial"; this.view.HITPreviewButton.style.display = "initial"; if(this.ribbon){ this.cornerRibbon(this.ribbon.text, this.ribbon.color || this.colors.blue) } if(this.state.front){ this.view.infoButtonIcon.style.color = this.colors.blue; } } break; default: //DRAFT this._updateMaxAssignmentsView("&infin;"); if(this.model.numberOfAssignmentsCompleted > 0){ this.model.maxAssignments = this.model.numberOfAssignmentsCompleted * 4; } this._enableButton(this.view.editHITButton, this.colors.blue, true); this._enableButton(this.view.HITResultsButton, this.colors.blue, this.hasResults) this._enableButton(this.view.deleteHITButton, this.colors.blue, true); this._enableButton(this.view.HITPreviewButton, this.colors.blue, true); this._enableButton(this.view.HITMturk, this.colors.blue, true); this._enableButton(this.view.HITEmail, this.colors.blue, true); this._updateProgressBarView(); this.view.HITStatusLabel.innerHTML = "publish"; this.view.HITEmailIcon.classList.add(this.icons.emailIcon); this.view.HITMturkIcon.classList.add("fab", this.icons.amazonIcon); this.view.HITStatusIcon.classList.add(this.icons.publishIcon); this.view.HITStatusIcon.style.paddingLeft = "0"; this.view.HITStatus.style.backgroundColor = this.colors.blue; this.view.progressBar.style.backgroundColor = this.colors.blue; this.view.HITPreviewButton.style.display = "initial"; if(this.ribbon){ this.cornerRibbon(this.ribbon.text, this.ribbon.color || this.colors.blue) } if(this.state.front){ this.view.infoButtonIcon.style.color = this.colors.blue; } break; } } cornerRibbon(message, color){ if(message){ this.view.cornerRibbonText.innerHTML = message; } if(color){ this.view.cornerRibbonBack.style.backgroundColor = color; this.view.cornerRibbon.style.backgroundColor = color; this.view.cornerRibbon.style.borderColor = color; } } flip(){ if(this.state.flipping || this.flippable === false){ return } else{ this.state.flipping = true } const _this = this; this.view.container.removeEventListener('click', this.event.click); this.view.container.style.zIndex = "100"; function startFlip(){ _this.state.scale += 0.010; _this.state.rotateY += 10; _this.view.container.style.transform = `rotateY(${_this.state.rotateY}deg) scale(${_this.state.scale})`; if(_this.state.rotateY % 90 === 0){ transitionState(); } else { window.requestAnimationFrame(startFlip); } } //TRANSITION STATE HERE function transitionState(){ //BACK CARD SHOWING if(_this.state.front){ _this.state.front = false; _this.state.back = true; _this.view.front.hidden = true; _this.view.back.hidden = false; //It's right, and not left, because back is flipped 180 degrees (so turns right into left) _this.view.infoButtonIcon.setAttribute("class", "fal fa-angle-right") _this.view.infoButtonIcon.style.fontSize = "1.5em"; _this.view.infoButtonIcon.style.color = "gray"; _this.view.cornerRibbonText.style.transform = `scaleX(-1)`; } else { //FRONT CARD SHOWING _this.view.cornerRibbonText.style.display = "block"; _this.state.front = true; _this.state.back = false; _this.view.front.hidden = false; _this.view.back.hidden = true; _this.view.infoButtonIcon.setAttribute("class", "fal fa-info-circle") _this.view.infoButtonIcon.style.fontSize = "1em"; _this.view.cornerRibbonText.style.transform = `scaleX(1)`; // Updates info icon to whatever color it should be _this._updateView(); } window.requestAnimationFrame(endFlip); } function endFlip(){ _this.state.scale -= 0.010; _this.state.rotateY += 10; _this.view.container.style.transform = `rotateY(${_this.state.rotateY}deg) scale(${_this.state.scale}) `; if(_this.state.rotateY % 180 !== 0){ window.requestAnimationFrame(endFlip); } else { _this.view.container.addEventListener('click', _this.event.click); _this.view.container.style.zIndex = "0"; _this.state.flipping = false; } } window.requestAnimationFrame(startFlip); } _updateView(view) { //No point in rendering if there isn't a model source, or a view on screen if(!this.model || !this.state.connected){ return; } switch(view){ case this.view.title: this._updateTitleView(); break; case this.view.cornerRibbonContainer: this._updateRibbonView(); break; case this.view.description: this._updateDescriptionView(); break; case this.view.numberOfAssignmentsCompleted: this._updateNumberOfAssignmentsCompletedView(); this._updateProgressBarView(); break; case this.view.maxAssignments: this._updateMaxAssignmentsView(); this._updateProgressBarView(); break; case this.view.creationTime: this._updateCreationTimeView(); break; case this.view.expiration: this._updateExpirationView(); break; case this.view.assignmentDurationInSeconds: this._updateAssignmentDurationInSecondsView(); break; case this.view.formattedPrice: this._updateFormattedPriceView(); break; case this.view.formattedPrice: this._updateFormattedPriceView(); break; case this.view.HITStatus: this._updateHITStatusView(); break; default: this._updateTitleView(); this._updateDescriptionView(); this._updateNumberOfAssignmentsCompletedView(); this._updateMaxAssignmentsView(); this._updateCreationTimeView(); this._updateExpirationView(); this._updateAssignmentDurationInSecondsView(); this._updateFormattedPriceView(); this._updateHITStatusView(); this._updateRibbonView(); //Secondary this._updateProgressBarView(); } } _updateRibbonView(){ if(this.ribbon){ this.showRibbon = true; this.cornerRibbon(this.ribbon.text, this.ribbon.color); } else { this.showRibbon = false; } } _updateTitleView(){ this.view.title.innerHTML = this.model.title; } _updateDescriptionView(){ this.view.description.innerHTML = this.model.description; } _updateNumberOfAssignmentsCompletedView(){ this.view.numberOfAssignmentsCompleted.innerHTML = this.model.numberOfAssignmentsCompleted; if(this.model.numberOfAssignmentsCompleted > this.model.maxAssignments){ this.view.numberOfAssignmentsCompleted.classList.add("text-danger"); } } _updateMaxAssignmentsView(override){ this.view.maxAssignments.innerHTML = override || this.model.maxAssignments; } _updateCreationTimeView(){ const isValidDate = moment && moment(this.model.creationTime).isValid(); this.view.creationTime.innerHTML = isValidDate? moment.utc(this.model.creationTime, this.DATE_FORMAT).fromNow() : "N/A"; } _updateExpirationView(){ const isValidDate = moment && moment(this.model.expiration, this.DATE_FORMAT).isValid(); this.view.expiration.innerHTML = isValidDate? moment.utc(this.model.expiration, this.DATE_FORMAT).fromNow() : "N/A"; this.view.expirationLabel.innerHTML = moment.utc(this.model.expiration, this.DATE_FORMAT).isBefore()? "Expired" : "Expires"; } _updateAssignmentDurationInSecondsView(){ const SECONDS = this.model.assignmentDurationInSeconds; const MINUTE = 60; const HOUR = 3600; const DAY = 86400; const WEEK = 604800; const MONTH = 2592000;//AVERAGE const YEAR = 31536000; let isMinutes = ((SECONDS/MINUTE) >= 2); let isHours = ((SECONDS/HOUR) >= 2); let isDays = ((SECONDS/DAY) >= 2); let isWeeks = ((SECONDS/WEEK) >= 2); let isMonths = ((SECONDS/MONTH) >= 2); let isYears = ((SECONDS/YEAR) >= 2); let amount = isMinutes? (SECONDS/MINUTE).toFixed(0) : SECONDS; amount = isHours? (SECONDS/HOUR).toFixed(0) : amount; amount = isDays? (SECONDS/DAY).toFixed(0) : amount; amount = isWeeks? (SECONDS/WEEK).toFixed(0) : amount; amount = isMonths? (SECONDS/MONTH).toFixed(0) : amount; amount = isYears? (SECONDS/YEAR).toFixed(0) : amount; let qualifier = isMinutes? "minutes" : "seconds"; qualifier = isHours? "hours" : qualifier; qualifier = isDays? "days" : qualifier; qualifier = isMonths? "months" : qualifier; qualifier = isYears? "years" : qualifier; let humanReadable = `${amount} ${qualifier}`; this.view.assignmentDurationInSeconds.innerHTML = humanReadable; } _updateFormattedPriceView(){ if(!this.model.reward) return; this.view.formattedPrice.innerHTML = this.model.reward.formattedPrice; } _updateProgressBarView(override){ //Width = progress bar % let fractionCompleted = (this.model.numberOfAssignmentsCompleted / this.model.maxAssignments); fractionCompleted = fractionCompleted > 1? 1 : fractionCompleted; override = override < 100? 100 : override; let completed = fractionCompleted * 100; this.view.progressBar.style.width = `${override || completed}%`; } disconnectedCallback() { this._removeEvents() this.state.connected = false; } _removeEvents(){ //this.view.container.removeEventListener('click', this.event.click); } } window.customElements.define('ui-mturk-hit', MTurkHITViewController);