WSGI functional
authorMax Value <greenwoodw50@gmail.com>
Wed, 5 Mar 2025 03:07:52 +0000 (03:07 +0000)
committerMax Value <greenwoodw50@gmail.com>
Wed, 5 Mar 2025 03:07:52 +0000 (03:07 +0000)
+ added all admin panels
~ fixed main gfx page to work with new system
~ schema is now vital to database sanity check
~ moved generate_docs.py to root for merge
- removed all PHP
- removed database from git

still to-do:
+ add 3 more discounts
+ more different fonts
~ make price and discount louder
~ balnce GFX
~ color testing
~ stress test with set design team
~ fix ebook system (low)

16 files changed:
.gitignore
auth/clock.php [deleted file]
auth/generate_docs.py [deleted file]
auth/index.php [deleted file]
autocue/index.html [deleted file]
generate_docs.py [new file with mode: 0755]
requirements.txt [new file with mode: 0644]
schema
teleshopping.py
templates/admin.html [new file with mode: 0644]
templates/clock.html [new file with mode: 0644]
templates/docs.html [new file with mode: 0644]
templates/gfx.html
templates/price.html [new file with mode: 0644]
templates/text.html [new file with mode: 0644]
templates/timer.html [new file with mode: 0644]

index 2b4f1d77a87aeab1f7385ab5280b97227ef4cde8..e3e7c2c00e602de84453a0d77d60961e2ad589a8 100644 (file)
@@ -1 +1,2 @@
 *.wsgi
