First version, for githup; UNSTABLE, DO NOT USE!
This commit is contained in:
441
www/displays/display.php
Normal file
441
www/displays/display.php
Normal file
@@ -0,0 +1,441 @@
|
||||
<?php
|
||||
|
||||
/*$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https://" : "http://";
|
||||
$fullDomain = $protocol . $_SERVER['HTTP_HOST'];*/
|
||||
|
||||
$lastSegment = strtolower($_GET['geraet']) ?? '';
|
||||
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
$token = "QQa2UMbEYW8oOL7wz9DjtqECVCikSZsDuSdmzxiadEXFsKyujEUyQOW1AYMD2OqU8VXxClIRweRuWLzvBrZpPYL41e89Rs96tM7Lq1KpjA5E2mg2UfgvztheGRV";
|
||||
|
||||
require_once $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
|
||||
require_once $baseDir . '/../scripts/db/db-functions.php';
|
||||
require_once $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
$stmt = $guest->prepare("SELECT `name` FROM $tableGeraete ORDER BY start_index ASC");
|
||||
|
||||
if (!$stmt->execute()) {
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$disciplines = array_map(
|
||||
'strtolower',
|
||||
array_column($result->fetch_all(MYSQLI_ASSOC), 'name')
|
||||
);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
// Define a small helper function to keep the code DRY (Don't Repeat Yourself)
|
||||
function sanitize_hex($color) {
|
||||
return preg_replace('/[^0-9a-fA-F#]/', '', $color);
|
||||
}
|
||||
|
||||
// Fetch and Sanitize Brand Colors
|
||||
$wkName = db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
|
||||
$cleanColor = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColourLogo']));
|
||||
$cleanColorText = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayTextColourLogo']));
|
||||
|
||||
// Fetch and Sanitize Layout Colors
|
||||
$displayColorScoringBg = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringBg']));
|
||||
$displayColorScoringBgSoft = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringBgSoft']));
|
||||
$displayColorScoringPanel = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanel']));
|
||||
$displayColorScoringPanelSoft = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelSoft']));
|
||||
|
||||
// Fetch and Sanitize Text Colors
|
||||
$displayColorScoringPanelText = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelText']));
|
||||
$displayColorScoringPanelTextSoft = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelTextSoft']));
|
||||
|
||||
// Fetch and Sanitize Accent Colors (Note: fixed 'diplay' typo here)
|
||||
$displayColorScoringPanelTextNoteL = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelTextNoteL']));
|
||||
$displayColorScoringPanelTextNoteR = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelTextNoteR']));
|
||||
|
||||
$guest->close();
|
||||
|
||||
if (!isset($lastSegment) || !in_array($lastSegment, $disciplines)){
|
||||
echo 'kein Gerät';
|
||||
exit;
|
||||
}
|
||||
|
||||
$jsonUrlconfig = '/displays/json/config.json';
|
||||
$jsonUrl = '/displays/json/display_' . $lastSegment . '.json';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><?= $wkName ?> Anzeigen</title>
|
||||
<meta name="robots" content="noindex">
|
||||
<link rel="stylesheet" href="/displays/css/display.css">
|
||||
<style>
|
||||
:root {
|
||||
/* Brand */
|
||||
--panelBgLogo: <?= $cleanColor ?>;
|
||||
--text-color: <?= $cleanColorText ?>;
|
||||
|
||||
/* Backgrounds */
|
||||
--bg: <?= $displayColorScoringBg ?>;
|
||||
--bg-soft: <?= $displayColorScoringBgSoft ?>26;
|
||||
--panel: <?= $displayColorScoringPanel ?>;
|
||||
--panel-soft: <?= $displayColorScoringPanelSoft ?>;
|
||||
|
||||
/* Typography */
|
||||
--text-main: <?= $displayColorScoringPanelText ?>;
|
||||
--text-muted: <?= $displayColorScoringPanelTextSoft ?>;
|
||||
|
||||
/* Noten */
|
||||
--color-note-l: <?= $displayColorScoringPanelTextNoteL ?>;
|
||||
--color-note-r: <?= $displayColorScoringPanelTextNoteR ?>;
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="errorws">
|
||||
<img class="logoimg" src="https://cdn-icons-png.freepik.com/512/12890/12890341.png?ga=GA1.1.991281105.1761199359">
|
||||
<p class="errortext">Keine WebSocket Verbindung</p>
|
||||
<p class="errortextSmall">Versuche Verbindung... (Versuch <span id="counterTries"></span> / <span id="counterMaxTries"></span>)</p>
|
||||
</div>
|
||||
|
||||
<div class="noWsConnection">
|
||||
<div class="rotator">
|
||||
<img src="https://cdn-icons-png.freepik.com/512/12890/12890341.png?ga=GA1.1.991281105.1761199359">
|
||||
<p>Keine WebSocket Verbindung, Syncronisation via FETCH</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logobg">
|
||||
<img id="jsImgLogo" class="logoimg" src="/intern/img/logo-normal.png">
|
||||
<p class="logotext"><?= $wkName ?></p>
|
||||
<p class="logoctext"></p>
|
||||
</div>
|
||||
|
||||
<div class="pagediv">
|
||||
<div class="display-row row1">
|
||||
<p class="row1text"></p>
|
||||
</div>
|
||||
<div class="display-row row2">
|
||||
<div class="row2text">
|
||||
<p class="row2_1text sds"></p>
|
||||
<p class="row2_2text"></p>
|
||||
</div>
|
||||
<div class="start_div">
|
||||
<p class="start_text"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="display-row row3">
|
||||
<p class="row3_1text"></p>
|
||||
<p class="row3_2text"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
const userAccess = "<?php echo $lastSegment; ?>_display";
|
||||
const jsonUrl = "<?php echo $jsonUrl; ?>";
|
||||
const jsonUrlconfig = "<?php echo $jsonUrlconfig; ?>";
|
||||
|
||||
// --- State Management ---
|
||||
let ws;
|
||||
let filedConectCount = 1;
|
||||
const fallbackConectCount = 10;
|
||||
const RETRY_DELAY = 1000;
|
||||
const FALLBACK_POLL_INTERVAL = 3000; // Fetch every 3 seconds if WS is dead
|
||||
|
||||
let fallbackTimer = null;
|
||||
|
||||
// Hold our current data in memory
|
||||
let displayConfig = { type: 'logo', ctext: '' };
|
||||
let currentScore = {};
|
||||
let lastUniqueId = null;
|
||||
|
||||
// UI Elements
|
||||
const counterTriesEl = document.getElementById('counterTries');
|
||||
const counterMaxTriesEl = document.getElementById('counterMaxTries');
|
||||
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
if(counterMaxTriesEl) counterMaxTriesEl.innerHTML = fallbackConectCount;
|
||||
|
||||
// --- Fullscreen Listener ---
|
||||
const fs = document.documentElement;
|
||||
document.body.addEventListener('click', () => {
|
||||
if (fs.requestFullscreen) fs.requestFullscreen();
|
||||
else if (fs.webkitRequestFullscreen) fs.webkitRequestFullscreen();
|
||||
else if (fs.mozRequestFullScreen) fs.mozRequestFullScreen();
|
||||
else if (fs.msRequestFullscreen) fs.msRequestFullscreen();
|
||||
});
|
||||
|
||||
// --- WebSocket Logic ---
|
||||
function startWebSocket() {
|
||||
console.log("Attempting WebSocket connection...");
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=${userAccess}`);
|
||||
} catch (err) {
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected!");
|
||||
document.querySelector('.errorws').style.display = 'none';
|
||||
document.querySelector('.noWsConnection').style.display = 'none';
|
||||
document.querySelector('.pagediv').classList.remove("manuel");
|
||||
|
||||
filedConectCount = 1;
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
|
||||
// Stop fallback polling since WS is alive
|
||||
stopFallbackPolling();
|
||||
|
||||
// Do ONE initial fetch to sync current state
|
||||
fetchFullState();
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
document.querySelector('.errorws').style.display = 'flex';
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
document.querySelector('.errorws').style.display = 'flex';
|
||||
scheduleRetry();
|
||||
};
|
||||
|
||||
ws.addEventListener("message", msg => {
|
||||
let msgJSON;
|
||||
try {
|
||||
msgJSON = JSON.parse(msg.data);
|
||||
} catch (error) {
|
||||
return; // Ignore malformed messages
|
||||
}
|
||||
|
||||
// Route the incoming push data
|
||||
switch (msgJSON.type) {
|
||||
case "EINSTELLUNGEN_DISPLAY_UPDATE":
|
||||
updateSettings(msgJSON.payload.key, msgJSON.payload.value);
|
||||
break;
|
||||
case "UPDATE_DISPLAYCONTROL":
|
||||
// Expecting payload: { type: 'logo'|'ctext'|'scoring', ctext: '...' }
|
||||
displayConfig = msgJSON.payload;
|
||||
renderDOM();
|
||||
break;
|
||||
case "UPDATE_SCORE":
|
||||
// Expecting payload: the exact same structure as your jsonUrl outputs
|
||||
currentScore = msgJSON.payload;
|
||||
renderDOM();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
||||
|
||||
if (filedConectCount >= fallbackConectCount) {
|
||||
// MAX RETRIES REACHED -> Enter Fallback Mode
|
||||
document.querySelector('.errorws').style.display = 'none';
|
||||
document.querySelector('.noWsConnection').style.display = 'flex';
|
||||
document.querySelector('.pagediv').classList.add("manuel");
|
||||
startFallbackPolling();
|
||||
} else {
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
setTimeout(startWebSocket, RETRY_DELAY);
|
||||
filedConectCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fallback Polling Logic ---
|
||||
function startFallbackPolling() {
|
||||
if (fallbackTimer === null) {
|
||||
console.warn("Starting JSON fallback polling...");
|
||||
fetchFullState(); // Fetch immediately once
|
||||
fallbackTimer = setInterval(fetchFullState, FALLBACK_POLL_INTERVAL);
|
||||
|
||||
// Optionally, keep trying to reconnect the WS slowly in the background
|
||||
setTimeout(startWebSocket, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
function stopFallbackPolling() {
|
||||
if (fallbackTimer !== null) {
|
||||
clearInterval(fallbackTimer);
|
||||
fallbackTimer = null;
|
||||
console.log("Stopped JSON fallback polling.");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Data Fetching (Initial Sync & Fallback) ---
|
||||
async function fetchFullState() {
|
||||
try {
|
||||
// Fetch both config and score simultaneously
|
||||
const [resConfig, resScore] = await Promise.all([
|
||||
fetch(jsonUrlconfig + '?t=' + Date.now(), { cache: "no-store" }),
|
||||
fetch(jsonUrl + '?t=' + Date.now(), { cache: "no-store" })
|
||||
]);
|
||||
|
||||
if (resConfig.ok) displayConfig = await resConfig.json();
|
||||
if (resScore.ok) currentScore = await resScore.json();
|
||||
|
||||
renderDOM();
|
||||
} catch (err) {
|
||||
console.error("Error fetching JSON:", err);
|
||||
const container = document.getElementById('score');
|
||||
if(container) {
|
||||
container.innerHTML = "";
|
||||
container.style.backgroundColor = "black";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- The Master Renderer ---
|
||||
function renderDOM() {
|
||||
const logobg = document.querySelector('.logobg');
|
||||
const ctext = document.querySelector('.logoctext');
|
||||
|
||||
if (displayConfig.type === 'logo' || displayConfig.type === 'ctext') {
|
||||
// LOGO OR CUSTOM TEXT MODE
|
||||
if (logobg) logobg.style.opacity = "1";
|
||||
if (ctext) ctext.innerText = (displayConfig.type === 'ctext') ? (displayConfig.ctext || '') : '';
|
||||
|
||||
} else if (displayConfig.type === 'scoring') {
|
||||
// SCORING MODE
|
||||
if (logobg) logobg.style.opacity = "0";
|
||||
|
||||
if (currentScore.uniqueid !== lastUniqueId) {
|
||||
lastUniqueId = currentScore.uniqueid;
|
||||
// Reset any animation/repeat logic here if needed
|
||||
}
|
||||
|
||||
const safeText = (selector, text) => {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) el.innerText = text !== undefined ? text : '';
|
||||
};
|
||||
|
||||
safeText('.row1text', `${currentScore.vorname || ''} ${currentScore.name || ''}`);
|
||||
safeText('.row2_1text', currentScore.verein ? `${currentScore.verein}, ` : '');
|
||||
safeText('.row2_2text', currentScore.programm || '');
|
||||
|
||||
const starttext = document.querySelector('.start_text');
|
||||
const row2El = document.querySelector('.row2');
|
||||
if (starttext && row2El) {
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
const dangerColor = rootStyles.getPropertyValue('--danger').trim();
|
||||
const successColor = rootStyles.getPropertyValue('--success').trim();
|
||||
|
||||
if (currentScore.start === true) {
|
||||
row2El.style.setProperty('--colorStartDiv', successColor);
|
||||
starttext.innerHTML = 'Start';
|
||||
} else {
|
||||
row2El.style.setProperty('--colorStartDiv', dangerColor);
|
||||
starttext.innerHTML = 'Stop';
|
||||
}
|
||||
}
|
||||
|
||||
safeText('.row3_1text', currentScore.noteLinks);
|
||||
safeText('.row3_2text', currentScore.noteRechts);
|
||||
}
|
||||
|
||||
fitTextAll();
|
||||
}
|
||||
|
||||
// --- Settings & UI Handlers ---
|
||||
function updateSettings(type, value) {
|
||||
|
||||
const sanitizeHex = (val) => val.replace(/[^0-9a-fA-F#]/g, '');
|
||||
|
||||
switch (type) {
|
||||
case 'wkName':
|
||||
const logotext = document.querySelector('.logotext');
|
||||
if (logotext) logotext.innerHTML = value;
|
||||
break;
|
||||
case 'displayColourLogo':
|
||||
document.documentElement.style.setProperty('--panelBgLogo', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayTextColourLogo':
|
||||
document.documentElement.style.setProperty('--text-color', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringBg':
|
||||
document.documentElement.style.setProperty('--bg', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringBgSoft':
|
||||
document.documentElement.style.setProperty('--bg-soft', sanitizeHex(value) + "26");
|
||||
break;
|
||||
case 'displayColorScoringPanel':
|
||||
document.documentElement.style.setProperty('--panel', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelSoft':
|
||||
document.documentElement.style.setProperty('--panel-soft', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelText':
|
||||
document.documentElement.style.setProperty('--text-main', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelTextSoft':
|
||||
document.documentElement.style.setProperty('--text-muted', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelTextNoteL': // Matching your PHP typo
|
||||
document.documentElement.style.setProperty('--color-note-l', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelTextNoteR': // Matching your PHP typo
|
||||
document.documentElement.style.setProperty('--color-note-r', sanitizeHex(value));
|
||||
break;
|
||||
case 'logo-normal':
|
||||
const jsImgLogo = document.getElementById('jsImgLogo');
|
||||
if(jsImgLogo) jsImgLogo.src = '/intern/img/logo-normal.png?' + Date.now();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Text Resizing Engine ---
|
||||
function isOverflown(parent, elem, heightscale, widthscale, paddingtext) {
|
||||
return (
|
||||
(elem.scrollWidth + paddingtext) > (parent.clientWidth / widthscale) ||
|
||||
(elem.scrollHeight + paddingtext) > (parent.clientHeight / heightscale)
|
||||
);
|
||||
}
|
||||
|
||||
function fitTextElement(elem, { minSize = 10, maxSize = 1000, step = 1, unit = 'px' } = {}) {
|
||||
if (!elem) return;
|
||||
const parent = elem.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
// FIXED: Declare variables properly so they don't leak into the global scope
|
||||
let heightscale = 1;
|
||||
let widthscale = 1;
|
||||
let paddingtext = 60;
|
||||
|
||||
if (parent.classList.contains('row2text')) {
|
||||
heightscale = 2;
|
||||
paddingtext = 0;
|
||||
}
|
||||
if (parent.classList.contains('row3')) {
|
||||
widthscale = 2;
|
||||
}
|
||||
|
||||
let size = minSize;
|
||||
elem.style.whiteSpace = 'nowrap';
|
||||
elem.style.fontSize = size + unit;
|
||||
|
||||
while (size < maxSize && !isOverflown(parent, elem, heightscale, widthscale, paddingtext)) {
|
||||
size += step;
|
||||
elem.style.fontSize = size + unit;
|
||||
}
|
||||
|
||||
elem.style.fontSize = (size - step) + unit;
|
||||
}
|
||||
|
||||
function fitTextAll() {
|
||||
const paragraphs = document.querySelectorAll('.pagediv p');
|
||||
paragraphs.forEach(p => fitTextElement(p));
|
||||
}
|
||||
|
||||
window.addEventListener('resize', fitTextAll);
|
||||
|
||||
// --- Initialize ---
|
||||
startWebSocket();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user