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(); } }); 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; function displayMsg(type, msg) { const colors = ["#900000ff", "#00b200ff"]; if (type !== 0 && type !== 1) return; // idx is ALWAYS a valid non-negative index now const $div = $('
') .css({'border-color': colors[type]}) .text(msg); $('.msgDiv').append($div); // trigger entry animation setTimeout(() => { $div.addClass('show'); }, 200); setTimeout(() => { // First: set the transition properties $div.css({ 'transition': 'all 0.3s ease' }); // Next frame: apply the transform so the transition animates requestAnimationFrame(() => { $div.removeClass('show'); $div.css({ 'transform': 'scaleY(0) translateX(calc(100% + 40px))' }); }); }, 3000); // auto-remove setTimeout(() => { $div.remove(); }, 3500); } const text = document.getElementById('wsInfo'); const rect = document.getElementById('wsInfoRectangle'); let ws; let firstConnect = true; const RETRY_DELAY = 2000; // ms const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php'; async function fetchNewWSToken(freigabe) { try { const response = await fetch(urlAjaxNewWSToken, { method: "POST", headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ access: freigabe, csrf_token }) }); 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('kampfrichter'); } 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; rect.innerHTML = ''; requestAnimationFrame(() => { text.style.opacity = 1; }); }; ws.onerror = (event) => { console.error("WebSocket error observed." + JSON.stringify(event)); }; ws.onclose = (event) => { displayMsg(0, "Live Syncronisation verloren"); firstConnect = 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; 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: '>' })) .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); $(".heading_fv_selturnerin").focus(); $(".div_edit_values_user").css("display", "flex"); $(".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; // 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; // --- 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'); // 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(); const runKeys = Object.keys(disciplineData).sort((a, b) => a - b); const totalRuns = runKeys.length; console.log(totalRuns); // 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}"]`); // 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 }).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); }); }); }; 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 start = performance.now(); const $input = $(this); const url = `/intern/scripts/kampfrichter/ajax/ajax-update_value_kampfrichter.php`; personId = $input.data('person-id'); fieldTypeId = $input.data('field-type-id'); gereatId = $input.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 => { const end = performance.now(); console.log(`Total AJAX time: ${(end - start).toFixed(3)} ms`); 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 [keyN, noteGroup] of Object.entries(noten)) { for (const [key, runGroup] of Object.entries(noteGroup)) { for (const [run, value] of Object.entries(runGroup)) { const selectorBase = `[data-field-type-id="${key}"][data-geraet-id="${keyN}"][data-person-id="${personId}"][data-run="${run}"]`; // Handle Inputs (excluding current one) $(`input.changebleValue${selectorBase}`) .not(this) .val(value ?? ''); // Handle Display elements (Spans/Divs) $(`.changebleValue:not(input)${selectorBase}`) .text(value ?? ''); } } } ws.send(JSON.stringify({ type: "KAMPFRICHTER_UPDATE", payload: { discipline: window.FREIGABE, gereatId: gereatId, personId: personId, 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); }); }); $('.inputnamekr').on('change', function() { const $input = $(this); const url = '/intern/scripts/kampfrichter/ajax/ajax-update_name_kampfrichter_protokoll.php'; fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ csrf_token, aufgabe: $input.data('id'), abteilung: $input.data('abt'), geraet: $input.data('user'), name: $input.val() }) }) .then(res => res.json()) .then(response => { if (response.success) { console.log(response.message); $input.css({ 'background-color': '#a4bf4a', 'color': '#fff', 'transition': 'all 0.3s ease-out' }); setTimeout(() => $input.css({ 'background-color': '', 'color': '' }), 2000); } else { console.error(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); displayMsg(0, 'AJAX fetch error:' + err); }); }); $('.submit-display-turnerin').on('click', function() { const $input = $(this); // 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: $input.attr('data-geraet-id'), jahr: window.AKTUELLES_JAHR, type: "neu" }) }) .then(res => res.json()) .then(response => { if (response.success) { ws.send(JSON.stringify({ type: "UPDATE_SCORE", payload: { geraet: response.nameGeraet, data: response.data } })); displayMsg(1, 'Neue Turnerin 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 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: $input.attr('data-geraet-id'), personId: $input.attr('data-person-id'), dataType: dataType, type: "start" }) }) .then(res => res.json()) .then(response => { if (response.success) { ws.send(JSON.stringify({ type: "UPDATE_SCORE", payload: { geraet: response.nameGeraet, data: response.data } })); 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); // 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, id: $input.attr('data-id'), discipline: $input.data('geraet') }) }) .then(res => res.json()) .then(response => { const end = performance.now(); ws.send(JSON.stringify({ type: "AUDIO", payload: {} })); 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); // 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 }) }) .then(res => res.json()) .then(response => { if (response.success) { ws.send(JSON.stringify({ type: "AUDIO", payload: {} })); 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); // 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: $input.attr('data-geraet-id'), run: $input.attr("data-run"), jahr: window.AKTUELLES_JAHR, type: "result" }) }) .then(res => res.json()) .then(response => { if (response.success) { ws.send(JSON.stringify({ type: "UPDATE_SCORE", payload: { geraet: response.nameGeraet, data: response.data } })); $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); }); }); }); 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.toLowerCase() === 'admin') { 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)) { // OPTIONAL: Skip if the current user is currently focused on this specific input if (document.activeElement.dataset.fieldTypeId === key && document.activeElement.dataset.geraetId === keyG && document.activeElement.dataset.run == run) continue; // 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(function() { const $el = $(this); // Update value or text if ($el.is('input')) { $el.val(value ?? ''); } else { $el.text(value ?? ''); } // Visual feedback: Flash color const nativeEl = $el[0]; nativeEl.style.transition = 'color 0.3s'; nativeEl.style.color = '#008e85'; setTimeout(() => { nativeEl.style.color = ''; }, 1000); }); } } } } } });