Improvements to interface

This commit is contained in:
Ruben van de Ven 2019-01-24 14:27:04 +01:00
parent 8cc9cea24f
commit acbd6fdd94
11 changed files with 675 additions and 415 deletions

View file

@ -3,22 +3,23 @@
This server controls all hugveys and the processing of their narratives. It exposes itself for control to the panopticon server. This server controls all hugveys and the processing of their narratives. It exposes itself for control to the panopticon server.
""" """
import asyncio import os
import logging
import threading
import time import time
import yaml import yaml
import zmq import zmq
from zmq.asyncio import Context from zmq.asyncio import Context
import asyncio
from hugvey.communication import getTopic, zmqSend, zmqReceive from hugvey.communication import getTopic, zmqSend, zmqReceive
from hugvey.panopticon import Panopticon from hugvey.panopticon import Panopticon
from hugvey.story import Story from hugvey.story import Story
from hugvey.voice.google import GoogleVoiceClient from hugvey.voice.google import GoogleVoiceClient
from hugvey.voice.player import Player from hugvey.voice.player import Player
from hugvey.voice.streamer import AudioStreamer from hugvey.voice.streamer import AudioStreamer
import json
import logging
import queue import queue
import os import threading
logger = logging.getLogger("command") logger = logging.getLogger("command")
@ -72,7 +73,8 @@ class CentralCommand(object):
lang_filename = os.path.join(self.config['web']['files_dir'], lang['file']) lang_filename = os.path.join(self.config['web']['files_dir'], lang['file'])
self.languageFiles[lang['code']] = lang['file'] self.languageFiles[lang['code']] = lang['file']
with open(lang_filename, 'r') as fp: with open(lang_filename, 'r') as fp:
self.languages[lang['code']] = yaml.load(fp) self.languages[lang['code']] = json.load(fp)
print(self.languages)
self.panopticon = Panopticon(self, self.config) self.panopticon = Panopticon(self, self.config)

View file

