tabs for sounds added
This commit is contained in:
parent
0eacdbf973
commit
702eba2370
37
app.py
37
app.py
@ -3,10 +3,16 @@ import os
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
|
import threading
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from flask import Flask, render_template, redirect, url_for, send_from_directory, request, jsonify
|
from flask import Flask, render_template, redirect, url_for, send_from_directory, request, jsonify
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
# pygame einmalig initialisieren (am besten global)
|
||||||
|
pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512)
|
||||||
|
pygame.mixer.music.set_volume(0.8) # Standard 80 %
|
||||||
|
|
||||||
# Logging-Verzeichnis (muss VOR cleanup_old_log_dirs definiert werden!)
|
# Logging-Verzeichnis (muss VOR cleanup_old_log_dirs definiert werden!)
|
||||||
LOG_DIR = 'logs'
|
LOG_DIR = 'logs'
|
||||||
os.makedirs(LOG_DIR, exist_ok=True)
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
@ -540,12 +546,6 @@ def api_reconnect():
|
|||||||
return jsonify({"success": False, "message": str(e)}), 500
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
import pygame
|
|
||||||
import threading
|
|
||||||
|
|
||||||
# pygame einmalig initialisieren (am besten global)
|
|
||||||
pygame.mixer.init()
|
|
||||||
|
|
||||||
@app.route('/api/play_sound', methods=['POST'])
|
@app.route('/api/play_sound', methods=['POST'])
|
||||||
def api_play_sound():
|
def api_play_sound():
|
||||||
try:
|
try:
|
||||||
@ -588,6 +588,31 @@ def api_play_sound():
|
|||||||
return jsonify({"success": False, "message": str(e)}), 500
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/set_volume', methods=['POST'])
|
||||||
|
def api_set_volume():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
vol = float(data.get('volume', 0.8))
|
||||||
|
vol = max(0.0, min(1.0, vol))
|
||||||
|
pygame.mixer.music.set_volume(vol)
|
||||||
|
logger.info(f"Volume auf {vol} gesetzt")
|
||||||
|
return jsonify({"success": True})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Volume-Fehler: {e}")
|
||||||
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/stop_sound', methods=['POST'])
|
||||||
|
def api_stop_sound():
|
||||||
|
try:
|
||||||
|
pygame.mixer.music.stop()
|
||||||
|
logger.info("Aktueller Sound gestoppt")
|
||||||
|
return jsonify({"success": True})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Stop-Sound-Fehler: {e}")
|
||||||
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,16 @@
|
|||||||
{
|
{
|
||||||
"global_sounds": [
|
"global_sounds": [
|
||||||
{
|
{
|
||||||
"id": "pfeife_lang",
|
"id": "dampflok-fahrt",
|
||||||
"file": "pfeife_lang.wav",
|
"file": "Dampflok-mit-Waggons-schnauft-heran-und-rollt-vorüber.mp3",
|
||||||
"name": "Pfeife lang",
|
"name": "Dampflok Ankunft und Vorbeifahrt",
|
||||||
"description": "Langgezogene Dampflok-Pfeife"
|
"description": "Dampflok mit Waggons schnauft heran und rollt vorüber"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dampf_ausstoß",
|
"id": "wind_western1",
|
||||||
"file": "dampf_ausstoß.mp3",
|
"file": "freesound_community-wind-western-64661.mp3",
|
||||||
"name": "Dampfausstoß",
|
"name": "Wind Western",
|
||||||
"description": "Kurzes Zischen"
|
"description": "einfache Windgeräusche"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "bahnhof_ankunft",
|
|
||||||
"file": "bahnhof_ankunft.mp3",
|
|
||||||
"name": "Bahnhofsankunft",
|
|
||||||
"description": "Stationsansage + Bremsquietschen"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
BIN
sounds/freesound_community-wind-western-64661.mp3
Normal file
BIN
sounds/freesound_community-wind-western-64661.mp3
Normal file
Binary file not shown.
252
static/js/app.js
252
static/js/app.js
@ -11,6 +11,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const stopAllBtn = document.getElementById('stop-all-btn');
|
const stopAllBtn = document.getElementById('stop-all-btn');
|
||||||
const reconnectSection = document.getElementById('reconnect-section');
|
const reconnectSection = document.getElementById('reconnect-section');
|
||||||
const reconnectBtn = document.getElementById('reconnect-btn');
|
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) {
|
if (!connectBtn) {
|
||||||
console.warn('Nicht auf der Steuerseite – Connect-Button nicht gefunden');
|
console.warn('Nicht auf der Steuerseite – Connect-Button nicht gefunden');
|
||||||
@ -18,8 +21,68 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Config aus Template ────────────────────────────────────────────────────
|
// ── Config aus Template ────────────────────────────────────────────────────
|
||||||
const config = window.mkConfig || {};
|
const config = {{ config | tojson | safe }};
|
||||||
console.log("app.js verwendet config:", config);
|
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 = '<i class="bi bi-circle-fill me-2"></i> Verbunden' + (message ? ` – ${message}` : '');
|
||||||
|
if (reconnectSection) reconnectSection.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
statusBadge.className = 'badge bg-danger px-3 py-2 fs-6';
|
||||||
|
statusBadge.innerHTML = '<i class="bi bi-circle-fill me-2"></i> 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 ────────────────────────────────────────────────────────
|
// ── Connect-Button ────────────────────────────────────────────────────────
|
||||||
connectBtn.addEventListener('click', async () => {
|
connectBtn.addEventListener('click', async () => {
|
||||||
@ -43,16 +106,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
controlSection.style.display = 'block';
|
controlSection.style.display = 'block';
|
||||||
reconnectSection.style.display = 'none';
|
reconnectSection.style.display = 'none';
|
||||||
|
|
||||||
// Status aktualisieren
|
|
||||||
updateStatus(true);
|
updateStatus(true);
|
||||||
|
startConnectionCheck();
|
||||||
|
|
||||||
console.log("→ Rufe renderChannels() auf");
|
console.log("→ Rufe renderChannels() auf");
|
||||||
renderChannels();
|
renderChannels();
|
||||||
|
|
||||||
console.log("→ renderChannels() abgeschlossen");
|
console.log("→ renderChannels() abgeschlossen");
|
||||||
|
|
||||||
// Automatische Prüfung starten
|
|
||||||
startConnectionCheck();
|
|
||||||
} else {
|
} else {
|
||||||
console.warn("→ Connect fehlgeschlagen:", result.message);
|
console.warn("→ Connect fehlgeschlagen:", result.message);
|
||||||
alert('Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannter Fehler'));
|
alert('Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannter Fehler'));
|
||||||
@ -179,7 +239,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// Licht, Sound, Fogger etc. → Toggle
|
|
||||||
controlHTML = `
|
controlHTML = `
|
||||||
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
|
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
|
||||||
<button class="btn btn-outline-secondary w-100 toggle-btn"
|
<button class="btn btn-outline-secondary w-100 toggle-btn"
|
||||||
@ -275,11 +334,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
throw new Error(errData.message || 'Steuerbefehl fehlgeschlagen');
|
throw new Error(errData.message || 'Steuerbefehl fehlgeschlagen');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('sendControl Fehler:', err);
|
console.error('sendControl Fehler:', err);
|
||||||
failedChecks++;
|
// Bei Fehler: Verbindungsprüfung triggern
|
||||||
if (failedChecks >= MAX_FAILED_CHECKS) {
|
updateStatus(false, 'Fehler beim Senden');
|
||||||
updateStatus(false, 'Mehrere Steuerbefehle fehlgeschlagen');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,110 +348,93 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Status-Anzeige ─────────────────────────────────────────────────────────
|
// ── Soundboard Buttons ─────────────────────────────────────────────────────
|
||||||
const statusBadge = document.getElementById('connection-status');
|
let currentSoundButton = null;
|
||||||
|
|
||||||
function updateStatus(connected, message = '') {
|
document.querySelectorAll('.play-sound-btn').forEach(btn => {
|
||||||
if (!statusBadge) return;
|
btn.addEventListener('click', async () => {
|
||||||
|
const soundId = btn.dataset.soundId;
|
||||||
|
|
||||||
if (connected) {
|
// Alten Play-Button zurücksetzen
|
||||||
statusBadge.className = 'badge bg-success px-3 py-2 fs-6';
|
if (currentSoundButton && currentSoundButton !== btn) {
|
||||||
statusBadge.innerHTML = '<i class="bi bi-circle-fill me-2"></i> Verbunden';
|
currentSoundButton.classList.remove('btn-success');
|
||||||
} else {
|
currentSoundButton.classList.add('btn-outline-primary', 'btn-outline-secondary');
|
||||||
statusBadge.className = 'badge bg-danger px-3 py-2 fs-6';
|
currentSoundButton.innerHTML = currentSoundButton.dataset.originalText || '<i class="bi bi-play-fill me-2"></i> Abspielen';
|
||||||
statusBadge.innerHTML = '<i class="bi bi-circle-fill me-2"></i> Getrennt' + (message ? ` – ${message}` : '');
|
|
||||||
showReconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Soundboard Buttons ─────────────────────────────────────────────────────
|
|
||||||
document.querySelectorAll('.play-sound-btn').forEach(btn => {
|
|
||||||
btn.addEventListener('click', async () => {
|
|
||||||
const soundId = btn.dataset.soundId;
|
|
||||||
btn.disabled = true;
|
|
||||||
const originalText = btn.innerHTML;
|
|
||||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span> 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 = '<i class="bi bi-check-lg me-2"></i> Gespielt';
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.classList.remove('btn-success');
|
|
||||||
btn.classList.add('btn-outline-primary'); // oder secondary
|
|
||||||
btn.innerHTML = originalText;
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
|
||||||
alert('Fehler: ' + (data.message || 'Unbekannt'));
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.innerHTML = originalText;
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.error('Sound-Play-Fehler:', err);
|
|
||||||
alert('Netzwerkfehler beim Abspielen');
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.innerHTML = originalText;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialer Status
|
btn.disabled = true;
|
||||||
updateStatus(false);
|
const originalText = btn.innerHTML;
|
||||||
|
btn.dataset.originalText = originalText;
|
||||||
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span> Spielt...';
|
||||||
|
|
||||||
// ── Automatische Verbindungsprüfung ────────────────────────────────────────
|
try {
|
||||||
let connectionCheckInterval = null;
|
const res = await fetch('/api/play_sound', {
|
||||||
let failedChecks = 0;
|
method: 'POST',
|
||||||
const MAX_FAILED_CHECKS = 3; // erst nach 3 Fehlschlägen in Folge rot
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ sound_id: soundId })
|
||||||
|
});
|
||||||
|
|
||||||
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();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.connected) {
|
if (data.success) {
|
||||||
failedChecks = 0;
|
btn.classList.remove('btn-outline-primary', 'btn-outline-secondary');
|
||||||
updateStatus(true);
|
btn.classList.add('btn-success');
|
||||||
|
btn.innerHTML = '<i class="bi bi-check-lg me-2"></i> Gespielt';
|
||||||
|
currentSoundButton = btn;
|
||||||
} else {
|
} else {
|
||||||
failedChecks++;
|
alert('Fehler: ' + (data.message || 'Unbekannt'));
|
||||||
console.warn(`Status-Check fehlgeschlagen (${failedChecks}/${MAX_FAILED_CHECKS}):`, data.message);
|
btn.innerHTML = originalText;
|
||||||
if (failedChecks >= MAX_FAILED_CHECKS) {
|
btn.disabled = false;
|
||||||
updateStatus(false, data.message || 'Mehrere Checks fehlgeschlagen');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failedChecks++;
|
console.error('Sound-Play-Fehler:', err);
|
||||||
console.warn(`Status-Check Netzwerkfehler (${failedChecks}/${MAX_FAILED_CHECKS}):`, err);
|
alert('Netzwerkfehler beim Abspielen');
|
||||||
if (failedChecks >= MAX_FAILED_CHECKS) {
|
btn.innerHTML = originalText;
|
||||||
updateStatus(false, 'Keine Antwort vom Hub/Server');
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}, 6000); // 6 Sekunden – aggressiver als 8 s
|
|
||||||
}
|
|
||||||
|
|
||||||
// In Connect- und Reconnect-Handler nach erfolgreichem Connect/Reconnect:
|
|
||||||
startConnectionCheck();
|
|
||||||
updateStatus(true);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => {
|
|
||||||
if (connectionCheckInterval) clearInterval(connectionCheckInterval);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Optional: Bei Seitenlade den Check starten (falls schon verbunden)
|
// ── Stop-Button für aktuellen Sound
|
||||||
// startConnectionCheck(); // ← auskommentiert, da nach Connect gestartet
|
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 || '<i class="bi bi-play-fill me-2"></i> 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Linke Spalte: Kanal-Steuerung (8/12 auf lg) -->
|
<!-- Linke Spalte: Kanal-Steuerung (8/12) -->
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<div class="row mb-4 align-items-center">
|
<div class="row mb-4 align-items-center">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
@ -65,7 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Reconnect-Bereich – wird nur bei Verbindungsverlust eingeblendet -->
|
<!-- Reconnect-Bereich -->
|
||||||
<div id="reconnect-section" class="text-center mt-5" style="display: none;">
|
<div id="reconnect-section" class="text-center mt-5" style="display: none;">
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
<strong>Verbindung unterbrochen</strong><br>
|
<strong>Verbindung unterbrochen</strong><br>
|
||||||
@ -77,45 +77,82 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rechte Spalte: Soundboard (4/12 auf lg) -->
|
<!-- Rechte Spalte: Soundboard (4/12) -->
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<div class="card shadow sticky-top" style="top: 20px;">
|
<div class="card shadow sticky-top" style="top: 20px;">
|
||||||
<div class="card-header bg-info text-white">
|
<div class="card-header bg-info text-white">
|
||||||
<h5 class="mb-0">Soundboard</h5>
|
<h5 class="mb-0">Soundboard</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" style="max-height: 70vh; overflow-y: auto;">
|
<div class="card-body">
|
||||||
|
|
||||||
<!-- Lok-spezifische Sounds -->
|
<!-- Volume-Regler (global) -->
|
||||||
{% if config.sounds and config.sounds|length > 0 %}
|
<div class="mb-4">
|
||||||
<h6 class="mt-0 mb-3">Lok-spezifisch</h6>
|
<label for="volume-slider" class="form-label fw-bold">Lautstärke</label>
|
||||||
<div class="d-grid gap-2 mb-4">
|
<input type="range" class="form-range" id="volume-slider" min="0" max="100" value="80" step="5">
|
||||||
{% for sound in config.sounds %}
|
<div class="text-center mt-1">
|
||||||
<button class="btn btn-outline-primary play-sound-btn"
|
<span id="volume-display">80 %</span>
|
||||||
data-sound-id="{{ sound.id }}">
|
|
||||||
{{ sound.name }}
|
|
||||||
{% if sound.description %}
|
|
||||||
<small class="d-block text-muted">{{ sound.description }}</small>
|
|
||||||
{% endif %}
|
|
||||||
</button>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
|
|
||||||
<!-- Globale / Standard-Sounds -->
|
<!-- Tabs -->
|
||||||
{% if global_sounds and global_sounds|length > 0 %}
|
<ul class="nav nav-tabs mb-3" id="soundTabs" role="tablist">
|
||||||
<h6 class="mt-4 mb-3">Standard-Sounds</h6>
|
{% if config.sounds and config.sounds|length > 0 %}
|
||||||
<div class="d-grid gap-2">
|
<li class="nav-item" role="presentation">
|
||||||
{% for sound in global_sounds %}
|
<button class="nav-link active" id="lok-tab" data-bs-toggle="tab" data-bs-target="#lok-sounds" type="button" role="tab">
|
||||||
<button class="btn btn-outline-secondary play-sound-btn"
|
Lok-spezifisch
|
||||||
data-sound-id="{{ sound.id }}">
|
|
||||||
{{ sound.name }}
|
|
||||||
{% if sound.description %}
|
|
||||||
<small class="d-block text-muted">{{ sound.description }}</small>
|
|
||||||
{% endif %}
|
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link {% if not config.sounds or config.sounds|length == 0 %}active{% endif %}" id="global-tab" data-bs-toggle="tab" data-bs-target="#global-sounds" type="button" role="tab">
|
||||||
|
Standard-Sounds
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content" id="soundTabContent" style="max-height: 50vh; overflow-y: auto;">
|
||||||
|
<!-- Lok-spezifische Sounds -->
|
||||||
|
{% if config.sounds and config.sounds|length > 0 %}
|
||||||
|
<div class="tab-pane fade show active" id="lok-sounds" role="tabpanel">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
{% for sound in config.sounds %}
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-outline-primary flex-grow-1 play-sound-btn"
|
||||||
|
data-sound-id="{{ sound.id }}">
|
||||||
|
{{ sound.name }}
|
||||||
|
{% if sound.description %}
|
||||||
|
<small class="d-block text-muted">{{ sound.description }}</small>
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-danger stop-sound-btn" title="Aktuellen Sound stoppen">
|
||||||
|
<i class="bi bi-stop-fill"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Globale Standard-Sounds -->
|
||||||
|
<div class="tab-pane fade {% if not config.sounds or config.sounds|length == 0 %}show active{% endif %}" id="global-sounds" role="tabpanel">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
{% for sound in global_sounds %}
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-outline-secondary flex-grow-1 play-sound-btn"
|
||||||
|
data-sound-id="{{ sound.id }}">
|
||||||
|
{{ sound.name }}
|
||||||
|
{% if sound.description %}
|
||||||
|
<small class="d-block text-muted">{{ sound.description }}</small>
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-danger stop-sound-btn" title="Aktuellen Sound stoppen">
|
||||||
|
<i class="bi bi-stop-fill"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
|
|
||||||
{% if (not config.sounds or config.sounds|length == 0) and (not global_sounds or global_sounds|length == 0) %}
|
{% if (not config.sounds or config.sounds|length == 0) and (not global_sounds or global_sounds|length == 0) %}
|
||||||
<p class="text-muted text-center py-4">Keine Sounds konfiguriert</p>
|
<p class="text-muted text-center py-4">Keine Sounds konfiguriert</p>
|
||||||
@ -134,5 +171,8 @@
|
|||||||
const config = {{ config | tojson | safe }};
|
const config = {{ config | tojson | safe }};
|
||||||
const globalSounds = {{ global_sounds | tojson | safe }};
|
const globalSounds = {{ global_sounds | tojson | safe }};
|
||||||
window.mkConfig = config; // falls du es global brauchst
|
window.mkConfig = config; // falls du es global brauchst
|
||||||
|
|
||||||
|
console.log("Config im JS:", config);
|
||||||
|
console.log("Globale Sounds:", globalSounds);
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
x
Reference in New Issue
Block a user