]> OzVa Git service - shopping-channel/commitdiff
Implemented websockets for the display and HUD pages
authorMax Value <greenwoodw50@gmail.com>
Fri, 25 Jul 2025 18:41:32 +0000 (19:41 +0100)
committerMax Value <greenwoodw50@gmail.com>
Fri, 25 Jul 2025 18:41:32 +0000 (19:41 +0100)
TODO [new file with mode: 0644]
main.py
requirements.txt
static/display.css [new file with mode: 0644]
static/utils.js [new file with mode: 0644]
teleshopping/admin.py
teleshopping/api/data.py
templates/autocue.html
templates/display.html

diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..cc33ad5
--- /dev/null
+++ b/TODO
@@ -0,0 +1,5 @@
+- websockets for the api
+- update the doomsday clock repo
+- better solution to loading static data
+       - potentially in the database?
+       - could open it up for sqlite browser
diff --git a/main.py b/main.py
index abcdd9a5b7dca16369c0e29c965c942f4ba96c11..887cdb35096f4f5af583aed9a36ef7d988f1acb6 100755 (executable)
--- a/main.py
+++ b/main.py
@@ -1,16 +1,17 @@
 #!.venv/bin/python
 
-from flask import Flask
+from flask_socketio import SocketIO, emit
 from flask_httpauth import HTTPBasicAuth
 from datetime import datetime, timezone
 from os import path, environ, system
-from markupsafe import escape
 from flask_cors import CORS
+from flask import Flask,request
 import json
 
 auth = HTTPBasicAuth()
 app = Flask(__name__)
 CORS(app)
+socketio = SocketIO(app, logger=True, engineio_logger=True)
 
 try:
        app.root_path = ROOT = environ["XMDV_PATH"]
@@ -67,5 +68,13 @@ app.route("/api/generate")(docs_generate)
 # utils
 app.before_request(teleshopping.check_database)
 
+# if the socket is connecting for the first time, send data
+@socketio.on('connect')
+def initial():
+       print("conected!")
+       request.method = "internal"
+       emit("apiUpdate", teleshopping.data(), json=True)
+
+
 if __name__ == "__main__":
-       app.run(host='127.0.0.1', port=8000, debug=True)
+       socketio.run(app, host='127.0.0.1', port=8000, debug=True)
index 19c1262e151c7d6a92fb4f3c5f2f8ff34d5949fb..e25dc389848bc7bc696d4602d1e354623ffd14ad 100644 (file)
@@ -1,4 +1,5 @@
 flask_httpauth
+flask_socketio
 markupsafe
 flask_cors
 werkzeug
diff --git a/static/display.css b/static/display.css
new file mode 100644 (file)
index 0000000..c85b420
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+CSS stylesheet for styling the HUD's including the '/hud' page and the
+'/autocue' page
+*/
+
+body {
+       font-family: sans-serif;
+       color: white;
+       background-color: black;
+}
+
+#slider {
+       position: fixed;
+       top: 10px;
+       left: 10px;
+}
+
+#notes, #message {
+       margin: 20px;
+}
+
+#timer {
+       position: absolute;
+       bottom: 0;
+       left: 0;
+       margin: 20px;
+}
+
+#numbers {
+       position: absolute;
+       top: 20px;
+       left: 0;
+       margin: 20px;
+}
+#discountBox {
+       opacity: 0;
+       transition: opacity 1s;
+}
+#discountBox.show {
+       opacity: 1;
+}
diff --git a/static/utils.js b/static/utils.js
new file mode 100644 (file)
index 0000000..828e96f
--- /dev/null
@@ -0,0 +1,16 @@
+export function makeTime(end, offset, strike) {
+       let current = Math.round((Date.now() + offset) / 1000);
+       var time = end - current;
+
+       if (Math.sign(time) == -1) {time = 0;}
+       var minutes = Math.floor(time / 60);
+       var seconds = (time - (minutes * 60));
+
+       var minutesString = minutes.toString().padStart(2, "0");
+       var secondsString = seconds.toString().padStart(2, "0");
+       if (strike) {
+               return `${minutesString}:${secondsString}`;
+       } else {
+               return `<s>${minutesString}:${secondsString}</s>`;
+       }
+}
index 960236871cfca9e68f613f772dc4f490b41b3094..ad1d1d0463f46e61fa85fd29ef1c7fab20b3c4d1 100644 (file)
@@ -1,6 +1,20 @@
+from flask import Flask, Response, request, render_template, send_from_directory
 from math import radians, cos, sin