@ -90,6 +90,11 @@ def getWebSocketHandler(central_command):
return WebSocketHandler return WebSocketHandler
class NonCachingStaticFileHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path):
# Disable cache
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
def getUploadHandler(central_command): def getUploadHandler(central_command):
class UploadHandler(tornado.web.RequestHandler): class UploadHandler(tornado.web.RequestHandler):
def post(self): def post(self):
@ -97,9 +102,10 @@ def getUploadHandler(central_command):
langCode = self.get_argument("language") langCode = self.get_argument("language")
langFile = os.path.join(central_command.config['web']['files_dir'] , central_command.languageFiles[langCode]) langFile = os.path.join(central_command.config['web']['files_dir'] , central_command.languageFiles[langCode])
print(self.request.files['json'][0])
storyData = json.loads(self.request.files['json'][0]['body']) storyData = json.loads(self.request.files['json'][0]['body'])
print(storyData) # print(json.dumps(storyData))
# self.finish()
# return
if 'audio' in self.request.files: if 'audio' in self.request.files:
msgId = self.get_argument("message_id") msgId = self.get_argument("message_id")
@ -120,9 +126,10 @@ def getUploadHandler(central_command):
# fp.write(audioFile['body']) # fp.write(audioFile['body'])
break break
with open(langFile, 'r') as fp: print(os.path.abspath(langFile))
logger.info(f'Save story to {langFile}') with open(langFile, 'w') as json_fp:
# json.dump(storyData, fp) logger.info(f'Save story to {langFile} {json_fp}')
json.dump(storyData, json_fp)
self.finish() self.finish()
return UploadHandler return UploadHandler
@ -132,7 +139,7 @@ class Panopticon(object):
self.config = config self.config = config
self.application = tornado.web.Application([ self.application = tornado.web.Application([
(r"/ws", getWebSocketHandler(self.command)), (r"/ws", getWebSocketHandler(self.command)),
(r"/local/(.*)", tornado.web.StaticFileHandler, (r"/local/(.*)", NonCachingStaticFileHandler,
{"path": config['web']['files_dir']}), {"path": config['web']['files_dir']}),
(r"/upload", getUploadHandler(self.command)), (r"/upload", getUploadHandler(self.command)),
(r"/(.*)", tornado.web.StaticFileHandler, (r"/(.*)", tornado.web.StaticFileHandler,

View file

@ -2,13 +2,22 @@ body {
font-family: "Noto Sans", sans-serif; font-family: "Noto Sans", sans-serif;
margin: 0; } margin: 0; }
.btn { .btn, input[type="submit"] {
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
background: #333; background: #333;
padding: 5px; padding: 5px;
color: white; color: white;
border-radius: 5px; } border-radius: 5px;
margin-right: 5px;
white-space: nowrap;
border: none; }
.btn:hover, input[type="submit"]:hover {
background: #666; }
@keyframes dash-animation {
to {
stroke-dashoffset: -1000; } }
#interface { #interface {
display: flex; display: flex;
@ -29,6 +38,8 @@ body {
border: solid 1px; border: solid 1px;
box-sizing: border-box; box-sizing: border-box;
position: relative; } position: relative; }
#status > div#overview {
width: 66.66667%; }
#status .hugvey { #status .hugvey {
background-image: linear-gradient(to top, #587457, #35a589); background-image: linear-gradient(to top, #587457, #35a589);
color: white; color: white;
@ -52,11 +63,13 @@ body {
text-align: center; } text-align: center; }
#story { #story {
position: relative; } position: relative;
width: calc(100% - 430px); }
#story #controls { #story #controls {
position: absolute; position: absolute;
top: 5px; top: 5px;
left: 5px; } left: 5px;
white-space: nowrap; }
#story svg#graph { #story svg#graph {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -77,6 +90,11 @@ body {
font-size: 11pt; font-size: 11pt;
font-family: sans-serif; font-family: sans-serif;
fill: white; } fill: white; }
#story text.msg_id {
transform: translateY(-20px);
opacity: .5; }
#story text.msg_txt {
font-weight: bold; }
#story line { #story line {
marker-end: url("#arrowHead"); marker-end: url("#arrowHead");
stroke-width: 2px; stroke-width: 2px;
@ -84,6 +102,10 @@ body {
#story line.link--noconditions { #story line.link--noconditions {
stroke-dasharray: 5 4; stroke-dasharray: 5 4;
stroke: red; } stroke: red; }
#story line.dir-highlight {
stroke-dasharray: 5;
animation: dash-animation 20s infinite linear;
stroke-width: 3px; }
#story label::after { #story label::after {
content: ''; content: '';
clear: both; clear: both;
@ -109,6 +131,18 @@ body {
padding: 10px; padding: 10px;
margin-bottom: 10px; margin-bottom: 10px;
background: lightgray; } background: lightgray; }
#story #msg .direction {
position: relative; }
#story #msg .direction h3 {
margin-top: 0; }
#story #msg .direction .btn--delete {
position: absolute;
top: 5px;
right: 0px; }
#story #msg .direction .condition--add h4 {
margin: 0; }
#story #msg .direction .condition--add h4 + div {
margin-top: 10px; }
#story #nodes g:hover circle, #story #nodes g:hover circle,
#story .selectedMsg circle { #story .selectedMsg circle {
stroke: lightgreen; stroke: lightgreen;
@ -130,3 +164,33 @@ body {
text-shadow: 2px 2px 2px lightgray,-2px 2px 2px lightgray,2px -2px 2px lightgray,-2px -2px 2px lightgray; } text-shadow: 2px 2px 2px lightgray,-2px 2px 2px lightgray,2px -2px 2px lightgray,-2px -2px 2px lightgray; }
#story .condition--add { #story .condition--add {
/* text-align: center; */ } /* text-align: center; */ }
.flag-icon {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
position: relative;
display: inline-block;
width: 1.33333em;
line-height: 1em; }
.flag-icon:before {
content: '\00a0'; }
.flag-icon.flag-icon-squared {
width: 1em; }
.flag-icon.en-GB {
background-image: url("/images/gb.svg"); }
.flag-icon.de-DE {
background-image: url("/images/de.svg"); }
.flag-icon.fr-FR {
background-image: url("/images/fr.svg"); }
.flag-icon.nl-NL {
background-image: url("/images/nl.svg"); }
.divToggle {
cursor: pointer; }
.divToggle:hover {
text-decoration: underline; }
.divToggle.opened + div {
display: block; }
.divToggle + div {
display: none; }

7
www/images/be.svg Normal file
View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-be" viewBox="0 0 640 480">
<g fill-rule="evenodd" stroke-width="1pt">
<path d="M0 0h213.3v480H0z"/>
<path fill="#ffd90c" d="M213.3 0h213.4v480H213.3z"/>
<path fill="#f31830" d="M426.7 0H640v480H426.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 290 B

5
www/images/de.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-de" viewBox="0 0 640 480">
<path fill="#ffce00" d="M0 320h640v160H0z"/>
<path d="M0 0h640v160H0z"/>
<path fill="#d00" d="M0 160h640v160H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 213 B

7
www/images/fr.svg Normal file
View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-fr" viewBox="0 0 640 480">
<g fill-rule="evenodd" stroke-width="1pt">
<path fill="#fff" d="M0 0h640v480H0z"/>
<path fill="#00267f" d="M0 0h213.3v480H0z"/>
<path fill="#f31830" d="M426.7 0H640v480H426.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 292 B

15
www/images/gb.svg Normal file
View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-gb" viewBox="0 0 640 480">
<defs>
<clipPath id="a">
<path fill-opacity=".7" d="M-85.3 0h682.6v512H-85.3z"/>
</clipPath>
</defs>
<g clip-path="url(#a)" transform="translate(80) scale(.94)">
<g stroke-width="1pt">
<path fill="#012169" d="M-256 0H768v512H-256z"/>
<path fill="#fff" d="M-256 0v57.2L653.5 512H768v-57.2L-141.5 0H-256zM768 0v57.2L-141.5 512H-256v-57.2L653.5 0H768z"/>
<path fill="#fff" d="M170.7 0v512h170.6V0H170.7zM-256 170.7v170.6H768V170.7H-256z"/>
<path fill="#c8102e" d="M-256 204.8v102.4H768V204.8H-256zM204.8 0v512h102.4V0H204.8zM-256 512L85.3 341.3h76.4L-179.7 512H-256zm0-512L85.3 170.7H9L-256 38.2V0zm606.4 170.7L691.7 0H768L426.7 170.7h-76.3zM768 512L426.7 341.3H503l265 132.5V512z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 837 B

7
www/images/nl.svg Normal file
View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-nl" viewBox="0 0 640 480">
<g fill-rule="evenodd" stroke-width="1pt" transform="scale(1.25 .9375)">
<rect width="512" height="509.8" fill="#fff" rx="0" ry="0"/>
<rect width="512" height="169.9" y="342.1" fill="#21468b" rx="0" ry="0"/>
<path fill="#ae1c28" d="M0 0h512v170H0z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 364 B

View file

