Compare commits

..

2 Commits

Author SHA1 Message Date
9b6ba6df85 Merge 2020-02-21 15:51:23 +01:00
17d14d45ae Massive structure rewrite 2020-02-21 15:45:30 +01:00
10 changed files with 313 additions and 132 deletions

View File

@ -4,5 +4,12 @@
"use_reloader": true, "use_reloader": true,
"host": "0.0.0.0", "host": "0.0.0.0",
"port": 5101 "port": 5101
},
"socket": {
"ping_timeout": 8,
"ping_interval": 2
},
"display": {
"timeout": 86400
} }
} }

2
helpers/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from . import uuid
from . import connection

10
helpers/connection.py Normal file
View File

@ -0,0 +1,10 @@
from objects import glob
def connect(sid):
glob.connections[sid] = set()
def disconnect(sid):
for display in glob.connections[sid]:
display.detach(sid)
del glob.connections[sid]

8
helpers/uuid.py Normal file
View File

@ -0,0 +1,8 @@
from random import randint
UUID_SIZE = (8, 4, 4, 4, 12)
UUID_GLUE = "-"
def generate():
#[randint(0, 15) for s in UUID_SIZE for _ in range(s)]
return "-".join("".join(format(randint(0, 15), "x") for _ in range(s)) for s in UUID_SIZE)

2
objects/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from . import display
from . import glob

View File

@ -1,24 +1,64 @@
displays = {} from datetime import datetime
windows = {}
tabs = {} from . import glob
from helpers.uuid import generate as generate_uuid
class Display: class Display:
def __init__(self, uuid): def __init__(self, uuid):
if uuid in glob.displays.keys():
raise Exception("Display with this UUID already exists!")
glob.displays[uuid] = self
self.uuid = uuid self.uuid = uuid
self.connections = [] # maybe set()? self.connections = set() # maybe set()?
self.windows = [] # maybe set()? self.windows = [] # maybe set()?
def attach(self, connection): self.title = None
self.connections.append(connection) self.start_time = int( datetime.now().timestamp() )
self.kill_time = self.start_time + glob.config["display"]["timeout"]
def detach(self, connection): def attach(self, sid):
self.connections.remove(connection) self.connections.add(sid)
glob.connections[sid].add(self)
self.kill_time = None
def detach(self, sid):
self.connections.remove(sid)
glob.connections[sid].remove(self)
if len(self.connections) == 0:
self.kill_time = int( datetime.now().timestamp() ) + glob.config["display"]["timeout"]
def serialize(self):
return {
"uuid": self.uuid,
"connections": len(self.connections),
"title": self.title,
"start_time": self.start_time,
"kill_time": self.kill_time
}
def __str__(self):
return self.uuid
@staticmethod
def generate():
while True:
uuid = generate_uuid()
if uuid not in glob.displays.keys():
break
return Display(uuid)
class Window: class Window:
def __init__(self, uuid, def __init__(self, uuid,
title = "__unnamed__", title = "__unnamed__",
grid_column = (0, 0), grid_column = (0, 0),
grid_row = (0, 0)): grid_row = (0, 0)):
if uuid in glob.windows.keys():
raise Exception("Window with this UUID already exists!")
glob.windows[uuid] = self
self.uuid = uuid self.uuid = uuid
self.title = title self.title = title
@ -53,6 +93,10 @@ class Window:
class Tab: class Tab:
def __init__(self, uuid, def __init__(self, uuid,
title = "__unnamed__"): title = "__unnamed__"):
if uuid in glob.tabs.keys():
raise Exception("Tab with this UUID already exists!")
glob.tabs[uuid] = self
self.uuid = uuid self.uuid = uuid
self.content = "" self.content = ""

View File

@ -2,8 +2,13 @@ import json
from flask import Flask from flask import Flask
from flask_socketio import SocketIO from flask_socketio import SocketIO
app = Flask("__main__")
websocket = SocketIO(app)
with open("config.json", "r") as f: with open("config.json", "r") as f:
config = json.load(f) config = json.load(f)
app = Flask("__main__")
websocket = SocketIO(app, **config["socket"])
connections = {}
displays = {}
windows = {}
tabs = {}

View File

