// static/js/app.js – MK Control Frontend document.addEventListener('DOMContentLoaded', () => { console.log('MK Control Frontend geladen'); // ── Elemente ─────────────────────────────────────────────────────────────── const connectBtn = document.getElementById('connect-btn'); const connectSection = document.getElementById('connect-section'); const controlSection = document.getElementById('control-section'); const channelsContainer = document.getElementById('channels-container'); const stopAllBtn = document.getElementById('stop-all-btn'); const reconnectSection = document.getElementById('reconnect-section'); const reconnectBtn = document.getElementById('reconnect-btn'); const statusBadge = document.getElementById('connection-status'); const volumeSlider = document.getElementById('volume-slider'); const volumeDisplay = document.getElementById('volume-display'); if (!connectBtn) { console.warn('Nicht auf der Steuerseite – Connect-Button nicht gefunden'); return; } // ── Config aus Template ──────────────────────────────────────────────────── const config = {{ config | tojson | safe }}; const globalSounds = {{ global_sounds | tojson | safe }}; window.mkConfig = config; // global für Konsistenz console.log("Config im JS:", config); console.log("Globale Sounds:", globalSounds); // ── Status-Anzeige ───────────────────────────────────────────────────────── function updateStatus(connected, message = '') { if (!statusBadge) return; if (connected) { statusBadge.className = 'badge bg-success px-3 py-2 fs-6'; statusBadge.innerHTML = ' Verbunden' + (message ? ` – ${message}` : ''); if (reconnectSection) reconnectSection.style.display = 'none'; } else { statusBadge.className = 'badge bg-danger px-3 py-2 fs-6'; statusBadge.innerHTML = ' Getrennt' + (message ? ` – ${message}` : ''); if (reconnectSection) reconnectSection.style.display = 'block'; } } // Initialer Status updateStatus(false); // ── Automatische Verbindungsprüfung ──────────────────────────────────────── let connectionCheckInterval = null; let failedChecks = 0; const MAX_FAILED_CHECKS = 3; function startConnectionCheck() { if (connectionCheckInterval) clearInterval(connectionCheckInterval); connectionCheckInterval = setInterval(async () => { try { console.log("→ Status-Check ..."); const res = await fetch('/api/status'); const data = await res.json(); if (data.connected) { failedChecks = 0; updateStatus(true); } else { failedChecks++; console.warn(`Status-Check fehlgeschlagen (${failedChecks}/${MAX_FAILED_CHECKS}):`, data.message); if (failedChecks >= MAX_FAILED_CHECKS) { updateStatus(false, data.message || 'Mehrere Checks fehlgeschlagen'); } } } catch (err) { failedChecks++; console.warn(`Status-Check Netzwerkfehler (${failedChecks}/${MAX_FAILED_CHECKS}):`, err); if (failedChecks >= MAX_FAILED_CHECKS) { updateStatus(false, 'Keine Antwort vom Hub/Server'); } } }, 6000); // 6 Sekunden } window.addEventListener('beforeunload', () => { if (connectionCheckInterval) clearInterval(connectionCheckInterval); }); // ── Connect-Button ──────────────────────────────────────────────────────── connectBtn.addEventListener('click', async () => { connectBtn.disabled = true; connectBtn.innerHTML = ' Verbinde...'; try { console.log("→ Sende /api/connect ..."); const response = await fetch('/api/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); console.log("→ Antwort erhalten:", response.status); const result = await response.json(); console.log("→ Resultat:", result); if (result.success) { console.log("→ Connect erfolgreich – blende Sections um"); connectSection.style.display = 'none'; controlSection.style.display = 'block'; reconnectSection.style.display = 'none'; updateStatus(true); startConnectionCheck(); console.log("→ Rufe renderChannels() auf"); renderChannels(); console.log("→ renderChannels() abgeschlossen"); } else { console.warn("→ Connect fehlgeschlagen:", result.message); alert('Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannter Fehler')); updateStatus(false); } } catch (err) { console.error("→ Connect-Fehler:", err); alert('Netzwerk- oder Verbindungsfehler: ' + err.message); updateStatus(false); } finally { connectBtn.disabled = false; connectBtn.innerHTML = ' Mit Hub verbinden'; } }); // ── Alle stoppen ────────────────────────────────────────────────────────── if (stopAllBtn) { stopAllBtn.addEventListener('click', async () => { if (!confirm('Wirklich ALLE Kanäle stoppen?')) return; try { const res = await fetch('/api/stop_all', { method: 'POST' }); const data = await res.json(); if (data.success) { // Alle Motor-Slider zurücksetzen document.querySelectorAll('.motor-slider').forEach(slider => { slider.value = 0; const display = slider.parentElement.querySelector('.value-display'); if (display) display.textContent = '0 %'; }); console.log('Alle Kanäle gestoppt'); alert('Alle Kanäle gestoppt'); } else { alert('Fehler beim Stoppen:\n' + (data.message || 'Unbekannt')); } } catch (err) { console.error('Stop-all Fehler:', err); alert('Netzwerkfehler beim Stoppen aller Kanäle'); } }); } // ── Erneut verbinden ────────────────────────────────────────────────────── if (reconnectBtn) { reconnectBtn.addEventListener('click', async () => { reconnectBtn.disabled = true; reconnectBtn.innerHTML = ' Verbinde...'; try { const response = await fetch('/api/reconnect', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.success) { reconnectSection.style.display = 'none'; controlSection.style.display = 'block'; updateStatus(true); startConnectionCheck(); alert('Verbindung wiederhergestellt!'); } else { alert('Erneute Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannt')); } } catch (err) { console.error('Reconnect-Fehler:', err); alert('Netzwerkfehler beim erneuten Verbinden'); } finally { reconnectBtn.disabled = false; reconnectBtn.innerHTML = ' Erneut verbinden'; } }); } // ── Kanäle dynamisch rendern ────────────────────────────────────────────── function renderChannels() { console.log("renderChannels() START"); console.log("→ config.channels existiert?", !!config?.channels); console.log("→ channels.length:", config?.channels?.length ?? "undefined"); if (!channelsContainer) { console.error("→ channelsContainer nicht gefunden im DOM!"); return; } channelsContainer.innerHTML = ''; console.log("→ Container geleert"); if (!config?.channels || config.channels.length === 0) { console.warn("→ Keine Kanäle erkannt – zeige leere Meldung"); channelsContainer.innerHTML = '