@ -11,16 +11,18 @@
</head> </head>
<body> <body>
<div id="interface"> <div id="interface" class='showStatus'>
<div id='status'> <div id='status'>
<div id='overview'> <div id='overview'>
<dl> <dl>
<dt>Uptime</dt> <dt>Uptime</dt>
<dd>{{uptime}}</dd> <dd>{{uptime}}</dd>
<dt>Languages</dt>
<dd v-for="lang in languages" :title="lang.file"
class="btn lang--btn" @click="loadNarrative(lang.code, lang.file)">{{lang.code}}</dd>
</dl> </dl>
<ul id='languages'>
<li v-for="lang in languages" :title="lang.file"
class="btn lang--btn" @click="loadNarrative(lang.code, lang.file)"><span :class="['flag-icon', lang.code]"></span> {{lang.code}}</li>
</ul>
</div> </div>
<div class='hugvey' v-for="hv in hugveys" <div class='hugvey' v-for="hv in hugveys"
:class="[{'hugvey--off': hv.status == 'off'},{'hugvey--on': hv.status != 'off'},{'hugvey--paused': hv.status == 'paused'},{'hugvey--running': hv.status == 'running'}]"> :class="[{'hugvey--off': hv.status == 'off'},{'hugvey--on': hv.status != 'off'},{'hugvey--paused': hv.status == 'paused'},{'hugvey--running': hv.status == 'running'}]">
@ -43,6 +45,7 @@
</div> </div>
<div id='story'> <div id='story'>
<div id="controls"> <div id="controls">
<span id="current_lang"></span>
<div id="btn-save" class="btn">Save</div> <div id="btn-save" class="btn">Save</div>
<div id="btn-addMsg" class="btn">Create message</div> <div id="btn-addMsg" class="btn">Create message</div>
</div> </div>

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,31 @@
$status_width: 430px;
$status_width_open: 860px;
body{ body{
font-family: "Noto Sans", sans-serif; font-family: "Noto Sans", sans-serif;
margin: 0; margin: 0;
} }
.btn{ .btn, input[type="submit"]{
display:inline-block; display:inline-block;
cursor: pointer; cursor: pointer;
background: #333; background: #333;
padding: 5px; padding: 5px;
color: white; color: white;
border-radius: 5px; border-radius: 5px;
margin-right: 5px;
white-space: nowrap;
border: none;
&:hover{
background: #666;
}
}
@keyframes dash-animation {
to {
stroke-dashoffset: -1000;
}
} }
#interface{ #interface{
@ -17,6 +33,9 @@ body{
flex-direction: row; flex-direction: row;
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
&.showStatus{
}
} }
#status{ #status{
@ -33,6 +52,10 @@ body{
border: solid 1px; border: solid 1px;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
&#overview{
width: 100% / 3 * 2;
}
} }
.hugvey{ .hugvey{
@ -72,11 +95,13 @@ body{
#story{ #story{
position: relative; position: relative;
width: calc(100% - #{$status_width});
#controls{ #controls{
position:absolute; position:absolute;
top: 5px; top: 5px;
left: 5px; left: 5px;
white-space: nowrap;
} }
svg#graph{ svg#graph{
width: 100%; width: 100%;
@ -106,16 +131,30 @@ body{
font-size: 11pt; font-size: 11pt;
font-family: sans-serif; font-family: sans-serif;
fill: white; fill: white;
&.msg_id {
transform: translateY(-20px);
opacity: .5;
}
&.msg_txt{
font-weight: bold;
}
} }
line{ line{
marker-end: url('#arrowHead'); marker-end: url('#arrowHead');
stroke-width: 2px; stroke-width: 2px;
stroke: black; stroke: black;
}
line.link--noconditions{ &.link--noconditions{
stroke-dasharray: 5 4; stroke-dasharray: 5 4;
stroke: red; stroke: red;
} }
&.dir-highlight{
stroke-dasharray: 5;
animation: dash-animation 20s infinite linear;
stroke-width: 3px;
}
}
label::after { label::after {
content: ''; content: '';
clear: both; clear: both;
@ -148,6 +187,27 @@ body{
margin-bottom: 10px; margin-bottom: 10px;
background:lightgray; background:lightgray;
} }
.direction{
position: relative;
h3{
margin-top:0;
}
.btn--delete{
position: absolute;
top: 5px;
right: 0px;
}
.condition--add{
h4{
margin: 0;
}
h4 +div {
margin-top: 10px;
}
}
}
} }
#nodes g:hover circle, #nodes g:hover circle,
@ -180,3 +240,53 @@ body{
/* text-align: center; */ /* text-align: center; */
} }
} }
.flag-icon {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
position: relative;
display: inline-block;
width: (4 / 3) * 1em;
line-height: 1em;
&:before {
content: '\00a0';
}
&.flag-icon-squared {
width: 1em;
}
&.en-GB {
background-image: url('/images/gb.svg');
}
&.de-DE {
background-image: url('/images/de.svg');
}
&.fr-FR {
background-image: url('/images/fr.svg');
}
&.nl-NL {
background-image: url('/images/nl.svg');
}
}
.divToggle{
cursor: pointer;
&:hover{
text-decoration: underline;
}
&.opened {
+ div{
display: block;
}
}
+ div{
display: none;
}
}