+data.db
diff --git a/auth/clock.php b/auth/clock.php
deleted file mode 100644 (file)
index f1e9b9d..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-/*
-
-XMDV TELESHOPPING GFX INTERFACE
-clock pannel
-
-*/
-#error_reporting(E_ALL);
-#ini_set('display_errors', 'On');
-
-# read the file from the server
-$file = fopen("../clock.json", "r") or die("unable to open file!");
-$data = fread($file, filesize("../clock.json"));
-fclose($file);
-$clock = json_decode($data, true);
-
-# setup the increment for the clock positions
-$clockIncrement = 18; # deg
-
-# if getting a POST request to update the data
-if (!empty($_POST['update'])) {
-    # replace required data with the POST data
-    $clock['movementSpeed'] = (int)$_POST['movementSpeed'];
-    $clock['currentPosition'] = (int)$_POST["doomsday"];
-
-    # write the data to the JSON file
-    $json = json_encode($clock);
-    $file = fopen("../clock.json", "w");
-    fwrite($file, $json);
-    fclose($file);
-}
-?>
-
-<html>
-<head>
-    <title>XMDV</title>
-    <style>
-.split {
-    display: inline-block;
-    width: calc(50% - 20px);
-    margin: 10px;
-    vertical-align: top;
-}
-/*
-clock styling
-    - makes div square
-    - positions center pannel in the middle of the clock
-    - positions radio buttons around the clock (PHP)
-*/
-/*
-media query to make sure the clock is always displayed well
-*/
-@media (orientation: portrait) {
-    :root {
-        font-size: 2em;
-    }
-    input {
-        font-size: 1em;
-    }
-    input[type="submit"] {
-        width: 100%;
-        aspect-ratio: 4
-    }
-    .clock {
-        position: relative;
-        width: 100%;
-        aspect-ratio: 1;
-    }
-    .clockButton {
-        position: absolute;
-        width: 2em;
-        height: 2em;
-        margin: 0;
-    }
-    <?php
-    for ($i = 0; $i < 360; $i += $clockIncrement) {
-        $radius = 45;
-        $x = $radius * cos(deg2rad($i - 90)) + 50;
-        $y = $radius * sin(deg2rad($i - 90)) + 50;
-        echo ".angle$i {top: calc($y% - 1em); left: calc($x% - 1em);}";
-    }
-    ?>
-}
-@media (orientation: landscape) {
-    .clock {
-        position: relative;
-        height: 70vh;
-        aspect-ratio: 1;
-    }
-    .clockButton {
-        position: absolute;
-        width: 1.5em;
-        height: 1.5em;
-        margin: 0;
-    }
-    <?php
-    for ($i = 0; $i < 360; $i += $clockIncrement) {
-        $radius = 45;
-        $x = $radius * cos(deg2rad($i - 90)) + 50;
-        $y = $radius * sin(deg2rad($i - 90)) + 50;
-        echo ".angle$i {top: calc($y% - 0.75em); left: calc($x% - 0.75em);}";
-    }
-    ?>
-}
-
-
-    </style>
-</head>
-<body>
-<form action="./clock.php" method="POST">
-<fieldset>
-    <legend>Doomsday clock <em>(Incr. <?php echo $clockIncrement;?> deg)</em></legend>
-    <div class=clock>
-        <svg class='clockFace' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'><circle r='45' cx='50' cy='50' fill='none' stroke='#a0a0a0' stroke-width='0.15px'/></svg>
-
-<?php
-for ($i = 0; $i < 360; $i += $clockIncrement) {
-    $checked = "";
-    if ($clock['currentPosition'] == $i) {
-        $checked = " checked='checked' style='outline: 2px solid red;'";
-    }
-    echo "<input type='radio' class='clockButton angle$i' value='$i' name='doomsday'$checked>";
-}
-?>
-
-    </div>
-    </fieldset>
-    <fieldset>
-        <legend>Movement</legend>
-        <input type='number' name='movementSpeed' id='movementSpeed' value='<?php echo $clock['movementSpeed']; ?>'>
-        <label for='movementSpeed'> Movement speed (ms/tick)</label><br>
-        <div class="split">
-            <input type='radio' value='ease' name='function'>
-            <label>Ease function</label>
-        </div><div class="split">
-            <input type='radio' value='linear' name='function' checked='checked'>
-            <label>Linear function</label>
-        </div>
-    </fieldset>
-<input type="submit" name="update" value="Update">
-</form>
-</body>
-</html>
diff --git a/auth/generate_docs.py b/auth/generate_docs.py
deleted file mode 100755 (executable)
index e7010e3..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-#!.venv/bin/python
-
-from pylatex import Command, Document, Section, Tabular, NewPage, NewLine
-from pylatex.utils import NoEscape, bold
-import json
-import os
-
-# clean up working-directory
-for filename in os.listdir("../docs/epub"):
-       if filename[:4] == "item": os.remove("../docs/epub/" + filename)
-
-# generate item pages
-with open("../items.json", "r") as f:
-       items = json.loads(f.read())
-
-for (i, item) in enumerate(items):
-       with open(f"../docs/epub/item{i}.html", "w") as f:
-               f.write(f"""
-       <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
-               "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-       <html xmlns="http://www.w3.org/1999/xhtml">
-       <head>
-               <title>{item['code']}</title>
-       </head>
-       <body>
-               <h2>{item['code']}</h2>
-               <p><em>{item['subtext']}</em></p>
-               <h4>Description</h4>
-               <p>{item['description']}</p>
-               <table>
-                       <tr>
-                               <th>Rating</th>
-                               <td>{item['rating']}</td>
-                       </tr>
-                       <tr>
-                               <th>Starting price</th>
-                               <td>{item['origionalPrice']}</td>
-                       </tr>
-                       <tr>
-                               <th>In stock</th>
-                               <td>{item['stockCount']}</td>
-                       </tr>
-               </table>
-               <h4>Anchor notes:</h4>
-               <p>{item['notes']}</p>
-               <h4>Crew notes:</h4>
-               <p>{item['crew_notes']}</p>
-       </body>
-       </html>
-       """)
-
-# update manifest
-manifest_items = " ".join([
-       f"<item id='item{i}' href='item{i}.html' media-type='application/xhtml+xml' />"
-       for i in range(len(items))
-       ])
-toc_items = " ".join([
-       f"<itemref idref='item{i}' />"
-       for i in range(len(items))
-       ])
-
-with open("../docs/epub/content.opf", "w") as f:
-       f.write(f"""
-<?xml version="1.0"?>
-
-<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="dcidid"
-       version="2.0">
-
-<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"
-       xmlns:dcterms="http://purl.org/dc/terms/"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:opf="http://www.idpf.org/2007/opf">
-
-       <dc:title>XMDV Teleshopping item manifest</dc:title>
-       <dc:language xsi:type="dcterms:RFC3066">en</dc:language>
-       <dc:identifier id="dcidid" opf:scheme="URI">
-               http://data.ozva.co.uk/shopping/docs/item_manifest.epub
-               </dc:identifier>
-       <dc:subject>Teleshopping, Manifest</dc:subject>
-       <dc:description>Describing all items to be sold during the XMDV Teleshopping performance</dc:description>
-       <dc:creator>William Greenwood</dc:creator>
-       <dc:publisher>Goodnight Publishing</dc:publisher>
-       <dc:date xsi:type="dcterms:W3CDTF">2024-12-28</dc:date>
-       <dc:rights>Creative Commons BY-SA 3.0 License.</dc:rights>
-</metadata>
-
-<manifest>
-       <item id="ncx"      href="toc.ncx"
-               media-type="application/x-dtbncx+xml" />
-       <item id="css"      href="stylesheet.css"
-               media-type="text/css" />
-       <item id="title"    href="title_page.html"
-               media-type="application/xhtml+xml" />
-       <item id="intro"    href="info_page.html"
-               media-type="application/xhtml+xml" />
-       {manifest_items}
-</manifest>
-
-<spine toc="ncx">
-       <itemref idref="title" />
-       <itemref idref="intro" />
-       {toc_items}
-</spine>
-
-</package>
-""")
-
-toc_items = " ".join([
-       f"""
-<navPoint id="navPoint-{i+3}" playOrder="{i+3}">
-       <navLabel>
-               <text>{i+1}: {item["code"]}</text>
-       </navLabel>
-       <content src="item{i}.html"/>
-</navPoint>
-       """
-       for (i, item) in enumerate(items)
-       ])
-
-with open("../docs/epub/toc.ncx", "w") as f:
-       f.write(f"""
-<?xml version="1.0"?>
-<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN"
-       "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
-
-<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
-
-<head>
-       <meta name="dtb:uid" content="http://data.ozva.co.uk/shopping/docs/item_manifest.epub"/>
-       <meta name="dtb:depth" content="2"/>
-       <meta name="dtb:totalPageCount" content="0"/>
-       <meta name="dtb:maxPageNumber" content="0"/>
-</head>
-
-<docTitle>
-       <text>XMDV Teleshopping item manifest</text>
-</docTitle>
-
-<navMap>
-       <navPoint id="navPoint-1" playOrder="1">
-               <navLabel>
-                       <text>Title Page</text>
-               </navLabel>
-               <content src="title_page.html"/>
-       </navPoint>
-       <navPoint id="navPoint-2" playOrder="2">
-               <navLabel>
-                       <text>Information</text>
-               </navLabel>
-               <content src="info_page.html"/>
-       </navPoint>
-       {toc_items}
-</navMap>
-
-</ncx>
-""")
-
-os.system("""
-       zip -X0 ../docs/item_manifest.epub ../docs/epub/mimetype &&
-       zip -Xur9D ../docs/item_manifest.epub ../docs/epub/* &&
-       ebook-convert ../docs/item_manifest.epub ../docs/item_manifest.mobi
-       """)
-
-os.system(f"pandoc -f html ../docs/epub/info_page.html -t latex -o ../docs/pdf/info.tex")
-for i in range(len(items)):
-       os.system(f"pandoc -f html ../docs/epub/item{i}.html -t latex -o ../docs/pdf/item{i}.tex")
-
-geometry_options = {
-       "tmargin": "3cm",
-       "bmargin": "2cm",
-       "lmargin": "1.5cm",
-       "rmargin": "1.5cm"
-               }
-doc = Document(
-       "XMDV Teleshopping item manifest", geometry_options=geometry_options,
-       textcomp = True
-       )
-doc.preamble.append(NoEscape(r"\usepackage{longtable}"))
-doc.preamble.append(NoEscape(r"\usepackage{booktabs}"))
-doc.preamble.append(NoEscape(r"\usepackage{hyperref}"))
-doc.preamble.append(NoEscape(r"\usepackage{xcolor}"))
-
-doc.preamble.append(NoEscape(r"\def\tightlist{}"))
-
-doc.preamble.append(Command("title", "XMDV Teleshopping item manifest"))
-doc.preamble.append(Command("author", "William Greenwood"))
-doc.preamble.append(Command("date", NoEscape(r"\today")))
-
-doc.append(NoEscape(r"""
-\maketitle
-\begin{center}
-\textcolor{red}{\large{}Contains HIDDEN INFORMATION. NOT to be shared with anchor before shoot.}
-\end{center}
-\tableofcontents
-\newpage
-                                       """))
-
-with open(f"../docs/pdf/info.tex", "r") as f:
-       doc.append(NoEscape(f.read().encode('latin-1', 'ignore').decode('utf-8')))
-
-doc.append(NoEscape(r"""
-       \vspace*{\fill}
-       \section{Items}
-       \newpage
-       """))
-
-for i in range(len(items)):
-       with open(f"../docs/pdf/item{i}.tex", "r") as f:
-               doc.append(NoEscape(f.read().encode('latin-1', 'ignore').decode('utf-8')))
-
-       doc.append(NoEscape(r"\newpage"))
-
-doc.generate_tex("../docs/pdf/item_manifest")
-doc.generate_pdf("../docs/item_manifest", silent=False)
diff --git a/auth/index.php b/auth/index.php
deleted file mode 100644 (file)
index 2889073..0000000
+++ /dev/null
@@ -1,503 +0,0 @@
-<?php
-error_reporting(E_ALL);
-ini_set('display_errors', 'On');
-
-$file = fopen("../items.json", "r") or die("unable to open file!");
-$items = fread($file, filesize("../items.json"));
-fclose($file);
-$items = json_decode($items, true);
-
-$file = fopen("../text.json", "r") or die("unable to open file!");
-$text = fread($file, filesize("../text.json"));
-fclose($file);
-$text = json_decode($text, true);
-
-$file = fopen("../data.json", "r") or die("unable to open file!");
-$data = fread($file, filesize("../data.json"));
-fclose($file);
-$data = json_decode($data, true);
-
-$file = fopen("../note", "r") or die("unable to open file!");
-$note = fread($file, filesize("../note"));
-fclose($file);
-
-$clockIncrement = 18;
-
-if (!empty($_POST['generate_docs'])) {
-       exec("./generate_docs.py");
-}
-
-if (!empty($_POST['update'])) {
-
-       $data['showingMain'] = var_export(!empty($_POST['main']), true);
-       $data['showingAll'] = var_export(!empty($_POST['all']), true);
-       $data['showingExtra'] = var_export(!empty($_POST['extra']), true);
-       $data['showingTimer1'] = var_export(!empty($_POST['timer1']), true);
-       $data['showingTimer2'] = var_export(!empty($_POST['timer2']), true);
-       $data['showingTimer3'] = var_export(!empty($_POST['timer3']), true);
-       $data['timer3Main'] = var_export(!empty($_POST['timer3Main']), true);
-       $data['showingBanner'] = var_export(!empty($_POST['banner']), true);
-       $data['showingSigil'] = [
-               var_export(!empty($_POST['sigilNE']), true),
-               var_export(!empty($_POST['sigilSE']), true),
-               var_export(!empty($_POST['sigilSW']), true),
-               var_export(!empty($_POST['sigilNW']), true)
-       ];
-       $data['round'] = var_export(!empty($_POST['round']), true);
-
-       if (!empty($_POST['allChange'])) {
-               if ($_POST['allChange'] == "on") {
-                       $data['showingSigil'] = ["true", "true", "true", "true"];
-               }
-               else if ($_POST['allChange'] == "off") {
-                       $data['showingSigil'] = ["false", "false", "false", "false"];
-               }
-       }
-
-       $data['timerOffset'] = (int)$_POST['timerOffset'];
-       $data['priceChange'] = (int)$_POST['priceChange'];
-       $data['discount'] = (int)$_POST['discount'];
-       $data['percentLeft'] = (int)$_POST['percentLeft'];
-       $data['itemId'] = (int)$_POST['item'];
-       $data['prefix'] = $_POST['prefix'];
-
-       $data['currency']['prefix'] = $_POST['prefixString'];
-       $data['currency']['postfix'] = $_POST['postfixString'];
-
-       $getTopText = function ($x) use ($text) {return $text['topText'][(int)$x];};
-       $getBottomText = function ($x) use ($text) {return $text['bottomText'][(int)$x];};
-
-       if (!empty($_POST['topText'])) {
-               $data['topText'] = array_map($getTopText, $_POST['topText']);
-       } else {
-               $data['topText'] = [];
-       }       
-       if (!empty($_POST['bottomText'])) {
-               $data['bottomText'] = array_map($getBottomText, $_POST['bottomText']);
-               
-       } else {
-               $data['bottomText'] = [];       
-       }
-
-       $data['bannerText'] = $text['bannerText'][(int)$_POST['bannerText']];
-
-       if (!empty($_POST['addTime1'])) {
-               $minutes = (int)substr($_POST['addTime1'], 0, 2);
-               $seconds = (int)substr($_POST['addTime1'], 2, 4) + ($minutes * 60);
-               $data['timer1End'] = time() + $seconds;
-       }
-       if (!empty($_POST['addTime2'])) {
-               $minutes = (int)substr($_POST['addTime2'], 0, 2);
-               $seconds = (int)substr($_POST['addTime2'], 2, 4) + ($minutes * 60);
-               $data['timer2End'] = time() + $seconds;
-       }
-       if (!empty($_POST['addTime3'])) {
-               $minutes = (int)substr($_POST['addTime3'], 0, 2);
-               $seconds = (int)substr($_POST['addTime3'], 2, 4) + ($minutes * 60);
-               $data['timer3End'] = time() + $seconds;
-       }
-
-       if (!empty($_POST['notes'])) {
-               $note = $_POST['notes'];
-       } else {
-               $note = "";
-       }
-
-       $file = fopen("../note", "w");
-       fwrite($file, $note);
-       fclose($file);
-
-       $json = json_encode($data);
-       $file = fopen("../data.json", "w");
-       fwrite($file, $json);
-       fclose($file);
-}
-?>
-<html>
-       <head>
-               <style>
-h1 {
-       margin: 0;
-}
-.split {
-       display: inline-block;
-       width: calc(50% - 20px);
-       margin: 10px;
-       vertical-align: top;
-}
-.third {
-       display: inline-block;
-       width: calc((100% / 3) - 20px);
-       margin: 10px;
-       vertical-align: top;
-}
-.entry {
-       width: 50%;
-       margin: 0;
-       white-space: nowrap;
-       overflow: hidden;
-       text-overflow: ellipsis;
-}
-fieldtext > .split {
-       width: 50%;
-       margin: 0;
-}
-.clock {
-       position: relative;
-       width: 100%;
-       aspect-ratio: 1;
-}
-.clock > div {
-       position: absolute;
-       top: 25%;
-       left: 25%;
-       width: 50%;
-       height: 50%;
-       overflow: scroll;
-}
-.clockButton {
-       position: absolute;
-       margin: 0;
-}
-<?php
-for ($i = 0; $i < 360; $i += $clockIncrement) {
-       $radius = 45;
-       $x = $radius * cos(deg2rad($i - 90)) + 50;
-       $y = $radius * sin(deg2rad($i - 90)) + 50;
-       echo ".angle$i {top: calc($y% - 7px); left: calc($x% - 7px);}";
-}
-?>
-@media screen and (max-width: 1130px) {
-       .split {
-               width: calc(100% - 20px);
-       }
-}
-               </style>
-       </head>
-       <body>
-               <form action="./index.php" method="POST">
-               <div class="third">
-                       <div class="split entry">
-                               <h1>XMDV Interface</h1>
-                       </div><div class="split entry">
-                               <p style="margin: 0; text-align: right;">
-                                       XMDV Shopping is created by William Greenwood<br>
-                                       <a href="../">GFX Overlay</a> - <a href="../autocue/">Autocue</a> - <a href="https://git.ozva.co.uk/?p=shopping-channel">Source repository</a>
-                               </p>
-                       </div>
-                       <fieldset>
-                               <legend>Products</legend>
-                               <div class='split'>
-<?php
-for ($i = 0; $i < count($items); $i++) {
-       $subtext = $items[$i]["subtext"];
-       $checked = "";
-
-       if ($data['itemId'] == $i) {
-               $checked = " checked='checked' ";
-       }
-       
-       echo "<input type='radio' id='$i' value='$i' name='item'$checked>";
-       echo "<label for='$i'> $subtext</label><br>";
-}
-$checkedExtra = "";
-if ($data['showingExtra'] == "true") {
-       $checkedExtra = " checked='checked' ";
-}
-$checkedMain = "";
-if ($data['showingMain'] == "true") {
-       $checkedMain = " checked='checked' ";
-}
-$checkedAll = "";
-if ($data['showingAll'] == "true") {
-       $checkedAll = " checked='checked' ";
-}
-?>
-                               </div><div class='split'>
-                                       <input type='checkbox' id='all' name='all' value='true'<?php echo $checkedAll; ?>>
-                                       <label for='all'> Showing all components</label><br>
-                                       <input type='checkbox' id='main' name='main' value='true'<?php echo $checkedMain; ?>>
-                                       <label for='main'> Showing product information</label><br>
-                                       <input type='checkbox' id='extra' name='extra' value='true'<?php echo $checkedExtra; ?>>
-                                       <label for='extra'> Showing extra information</label>
-                               </div>
-                       </fieldset>
-
-                       <fieldset>
-                               <legend>Pricing</legend>
-                               <div class='split'>
-
-<?php
-$preChecked = "";
-$postChecked = "";
-if ($data['prefix'] == "true") {
-       $preChecked = " checked='checked' ";
-} else {
-       $postChecked = " checked='checked' ";
-}
-$prefix = $data['currency']['prefix'];
-$postfix = $data['currency']['postfix'];
-
-$checked = "";
-if ($data['round'] == "true") {
-       $checked = " checked='checked' ";
-}
-?>
-
-                                       <input type='radio' id='prefix' name='prefix' value='true'<?php echo $preChecked; ?>>
-                                       <input type='text' id='prefix' name='prefixString' value='<?php echo $prefix; ?>'>
-                                       <label for='prefix'> Prefix</label><br>
-
-                                       <input type='radio' id='postfix' name='prefix' value='false'<?php echo $postChecked; ?>>
-                                       <input type='text' id='postfix' name='postfixString' value='<?php echo $postfix; ?>'>
-                                       <label for='postfix'> Postfix</label><br><br>
-
-                                       <input type='number' name='percentLeft' id='percentLeft' value='<?php echo $data['percentLeft']; ?>'>
-                                       <label for='percentLeft'> Unsold (%)</label>
-
-                               </div><div class='split'>
-
-                                       <input type='number' name='discount' id='discount' value='<?php echo $data['discount']; ?>'>
-                                       <label for='discount'> Price of origional (%)</label><br>
-
-                                       <input type='number' name='priceChange' id='priceChange' value='<?php echo $data['priceChange']; ?>'>
-                                       <label for='priceChange'> Discount change (%/s)</label><br>
-
-                                       <input type='checkbox' id='round' name='round' value='true'<?php echo $checked; ?>>
-                                       <label for='round'> Round prices</label>
-                               </div>
-                       </fieldset>
-
-                       <fieldset>
-                               <legend>Top text</legend>
-<?php
-for ($i = 0; $i < count($text['topText']); $i++) {
-       $line = $text['topText'][$i];
-       $checked = "";
-       if (in_array($line, $data['topText'])) {
-               $checked = " checked='checked' ";
-       }
-       echo "<div class='split entry' style='margin: 0;'><input type='checkbox' id='topText' value='$i' name='topText[]'$checked>";
-       echo "<label for='topText'> $line</label></div>";
-}
-echo "</fieldset>";
-echo "<fieldset><legend>Bottom text</legend>";
-for ($i = 0; $i < count($text['bottomText']); $i++) {
-       $line = $text['bottomText'][$i];
-       $checked = "";
-       if (in_array($line, $data['bottomText'])) {
-               $checked = " checked='checked' ";
-       }
-       echo "<div class='split entry'><input type='checkbox' id='bottomText' value='$i' name='bottomText[]'$checked>";
-       echo "<label for='bottomText'> $line</label></div>";
-}
-echo "</fieldset>";
-
-echo "<fieldset><legend>Notes</legend>";
-echo "<textarea name='notes' rows='10' style='width: 100%;'>";
-echo $note;
-echo "</textarea>";
-echo "</fieldset>";
-
-echo "</div><div class='third'>";
-echo "<fieldset><legend>Banner text</legend>";
-for ($i = 0; $i < count($text['bannerText']); $i++) {
-       $line = $text['bannerText'][$i];
-       $checked = "";
-       if ($data['bannerText'] == $line) {
-               $checked = " checked='checked' ";
-       }
-       echo "<div class='split entry' style='margin: 0;'><input type='radio' id='$i' value='$i' name='bannerText'$checked>";
-       echo "<label for='$i'> $line</label></div>";
-}
-$checked = "";
-if ($data['showingBanner'] == "true") {
-       $checked = " checked='checked' ";
-}
-?>
-                               <input type='checkbox' id='banner' name='banner' value='true'<?php echo $checked; ?>>
-                               <label for='banner'>Show banner</label>
-                       </fieldset>
-
-                       <fieldset>
-                               <legend>Sigil display</legend>
-                               <div class='split'>
-<?php
-$checked = "";
-if ($data['showingSigil'][3] == "true") {
-       $checked = " checked='checked' ";
-}
-echo "<input type='checkbox' id='sigilNW' name='sigilNW' value='true'$checked>";
-echo "<label for='sigilNW'>Top left (NW)</label><br>";
-$checked = "";
-if ($data['showingSigil'][2] == "true") {
-       $checked = " checked='checked' ";
-}
-echo "<input type='checkbox' id='sigilSW' name='sigilSW' value='true'$checked>";
-echo "<label for='sigilSW'>Bottom left (SW)</label><br>";
-
-echo "<input type='radio' id='allOn' value='on' name='allChange'>";
-echo "<label for='allOn'>All on</label><br>";
-
-echo "</div><div class='split'>";
-$checked = "";
-if ($data['showingSigil'][0] == "true") {
-       $checked = " checked='checked' ";
-}
-echo "<input type='checkbox' id='sigilNE' name='sigilNE' value='true'$checked>";
-echo "<label for='sigilNE'>Top right (NE)</label><br>";
-$checked = "";
-if ($data['showingSigil'][1] == "true") {
-       $checked = " checked='checked' ";
-}
-echo "<input type='checkbox' id='sigilSE' name='sigilSE' value='true'$checked>";
-echo "<label for='sigilSW'>Bottom right (SE)</label><br>";
-
-echo "<input type='radio' id='allOff' value='off' name='allChange'>";
-echo "<label for='allOff'>All off</label><br>";
-?>
-                               </div>
-                       </fieldset>
-                       <fieldset>
-                               <legend>Timer</legend>
-<?php
-$timer1Checked = "";
-if ($data['showingTimer1'] == "true") {
-       $timer1Checked = " checked='checked' ";
-}
-$timer2Checked = "";
-if ($data['showingTimer2'] == "true") {
-       $timer2Checked = " checked='checked' ";
-}
-$timer3Checked = "";
-if ($data['showingTimer3'] == "true") {
-       $timer3Checked = " checked='checked' ";
-}
-$timer3MainChecked = "";
-if ($data['timer3Main'] == "true") {
-       $timer3MainChecked = " checked='checked' ";
-}
-$timerOffset = $data["timerOffset"];
-?>
-                               <div class="third">
-                                       <input type='radio' id='0000' value='0000' name='addTime1'>
-                                       <label for='0000'> Reset</label><br>
-                                       <input type='radio' id='0030' value='0030' name='addTime1'>
-                                       <label for='0030'> +00:30</label><br>
-                                       <input type='radio' id='0100' value='0100' name='addTime1'>
-                                       <label for='0100'> +01:00</label><br>
-                                       <input type='radio' id='0200' value='0200' name='addTime1'>
-                                       <label for='0200'> +02:00</label><br>
-                                       <input type='radio' id='0500' value='0500' name='addTime1'>
-                                       <label for='0500'> +05:00</label><br>
-                                       <input type='radio' id='1000' value='1000' name='addTime1'>
-                                       <label for='1000'> +10:00</label><br>
-                                       <input type='checkbox' id='timer1' name='timer1' value='true'<?php echo $timer1Checked; ?>>
-                                       <label for='timer1'> Showing timer</label>
-                               </div><div class="third">
-                                       <input type='radio' id='0000' value='0000' name='addTime2'>
-                                       <label for='0000'> Reset</label><br>
-                                       <input type='radio' id='0030' value='0030' name='addTime2'>
-                                       <label for='0030'> +00:30</label><br>
-                                       <input type='radio' id='0100' value='0100' name='addTime2'>
-                                       <label for='0100'> +01:00</label><br>
-                                       <input type='radio' id='0200' value='0200' name='addTime2'>
-                                       <label for='0200'> +02:00</label><br>
-                                       <input type='radio' id='0500' value='0500' name='addTime2'>
-                                       <label for='0500'> +05:00</label><br>
-                                       <input type='radio' id='1000' value='1000' name='addTime2'>
-                                       <label for='1000'> +10:00</label><br>
-                                       <input type='checkbox' id='timer2' name='timer2' value='true'<?php echo $timer2Checked; ?>>
-                                       <label for='timer2'> Showing timer</label>
-                               </div><div class="third">
-                                       <input type='radio' id='0000' value='0000' name='addTime3'>
-                                       <label for='0000'> Reset</label><br>
-                                       <input type='radio' id='0030' value='0030' name='addTime3'>
-                                       <label for='0030'> +00:30</label><br>
-                                       <input type='radio' id='0100' value='0100' name='addTime3'>
-                                       <label for='0100'> +01:00</label><br>
-                                       <input type='radio' id='0200' value='0200' name='addTime3'>
-                                       <label for='0200'> +02:00</label><br>
-                                       <input type='radio' id='0500' value='0500' name='addTime3'>
-                                       <label for='0500'> +05:00</label><br>
-                                       <input type='radio' id='1000' value='1000' name='addTime3'>
-                                       <label for='1000'> +10:00</label><br>
-                                       <input type='checkbox' id='timer3' name='timer3' value='true'<?php echo $timer3Checked; ?>>
-                                       <label for='timer3'> Showing timer</label>
-                               </div>
-                               <div class="split">
-                                       <input type='checkbox' id='timer3Main' name='timer3Main' value='true'<?php echo $timer3MainChecked; ?>>
-                                       <label for='timer3Main'> Timer 3 focus</label>
-                               </div><div class="split">
-                                       <input type='number' id='timerOffset' name='timerOffset' min='-5000' max='5000' step='1' value='<?php echo $timerOffset; ?>'>
-                                       <label for='timerOffset'> Offset (ms)</label>
-                               </div>
-                       </fieldset>
-                       <fieldset>
-                                                       <legend>Lighting</legend>
-                                                       <div class="split">
-<?php
-for ($i = 0; $i < 5; $i ++) {
-       $checked = "";
-       if ($clock['lightingCues'][$i] == "true") {
-               $checked = " checked='checked'";
-       }
-       echo "<input type='radio' value='$i' name='sceneCue'$checked>";
-       echo "<label>Scene $i</label><br>";
-}
-?>
-                                                       </div><div class="split">
-<?php
-for ($i = 0; $i < 5; $i ++) {
-       $checked = "";
-       if ($clock['lightingCues'][$i + 5] == "true") {
-               $checked = " checked='checked'";
-       }
-       echo "<input type='checkbox' value='true' name='triggerCue$i'$checked>";
-       echo "<label>Trigger $i</label><br>";
-}
-?>
-                                                       </div>
-                       </fieldset>
-               </div><div class="third">
-<?php
-echo "<fieldset><legend>Doomsday clock <em>(Incr. $clockIncrement deg)</em></legend>";
-echo "<div class=clock>";
-echo "<svg class='clockFace' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'><circle r='45' cx='50' cy='50' fill='none' stroke='#a0a0a0' stroke-width='0.15px'/></svg>";
-for ($i = 0; $i < 360; $i += $clockIncrement) {
-       $checked = "";
-       if ($clock['currentPosition'] == $i) {
-               $checked = " checked='checked' style='outline: 2px solid red;'";
-       }
-       echo "<input type='radio' class='clockButton angle$i' value='$i' name='doomsday'$checked>";
-}
-?>
-                                       <div>
-                                               <fieldset>
-                                                       <legend>Movement</legend>
-                                                       <input type='number' name='movementSpeed' id='movementSpeed' value='<?php echo $clock['movementSpeed']; ?>'>
-                                                       <label for='movementSpeed'> Movement speed (ms/tick)</label><br>
-                                                       <div class="split">
-                                                               <input type='radio' value='ease' name='function'>
-                                                               <label>Ease function</label>
-                                                       </div><div class="split">
-                                                               <input type='radio' value='linear' name='function' checked='checked'>
-                                                               <label>Linear function</label>
-                                                       </div>
-                                               </fieldset>
-                                       </div>
-                               </div>
-                       </fieldset>
-                       <br>
-                       <input style="vertical-align: top;" type="submit" name="update" value="Update">
-                       <input style="vertical-align: top;" type="submit" name="generate_docs" value="Generate docs">
-                       <br>
-                       <details style="display: inline-block;">
-                               <summary>Current data</summary>
-<?php echo "<pre>" . var_export($data, true) . "</pre>" ?>
-<?php echo "<pre>" . var_export($clock, true) . "</pre>" ?>
-                       </details>
-               </div>
-               </form>
-       </body>
-</html>
diff --git a/autocue/index.html b/autocue/index.html
deleted file mode 100644 (file)
index 51b0c8f..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-<hmtl>
-       <head>
-               <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;
-}
-#clock {
-       position: absolute;
-       bottom: 0;
-       right: 0;
-       width: 250px;
-       height: 250px;
-       margin: 10px;
-}
-#arrow {
-       transition: transform 1.5s;
-}
-               </style>
-               <script>
-var itemId = 0;
-var unsold = 1;
-
-function makeTime(t, o, s) {
-       let current = Math.round((Date.now() + o) / 1000);
-       var time = t - current;
-       if (Math.sign(time) == -1) {time = 0;}
-       var minutes = Math.floor(time / 60);
-       var seconds = (time - (minutes * 60));
-
-       var minutesString = minutes.toString();
-       var secondsString = seconds.toString();
-       minutesString = minutesString.padStart(2, "0");
-       secondsString = secondsString.padStart(2, "0");
-       if (s == "true") {
-               return `${minutesString}:${secondsString}`;
-       } else {
-               return `<s>${minutesString}:${secondsString}</s>`;
-       }
-}
-
-function frame() {
-       var slider = document.getElementById("slider");
-       var notes = document.getElementById("notes");
-       var message = document.getElementById("message");
-       var timers = document.getElementById("timer");
-
-       notes.style.fontSize = `${slider.value}px`;
-       message.style.fontSize = `${slider.value}px`;
-       timers.style.fontSize = `${slider.value}px`;
-}
-
-function update() {
-       fetch("../data.json", {cache: "no-store"})
-               .then(data => data.json())
-               .then(data => {
-                       itemId = data.itemId;
-                       var timers = document.getElementsByClassName("time");
-                       timers[0].innerHTML = makeTime(data.timer1End, data.timerOffset, data.showingTimer1);
-                       timers[1].innerHTML = makeTime(data.timer2End, data.timerOffset, data.showingTimer2);
-                       timers[2].innerHTML = makeTime(data.timer3End, data.timerOffset, data.showingTimer3);
-                       unsold = data.percentLeft / 100;
-               })
-       fetch("../items.json", {cache: "no-store"})
-               .then(data => data.json())
-               .then(data => {
-                       const noteContainer = document.getElementById("notes");
-                       noteContainer.innerHTML = data[itemId].notes;
-
-                       const left = document.getElementById("left");
-                       const stock = document.getElementById("stock");
-                       left.innerHTML = Math.round(data[itemId].stockCount * unsold);
-                       stock.innerHTML = data[itemId].stockCount;
-
-               })
-       fetch("../clock.json", {cache: "no-store"})
-               .then(data => data.json())
-               .then(data => {
-                       document.getElementById("arrow").style.transform = `rotate(${data.currentPosition}deg)`;
-
-               })
-       fetch("../note", {cache: "no-store"})
-               .then(data => data.text())
-               .then(data => {
-                       const messageContainer = document.getElementById("message");
-                       messageContainer.innerHTML = data.replaceAll("\n", "<br>");
-               })
-}
-
-setInterval(frame, 50);
-setInterval(update, 1000);
-               </script>
-       </head>
-       <body onload="frame(); update();">
-               <input type="range" min="30" max="100" value="45" id="slider">
-               <div style="font-size: 50px;" id="notes">some test text!</div>
-               <div style="font-size: 50px; color: yellow;" id="message">Producer Notes!</div>
-               <div id="timer">
-                       T1: <span style="background-color: red;" class="time"></span> -
-                       T2: <span style="background-color: green;" class="time"></span> -
-                       T3: <span style="background-color: blue; margin-right: 40px;" class="time"></span>
-                       Left: <span id="left"></span>/<span id="stock"></span>
-               </div>
-               <div id="clock">
-                       <img style="width: 100%; margin-right: -100%;" src="../assets/clock.svg"><img id="arrow" style="width: 100%;" src="../assets/arrow.svg">
-               </div>
-       </body>
-</html>
diff --git a/generate_docs.py b/generate_docs.py
new file mode 100755 (executable)
index 0000000..35ff0fe
--- /dev/null
@@ -0,0 +1,162 @@
+#!.venv/bin/python
+
+from pylatex import Command, Document, Section, Tabular, NewPage, NewLine
+from pylatex.utils import NoEscape, bold
+import json
+import os
+
+# clean up working-directory
+for filename in os.listdir("../docs/epub"):
+       if filename[:4] == "item": os.remove("../docs/epub/" + filename)
+
+# generate item pages
+with open("../items.json", "r") as f:
+       items = json.loads(f.read())
+
+for (i, item) in enumerate(items):
+       with open(f"../docs/epub/item{i}.html", "w") as f:
+               f.write(f"""
+       <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+               "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+       <html xmlns="http://www.w3.org/1999/xhtml">
+       <head>
+               <title>{item['code']}</title>
+       </head>
+       <body>
+               <h2>{item['code']}</h2>
+               <p><em>{item['subtext']}</em></p>
+               <h4>Description</h4>
+               <p>{item['description']}</p>
+               <table>
+                       <tr>
+                               <th>Rating</th>
+                               <td>{item['rating']}</td>
+                       </tr>
+                       <tr>
+                               <th>Starting price</th>
+                               <td>{item['origionalPrice']}</td>
+                       </tr>
+                       <tr>
+                               <th>In stock</th>
+                               <td>{item['stockCount']}</td>
+                       </tr>
+               </table>
+               <h4>Anchor notes:</h4>
+               <p>{item['notes']}</p>
+               <h4>Crew notes:</h4>
+               <p>{item['crew_notes']}</p>
+       </body>
+       </html>
+       """)
+
+# update manifest
+manifest_items = " ".join([
+       f"<item id='item{i}' href='item{i}.html' media-type='application/xhtml+xml' />"
+       for i in range(len(items))
+       ])
+toc_items = " ".join([
+       f"<itemref idref='item{i}' />"
+       for i in range(len(items))
+       ])
+
+with open("../docs/epub/content.opf", "w") as f:
+       f.write(f"""
+<?xml version="1.0"?>
+
+<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="dcidid"
+       version="2.0">
+
+<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"
+       xmlns:dcterms="http://purl.org/dc/terms/"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:opf="http://www.idpf.org/2007/opf">
+
+       <dc:title>XMDV Teleshopping item manifest</dc:title>
+       <dc:language xsi:type="dcterms:RFC3066">en</dc:language>
+       <dc:identifier id="dcidid" opf:scheme="URI">
+               http://data.ozva.co.uk/shopping/docs/item_manifest.epub
+               </dc:identifier>
+       <dc:subject>Teleshopping, Manifest</dc:subject>
+       <dc:description>Describing all items to be sold during the XMDV Teleshopping performance</dc:description>
+       <dc:creator>William Greenwood</dc:creator>
+       <dc:publisher>Goodnight Publishing</dc:publisher>
+       <dc:date xsi:type="dcterms:W3CDTF">2024-12-28</dc:date>
+       <dc:rights>Creative Commons BY-SA 3.0 License.</dc:rights>
+</metadata>
+
+<manifest>
+       <item id="ncx"      href="toc.ncx"
+               media-type="application/x-dtbncx+xml" />
+       <item id="css"      href="stylesheet.css"
+               media-type="text/css" />
+       <item id="title"    href="title_page.html"
+               media-type="application/xhtml+xml" />
+       <item id="intro"    href="info_page.html"
+               media-type="application/xhtml+xml" />
+       {manifest_items}
+</manifest>
+
+<spine toc="ncx">
+       <itemref idref="title" />
+       <itemref idref="intro" />
+       {toc_items}
+</spine>
+
+</package>
+""")
+
+toc_items = " ".join([
+       f"""
+<navPoint id="navPoint-{i+3}" playOrder="{i+3}">
+       <navLabel>
+               <text>{i+1}: {item["code"]}</text>
+       </navLabel>
+       <content src="item{i}.html"/>
+</navPoint>
+       """
+       for (i, item) in enumerate(items)
+       ])
+
+with open("../docs/epub/toc.ncx", "w") as f:
+       f.write(f"""
+<?xml version="1.0"?>
+<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN"
+       "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
+
+<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
+
+<head>
+       <meta name="dtb:uid" content="http://data.ozva.co.uk/shopping/docs/item_manifest.epub"/>
+       <meta name="dtb:depth" content="2"/>
+       <meta name="dtb:totalPageCount" content="0"/>
+       <meta name="dtb:maxPageNumber" content="0"/>
+</head>
+
+<docTitle>
+       <text>XMDV Teleshopping item manifest</text>
+</docTitle>
+
+<navMap>
+       <navPoint id="navPoint-1" playOrder="1">
+               <navLabel>
+                       <text>Title Page</text>
+               </navLabel>
+               <content src="title_page.html"/>
+       </navPoint>
+       <navPoint id="navPoint-2" playOrder="2">
+               <navLabel>
+                       <text>Information</text>
+               </navLabel>
+               <content src="info_page.html"/>
+       </navPoint>
+       {toc_items}
+</navMap>
+
+</ncx>
+""")
+
+os.system("""
+       zip -X0 ../docs/item_manifest.epub ../docs/epub/mimetype &&
+       zip -Xur9D ../docs/item_manifest.epub ../docs/epub/* &&
+       ebook-convert ../docs/item_manifest.epub ../docs/item_manifest.mobi
+       """)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644 (file)
index 0000000..09e14b6
--- /dev/null
@@ -0,0 +1,3 @@
+flask_httpauth
+flask
+werkzeug
diff --git a/schema b/schema
index 4ce7e55b195c3781f4f2854049a68aaf63e992d0..24fac1cebdb96f369f5ef94fa0ae67da843f931c 100644 (file)
--- a/schema
+++ b/schema
@@ -1,7 +1,10 @@
 CREATE TABLE state (
        id INTEGER PRIMARY KEY,
        item_id INTEGER,
-       discount REAL,
+       discount_1 REAL,
+       discount_2 REAL,
+       discount_3 REAL,
+       discount_4 REAL,
        discount_change INTEGER,
        percent_remaining INTEGER,
        currency_symbol TEXT,
@@ -37,10 +40,14 @@ CREATE TABLE state (
        movement_function TEXT,
        note TEXT
 );
+
 INSERT INTO state (
        id,
        item_id,
-       discount,
+       discount_1,
+       discount_2,
+       discount_3,
+       discount_4,
        discount_change,
        percent_remaining,
        currency_symbol,
@@ -74,4 +81,4 @@ INSERT INTO state (
        current_position,
        movement_speed,
        movement_function,
-       note) VALUES (1, 0, 0.0, 50, 100, '£', 0, '[0,1]', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 40, 'linear', 'test note');
+       note) VALUES (1, 0, 0.0, 0.0, 0.0, 0.0, 50, 100, '£', 0, '[0,1]', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 40, 'linear', 'test note');
index e234dc8b367e10e9b04ca097a6598d97bc48ef3f..686e1fb741d45f00cfc17d0e6881245aa27598f2 100755 (executable)
@@ -1,13 +1,40 @@
 #!.venv/bin/python
 
+from werkzeug.security import generate_password_hash, check_password_hash
 from flask import Flask, Response, request, render_template, jsonify
+from flask_httpauth import HTTPBasicAuth
+from datetime import datetime, timezone
+from math import radians, cos, sin
+from ast import literal_eval
 from os import path
 import sqlite3
 import json
-from ast import literal_eval
+
+INCREMENT = 18
 
 app = Flask(__name__)
+auth = HTTPBasicAuth()
+
+# get the secrets ready
+with open(path.join(app.root_path, "..", "secrets.json"), "r") as f:
+       users = json.loads(f.read())
+users = {k: generate_password_hash(v) for (k, v) in users.items()}
+
+# get the static data ready
+with open(path.join(app.root_path, "static", "static.json"), "r") as f:
+       static_data = json.loads(f.read())
+
+
 
+
+# make the password verifier function
+@auth.verify_password
+def verify_password(username, password):
+    if username in users and \
+            check_password_hash(users.get(username), password):
+        return username
+
+# database helper function
 def database(field: str | list):
        query = f"SELECT {','.join(field) if type(field) == list else field} FROM state WHERE id=1;"
 
@@ -19,28 +46,100 @@ def database(field: str | list):
 
        return dict(zip(column_names, result)) # combine column names with data into dict
 
+
+
+
 @app.route("/")
 def gfx_main():
        return Response(render_template("gfx.html"), mimetype="text/html")
 
 @app.route("/<string:page>")
 def gfx_page(page):
-       if page in ["autocue", "docs"]:
+       if page in ["autocue"]:
                return Response(render_template(f"{page}.html"), mimetype="text/html")
        else:
                return "", 404
 
+
+
+
 @app.route("/admin")
+@auth.login_required
 def admin_main():
-       ...
+       return Response(render_template("admin.html", mimetype="text/html"))
 
-@app.route("/admin/<string:page>")
+@app.route("/admin/<string:page>", methods=['GET', 'POST'])
+@auth.login_required
 def admin_page(page):
-       ...
+       if page in ["text", "clock", "timer", "price"]:
+               if request.method == "POST": # if posting with data update the database then return the new admin page
+
+                       post_data = request.form.to_dict()
+                       aggrigate_data = {}
+
+                       # perform all data clean up for special cases
+                       aggrigate_data = {}
+                       for key in post_data:
+                               # if a key begins with the word "aggrigate": aggrigate it with all other keys with the same body into a list
+                               if key[:9] == "aggrigate":
+                                       new_key = key[10:-5]
+                                       if new_key in aggrigate_data:
+                                               aggrigate_data[new_key].append(int(post_data[key]))
+                                       else:
+                                               aggrigate_data.update({new_key: [int(post_data[key])]})
+
+                               # if the key is a timer, make the time fixed to the epoch instead of relative (& account for offset)
+                               if key[:3] == "end":
+                                       aggrigate_data.update(
+                                               {key: int(datetime.now(timezone.utc).timestamp()) + int(post_data[key])}
+                                               )
+
+                               else: aggrigate_data.update({key: post_data[key]})
+
+                       with sqlite3.connect(path.join(app.root_path, "data.db")) as connection:
+                               cursor = connection.cursor()
+                               result = cursor.execute("SELECT * FROM pragma_table_info('state');").fetchall()[1:]
+
+                               types = {"TEXT":str, "INTEGER":int, "REAL":float}
+                               type_note = {c[1]:types[c[2]] for c in result}
+
+                               valid_data = {k: type_note[k].__call__(aggrigate_data[k]) for k in aggrigate_data if k in type_note}
+
+                               query = f"""
+                                       UPDATE state SET
+                                       {', '.join([f'{k} = \'{v}\'' if type(v) == str else f'{k} = {v}' for (k,v) in valid_data.items()])}
+                                       WHERE id = 1;
+                                       """
+
+                               cursor.execute(query)
+                               connection.commit()
+
+               request.method = "internal" # set method to internal so that the api call returns only the python object
+               if page == "clock":
+                       coords = [{
+                               "i":i,
+                               "x":45*cos(radians(i-90))+50,
+                               "y":45*sin(radians(i-90))+50
+                               } for i in list(range(0, 360, INCREMENT))]
+                       return Response(render_template("clock.html", data=api(), positions=coords), mimetype="text/html")
+
+               if page == "text":
+                       return Response(render_template("text.html", data=api(),  text=static_data['text']), mimetype="text/html")
+
+               if page == "timer":
+                       return Response(render_template("timer.html", data=api()), mimetype="text/html")
+
+               if page == "price":
+                       return Response(render_template("price.html", data=api(),  items=static_data['items']), mimetype="text/html")
+       else:
+               return "", 404
+
+
+
 
 @app.route("/api")
 def api():
-       if request.method == "GET":
+       if request.method in ["GET", "internal"]:
                data = database("*")
 
                focus_extra = {}
@@ -52,17 +151,15 @@ def api():
                                focus_extra[f"bool_{key[6:]}"] = bool(value) # if 1,2 then True, if 0 then False
                data |= focus_extra
 
+               if request.method == "internal": return data
                return jsonify(data)
-
-       elif request.method == "POST":
-               ...
        else:
                return "", 404
 
 @app.route("/api/items")
 def api_items():
        if request.method == "GET":
-               return app.send_static_file("static.json")
+               return jsonify(static_data)
 
        else:
                return "", 404
@@ -76,5 +173,25 @@ def api_clock():
        else:
                return "", 404
 
+
+
+
 if __name__ == "__main__":
-       app.run(host='127.0.0.1', port=5000, debug=True)
+       # sanity check on the db
+       with open(path.join(app.root_path, "schema"), "r") as f:
+               schema, load = f.read().split("\n\n")
+               with sqlite3.connect(path.join(app.root_path, "data.db")) as connection:
+                       cursor = connection.cursor()
+                       try:
+                               cursor.execute(load)
+                       except sqlite3.IntegrityError:
+                               print("Database is setup correctly")
+                               pass
+                       except sqlite3.OperationalError:
+                               print("Table missing or corrupt...")
+                               cursor.execute("DROP TABLE state;")
+                               cursor.execute(schema)
+                               cursor.execute(load)
+                       connection.commit()
+
+       app.run(host='192.168.8.143', port=5080, debug=True)
diff --git a/templates/admin.html b/templates/admin.html
new file mode 100644 (file)
index 0000000..fece3cf
--- /dev/null
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <meta charset="utf-8">
+               <title>XMDV</title>
+       </head>
+       <body>
+               <h1>Hello!</h1>
+               <h2>Get ready!</h2>
+               <fieldset>
+                       <legend>Control pannels</legend>
+                       <ul>
+                               <li><a href="/admin/clock">Doomsday control</a></li>
+                               <li><a href="/admin/price">Pricing and item panel</a></li>
+                               <li><a href="/admin/text">Text panel</a></li>
+                               <li><a href="/admin/timer">Timer panel</a></li>
+                       </ul>
+               </fieldset>
+       </body>
+</html>
diff --git a/templates/clock.html b/templates/clock.html
new file mode 100644 (file)
index 0000000..aba0241
--- /dev/null
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <meta charset="utf-8">
+               <title>XMDV</title>
+               <style>
+.split {
+       display: inline-block;
+       width: calc(50% - 20px);
+       margin: 10px;
+       vertical-align: top;
+}
+/*
+clock styling
+       - makes div square
+       - positions center pannel in the middle of the clock
+       - positions radio buttons around the clock (PHP)
+*/
+/*
+media query to make sure the clock is always displayed well
+*/
+@media (orientation: portrait) {
+       :root {
+               font-size: 2em;
+       }
+       input {
+               font-size: 1em;
+       }
+       input[type="submit"] {
+               width: 100%;
+               aspect-ratio: 4
+       }
+       .clock {
+               position: relative;
+               width: 100%;
+               aspect-ratio: 1;
+       }
+       .clockButton {
+               position: absolute;
+               width: 2em;
+               height: 2em;
+               margin: 0;
+       }
+       {% for p in positions %}
+               .angle{{p.i}} {top: calc({{p.y}}% - 1em); left: calc({{p.x}}% - 1em);}
+       {% endfor %}
+}
+@media (orientation: landscape) {
+       .clock {
+               position: relative;
+               height: 70vh;
+               aspect-ratio: 1;
+       }
+       .clockButton {
+               position: absolute;
+               width: 1.5em;
+               height: 1.5em;
+               margin: 0;
+       }
+       {% for p in positions %}
+               .angle{{p.i}} {top: calc({{p.y}}% - 0.75em); left: calc({{p.x}}% - 0.75em);}
+       {% endfor %}
+}
+
+               </style>
+       </head>
+       <body>
+               <form action="/admin/clock" method="POST">
+               <fieldset>
+                       <legend>Doomsday clock <em>(Incr. {{positions[1].i}} deg)</em></legend>
+                       <div class=clock>
+                               <svg class='clockFace' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'><circle r='45' cx='50' cy='50' fill='none' stroke='#a0a0a0' stroke-width='0.15px'/></svg>
+
+                               {% for p in positions %}
+                                       {% if p.i == data.current_position %}
+                                               <input type='radio' class='clockButton angle{{p.i}}' value='{{p.i}}' name='current_position' checked='checked' style='outline: 2px solid red;'>
+                                       {% else %}
+                                               <input type='radio' class='clockButton angle{{p.i}}' value='{{p.i}}' name='current_position'>
+                                       {% endif %}
+                               {% endfor %}
+
+                       </div>
+                       </fieldset>
+                       <fieldset>
+                               <legend>Movement</legend>
+                               <input type='number' name='movement_speed' id='movementSpeed' value='{{data.movement_speed}}'>
+                               <label for='movementSpeed'> Movement speed (ms/tick)</label><br>
+                               <div class="split">
+                                       <input type='radio' value='ease' name='movement_function'>
+                                       <label>Ease function</label>
+                               </div><div class="split">
+                                       <input type='radio' value='linear' name='movement_function' checked='checked'>
+                                       <label>Linear function</label>
+                               </div>
+                       </fieldset>
+                       <input type="submit" name="update" value="Update">
+               </form>
+       </body>
+</html>
diff --git a/templates/docs.html b/templates/docs.html
new file mode 100644 (file)
index 0000000..4af7662
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="utf-8">
+    <title></title>
+  </head>
+  <body>
+    <header></header>
+    <main></main>
+    <footer></footer>
+  </body>
+</html>
index 231551d784850e2f30aafeef640e4f915334f880..a80f8482f7f6bf8ce7b1fcde5e23123ee6de5b7f 100644 (file)
 <!DOCTYPE html>
-<html>
+<html lang="en">
        <head>
+               <meta charset="utf-8">
+               <title>XMDV</title>
                <style>
+
 body {
-       --star-light: #FFE0B3;
-       --star-dark: #FFC266;
-       --dark: #DEDEDE;
-       --black: #3b3b45;
-       --feature-dark: #AAAAEE;
-       --feature-light: #BBBBEE;
-       --feature-white: #EAEAFA;
-       --background-light: #FFFFFF;
-       --background-dark: #EEEEEE;
-       --feature-gradient: linear-gradient(var(--feature-dark), var(--feature-light));
-       --background: linear-gradient(var(--background-light), var(--background-dark));
-       --clock: conic-gradient(var(--background-light) 0deg, var(--background-light) 0deg, rgb(0,0,0,0) 0.1deg), rgb(0,0,0,0) 360deg);
-
-       font-family: "Archivo", sans-serif;
-       font-size: 1.6vh;
-       color: var(--black);
-
-       opacity: 0;
-       transition: opacity 1.5s;
+    --star-light: #FFE0B3;
+    --star-dark: #FFC266;
+    --dark: #DEDEDE;
+    --black: #3b3b45;
+    --feature-dark: #AAAAEE;
+    --feature-light: #BBBBEE;
+    --feature-white: #EAEAFA;
+    --background-light: #FFFFFF;
+    --background-dark: #EEEEEE;
+    --feature-gradient: linear-gradient(var(--feature-dark), var(--feature-light));
+    --background: linear-gradient(var(--background-light), var(--background-dark));
+    --clock: conic-gradient(var(--background-light) 0deg, var(--background-light) 0deg, rgb(0,0,0,0) 0.1deg), rgb(0,0,0,0) 360deg);
+
+    font-family: "Archivo", sans-serif;
+    font-size: 1.6vh;
+    color: var(--black);
+
+    opacity: 0;
+    transition: opacity 1.5s;
 }
 @keyframes spin {
-       0% {transform: rotate(0turn) scale(1.2);}
-       50% {transform: rotate(0.25turn) scale(1);}
-       100% {transform: rotate(0.5turn) scale(1.2);}
+    0% {transform: rotate(0turn) scale(1.2);}
+    50% {transform: rotate(0.25turn) scale(1);}
+    100% {transform: rotate(0.5turn) scale(1.2);}
 }
 @keyframes spinText {
-        0% {transform: rotate(-0.01turn);}
-        50% {transform: rotate(0.01turn);}
-        100% {transform: rotate(-0.01turn);}
+    0% {transform: rotate(-0.01turn);}
+    50% {transform: rotate(0.01turn);}
+    100% {transform: rotate(-0.01turn);}
 }
 .container {
-       position: absolute;
-       border-radius: 1vh;
+    position: absolute;
+    border-radius: 1vh;
 }
 .box {
-       border-radius: 1vh;
-       box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px;
-       background-image: var(--background);
+    border-radius: 1vh;
+    box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px;
+    background-image: var(--background);
 }
 #side {
