From d081477d634220f4ca0e52bbfc9afc4640208840 Mon Sep 17 00:00:00 2001 From: oberon Date: Mon, 16 Feb 2026 11:24:23 +0100 Subject: [PATCH] update for app.js --- app.py | 21 +- configs/{sounds.json => default_sounds.json} | 1 + static/js/app.js | 444 +------------------ static/js/config.js | 16 + static/js/connection-check.js | 33 ++ static/js/ui-channels.js | 89 ++++ static/js/ui-connect.js | 72 +++ static/js/ui-soundboard.js | 135 ++++++ static/js/ui-status.js | 30 ++ templates/base.html | 15 +- templates/control.html | 25 +- 11 files changed, 432 insertions(+), 449 deletions(-) rename configs/{sounds.json => default_sounds.json} (92%) create mode 100644 static/js/config.js create mode 100644 static/js/connection-check.js create mode 100644 static/js/ui-channels.js create mode 100644 static/js/ui-connect.js create mode 100644 static/js/ui-soundboard.js create mode 100644 static/js/ui-status.js diff --git a/app.py b/app.py index 16b1678..398d319 100644 --- a/app.py +++ b/app.py @@ -42,16 +42,20 @@ def cleanup_old_log_dirs(max_age_days=90): logger.error(f"Fehler beim Löschen von {subdir_path}: {e}") # ── Hilfsfunktion - globale Sounds laden ──────────────────────────────────────────────────────────────── -def load_global_sounds(): - global_sounds_path = os.path.join(app.root_path, 'sounds.json') # oder configs/sounds.json - if os.path.exists(global_sounds_path): +def load_default_sounds(): + path = os.path.join(app.config['CONFIG_DIR'], 'default_sounds.json') + if os.path.exists(path): try: - with open(global_sounds_path, 'r', encoding='utf-8') as f: + with open(path, 'r', encoding='utf-8') as f: data = json.load(f) - return data.get('global_sounds', []) +# sounds = data.get('global_sounds', data.get('sounds', [])) # flexibel + sounds = data.get('global_sounds', []) + logger.info(f"Globale Default-Sounds geladen: {len(sounds)} Einträge") + return sounds except Exception as e: - logger.error(f"Fehler beim Laden globaler Sounds: {e}") + logger.error(f"Fehler beim Laden default_sounds.json: {e}") return [] + logger.info("Keine default_sounds.json gefunden") return [] # ── Bluetooth ──────────────────────────────────────────────────────────────── @@ -164,7 +168,7 @@ logger.info("Background-Monitor für Hub-Verbindung gestartet") def load_configs(): configs = [] for filename in os.listdir(app.config['CONFIG_DIR']): - if filename.lower().endswith('.json'): + if filename.lower().endswith('.json') and filename != 'default_sounds.json': path = os.path.join(app.config['CONFIG_DIR'], filename) try: with open(path, 'r', encoding='utf-8') as f: @@ -234,7 +238,8 @@ def control_page(): if current_config is None: logger.warning("current_config ist None → Redirect zu index") return redirect(url_for('index')) - + + # Globale Sounds immer laden global_sounds = load_global_sounds() logger.info(f"Übergebe config an Template: {current_config}") diff --git a/configs/sounds.json b/configs/default_sounds.json similarity index 92% rename from configs/sounds.json rename to configs/default_sounds.json index 1412eef..7b0e993 100644 --- a/configs/sounds.json +++ b/configs/default_sounds.json @@ -1,4 +1,5 @@ { + "name": "Standard-Sounds (global)", "global_sounds": [ { "id": "dampflok-fahrt", diff --git a/static/js/app.js b/static/js/app.js index ad5b6e1..322dd22 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -1,439 +1,21 @@ -// static/js/app.js – MK Control Frontend +// static/js/app.js – MK Control Frontend – Haupt-Einstieg document.addEventListener('DOMContentLoaded', () => { - console.log('MK Control Frontend geladen'); + console.log('MK Control Frontend gestartet'); - // ── 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'); + // Module laden (in dieser Reihenfolge wichtig!) + initConfig(); // config & globalSounds bereitstellen + initStatus(); // Status-Badge & Reconnect + initConnect(); // Connect- & Reconnect-Button + initChannels(); // Kanäle rendern + Listener + initSoundboard(); // Soundboard-Buttons + Volume + initConnectionCheck(); // Hintergrund-Status-Polling - if (!connectBtn) { - console.warn('Nicht auf der Steuerseite – Connect-Button nicht gefunden'); - // Dennoch Status und Soundboard-Logik initialisieren (falls vorhanden) - } - - // ── Config & globale Sounds aus Template ────────────────────────────────── - const config = window.mkConfig || {}; - const globalSounds = window.mkGlobalSounds || []; - - console.log("app.js verwendet config:", config); - console.log("app.js verwendet globalSounds:", 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 + // Initialen Status setzen 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 ──────────────────────────────────────────────────────── - if (connectBtn) { - 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 ────────────────────────────────────── - 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); - 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; - - 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); - } - }); + // Status-Check nur starten, wenn auf Steuerseite + if (document.getElementById('connect-btn')) { + // startConnectionCheck(); // wird jetzt von connect/reconnect aufgerufen } }); \ No newline at end of file diff --git a/static/js/config.js b/static/js/config.js new file mode 100644 index 0000000..644cf75 --- /dev/null +++ b/static/js/config.js @@ -0,0 +1,16 @@ +// static/js/config.js – Konfiguration & globale Daten + +let config = {}; +let globalSounds = []; + +function initConfig() { + // Diese Werte werden in control.html per Jinja gesetzt + config = window.mkConfig || {}; + globalSounds = window.mkGlobalSounds || []; + + console.log("config.js → Config geladen:", config); + console.log("config.js → Globale Sounds:", globalSounds); +} + +// Export (für andere Module) +window.initConfig = initConfig; \ No newline at end of file diff --git a/static/js/connection-check.js b/static/js/connection-check.js new file mode 100644 index 0000000..2f3cef7 --- /dev/null +++ b/static/js/connection-check.js @@ -0,0 +1,33 @@ +// static/js/connection-check.js + +let connectionCheckInterval = null; +let failedChecks = 0; +const MAX_FAILED_CHECKS = 3; + +function startConnectionCheck() { + if (connectionCheckInterval) clearInterval(connectionCheckInterval); + + connectionCheckInterval = setInterval(async () => { + try { + const res = await fetch('/api/status'); + const data = await res.json(); + + if (data.connected) { + failedChecks = 0; + updateStatus(true); + } else { + failedChecks++; + if (failedChecks >= MAX_FAILED_CHECKS) { + updateStatus(false, data.message || 'Mehrere Checks fehlgeschlagen'); + } + } + } catch (err) { + failedChecks++; + if (failedChecks >= MAX_FAILED_CHECKS) { + updateStatus(false, 'Keine Antwort'); + } + } + }, 6000); +} + +window.startConnectionCheck = startConnectionCheck; \ No newline at end of file diff --git a/static/js/ui-channels.js b/static/js/ui-channels.js new file mode 100644 index 0000000..84f73cd --- /dev/null +++ b/static/js/ui-channels.js @@ -0,0 +1,89 @@ +// static/js/ui-channels.js – Kanal-Rendering & Listener + +function renderChannels() { + console.log("renderChannels() START"); + console.log("→ config.channels existiert?", !!config?.channels); + console.log("→ channels.length:", config?.channels?.length ?? "undefined"); + + const container = document.getElementById('channels-container'); + if (!container) { + console.error("channelsContainer nicht gefunden!"); + return; + } + + container.innerHTML = ''; + + if (!config?.channels || config.channels.length === 0) { + container.innerHTML = '

