New HIT status page

This commit is contained in:
Ruben van de Ven 2020-01-13 16:13:42 +01:00
parent d3bf3d47ea
commit b2626244a4
6 changed files with 155 additions and 104 deletions

View file

@ -56,10 +56,11 @@ class HIT(Base):
return os.path.join('/frames', f"{self.id:06d}.jpg")
def getSvgImageUrl(self):
return f"scans/{self.id:06d}.svg"
return f"/scans/{self.id:06d}.svg"
def getSvgImagePath(self):
return os.path.join('www', self.getSvgImageUrl())
# os.path.join on svgImageUrl leads to invalid absolute url
return os.path.join(f'www/scans/{self.id:06d}.svg')
def getLastAssignment(self):
if not len(self.assignments):

View file

@ -238,6 +238,8 @@ class CentralManagement():
continue
self.currentHit.scanned_at = datetime.datetime.utcnow()
self.store.saveHIT(self.currentHit)
time_diff = datetime.datetime.now() - self.lastHitTime
to_wait = 10 - time_diff.total_seconds()
# self.statusPageQueue.add(dict(hit_id=self.currentHit.id, state='scan'))

View file

@ -165,6 +165,7 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
def on_close(self):
self.__class__.rmConnection(self)
logger.info(f"Client disconnected: {self.request.remote_ip}")
# TODO: abandon assignment??
def submit_strokes(self):
if len(self.strokes) < 1:

View file

@ -5,10 +5,69 @@
<link rel="stylesheet" type="text/css" href="style.css" />
<script src='dateformat.js'></script>
<script src='reconnecting-websocket.min.js'></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="wrapper">
<div class='hit' v-for="(hit, hitId) in hits"
:class="[{'hugvey--on': hit.status != 'off'},'hugvey--' + hit.status]"
>
<div class='transition'>
<p v-if="hit.hit_id">Created HIT at {{hit.created_at}}</p>
<p v-else>Creating HIT</p>
</div>
<div class='state' v-if="hit.hit_id">
<h2>Human intelligence task</h2>
<p>{{hit.hit_id}}</p>
<p>Description</p>
</div>
<div class='transition' v-if="hit.hit_id">
<p v-if="!hit.assignment">Wait for human</p>
<template v-else>
<p v-if="hit.assignment.returned_at || hit.assignment.abandoned_at">Asignment abandoned</p>
<p v-else>Assignment accepted</p>
</template>
</div>
<template v-if="hit.assignment">
<div class='state' v-if="hit.assignment"
:class="[{'assignment-hidden': hit.assignment.returned_at || hit.assignment.abandoned_at}]">
<h2>Worker {{hit.assignment.worker_id}}</h2>
<p>{{hit.assignment.turk_browser}} / {{hit.assignment.turk_ip}} / {{hit.assignment.turk_country}} / {{hit.assignment.turk_os}}</p>
</div>
<div class='transition' v-if="hit.assignment.submit_page_at">
<p v-if="!hit.assignment.confirmed_at">Assignment submitted</p>
<p v-else>Confirmed submission</p> <!-- Validated submitted code through SQSmessage -->
</div>
<div class='state' v-if="hit.assignment.submit_page_at">
result: (add duration)
<img :src="hit.svg_image">
</div>
<div class='transition' v-if="hit.assignment.submit_page_at">
<p v-if="!hit.scanned_at">Scanning</p>
<p v-else>Scanned at {{hit.scanned_at}}</p>
</div>
<div class='state' v-if="hit.scanned_at">
scan:
<img :src="hit.scan_image">
</div>
</template>
<!-- <div class="phase" id="waiting_for_human">
<span class="narrative_phase_text">waiting for human worker to accept task</span>
@ -17,10 +76,10 @@
<div class="phase" id="human_accepted_task">
<span class="narrative_phase_text">task accepted by human worker</span>
</div> -->
<!--
<div class="phase" id="worker_specs">
<span class="grid-item spec_name" id="hit_id_descriptor">human intelligent task id</span>
<span class="grid-item spec_value" id="hit_id">&nbsp;</span>
<span class="grid-item spec_value" id="hit_id">{{hit.id}}</span>
<span class="grid-item spec_name" id="worker_id_descriptor">human worker id</span>
<span class="grid-item spec_value" id="worker_id">&nbsp;</span>
@ -43,10 +102,9 @@
<span class="grid-item spec_name" id="elapsed_time_descriptor">time elapsed</span>
<span class="grid-item spec_value" id="elapsed_time">&nbsp;</span>
</div>
</div>-->
</div>