-       left: 6vh;
-       top: 6vh;
-       width: 40vh;
-       opacity: 0;
-       transition: opacity 1.5s;
+left: 6vh;
+top: 6vh;
+width: 40vh;
+opacity: 0;
+transition: opacity 1.5s;
 }
 .soldBox {
-       margin: 5px;
-       padding: 5px;
-       border-radius: 5px;
-       background-image: var(--background);
-       box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
+    margin: 5px;
+    padding: 5px;
+    border-radius: 5px;
+    background-image: var(--background);
+    box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
 }
 .soldBox > * {
-       margin: 0;
-       font-style: italic;
+    margin: 0;
+    font-style: italic;
 }
 .soldBox > * > span {
-       font-weight: bold;
-       font-style: normal;
+    font-weight: bold;
+    font-style: normal;
 }
 #raiting {
-       margin: 5px;
-       margin-top: 0;
-       border-radius: 5px;
-       font-size: 2em;
-       line-height: 1em;
-       color: var(--star-dark);
-       text-shadow: -1px -1px 0 var(--star-light), 1px -1px 0 var(--star-light), -1px 1px 0 var(--star-light), 1px 1px 0 var(--star-light), 2px 2px 0 var(--dark);
-       background-image: linear-gradient(var(--background-dark), var(--dark));
-       box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
+margin: 5px;
+margin-top: 0;
+border-radius: 5px;
+font-size: 2em;
+line-height: 1em;
+color: var(--star-dark);
+text-shadow: -1px -1px 0 var(--star-light), 1px -1px 0 var(--star-light), -1px 1px 0 var(--star-light), 1px 1px 0 var(--star-light), 2px 2px 0 var(--dark);
+background-image: linear-gradient(var(--background-dark), var(--dark));
+box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
 }
 .main {
-       z-index: 0;
+    z-index: 0;
 }
 #banner {
