Improvements to interface
This commit is contained in:
parent
8cc9cea24f
commit
acbd6fdd94
11 changed files with 675 additions and 415 deletions
|
@ -3,22 +3,23 @@
|
|||
This server controls all hugveys and the processing of their narratives. It exposes itself for control to the panopticon server.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
import os
|
||||
import time
|
||||
import yaml
|
||||
import zmq
|
||||
from zmq.asyncio import Context
|
||||
|
||||
import asyncio
|
||||
from hugvey.communication import getTopic, zmqSend, zmqReceive
|
||||
from hugvey.panopticon import Panopticon
|
||||
from hugvey.story import Story
|
||||
from hugvey.voice.google import GoogleVoiceClient
|
||||
from hugvey.voice.player import Player
|
||||
from hugvey.voice.streamer import AudioStreamer
|
||||
import json
|
||||
import logging
|
||||
import queue
|
||||
import os
|
||||
import threading
|
||||
|
||||
|
||||
logger = logging.getLogger("command")
|
||||
|
@ -72,7 +73,8 @@ class CentralCommand(object):
|
|||
lang_filename = os.path.join(self.config['web']['files_dir'], lang['file'])
|
||||
self.languageFiles[lang['code']] = lang['file']
|
||||
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)
|
||||
|
||||
|
|
|
@ -90,6 +90,11 @@ def getWebSocketHandler(central_command):
|
|||
|
||||
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):
|
||||
class UploadHandler(tornado.web.RequestHandler):
|
||||
def post(self):
|
||||
|
@ -97,9 +102,10 @@ def getUploadHandler(central_command):
|
|||
langCode = self.get_argument("language")
|
||||
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'])
|
||||
print(storyData)
|
||||
# print(json.dumps(storyData))
|
||||
# self.finish()
|
||||
# return
|
||||
|
||||
if 'audio' in self.request.files:
|
||||
msgId = self.get_argument("message_id")
|
||||
|
@ -120,9 +126,10 @@ def getUploadHandler(central_command):
|
|||
# fp.write(audioFile['body'])
|
||||
break
|
||||
|
||||
with open(langFile, 'r') as fp:
|
||||
logger.info(f'Save story to {langFile}')
|
||||
# json.dump(storyData, fp)
|
||||
print(os.path.abspath(langFile))
|
||||
with open(langFile, 'w') as json_fp:
|
||||
logger.info(f'Save story to {langFile} {json_fp}')
|
||||
json.dump(storyData, json_fp)
|
||||
self.finish()
|
||||
return UploadHandler
|
||||
|
||||
|
@ -132,7 +139,7 @@ class Panopticon(object):
|
|||
self.config = config
|
||||
self.application = tornado.web.Application([
|
||||
(r"/ws", getWebSocketHandler(self.command)),
|
||||
(r"/local/(.*)", tornado.web.StaticFileHandler,
|
||||
(r"/local/(.*)", NonCachingStaticFileHandler,
|
||||
{"path": config['web']['files_dir']}),
|
||||
(r"/upload", getUploadHandler(self.command)),
|
||||
(r"/(.*)", tornado.web.StaticFileHandler,
|
||||
|
|
|
@ -2,13 +2,22 @@ body {
|
|||
font-family: "Noto Sans", sans-serif;
|
||||
margin: 0; }
|
||||
|
||||
.btn {
|
||||
.btn, input[type="submit"] {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
background: #333;
|
||||
padding: 5px;
|
||||
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 {
|
||||
display: flex;
|
||||
|
@ -29,6 +38,8 @@ body {
|
|||
border: solid 1px;
|
||||
box-sizing: border-box;
|
||||
position: relative; }
|
||||
#status > div#overview {
|
||||
width: 66.66667%; }
|
||||
#status .hugvey {
|
||||
background-image: linear-gradient(to top, #587457, #35a589);
|
||||
color: white;
|
||||
|
@ -52,11 +63,13 @@ body {
|
|||
text-align: center; }
|
||||
|
||||
#story {
|
||||
position: relative; }
|
||||
position: relative;
|
||||
width: calc(100% - 430px); }
|
||||
#story #controls {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px; }
|
||||
left: 5px;
|
||||
white-space: nowrap; }
|
||||
#story svg#graph {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -77,6 +90,11 @@ body {
|
|||
font-size: 11pt;
|
||||
font-family: sans-serif;
|
||||
fill: white; }
|
||||
#story text.msg_id {
|
||||
transform: translateY(-20px);
|
||||
opacity: .5; }
|
||||
#story text.msg_txt {
|
||||
font-weight: bold; }
|
||||
#story line {
|
||||
marker-end: url("#arrowHead");
|
||||
stroke-width: 2px;
|
||||
|
@ -84,6 +102,10 @@ body {
|
|||
#story line.link--noconditions {
|
||||
stroke-dasharray: 5 4;
|
||||
stroke: red; }
|
||||
#story line.dir-highlight {
|
||||
stroke-dasharray: 5;
|
||||
animation: dash-animation 20s infinite linear;
|
||||
stroke-width: 3px; }
|
||||
#story label::after {
|
||||
content: '';
|
||||
clear: both;
|
||||
|
@ -109,6 +131,18 @@ body {
|
|||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
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 .selectedMsg circle {
|
||||
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; }
|
||||
#story .condition--add {
|
||||
/* 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
7
www/images/be.svg
Normal 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
5
www/images/de.svg
Normal 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
7
www/images/fr.svg
Normal 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
15
www/images/gb.svg
Normal 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
7
www/images/nl.svg
Normal 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 |
|
@ -11,16 +11,18 @@
|
|||
|
||||
</head>
|
||||
<body>
|
||||
<div id="interface">
|
||||
<div id="interface" class='showStatus'>
|
||||
<div id='status'>
|
||||
<div id='overview'>
|
||||
<dl>
|
||||
<dt>Uptime</dt>
|
||||
<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>
|
||||
|
||||
<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 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'}]">
|
||||
|
@ -43,6 +45,7 @@
|
|||
</div>
|
||||
<div id='story'>
|
||||
<div id="controls">
|
||||
<span id="current_lang"></span>
|
||||
<div id="btn-save" class="btn">Save</div>
|
||||
<div id="btn-addMsg" class="btn">Create message</div>
|
||||
</div>
|
||||
|
|
|
@ -234,6 +234,8 @@ class Graph{
|
|||
);
|
||||
msgEl.appendChild( msgInfoEl );
|
||||
|
||||
// crel('form')
|
||||
|
||||
// let directionHEl = document.createElement('h2');
|
||||
// directionHEl.innerHTML = "Directions";
|
||||
|
||||
|
@ -255,20 +257,35 @@ class Graph{
|
|||
}
|
||||
|
||||
getDirectionEl( direction, msg ) {
|
||||
let directionEl = document.createElement('div');
|
||||
if(direction['source'] == msg) {
|
||||
directionEl.innerHTML = `<h3>To ${direction['target']['@id']}</h3>`;
|
||||
} else {
|
||||
directionEl.innerHTML = `<h3>From ${direction['source']['@id']}</h3>`;
|
||||
}
|
||||
let del = document.createElement('div');
|
||||
del.innerHTML = "delete";
|
||||
del.classList.add("deleteBtn");
|
||||
let g = this;
|
||||
del.addEventListener('click', (e) => g.rmDirection(direction));
|
||||
directionEl.appendChild(del);
|
||||
|
||||
// TODO; conditions
|
||||
let directionEl = crel('div',
|
||||
{
|
||||
'class': 'direction ' + (direction['source'] == msg ? 'dir-to' : 'dir-from'),
|
||||
'on': {
|
||||
'mouseover': function(e) {
|
||||
console.log('over', direction['@id']);
|
||||
directionEl.classList.add('dir-highlight');
|
||||
document.getElementById(direction['@id']).classList.add('dir-highlight');
|
||||
},
|
||||
'mouseout': function(e) {
|
||||
console.log('& out ', direction['@id']);
|
||||
directionEl.classList.remove('dir-highlight');
|
||||
document.getElementById(direction['@id']).classList.remove('dir-highlight');
|
||||
}
|
||||
}
|
||||
},
|
||||
crel(
|
||||
'h3',
|
||||
{'title': direction['@id']},
|
||||
direction['source'] == msg ? `To ${direction['target']['@id']}`: `From ${direction['source']['@id']}`
|
||||
),
|
||||
crel('div', {
|
||||
'class':'btn btn--delete',
|
||||
'on': {
|
||||
'click': ( e ) => g.rmDirection( direction )
|
||||
}
|
||||
}, 'delete')
|
||||
);
|
||||
|
||||
for ( let conditionId of direction['conditions'] ) {
|
||||
let condition = this.getNodeById( conditionId );
|
||||
|
@ -371,7 +388,13 @@ class Graph{
|
|||
}
|
||||
}
|
||||
},
|
||||
crel("h4", "Create New Condition"),
|
||||
crel( "h4", {
|
||||
'class': "divToggle",
|
||||
'on': {
|
||||
'click': function(e) { this.classList.toggle('opened'); }
|
||||
}
|
||||
}, "Create New Condition" ),
|
||||
crel('div', {'class': 'divToggle-target'},
|
||||
crel( "label",
|
||||
crel( 'span', "Type" ),
|
||||
crel( 'select', {
|
||||
|
@ -380,7 +403,8 @@ class Graph{
|
|||
'change': function( e ) {
|
||||
g.fillConditionFormForType( conditionForm, e.target.value );
|
||||
}
|
||||
}}, optionEls),
|
||||
}
|
||||
}, optionEls ),
|
||||
),
|
||||
crel( "label",
|
||||
crel( 'span', "Description" ),
|
||||
|
@ -392,6 +416,7 @@ class Graph{
|
|||
'value': 'create'
|
||||
} )
|
||||
)
|
||||
)
|
||||
);
|
||||
this.fillConditionFormForType( conditionForm, optionEls[0].value );
|
||||
|
||||
|
@ -424,7 +449,7 @@ class Graph{
|
|||
|
||||
addMsg() {
|
||||
let msg = {
|
||||
"@id": "n" + Date.now().toString(36),
|
||||
"@id": this.language_code.substring( 0, 2 ) + "-n" + Date.now().toString( 36 ),
|
||||
"@type": "Msg",
|
||||
"text": "New",
|
||||
"start": false
|
||||
|
@ -465,7 +490,7 @@ class Graph{
|
|||
|
||||
addCondition( type, label, vars, skip ) {
|
||||
let con = {
|
||||
"@id": "c" + Date.now().toString(36),
|
||||
"@id": this.language_code.substring( 0, 2 ) + "-c" + Date.now().toString( 36 ),
|
||||
"@type": "Condition",
|
||||
"type": type,
|
||||
"label": label,
|
||||
|
@ -481,7 +506,7 @@ class Graph{
|
|||
|
||||
addDirection( source, target ) {
|
||||
let dir = {
|
||||
"@id": "d" + Date.now().toString(36),
|
||||
"@id": this.language_code.substring( 0, 2 ) + "-d" + Date.now().toString( 36 ),
|
||||
"@type": "Direction",
|
||||
"source": source,
|
||||
"target": target,
|
||||
|
@ -612,6 +637,11 @@ class Graph{
|
|||
this.conditions = this.data.filter(( node ) => node['@type'] == 'Condition' );
|
||||
this.interruptions = this.data.filter(( node ) => node['@type'] == 'Interruption' );
|
||||
|
||||
document.getElementById('current_lang').innerHTML = "";
|
||||
document.getElementById('current_lang').appendChild(crel('span', {
|
||||
'class': 'flag-icon ' + this.language_code
|
||||
}));
|
||||
|
||||
// save state;
|
||||
this.saveState();
|
||||
}
|
||||
|
@ -657,8 +687,8 @@ class Graph{
|
|||
.attr( 'r', this.nodeSize )
|
||||
// .text(d => d.id)
|
||||
;
|
||||
let text = newNodeG.append("text")
|
||||
;
|
||||
let textId = newNodeG.append( "text" ).attr( 'class', 'msg_id' );
|
||||
let textContent = newNodeG.append( "text" ).attr( 'class', 'msg_txt' );
|
||||
|
||||
// remove
|
||||
node.exit().remove();
|
||||
|
@ -684,11 +714,13 @@ class Graph{
|
|||
let newLink = link.enter()
|
||||
.append( "line" )
|
||||
;
|
||||
|
||||
//remove
|
||||
link.exit().remove();
|
||||
link = link.merge( newLink );
|
||||
|
||||
link.attr( 'class', l => { return `link ` + ( l['conditions'].length == 0 ? "link--noconditions" : "link--withconditions" ); } );
|
||||
link.attr('id', (l) => l['@id']);
|
||||
|
||||
// console.log('c');
|
||||
let formatText = ( t ) => {
|
||||
|
@ -699,7 +731,8 @@ class Graph{
|
|||
}
|
||||
};
|
||||
|
||||
node.selectAll("text").text(d => formatText(`(${d['@id']}) ${d['text']}`));
|
||||
node.selectAll( "text.msg_id" ).text( d => d['@id'] );
|
||||
node.selectAll( "text.msg_txt" ).text( d => formatText( `${d['text']}` ) );
|
||||
// console.log('q');
|
||||
// // TODO: update text
|
||||
// let text = newNodeG.append("text")
|
||||
|
|
|
@ -1,15 +1,31 @@
|
|||
$status_width: 430px;
|
||||
$status_width_open: 860px;
|
||||
|
||||
body{
|
||||
font-family: "Noto Sans", sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn{
|
||||
.btn, input[type="submit"]{
|
||||
display:inline-block;
|
||||
cursor: pointer;
|
||||
background: #333;
|
||||
padding: 5px;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
white-space: nowrap;
|
||||
border: none;
|
||||
|
||||
&:hover{
|
||||
background: #666;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash-animation {
|
||||
to {
|
||||
stroke-dashoffset: -1000;
|
||||
}
|
||||
}
|
||||
|
||||
#interface{
|
||||
|
@ -17,6 +33,9 @@ body{
|
|||
flex-direction: row;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
&.showStatus{
|
||||
}
|
||||
}
|
||||
|
||||
#status{
|
||||
|
@ -33,6 +52,10 @@ body{
|
|||
border: solid 1px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
&#overview{
|
||||
width: 100% / 3 * 2;
|
||||
}
|
||||
}
|
||||
|
||||
.hugvey{
|
||||
|
@ -72,11 +95,13 @@ body{
|
|||
|
||||
#story{
|
||||
position: relative;
|
||||
width: calc(100% - #{$status_width});
|
||||
|
||||
#controls{
|
||||
position:absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
svg#graph{
|
||||
width: 100%;
|
||||
|
@ -106,16 +131,30 @@ body{
|
|||
font-size: 11pt;
|
||||
font-family: sans-serif;
|
||||
fill: white;
|
||||
|
||||
&.msg_id {
|
||||
transform: translateY(-20px);
|
||||
opacity: .5;
|
||||
}
|
||||
&.msg_txt{
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
line{
|
||||
marker-end: url('#arrowHead');
|
||||
stroke-width: 2px;
|
||||
stroke: black;
|
||||
}
|
||||
line.link--noconditions{
|
||||
|
||||
&.link--noconditions{
|
||||
stroke-dasharray: 5 4;
|
||||
stroke: red;
|
||||
}
|
||||
&.dir-highlight{
|
||||
stroke-dasharray: 5;
|
||||
animation: dash-animation 20s infinite linear;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
}
|
||||
label::after {
|
||||
content: '';
|
||||
clear: both;
|
||||
|
@ -148,6 +187,27 @@ body{
|
|||
margin-bottom: 10px;
|
||||
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,
|
||||
|
@ -180,3 +240,53 @@ body{
|
|||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue