Introduceer een ThreadPoolExecutor om gelijktijdige API-oproepen mogelijk te maken. Deze wijziging verbetert de efficiëntie door weersinformatie, Sonos-zones en datumgegevens parallel op te halen. In de instellingenroute is logica toegevoegd om data parallel te laden, wat resulteert in snellere paginaprocessen. Daarnaast zijn er optimalisaties aan de UI gedaan om FOUC te voorkomen door het toepassen van een visibiliteitsstijl totdat de pagina volledig is geladen.
472 lines
18 KiB
HTML
472 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="nl">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Instellingen - Gebedstijden Display</title>
|
||
<link rel="stylesheet" href="/static/style.css">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@700&family=Lato:wght@400;700&display=swap" rel="stylesheet">
|
||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||
<script>
|
||
// Voorkom FOUC (Flash of Unstyled Content)
|
||
document.documentElement.style.visibility = 'hidden';
|
||
window.addEventListener('load', function() {
|
||
document.documentElement.style.visibility = 'visible';
|
||
});
|
||
</script>
|
||
</head>
|
||
<body class="settings-page">
|
||
<div class="app">
|
||
<!-- Dark/Light toggle -->
|
||
<button id="themeToggle" title="Schakel modus"><span id="themeIcon" class="material-icons">brightness_6</span></button>
|
||
|
||
<div class="settings-container">
|
||
<div class="settings-header">
|
||
<a href="/" class="back-link" title="Terug naar hoofdpagina">
|
||
<span class="material-icons">arrow_back</span>
|
||
Terug naar hoofdpagina
|
||
</a>
|
||
<h1>Instellingen</h1>
|
||
</div>
|
||
|
||
<form method="POST" class="settings-form">
|
||
<!-- Tab Navigation -->
|
||
<div class="tab-navigation">
|
||
<button type="button" class="tab-btn active" data-tab="audio">
|
||
<span class="material-icons">volume_up</span>
|
||
Audio & Volume
|
||
</button>
|
||
<button type="button" class="tab-btn" data-tab="prayer">
|
||
<span class="material-icons">schedule</span>
|
||
Gebedstijden
|
||
</button>
|
||
<button type="button" class="tab-btn" data-tab="features">
|
||
<span class="material-icons">auto_stories</span>
|
||
Functies
|
||
</button>
|
||
<button type="button" class="tab-btn" data-tab="system">
|
||
<span class="material-icons">settings</span>
|
||
Systeem
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Audio & Volume Tab -->
|
||
<div class="tab-content active" id="audio-tab">
|
||
<div class="settings-section">
|
||
<div class="form-group">
|
||
<label>
|
||
<span class="material-icons">info</span>
|
||
Volume Instellingen:
|
||
</label>
|
||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0.5rem 0;">
|
||
Stel verschillende volumes in voor overdag en 's avonds. Het systeem wisselt automatisch tussen deze volumes op basis van de ingestelde tijden.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="volume_day">
|
||
<span class="material-icons">volume_up</span>
|
||
Volume Overdag (0–100):
|
||
</label>
|
||
<input type="number" name="volume_day" value="{{ settings.volume_day or settings.volume }}" min="0" max="100" />
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="volume_night">
|
||
<span class="material-icons">volume_down</span>
|
||
Volume Avond (0–100):
|
||
</label>
|
||
<input type="number" name="volume_night" value="{{ settings.volume_night or (settings.volume // 2) }}" min="0" max="100" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>
|
||
<span class="material-icons">tv</span>
|
||
Pi HDMI Volume:
|
||
</label>
|
||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0.5rem 0;">
|
||
Stel het HDMI audio volume van de Raspberry Pi in (0-100%). Dit beïnvloedt het volume van de browser audio via HDMI.
|
||
</p>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<input type="range" name="pi_hdmi_volume" id="piVolumeSlider"
|
||
value="{{ settings.pi_hdmi_volume or 70 }}" min="0" max="100"
|
||
oninput="updateVolumeDisplay(this.value)" />
|
||
<span id="volumeDisplay">{{ settings.pi_hdmi_volume or 70 }}%</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<button type="button" class="btn btn-secondary" onclick="testPiVolume()">
|
||
<span class="material-icons">volume_up</span>
|
||
Test Volume
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="day_start">
|
||
<span class="material-icons">wb_sunny</span>
|
||
Dag begint om:
|
||
</label>
|
||
<input type="time" name="day_start" value="{{ settings.day_start or '07:00' }}" />
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="night_start">
|
||
<span class="material-icons">nightlight</span>
|
||
Avond begint om:
|
||
</label>
|
||
<input type="time" name="night_start" value="{{ settings.night_start or '20:00' }}" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>
|
||
<span class="material-icons">speaker_group</span>
|
||
Sonos Zones:
|
||
</label>
|
||
<div class="checkboxes">
|
||
{% for zone in alle_zones %}
|
||
<div class="checkbox-item">
|
||
<input type="checkbox" name="zones" value="{{ zone }}" id="zone_{{ loop.index }}"
|
||
{% if zone in settings.zones %}checked{% endif %}>
|
||
<label for="zone_{{ loop.index }}">{{ zone }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row audio-row">
|
||
<div class="form-group audio-section">
|
||
<label for="audio_clip">
|
||
<span class="material-icons">music_note</span>
|
||
Adhaan Audio Bestand:
|
||
</label>
|
||
<select name="audio_clip" id="audioSelect">
|
||
{% for clip in clips %}
|
||
<option value="{{ clip }}" {% if clip == settings.audio_clip %}selected{% endif %}>
|
||
{% if clip == 'adhan1.mp3' %}Klassieke Adhaan
|
||
{% elif 'mekka' in clip %}Mekka Stijl
|
||
{% elif 'medina' in clip %}Medina Stijl
|
||
{% elif 'kort' in clip %}Korte Versie
|
||
{% elif 'traditioneel' in clip %}Traditioneel
|
||
{% else %}{{ clip }}
|
||
{% endif %}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group audio-preview-section">
|
||
<label>
|
||
<span class="material-icons">play_circle</span>
|
||
Audio Voorbeeld:
|
||
</label>
|
||
<div class="audio-preview">
|
||
<button type="button" class="preview-btn" onclick="previewAudio()">
|
||
<span class="material-icons">play_arrow</span>
|
||
Voorbeeld afspelen
|
||
</button>
|
||
<audio id="previewAudio" preload="none"></audio>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Prayer Times Tab -->
|
||
<div class="tab-content" id="prayer-tab">
|
||
<div class="settings-section">
|
||
<div class="form-group">
|
||
<label>
|
||
<span class="material-icons">schedule</span>
|
||
Gebedstijd Aanpassingen:
|
||
</label>
|
||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0.5rem 0;">
|
||
Voeg minuten toe (+) of haal af (-) van de VUMG gebedstijden. Bijvoorbeeld: +30 voor 30 minuten later, -15 voor 15 minuten eerder.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-row three-columns">
|
||
<div class="form-group">
|
||
<label for="fajr_offset">
|
||
<span class="material-icons">wb_twilight</span>
|
||
Fajr Aanpassing (minuten):
|
||
</label>
|
||
<input type="number" name="fajr_offset" value="{{ settings.fajr_offset or 0 }}" min="-120" max="120" step="5" />
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="zuhr_offset">
|
||
<span class="material-icons">wb_sunny</span>
|
||
Zuhr Aanpassing (minuten):
|
||
</label>
|
||
<input type="number" name="zuhr_offset" value="{{ settings.zuhr_offset or 0 }}" min="-120" max="120" step="5" />
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="asr_offset">
|
||
<span class="material-icons">brightness_low</span>
|
||
Asr Aanpassing (minuten):
|
||
</label>
|
||
<input type="number" name="asr_offset" value="{{ settings.asr_offset or 0 }}" min="-120" max="120" step="5" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="maghrib_offset">
|
||
<span class="material-icons">brightness_3</span>
|
||
Maghrib Aanpassing (minuten):
|
||
</label>
|
||
<input type="number" name="maghrib_offset" value="{{ settings.maghrib_offset or 0 }}" min="-120" max="120" step="5" />
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="isha_offset">
|
||
<span class="material-icons">brightness_2</span>
|
||
Isha Aanpassing (minuten):
|
||
</label>
|
||
<input type="number" name="isha_offset" value="{{ settings.isha_offset or 0 }}" min="-120" max="120" step="5" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Features Tab -->
|
||
<div class="tab-content" id="features-tab">
|
||
<div class="settings-section">
|
||
<div class="form-group">
|
||
<label>
|
||
<span class="material-icons">auto_stories</span>
|
||
Adzkaar na Gebed:
|
||
</label>
|
||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0.5rem 0;">
|
||
Toon automatisch een fullscreen Adzkaar scherm na elke gebedstijd voor spirituele versterking en dhikr.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<div class="checkbox-item">
|
||
<input type="checkbox" name="adzkaar_enabled" id="adzkaar_enabled"
|
||
{% if settings.adzkaar_enabled != False %}checked{% endif %}>
|
||
<label for="adzkaar_enabled">
|
||
<span class="material-icons">visibility</span>
|
||
Adzkaar scherm inschakelen
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="adzkaar_duration">
|
||
<span class="material-icons">timer</span>
|
||
Adzkaar duur (minuten):
|
||
</label>
|
||
<input type="number" name="adzkaar_duration" value="{{ settings.adzkaar_duration or 5 }}" min="1" max="30" step="1" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>
|
||
<span class="material-icons">format_quote</span>
|
||
Hadith Instellingen:
|
||
</label>
|
||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0.5rem 0;">
|
||
Stel in hoe vaak de hadith tekst op het hoofdscherm wordt bijgewerkt met een nieuwe hadith.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="hadith_interval_seconds">
|
||
<span class="material-icons">refresh</span>
|
||
Hadith update interval (seconden):
|
||
</label>
|
||
<input type="number" name="hadith_interval_seconds" value="{{ settings.hadith_interval_seconds or 30 }}" min="5" max="300" step="5" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- System Tab -->
|
||
<div class="tab-content" id="system-tab">
|
||
<div class="settings-section">
|
||
<div class="form-group">
|
||
<label>
|
||
<span class="material-icons">info</span>
|
||
Systeem Informatie:
|
||
</label>
|
||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0.5rem 0;">
|
||
Algemene systeem instellingen en test functies.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="button-group">
|
||
<button type="submit" name="test_clip" class="btn-secondary">
|
||
<span class="material-icons">speaker</span>
|
||
Test Adhaan op Sonos
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Save Button (always visible) -->
|
||
<div class="save-section">
|
||
<button type="submit" class="btn-primary btn-save">
|
||
<span class="material-icons">save</span>
|
||
Alle Instellingen Opslaan
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function previewAudio() {
|
||
const select = document.getElementById('audioSelect');
|
||
const audio = document.getElementById('previewAudio');
|
||
const selectedClip = select.value;
|
||
|
||
audio.src = `/static/clips/${selectedClip}`;
|
||
audio.play().catch(e => {
|
||
console.log('Audio afspelen mislukt:', e);
|
||
alert('Kon audio niet afspelen. Controleer of het bestand bestaat.');
|
||
});
|
||
}
|
||
|
||
// Auto-update preview audio source when selection changes
|
||
document.getElementById('audioSelect').addEventListener('change', function() {
|
||
const audio = document.getElementById('previewAudio');
|
||
audio.src = `/static/clips/${this.value}`;
|
||
});
|
||
|
||
// Set initial audio source
|
||
window.onload = function() {
|
||
const select = document.getElementById('audioSelect');
|
||
const audio = document.getElementById('previewAudio');
|
||
audio.src = `/static/clips/${select.value}`;
|
||
|
||
// Setup theme toggle
|
||
setupThemeToggle();
|
||
updateThemeIcon();
|
||
|
||
// Setup tabs
|
||
setupTabs();
|
||
};
|
||
|
||
// Theme toggle functionaliteit (gekopieerd van index.html)
|
||
function setupThemeToggle() {
|
||
const toggleBtn = document.getElementById("themeToggle");
|
||
if (!toggleBtn) return;
|
||
|
||
const currentTheme = localStorage.getItem("theme") || "dark";
|
||
document.documentElement.className = currentTheme;
|
||
|
||
toggleBtn.addEventListener("click", function () {
|
||
const html = document.documentElement;
|
||
const newTheme = html.className === "light" ? "dark" : "light";
|
||
html.className = newTheme;
|
||
localStorage.setItem("theme", newTheme);
|
||
updateThemeIcon();
|
||
});
|
||
}
|
||
|
||
function updateThemeIcon() {
|
||
const html = document.documentElement;
|
||
const icon = document.getElementById('themeIcon');
|
||
if (icon) {
|
||
if (html.className === 'light') {
|
||
icon.textContent = 'dark_mode';
|
||
} else {
|
||
icon.textContent = 'light_mode';
|
||
}
|
||
}
|
||
}
|
||
|
||
// Tab functionality
|
||
function setupTabs() {
|
||
const tabBtns = document.querySelectorAll('.tab-btn');
|
||
const tabContents = document.querySelectorAll('.tab-content');
|
||
|
||
tabBtns.forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const targetTab = btn.getAttribute('data-tab');
|
||
|
||
// Remove active class from all buttons and contents
|
||
tabBtns.forEach(b => b.classList.remove('active'));
|
||
tabContents.forEach(c => c.classList.remove('active'));
|
||
|
||
// Add active class to clicked button and corresponding content
|
||
btn.classList.add('active');
|
||
document.getElementById(targetTab + '-tab').classList.add('active');
|
||
|
||
// Save active tab to localStorage
|
||
localStorage.setItem('activeSettingsTab', targetTab);
|
||
});
|
||
});
|
||
|
||
// Restore last active tab
|
||
const savedTab = localStorage.getItem('activeSettingsTab');
|
||
if (savedTab) {
|
||
const savedBtn = document.querySelector(`[data-tab="${savedTab}"]`);
|
||
const savedContent = document.getElementById(savedTab + '-tab');
|
||
|
||
if (savedBtn && savedContent) {
|
||
// Remove active from all
|
||
tabBtns.forEach(b => b.classList.remove('active'));
|
||
tabContents.forEach(c => c.classList.remove('active'));
|
||
|
||
// Activate saved tab
|
||
savedBtn.classList.add('active');
|
||
savedContent.classList.add('active');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Pi HDMI Volume functies
|
||
function updateVolumeDisplay(value) {
|
||
document.getElementById('volumeDisplay').textContent = value + '%';
|
||
}
|
||
|
||
function testPiVolume() {
|
||
const volume = document.getElementById('piVolumeSlider').value;
|
||
|
||
// Verstuur volume naar backend
|
||
fetch('/api/set-pi-volume', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ volume: parseInt(volume) })
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
console.log('Pi volume ingesteld op', volume + '%');
|
||
|
||
// Test audio afspelen
|
||
const audio = document.getElementById('previewAudio');
|
||
if (audio && audio.src) {
|
||
audio.play().catch(e => {
|
||
console.log('Audio test mislukt:', e);
|
||
alert('Volume ingesteld, maar audio test mislukt. Controleer HDMI verbinding.');
|
||
});
|
||
} else {
|
||
alert(`Pi HDMI volume ingesteld op ${volume}%`);
|
||
}
|
||
} else {
|
||
alert('Fout bij instellen volume: ' + data.error);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Netwerk fout:', error);
|
||
alert('Kon volume niet instellen. Controleer verbinding.');
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|