Keine Kanäle in der Konfiguration definiert.

'; return; } console.log("→ Beginne mit Rendern von", config.channels.length, "Kanälen"); config.channels.forEach(channel => { const col = document.createElement('div'); col.className = 'col-md-6 mb-4'; let controlHTML = ''; if (channel.type === 'motor') { controlHTML = `
-100 % 0 % +100 %
`; } else { controlHTML = ` `; } col.innerHTML = `
${controlHTML}
`; channelsContainer.appendChild(col); }); // ── Event-Listener Slider (Motoren) ───────────────────────────────────── document.querySelectorAll('.motor-slider').forEach(slider => { const display = slider.parentElement.querySelector('.value-display'); slider.addEventListener('input', async () => { const value = parseInt(slider.value) / 100; if (display) display.textContent = `${slider.value} %`; try { await sendControl(slider.dataset.port, value); } catch (err) { console.error('Slider-Steuerfehler:', err); } }); }); // ── Event-Listener Einzel-Stop pro Kanal ──────────────────────────────── document.querySelectorAll('.stop-channel-btn').forEach(btn => { btn.addEventListener('click', async () => { const port = btn.dataset.port; try { await sendControl(port, 0); // Slider zurücksetzen const slider = document.querySelector(`input[data-port="${port}"]`); if (slider) { slider.value = 0; const display = slider.parentElement.querySelector('.value-display'); if (display) display.textContent = '0 %'; } // Feedback btn.classList.add('btn-danger'); setTimeout(() => btn.classList.remove('btn-danger'), 600); console.log(`Einzelstop: ${port}`); } catch (err) { console.error('Einzelstop-Fehler:', err); } }); }); // ── Event-Listener Toggle-Buttons (Licht/Sound/Fogger) ────────────────── document.querySelectorAll('.toggle-btn').forEach(btn => { btn.addEventListener('click', async () => { const current = btn.dataset.state; const newState = current === 'off' ? 'on' : 'off'; btn.dataset.state = newState; btn.textContent = newState === 'on' ? 'EIN' : 'AUS'; btn.classList.toggle('btn-success', newState === 'on'); btn.classList.toggle('btn-outline-secondary', newState === 'off'); try { await sendControl(btn.dataset.port, newState === 'on' ? 1 : 0); } catch (err) { console.error('Toggle-Fehler:', err); } }); }); } // ── Hilfsfunktion: Steuerbefehl senden ──────────────────────────────────── async function sendControl(port, value) { try { const res = await fetch('/api/control', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ port, value }) }); if (!res.ok) { const errData = await res.json(); throw new Error(errData.message || 'Steuerbefehl fehlgeschlagen'); } } catch (err) { console.error('sendControl Fehler:', err); // Bei Fehler: Verbindungsprüfung triggern updateStatus(false, 'Fehler beim Senden'); } } // ── Hilfsfunktion: Reconnect-Bereich anzeigen ───────────────────────────── function showReconnect() { if (reconnectSection && controlSection) { controlSection.style.display = 'none'; reconnectSection.style.display = 'block'; } } // ── Soundboard Buttons ───────────────────────────────────────────────────── let currentSoundButton = null; document.querySelectorAll('.play-sound-btn').forEach(btn => { btn.addEventListener('click', async () => { const soundId = btn.dataset.soundId; // Alten Play-Button zurücksetzen if (currentSoundButton && currentSoundButton !== btn) { currentSoundButton.classList.remove('btn-success'); currentSoundButton.classList.add('btn-outline-primary', 'btn-outline-secondary'); currentSoundButton.innerHTML = currentSoundButton.dataset.originalText || ' Abspielen'; } btn.disabled = true; const originalText = btn.innerHTML; btn.dataset.originalText = originalText; btn.innerHTML = ' Spielt...'; try { const res = await fetch('/api/play_sound', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sound_id: soundId }) }); const data = await res.json(); if (data.success) { btn.classList.remove('btn-outline-primary', 'btn-outline-secondary'); btn.classList.add('btn-success'); btn.innerHTML = ' Gespielt'; currentSoundButton = btn; } else { alert('Fehler: ' + (data.message || 'Unbekannt')); btn.innerHTML = originalText; btn.disabled = false; } } catch (err) { console.error('Sound-Play-Fehler:', err); alert('Netzwerkfehler beim Abspielen'); btn.innerHTML = originalText; btn.disabled = false; } }); }); // ── Stop-Button für aktuellen Sound document.querySelectorAll('.stop-sound-btn').forEach(btn => { btn.addEventListener('click', async () => { try { const res = await fetch('/api/stop_sound', { method: 'POST' }); const data = await res.json(); if (data.success) { if (currentSoundButton) { currentSoundButton.classList.remove('btn-success'); currentSoundButton.classList.add('btn-outline-primary', 'btn-outline-secondary'); currentSoundButton.innerHTML = currentSoundButton.dataset.originalText || ' Abspielen'; currentSoundButton.disabled = false; currentSoundButton = null; } console.log('Sound gestoppt'); } else { alert('Fehler: ' + data.message); } } catch (err) { console.error('Stop-Sound-Fehler:', err); alert('Netzwerkfehler beim Stoppen'); } }); }); // ── Volume-Regler if (volumeSlider && volumeDisplay) { volumeSlider.addEventListener('input', () => { const vol = volumeSlider.value / 100; volumeDisplay.textContent = `${volumeSlider.value} %`; try { fetch('/api/set_volume', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ volume: vol }) }).catch(err => console.error('Volume-Set-Fehler:', err)); } catch (err) { console.error('Volume-Fehler:', err); } }); } });