Compare commits
2 Commits
82a5e4be62
...
9b6ba6df85
Author | SHA1 | Date | |
---|---|---|---|
9b6ba6df85 | |||
17d14d45ae |
|
@ -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
2
helpers/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
from . import uuid
|
||||||
|
from . import connection
|
10
helpers/connection.py
Normal file
10
helpers/connection.py
Normal 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
8
helpers/uuid.py
Normal 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
2
objects/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
from . import display
|
||||||
|
from . import glob
|
|
@ -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):
|
||||||
self.uuid = uuid
|
if uuid in glob.displays.keys():
|
||||||
self.connections = [] # maybe set()?
|
raise Exception("Display with this UUID already exists!")
|
||||||
self.windows = [] # maybe set()?
|
glob.displays[uuid] = self
|
||||||
|
|
||||||
def attach(self, connection):
|
|
||||||
self.connections.append(connection)
|
|
||||||
|
|
||||||
def detach(self, connection):
|
self.uuid = uuid
|
||||||
self.connections.remove(connection)
|
self.connections = set() # maybe set()?
|
||||||
|
self.windows = [] # maybe set()?
|
||||||
|
|
||||||
|
self.title = None
|
||||||
|
self.start_time = int( datetime.now().timestamp() )
|
||||||
|
self.kill_time = self.start_time + glob.config["display"]["timeout"]
|
||||||
|
|
||||||
|
def attach(self, sid):
|
||||||
|
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 = ""
|
||||||
|
|
||||||
|
|
|
@ -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 = {}
|
||||||
|
|
|
@ -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 %}
|
|
@ -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;
|
||||||
|
|
||||||
displaylist list > a {
|
|
||||||
border: 2px solid #333;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
displaylist fallback {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
list:not(:empty) + fallback {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
fallback {
|
|
||||||
display: grid;
|
|
||||||
|
|
||||||
place-content: center;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
place-items: center;
|
|
||||||
|
|
||||||
cursor: pointer;
|
border: 1px solid var(--color-border-sides);
|
||||||
|
border-top-color: var(--color-nav);
|
||||||
border: 2px solid #333;
|
border-bottom-color: var(--color-border-bottom);
|
||||||
|
|
||||||
color: #919191;
|
|
||||||
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
padding: 1em;
|
|
||||||
|
|
||||||
transition: .2s ease background-color, .2s ease color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover, button:focus {
|
ul li:hover {
|
||||||
background-color: #000;
|
background-color: var(--color-nav);
|
||||||
color: #EAEAEA;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul li[title]:before {
|
||||||
|
content: "Display: " attr(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li.new:before {
|
||||||
|
content: "+";
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li.new {
|
||||||
|
order: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
</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 %}
|
39
websocket.py
39
websocket.py
|
@ -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)
|
Loading…
Reference in New Issue
Block a user