-       left: 48vh;
-       top: 6vh;
-       height: 2em;
-       width: 0vh;
-       padding: 0.5vh;
-       overflow: hidden;
-       opacity: 0;
-       transition: opacity 1.5s, width 1.5s, left 1.5s;
-       background: linear-gradient(var(--feature-white), rgb(0,0,0,0), var(--feature-white)), var(--background);
-       border: 1px solid var(--feature-dark);
+left: 48vh;
+top: 6vh;
+height: 2em;
+width: 0vh;
+padding: 0.5vh;
+overflow: hidden;
+opacity: 0;
+transition: opacity 1.5s, width 1.5s, left 1.5s;
+background: linear-gradient(var(--feature-white), rgb(0,0,0,0), var(--feature-white)), var(--background);
+border: 1px solid var(--feature-dark);
 }
 #banner.moved, #timer2.moved {
-       left: 6vh;
+left: 6vh;
 }
 #timer2.movedUp {
-       top: 6vh;
+top: 6vh;
 }
 #timer3.focus {
-       bottom: calc(50% - 15vh);
-       right: calc(50% - 15vh);
-       width: 30vh;
-       height: 30vh;
-       border-radius: 15vh;
-       font-size: 6em;
-       line-height: 30vh;
+bottom: calc(50% - 15vh);
+right: calc(50% - 15vh);
+width: 30vh;
+height: 30vh;
+border-radius: 15vh;
+font-size: 6em;
+line-height: 30vh;
 }
 .extra {
-       position: relative;
-       top: -2vh;
-       z-index: -1;
-       transform: translateY(-100%);
-       padding-top: 2vh;
-       padding-bottom: 1px;
-       background-image: var(--feature-gradient);
-       transition: transform 1.5s;
+    position: relative;
+    top: -2vh;
+    z-index: -1;
+    transform: translateY(-100%);
+    padding-top: 2vh;
+    padding-bottom: 1px;
+    background-image: var(--feature-gradient);
+    transition: transform 1.5s;
 }
 .showExtra > .extra {
-       transform: translateY(0%);
+    transform: translateY(0%);
 }
 .main > *, .extra > *:not(.soldBox), .bottom > *, .bottom > div > *, #marquee > * {
