Files
WKVS/www/displays/display.php

439 lines
16 KiB
PHP

<?php
/*$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https://" : "http://";
$fullDomain = $protocol . $_SERVER['HTTP_HOST'];*/
$lastSegment = strtolower($_GET['geraet']) ?? '';
$baseDir = $_SERVER['DOCUMENT_ROOT'];
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>