+from .api.data import data as api
+from flask_socketio import emit
+from markupsafe import escape
+from os import environ
+import sqlite3
+import json
 
 INCREMENT = 18
+ROOT = environ["XMDV_PATH"]
+
+with open(f"{ROOT}/static/static.json", "r", encoding="utf-8") as f:
+       static_data = json.loads(f.read())
+
+with open(f"{ROOT}/static/info.json", "r", encoding="utf-8") as f:
+       static_info = json.loads(f.read())
 
 def admin():
        return Response(render_template("admin.html", mimetype="text/html"))
@@ -31,7 +45,7 @@ def admin_page(page):
 
                                else: aggrigate_data.update({key: post_data[key]})
 
-                       with sqlite3.connect(path.join(app.root_path, "data.db")) as connection:
+                       with sqlite3.connect(f"{ROOT}/data.db") as connection:
                                cursor = connection.cursor()
                                result = cursor.execute("SELECT * FROM pragma_table_info('state');").fetchall()[1:]
 
@@ -47,12 +61,15 @@ def admin_page(page):
                                        WHERE id = 1;
                                        """
 
-                               print(query)
-
                                cursor.execute(query)
                                connection.commit()
 
-               request.method = "internal" # set method to internal so that the api call returns only the python object
+                       request.method = "internal" # set method to internal so that the api call returns only the python object
+                       emit('apiUpdate', api(), broadcast=True, namespace="/")
+
+               else:
+                       request.method = "internal" # set method to internal so that the api call returns only the python object
+
                if page == "clock":
                        coords = [{
                                "i":i,
index d96949a77a563e86d7e529f05c8faa8ce501e784..7be9dc37da17e0b25cf9f013770198f1edc0cef4 100644 (file)
@@ -27,6 +27,9 @@ def data():
        else:
                return "", 404
 
+def handle_message(message):
+    send(message)
+
 def items():
        if request.method == "GET":
                return jsonify(static_data)
index 31caa057d2a84b69437c0b536a5c9c6fa43191c6..cd3e934b284e0791df2f2adf0f2b10ba1503413e 100644 (file)
@@ -4,85 +4,60 @@
                <meta charset="utf-8">
                <title>XMDV Autocue</title>
                <link rel="icon" type="image/x-icon" href="/static/assets/star3.svg">
-               <style>
-body {
-       font-family: sans-serif;
-       color: white;
-       background-color: black;
-}
-#slider {
-       position: fixed;
-       top: 10px;
-       left: 10px;
-}
-#notes, #message {
-       margin: 20px;
-}
-#timer {
-       position: absolute;
-       bottom: 0;
-       left: 0;
-       margin: 20px;
-}
-               </style>
-               <script>
+               <link rel="stylesheet" href="/static/display.css">
+               <script type="module">
 
-function makeTime(end, offset, strike) {
-       let current = Math.round((Date.now() + offset) / 1000);
-       var time = end - current;
+import { io } from "https://cdn.socket.io/4.8.1/socket.io.esm.min.js";
 
-       if (Math.sign(time) == -1) {time = 0;}
-       var minutes = Math.floor(time / 60);
-       var seconds = (time - (minutes * 60));
+/*
+websocket updates when data is sent to the admin pannel, updating the autocue
+this eliminates the ovehead that comes from constantly polling the api.
+on connection, the system also sends the api data.
+*/
 
-       var minutesString = minutes.toString().padStart(2, "0");
-       var secondsString = seconds.toString().padStart(2, "0");
-       if (strike) {
-               return `${minutesString}:${secondsString}`;
-       } else {
-               return `<s>${minutesString}:${secondsString}</s>`;
-       }
-}
+var socket = io('/');
+socket.on('apiUpdate', function (data) {
+       console.info("Recived data, updating...");
+       update(data);
+});
 
-// dynamically resize the UI
-function resize() {
-       var slider = document.getElementById("slider");
+               </script>
+       </head>
+       <body>
+               <input type="range" min="30" max="100" value="45" id="slider">
+               <div style="font-size: 45px;" id="note">{{item.notes|safe}}</div>
+               <div style="font-size: 45px; color: yellow;" id="producer">{{data.note}}</div>
+
+               <script>
+
+// update text when slider changes
+var slider = document.getElementById("slider");
+slider.oninput = function (e) {
        for (let id of ["note", "producer"]) {
-               document.getElementById(id).style.fontSize = `${slider.value}px`;
+               document.getElementById(id).style.fontSize = `${this.value}px`;
        }
 }
 
-function update () {
+// update function called by the websocket handler
+function update ( data ) {
        // fetch the item manifest and chache it
        fetch("./api/items", {cache: "default"})
-                       .then(data => data.json())
-                       .then(data => data.items)
-                       .then(items => {
-                               // fetch the current state without cache
-                               fetch("./api", {cache: "no-store"})
-                                       .then(data => data.json())
-                                       .then(data => {
-                                               let id = data.item_id;
+               .then(items => items.json())
+               .then(items => items.items)
+               .then(items => {
 
-                                               // update the producer note
-                                               const producer = document.getElementById("producer");
-                                               producer.innerHTML = data.note.replaceAll("\n", "<br>");
+                       // update the producer note
+                       const producer = document.getElementById("producer");
+                       producer.innerHTML = data.note.replaceAll("\n", "<br>");
 
-                                               // update the item information
-                                               const note = document.getElementById("note");
-                                               note.innerHTML = items[id].notes;
-                                       })
-                               });
-}
+                       // update the item information
+                       let id = data.item_id;
+                       const note = document.getElementById("note");
+                       note.innerHTML = items[id].notes;
 
-setInterval(resize, 50);
-setInterval(update, 1000);
+               });
+       }
 
                </script>
-       </head>
-       <body onload="resize(); update();">
-               <input type="range" min="30" max="100" value="45" id="slider">
-               <div style="font-size: 50px;" id="note">{{item.notes|safe}}</div>
-               <div style="font-size: 50px; color: yellow;" id="producer">{{data.note}}</div>
        </body>
 </html>
index 7474dfde85ef067df1616c350f21e45bf853ec1c..57be682e0ac3d789b3ca7ef5f803a24218b8a221 100644 (file)
        <head>
                <meta charset="utf-8">
                <title>XMDV Display</title>
-               <style>
-body {
-       font-family: sans-serif;
-       color: white;
-       background-color: black;
-}
-#slider {
-       position: fixed;
-       top: 10px;
-       left: 10px;
-}
-body > div {
-       position: absolute;
-       top: 0;
-       left: 0;
-       margin: 20px;
-}
-#discountBox {
-       opacity: 0;
-       transition: opacity 1s;
-}
-#discountBox.show {
-       opacity: 1;
-}
-               </style>
-               <script>
+               <link rel="icon" type="image/x-icon" href="/static/assets/star3.svg">
+               <link rel="stylesheet" href="/static/display.css">
+       </head>
+       <body style="font-size:80px;">
+               <input type="range" min="30" max="100" value="80" id="slider">
+               <div id="numbers">
+                       T1: <span style="background-color: green;" id="timer_1">00:00</span> -
+                       T2: <span style="background-color: purple;" id="timer_2">00:00</span> -
+                       T3: <span style="background-color: blue;" id="timer_3">00:00</span><br>
+                       Left: <span id="left">0</span> out of <span id="stock">0</span><br>
+                       Start price: <span style="background-color:deeppink" id="price">0.00</span>
+                       &DownArrowBar;<span style="color:orange" id="gallery">00</span>%
+                       &#10504;<span style="color:red" id="cost">00</span>%<br>
+                       <span id="discountBox">Discount: <span style="background-color:darkviolet"><span id="discount">00</span>%</span> (<em>Now <span id="currentPrice" style="background-color:darkviolet;">0.00</span></em>)</span>
+               </div>
+               <script type="module">
 
-function makeTime(end, offset, strike) {
-       let current = Math.round((Date.now() + offset) / 1000);
-       var time = end - current;
+import { makeTime } from "../static/utils.js";
+import { io } from "https://cdn.socket.io/4.8.1/socket.io.esm.min.js";
 
-       if (Math.sign(time) == -1) {time = 0;}
-       var minutes = Math.floor(time / 60);
-       var seconds = (time - (minutes * 60));
+/*
+websocket updates when data is sent to the admin pannel, updating the autocue
+this eliminates the ovehead that comes from constantly polling the api.
+on connection, the system also sends the api data.
+*/
 
-       var minutesString = minutes.toString().padStart(2, "0");
-       var secondsString = seconds.toString().padStart(2, "0");
-       if (strike) {
-               return `${minutesString}:${secondsString}`;
-       } else {
-               return `<s>${minutesString}:${secondsString}</s>`;
-       }
-}
+var socket = io('/');
+socket.on('apiUpdate', function (data) {
+       console.info("Recived data, updating...");
+       update(data);
+});
 
-// dynamically resize the UI
-function resize() {
-       var slider = document.getElementById("slider");
-       document.getElementsByTagName("body")[0].style.fontSize = `${slider.value}px`;
+// update text when slider changes
+var slider = document.getElementById("slider");
+slider.oninput = function (e) {
+       document.getElementsByTagName("body")[0].style.fontSize = `${this.value}px`;
 }
 
 let rep;
-
-function update () {
+function update ( data ) {
        // fetch the item manifest and chache it
        fetch("./api/items", {cache: "default"})
-                       .then(data => data.json())
-                       .then(data => data.items)
-                       .then(items => {
-                               // fetch the current state without cache
-                               fetch("./api", {cache: "no-store"})
-                                       .then(data => data.json())
-                                       .then(data => {
-                                               let id = data.item_id;
+               .then(items => items.json())
+               .then(items => items.items)
+               .then(items => {
+                       let id = data.item_id;
 
-                                               if (data.bool_number) { rep = "7"; }
-                                               else { rep = "#####"; }
+                       if (data.bool_number) { rep = "7"; }
+                       else { rep = "#####"; }
 
-                                               // update the total items and the items sold already
-                                               document.getElementById("left").innerHTML = String(Math.round(
-                                                       items[id].stock_count * (data.percent_remaining / 100)
-                                                       )).replaceAll(rep, "&#9608;");
-                                               document.getElementById("stock").innerHTML = String(items[id].stock_count).replaceAll(rep, "&#9608;");
+                       // update the total items and the items sold already
+                       document.getElementById("left").innerHTML = String(Math.round(
+                               items[id].stock_count * (data.percent_remaining / 100)
+                               )).replaceAll(rep, "&#9608;");
+                       document.getElementById("stock").innerHTML = String(items[id].stock_count).replaceAll(rep, "&#9608;");
 
-                                               // update the timers
-                                               for (let t = 1; t <= 3; t++) {
-                                                       document.getElementById(`timer_${t}`).innerHTML = String(makeTime(
-                                                               data[`end_timer_${t}`],
-                                                               data['timer_offset'],
-                                                               data[`bool_timer_${t}`]
-                                                               )).replaceAll(rep, "&#9608;")
-                                               }
+                       // update the timers
+                       for (let t = 1; t <= 3; t++) {
+                               document.getElementById(`timer_${t}`).innerHTML = String(makeTime(
+                                       data[`end_timer_${t}`],
+                                       data['timer_offset'],
+                                       data[`bool_timer_${t}`]
+                                       )).replaceAll(rep, "&#9608;")
+                       }
 
-                                               // update the discount
-                                               document.getElementById("discount").innerHTML = String(data.discount).replaceAll(rep, "&#9608;");
-                                               document.getElementById("gallery").innerHTML = String(items[id].gallery_price * 100).replaceAll(rep, "&#9608;");
-                                               document.getElementById("cost").innerHTML = String(items[id].cost_price * 100).replaceAll(rep, "&#9608;");
-                                               if (data.discount != 0) {
-                                                       document.getElementById("discountBox").classList.add("show");
-                                               } else {
-                                                       document.getElementById("discountBox").classList.remove("show");
-                                               }
-
-                                               // update the prices
-                                               const price = document.getElementById("price");
-                                               const currentPrice = document.getElementById("currentPrice");
-                                               if (items[id].prefix) {
-                                                       price.innerHTML = `${items[id].currency}${String(items[id].origional_price).replaceAll(rep, "&#9608;")}`;
-                                                       currentPrice.innerHTML = `${items[id].currency}${String(Math.round(items[id].origional_price * data.discount) / 100).replaceAll(rep, "&#9608;")}`;
-                                               } else {
-                                                       price.innerHTML = `${String(items[id].origional_price).replaceAll(rep, "&#9608;")}${items[id].currency}`;
-                                                       currentPrice.innerHTML = `${String(Math.round(items[id].origional_price * data.discount) / 100).replaceAll(rep, "&#9608;")}${items[id].currency}`;
-                                               }
-                                       })
-                               });
-}
+                       // update the discount
+                       document.getElementById("discount").innerHTML = String(data.discount).replaceAll(rep, "&#9608;");
+                       document.getElementById("gallery").innerHTML = String(items[id].gallery_price * 100).replaceAll(rep, "&#9608;");
+                       document.getElementById("cost").innerHTML = String(items[id].cost_price * 100).replaceAll(rep, "&#9608;");
+                       if (data.discount != 0) {
+                               document.getElementById("discountBox").classList.add("show");
+                       } else {
+                               document.getElementById("discountBox").classList.remove("show");
+                       }
 
-setInterval(resize, 50);
-setInterval(update, 1000);
+                       // update the prices
+                       const price = document.getElementById("price");
+                       const currentPrice = document.getElementById("currentPrice");
+                       if (items[id].prefix) {
+                               price.innerHTML = `${items[id].currency}${String(items[id].origional_price).replaceAll(rep, "&#9608;")}`;
+                               currentPrice.innerHTML = `${items[id].currency}${String(Math.round(items[id].origional_price * data.discount) / 100).replaceAll(rep, "&#9608;")}`;
+                       } else {
+                               price.innerHTML = `${String(items[id].origional_price).replaceAll(rep, "&#9608;")}${items[id].currency}`;
+                               currentPrice.innerHTML = `${String(Math.round(items[id].origional_price * data.discount) / 100).replaceAll(rep, "&#9608;")}${items[id].currency}`;
+                       }
+               });
+       }
 
                </script>
-       </head>
-       <body onload="resize(); update();">
-               <input type="range" min="30" max="100" value="95" id="slider">
-               <div>
-                       T1: <span style="background-color: green;" id="timer_1">00:00</span> -
-                       T2: <span style="background-color: purple;" id="timer_2">00:00</span> -
-                       T3: <span style="background-color: blue;" id="timer_3">00:00</span><br>
-                       Left: <span id="left">0</span> out of <span id="stock">0</span><br>
-                       Start price: <span style="background-color:deeppink" id="price">0.00</span>
-                       &DownArrowBar;<span style="color:orange" id="gallery">00</span>%
-                       &#10504;<span style="color:red" id="cost">00</span>%<br>
-                       <span id="discountBox">Discount: <span style="background-color:darkviolet"><span id="discount">00</span>%</span> (<em>Now <span id="currentPrice">0.00</span></em>)</span>
-               </div>
        </body>
 </html>