Deze commit introduceert de overstap van traditionele threading naar async/await voor efficiëntere API-oproepen binnen de adhan-webapp. Dit omvat de vervanging van parallele executors met asyncio, waardoor de webapplicatie asynchroon weer- en Sonos-zonegegevens kan ophalen. Daarnaast zijn er prestatieverbeteringen in de gebruikersinterface doorgevoerd, zoals het cachen van DOM-elementen, debouncing van volume-updates en geoptimaliseerde tab-navigatie. Deze wijzigingen verbeteren de algehele snelheid en responsiviteit van de applicatie aanzienlijk.
490 lines
19 KiB
HTML
490 lines
19 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>
|
||
// Debounce functie voor betere performance
|
||
function debounce(func, wait) {
|
||
let timeout;
|
||
return function executedFunction(...args) {
|
||
const later = () => {
|
||
clearTimeout(timeout);
|
||
func(...args);
|
||
};
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
};
|
||
}
|
||
|
||
// Cache DOM elementen
|
||
const audioSelect = document.getElementById('audioSelect');
|
||
const previewAudio = document.getElementById('previewAudio');
|
||
const piVolumeSlider = document.getElementById('piVolumeSlider');
|
||
const volumeDisplay = document.getElementById('volumeDisplay');
|
||
|
||
function previewAudio() {
|
||
if (!previewAudio || !audioSelect) return;
|
||
|
||
const selectedClip = audioSelect.value;
|
||
previewAudio.src = `/static/clips/${selectedClip}`;
|
||
previewAudio.play().catch(e => {
|
||
console.log('Audio afspelen mislukt:', e);
|
||
alert('Kon audio niet afspelen. Controleer of het bestand bestaat.');
|
||
});
|
||
}
|
||
|
||
// Gebruik event delegation voor betere performance
|
||
document.addEventListener('change', function(e) {
|
||
if (e.target === audioSelect && previewAudio) {
|
||
previewAudio.src = `/static/clips/${e.target.value}`;
|
||
}
|
||
});
|
||
|
||
// Optimize volume updates
|
||
const updateVolumeDisplay = debounce(function(value) {
|
||
if (volumeDisplay) {
|
||
volumeDisplay.textContent = value + '%';
|
||
}
|
||
}, 100);
|
||
|
||
// Optimize Pi volume testing
|
||
const testPiVolume = debounce(function() {
|
||
if (!piVolumeSlider) return;
|
||
|
||
const volume = piVolumeSlider.value;
|
||
|
||
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 + '%');
|
||
|
||
if (previewAudio && previewAudio.src) {
|
||
previewAudio.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.');
|
||
});
|
||
}, 300);
|
||
|
||
// Optimize tab switching
|
||
function setupTabs() {
|
||
const tabBtns = document.querySelectorAll('.tab-btn');
|
||
const tabContents = document.querySelectorAll('.tab-content');
|
||
|
||
// Gebruik event delegation voor tab clicks
|
||
document.querySelector('.tab-navigation').addEventListener('click', function(e) {
|
||
const btn = e.target.closest('.tab-btn');
|
||
if (!btn) return;
|
||
|
||
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) {
|
||
tabBtns.forEach(b => b.classList.remove('active'));
|
||
tabContents.forEach(c => c.classList.remove('active'));
|
||
|
||
savedBtn.classList.add('active');
|
||
savedContent.classList.add('active');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Initialize on load
|
||
window.addEventListener('load', function() {
|
||
if (audioSelect && previewAudio) {
|
||
previewAudio.src = `/static/clips/${audioSelect.value}`;
|
||
}
|
||
|
||
setupThemeToggle();
|
||
updateThemeIcon();
|
||
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';
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|