Adhaan/adhan-webapp/templates/settings.html
filoor a5e27baa86 feat(webapp): implementatie van async/await voor API-calls en UI optimalisaties
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.
2025-05-29 21:30:40 +02:00

490 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 (0100):
</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 (0100):
</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>