Add JS to render display

This commit is contained in:
Stefan Bethke 2025-06-01 13:00:49 +02:00
commit c3316e3086
10 changed files with 610 additions and 29 deletions

View file

@ -1,29 +1,26 @@
import logging
from bottle import Bottle, static_file, TEMPLATE_PATH, jinja2_view
from bottle_log import LoggingPlugin
from bottle_websocket import websocket, GeventWebSocketServer
from geventwebsocket.websocket import WebSocket
from buba.AppConfig import AppConfig
config = AppConfig()
if config.debug:
logging.basicConfig(level=logging.DEBUG)
app = Bottle()
if config.debug:
app.config.update({"logging.level": "DEBUG"})
app.install(LoggingPlugin(app.config))
TEMPLATE_PATH.insert(0, config.templatepath)
app.install(BottleSessions())
auth = BottleOIDC(app, config={
"discovery_url": config.discovery_url,
"client_id": config.client_id,
"client_secret": config.client_secret,
"client_scope": config.oidc_scope,
"user_attr": config.oidc_user_attr,
})
websocket_clients = WebSocketClients()
bottle_helpers = BottleHelpers(auth, group=config.requires_group, allowed=config.allowed)
update_poller = UpdatePoller(websocket_clients, ccujack, 1 if config.debug else 0.1)
# websocket_clients = WebSocketClients()
# bottle_helpers = BottleHelpers(auth, group=config.requires_group, allowed=config.allowed)
# update_poller = UpdatePoller(websocket_clients, ccujack, 1 if config.debug else 0.1)
@app.route("/static/<filepath>")
@ -36,6 +33,7 @@ def server_static(filepath):
def root():
return {}
if __name__ == '__main__':
app.run(host=config.listen_host, port=config.listen_port, server=GeventWebSocketServer, debug=config.debug,
quiet=not config.debug)

427
buba/static/display.js Normal file
View file

