ui-mturk-hit
Version:
A custom element for Amazon Mechanical Turk HITs
812 lines (689 loc) • 32.5 kB
JavaScript
'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("∞");
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);