Keine Kanäle definiert.

'; + return; + } + + config.channels.forEach(channel => { + const col = document.createElement('div'); + col.className = 'col-md-6 mb-4'; + + let html = ''; + + if (channel.type === 'motor') { + html = ` + + +
+ -100 % + 0 % + +100 % +
+
+ +
+ `; + } else { + html = ` + + + `; + } + + col.innerHTML = `
${html}
`; + container.appendChild(col); + }); + + // Listener hinzufügen (wie bisher) + 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} %`; + await sendControl(slider.dataset.port, value); + }); + }); + + document.querySelectorAll('.stop-channel-btn').forEach(btn => { + btn.addEventListener('click', async () => { + await sendControl(btn.dataset.port, 0); + const slider = document.querySelector(`input[data-port="${btn.dataset.port}"]`); + if (slider) { + slider.value = 0; + const display = slider.parentElement.querySelector('.value-display'); + if (display) display.textContent = '0 %'; + } + }); + }); + + document.querySelectorAll('.toggle-btn').forEach(btn => { + btn.addEventListener('click', async () => { + const state = btn.dataset.state === 'off' ? 'on' : 'off'; + btn.dataset.state = state; + btn.textContent = state === 'on' ? 'EIN' : 'AUS'; + btn.classList.toggle('btn-success', state === 'on'); + btn.classList.toggle('btn-outline-secondary', state === 'off'); + await sendControl(btn.dataset.port, state === 'on' ? 1 : 0); + }); + }); +} + +window.renderChannels = renderChannels; \ No newline at end of file diff --git a/static/js/ui-connect.js b/static/js/ui-connect.js new file mode 100644 index 0000000..50339c3 --- /dev/null +++ b/static/js/ui-connect.js @@ -0,0 +1,72 @@ +// static/js/ui-connect.js – Connect- & Reconnect-Logik + +function initConnect() { + const connectBtn = document.getElementById('connect-btn'); + const reconnectBtn = document.getElementById('reconnect-btn'); + + if (connectBtn) { + connectBtn.addEventListener('click', async () => { + connectBtn.disabled = true; + connectBtn.innerHTML = ' Verbinde...'; + + try { + console.log("→ Sende /api/connect ..."); + const res = await fetch('/api/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); + const data = await res.json(); + + if (data.success) { + document.getElementById('connect-section').style.display = 'none'; + document.getElementById('control-section').style.display = 'block'; + document.getElementById('reconnect-section').style.display = 'none'; + + updateStatus(true); + startConnectionCheck(); + renderChannels(); + + console.log("→ Verbunden"); + } else { + alert('Verbindungsfehler: ' + (data.message || 'Unbekannt')); + updateStatus(false); + } + } catch (err) { + console.error("Connect-Fehler:", err); + alert('Netzwerkfehler'); + updateStatus(false); + } finally { + connectBtn.disabled = false; + connectBtn.innerHTML = ' Mit Hub verbinden'; + } + }); + } + + if (reconnectBtn) { + reconnectBtn.addEventListener('click', async () => { + reconnectBtn.disabled = true; + reconnectBtn.innerHTML = ' Verbinde...'; + + try { + const res = await fetch('/api/reconnect', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); + const data = await res.json(); + + if (data.success) { + document.getElementById('reconnect-section').style.display = 'none'; + document.getElementById('control-section').style.display = 'block'; + + updateStatus(true); + startConnectionCheck(); + alert('Verbindung wiederhergestellt!'); + } else { + alert('Re-Connect fehlgeschlagen: ' + (data.message || 'Unbekannt')); + } + } catch (err) { + console.error("Reconnect-Fehler:", err); + alert('Netzwerkfehler'); + } finally { + reconnectBtn.disabled = false; + reconnectBtn.innerHTML = ' Erneut verbinden'; + } + }); + } +} + +window.initConnect = initConnect; \ No newline at end of file diff --git a/static/js/ui-soundboard.js b/static/js/ui-soundboard.js new file mode 100644 index 0000000..6387319 --- /dev/null +++ b/static/js/ui-soundboard.js @@ -0,0 +1,135 @@ +// static/js/ui-soundboard.js +// Verantwortlich für: Soundboard-Buttons, Play/Stop, Volume-Regler + +let currentSoundButton = null; // welcher Button gerade spielt (für optisches Feedback) + +// Initialisierung – wird von app.js aufgerufen +function initSoundboard() { + console.log('ui-soundboard.js → Initialisierung'); + + // ── Volume-Regler ────────────────────────────────────────────────────────── + const volumeSlider = document.getElementById('volume-slider'); + const volumeDisplay = document.getElementById('volume-display'); + + if (volumeSlider && volumeDisplay) { + // Startwert anzeigen + volumeDisplay.textContent = `${volumeSlider.value} %`; + + volumeSlider.addEventListener('input', () => { + const vol = parseInt(volumeSlider.value) / 100; + volumeDisplay.textContent = `${volumeSlider.value} %`; + + // Lautstärke an Server senden + fetch('/api/set_volume', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ volume: vol }) + }) + .then(res => { + if (!res.ok) throw new Error('Volume-Set-Fehler'); + console.log(`Volume auf ${vol} gesetzt`); + }) + .catch(err => { + console.error('Volume-Set-Fehler:', err); + alert('Lautstärke konnte nicht gesetzt werden'); + }); + }); + } else { + console.warn('Volume-Regler nicht gefunden im DOM'); + } + + // ── Play-Buttons ─────────────────────────────────────────────────────────── + 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'; + currentSoundButton.disabled = false; + } + + // Neuen Button markieren + 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; + + // Optional: Automatisch nach 3–5 Sekunden zurücksetzen + setTimeout(() => { + if (currentSoundButton === btn) { + btn.classList.remove('btn-success'); + btn.classList.add('btn-outline-primary'); + btn.innerHTML = originalText; + btn.disabled = false; + } + }, 4000); // 4 Sekunden – anpassbar + } else { + alert('Fehler beim Abspielen:\n' + (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', + headers: { 'Content-Type': 'application/json' } + }); + + const data = await res.json(); + + if (data.success) { + // Visuelles Feedback: aktuellen Play-Button zurücksetzen + 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('Aktueller Sound gestoppt'); + // Optional: kurzes Feedback am Button + btn.classList.add('btn-danger'); + setTimeout(() => btn.classList.remove('btn-danger'), 800); + } else { + alert('Fehler beim Stoppen: ' + (data.message || 'Unbekannt')); + } + } catch (err) { + console.error('Stop-Sound-Fehler:', err); + alert('Netzwerkfehler beim Stoppen'); + } + }); + }); +} + +// Export für app.js +window.initSoundboard = initSoundboard; \ No newline at end of file diff --git a/static/js/ui-status.js b/static/js/ui-status.js new file mode 100644 index 0000000..bf449bc --- /dev/null +++ b/static/js/ui-status.js @@ -0,0 +1,30 @@ +// static/js/ui-status.js – Status-Badge & Reconnect + +function updateStatus(connected, message = '') { + const badge = document.getElementById('connection-status'); + const reconnect = document.getElementById('reconnect-section'); + + if (!badge) return; + + if (connected) { + badge.className = 'badge bg-success px-3 py-2 fs-6'; + badge.innerHTML = ' Verbunden' + (message ? ` – ${message}` : ''); + if (reconnect) reconnect.style.display = 'none'; + } else { + badge.className = 'badge bg-danger px-3 py-2 fs-6'; + badge.innerHTML = ' Getrennt' + (message ? ` – ${message}` : ''); + if (reconnect) reconnect.style.display = 'block'; + } +} + +function showReconnect() { + const control = document.getElementById('control-section'); + const reconnect = document.getElementById('reconnect-section'); + if (control && reconnect) { + control.style.display = 'none'; + reconnect.style.display = 'block'; + } +} + +window.updateStatus = updateStatus; +window.showReconnect = showReconnect; \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 328ec23..617cccb 100644 --- a/templates/base.html +++ b/templates/base.html @@ -86,9 +86,18 @@ integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"> - - + + + + + + + - {% block scripts %}{% endblock %} + + + + +{% block scripts %}{% endblock %} \ No newline at end of file diff --git a/templates/control.html b/templates/control.html index d92bb71..42fcc7a 100644 --- a/templates/control.html +++ b/templates/control.html @@ -165,13 +165,24 @@ {% endblock %} + {% block scripts %} - + console.log("base.html → mkConfig gesetzt:", window.mkConfig); + console.log("base.html → mkGlobalSounds:", window.mkGlobalSounds); + + // Module initialisieren (kann auch in app.js passieren, hier nur für Sicherheit) + initConfig(); + initStatus(); + initConnect(); + initChannels(); + initSoundboard(); + // connection-check wird von connect/reconnect aufgerufen + + {% endif %} {% endblock %} \ No newline at end of file