-       margin: 0;
-       padding: 0.5vh 1vh;
+    margin: 0;
+    padding: 0.5vh 1vh;
 }
 #banner > h1 {
-       width: 49vh;
-       margin: 0;
-       font-size: 2em;
-       line-height: 1;
-       font-style: italic;
-       font-weight: normal;
-       text-align: center;
+width: 49vh;
+margin: 0;
+font-size: 2em;
+line-height: 1;
+font-style: italic;
+font-weight: normal;
+text-align: center;
 }
 .sigilBox {
-       opacity: 0;
-       transition: opacity 10s;
+    opacity: 0;
+    transition: opacity 10s;
 }
 .sigil {
-       width: 20vh;
-       height: 20vh;
+    width: 20vh;
+    height: 20vh;
 }
 #sigil1 {
-       right: 4vh;
-       top: 4vh;
+right: 4vh;
+top: 4vh;
 }
 #sigil2 {
-        right: 4vh;
-        bottom: 4vh;
+right: 4vh;
+bottom: 4vh;
 }
 #sigil3 {
-        left: 4vh;
-        bottom: 4vh;
+left: 4vh;
+bottom: 4vh;
 }
 #sigil4 {
-        left: 4vh;
-        top: 4vh;
+left: 4vh;
+top: 4vh;
 }
 #badgeContainer {
-       position: relative;
-       padding: 0;
+position: relative;
+padding: 0;
 }
 .badge {
-        position: absolute;
-        top: -2vh;
-        left: 32vh;
-        width: 10vh;
-        height: 10vh;
-        padding: 0;
-        animation: spin 10s linear 0s infinite;
+    position: absolute;
+    top: -2vh;
+    left: 32vh;
+    width: 10vh;
+    height: 10vh;
+    padding: 0;
+    animation: spin 10s linear 0s infinite;
 }
 h2.badge {
-       top: -1vh;
-       left: 26.5vh;
-       color: white;
-       width: 20vh;
-       animation: spinText 10s linear 0s infinite;
-       rotate: 0.025turn;
-       text-align: center;
-       text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
+    top: -1vh;
+    left: 26.5vh;
+    color: white;
+    width: 20vh;
+    animation: spinText 10s linear 0s infinite;
+    rotate: 0.025turn;
+    text-align: center;
+    text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
 }
 #badgeContainer {
-       transform: translateX(12vh) scale(0%);
-       transition: transform 1.5s;
+transform: translateX(12vh) scale(0%);
+transition: transform 1.5s;
 }
 .main > hr, .extra > hr {
-       margin: 0 1vh;
-       padding: 0;
-       border-color: var(--dark);
+    margin: 0 1vh;
+    padding: 0;
+    border-color: var(--dark);
 }
 .feature {
-       border-top: 2px solid var(--dark);
-       border-bottom: 2px solid var(--dark);
-       background-image: var(--feature-gradient);
+    border-top: 2px solid var(--dark);
+    border-bottom: 2px solid var(--dark);
+    background-image: var(--feature-gradient);
 }
 .bottom {
-       left: 6vh;
-       bottom: 6vh;
-       width: 100vh;
+    left: 6vh;
+    bottom: 6vh;
+    width: 100vh;
 }
 .bottom > *:not(#timer1) {
-       display: inline-block;
-       vertical-align: top;
+    display: inline-block;
+    vertical-align: top;
 }
 #topTextBox {
