Save path into image & fetch latest image

This commit is contained in:
Ruben van de Ven 2019-09-11 21:00:06 +02:00
parent 6c908a572f
commit dd09207066
3 changed files with 156 additions and 26 deletions

103
server.py
View File

@ -1,11 +1,15 @@
import argparse
import json
import logging
import os
import tornado.ioloop
import tornado.web
import tornado.websocket
import logging
import coloredlogs
import argparse
import json
from urllib.parse import urlparse
import uuid
import coloredlogs
import glob
logger = logging.getLogger("drawing")
@ -23,11 +27,38 @@ argParser.add_argument(
action="store_true",
)
generated_image_dir = os.path.join('www','generated')
def strokes2D(strokes):
# strokes to a d attribute for a path
d = "";
last_stroke = None;
cmd = "";
for stroke in strokes:
if not last_stroke:
d += f"M{stroke[0]},{stroke[1]} "
cmd = 'M'
else:
if last_stroke[2] == 1:
d += " m"
cmd = 'm'
elif cmd != 'l':
d+=' l '
cmd = 'l'
rel_stroke = [stroke[0] - last_stroke[0], stroke[1] - last_stroke[1]];
d += f"{rel_stroke[0]},{rel_stroke[1]} "
last_stroke = stroke;
return d;
class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path):
"""For subclass to add extra headers to the response"""
if path[-5:] == '.html':
self.set_header("Access-Control-Allow-Origin", "*")
if path[-4:] == '.svg':
self.set_header("Content-Type", "image/svg+xml")
class WebSocketHandler(tornado.websocket.WebSocketHandler):
CORS_ORIGINS = ['localhost', '.mturk.com']
@ -43,20 +74,36 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self, p = None):
self.__class__.connections.add(self)
logger.info("New client connected")
self.write_message("hello!")
self.strokes = []
# self.write_message("hello!")
# the client sent the message
def on_message(self, message):
logger.debug(f"recieve: {message}")
try:
msg = json.loads(message)
# TODO: sanitize input: min/max, limit strokes
if msg['action'] == 'move':
pass
# TODO: min/max input
point = [float(msg['direction'][0]),float(msg['direction'][1]), 0]
self.strokes.append(point)
elif msg['action'] == 'up':
logger.info(f'up: {msg}')
point = [msg['direction'][0],msg['direction'][1], 1]
self.strokes.append(point)
elif msg['action'] == 'submit':
logger.info(f'up: {msg}')
self.write_message(json.dumps('submitted'))
id = self.submit_strokes()
if not id:
self.write_message(json.dumps('error'))
return
self.write_message(json.dumps({
'action': 'submitted',
'msg': f"Submission ok, please refer to your submission as: {id}"
}))
elif msg['action'] == 'down':
# not used, implicit in move?
pass
@ -72,17 +119,54 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
def on_close(self):
self.__class__.rmConnection(self)
logger.info("Client disconnected")
def submit_strokes(self):
if len(self.strokes) < 1:
return False
d = strokes2D(self.strokes)
svg = f"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 600 600"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
>
<path d="{d}" style="stroke:black;stroke-width:2;fill:none;" />
</svg>
"""
id = uuid.uuid4().hex
filename = os.path.join(generated_image_dir , id+'.svg')
with open(filename, 'w') as fp:
logger.info(f"Wrote {filename}")
fp.write(svg)
return id
@classmethod
def rmConnection(cls, client):
if client not in cls.connections:
return
cls.connections.remove(client)
class LatestImageHandler(tornado.web.RequestHandler):
def get(self):
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
self.set_header("Content-Type", "image/svg+xml")
list_of_files = glob.glob(os.path.join(generated_image_dir,'*.svg'))
latest_file = max(list_of_files, key=os.path.getctime)
with open(latest_file, 'r') as fp:
self.write(fp.read())
if __name__ == "__main__":
args = argParser.parse_args()
print(logger.level)
coloredlogs.install(
level=logging.DEBUG if args.verbose else logging.INFO,
)
@ -98,6 +182,7 @@ if __name__ == "__main__":
application = tornado.web.Application([
(r"/ws(.*)", WebSocketHandler),
(r"/latest.svg", LatestImageHandler), # TODO: have js request the right image, based on a 'start' button. This way we can trace the history of a drawing
(r"/(.*)", StaticFileWithHeaderHandler,
{"path": 'www', "default_filename": 'index.html'}),
], debug=True)

0
www/generated/.gitignore vendored Normal file
View File

View File

@ -4,10 +4,14 @@
<meta charset="utf-8">
<title>MT Request: draw over the image</title>
<style media="screen">
svg{
height:600px;
width:600px;
border:solid 1px;
#sample, svg{
position:absolute;
top: 0;
left: 0;
bottom: 0;
right:0;
width:100%;
height:100%;
}
path {
@ -15,21 +19,45 @@
stroke: red;
stroke-width: 2px;
}
body.submitted path{
stroke:darkgray;
}
body.submitted .buttons{
display:none;
}
#wrapper {
height: 600px;
width: 600px;
position: relative;
}
</style>
</head>
<body>
<div id='interface'>
<img src="" id='sample'>
<div id='wrapper'>
<img src="/latest.svg" id='sample'>
<svg id="canvas">
<path d="" id="stroke">
<path d="" id="stroke" />
</svg>
<button id='submit'>Submit</button>
</div>
<div class='buttons'>
<button id='submit'>Submit</button>
<button id='reset'>Reset</button>
</div>
<div id='message'></div>
</div>
<script type="text/javascript">
let url = window.location.origin.replace('http', 'ws') +'/ws';
let svgEl = document.getElementById("canvas");
let strokeEl = document.getElementById('stroke');
let submitEl = document.getElementById('submit');
let resetEl = document.getElementById('reset');
let messageEl = document.getElementById('message');
let strokes = [];
let isDrawing = false;
let draw = function(e) {
@ -54,6 +82,10 @@
isDrawing = false;
document.body.removeEventListener('mousemove', draw);
let pos = svgEl.getBoundingClientRect()
let x = e.x - pos['left'];
let y = e.y - pos['top'];
if(strokes.length > 0){
// mark point as last of stroke
@ -61,6 +93,7 @@
}
socket.send(JSON.stringify({
'action': 'up',
'direction': [x, y]
}));
};
@ -89,25 +122,37 @@
}
return d;
}
let startDrawing = function(e){
isDrawing = true;
// start drawing
document.body.addEventListener('mousemove', draw);
};
let reset = function() {
strokes = [];
strokeEl.setAttribute('d', "");
socket.send(JSON.stringify({
'action': 'reset',
}));
}
let socket = new WebSocket(url);
socket.addEventListener('message', function(e){
console.log('receive', e.data);
if(e.data == 'submitted') {
// TODO close the interface
let msg = JSON.parse(e.data);
console.log('receive', msg);
if(msg['action'] == 'submitted') {
document.body.classList.add('submitted');
messageEl.innerHTML = msg['msg'];
svgEl.removeEventListener('mousedown', startDrawing);
}
});
document.body.addEventListener('mouseup', penup);
svgEl.addEventListener('mousedown', function(e){
isDrawing = true;
// start drawing
document.body.addEventListener('mousemove', draw);
});
svgEl.addEventListener('mousedown', startDrawing);
resetEl.addEventListener('click', reset);
submitEl.addEventListener('click', function(e){
if(!strokes.length){
alert('please draw before submitting');