New HIT status page
This commit is contained in:
parent
d3bf3d47ea
commit
b2626244a4
6 changed files with 155 additions and 104 deletions
|
@ -56,10 +56,11 @@ class HIT(Base):
|
||||||
return os.path.join('/frames', f"{self.id:06d}.jpg")
|
return os.path.join('/frames', f"{self.id:06d}.jpg")
|
||||||
|
|
||||||
def getSvgImageUrl(self):
|
def getSvgImageUrl(self):
|
||||||
return f"scans/{self.id:06d}.svg"
|
return f"/scans/{self.id:06d}.svg"
|
||||||
|
|
||||||
def getSvgImagePath(self):
|
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):
|
def getLastAssignment(self):
|
||||||
if not len(self.assignments):
|
if not len(self.assignments):
|
||||||
|
|
|
@ -238,6 +238,8 @@ class CentralManagement():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.currentHit.scanned_at = datetime.datetime.utcnow()
|
self.currentHit.scanned_at = datetime.datetime.utcnow()
|
||||||
|
self.store.saveHIT(self.currentHit)
|
||||||
|
|
||||||
time_diff = datetime.datetime.now() - self.lastHitTime
|
time_diff = datetime.datetime.now() - self.lastHitTime
|
||||||
to_wait = 10 - time_diff.total_seconds()
|
to_wait = 10 - time_diff.total_seconds()
|
||||||
# self.statusPageQueue.add(dict(hit_id=self.currentHit.id, state='scan'))
|
# self.statusPageQueue.add(dict(hit_id=self.currentHit.id, state='scan'))
|
||||||
|
|
|
@ -165,6 +165,7 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
self.__class__.rmConnection(self)
|
self.__class__.rmConnection(self)
|
||||||
logger.info(f"Client disconnected: {self.request.remote_ip}")
|
logger.info(f"Client disconnected: {self.request.remote_ip}")
|
||||||
|
# TODO: abandon assignment??
|
||||||
|
|
||||||
def submit_strokes(self):
|
def submit_strokes(self):
|
||||||
if len(self.strokes) < 1:
|
if len(self.strokes) < 1:
|
||||||
|
|
|
@ -5,10 +5,69 @@
|
||||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
<link rel="stylesheet" type="text/css" href="style.css" />
|
||||||
<script src='dateformat.js'></script>
|
<script src='dateformat.js'></script>
|
||||||
<script src='reconnecting-websocket.min.js'></script>
|
<script src='reconnecting-websocket.min.js'></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="wrapper">
|
<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">
|
<!-- <div class="phase" id="waiting_for_human">
|
||||||
<span class="narrative_phase_text">waiting for human worker to accept task</span>
|
<span class="narrative_phase_text">waiting for human worker to accept task</span>
|
||||||
|
@ -17,10 +76,10 @@
|
||||||
<div class="phase" id="human_accepted_task">
|
<div class="phase" id="human_accepted_task">
|
||||||
<span class="narrative_phase_text">task accepted by human worker</span>
|
<span class="narrative_phase_text">task accepted by human worker</span>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
<!--
|
||||||
<div class="phase" id="worker_specs">
|
<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_name" id="hit_id_descriptor">human intelligent task id</span>
|
||||||
<span class="grid-item spec_value" id="hit_id"> </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_name" id="worker_id_descriptor">human worker id</span>
|
||||||
<span class="grid-item spec_value" id="worker_id"> </span>
|
<span class="grid-item spec_value" id="worker_id"> </span>
|
||||||
|
@ -43,14 +102,13 @@
|
||||||
|
|
||||||
<span class="grid-item spec_name" id="elapsed_time_descriptor">time elapsed</span>
|
<span class="grid-item spec_name" id="elapsed_time_descriptor">time elapsed</span>
|
||||||
<span class="grid-item spec_value" id="elapsed_time"> </span>
|
<span class="grid-item spec_value" id="elapsed_time"> </span>
|
||||||
|
</div>-->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
|
|
|
@ -1,38 +1,22 @@
|
||||||
|
|
||||||
// DOM STUFF ///////////////////////////////////////////////////////////////////
|
// DOM STUFF ///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
let divs = {},
|
var app = new Vue({
|
||||||
spec_names = [
|
el: '#wrapper',
|
||||||
'worker_id',
|
data: {
|
||||||
'ip',
|
message: 'Hello Vue!',
|
||||||
'location',
|
hits: {
|
||||||
'browser',
|
|
||||||
'os',
|
|
||||||
'state',
|
|
||||||
'fee',
|
|
||||||
'hit_created',
|
|
||||||
'hit_opened',
|
|
||||||
'hit_submitted',
|
|
||||||
'elapsed_time',
|
|
||||||
'hit_id'
|
|
||||||
]
|
|
||||||
|
|
||||||
divs.linkDOM = function(name){
|
|
||||||
divs[name] = document.getElementById(`${name}`)
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
spec_names.forEach(function(name){
|
// watch: {
|
||||||
divs.linkDOM(name)
|
// hits: {
|
||||||
|
// deep: true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let request_time = timeStamp(),
|
|
||||||
hit_started = false,
|
|
||||||
elapsed_time,
|
|
||||||
hit_finished = false
|
|
||||||
|
|
||||||
|
|
||||||
// SOCKET STUFF ////////////////////////////////////////////////////////////////
|
// SOCKET STUFF ////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,76 +27,29 @@ ws.addEventListener('open', () => {
|
||||||
// ws.send('hi server')
|
// ws.send('hi server')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
ws.addEventListener('message', (event) => {
|
ws.addEventListener('message', (event) => {
|
||||||
console.log('message: ' + event.data)
|
console.log('message: ' + event.data)
|
||||||
|
|
||||||
let data = JSON.parse(event.data)
|
let hits = JSON.parse(event.data)
|
||||||
if(data.property === 'hit_opened') {
|
let a = {};
|
||||||
if(data.value != null){
|
for(let hitid in app.hits) {
|
||||||
hit_started = true
|
a[hitid] = app.hits[hitid];
|
||||||
hit_finished = false
|
|
||||||
request_time = new Date()
|
|
||||||
divs[data.property].innerHTML = `${request_time.format('dd mmm HH:MM:ss')}`
|
|
||||||
}else{
|
|
||||||
divs[data.property].innerHTML = `—`
|
|
||||||
hit_started = false
|
|
||||||
}
|
}
|
||||||
|
for(let hit of hits){
|
||||||
|
a[hit.id] = hit;
|
||||||
}
|
}
|
||||||
else if(data.property === 'hit_submitted'){
|
app.hits = a;
|
||||||
hit_finished = true;
|
|
||||||
}
|
|
||||||
else if(divs[data.property]){
|
|
||||||
data.value === null ? divs[data.property].innerHTML = `—` : divs[data.property].innerHTML = `${data.value}`
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ANIMATION STUFF /////////////////////////////////////////////////////////////
|
// ANIMATION STUFF /////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
let frames,
|
//function update(step){
|
||||||
frames_per_sec = 10,
|
//
|
||||||
current_frame = 0
|
// 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{
|
||||||
function makeAnimation(){
|
// divs['elapsed_time'].innerHTML = `—`
|
||||||
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 = `—`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
makeAnimation()
|
|
||||||
|
|
||||||
|
|
||||||
function timeStamp(){return window.performance && window.performance.now ? window.performance.now() : new Date().getTime()}
|
|
||||||
|
|
|
@ -47,9 +47,9 @@ html, body{
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: var(--pos-x);
|
left: var(--pos-x);
|
||||||
top: var(--pos-y);
|
bottom: calc(100vh - (var(--pos-y) + var(--height)));
|
||||||
width: var(--width);
|
width: var(--width);
|
||||||
height: var(--height);
|
height: auto;
|
||||||
|
|
||||||
background: var(--alt-color);
|
background: var(--alt-color);
|
||||||
box-sizing: border-box;
|
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{
|
#worker_specs{
|
||||||
display:grid;
|
display:grid;
|
||||||
grid-template-columns: 1fr ;
|
grid-template-columns: 1fr ;
|
||||||
|
@ -97,3 +148,4 @@ html, body{
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
}
|
}
|
||||||
|
*/
|
Loading…
Reference in a new issue