-       width: calc(100% - 20px);
-       margin: 5px;
-       margin-bottom: 0;
-       padding: 5px;
-       border-radius: 5px;
-       background-image: linear-gradient(0.25turn, var(--feature-dark), rgba(0,0,0,0)  40%), var(--background);
-       box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
+width: calc(100% - 20px);
+margin: 5px;
+margin-bottom: 0;
+padding: 5px;
+border-radius: 5px;
+background-image: linear-gradient(0.25turn, var(--feature-dark), rgba(0,0,0,0)  40%), var(--background);
+box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
 }
 #topText {
-       display: inline-block;
-       width: 50%;
-       padding: 0;
+display: inline-block;
+width: 50%;
+padding: 0;
 }
 #idText {
-       display: inline-block;
-       width: 50%;
-       padding: 0;
-       text-align: right;
-       font-size: 1.5em;
-       font-weight: bold;
-       font-style: italic;
-       text-shadow: 0 0 3px rgba(0,0,0,0.25);
-       color: var(--feature-dark);
+display: inline-block;
+width: 50%;
+padding: 0;
+text-align: right;
+font-size: 1.5em;
+font-weight: bold;
+font-style: italic;
+text-shadow: 0 0 3px rgba(0,0,0,0.25);
+color: var(--feature-dark);
 }
 #marquee {
-       padding: 0;
-       width: 100%;
-       mask-image: linear-gradient(0.25turn, transparent, black 5%, black 95%, transparent);
-       overflow: hidden;
+padding: 0;
+width: 100%;
+mask-image: linear-gradient(0.25turn, transparent, black 5%, black 95%, transparent);
+overflow: hidden;
 }
 #marqueeText {
-       white-space: nowrap;
-       overflow: hidden;
+white-space: nowrap;
+overflow: hidden;
 }
 #timer1 {
-       position: absolute;
-       left: 101vh;
-       padding: 0.5vh;
-       opacity: 0;
-       text-align: center;
-       border: 2px solid var(--dark);
-       border-radius: calc(1vh - 2px);
-       transition: opacity 1.5s;
+position: absolute;
+left: 101vh;
+padding: 0.5vh;
+opacity: 0;
+text-align: center;
+border: 2px solid var(--dark);
+border-radius: calc(1vh - 2px);
+transition: opacity 1.5s;
 }
 #timer2 {
-       position: absolute;
-       left: calc(48vh + 2px);
-       top: calc(11.5vh + 2px);
-       height: 1.2em;
-       width: 3em;
-       margin: 0;
-       text-align: center;
-       font-size: 2em;
-       font-weight: bold;
-       line-height: 1.2em;
-       border: 2px solid var(--dark);
-       outline: 2px solid var(--feature-dark);
-       opacity: 0;
-       transition: opacity 1.5s, left 1.5s, top 1.5s;
+position: absolute;
+left: calc(48vh + 2px);
+top: calc(11.5vh + 2px);
+height: 1.2em;
+width: 3em;
+margin: 0;
+text-align: center;
+font-size: 2em;
+font-weight: bold;
+line-height: 1.2em;
+border: 2px solid var(--dark);
+outline: 2px solid var(--feature-dark);
+opacity: 0;
+transition: opacity 1.5s, left 1.5s, top 1.5s;
 }
 #timer3 {
-       position: absolute;
-       bottom: 4vh;
-       right: 4vh;
-       opacity: 0;
-       width: 12vh;
-       height: 12vh;
-       border-radius: 6vh;
-       margin: 0;
-       text-align: center;
-       vertical-align: center;
-       font-size: 2em;
-       font-weight: bold;
-       line-height: 12vh;
-       background-image: var(--background);
-       border: 2px solid var(--feature-dark);
-       outline: 2px solid var(--dark);
-       opacity: 0;
-       transition: opacity 1.5s, bottom 1.5s, right 1.5s, width 1.5s, height 1.5s, border-radius 1.5s, font-size 1.5s, line-height 1.5s, transform 1.5s;
-       animation: spinText 10s linear 0s infinite;
+position: absolute;
+bottom: 4vh;
+right: 4vh;
+opacity: 0;
+width: 12vh;
+height: 12vh;
+border-radius: 6vh;
+margin: 0;
+text-align: center;
+vertical-align: center;
+font-size: 2em;
+font-weight: bold;
+line-height: 12vh;
+background-image: var(--background);
+border: 2px solid var(--feature-dark);
+outline: 2px solid var(--dark);
+opacity: 0;
+transition: opacity 1.5s, bottom 1.5s, right 1.5s, width 1.5s, height 1.5s, border-radius 1.5s, font-size 1.5s, line-height 1.5s, transform 1.5s;
+animation: spinText 10s linear 0s infinite;
 }
 .show {
-       opacity: 1 !important;
+    opacity: 1 !important;
 }
 #badgeContainer.show {
-       transform: translateX(0vh) scale(100%);
+transform: translateX(0vh) scale(100%);
 }
 #banner.show {
-       width: 50vh;
+width: 50vh;
 }
 .sigilBox.show {
-       opacity: 0.9;
+    opacity: 0.9;
 }
 @keyframes spinBox {
-       0% {transform: translateY(100%);}
-       50% {}
-       100% {}
+    0% {transform: translateY(100%);}
+    50% {}
+    100% {}
 
 }
 line, polyline, circle {
-       fill: none;
-       stroke: var(--feature-dark);
-       stroke-width: 3;
+    fill: none;
+    stroke: var(--feature-dark);
+    stroke-width: 3;
 }
 circle {
-       fill: #F2F2F2;
+    fill: #F2F2F2;
 }
 .sigilGrid {
-       stroke: var(--dark);
-       stroke-width: 1;
+    stroke: var(--dark);
+    stroke-width: 1;
 }
+
                </style>
-               <link rel="stylesheet" href="static/fonts.css">
+               <link rel="stylesheet" href="./static/fonts.css">
        </head>
        <body id="all" class="show" onload="frame();update();">
 
@@ -404,24 +408,24 @@ circle {
                                <circle class="sigilEnd" r="3">
                        </svg>
                </div>
-
-                               <script>
+               <script>
 
 /*
-structure
-       *: function
-       -: section
+ structure
+
+ *: function
+ -: section
 
-- setup of all const html elements
-- setup of all variables shared between the main call function and the other funtions
-* main call function (update) happens 2x per second
+ - setup of all const html elements
+ - setup of all variables shared between the main call function and the other funtions
+ * main call function (update) happens 2x per second
 
-- setup of all variables remebered between frame calls
-* frame function (frame) 200x per second
+ - setup of all variables remebered between frame calls
+ * frame function (frame) 200x per second
 
-* sigil function (sigil) 2X per second
+ * sigil function (sigil) 2X per second
 
-*/
+ */
 
 const all = document.getElementById("all");
 
@@ -432,23 +436,23 @@ const timer4 = document.getElementById("timer4");
 const timer5 = document.getElementById("timer5");
 const timer6 = document.getElementById("timer6");
 const timers = [
-       timer1,
-       timer2,
-       timer3,
-       timer4,
-       timer5,
-       timer6
-       ];
+    timer1,
+timer2,
+timer3,
+timer4,
+timer5,
+timer6
+];
 const banner = document.getElementById("banner");
 const side = document.getElementById("side");
 const badgeContainer = document.getElementById("badgeContainer");
 // 1:NE 2:SE 3:SW 4:NW
 const sigils = [
-       document.getElementById("sigil1"),
-       document.getElementById("sigil2"),
-       document.getElementById("sigil3"),
-       document.getElementById("sigil4")
-       ];
+    document.getElementById("sigil1"),
+    document.getElementById("sigil2"),
+    document.getElementById("sigil3"),
+    document.getElementById("sigil4")
+];
 const remaining = document.getElementById("unitsLeft");
 const origional = document.getElementById("origionalPrice");
 const topTextElement = document.getElementById("topText");