View file

@ -1,38 +1,22 @@
// DOM STUFF ///////////////////////////////////////////////////////////////////
let divs = {},
spec_names = [
'worker_id',
'ip',
'location',
'browser',
'os',
'state',
'fee',
'hit_created',
'hit_opened',
'hit_submitted',
'elapsed_time',
'hit_id'
]
var app = new Vue({
el: '#wrapper',
data: {
message: 'Hello Vue!',
hits: {
divs.linkDOM = function(name){
divs[name] = document.getElementById(`${name}`)
}
spec_names.forEach(function(name){
divs.linkDOM(name)
}
},
// watch: {
// hits: {
// deep: true
// }
// }
})
let request_time = timeStamp(),
hit_started = false,
elapsed_time,
hit_finished = false
// SOCKET STUFF ////////////////////////////////////////////////////////////////
@ -43,76 +27,29 @@ ws.addEventListener('open', () => {
// ws.send('hi server')
})
ws.addEventListener('message', (event) => {
console.log('message: ' + event.data)
let data = JSON.parse(event.data)
if(data.property === 'hit_opened') {
if(data.value != null){
hit_started = true
hit_finished = false
request_time = new Date()
divs[data.property].innerHTML = `${request_time.format('dd mmm HH:MM:ss')}`
}else{
divs[data.property].innerHTML = `&mdash;`
hit_started = false
}
let hits = JSON.parse(event.data)
let a = {};
for(let hitid in app.hits) {
a[hitid] = app.hits[hitid];
}
else if(data.property === 'hit_submitted'){
hit_finished = true;
}
else if(divs[data.property]){
data.value === null ? divs[data.property].innerHTML = `&mdash;` : divs[data.property].innerHTML = `${data.value}`
for(let hit of hits){
a[hit.id] = hit;
}
app.hits = a;
})
// ANIMATION STUFF /////////////////////////////////////////////////////////////
let frames,
frames_per_sec = 10,
current_frame = 0
function makeAnimation(){
let now,
delta = 0,
last = timeStamp(),
step = 1/frames_per_sec
function frame() {
now = timeStamp()
delta += Math.min(1, (now - last) / 1000)
while(delta > step){
delta -= step
update(step)
}
last = now
requestAnimationFrame(frame)
}
requestAnimationFrame(frame)
}
function update(step){
if(!hit_finished) elapsed_time = `${new Date((Date.now() - request_time)).format('MM"m "ss"s"')}`
if(hit_started){
divs['elapsed_time'].innerHTML = elapsed_time
}else{
divs['elapsed_time'].innerHTML = `&mdash;`
}
}
makeAnimation()
function timeStamp(){return window.performance && window.performance.now ? window.performance.now() : new Date().getTime()}
//
//function update(step){
//
// if(!hit_finished) elapsed_time = `${new Date((Date.now() - request_time)).format('MM"m "ss"s"')}`
// if(hit_started){
// divs['elapsed_time'].innerHTML = elapsed_time
// }else{
// divs['elapsed_time'].innerHTML = `&mdash;`
// }
//}

View file

@ -47,9 +47,9 @@ html, body{
position: absolute;
left: var(--pos-x);
top: var(--pos-y);
bottom: calc(100vh - (var(--pos-y) + var(--height)));
width: var(--width);
height: var(--height);
height: auto;
background: var(--alt-color);
box-sizing: border-box;
@ -57,6 +57,57 @@ html, body{
}
#wrapper .hit{
display:block;
}
.transition{
overflow:hidden;
position:relative;
padding-left: calc(50%);
font-size: 12px;
height: 40px;
animation-duration: 3s;
animation-name: slidein;
}
.transition::before{
content:'⇩'; /*'⇓';*/
display:block;
position:absolute;
font-family:monospace;
font-size: 100px;
top:0;
left:calc(50% - 30px);
line-height:0;
}
.state{
overflow:hidden;
border:solid 1px black;
animation-duration: 3s;
animation-name: slidein;
transition: max-height 1s;
max-height: 200px;
}
.state.assignment-hidden {
/*On abandon etc.*/
max-height: 0px;
}
@keyframes slidein {
from {
max-height:0;
}
to {
max-height: 200px;
}
}
/*
#worker_specs{
display:grid;
grid-template-columns: 1fr ;
@ -97,3 +148,4 @@ html, body{
position: relative;
top: 5px;
}
*/