Compare commits
No commits in common. "47ab4241b8a262b4877dc8ba11be6d2f9f4f76bd" and "a83b05daaa2d7be830b19310853d5c42c20e66a6" have entirely different histories.
47ab4241b8
...
a83b05daaa
9 changed files with 46 additions and 352 deletions
2
files/audio/.gitignore
vendored
Normal file
2
files/audio/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
2
log/.gitignore
vendored
2
log/.gitignore
vendored
|
@ -1,2 +1,2 @@
|
||||||
*
|
*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=SVG animation interfaces
|
Description=SVG animation interfaces
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/home/svganim/.poetry/bin/poetry run python webserver.py
|
ExecStart=/home/svganim/.poetry/bin/poetry webserver.py
|
||||||
WorkingDirectory=/home/svganim/svganim
|
WorkingDirectory=/home/svganim/svganim
|
||||||
User=svganim
|
User=svganim
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
Environment="PATH=/home/svganim/.pyenv/plugins/pyenv-virtualenv/shims:/home/svganim/.pyenv/shims:/home/svganim/.pyenv/bin:/home/svganim/.poetry/bin:/usr/local/bin:/usr/bin:/bin"
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,7 @@ from pydub import AudioSegment
|
||||||
import svgwrite
|
import svgwrite
|
||||||
import tempfile
|
import tempfile
|
||||||
import io
|
import io
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger('svganim.strokes')
|
|
||||||
|
|
||||||
class Annotation:
|
class Annotation:
|
||||||
def __init__(self, tag: str, drawing: Drawing, t_in: float, t_out: float) -> None:
|
def __init__(self, tag: str, drawing: Drawing, t_in: float, t_out: float) -> None:
|
||||||
|
@ -48,7 +46,6 @@ class Drawing:
|
||||||
return f"/annotations/{self.id}"
|
return f"/annotations/{self.id}"
|
||||||
|
|
||||||
def get_canvas_metadata(self) -> list:
|
def get_canvas_metadata(self) -> list:
|
||||||
logger.info(f'metadata for {self.id}')
|
|
||||||
with open(self.eventfile, "r") as fp:
|
with open(self.eventfile, "r") as fp:
|
||||||
first_line = fp.readline().strip()
|
first_line = fp.readline().strip()
|
||||||
|
|
||||||
|
@ -82,10 +79,6 @@ class Drawing:
|
||||||
# metadata on first line
|
# metadata on first line
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if type(event) is list:
|
|
||||||
# ignore double metadatas, which appear when continuaing an existing drawing
|
|
||||||
continue
|
|
||||||
|
|
||||||
if event["event"] == "viewbox":
|
if event["event"] == "viewbox":
|
||||||
pass
|
pass
|
||||||
if event["event"] == "stroke":
|
if event["event"] == "stroke":
|
||||||
|
@ -282,12 +275,11 @@ class AnnotationIndex:
|
||||||
self.drawing_dir = drawing_dir
|
self.drawing_dir = drawing_dir
|
||||||
self.metadata_dir = metadata_dir
|
self.metadata_dir = metadata_dir
|
||||||
|
|
||||||
self.shelve = {} #disable disk cache because of glitches shelve.open(filename, writeback=True)
|
self.shelve = shelve.open(filename, writeback=True)
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
# reset the index
|
# reset the index
|
||||||
for key in list(self.shelve.keys()):
|
for key in self.shelve:
|
||||||
print(key)
|
|
||||||
del self.shelve[key]
|
del self.shelve[key]
|
||||||
|
|
||||||
self.shelve["_drawings"] = {
|
self.shelve["_drawings"] = {
|
||||||
|
@ -336,7 +328,7 @@ class AnnotationIndex:
|
||||||
return [
|
return [
|
||||||
name[:-16]
|
name[:-16]
|
||||||
for name in os.listdir(self.drawing_dir)
|
for name in os.listdir(self.drawing_dir)
|
||||||
if name.endswith("json_appendable") and os.stat(os.path.join(self.drawing_dir, name)).st_size > 0
|
if name.endswith("json_appendable")
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_drawing_filenames(self) -> list[Filename]:
|
def get_drawing_filenames(self) -> list[Filename]:
|
||||||
|
@ -354,8 +346,8 @@ class AnnotationIndex:
|
||||||
|
|
||||||
class Point:
|
class Point:
|
||||||
def __init__(self, x: float, y: float, last: bool, t: float):
|
def __init__(self, x: float, y: float, last: bool, t: float):
|
||||||
self.x = float(x)
|
self.x = x
|
||||||
self.y = float(y) # if y == 0 it can still be integer.... odd python
|
self.y = y
|
||||||
self.last = last
|
self.last = last
|
||||||
self.t = t
|
self.t = t
|
||||||
|
|
||||||
|
|
150
webserver.py
150
webserver.py
|
@ -46,7 +46,6 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.strokes = []
|
self.strokes = []
|
||||||
self.hasWritten = False
|
self.hasWritten = False
|
||||||
self.prev_file = None
|
|
||||||
self.dimensions = [None, None]
|
self.dimensions = [None, None]
|
||||||
|
|
||||||
# def check_origin(self, origin):
|
# def check_origin(self, origin):
|
||||||
|
@ -70,28 +69,11 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
return len(files) + 1
|
return len(files) + 1
|
||||||
|
|
||||||
def appendEvent(self, row):
|
def appendEvent(self, row):
|
||||||
if not self.hasWritten and self.prev_file and 'event' in row and row['event'] == 'viewbox':
|
|
||||||
# ignore canvas movement after
|
|
||||||
return
|
|
||||||
|
|
||||||
# write to an appendable json format. So basically a file that should be wrapped in [] to be json-parsable
|
# write to an appendable json format. So basically a file that should be wrapped in [] to be json-parsable
|
||||||
with open(
|
with open(
|
||||||
os.path.join(self.config.storage, self.filename +
|
os.path.join(self.config.storage, self.filename + ".json_appendable"), "a"
|
||||||
".json_appendable"), "a"
|
|
||||||
) as fp:
|
) as fp:
|
||||||
if not self.hasWritten:
|
if not self.hasWritten:
|
||||||
if self.prev_file:
|
|
||||||
# TODO WIP
|
|
||||||
with open(
|
|
||||||
self.prev_file, 'r'
|
|
||||||
) as fprev:
|
|
||||||
wrote = False
|
|
||||||
for line in fprev:
|
|
||||||
wrote = True
|
|
||||||
fp.write(line)
|
|
||||||
if wrote:
|
|
||||||
fp.write(",\n")
|
|
||||||
|
|
||||||
# metadata to first row, but only on demand
|
# metadata to first row, but only on demand
|
||||||
fp.write(
|
fp.write(
|
||||||
json.dumps(
|
json.dumps(
|
||||||
|
@ -109,33 +91,6 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
# first column is color, rest is points
|
# first column is color, rest is points
|
||||||
fp.write(json.dumps(row))
|
fp.write(json.dumps(row))
|
||||||
|
|
||||||
def preloadFile(self, file):
|
|
||||||
if self.hasWritten:
|
|
||||||
logger.error("Cannot preload when already written content")
|
|
||||||
return False
|
|
||||||
|
|
||||||
logger.info(f"load {file}")
|
|
||||||
# TODO, make sure file doesn't load file outside of storage
|
|
||||||
prev_file = os.path.join(
|
|
||||||
self.config.storage, file + ".json_appendable")
|
|
||||||
if not os.path.exists(prev_file):
|
|
||||||
logger.error(f"Cannot preload non-existent file: {prev_file}")
|
|
||||||
self.write_message(json.dumps(
|
|
||||||
{"error": f"Non-existent file: {file}"}))
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.prev_file = prev_file
|
|
||||||
|
|
||||||
with open(self.prev_file, "r") as fp:
|
|
||||||
first_line = fp.readline().strip()
|
|
||||||
if first_line.endswith(","):
|
|
||||||
first_line = first_line[:-1]
|
|
||||||
|
|
||||||
metadata = json.loads(first_line)
|
|
||||||
|
|
||||||
self.write_message(json.dumps(
|
|
||||||
{"preloaded_svg": f"/drawing/{file}", "dimensions": [metadata[1], metadata[2]]}))
|
|
||||||
|
|
||||||
# the client sent the message
|
# the client sent the message
|
||||||
def on_message(self, message):
|
def on_message(self, message):
|
||||||
logger.info(f"recieve: {message}")
|
logger.info(f"recieve: {message}")
|
||||||
|
@ -151,8 +106,6 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
elif msg["event"] == "viewbox":
|
elif msg["event"] == "viewbox":
|
||||||
logger.info("move or resize")
|
logger.info("move or resize")
|
||||||
self.appendEvent(msg)
|
self.appendEvent(msg)
|
||||||
elif msg["event"] == "preload":
|
|
||||||
self.preloadFile(msg["file"])
|
|
||||||
else:
|
else:
|
||||||
# self.send({'alert': 'Unknown request: {}'.format(message)})
|
# self.send({'alert': 'Unknown request: {}'.format(message)})
|
||||||
logger.warn("Unknown request: {}".format(message))
|
logger.warn("Unknown request: {}".format(message))
|
||||||
|
@ -215,34 +168,25 @@ class AnimationHandler(tornado.web.RequestHandler):
|
||||||
if name.endswith("json_appendable")
|
if name.endswith("json_appendable")
|
||||||
]
|
]
|
||||||
for name in names:
|
for name in names:
|
||||||
fn = os.path.join(self.config.storage, name)
|
with open(os.path.join(self.config.storage, name), "r") as fp:
|
||||||
stat = os.stat(fn)
|
|
||||||
if stat.st_size == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open(fn, "r") as fp:
|
|
||||||
first_line = fp.readline().strip()
|
first_line = fp.readline().strip()
|
||||||
if first_line.endswith(","):
|
if first_line.endswith(","):
|
||||||
first_line = first_line[:-1]
|
first_line = first_line[:-1]
|
||||||
|
print(first_line)
|
||||||
metadata = json.loads(first_line)
|
metadata = json.loads(first_line)
|
||||||
files.append(
|
files.append(
|
||||||
{
|
{
|
||||||
"name": f"/files/{name[:-16]}",
|
"name": f"/files/{name[:-16]}",
|
||||||
"id": name[:-16],
|
"time": metadata[0],
|
||||||
"ctime": metadata[0],
|
|
||||||
"mtime": datetime.datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %T"),
|
|
||||||
"dimensions": [metadata[1], metadata[2]],
|
"dimensions": [metadata[1], metadata[2]],
|
||||||
"svg": f"/drawing/{name[:-16]}.svg",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
files.sort(key=lambda k: k["mtime"])
|
files.sort(key=lambda k: k["time"])
|
||||||
self.write(json.dumps(files))
|
self.write(json.dumps(files))
|
||||||
else:
|
else:
|
||||||
path = os.path.join(
|
path = os.path.join(
|
||||||
self.config.storage, os.path.basename(
|
self.config.storage, os.path.basename(filename) + ".json_appendable"
|
||||||
filename) + ".json_appendable"
|
|
||||||
)
|
)
|
||||||
drawing = {"file": filename, "shape": []}
|
drawing = {"file": filename, "shape": []}
|
||||||
with open(path, "r") as fp:
|
with open(path, "r") as fp:
|
||||||
|
@ -261,8 +205,7 @@ class AnimationHandler(tornado.web.RequestHandler):
|
||||||
# p = stroke[i*4:i*4+4]
|
# p = stroke[i*4:i*4+4]
|
||||||
# points.append([float(p[0]), float(p[1]), int(p[2]), float(p[3])])
|
# points.append([float(p[0]), float(p[1]), int(p[2]), float(p[3])])
|
||||||
drawing["shape"].append(
|
drawing["shape"].append(
|
||||||
{"color": event["color"],
|
{"color": event["color"], "points": event["points"]}
|
||||||
"points": event["points"]}
|
|
||||||
)
|
)
|
||||||
self.write(json.dumps(drawing))
|
self.write(json.dumps(drawing))
|
||||||
|
|
||||||
|
@ -330,12 +273,10 @@ class AnnotationHandler(tornado.web.RequestHandler):
|
||||||
self.write(annotation.get_as_svg())
|
self.write(annotation.get_as_svg())
|
||||||
elif extension == "mp3":
|
elif extension == "mp3":
|
||||||
self.set_header("Content-Type", "audio/mp3")
|
self.set_header("Content-Type", "audio/mp3")
|
||||||
self.write(annotation.getAnimationSlice(
|
self.write(annotation.getAnimationSlice().audio.export(format="mp3").read())
|
||||||
).audio.export(format="mp3").read())
|
|
||||||
elif extension == "wav":
|
elif extension == "wav":
|
||||||
self.set_header("Content-Type", "audio/wav")
|
self.set_header("Content-Type", "audio/wav")
|
||||||
self.write(annotation.getAnimationSlice(
|
self.write(annotation.getAnimationSlice().audio.export(format="wav").read())
|
||||||
).audio.export(format="wav").read())
|
|
||||||
else:
|
else:
|
||||||
self.set_header("Content-Type", "application/json")
|
self.set_header("Content-Type", "application/json")
|
||||||
self.write(json.dumps({
|
self.write(json.dumps({
|
||||||
|
@ -345,57 +286,6 @@ class AnnotationHandler(tornado.web.RequestHandler):
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
class DrawingHandler(tornado.web.RequestHandler):
|
|
||||||
"""Get drawing as svg"""
|
|
||||||
|
|
||||||
def initialize(self, config, index: svganim.strokes.AnnotationIndex):
|
|
||||||
self.config = config
|
|
||||||
self.index = index
|
|
||||||
self.metadir = os.path.join(self.config.storage, "metadata")
|
|
||||||
|
|
||||||
def get(self, drawing_id):
|
|
||||||
if drawing_id[-4:] == ".svg":
|
|
||||||
extension = "svg"
|
|
||||||
drawing_id = drawing_id[:-4]
|
|
||||||
elif drawing_id[-4:] == ".mp3":
|
|
||||||
extension = "mp3"
|
|
||||||
drawing_id = drawing_id[:-4]
|
|
||||||
elif drawing_id[-4:] == ".wav":
|
|
||||||
extension = "wav"
|
|
||||||
drawing_id = drawing_id[:-4]
|
|
||||||
else:
|
|
||||||
extension = None
|
|
||||||
|
|
||||||
logger.info(f"drawing {drawing_id=}, {extension=}")
|
|
||||||
if drawing_id not in self.index.drawings:
|
|
||||||
self.index.refresh()
|
|
||||||
# double check
|
|
||||||
if drawing_id not in self.index.drawings:
|
|
||||||
raise tornado.web.HTTPError(404)
|
|
||||||
|
|
||||||
drawing = self.index.drawings[drawing_id]
|
|
||||||
|
|
||||||
if extension == "svg":
|
|
||||||
self.set_header("Content-Type", "image/svg+xml")
|
|
||||||
self.write(drawing.get_animation().get_as_svg())
|
|
||||||
elif extension == "mp3":
|
|
||||||
self.set_header("Content-Type", "audio/mp3")
|
|
||||||
self.write(drawing.get_animation(
|
|
||||||
).audio.export(format="mp3").read())
|
|
||||||
elif extension == "wav":
|
|
||||||
self.set_header("Content-Type", "audio/wav")
|
|
||||||
self.write(drawing.get_animation(
|
|
||||||
).audio.export(format="wav").read())
|
|
||||||
else:
|
|
||||||
self.set_header("Content-Type", "application/json")
|
|
||||||
self.write(json.dumps({
|
|
||||||
"id": drawing.id,
|
|
||||||
"annotations_url": drawing.get_annotations_url(),
|
|
||||||
"audio": f"/drawing/{drawing.id}.mp3",
|
|
||||||
"svg": f"/drawing/{drawing.id}.svg",
|
|
||||||
}))
|
|
||||||
|
|
||||||
|
|
||||||
class AnnotationsHandler(tornado.web.RequestHandler):
|
class AnnotationsHandler(tornado.web.RequestHandler):
|
||||||
def initialize(self, config):
|
def initialize(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
@ -447,7 +337,6 @@ class AnnotationsHandler(tornado.web.RequestHandler):
|
||||||
with open(meta_file, "w") as fp:
|
with open(meta_file, "w") as fp:
|
||||||
json.dump(self.json_args, fp)
|
json.dump(self.json_args, fp)
|
||||||
|
|
||||||
|
|
||||||
class IndexHandler(tornado.web.RequestHandler):
|
class IndexHandler(tornado.web.RequestHandler):
|
||||||
"""Get annotation as svg"""
|
"""Get annotation as svg"""
|
||||||
|
|
||||||
|
@ -461,10 +350,9 @@ class IndexHandler(tornado.web.RequestHandler):
|
||||||
self.logger.info("Reloading Annotation Index")
|
self.logger.info("Reloading Annotation Index")
|
||||||
self.index.refresh()
|
self.index.refresh()
|
||||||
self.logger.info("\treloaded annotation index")
|
self.logger.info("\treloaded annotation index")
|
||||||
|
|
||||||
self.render("templates/index.html", index=self.index)
|
self.render("templates/index.html", index=self.index)
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
"""
|
"""
|
||||||
Server for HIT -> plotter events
|
Server for HIT -> plotter events
|
||||||
|
@ -504,10 +392,8 @@ class Server:
|
||||||
{"path": os.path.join(self.config.storage, "audio")},
|
{"path": os.path.join(self.config.storage, "audio")},
|
||||||
),
|
),
|
||||||
(r"/audio", AudioListingHandler, {"config": self.config}),
|
(r"/audio", AudioListingHandler, {"config": self.config}),
|
||||||
(r"/annotations/(.+)", AnnotationsHandler,
|
(r"/annotations/(.+)", AnnotationsHandler, {"config": self.config}),
|
||||||
{"config": self.config}),
|
(r"/tags", TagHandler, {"config": self.config, "index": self.index}),
|
||||||
(r"/tags", TagHandler,
|
|
||||||
{"config": self.config, "index": self.index}),
|
|
||||||
(
|
(
|
||||||
r"/tags/(.+)",
|
r"/tags/(.+)",
|
||||||
TagAnnotationsHandler,
|
TagAnnotationsHandler,
|
||||||
|
@ -518,17 +404,9 @@ class Server:
|
||||||
AnnotationHandler,
|
AnnotationHandler,
|
||||||
{"config": self.config, "index": self.index},
|
{"config": self.config, "index": self.index},
|
||||||
),
|
),
|
||||||
(
|
(r"/(.+)", StaticFileWithHeaderHandler, {"path": self.web_root}),
|
||||||
r"/drawing/(.+)",
|
|
||||||
DrawingHandler,
|
|
||||||
{"config": self.config, "index": self.index},
|
|
||||||
),
|
|
||||||
|
|
||||||
(r"/index", IndexHandler,
|
(r"/", IndexHandler, {"config": self.config, "index": self.index}),
|
||||||
{"config": self.config, "index": self.index}),
|
|
||||||
|
|
||||||
(r"/(.*)", StaticFileWithHeaderHandler,
|
|
||||||
{"path": self.web_root, 'default_filename': 'index.html'}),
|
|
||||||
],
|
],
|
||||||
debug=True,
|
debug=True,
|
||||||
autoreload=True,
|
autoreload=True,
|
||||||
|
|
|
@ -193,21 +193,6 @@
|
||||||
.annotation-google {
|
.annotation-google {
|
||||||
background-color: blueviolet !important;
|
background-color: blueviolet !important;
|
||||||
}
|
}
|
||||||
.annotation-map {
|
|
||||||
background-color: red !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.annotation-relation {
|
|
||||||
background-color: blue !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.annotation-text {
|
|
||||||
background-color: blueviolet !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.annotation-figure {
|
|
||||||
background-color: pink !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unsaved::before {
|
.unsaved::before {
|
||||||
content: '*';
|
content: '*';
|
||||||
|
@ -255,14 +240,6 @@
|
||||||
width: 100px; /* hides seek head */
|
width: 100px; /* hides seek head */
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist img{
|
|
||||||
position: static;
|
|
||||||
width: 250px;
|
|
||||||
height: 250px;
|
|
||||||
background: white;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="assets/nouislider-15.5.0.css">
|
<link rel="stylesheet" href="assets/nouislider-15.5.0.css">
|
||||||
<link rel="stylesheet" href="core.css">
|
<link rel="stylesheet" href="core.css">
|
||||||
|
@ -280,7 +257,7 @@
|
||||||
if (location.search) {
|
if (location.search) {
|
||||||
ann = new Annotator(
|
ann = new Annotator(
|
||||||
document.getElementById("interface"),
|
document.getElementById("interface"),
|
||||||
["map", "text", "relation", "figure"],
|
["test", "another", "google"],
|
||||||
location.search.substring(1)
|
location.search.substring(1)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,9 +7,6 @@
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' />
|
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' />
|
||||||
<title>Draw a line animation</title>
|
<title>Draw a line animation</title>
|
||||||
<style media="screen">
|
<style media="screen">
|
||||||
body{
|
|
||||||
background:black;
|
|
||||||
}
|
|
||||||
#sample,
|
#sample,
|
||||||
svg {
|
svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -37,7 +34,7 @@
|
||||||
|
|
||||||
path {
|
path {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: auto;
|
stroke: red;
|
||||||
stroke-width: 1mm;
|
stroke-width: 1mm;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +60,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
/* prevent reload on scroll in chrome */
|
/* prevent reload on scroll in chrome */
|
||||||
position: fixed;
|
position: fixed;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
@ -92,7 +89,6 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background:white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#info {
|
#info {
|
||||||
|
@ -118,7 +114,7 @@
|
||||||
.toolbox {
|
.toolbox {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 40%;
|
top: 50px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
@ -154,7 +150,7 @@
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
color: gray;
|
color: gray;
|
||||||
z-index: 1;
|
z-index: -1;
|
||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,24 +162,6 @@
|
||||||
cursor: wait;
|
cursor: wait;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-fullscreen{
|
|
||||||
display: block;
|
|
||||||
cursor:pointer;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 50px;
|
|
||||||
left: calc(50% - 200px);
|
|
||||||
width: 400px;
|
|
||||||
border-radius: 50px;
|
|
||||||
background:black; color:white;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 2;
|
|
||||||
font-size: 40px;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
body.fullscreen .button-fullscreen{
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -192,25 +170,7 @@
|
||||||
</div>
|
</div>
|
||||||
<script src="draw.js"></script>
|
<script src="draw.js"></script>
|
||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
// let canvas;
|
const canvas = new Canvas(document.getElementById("interface"));
|
||||||
// if (location.search) {
|
|
||||||
// const
|
|
||||||
// canvas = new Canvas(
|
|
||||||
// document.getElementById("interface"),
|
|
||||||
// ["map", "text", "relation", "figure"],
|
|
||||||
// location.search.substring(1)
|
|
||||||
// );
|
|
||||||
// } else{
|
|
||||||
let preload;
|
|
||||||
if (location.hash.length) {
|
|
||||||
preload = location.hash.substring(1);
|
|
||||||
} else {
|
|
||||||
preload = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvas = new Canvas(document.getElementById("interface"), preload);
|
|
||||||
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
125
www/draw.js
125
www/draw.js
|
@ -1,31 +1,6 @@
|
||||||
// loadDrawing = function(identifier){
|
|
||||||
// const request = new Request('/copy_and_load/'+identifier, {
|
|
||||||
// method: 'GET',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // fetch(request)
|
|
||||||
// // .then(response => response.json())
|
|
||||||
// // .then(data => {
|
|
||||||
// // const metadata_req = new Request(`/annotations/${data.file}`, {
|
|
||||||
// // method: 'GET',
|
|
||||||
// // });
|
|
||||||
// // fetch(metadata_req)
|
|
||||||
// // .then(response => response.ok ? response.json() : null)
|
|
||||||
// // .then(metadata => {
|
|
||||||
// // if (metadata !== null) {
|
|
||||||
// // metadata.annotations = metadata.annotations.map((a) => new Annotation(a.tag, a.t_in, a.t_out))
|
|
||||||
// // }
|
|
||||||
// // this.loadStrokes(data, metadata)
|
|
||||||
// // })
|
|
||||||
// // .catch(e => console.log(e));
|
|
||||||
// // // do something with the data sent in the request
|
|
||||||
// // });
|
|
||||||
// }
|
|
||||||
|
|
||||||
class Canvas {
|
class Canvas {
|
||||||
constructor(wrapperEl, preload_id) {
|
constructor(wrapperEl) {
|
||||||
this.allowDrawing = false;
|
this.allowDrawing = false;
|
||||||
this.socket = null; // don't initialise right away
|
|
||||||
this.viewbox = { "x": 0, "y": 0, "width": null, "height": null };
|
this.viewbox = { "x": 0, "y": 0, "width": null, "height": null };
|
||||||
this.url = window.location.origin.replace('http', 'ws') + '/ws?' + window.location.search.substring(1);
|
this.url = window.location.origin.replace('http', 'ws') + '/ws?' + window.location.search.substring(1);
|
||||||
|
|
||||||
|
@ -45,22 +20,6 @@ class Canvas {
|
||||||
this.wrapperEl.appendChild(this.filenameEl);
|
this.wrapperEl.appendChild(this.filenameEl);
|
||||||
|
|
||||||
|
|
||||||
this.fullscreenEl = document.createElement('div');
|
|
||||||
this.fullscreenEl.classList.add('button-fullscreen');
|
|
||||||
this.fullscreenEl.innerText = "Fullscreen";
|
|
||||||
this.wrapperEl.appendChild(this.fullscreenEl);
|
|
||||||
this.fullscreenEl.addEventListener('click', (e) => {
|
|
||||||
document.body.requestFullscreen();
|
|
||||||
});
|
|
||||||
document.body.addEventListener('fullscreenchange', (e) => {
|
|
||||||
if (document.fullscreenElement) {
|
|
||||||
document.body.classList.add('fullscreen');
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('fullscreen');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
this.colors = ["black", "red", "blue", "green"];
|
this.colors = ["black", "red", "blue", "green"];
|
||||||
|
|
||||||
this.resize();
|
this.resize();
|
||||||
|
@ -81,7 +40,7 @@ class Canvas {
|
||||||
document.body.addEventListener('pointermove', (ev) => {
|
document.body.addEventListener('pointermove', (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
if (ev.pointerType == "touch" || ev.buttons & 2) { // 4: middle mouse button
|
if (ev.pointerType == "touch" || ev.buttons & 4) { // 4: middle mouse button
|
||||||
this.moveCanvas(ev);
|
this.moveCanvas(ev);
|
||||||
} else { // pointerType == pen or mouse
|
} else { // pointerType == pen or mouse
|
||||||
this.draw(ev);
|
this.draw(ev);
|
||||||
|
@ -90,26 +49,20 @@ class Canvas {
|
||||||
document.body.addEventListener('pointerup', (ev) => {
|
document.body.addEventListener('pointerup', (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
if (ev.pointerType == "touch" || ev.buttons & 4 || this.isMoving) { // buttons is 0 on pointerup
|
||||||
if (ev.pointerType == "touch" || ev.buttons & 2 || this.isMoving) { // buttons is 0 on pointerup
|
|
||||||
this.endMoveCanvas(ev);
|
this.endMoveCanvas(ev);
|
||||||
this.isMoving = false;
|
this.isMoving = false;
|
||||||
} else { // pointerType == pen or mouse
|
} else { // pointerType == pen or mouse
|
||||||
location.hash = '#' + this.filename; // only update when drawn.
|
|
||||||
this.penup(ev);
|
this.penup(ev);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.svgEl.addEventListener('contextmenu', function (e) {
|
|
||||||
// do something here...
|
|
||||||
e.preventDefault();
|
|
||||||
}, false);
|
|
||||||
this.svgEl.addEventListener('pointerdown', (ev) => {
|
this.svgEl.addEventListener('pointerdown', (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
if (ev.pointerType == "touch" || ev.buttons & 2) { // 4: middle mouse button, 2; right mouse button
|
if (ev.pointerType == "touch" || ev.buttons & 4) { // 4: middle mouse button
|
||||||
this.isMoving = true;
|
this.isMoving = true;
|
||||||
this.startMoveCanvas(ev);
|
this.startMoveCanvas(ev);
|
||||||
} else if (ev.buttons & 1) { // pointerType == pen or mouse
|
} else { // pointerType == pen or mouse
|
||||||
this.startStroke(ev);
|
this.startStroke(ev);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -121,16 +74,14 @@ class Canvas {
|
||||||
|
|
||||||
this.socket = new WebSocket(this.url);
|
this.socket = new WebSocket(this.url);
|
||||||
this.socket.addEventListener('open', (e) => {
|
this.socket.addEventListener('open', (e) => {
|
||||||
this.sendDimensions();
|
const d = {
|
||||||
|
'event': 'dimensions',
|
||||||
if (preload_id) {
|
'width': this.viewbox.width,
|
||||||
// signal if we want to continue from an existing drawing
|
'height': this.viewbox.height
|
||||||
this.socket.send(JSON.stringify({
|
};
|
||||||
'event': 'preload',
|
console.log('send', d);
|
||||||
'file': preload_id,
|
this.socket.send(JSON.stringify(d));
|
||||||
}));
|
})
|
||||||
}
|
|
||||||
});
|
|
||||||
this.socket.addEventListener('message', (e) => {
|
this.socket.addEventListener('message', (e) => {
|
||||||
let msg = JSON.parse(e.data);
|
let msg = JSON.parse(e.data);
|
||||||
console.log('receive', msg);
|
console.log('receive', msg);
|
||||||
|
@ -139,36 +90,9 @@ class Canvas {
|
||||||
this.setFilename(msg.filename);
|
this.setFilename(msg.filename);
|
||||||
this.openTheFloor()
|
this.openTheFloor()
|
||||||
}
|
}
|
||||||
if (msg.hasOwnProperty('preloaded_svg')) {
|
|
||||||
console.log('preloaded', msg);
|
|
||||||
if (msg.dimensions[0] != this.viewbox.width || msg.dimensions[1] != this.viewbox.height) {
|
|
||||||
alert(`Loading file with different dimensions. This can lead to odd results. Original: ${msg.dimensions[0]}x${msg.dimensions[1]} Now: ${this.viewbox.width}x${this.viewbox.height}`)
|
|
||||||
}
|
|
||||||
this.setPreloaded(msg.preloaded_svg);
|
|
||||||
|
|
||||||
// this.setFilename(msg.filename);
|
|
||||||
// this.openTheFloor()
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setPreloaded(json_url) {
|
|
||||||
this.preloaded_resource = json_url
|
|
||||||
const request = new Request(this.preloaded_resource + '.svg', {
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
|
|
||||||
fetch(request)
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(body => {
|
|
||||||
const parser = new DOMParser()
|
|
||||||
const dom = parser.parseFromString(body, "image/svg+xml");
|
|
||||||
console.log(dom, dom.getRootNode().querySelectorAll('g'))
|
|
||||||
const group = dom.getRootNode().querySelectorAll('g')[0]
|
|
||||||
this.svgEl.prepend(group);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
startMoveCanvas(ev) {
|
startMoveCanvas(ev) {
|
||||||
this.moveCanvasPrevPoint = { "x": ev.x, "y": ev.y };
|
this.moveCanvasPrevPoint = { "x": ev.x, "y": ev.y };
|
||||||
this.currentMoves = [];
|
this.currentMoves = [];
|
||||||
|
@ -197,7 +121,7 @@ class Canvas {
|
||||||
this.viewbox.x -= diff.x;
|
this.viewbox.x -= diff.x;
|
||||||
this.viewbox.y -= diff.y;
|
this.viewbox.y -= diff.y;
|
||||||
this.moveCanvasPrevPoint = { "x": ev.x, "y": ev.y };
|
this.moveCanvasPrevPoint = { "x": ev.x, "y": ev.y };
|
||||||
this.currentMoves.push(Object.assign({ 't': window.performance.now() - this.startTime }, this.viewbox));
|
this.currentMoves.push(Object.assign({'t': window.performance.now() - this.startTime}, this.viewbox));
|
||||||
|
|
||||||
this.applyViewBox()
|
this.applyViewBox()
|
||||||
}
|
}
|
||||||
|
@ -221,6 +145,7 @@ class Canvas {
|
||||||
colorEl.addEventListener('click', (e) => {
|
colorEl.addEventListener('click', (e) => {
|
||||||
console.log('set color', color)
|
console.log('set color', color)
|
||||||
this.setColor(color);
|
this.setColor(color);
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
colorsEl.appendChild(colorEl);
|
colorsEl.appendChild(colorEl);
|
||||||
|
@ -246,24 +171,6 @@ class Canvas {
|
||||||
this.viewbox.height = window.innerHeight;
|
this.viewbox.height = window.innerHeight;
|
||||||
|
|
||||||
this.applyViewBox();
|
this.applyViewBox();
|
||||||
this.sendDimensions();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendDimensions() {
|
|
||||||
const d = {
|
|
||||||
'event': 'dimensions',
|
|
||||||
'width': this.viewbox.width,
|
|
||||||
'height': this.viewbox.height
|
|
||||||
};
|
|
||||||
if (this.socket === null) {
|
|
||||||
// ignore ...
|
|
||||||
} else if (this.socket.readyState) {
|
|
||||||
this.socket.send(JSON.stringify(d));
|
|
||||||
} else {
|
|
||||||
this.socket.addEventListener('open', (ev) => {
|
|
||||||
this.socket.send(JSON.stringify(d));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyViewBox() {
|
applyViewBox() {
|
||||||
|
@ -341,7 +248,7 @@ class Canvas {
|
||||||
if (!this.isDrawing) {
|
if (!this.isDrawing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isDrawing = false;
|
this.isDrawing = false;
|
||||||
//document.body.removeEventListener('mousemove', draw);
|
//document.body.removeEventListener('mousemove', draw);
|
||||||
|
|
||||||
|
|
|
@ -23,24 +23,11 @@ class Playlist {
|
||||||
for (let file of data) {
|
for (let file of data) {
|
||||||
const liEl = document.createElement("li");
|
const liEl = document.createElement("li");
|
||||||
|
|
||||||
|
|
||||||
const imgEl = document.createElement("img");
|
|
||||||
imgEl.classList.add('img');
|
|
||||||
imgEl.title = file.id;
|
|
||||||
imgEl.src = file.svg;
|
|
||||||
liEl.append(imgEl);
|
|
||||||
|
|
||||||
|
|
||||||
let time = file.mtime;
|
|
||||||
if (file.ctime != file.mtime){
|
|
||||||
time += ` (orig: ${file.ctime})`;
|
|
||||||
}
|
|
||||||
const dateEl = document.createElement("span");
|
const dateEl = document.createElement("span");
|
||||||
dateEl.classList.add('date');
|
dateEl.classList.add('date');
|
||||||
dateEl.innerText = time;
|
dateEl.innerText = file.time;
|
||||||
liEl.append(dateEl);
|
liEl.append(dateEl);
|
||||||
|
|
||||||
|
|
||||||
const nameEl = document.createElement("span");
|
const nameEl = document.createElement("span");
|
||||||
nameEl.classList.add('name');
|
nameEl.classList.add('name');
|
||||||
nameEl.innerText = file.name;
|
nameEl.innerText = file.name;
|
||||||
|
@ -65,14 +52,6 @@ class Playlist {
|
||||||
annotateEl.pathname = "annotate.html";
|
annotateEl.pathname = "annotate.html";
|
||||||
annotateEl.search = "?"+file.name;
|
annotateEl.search = "?"+file.name;
|
||||||
linksEl.append(annotateEl);
|
linksEl.append(annotateEl);
|
||||||
|
|
||||||
const drawEl = document.createElement("a");
|
|
||||||
drawEl.classList.add('draw');
|
|
||||||
drawEl.innerText = "Draw";
|
|
||||||
drawEl.href = location;
|
|
||||||
drawEl.pathname = "draw.html";
|
|
||||||
drawEl.hash = file.id;
|
|
||||||
linksEl.append(drawEl);
|
|
||||||
|
|
||||||
// liEl.addEventListener('click', (e) => {
|
// liEl.addEventListener('click', (e) => {
|
||||||
// this.play(fileUrl);
|
// this.play(fileUrl);
|
||||||
|
|
Loading…
Reference in a new issue