@@ -473,199 +477,199 @@ let bottomText = [];
 
 // handles all updates from the server data that are not required to be instantanious (2 times per second)
 function update() {
-       // fetch the item manifest and chache it
-       fetch("./api/items", {cache: "default"})
-                       .then(data => data.json())
-                       .then(dataStatic => {
-                               fetch("./api", {cache: "no-store"})
-                                       .then(data => data.json())
-                                       .then(data => {
-
-                                               // some variable setup
-                                               let id = data.item_id;
-                                               const items = dataStatic.items
-                                               const item = items[id];
-
-                                               // frame function variable hand-over
-                                               discountHard = data.discount;
-                                               discountRate = data.discount_change / 200; // happens 200x per second in the frame function
-                                               topText = dataStatic.text.crawler_top[data.crawler_top_index]
-                                               bottomText = [];
-                                               for (let i of data.list_crawler_bottom) {bottomText.push(dataStatic.text.crawler_bottom[i])}
-
-                                               // handle all optional elements showing/hiding
-                                               if (data.bool_all) {all.classList.add("show");}
-                                               else {all.classList.remove("show");}
-
-                                               // set all timers to correct time and show/focus
-                                               for (let t = 1; t <= 6; t++) {
-                                                       const timer = timers[t-1];
-
-                                                       if (timer != null) {
-                                                               let current = Math.round((Date.now() + data['timer_offset']) / 1000);
-                                                               var time = data[`end_timer_${t}`] - 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");
-
-                                                               timer.innerHTML = `${minutesString}:${secondsString}`;
-
-                                                               if (data[`bool_timer_${t}`]) {timer.classList.add("show");}
-                                                               else {timer.classList.remove("show");}
-
-                                                               if (data[`focus_timer_${t}`]) {timer.classList.add("focus");}
-                                                               else {timer.classList.remove("focus");}
-
-                                                               // additional styling for individual timers
-                                                               switch (t) {
-                                                                       case 2: {
-                                                                               let angle;
-                                                                               if (minutes > 0) {angle = 360;}
-                                                                                       else {angle = Math.round(seconds * (360 / 60))};
-                                                                               timer2.style.background = `
-                                                                                       conic-gradient(
-                                                                                               rgb(0,0,0,0) 0deg,
-                                                                                               rgb(0,0,0,0) ${angle}deg,
-                                                                                               var(--feature-light) ${angle}.1deg,
-                                                                                               var(--feature-light) 360deg
-                                                                                               ), var(--background)`;
-                                                                                       }
-
-                                                                       case 3: {
-                                                                               let radius;
-                                                                               if (minutes > 0) {radius = 100;}
-                                                                                       else {radius = Math.round(seconds * (100 / 60));}
-                                                                               timer3.style.background = `
-                                                                                       linear-gradient(
-                                                                                               rgb(0,0,0,0) 0%, rgb(0,0,0,0) ${radius}%,
-                                                                                               var(--feature-light) ${radius}.1%,
-                                                                                               var(--feature-dark) 100%
-                                                                                               ), var(--background)`;
-                                                                                       }
-                                                               }
-                                                       }
-                                               }
-
-                                               // banner placement
-                                               if (data.bool_banner) {
-                                                       if (!banner.classList.contains("show")) { // only change the banner text when the banner is turning back on
-                                                               document.getElementById("bannerText").innerHTML = dataStatic.text.banner[data.banner_index];
-                                                       }
-                                                       banner.classList.add("show");
-                                                       timer2.classList.remove("movedUp");
-                                               } else {
-                                                       banner.classList.remove("show");
-                                                       timer2.classList.add("movedUp");
-                                               }
-
-                                               // handle element showing
-                                               // hierarchy: main product information -> extra information -> discount badge
-                                               if (data.bool_product) {
-                                                       side.classList.add("show");
-                                                       banner.classList.remove("moved");
-                                                       timer2.classList.remove("moved");
-
-                                                       if (data.bool_extra) {
-                                                               side.classList.add("showExtra");
-
-                                                               if (discount <= 0.99) {
-                                                                       badgeContainer.classList.add("show");
-                                                               } else {
-                                                                       badgeContainer.classList.remove("show");
-                                                               }
-
-                                                       } else {
-                                                               side.classList.remove("showExtra");
-                                                       }
-
-                                               } else {
-                                                       side.classList.remove("show");
-                                                       banner.classList.add("moved");
-                                                       timer2.classList.add("moved");
-                                                       side.classList.remove("showExtra");
-                                                       badgeContainer.classList.remove("show");
-                                               }
-
-                                               // Sigil handling
-                                               for (let s = 0; s < 4; s++) {
-                                                       if (data[`bool_sigil_${s+1}`]) {
-                                                               let phrase = dataStatic.text.sigil[Math.floor(Math.random()*dataStatic.text.sigil.length)];
-                                                               let pointString = "";
-                                                               for (let i = 0; i < phrase.length; i++) {
-                                                                       let point = phrase.charCodeAt(i) - 97; // get the coords of the letter
-                                                                       if (i == 0) {var startPoint = dataStatic.text.square[point].split(",");} // get the start and end points
-                                                                               else if (i == phrase.length - 1) {var endPoint = dataStatic.text.square[point].split(",");}
-
-                                                                       pointString += `${dataStatic.text.square[point]} `;
-                                                               }
-
-                                                               sigilCap[s].setAttribute("x1", Number(startPoint[0]) - 5)
-                                                               sigilCap[s].setAttribute("y1", startPoint[1])
-                                                               sigilCap[s].setAttribute("x2", Number(startPoint[0]) + 5)
-                                                               sigilCap[s].setAttribute("y2", startPoint[1])
-                                                               sigilLine[s].setAttribute("points", pointString);
-                                                               sigilEnd[s].setAttribute("cx", endPoint[0]);
-                                                               sigilEnd[s].setAttribute("cy", endPoint[1]);
-
-                                                               sigils[s].classList.add("show");
-                                                       } else {
-                                                               sigils[s].classList.remove("show");
-                                                       }
-                                               }
-
-                                               // set item properties
-                                               document.getElementById("code").innerHTML = item.code;
-                                               document.getElementById("raiting").innerHTML = item.rating;
-                                               document.getElementById("subtext").innerHTML = item.subtext;
-                                               document.getElementById("description").innerHTML = item.description;
-
-                                               // calculate the price sting with the pre/postfix
-                                               let price = item.origional_price * discount;
-                                               if (data.bool_rounding) {
-                                                       price = Math.round(price * 100) / 100;
-                                                       }
-
-                                               let priceString;
-                                               let ezString;
-                                               if (data.bool_prefix) {priceString = `${data.currency_symbol}${price}`}
-                                                       else {priceString = `${price}${data.currency_symbol}`};
-                                               let ezPrice = Math.round((price * 1.1) / 12);
-                                               if (data.bool_prefix) {ezString = `${data.currency_symbol}${ezPrice}`}
-                                                       else {ezString = `${ezPrice}${data.currency_symbol}`};
-
-                                               // set discount, pricing and ez pay
-                                               document.getElementById("currentPrice").innerHTML = `<em>Now only:</em> ${priceString}`;
-                                               document.getElementById("monthlyPrice").innerHTML = `12 monthly payments of <b>${ezString}</b>`;
-                                               document.getElementById("badgeText").innerHTML = `${Math.round((1 - discount) * 100)}% OFF`;
-                                               document.getElementById("stock").innerHTML = `${item.stock_count} units`;
-                                               document.getElementById("sold").innerHTML = `${Math.round(item.stock_count * (1 - data.percent_remaining))} units`;
-
-                                               if (discount <= 0.99) {
-                                                       let origionalPrice = Math.round(item.origional_price);
-                                                       if (data.bool_prefix) {origionalString = `${data.currency_symbol}${origionalPrice}`}
-                                                               else {origionalString = `${origionalPrice}${data.currency_symbol}`};
-                                                       origional.innerHTML = `<s><em>WAS:</em> ${origionalString}</s>`;
-                                               } else {
-                                                       origional.innerHTML = `<em>NOW:</em> ${priceString}`;
-                                               }
-
-                                               if (data.percent_remaining == 0) {
-                                                       remaining.innerHTML = "Sold out!";
-                                                       }
-                                               else { // work out the prefix for the discount to be more reactive
-                                                       let descriptior;
-                                                       if (data.percent_remaining < 0.1) {descriptor = "Quick! Only"}
-                                                               else if (data.percent_remaining < 0.25) {descriptor = "Only"}
-                                                               else if (data.percent_remaining < 0.5) {descriptor = "Just"}
-                                                               else {descriptor = ""}
-                                                       remaining.innerHTML = `
-                                                       ${descriptor} ${Math.round(item.stock_count * (data.percent_remaining))} left!
-                                                       `; }
-                                       });
-               });
+    // fetch the item manifest and chache it
+    fetch("./api/items", {cache: "default"})
+    .then(data => data.json())
+    .then(dataStatic => {
+        fetch("./api", {cache: "no-store"})
+        .then(data => data.json())
+        .then(data => {
+
+            // some variable setup
+            let id = data.item_id;
+            const items = dataStatic.items
+            const item = items[id];
+
+            // frame function variable hand-over
+            discountHard = data.discount_1;
+            discountRate = data.discount_change / 200; // happens 200x per second in the frame function
+            topText = dataStatic.text.crawler_top[data.crawler_top_index]
+            bottomText = [];
+            for (let i of data.list_crawler_bottom) {bottomText.push(dataStatic.text.crawler_bottom[i])}
+
+            // handle all optional elements showing/hiding
+            if (data.bool_all) {all.classList.add("show");}
+            else {all.classList.remove("show");}
+
+            // set all timers to correct time and show/focus
+            for (let t = 1; t <= 6; t++) {
+                const timer = timers[t-1];
+
+                if (timer != null) {
+                    let current = Math.round((Date.now() + data['timer_offset']) / 1000);
+                    var time = data[`end_timer_${t}`] - 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");
+
+                    timer.innerHTML = `${minutesString}:${secondsString}`;
+
+                    if (data[`bool_timer_${t}`]) {timer.classList.add("show");}
+                    else {timer.classList.remove("show");}
+
+                    if (data[`focus_timer_${t}`]) {timer.classList.add("focus");}
+                    else {timer.classList.remove("focus");}
+
+                    // additional styling for individual timers
+                    switch (t) {
+                        case 2: {
+                            let angle;
+                            if (minutes > 0) {angle = 360;}
+                            else {angle = Math.round(seconds * (360 / 60))};
+                            timer2.style.background = `
+                            conic-gradient(
+                                rgb(0,0,0,0) 0deg,
+                                           rgb(0,0,0,0) ${angle}deg,
+                                           var(--feature-light) ${angle}.1deg,
+                                           var(--feature-light) 360deg
+                            ), var(--background)`;
+                        }
+
+                        case 3: {
+                            let radius;
+                            if (minutes > 0) {radius = 100;}
+                            else {radius = Math.round(seconds * (100 / 60));}
+                            timer3.style.background = `
+                            linear-gradient(
+                                rgb(0,0,0,0) 0%, rgb(0,0,0,0) ${radius}%,
+                                            var(--feature-light) ${radius}.1%,
+                                            var(--feature-dark) 100%
+                            ), var(--background)`;
+                        }
+                    }
+                }
+            }
+
+            // banner placement
+            if (data.bool_banner) {
+                if (!banner.classList.contains("show")) { // only change the banner text when the banner is turning back on
+                    document.getElementById("bannerText").innerHTML = dataStatic.text.banner[data.banner_index];
+                }
+                banner.classList.add("show");
+                timer2.classList.remove("movedUp");
+            } else {
+                banner.classList.remove("show");
+                timer2.classList.add("movedUp");
+            }
+
+            // handle element showing
+            // hierarchy: main product information -> extra information -> discount badge
+            if (data.bool_product) {
+                side.classList.add("show");
+                banner.classList.remove("moved");
+                timer2.classList.remove("moved");
+
+                if (data.bool_extra) {
+                    side.classList.add("showExtra");
+
+                    if (discount <= 0.99) {
+                        badgeContainer.classList.add("show");
+                    } else {
+                        badgeContainer.classList.remove("show");
+                    }
+
+                } else {
+                    side.classList.remove("showExtra");
+                }
+
+            } else {
+                side.classList.remove("show");
+                banner.classList.add("moved");
+                timer2.classList.add("moved");
+                side.classList.remove("showExtra");
+                badgeContainer.classList.remove("show");
+            }
+
+            // Sigil handling
+            for (let s = 0; s < 4; s++) {
+                if (data[`bool_sigil_${s+1}`]) {
+                    let phrase = dataStatic.text.sigil[Math.floor(Math.random()*dataStatic.text.sigil.length)];
+                    let pointString = "";
+                    for (let i = 0; i < phrase.length; i++) {
+                        let point = phrase.charCodeAt(i) - 97; // get the coords of the letter
+                        if (i == 0) {var startPoint = dataStatic.text.square[point].split(",");} // get the start and end points
+                        else if (i == phrase.length - 1) {var endPoint = dataStatic.text.square[point].split(",");}
+
+                        pointString += `${dataStatic.text.square[point]} `;
+                    }
+
+                    sigilCap[s].setAttribute("x1", Number(startPoint[0]) - 5)
+                    sigilCap[s].setAttribute("y1", startPoint[1])
+                    sigilCap[s].setAttribute("x2", Number(startPoint[0]) + 5)
+                    sigilCap[s].setAttribute("y2", startPoint[1])
+                    sigilLine[s].setAttribute("points", pointString);
+                    sigilEnd[s].setAttribute("cx", endPoint[0]);
+                    sigilEnd[s].setAttribute("cy", endPoint[1]);
+
+                    sigils[s].classList.add("show");
+                } else {
+                    sigils[s].classList.remove("show");
+                }
+            }
+
+            // set item properties
+            document.getElementById("code").innerHTML = item.code;
+            document.getElementById("raiting").innerHTML = item.rating;
+            document.getElementById("subtext").innerHTML = item.subtext;
+            document.getElementById("description").innerHTML = item.description;
+
+            // calculate the price sting with the pre/postfix
+            let price = item.origional_price * discount;
+            if (data.bool_rounding) {
+                price = Math.round(price * 100) / 100;
+            }
+
+            let priceString;
+            let ezString;
+            if (data.bool_prefix) {priceString = `${data.currency_symbol}${price}`}
+            else {priceString = `${price}${data.currency_symbol}`};
+            let ezPrice = Math.round((price * 1.1) / 12);
+            if (data.bool_prefix) {ezString = `${data.currency_symbol}${ezPrice}`}
+            else {ezString = `${ezPrice}${data.currency_symbol}`};
+
+            // set discount, pricing and ez pay
+            document.getElementById("currentPrice").innerHTML = `<em>Now only:</em> ${priceString}`;
+            document.getElementById("monthlyPrice").innerHTML = `12 monthly payments of <b>${ezString}</b>`;
+            document.getElementById("badgeText").innerHTML = `${Math.round((1 - discount) * 100)}% OFF`;
+            document.getElementById("stock").innerHTML = `${item.stock_count} units`;
+            document.getElementById("sold").innerHTML = `${Math.round(item.stock_count * (1 - data.percent_remaining))} units`;
+
+            if (discount <= 0.99) {
+                let origionalPrice = Math.round(item.origional_price);
+                if (data.bool_prefix) {origionalString = `${data.currency_symbol}${origionalPrice}`}
+                else {origionalString = `${origionalPrice}${data.currency_symbol}`};
+                origional.innerHTML = `<s><em>WAS:</em> ${origionalString}</s>`;
+            } else {
+                origional.innerHTML = `<em>NOW:</em> ${priceString}`;
+            }
+
+            if (data.percent_remaining == 0) {
+                remaining.innerHTML = "Sold out!";
+            }
+            else { // work out the prefix for the discount to be more reactive
+                let descriptior;
+                if (data.percent_remaining < 0.1) {descriptor = "Quick! Only"}
+                else if (data.percent_remaining < 0.25) {descriptor = "Only"}
+                else if (data.percent_remaining < 0.5) {descriptor = "Just"}
+                else {descriptor = ""}
+                remaining.innerHTML = `
+                ${descriptor} ${Math.round(item.stock_count * (data.percent_remaining))} left!
+                `; }
+        });
+    });
 }
 
 // frame dynamic variables
@@ -677,41 +681,41 @@ let bottomTextIndex = 0;
 // function handles all animated events that are required to look smooth (marquee movement / price changes) (200 times per second)
 function frame() {
 
-       // move the current discount towards the target distance at the supplied rate
-       if (Math.abs(discount - discountHard) <= discountRate) { // if the discount is within one tick, snap to current discount
-               discount = discountHard;
-       } else if (discount > discountHard) {
-               discount -= discountRate;
-       } else {
-               discount += discountRate;
-       }
+    // move the current discount towards the target distance at the supplied rate
+    if (Math.abs(discount - discountHard) <= discountRate) { // if the discount is within one tick, snap to current discount
+        discount = discountHard;
+    } else if (discount > discountHard) {
+        discount -= discountRate;
+    } else {
+        discount += discountRate;
+    }
 
-       bottomTextElement.style.transform = `translateX(${marqueeOffset}px)`;
-       marqueeOffset += 0.2;
+    bottomTextElement.style.transform = `translateX(${marqueeOffset}px)`;
+    marqueeOffset += 0.2;
 
-       // only replace text where neccicary or where the page has just loaded
-       if (marqueeOffset >= marqueeContainer.offsetWidth || (topTextElement.innerHTML == "" && bottomTextElement.innerHTML == "")) {
+    // only replace text where neccicary or where the page has just loaded
+    if (marqueeOffset >= marqueeContainer.offsetWidth || (topTextElement.innerHTML == "" && bottomTextElement.innerHTML == "")) {
 
-               topTextElement.innerHTML = topText;
+        topTextElement.innerHTML = topText;
 
-               bottomTextElement.innerHTML = bottomText[bottomTextIndex];
-               bottomTextIndex += 1;
-               if (bottomTextIndex >= bottomText.length) { // make sure the changed list wont ovreflow the bottom text index
-                       bottomTextIndex = 0;
-               }
+        bottomTextElement.innerHTML = bottomText[bottomTextIndex];
+        bottomTextIndex += 1;
+        if (bottomTextIndex >= bottomText.length) { // make sure the changed list wont ovreflow the bottom text index
+            bottomTextIndex = 0;
+        }
 
-               // use a canvas element to measure the size of text precicely
-               const canvas = document.getElementById("canvas");
-               const ctx = canvas.getContext("2d");
+        // use a canvas element to measure the size of text precicely
+        const canvas = document.getElementById("canvas");
+        const ctx = canvas.getContext("2d");
 
-               var style = window.getComputedStyle(bottomTextElement, null).getPropertyValue("font-size");
-               var fontSize = parseFloat(style);
+        var style = window.getComputedStyle(bottomTextElement, null).getPropertyValue("font-size");
+        var fontSize = parseFloat(style);
 
-               ctx.font = `bold ${fontSize}px sans-serif`;
-               let text = ctx.measureText(bottomTextElement.innerHTML);
+        ctx.font = `bold ${fontSize}px sans-serif`;
+        let text = ctx.measureText(bottomTextElement.innerHTML);
 
-               marqueeOffset = -1.1 * text.width; // place text just before its end-point
-       }
+        marqueeOffset = -1.1 * text.width; // place text just before its end-point
+    }
 }
 
 setInterval(frame, 5);