@ -0,0 +1,427 @@
export default class {
constructor(container, config = {}) {
this.svgns = "http://www.w3.org/2000/svg";
this.config = Object.assign({
templateSvgUrl: "static/geascript-proportional.svg",
rows: 4,
cols: 180,
stripWidth: 0,
}, config);
console.log("Building display...");
this.container = container;
this.segmentsDom = undefined;
this.defineFont()
fetch(this.config.templateSvgUrl)
.then(res => res.blob())
.then(blob => blob.bytes())
.then(blob => {
let parser = new DOMParser();
this.segmentsDom = parser.parseFromString(new TextDecoder().decode(blob), 'image/svg+xml');
this.buildDisplay()
console.log("Done building display");
})
.catch(err => console.log(err));
}
buildDisplay() {
const svgTemplate = this.segmentsDom.getElementsByTagName("svg")[0];
const segmentsGroup = svgTemplate.getElementById("segments");
const segmentElements = Array.from(segmentsGroup.childNodes).filter(e => e.nodeType === Node.ELEMENT_NODE);
const viewBox = svgTemplate.getAttribute("viewBox").split(" ").map(x => parseFloat(x));
if (this.config.stripWidth === 0) {
this.config.stripWidth = viewBox[2];
}
for (var s of segmentElements) {
s.removeAttribute("style");
}
this.container.innerHTML = "";
for (let r = 1; r <= 4; r++) {
const rowDiv = document.createElement("div");
this.container.appendChild(rowDiv);
rowDiv.id = `geavision__row_${r}`
rowDiv.classList = "geavision geavision__row";
let rowSvg = document.createElementNS(this.svgns, "svg");
rowDiv.appendChild(rowSvg);
rowSvg.classList = "geavision geavision__row geavision__row_svg";
rowSvg.id = `geavision__row_${r}_svg`;
const width = this.config.stripWidth * this.config.cols
rowSvg.setAttribute("viewBox", `0 0 ${parseInt(width)} ${parseInt(viewBox[3])}`);
// rowSvg.setAttribute("width", "320");
// rowSvg.setAttribute("height", "15");
let rect = document.createElementNS(this.svgns, "rect");
for (let c = 1; c <= this.config.cols; c++) {
let g = document.createElementNS(this.svgns, "g");
rowSvg.appendChild(g);
g.id = `geavision__row_${r}_${c}`;
g.setAttribute("transform", `translate(${this.config.stripWidth * (c-1)} 0)`);
// segmentElements must be in the correct order in the SVG
for (let s = 1; s <= segmentElements.length; s++) {
const clone = segmentElements[s - 1].cloneNode(true);
g.appendChild(clone);
clone.id = `geavision__row_${r}_${c}_${s}`;
clone.classList = "gvsoff";
}
}
}
let c = 1
c += this.applyText(1, 1,"@ABCD DCBA")
}
applyCharacter(row, col, char) {
for (let c of this.font[char]) {
for (let s = 0; s < c.length; s++) {
const e = this.container.querySelector(`#geavision__row_${row}_${col}_${s+1}`)
if (e) {
e.classList = c[s] === '#' ? "gvson" : "gvsoff";
} else {
console.log(`Unable to find element #geavision__row_${row}_${col}_${s+1} for segment '${c}'`)
}
}
col++;
}
return this.font[char].length
}
applyText(row, col, text) {
for (let c of text) {
col += this.applyCharacter(row, col, c.codePointAt(0))
}
return col;
}
defineFont() {
let defs = []
// 0x00-0x0f
defs[0x00] = []
defs[0x01] = []
defs[0x02] = []
defs[0x03] = []
defs[0x04] = []
defs[0x05] = []
defs[0x06] = []
defs[0x07] = []
defs[0x08] = []
defs[0x09] = []
defs[0x0a] = []
defs[0x0b] = []
defs[0x0c] = []
defs[0x0d] = []
defs[0x0e] = []
defs[0x0f] = []
// 0x10-0x1f
defs[0x10] = []
defs[0x11] = []
defs[0x12] = []
defs[0x13] = []
defs[0x14] = []
defs[0x15] = []
defs[0x16] = []
defs[0x17] = []
defs[0x18] = []
defs[0x19] = []
defs[0x1a] = []
defs[0x1b] = []
defs[0x1c] = []
defs[0x1d] = []
defs[0x1e] = []
defs[0x1f] = []
// 0x20-0x2f
defs[0x20] = [
'...... ...... ...... ......'
]
defs[0x21] = []
defs[0x22] = []
defs[0x23] = []
defs[0x24] = []
defs[0x25] = []
defs[0x26] = []
defs[0x27] = []
defs[0x28] = []
defs[0x29] = []
defs[0x2a] = []
defs[0x2b] = []
defs[0x2c] = []
defs[0x2d] = []
defs[0x2e] = []
defs[0x2f] = []
// 0x30-0x3f
defs[0x30] = []
defs[0x31] = []
defs[0x32] = []
defs[0x33] = []
defs[0x34] = []
defs[0x35] = []
defs[0x36] = []
defs[0x37] = []
defs[0x38] = []
defs[0x39] = []
defs[0x3a] = []
defs[0x3b] = []
defs[0x3c] = []
defs[0x3d] = []
defs[0x3e] = []
defs[0x3f] = []
// 0x40-0x4f
defs[0x40] = [ // @
'...... .##### ###### ######',
'...... ###... ...... ....##',
'...... ###... .##### #.#.#.',
'...... ###... .#.... ##....',
'...... #.#### ###### ###...',
'...... ...... ...... ......'
]
defs[0x41] = [ // A
'.##### ###### ###### ###...',
'###... ...### #..... ......',
'#.#### ###### ###### ###...',
'...... ...... ...... ......'
]
defs[0x42] = [ // B
'###### ###### ###### ###...',
'###... ...### #..... ###...',
'#.#### #####. ###### ##....',
'...... ...... ...... ......'
]
defs[0x43] = [ // C
'.##### ###### ###### #.#...',
'###... ...... ...... ###...',
'#.#... ...... ...... ##....',
'...... ...... ...... ......'
]
defs[0x44] = [ // D
'###### ###### ###### ###...',
'###... ...... ...... ###...',
'#.#### ###### ###### ##....',
'...... ...... ...... ......'
]
defs[0x45] = []
defs[0x46] = []
defs[0x47] = []
defs[0x48] = []
defs[0x49] = []
defs[0x4a] = []
defs[0x4b] = []
defs[0x4c] = []
defs[0x4d] = []
defs[0x4e] = []
defs[0x4f] = []
// 0x50-0x5f
defs[0x50] = []
defs[0x51] = []
defs[0x52] = []
defs[0x53] = []
defs[0x54] = []
defs[0x55] = []
defs[0x56] = []
defs[0x57] = []
defs[0x58] = []
defs[0x59] = []
defs[0x5a] = []
defs[0x5b] = []
defs[0x5c] = []
defs[0x5d] = []
defs[0x5e] = []
defs[0x5f] = []
// 0x60-0x6f
defs[0x60] = []
defs[0x61] = []
defs[0x62] = []
defs[0x63] = []
defs[0x64] = []
defs[0x65] = []
defs[0x66] = []
defs[0x67] = []
defs[0x68] = []
defs[0x69] = []
defs[0x6a] = []
defs[0x6b] = []
defs[0x6c] = []
defs[0x6d] = []
defs[0x6e] = []
defs[0x6f] = []
// 0x70-0x7f
defs[0x70] = []
defs[0x71] = []
defs[0x72] = []
defs[0x73] = []
defs[0x74] = []
defs[0x75] = []
defs[0x76] = []
defs[0x77] = []
defs[0x78] = []
defs[0x79] = []
defs[0x7a] = []
defs[0x7b] = []
defs[0x7c] = []
defs[0x7d] = []
defs[0x7e] = []
defs[0x7f] = []
// 0x80-0x8f
defs[0x80] = []
defs[0x81] = []
defs[0x82] = []
defs[0x83] = []
defs[0x84] = []
defs[0x85] = []
defs[0x86] = []
defs[0x87] = []
defs[0x88] = []
defs[0x89] = []
defs[0x8a] = []
defs[0x8b] = []
defs[0x8c] = []
defs[0x8d] = []
defs[0x8e] = []
defs[0x8f] = []
// 0x90-0x9f
defs[0x90] = []
defs[0x91] = []
defs[0x92] = []
defs[0x93] = []
defs[0x94] = []
defs[0x95] = []
defs[0x96] = []
defs[0x97] = []
defs[0x98] = []
defs[0x99] = []
defs[0x9a] = []
defs[0x9b] = []
defs[0x9c] = []
defs[0x9d] = []
defs[0x9e] = []
defs[0x9f] = []
// 0xa0-0xaf
defs[0xa0] = []
defs[0xa1] = []
defs[0xa2] = []
defs[0xa3] = []
defs[0xa4] = []
defs[0xa5] = []
defs[0xa6] = []
defs[0xa7] = []
defs[0xa8] = []
defs[0xa9] = []
defs[0xaa] = []
defs[0xab] = []
defs[0xac] = []
defs[0xad] = []
defs[0xae] = []
defs[0xaf] = []
// 0xb0-0xbf
defs[0xb0] = []
defs[0xb1] = []
defs[0xb2] = []
defs[0xb3] = []
defs[0xb4] = []
defs[0xb5] = []
defs[0xb6] = []
defs[0xb7] = []
defs[0xb8] = []
defs[0xb9] = []
defs[0xba] = []
defs[0xbb] = []
defs[0xbc] = []
defs[0xbd] = []
defs[0xbe] = []
defs[0xbf] = []
// 0xc0-0xcf
defs[0xc0] = []
defs[0xc1] = []
defs[0xc2] = []
defs[0xc3] = []
defs[0xc4] = []
defs[0xc5] = []
defs[0xc6] = []
defs[0xc7] = []
defs[0xc8] = []
defs[0xc9] = []
defs[0xca] = []
defs[0xcb] = []
defs[0xcc] = []
defs[0xcd] = []
defs[0xce] = []
defs[0xcf] = []
// 0xd0-0xdf
defs[0xd0] = []
defs[0xd1] = []
defs[0xd2] = []
defs[0xd3] = []
defs[0xd4] = []
defs[0xd5] = []
defs[0xd6] = []
defs[0xd7] = []
defs[0xd8] = []
defs[0xd9] = []
defs[0xda] = []
defs[0xdb] = []
defs[0xdc] = []
defs[0xdd] = []
defs[0xde] = []
defs[0xdf] = []
// 0xe0-0xef
defs[0xe0] = []
defs[0xe1] = []
defs[0xe2] = []
defs[0xe3] = []
defs[0xe4] = []
defs[0xe5] = []
defs[0xe6] = []
defs[0xe7] = []
defs[0xe8] = []
defs[0xe9] = []
defs[0xea] = []
defs[0xeb] = []
defs[0xec] = []
defs[0xed] = []
defs[0xee] = []
defs[0xef] = []
// 0xf0-0xff
defs[0xf0] = []
defs[0xf1] = []
defs[0xf2] = []
defs[0xf3] = []
defs[0xf4] = []
defs[0xf5] = []
defs[0xf6] = []
defs[0xf7] = []
defs[0xf8] = []
defs[0xf9] = []
defs[0xfa] = []
defs[0xfb] = []
defs[0xfc] = []
defs[0xfd] = []
defs[0xfe] = []
defs[0xff] = []
this.font = []
for (let i = 0; i < 256; i++) {
if (defs[i] === undefined) {
this.font[i] = []
continue
}
let f = []
for (let c of defs[i]) {
f.push(c.replace(/[^.#]/g, ""))
}
this.font[i] = f
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

View file

@ -4,7 +4,7 @@
<svg
width="1.7mm"
height="15.7mm"
viewBox="0 0 1.7000001 15.699999"
viewBox="0 0 1.7000001 15.7"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
@ -38,14 +38,8 @@
id="defs1"/>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Segments"><rect
style="display:inline;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.0396;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="seg22"
width="1.6688769"
height="1.0673051"
x="0.019799877"
y="12.866271" />
id="segments"
inkscape:label="Segments">
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.0396;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="M 0.01979999,1.4711941 V 0.0198 h 0.82232429 z"
@ -133,6 +127,13 @@
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.0396;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 1.6820047,11.414717 v 1.451408 H 0.86041373 Z"
id="seg21"/>
<rect
style="display:inline;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.0396;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="seg22"
width="1.6688769"
height="1.0673051"
x="0.019799877"
y="12.866271"/>
<path
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.0396;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="M 1.688677,14.473684 V 13.933575 H 0.01979993 v 1.728568 h 0.8344386 z"

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Before After
Before After

View file

@ -1,7 +1,28 @@
svg.char {
width: 2em;
/*height: 10em;*/
stroke: #ccc;
stroke-width: .1;
fill: black;
#geavision-display {
padding: 1em;
background-color: #222;
}
div.geavision__row {
margin-bottom: 1em;
}
div.geavision__row:last-child {
margin-bottom: 0;
}
svg.geavision__row {
width: 100%;
height: auto;
stroke: #222;
stroke-width: .01;
fill: none;
}
.gvsoff {
fill: none;
}
.gvson {
fill: #4f0;
}

7
buba/static/main.js Normal file
View file

@ -0,0 +1,7 @@
import Display from "./display.js";
let container = document.getElementById("geavision-display");
if (container) {
const d = new Display(container);
// do more stuff
}

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block page_title %}{% endblock %}</title>
<meta name="viewport" content="width=device-width">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
<link rel=stylesheet type="text/css" href="static/main.css">
<script src="static/main.js" type="module"></script>
</head>
<body>
<h1>{{ self.page_title() }}</h1>
{% block page_body %}{% endblock %}
</body>
</html>

View file

@ -0,0 +1,5 @@
{% extends "base.html.j2" %}
{% block page_title %}CCCHH Buba{% endblock %}
{% block page_body %}
<div id="geavision-display">...</div>
{% endblock %}