update for app.js
This commit is contained in:
parent
b8b2aec67d
commit
d081477d63
19
app.py
19
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}")
|
logger.error(f"Fehler beim Löschen von {subdir_path}: {e}")
|
||||||
|
|
||||||
# ── Hilfsfunktion - globale Sounds laden ────────────────────────────────────────────────────────────────
|
# ── Hilfsfunktion - globale Sounds laden ────────────────────────────────────────────────────────────────
|
||||||
def load_global_sounds():
|
def load_default_sounds():
|
||||||
global_sounds_path = os.path.join(app.root_path, 'sounds.json') # oder configs/sounds.json
|
path = os.path.join(app.config['CONFIG_DIR'], 'default_sounds.json')
|
||||||
if os.path.exists(global_sounds_path):
|
if os.path.exists(path):
|
||||||
try:
|
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)
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Laden globaler Sounds: {e}")
|
logger.error(f"Fehler beim Laden default_sounds.json: {e}")
|
||||||
return []
|
return []
|
||||||
|
logger.info("Keine default_sounds.json gefunden")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ── Bluetooth ────────────────────────────────────────────────────────────────
|
# ── Bluetooth ────────────────────────────────────────────────────────────────
|
||||||
@ -164,7 +168,7 @@ logger.info("Background-Monitor für Hub-Verbindung gestartet")
|
|||||||
def load_configs():
|
def load_configs():
|
||||||
configs = []
|
configs = []
|
||||||
for filename in os.listdir(app.config['CONFIG_DIR']):
|
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)
|
path = os.path.join(app.config['CONFIG_DIR'], filename)
|
||||||
try:
|
try:
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
@ -235,6 +239,7 @@ def control_page():
|
|||||||
logger.warning("current_config ist None → Redirect zu index")
|
logger.warning("current_config ist None → Redirect zu index")
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
# Globale Sounds immer laden
|
||||||
global_sounds = load_global_sounds()
|
global_sounds = load_global_sounds()
|
||||||
|
|
||||||
logger.info(f"Übergebe config an Template: {current_config}")
|
logger.info(f"Übergebe config an Template: {current_config}")
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"name": "Standard-Sounds (global)",
|
||||||
"global_sounds": [
|
"global_sounds": [
|
||||||
{
|
{
|
||||||
"id": "dampflok-fahrt",
|
"id": "dampflok-fahrt",
|
||||||
444
static/js/app.js
444
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', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('MK Control Frontend geladen');
|
console.log('MK Control Frontend gestartet');
|
||||||
|
|
||||||
// ── Elemente ───────────────────────────────────────────────────────────────
|
// Module laden (in dieser Reihenfolge wichtig!)
|
||||||
const connectBtn = document.getElementById('connect-btn');
|
initConfig(); // config & globalSounds bereitstellen
|
||||||
const connectSection = document.getElementById('connect-section');
|
initStatus(); // Status-Badge & Reconnect
|
||||||
const controlSection = document.getElementById('control-section');
|
initConnect(); // Connect- & Reconnect-Button
|
||||||
const channelsContainer = document.getElementById('channels-container');
|
initChannels(); // Kanäle rendern + Listener
|
||||||
const stopAllBtn = document.getElementById('stop-all-btn');
|
initSoundboard(); // Soundboard-Buttons + Volume
|
||||||
const reconnectSection = document.getElementById('reconnect-section');
|
initConnectionCheck(); // Hintergrund-Status-Polling
|
||||||
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) {
|
// Initialen Status setzen
|
||||||
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 = '<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);
|
updateStatus(false);
|
||||||
|
|
||||||
// ── Automatische Verbindungsprüfung ────────────────────────────────────────
|
// Status-Check nur starten, wenn auf Steuerseite
|
||||||
let connectionCheckInterval = null;
|
if (document.getElementById('connect-btn')) {
|
||||||
let failedChecks = 0;
|
// startConnectionCheck(); // wird jetzt von connect/reconnect aufgerufen
|
||||||
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 = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> 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 = '<i class="bi bi-bluetooth me-2"></i> 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 = '<span class="spinner-border spinner-border-sm me-2"></span> 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 = '<i class="bi bi-arrow-repeat me-2"></i> 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 = '<p class="text-center text-muted py-5">Keine Kanäle in der Konfiguration definiert.</p>';
|
|
||||||
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 = `
|
|
||||||
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
|
|
||||||
<input type="range" class="form-range motor-slider"
|
|
||||||
min="-100" max="100" value="0" step="5"
|
|
||||||
data-port="${channel.port}">
|
|
||||||
<div class="d-flex justify-content-between mt-1 small">
|
|
||||||
<span>-100 %</span>
|
|
||||||
<span class="value-display fw-bold">0 %</span>
|
|
||||||
<span>+100 %</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-center mt-2">
|
|
||||||
<button class="btn btn-sm btn-outline-danger stop-channel-btn"
|
|
||||||
data-port="${channel.port}">
|
|
||||||
<i class="bi bi-stop-fill me-1"></i> Stop
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
controlHTML = `
|
|
||||||
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
|
|
||||||
<button class="btn btn-outline-secondary w-100 toggle-btn"
|
|
||||||
data-port="${channel.port}" data-state="off">
|
|
||||||
AUS
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
col.innerHTML = `
|
|
||||||
<div class="card h-100 shadow-sm">
|
|
||||||
<div class="card-body">
|
|
||||||
${controlHTML}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
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 || '<i class="bi bi-play-fill me-2"></i> Abspielen';
|
|
||||||
}
|
|
||||||
|
|
||||||
btn.disabled = true;
|
|
||||||
const originalText = btn.innerHTML;
|
|
||||||
btn.dataset.originalText = originalText;
|
|
||||||
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';
|
|
||||||
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 || '<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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
16
static/js/config.js
Normal file
16
static/js/config.js
Normal file
@ -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;
|
||||||
33
static/js/connection-check.js
Normal file
33
static/js/connection-check.js
Normal file
@ -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;
|
||||||
89
static/js/ui-channels.js
Normal file
89
static/js/ui-channels.js
Normal file
@ -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 = '<p class="text-center text-muted py-5">Keine Kanäle definiert.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.channels.forEach(channel => {
|
||||||
|
const col = document.createElement('div');
|
||||||
|
col.className = 'col-md-6 mb-4';
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
if (channel.type === 'motor') {
|
||||||
|
html = `
|
||||||
|
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
|
||||||
|
<input type="range" class="form-range motor-slider" min="-100" max="100" value="0" step="5" data-port="${channel.port}">
|
||||||
|
<div class="d-flex justify-content-between mt-1 small">
|
||||||
|
<span>-100 %</span>
|
||||||
|
<span class="value-display fw-bold">0 %</span>
|
||||||
|
<span>+100 %</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-2">
|
||||||
|
<button class="btn btn-sm btn-outline-danger stop-channel-btn" data-port="${channel.port}">
|
||||||
|
<i class="bi bi-stop-fill me-1"></i> Stop
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
html = `
|
||||||
|
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
|
||||||
|
<button class="btn btn-outline-secondary w-100 toggle-btn" data-port="${channel.port}" data-state="off">
|
||||||
|
AUS
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
col.innerHTML = `<div class="card h-100 shadow-sm"><div class="card-body">${html}</div></div>`;
|
||||||
|
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;
|
||||||
72
static/js/ui-connect.js
Normal file
72
static/js/ui-connect.js
Normal file
@ -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 = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> 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 = '<i class="bi bi-bluetooth me-2"></i> Mit Hub verbinden';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reconnectBtn) {
|
||||||
|
reconnectBtn.addEventListener('click', async () => {
|
||||||
|
reconnectBtn.disabled = true;
|
||||||
|
reconnectBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span> 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 = '<i class="bi bi-arrow-repeat me-2"></i> Erneut verbinden';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.initConnect = initConnect;
|
||||||
135
static/js/ui-soundboard.js
Normal file
135
static/js/ui-soundboard.js
Normal file
@ -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 || '<i class="bi bi-play-fill me-2"></i> Abspielen';
|
||||||
|
currentSoundButton.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuen Button markieren
|
||||||
|
btn.disabled = true;
|
||||||
|
const originalText = btn.innerHTML;
|
||||||
|
btn.dataset.originalText = originalText;
|
||||||
|
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';
|
||||||
|
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 || '<i class="bi bi-play-fill me-2"></i> 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;
|
||||||
30
static/js/ui-status.js
Normal file
30
static/js/ui-status.js
Normal file
@ -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 = '<i class="bi bi-circle-fill me-2"></i> Verbunden' + (message ? ` – ${message}` : '');
|
||||||
|
if (reconnect) reconnect.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
badge.className = 'badge bg-danger px-3 py-2 fs-6';
|
||||||
|
badge.innerHTML = '<i class="bi bi-circle-fill me-2"></i> 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;
|
||||||
@ -86,9 +86,18 @@
|
|||||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Eigenes JS -->
|
<!-- Globale JavaScript-Module (immer laden) -->
|
||||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/config.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/ui-status.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/ui-connect.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/ui-channels.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/ui-soundboard.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/connection-check.js') }}"></script>
|
||||||
|
|
||||||
{% block scripts %}{% endblock %}
|
<!-- Einstiegspunkt – lädt und initialisiert alles -->
|
||||||
|
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||||
|
|
||||||
|
<!-- Seiten-spezifische Config setzen (nur wenn config existiert) -->
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -165,13 +165,24 @@
|
|||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- Seiten-spezifische Config setzen (nur wenn config existiert) -->
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
{% if config %}
|
||||||
<script>
|
<script>
|
||||||
// Config + globale Sounds global verfügbar machen
|
// Config + globale Sounds global verfügbar machen (nur auf Seiten mit config)
|
||||||
window.mkConfig = {{ config | tojson | safe }};
|
window.mkConfig = {{ config | tojson | safe }};
|
||||||
window.mkGlobalSounds = {{ global_sounds | tojson | safe }};
|
window.mkGlobalSounds = {{ global_sounds | tojson | safe }};
|
||||||
|
|
||||||
console.log("window.mkConfig gesetzt:", window.mkConfig);
|
console.log("base.html → mkConfig gesetzt:", window.mkConfig);
|
||||||
console.log("window.mkGlobalSounds:", window.mkGlobalSounds);
|
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
|
||||||
</script>
|
</script>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
x
Reference in New Issue
Block a user