@ -3,8 +3,13 @@
{% block extend_head %} {% block extend_head %}
<script src="{{ url_for('static', filename='js/socket.io.js') }}"></script> <script src="{{ url_for('static', filename='js/socket.io.js') }}"></script>
<style> <style>
body { * {
color: #EAEAEA; -webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
} }
display:before { display:before {
@ -53,7 +58,6 @@
} }
displaylist list > a { displaylist list > a {
border: 2px solid #333;
padding: 1em; padding: 1em;
} }
@ -124,11 +128,15 @@
} }
displayList.getElementsByTagName("list")[0].innerHTML = ""; displayList.getElementsByTagName("list")[0].innerHTML = "";
for (item in list) { for (const item of list) {
add(item); add(item);
} }
displayList.hidden = false; displayList.hidden = false;
} }
function addItemToDisplayList(item, onclick) {
}
</script> </script>
{% endblock %} {% endblock %}

View File

@ -2,23 +2,150 @@
{% block extend_head %} {% block extend_head %}
<script src="{{ url_for('static', filename='js/socket.io.js') }}"></script> <script src="{{ url_for('static', filename='js/socket.io.js') }}"></script>
<script>
const socket = io();
let timeout_interval_handle = -1;
socket.on("connect", () => { // ฅ^•ﻌ•^ฅ
socket.emit("display", {action: "list"}, ack = (list) => onDisplayListReceive(list));
});
function onDisplayListReceive(list) {
clearDisplayList();
for (const item of list) {
dlist.appendChild(
createDisplayItem(item.uuid, item.title, item.connections, item.start_time, item.kill_time)
);
}
dwindow.hidden = false;
}
function onDisplayNewReceive(item) {
dlist.appendChild(
createDisplayItem(item.uuid, item.title, item.connections, item.start_time, item.kill_time)
);
}
function clearDisplayList() {
dlist.innerHTML = "";
dlist.appendChild( createDisplayItemNew() );
}
function createDisplayItem(uuid, title, connections, start_time, kill_time) {
let displayItem = document.createElement("li");
displayItem.onclick = () => {
socket.emit("display",
{action: "connect", uuid: uuid}//,
//ack = () => onDisplayNewReceive(uuid)
);
};
displayItem.id = uuid;
displayItem.title = title || uuid;
displayItem.connections = connections;
displayItem.start_time = start_time;
displayItem.kill_time = kill_time;
displayItem.innerHTML = `<pre>Connections: ${displayItem.connections}
Created: ${new Date(displayItem.start_time * 1000).toLocaleString()}` + ( displayItem.kill_time ? `
Timeout: <span class="timeout" data-timeout="${displayItem.kill_time}">Fetching...</span>` : "") + `</pre>`;
// Not actually fetching... just sounded better for the time being ^
if (timeout_interval_handle == -1 && document.getElementsByClassName("timeout")) {
timeout_interval_handle = setInterval(() => {
if (document.getElementsByClassName("timeout").length == 0) {
clearInterval(timeout_interval_handle);
timeout_interval_handle = -1;
} else {
let remove_list = [];
for (const dom of document.getElementsByClassName("timeout")) {
const date = dom.dataset.timeout - Math.floor(Date.now() / 1000);
if (date < 0) {
dom.innerText = "Should be gone by now...";
remove_list.push(dom); // We cant remove this class while iterating using it
} else {
let h = parseInt(date / 3600);
let m = parseInt(date / 60) % 60;
let s = date % 60;
if (h < 10) h = "0" + h;
if (m < 10) m = "0" + m;
if (s < 10) s = "0" + s;
dom.innerText = `${h}:${m}:${s}`;
}
}
for (const dom of remove_list) { // Remove the timeout class
dom.classList.remove("timeout");
}
}
}, 1000);
}
return displayItem;
}
function createDisplayItemNew() {
let displayItem = document.createElement("li");
displayItem.classList.add("new");
displayItem.onclick = () => {
socket.emit("display",
{action: "new"},
ack = (uuid) => onDisplayNewReceive(uuid)
);
};
return displayItem;
}
</script>
<style> <style>
:root {
--color-nav: #333;
--color-background: #242424;
--color-text: #CCC;
--color-border-bottom: #3D3D3D;
--color-border-sides: #525252;
}
* {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
body { body {
color: #EAEAEA; color: var(--color-text);
} }
display:before { .border {
border: 1px solid var(--color-border-sides);
border-top-width: 0;
border-bottom-color: var(--color-border-bottom);
}
.window[title]:before {
content: attr(title); content: attr(title);
background-color: var(--color-nav);
padding: .5em;
border-bottom: 1px solid var(--color-border-bottom);
} }
displaylist:before { .window.list {
content: "Displays";
background-color: #333;
padding: 1em;
}
displaylist {
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
@ -28,127 +155,66 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
background-color: #242424; background-color: var(--color-background);
transition: transform .2s ease, opacity .2s ease; transition: transform .2s ease, opacity .2s ease;
transform: scale(1); transform: scale(1);
opacity: 1; opacity: 1;
} }
displaylist[hidden], displaylist:focus { .window[hidden] {
transform: scale(.96); transform: scale(.96);
opacity: 0; opacity: 0;
} }
scrollcontainer { .scrollable {
display: block; display: block;
overflow: auto; overflow: auto;
} }
displaylist list { ul {
display: grid; display: flex;
gap: 1em; flex-flow: column;
padding-inline-start: 0px;
}
ul li {
display: block;
padding: 1em; padding: 1em;
margin: .5em 1em;
border: 1px solid var(--color-border-sides);
border-top-color: var(--color-nav);
border-bottom-color: var(--color-border-bottom);
} }
displaylist list > a { ul li:hover {
border: 2px solid #333; background-color: var(--color-nav);
padding: 1em;
} }
displaylist fallback { ul li[title]:before {
padding: 1em; content: "Display: " attr(title);
} }
list:not(:empty) + fallback { ul li.new:before {
display: none; content: "+";
} }
fallback { ul li.new {
display: grid; order: 1;
place-content: center;
text-align: center; text-align: center;
gap: 1em;
} }
button {
display: grid;
place-content: center;
place-items: center;
cursor: pointer;
border: 2px solid #333;
color: #919191;
background-color: transparent;
padding: 1em;
transition: .2s ease background-color, .2s ease color;
}
button:hover, button:focus {
background-color: #000;
color: #EAEAEA;
}
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<displaylist hidden> <div class="window list" id="dwindow" title="Displays" hidden>
<scrollcontainer> <div class="scrollable">
<list></list> <ul id="dlist"></ul>
<fallback> </div>
<span> </div>
There is no running displays.<br> <div class="window" hidden></div>
Create new display to continue.
</span>
<button>
<i class="fas fa-plus fa-2x"></i>
<span>New display</span>
</button>
</fallback>
</scrollcontainer>
</displaylist>
<display></display>
<script>
const displayList = document.getElementsByTagName("displaylist")[0];
const display = document.getElementsByTagName("display")[0];
const socket = io();
socket.on("connect", () => {
socket.emit("display", {action: "list"});
});
socket.on("display", (json) => {
switch(json.action) {
default:
console.warn("Unknown data: " + json);
break;
case "list":
onDisplayList(json.value);
break;
}
});
function onDisplayList(list) {
function add(item) {
let displayOption = document.createElement("a");
displayOption.innerText = item;
displayList.getElementsByTagName("list")[0].appendChild(displayOption);
}
displayList.getElementsByTagName("list")[0].innerHTML = "";
for (item in list) {
add(item);
}
displayList.hidden = false;
}
</script>
{% endblock %} {% endblock %}

View File

@ -1,20 +1,49 @@
import json import json
from flask import request
from flask_socketio import emit from flask_socketio import emit
from objects import display from objects.display import Display, Window, Tab
from objects import glob from objects import glob
from helpers import uuid, connection
@glob.websocket.on_error_default # handles all namespaces without an explicit error handler
def default_error_handler(e):
print("ERR:", e)
return 500
@glob.websocket.on("connect") @glob.websocket.on("connect")
def ws_connect(): def ws_connect():
emit("msg", "hi") connection.connect(request.sid)
return "hi"
@glob.websocket.on("disconnect")
def ws_disconnect():
connection.disconnect(request.sid)
@glob.websocket.on("display") @glob.websocket.on("display")
def ws_display(data): def ws_display(data):
def handle_list(_): def handle_list(_):
emit("display", {"action": "list", "value": list( display.displays.keys() )}) return [ item.serialize() for item in glob.displays.values() ]
def handle_new(_):
display = Display.generate()
return display.serialize()
def handle_connect(data):
uuid = data["uuid"]
if uuid not in glob.displays.keys():
raise Exception("Display not found")
glob.displays[uuid].attach(request.sid)
#print(dir(request))
#print(request.sid)
#print("TODO")
switch = { switch = {
"list": handle_list "list": handle_list,
"new": handle_new,
"connect": handle_connect
} }
switch.get(data["action"])(data) return switch.get(data["action"])(data)