// Socket.IO laeuft am selben Host und Port wie die Seite, io() ohne Argument // verbindet sich dorthin zurueck. const socket = io(); // ###################################################################################################################################################### // Websockets event handler // ###################################################################################################################################################### // 'connect' feuert bei jeder Verbindung, auch nach einem Reconnect. Da der Pi das // WLAN selbst aufspannt, sind kurze Abbrueche normal: danach werden die Werte neu // geholt, damit das Panel nicht auf einem veralteten Stand stehen bleibt. socket.on('connect', () => { console.log("Verbunden"); initialUpdate(); }); socket.on('timerDurationLeft', (message) => { console.log(message); // Log durationLeft in client console document.getElementById("durationLeft").innerHTML = message; // Display durationLeft in corresponding html Element }); socket.on('timerEnded', (message) => { initialUpdate(); // Update admin frontend to set correct buttonstatus }); socket.on('score', (message) => { console.log(message); // Log score in client console scoreGetValues(); // document.getElementById("score").innerHTML = message; // Display score in corresponding html Element }); // ###################################################################################################################################################### // General functions // ###################################################################################################################################################### // Holt JSON von einem Endpunkt. Liefert null statt zu werfen, wenn der Server nicht // erreichbar ist oder mit einem Fehler antwortet — sonst bricht der Aufrufer still ab. async function fetchJson(url, options) { try { const response = await fetch(url, options); if(!response.ok) { console.error("Anfrage fehlgeschlagen:", url, response.status); return null; } return await response.json(); } catch (err) { console.error("Server nicht erreichbar:", url, err); return null; } } // Initial update gets called whenever page has been loaded async function initialUpdate() { etcGetValues(); // Request new values for etc stuff timerGetValues(); // Request new values for timer scoreGetValues(); // Request new values for score dbGetValues(); // Request new values for db contents } // Update DOM timer elements from passed values function updateEtcFrontend(values) { console.log(values); document.getElementById("etcSwitchEnable").checked = values.splashscreenEnabled;// Set switch status to received score enabled value } // Update DOM timer elements from passed values function updateTimerFrontend(values) { console.log(values); // Print received data values.duration = moment.duration(values.duration); // Create moment object from ISO 8601 string values.durationLeft = moment.duration(values.durationLeft); // Create moment object from ISO 8601 string document.getElementById("durationLeft").innerHTML = values.print; // Display durationLeft as prettyfied string if(values.isPaused) { // Set button state depending on timer status document.getElementById("timerStartBtn").disabled = false; document.getElementById("timerPauseBtn").disabled = true; } else { document.getElementById("timerStartBtn").disabled = true; document.getElementById("timerPauseBtn").disabled = false; } } // Build colname to show Teams on Adminpanel based on isSpielgemeinschaft function buildColFromIsSpielgemeinschaft(team) { if(team.isSpielgemeinschaft) { return team.name + "
" + team.name2; } else { return team.name + "
 "; } } // Update DOM score elements from passed values function updateScoreFrontend(values) { console.log(values); // Print received data document.getElementById("scoreSwitchEnable").checked = values.enabled; // Set switch status to received score enabled value document.getElementById("scoreSwitchReferenceMirrored").checked = values.referenceMirrored; // Set switch status to received score enabled value // Config Teams - Input current teams into text fields document.getElementById("teamAname").value = values.teamA.name; document.getElementById("teamAname2").value = values.teamA.name2; document.getElementById("teamAisSpielgemeinschaft").checked = values.teamA.isSpielgemeinschaft; document.getElementById("teamBname").value = values.teamB.name; document.getElementById("teamBname2").value = values.teamB.name2; document.getElementById("teamBisSpielgemeinschaft").checked = values.teamB.isSpielgemeinschaft; // Show current Teams on admin panel if(!values.referenceMirrored && !values.sideswitch || values.referenceMirrored && values.sideswitch) { // If not mirrored on adminpanel, show like main frontend while considering sideswitch state document.getElementById("score").innerHTML = values.teamA.score + ':' + values.teamB.score; document.getElementById("teamAcolName").innerHTML = buildColFromIsSpielgemeinschaft(values.teamA); document.getElementById("teamBcolName").innerHTML = buildColFromIsSpielgemeinschaft(values.teamB); document.getElementById("teamAinc").setAttribute("onclick", "scoreAlterScore('teamA', 'inc')"); document.getElementById("teamAdec").setAttribute("onclick", "scoreAlterScore('teamA', 'dec')"); document.getElementById("teamBinc").setAttribute("onclick", "scoreAlterScore('teamB', 'inc')"); document.getElementById("teamBdec").setAttribute("onclick", "scoreAlterScore('teamB', 'dec')"); } else if(!values.referenceMirrored && values.sideswitch || values.referenceMirrored && !values.sideswitch) { // Else, swap posistions document.getElementById("score").innerHTML = values.teamB.score + ':' + values.teamA.score; document.getElementById("teamAcolName").innerHTML = buildColFromIsSpielgemeinschaft(values.teamB); document.getElementById("teamBcolName").innerHTML = buildColFromIsSpielgemeinschaft(values.teamA); document.getElementById("teamAinc").setAttribute("onclick", "scoreAlterScore('teamB', 'inc')"); document.getElementById("teamAdec").setAttribute("onclick", "scoreAlterScore('teamB', 'dec')"); document.getElementById("teamBinc").setAttribute("onclick", "scoreAlterScore('teamA', 'inc')"); document.getElementById("teamBdec").setAttribute("onclick", "scoreAlterScore('teamA', 'dec')"); } } // Erzeugt einen Dropdown-Eintrag, der den Teamnamen in das angegebene Feld uebernimmt. // Der Name wird als Text gesetzt und per Closure an den Handler gegeben, statt ihn in // einen onclick-String zu schreiben: Namen mit Apostroph zerlegten sonst den Handler. function createTeamDropdownItem(field, team) { const li = document.createElement("li"); const a = document.createElement("a"); a.className = "dropdown-item"; a.textContent = team; a.addEventListener("click", () => scoreInsertChosenName(field, team)); li.appendChild(a); return li; } // Update DOM for all db contents with the passed values function updateDbFrontend(values) { console.log(values); let teamAnameDropdown = document.getElementById("teamAnameDropdown"); // Dropdown for TeamA name let teamAname2Dropdown = document.getElementById("teamAname2Dropdown"); // Dropdown for TeamA name2 let teamBnameDropdown = document.getElementById("teamBnameDropdown"); // Dropdown for TeamB name let teamBname2Dropdown = document.getElementById("teamBname2Dropdown"); // Dropdown for TeamB name2 // Clear innerHTML of dropdowns teamAnameDropdown.innerHTML = ""; teamAname2Dropdown.innerHTML = ""; teamBnameDropdown.innerHTML = ""; teamBname2Dropdown.innerHTML = ""; let selectDelete = document.getElementById("dbTeamToDelete"); // Select for team deletion from db selectDelete.innerHTML = "" // Clears all existing options from select for(const team of values.teams) { // Create custom Dropdowns with different function arguments for team configuration teamAnameDropdown.appendChild(createTeamDropdownItem("teamAname", team)); teamAname2Dropdown.appendChild(createTeamDropdownItem("teamAname2", team)); teamBnameDropdown.appendChild(createTeamDropdownItem("teamBname", team)); teamBname2Dropdown.appendChild(createTeamDropdownItem("teamBname2", team)); // Create select options for deletion in debugwindow let opt = document.createElement("option"); opt.value = team; opt.textContent = team; selectDelete.appendChild(opt); } } // Inserts the passed name into the passed name text input async function scoreInsertChosenName(field, name) { switch(field) { case 'teamAname': document.getElementById("teamAname").value = name; break; case 'teamAname2': document.getElementById("teamAname2").value = name; break; case 'teamBname': document.getElementById("teamBname").value = name; break; case 'teamBname2': document.getElementById("teamBname2").value = name; break; } } // Request monitor refresh for index frontend async function refreshMonitor() { const response = await fetch("/admin/refreshMonitor"); // Call API Endpoint /admin/timerStart } async function openBrowser() { const response = await fetch("/admin/openBrowser"); // Call API Endpoint /admin/openBrowser } async function killBrowser() { const response = await fetch("/admin/killBrowser"); // Call API Endpoint /admin/killBrowser } // ###################################################################################################################################################### // Etc endpoints // ###################################################################################################################################################### // Request important values for etc stuff async function etcGetValues() { const data = await fetchJson("/admin/etcGetValues"); // Call API Endpoint /admin/etcGetValues if(data) updateEtcFrontend(data); // Update admin frontend } // Toggle index page to also show scoreboard async function etcToggleSplashscreen() { const data = await fetchJson("/admin/etcToggleSplashscreen");// Call API Endpoint /admin/etcToggleSplashscreen if(data) updateEtcFrontend(data); // Update admin frontend refreshMonitor(); // Refresh main monitor to display new frontend } // ###################################################################################################################################################### // Timerfunctions // ###################################################################################################################################################### // Request important timer values async function timerGetValues() { const data = await fetchJson("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues if(data) updateTimerFrontend(data); // Update admin frontend } // Request to start the timer async function timerStart() { const response = await fetch("/admin/timerStart"); // Call API Endpoint /admin/timerStart if(response.status == 200) { // If request successfull const data = await response.json(); // Wait for asyncronous transfer to complete updateTimerFrontend(data); // Update admin frontend } else { // Timer already finished const timerStartErrorToast = bootstrap.Toast.getOrCreateInstance(document.getElementById("timerStartErrorToast")); // Create toast instance byId timerStartErrorToast timerStartErrorToast.show(); // Show error toast } } // Request to pause the timer async function timerPause() { const response = await fetch("/admin/timerPause"); // Call API Endpoint /admin/timerStart if(response.status == 200) { const data = await response.json(); // Wait for asyncronous transfer to complete updateTimerFrontend(data); // Update admin frontend } else { // Timer already paused const timerPauseErrorToast = bootstrap.Toast.getOrCreateInstance(document.getElementById("timerPauseErrorToast")); // Create toast instance byId timerPauseErrorToast timerPauseErrorToast.show(); // Show error toast } } // Request to reset the timer with the specified time async function timerReset() { let newDuration = document.getElementById("timerSelectDuration").value; const data = await fetchJson("/admin/timerReset", { // Call API Endpoint /admin/timerStart with selected new duration in seconds method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify({duration: newDuration}) }); if(data) updateTimerFrontend(data); // Update admin frontend } // Request to increase or decrease the timer depending on passed value in seconds async function timerIncDec(value) { const data = await fetchJson("/admin/timerIncDec", { // Call API Endpoint /admin/timerIncDec with value in seconds to increase or decrease the timer method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify({value: value}) }); if(data) updateTimerFrontend(data); // Update admin frontend } // ###################################################################################################################################################### // Scoreboardfunctions // ###################################################################################################################################################### // Request important values for score async function scoreGetValues() { const data = await fetchJson("/admin/scoreGetValues"); // Call API Endpoint /admin/scoreGetValues if(data) updateScoreFrontend(data); // Update admin frontend } // Toggle index page to also show scoreboard async function scoreToggle() { const data = await fetchJson("/admin/scoreToggle"); // Call API Endpoint /admin/scoreToggle if(data) updateScoreFrontend(data); // Update admin frontend refreshMonitor(); // Refresh main monitor to display new frontend } // Alter the score of passed team in the specified direction async function scoreAlterScore(team, dir) { const data = await fetchJson("/admin/scoreAlterScore", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify({team: team, dir: dir}) }); if(data) updateScoreFrontend(data); // Update admin frontend } // Switch sides async function scoreToggleSideswitch() { const data = await fetchJson("/admin/scoreToggleSideswitch");// Call API Endpoint /admin/scoreSwitch if(data) updateScoreFrontend(data); // Update admin frontend } // Switch sides to mirror admin panel when sitting behind main monitor async function scoreToggleReferenceMirrored() { const data = await fetchJson("/admin/scoreToggleReferenceMirrored");// Call API Endpoint /admin/scoreSwitch if(data) updateScoreFrontend(data); // Update admin frontend } // Clear score async function scoreClearScore() { const data = await fetchJson("/admin/scoreClearScore"); // Call API Endpoint /admin/scoreClearScore if(data) updateScoreFrontend(data); // Update admin frontend } // Configure Teams async function scoreConfigTeams() { teamA = { name: document.getElementById("teamAname").value, name2: document.getElementById("teamAname2").value, isSpielgemeinschaft: document.getElementById("teamAisSpielgemeinschaft").checked }; teamB = { name: document.getElementById("teamBname").value, name2: document.getElementById("teamBname2").value, isSpielgemeinschaft: document.getElementById("teamBisSpielgemeinschaft").checked } console.log(teamA, teamB) const data = await fetchJson("/admin/scoreConfigTeams", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify({teamA: teamA, teamB: teamB}) }); if(data) updateScoreFrontend(data); // Update admin frontend refreshMonitor(); } // ###################################################################################################################################################### // DB functions // ###################################################################################################################################################### // Request important values for db contents async function dbGetValues() { const data = await fetchJson("/admin/dbGetValues"); // Call API Endpoint /admin/dbGetValues if(data) updateDbFrontend(data); // Update admin frontend } // Add team to DB async function dbAddTeam() { const data = await fetchJson("/admin/dbAddTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify({teamName: document.getElementById("dbTeamToAdd").value}) }); document.getElementById("dbTeamToAdd").value = ""; // Empty the text field after sending if(data) updateDbFrontend(data); } // Delete team from DB async function dbDeleteTeam() { let teamName = document.getElementById("dbTeamToDelete").value; const data = await fetchJson("/admin/dbDeleteTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify({teamName: teamName}) }); if(data) updateDbFrontend(data); }