document.addEventListener('keydown', function (e) { // Mac = Option, Windows/Linux = Alt const isOptionOrAlt = e.altKey || e.metaKey; if (isOptionOrAlt && e.shiftKey && e.key.toLowerCase() === 'f') { toggleFullscreen(); } if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { e.preventDefault(); } }); function toggleFullscreen() { // If not in fullscreen → enter fullscreen if (!document.fullscreenElement) { document.documentElement.requestFullscreen() .catch(err => console.error("Fullscreen error:", err)); } // If already fullscreen → exit fullscreen else { document.exitFullscreen(); } } let messagePosArray = []; const csrf_token = window.CSDR_TOKEN; const text = document.getElementById('wsInfo'); const rect = document.getElementById('wsInfoRectangle'); let ws; let firstConnect = true; let wsOpen = false; const RETRY_DELAY = 2000; // ms const WSaccesstype = "kampfrichter"; const WSaccess = window.FREIGABE; const WSaccessPermission = "W"; const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php'; async function fetchNewWSToken(accesstype, access, accessPermission) { try { const response = await fetch(urlAjaxNewWSToken, { method: "POST", headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ csrf_token, accesstype, access, accessPermission }) }); if (response.status === 403) { console.warn("Please Re-Autenithicate. Reloading page..."); location.reload(); return null; } if (!response.ok) return null; const data = await response.json(); return data.success ? data.token : null; } catch (error) { console.error("Token fetch failed:", error); return null; } } async function startWebSocket() { console.log("Attempting WebSocket connection..."); let token; if (firstConnect) { token = window.WS_ACCESS_TOKEN; } else { token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission); } if (!token) { console.error("No valid token available. Retrying..."); scheduleRetry(); return; } try { ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`); } catch (err) { console.error("Malformed WebSocket URL", err); scheduleRetry(); return; } ws.onopen = () => { console.log("WebSocket connected!"); if (!firstConnect) { displayMsg(1, "Live Syncronisation wieder verfügbar"); } firstConnect = true; wsOpen = true; rect.innerHTML = ''; requestAnimationFrame(() => { text.style.opacity = 1; }); }; ws.onerror = (event) => { console.error("WebSocket error observed." + JSON.stringify(event)); }; ws.onclose = (event) => { if (firstConnect) { displayMsg(0, "Live Syncronisation verloren"); console.log(event); } firstConnect = false; wsOpen = false; rect.innerHTML = ''; requestAnimationFrame(() => { text.style.opacity = 1; }); scheduleRetry(); }; } function scheduleRetry() { console.log(`Retrying in ${RETRY_DELAY}ms...`); setTimeout(startWebSocket, RETRY_DELAY); } // Start the initial connection attempt safely startWebSocket(); function updateRunButtons(targetCount, personId, $container) { if (targetCount === 0) { return; } const geraetId = $container.find('.submit-display-result').first().data('geraet-id') || ""; const currentCount = $container.find('.submit-display-result').length; $container.find('.submit-display-result').removeClass('hidden'); if (targetCount > currentCount) { for (let i = currentCount + 1; i <= targetCount; i++) { const buttonHtml = ` `; $container.append(buttonHtml); } $container.find('.submit-display-result[data-run="1"]').val('Ergebnis anzeigen (Run 1)'); } else if (targetCount < currentCount) { for (let i = currentCount; i > targetCount; i--) { $container.find(`.submit-display-result[data-run="${i}"]`).remove(); } if (targetCount === 1 && $container.find('.submit-display-result').length === 1) { $container.find('.submit-display-result').val('Ergebnis anzeigen'); } } $container.find('.submit-display-result').each(function() { $(this).attr('data-person-id', personId); }); } $.fn.updateCurrentEdit = function() { return this.each(function() { const $input = $(this); const url = '/intern/scripts/kampfrichter/ajax/ajax-kampfrichter_currentedit.php'; if ($input.attr('data-person-id') === "0"){ $('
', { action: '/intern/kampfrichter', method: 'post' }) .append($('', { type: 'hidden', name: 'next_subabt', value: 1 })) .append($('', { type: 'hidden', name: 'next_subabt_submit', value: '>' })) .append($('', { type: 'hidden', name: 'csrf_token', value: csrf_token })) .appendTo('body') .submit(); } fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ csrf_token, editId: $input.attr('data-person-id'), geraet: $input.attr('data-geraet-id') ?? null }) }) .then(res => res.json()) .then(response => { if (response.success) { $(".current-turnerin-name").css({ 'color': '#209200ff', 'transition': 'all 0.3s ease-out' }); setTimeout(() => $(".current-turnerin-name").css({ 'color': '' }), 2000); $(".div_edit_values_user").css("display", "flex"); $(".heading_fv_selturnerin")[0].scrollIntoView(); $(".current-turnerin-name").text(response.titel); $(".fv_nextturnerin").text(response.nturnerin?.name ?? ''); $(".fv_nextturnerin").val(response.nturnerin?.id ?? 0).attr('data-person-id', response.nturnerin?.id ?? 0); $(".submit-display-turnerin").css("opacity", "1"); $(".submit-display-start").css("opacity", "1"); $(".submit-display-result").css("opacity", "1"); const $editAllDiv = $('.div_edit_values_all_gereate'); const noten = response.noten; const personId = response.id; const programmId = response.programm_id; $editAllDiv.find(`.submit-display-turnerin`).closest('.all_vaules_div').addClass('hidden'); // 1. Loop directly through the 'noten' object for (const [geraetId, disciplineData] of Object.entries(noten)) { // Find the specific DOM wrapper for this Geraet using the outer div // Assuming your PHP renders the tables with the correct geraetId on the button const $disciplineWrapper = $editAllDiv.find(`.submit-display-turnerin[data-geraet-id="${geraetId}"]`).closest('.all_vaules_div'); if ($disciplineWrapper.length === 0) continue; $disciplineWrapper.removeClass('hidden'); // --- UPDATE GENERAL BUTTONS FOR THIS GERAET --- $disciplineWrapper.find(".submit-display-turnerin, .submit-display-start").attr({ 'data-person-id': personId, 'data-geraet-id': geraetId }); $disciplineWrapper.find(".submit-musik-start, .submit-musik-stopp").attr({ 'data-id': personId, 'data-geraet': geraetId }); // 2. Identify master containers for this specific discipline const $masterContainer = $disciplineWrapper.find('.singleNotentable').first(); const $displayresultDiv = $disciplineWrapper.find('.div-submit-display-result'); $masterContainer.find('.note-container').addClass('hidden'); // 3. CLEANUP: Remove previously generated runs and buttons $disciplineWrapper.find('.singleNotentable').not(':first').remove(); $displayresultDiv.find('.submit-display-result').not(':first').remove(); const $originalResultBtn = $displayresultDiv.find('.submit-display-result').first(); $displayresultDiv.find('.submit-display-result').addClass('hidden'); const runKeys = Object.keys(disciplineData).sort((a, b) => a - b); const totalRuns = runKeys.length; // 4. Process each Run in the data runKeys.forEach(runNum => { const runInt = parseInt(runNum); let $currentRunContainer; if (runInt === 1) { $currentRunContainer = $masterContainer; } else { // CLONE the entire container for Run 2, 3, etc. $currentRunContainer = $masterContainer.clone(); $currentRunContainer.addClass(`run-container-block run-${runNum}`); $currentRunContainer.insertAfter($disciplineWrapper.find('.singleNotentable').last()); } // 5. Update all Tables and Inputs inside this Run Container for (const [noteId, value] of Object.entries(disciplineData[runNum])) { const $table = $currentRunContainer.find(`.note-container[data-note-id="${noteId}"]`); $table.removeClass('hidden'); // Update Header to show Run Number if (runInt > 1) { const $header = $table.find('.note-name-header'); if (!$header.find('.rm-tag').length) { $header.append(` (R${runNum})`); } } // Update Input attributes and value const $input = $table.find('input'); $input.attr({ 'data-run': runNum, 'data-person-id': personId, 'data-geraet-id': geraetId, 'data-programm-id': programmId }).val(value ?? ''); } // 6. Remove tables cloned from Run 1 that don't exist in Run 2+ if (runInt > 1) { $currentRunContainer.find('input[data-run="1"]').closest('.note-container').remove(); } }); // Ensure the UI script tracking the buttons is updated last updateRunButtons(totalRuns, personId, $displayresultDiv); } //$(".submit-display-result").attr('data-person-id', response.id); } else { displayMsg(0, response.message); $input.css({ 'background-color': '#f8d7da', 'color': '#fff', 'transition': 'all 0.3s ease-out' }); setTimeout(() => $input.css({ 'background-color': '', 'color': '' }), 2000); } }) .catch(err => { $input.css('background-color', '#f8d7da'); console.error('AJAX fetch error:', err); }); }); }; function getRandomInt(max) { return Math.floor(Math.random() * max); } jQuery(document).ready(function($) { $('.editTurnerin').on('click', function() { $(this).updateCurrentEdit(); }); const $ajaxInputDiv = $('.div_edit_values_all_gereate'); $ajaxInputDiv.on('change', '.ajax-input', function(e) { const $input = $(this); const url = `/intern/scripts/kampfrichter/ajax/ajax-update_value_kampfrichter.php`; personId = $input.attr('data-person-id'); fieldTypeId = $input.attr('data-field-type-id'); programmId = $input.attr('data-programm-id'); gereatId = $input.attr('data-geraet-id'); runNum = $input.attr('data-run') || 1; jahr = window.AKTUELLES_JAHR; value = $input.val(); fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ csrf_token, personId: personId, fieldTypeId: fieldTypeId, gereatId: gereatId, run: runNum, jahr: jahr, value: value }) }) .then(res => res.json()) .then(response => { if (response.success) { let objValues = []; const rowId = $input.attr('data-id'); $input.css({"color": "#0e670d", "font-weight": "600"}); setTimeout(() => $input.css({'color': '', "font-weight": ""}), 2000); const noten = response.noten; for (const [keyG, noteGroup] of Object.entries(noten)) { for (const [key, runGroup] of Object.entries(noteGroup)) { for (const [run, value] of Object.entries(runGroup)) { const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${personId}"][data-run="${run}"]`); $elements.each((ind, el) => { const $el = $(el); if ($el.is('input')) { $el.val(value ?? ''); } else { const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true'; const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0; let hasValue = false; if (isNullable && floatValZero || value === '') { $el.text('-').addClass('emtyPlaceholder'); } else { $el.text(value).removeClass('emtyPlaceholder'); hasValue = true; } updateDependentVisibility($el, hasValue); } }); } } } if (selecteddiscipline === 'A') { const event = new CustomEvent('valueChanged', { detail: { noten: noten, programmId: programmId, personId: personId } }); window.dispatchEvent(event); } if (wsOpen) { ws.send(JSON.stringify({ type: "KAMPFRICHTER_UPDATE", payload: { discipline: window.FREIGABE, gereatId: gereatId, personId: personId, programmId: programmId, jahr: jahr, noten: noten } })); } } else { // Flash red on error $input.css({'color': '#ff6a76ff'}); displayMsg(0, response.message || 'Unknown error'); console.error(response.message || 'Unknown error'); } }) .catch(err => { $input.css({'color': '#670d0d'}); console.error('AJAX fetch error:', err); }); }); $('.submit-display-turnerin').on('click', function() { const $input = $(this); const geraetId = $input.attr('data-geraet-id'); // Build the URL with GET parameters safely const url = '/intern/scripts/kampfrichter/ajax/displays/ajax-display-functions.php'; fetch(url,{ method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ csrf_token, personId: $input.attr('data-person-id'), geraetId, jahr: window.AKTUELLES_JAHR, type: "neu" }) }) .then(res => res.json()) .then(response => { if (response.success) { if (wsOpen) { ws.send(JSON.stringify({ type: "UPDATE_SCORE", payload: { geraet: response.nameGeraet, geraetId, data: response.data } })); } else { displayMsg(2, 'Keine live Syncrosation, Verzögerungen'); } displayMsg(1, 'Neue Person wird angezigt'); $input.css('opacity', 0.5); } else { displayMsg(0, response.message); } }) .catch(err => { $input.css('background-color', '#f8d7da'); displayMsg(0, 'AJAX fetch error:' + err); console.error('AJAX fetch error:', err); }); }); $('.submit-display-start').on('click', function() { const $input = $(this); const geraetId = $input.attr('data-geraet-id'); const personId = $input.attr('data-person-id') const url = '/intern/scripts/kampfrichter/ajax/displays/ajax-display-functions.php'; const dataType = $input.attr('data-type'); fetch(url,{ method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ csrf_token, geraetId, personId, dataType: dataType, type: "start" }) }) .then(res => res.json()) .then(response => { if (response.success) { if (wsOpen) { ws.send(JSON.stringify({ type: "UPDATE_SCORE", payload: { geraet: response.nameGeraet, geraetId, data: response.data } })); } else { displayMsg(2, 'Keine live Syncrosation, Verzögerungen'); } if (dataType == 1) { displayMsg(1, 'Start freigegeben'); } else if (dataType == 0) { displayMsg(1, 'Startfreigabe entzogen'); } } else { displayMsg(0, response.message); } }) .catch(err => { $input.css('background-color', '#f8d7da'); displayMsg(0, 'AJAX fetch error:' + err); console.error('AJAX fetch error:', err); }); }); $('.submit-musik-start').on('click', function() { const $input = $(this); const geraetId = $input.attr('data-geraet-id'); // Build the URL with GET parameters safely const url = `/intern/scripts/kampfrichter/ajax/ajax-update_kampfrichter_start_musik.php`; fetch(url,{ method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ csrf_token, personId: $input.attr('data-id'), geraetId }) }) .then(res => res.json()) .then(response => { if (wsOpen) { ws.send(JSON.stringify({ type: "AUDIO", payload: { audioDiscipline: geraetId } })); } else { displayMsg(2, 'Keine live Syncrosation, Verzögerungen'); } if (response.success) { displayMsg(1, 'Musik wird abgespielt werden'); } else { displayMsg(0, 'Error: ' + response.message); } }) .catch(err => { $input.css('background-color', '#f8d7da'); displayMsg(0, 'AJAX fetch error:' + err); console.error('AJAX fetch error:', err); }); }); $('.submit-musik-stopp').on('click', function() { const $input = $(this); const geraetId = $input.attr('data-geraet-id'); // Build the URL with GET parameters safely const url = `/intern/scripts/kampfrichter/ajax/ajax-update_kampfrichter_stopp_musik.php`; fetch(url,{ method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ csrf_token, geraetId }) }) .then(res => res.json()) .then(response => { if (response.success) { if (wsOpen) { ws.send(JSON.stringify({ type: "AUDIO", payload: { audioDiscipline: geraetId } })); } else { displayMsg(2, 'Keine live Syncrosation, Verzögerungen'); } displayMsg(1, 'Musik wird gestoppt werden'); } else { alert('Error: ' + response.message); } }) .catch(err => { $input.css('background-color', '#f8d7da'); displayMsg(0, 'AJAX fetch error:' + err); console.error('AJAX fetch error:', err); }); }); $('.div-submit-display-result').on('click', '.submit-display-result', function() { $input = $(this); const geraetId = $input.attr('data-geraet-id'); const personId = $input.attr('data-person-id'); // Build the URL with GET parameters safely const url = '/intern/scripts/kampfrichter/ajax/displays/ajax-display-functions.php'; fetch(url,{ method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ csrf_token, personId, geraetId, run: $input.attr("data-run"), jahr: window.AKTUELLES_JAHR, type: "result" }) }) .then(res => res.json()) .then(response => { if (response.success) { if (wsOpen) { ws.send(JSON.stringify({ type: "UPDATE_SCORE", payload: { geraetId, geraet: response.nameGeraet, data: response.data } })); ws.send(JSON.stringify({ type: "UPDATE_RANKLIVE_SCORE", payload: { geraetId, personId, jahr: window.AKTUELLES_JAHR, rankLive: response.rankLive } })); } else { displayMsg(2, 'Keine live Syncrosation, Verzögerungen'); } $input.css('opacity', 0.5); displayMsg(1, 'Resultat wird angezeigt'); } else { alert('Error: ' + response.message); } }) .catch(err => { $input.css('background-color', '#f8d7da'); displayMsg(0, 'AJAX fetch error:' + err); console.error('AJAX fetch error:', err); }); }); }); function updateDependentVisibility($changedEl, hasValue = false) { const uid = $changedEl.attr('data-uid'); if (uid === undefined || uid === null) return; const $row = $changedEl.closest('tr'); if ($row.length !== 1) return; const isVisible = $changedEl.hasClass('emtyPlaceholder'); const doesExist = $changedEl.hasClass('nonExistentEl'); const $linkedEls = $row.find(`.singleValueSpan[data-linked-el-uid="${uid}"]`); $linkedEls.each((ind, el) => { const $linkedEl = $(el); if (!hasValue) { hasValue = $linkedEl.hasClass('changebleValue') && $linkedEl.text().trim() !== '-'; } $linkedEl.toggleClass('emtyPlaceholder', (isVisible && !hasValue)).toggleClass('nonExistentEl', doesExist); const linkedElUid = $linkedEl.data('linked-el-uid'); const $subLinkedEls = $row.find(`.singleValueSpan[data-linked-el-uid="${linkedElUid}"]`); if ($subLinkedEls.length > 0) updateDependentVisibility($linkedEl); }); } function initConditionalVisibility() { const $table = $('table.customDisplayEditorTable'); const $rows = $table.find('tr'); $rows.each((ind, el) => { const $row = $(el); $row.find('.singleValueSpan[data-linked-el-uid]').each(function() { const linkedUid = $(this).data('linked-el-uid'); const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`); if ($linkedEl.length === 1) { updateDependentVisibility($linkedEl); } }); }) } initConditionalVisibility(); initConditionalVisibility(); const selecteddiscipline = window.FREIGABE; ws.addEventListener("message", event => { // Use 'event' as it's more standard than 'blob' let msgOBJ; try { msgOBJ = JSON.parse(event.data); } catch (error) { return; } // Ensure it's an UPDATE type (matches your sendToGroup logic) if (msgOBJ?.type === "UPDATE") { const data = msgOBJ.payload; // Check access rights if (data.discipline === selecteddiscipline.toLowerCase() || selecteddiscipline === 'A') { const noten = data.noten; for (const [keyG, noteGroup] of Object.entries(noten)) { for (const [key, runGroup] of Object.entries(noteGroup)) { for (const [run, value] of Object.entries(runGroup)) { // Select all matching elements (inputs and spans) const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${data.personId}"][data-run="${run}"]`); $elements.each((ind, el) => { const $el = $(el); if ($el.is('input')) { $el.val(value ?? ''); // Visual feedback: Flash color const nativeEl = $el[0]; nativeEl.style.transition = 'color 0.3s'; nativeEl.style.color = '#008e85'; setTimeout(() => { nativeEl.style.color = ''; }, 1000); } else { const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true'; const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0; let hasValue = false; if (isNullable && floatValZero || value === '') { $el.text('-').addClass('emtyPlaceholder'); } else { $el.text(value).removeClass('emtyPlaceholder'); hasValue = true; } updateDependentVisibility($el, hasValue); $el.removeClass('updated'); $el.addClass('updated'); setTimeout(() => { $el.removeClass('updated'); }, 2000); } }); } } } } } });