diff --git a/templates/price.html b/templates/price.html
new file mode 100644 (file)
index 0000000..6d41814
--- /dev/null
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <meta charset="utf-8">
+               <title>XMDV</title>
+       </head>
+       <body>
+               <form action="/admin/price" method="POST">
+                       <fieldset>
+                               <legend>Products</legend>
+                               {% for item in items %}
+                                       {% if loop.index0 == data.item_id %}
+                                               <input type='radio' value='{{loop.index0}}' name='item_id' checked='checked'>
+                                               <label style='color: red;'>{{item.subtext}}</label>
+                                       {% else %}
+                                               <input type='radio' value='{{loop.index0}}' name='item_id'>
+                                               <label>{{item.subtext}}</label>
+                                       {% endif %}
+                                       <br>
+                               {% endfor %}
+                       </fieldset>
+
+                       <fieldset>
+                               <legend>Showing</legend>
+                               {% if data.bool_all %}
+                                       <input type='radio' name='bool_all' value='0'>
+                                       <label>Hide all</label>
+                                       <input type='radio' name='bool_all' value='1' checked='checked'>
+                                       <label style='color: red;'>Show all</label>
+                               {% else %}
+                                       <input type='radio' name='bool_all' value='0' checked='checked'>
+                                       <label style='color: red;'>Hide all</label>
+                                       <input type='radio' name='bool_all' value='1'>
+                                       <label>Show all</label>
+                               {% endif %}
+                               <br>
+                               {% if data.bool_product %}
+                                       <input type='radio' name='bool_product' value='0'>
+                                       <label>Hide product</label>
+                                       <input type='radio' name='bool_product' value='1' checked='checked'>
+                                       <label style='color: red;'>Show product</label>
+                               {% else %}
+                                       <input type='radio' name='bool_product' value='0' checked='checked'>
+                                       <label style='color: red;'>Hide product</label>
+                                       <input type='radio' name='bool_product' value='1'>
+                                       <label>Show product</label>
+                               {% endif %}
+                               <br>
+                               {% if data.bool_extra %}
+                                       <input type='radio' name='bool_extra' value='0'>
+                                       <label>Hide extra</label>
+                                       <input type='radio' name='bool_extra' value='1' checked='checked'>
+                                       <label style='color: red;'>Show extra</label>
+                               {% else %}
+                                       <input type='radio' name='bool_extra' value='0' checked='checked'>
+                                       <label style='color: red;'>Hide extra</label>
+                                       <input type='radio' name='bool_extra' value='1'>
+                                       <label>Show extra</label>
+                               {% endif %}
+                       </fieldset>
+
+                       <fieldset>
+                               <legend>Pricing</legend>
+                               <input type='text' name='currency_symbol' value='{{data.currency_symbol}}'>
+                               <label>Currency</label>
+                               {% if data.bool_prefix %}
+                                       <input type='radio' name='bool_prefix' value='0'>
+                                       <label>Postfix</label>
+                                       <input type='radio' name='bool_prefix' value='1' checked='checked'>
+                                       <label style='color: red;'>Prefix</label>
+                               {% else %}
+                                       <input type='radio' name='bool_prefix' value='0' checked='checked'>
+                                       <label style='color: red;'>Postfix</label>
+                                       <input type='radio' name='bool_prefix' value='1'>
+                                       <label>Prefix</label>
+                               {% endif %}
+                               <br>
+
+                               <input type='number' name='percent_remaining' value='{{data.percent_remaining}}'>
+                               <label>Unsold (%)</label> <label style='color: red;'>was {{data.percent_remaining}}</label><br>
+
+                               <br>
+                               {% for d in (1,2,3,4) %}
+                                       <input type='number' name='discount_{{d}}' step='0.01' value='{{data['discount_' ~ d]}}'>
+                                       <label>Discount {{d}} (%)</label> <label style='color: red;'>was {{data['discount_' ~ d]}}</label><br>
+                               {% endfor %}
+                               <br>
+
+                               <input type='number' name='discount_change' value='{{data.discount_change}}'>
+                               <label>Discount change (%/s)</label> <label style='color: red;'>was {{data.discount_change}}</label><br>
+
+                               {% if data.bool_rounding %}
+                                       <input type='radio' name='bool_rounding' value='0'>
+                                       <label>Don't round prices</label>
+                                       <input type='radio' name='bool_rounding' value='1' checked='checked'>
+                                       <label style='color: red;'>Round prices</label>
+                               {% else %}
+                                       <input type='radio' name='bool_rounding' value='0' checked='checked'>
+                                       <label style='color: red;'>Don't round prices</label>
+                                       <input type='radio' name='bool_rounding' value='1'>
+                                       <label>Round prices</label>
+                               {% endif %}
+                       </fieldset>
+
+                       <fieldset>
+                               <legend>Note</legend>
+                               <textarea name='note' rows='10' style='width: 100%;'>{{data.note}}</textarea>
+                       </fieldset>
+                       <input type="submit" name="update" value="Update">
+               </form>
+       </body>
+</html>
diff --git a/templates/text.html b/templates/text.html
new file mode 100644 (file)
index 0000000..5f9b33c
--- /dev/null
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <meta charset="utf-8">
+               <title>XMDV</title>
+       </head>
+       <body>
+               <form action="/admin/text" method="POST">
+                       <fieldset>
+                               <legend>Crawler top text</legend>
+                                       {% for line in text.crawler_top %}
+                                               {% if loop.index0 == data.crawler_top_index %}
+                                                       <input type='radio' value='{{loop.index0}}' name='crawler_top_index' checked='checked'>
+                                                       <label style='color: red;'>{{line}}</label>
+                                               {% else %}
+                                                       <input type='radio' value='{{loop.index0}}' name='crawler_top_index'>
+                                                       <label>{{line}}</label>
+                                               {% endif %}
+                                               <br>
+                                       {% endfor %}
+                       </fieldset>
+
+                       <fieldset>
+                               <legend>Bottom text</legend>
+                                       {% for line in text.crawler_bottom %}
+                                               {% if loop.index0 in data.list_crawler_bottom %}
+                                                       <input type='checkbox' value='{{loop.index0}}' name='aggrigate_list_crawler_bottom_{{ '%04d' % loop.index0 }}' checked='checked'>
+                                                       <label style='color: red;'>{{line}}</label>
+                                               {% else %}
+                                                       <input type='checkbox' value='{{loop.index0}}' name='aggrigate_list_crawler_bottom_{{ '%04d' % loop.index0 }}'>
+                                                       <label>{{line}}</label>
+                                               {% endif %}
+                                               <br>
+                                       {% endfor %}
+                       </fieldset><fieldset>
+                               <legend>Banner text</legend>
+                                       {% for line in text.banner %}
+                                               {% if loop.index0 == data.banner_index %}
+                                                       <input type='radio' value='{{loop.index0}}' name='banner_index' checked='checked'>
+                                                       <label style='color: red;'>{{line}}</label>
+                                               {% else %}
+                                                       <input type='radio' value='{{loop.index0}}' name='banner_index'>
+                                                       <label>{{line}}</label>
+                                               {% endif %}
+                                               <br>
+                                       {% endfor %}
+                               <br>
+                               {% if data.bool_banner %}
+                                       <input type='radio' name='bool_banner' value='0'>
+                                       <label>Hide banner</label>
+                                       <input type='radio' name='bool_banner' value='1' checked='checked'>
+                                       <label style='color: red;'>Show banner</label>
+                               {% else %}
+                                       <input type='radio' name='bool_banner' value='0' checked='checked'>
+                                       <label style='color: red;'>Hide banner</label>
+                                       <input type='radio' name='bool_banner' value='1'>
+                                       <label>Show banner</label>
+                               {% endif %}
+                       </fieldset>
+
+
+                       <fieldset>
+                               <legend>Sigil display</legend>
+                               {% for t in (1,2,3,4) %}
+                                       {% if data['bool_sigil_' ~ t] %}
+                                               <input type='radio' name='bool_sigil_{{t}}' value='0'>
+                                               <label>Hide sigil {{t}}</label>
+                                               <input type='radio' name='bool_sigil_{{t}}' value='1' checked='checked'>
+                                               <label style='color: red;'>Show sigil {{t}}</label>
+                                       {% else %}
+                                               <input type='radio' name='bool_sigil_{{t}}' value='0' checked='checked'>
+                                               <label style='color: red;'>Hide sigil {{t}}</label>
+                                               <input type='radio' name='bool_sigil_{{t}}' value='1'>
+                                               <label>Show sigil {{t}}</label>
+                                       {% endif %}
+                                       <br>
+                               {% endfor %}
+                       </fieldset>
+
+                       <input type="submit" name="update" value="Update">
+               </form>
+       </body>
+</html>
diff --git a/templates/timer.html b/templates/timer.html
new file mode 100644 (file)
index 0000000..78409da
--- /dev/null
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <meta charset="utf-8">
+               <title>XMDV</title>
+       </head>
+       <body onload="update();">
+               <form action="/admin/timer" method="POST">
+                       <fieldset>
+                               <legend>Timers</legend>
+                               <p style='color: red;'>Note: time is approximate and may not be accurate to the GFX display.</p>
+                               {% for t in (1,2,3,4,5,6) %}
+                                       <label>Timer {{t}}</label> <label style='color: blue;' id='{{t}}'>00:00</label>
+                                       <input type='radio' value='0' name='end_timer_{{t}}'><label>Reset</label>
+                                       <input type='radio' value='30' name='end_timer_{{t}}'><label>+00:30</label>
+                                       <input type='radio' value='60' name='end_timer_{{t}}'><label>+01:00</label>
+                                       <input type='radio' value='120' name='end_timer_{{t}}'><label>+02:00</label>
+                                       <input type='radio' value='300' name='end_timer_{{t}}'><label>+05:00</label>
+                                       <input type='radio' value='600' name='end_timer_{{t}}'><label>+10:00</label>
+
+                                       {% if data['focus_timer_' ~ t] %}
+                                               <input type='radio' name='focus_timer_{{t}}' value='0' style='margin-left:50px;'>
+                                               <label>Hide timer {{t}}</label>
+                                               <input type='radio' name='focus_timer_{{t}}' value='1'>
+                                               <label>Show timer {{t}}</label>
+                                               <input type='radio' name='focus_timer_{{t}}' value='2' checked='checked'>
+                                               <label style='color: red;'>Focus timer {{t}}</label>
+                                       {% elif data['bool_timer_' ~ t] %}
+                                               <input type='radio' name='focus_timer_{{t}}' value='0' style='margin-left:50px;'>
+                                               <label>Hide timer {{t}}</label>
+                                               <input type='radio' name='focus_timer_{{t}}' value='1' checked='checked'>
+                                               <label style='color: red;'>Show timer {{t}}</label>
+                                               <input type='radio' name='focus_timer_{{t}}' value='2'>
+                                               <label>Focus timer {{t}}</label>
+                                       {% else %}
+                                               <input type='radio' name='focus_timer_{{t}}' value='0' style='margin-left:50px;' checked='checked'>
+                                               <label style='color: red;'>Hide timer {{t}}</label>
+                                               <input type='radio' name='focus_timer_{{t}}' value='1'>
+                                               <label>Show timer {{t}}</label>
+                                               <input type='radio' name='focus_timer_{{t}}' value='2'>
+                                               <label>Focus timer {{t}}</label>
+                                       {% endif %}
+                                       <br>
+                               {% endfor %}
+
+                               <br>
+                               <input type='number' name='timer_offset' min='-5000' max='5000' step='1' value='{{data.timer_offset}}'>
+                               <label> Offset (ms)</label> <label style='color: red;'> was {{data.timer_offset}}</label>
+                       </fieldset>
+                       <input type="submit" name="update" value="Update">
+               </form>
+
+               <script>
+
+function update() {
+       fetch("/api", {cache: "no-store"})
+               .then(data => data.json())
+               .then(data => {
+                       for (let t = 1; t <= 6; t++) {
+                               const timer = document.getElementById(t);
+
+                               let current = Math.round((Date.now() + data['timer_offset']) / 1000);
+                               var time = data[`end_timer_${t}`] - 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");
+
+                               timer.innerHTML = `${minutesString}:${secondsString}`;
+                               if (minutes == 0) {
+                                       if (seconds == 0) {timer.style.color = "blue";}
+                                       else if (seconds % 2 == 0) {timer.style.color = "red";}
+                                       else {timer.style.color = "orange";}
+                               } else {
+                                       timer.style.color = "green";
+                               }
+                       }
+               });
+}
+
+setInterval(update, 1000)
+
+               </script>
+       </body>
+</html>