439 lines
16 KiB
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>
|