Merge conflict resolved in done file

This commit is contained in:
filoor 2025-05-29 21:47:39 +02:00
commit 7a74b43b09
5 changed files with 426 additions and 341 deletions

View File

@ -1,11 +1,99 @@
from flask import Flask, render_template, request, redirect, send_from_directory, jsonify, request as flask_request
import requests, json, os, random, subprocess
from datetime import datetime
from datetime import datetime, timedelta
from config import *
from hijridate import Gregorian
from functools import lru_cache
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
app = Flask(__name__)
# Cache configuratie
CACHE_DURATION = 300 # 5 minuten cache voor API calls
last_api_call = {}
cached_data = {}
executor = ThreadPoolExecutor(max_workers=5) # Verhoogd naar 5 workers voor betere parallelle uitvoering
def get_cached_data(key, fetch_func, duration=CACHE_DURATION):
"""Haal data uit cache of voer fetch_func uit als cache verlopen is"""
current_time = time.time()
if key in cached_data and key in last_api_call:
if current_time - last_api_call[key] < duration:
return cached_data[key]
data = fetch_func()
cached_data[key] = data
last_api_call[key] = current_time
return data
def fetch_data_parallel():
"""Haal alle data parallel op met verbeterde error handling"""
futures = {
'weather': executor.submit(fetch_weather_data),
'sonos': executor.submit(fetch_sonos_zones),
'date': executor.submit(get_date_info),
'hadith': executor.submit(load_hadith),
'prayer_times': executor.submit(fetch_prayer_times)
}
results = {}
for key, future in futures.items():
try:
results[key] = future.result(timeout=5) # Timeout van 5 seconden per request
except Exception as e:
print(f"⚠️ Fout bij ophalen {key} data: {e}")
results[key] = get_fallback_data(key)
return results
def get_fallback_data(key):
"""Geef fallback data terug als API calls falen"""
fallbacks = {
'weather': {
'temperature': '--',
'feels_like': '--',
'description': 'Weer niet beschikbaar',
'humidity': '--',
'wind_speed': '--',
'icon': '01d'
},
'sonos': ['Woonkamer'],
'date': get_date_info(), # Gebruik lokale functie als fallback
'hadith': {
"text": "De daden die Allah het meest liefheeft zijn degenen die regelmatig worden verricht, zelfs als ze klein zijn.",
"bron": "Sahih al-Bukhari"
},
'prayer_times': {
"Fajr": "06:00",
"Zuhr": "12:30",
"Asr": "15:00",
"Maghrib": "17:30",
"Isha": "19:00"
}
}
return fallbacks.get(key, {})
@lru_cache(maxsize=1)
def fetch_prayer_times():
"""Haal gebedstijden op met caching"""
try:
res = requests.get(VUMG_API, timeout=5)
res.raise_for_status()
data = res.json()[0]
return {
"Fajr": data.get("fajr_jamah", "00:00")[:5],
"Zuhr": data.get("zuhr_jamah", "00:00")[:5],
"Asr": data.get("asr_jamah", "00:00")[:5],
"Maghrib": data.get("maghrib_jamah", "00:00")[:5],
"Isha": data.get("isha_jamah", "00:00")[:5]
}
except Exception as e:
print(f"⚠️ Fout bij ophalen gebedstijden: {e}")
return get_fallback_data('prayer_times')
# Voeg cache-control headers toe voor statische bestanden
@app.after_request
def add_header(response):
@ -28,8 +116,21 @@ CLIPS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static',
debug_time_offset = 0 # Offset in seconden voor debug mode
debug_mode_active = False
@lru_cache(maxsize=1)
def load_settings():
try:
if os.path.exists(SETTINGS_PATH) and os.path.getsize(SETTINGS_PATH) > 0:
with open(SETTINGS_PATH) as f:
return json.load(f)
except Exception as e:
print(f"⚠️ Fout bij laden van settings: {e}")
return {"volume": 30, "zones": ["Woonkamer"], "audio_clip": "adhan1.mp3"}
def fetch_weather_data():
"""Haalt weersinformatie op voor de geconfigureerde locatie"""
return get_cached_data('weather', lambda: _fetch_weather_data_impl())
def _fetch_weather_data_impl():
try:
params = {
'q': WEATHER_LOCATION,
@ -70,15 +171,6 @@ def load_hadith():
print(f"⚠️ Fout bij laden van hadith: {e}")
return {"text": "De daden die Allah het meest liefheeft zijn degenen die regelmatig worden verricht, zelfs als ze klein zijn.", "bron": "Sahih al-Bukhari"}
def load_settings():
try:
if os.path.exists(SETTINGS_PATH) and os.path.getsize(SETTINGS_PATH) > 0:
with open(SETTINGS_PATH) as f:
return json.load(f)
except Exception as e:
print(f"⚠️ Fout bij laden van settings: {e}")
return {"volume": 30, "zones": ["Woonkamer"], "audio_clip": "adhan1.mp3"}
def get_current_volume(settings):
"""Bepaal het juiste volume op basis van de huidige tijd"""
from datetime import datetime, timedelta
@ -158,7 +250,11 @@ def apply_prayer_offsets(gebedstijden, settings):
return adjusted_times
@lru_cache(maxsize=1)
def fetch_sonos_zones():
return get_cached_data('sonos_zones', lambda: _fetch_sonos_zones_impl())
def _fetch_sonos_zones_impl():
try:
res = requests.get(f'http://{SONOS_API_IP}:5005/zones', timeout=5)
res.raise_for_status()
@ -235,31 +331,18 @@ def get_date_info():
@app.route('/')
def index():
settings = load_settings()
# Haal alle data parallel op
data = fetch_data_parallel()
# Verwerk gebedstijden
gebedstijden = apply_prayer_offsets(data['prayer_times'], settings)
# Bepaal volgende gebed
now = datetime.now().strftime('%H:%M')
next_time = "Onbekend"
next_name = "Onbekend"
debug_data = {}
gebedstijden = {}
hadith = load_hadith()
weather = fetch_weather_data()
date_info = get_date_info()
try:
res = requests.get(VUMG_API)
res.raise_for_status()
data = res.json()[0]
gebedstijden = {
"Fajr": data.get("fajr_jamah", "00:00")[:5], # Haal seconden weg
"Zuhr": data.get("zuhr_jamah", "00:00")[:5], # Haal seconden weg
"Asr": data.get("asr_jamah", "00:00")[:5], # Haal seconden weg
"Maghrib": data.get("maghrib_jamah", "00:00")[:5], # Haal seconden weg
"Isha": data.get("isha_jamah", "00:00")[:5] # Haal seconden weg
}
# Pas offsets toe op gebedstijden
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
now = datetime.now().strftime('%H:%M')
for naam, tijd in gebedstijden.items():
if tijd > now:
next_time = tijd
@ -269,39 +352,23 @@ def index():
next_time = list(gebedstijden.values())[0]
next_name = list(gebedstijden.keys())[0]
debug_data = {
"api_response": data,
"gebedstijden": gebedstijden,
"now": now,
"next_time": next_time,
"next_name": next_name
}
except Exception as e:
print("❌ Fout bij ophalen/verwerken van gebedstijden:", e)
dua = "اللّهُمَّ اجْعَلْ صَلاتِي نُورًا"
return render_template('index.html',
next_time=next_time,
next_name=next_name,
dua=dua,
hadith=hadith,
dua="اللّهُمَّ اجْعَلْ صَلاتِي نُورًا",
hadith=data['hadith'],
gebedstijden=gebedstijden,
debug=debug_data,
settings=settings,
weather=weather,
date_info=date_info)
weather=data['weather'],
date_info=data['date'])
@app.route('/instellingen', methods=['GET', 'POST'])
def instellingen():
settings = load_settings()
clips = [f for f in os.listdir(CLIPS_PATH) if f.endswith('.mp3') or f.endswith('.wav')]
alle_zones = fetch_sonos_zones()
if request.method == 'POST':
try:
# Nieuwe volume instellingen
if 'volume_day' in request.form and 'volume_night' in request.form:
settings = load_settings()
settings['volume_day'] = int(request.form['volume_day'])
settings['volume_night'] = int(request.form['volume_night'])
settings['night_start'] = request.form['night_start']
@ -310,6 +377,7 @@ def instellingen():
settings['volume'] = settings['volume_day']
else:
# Fallback: gebruik default waarden als velden ontbreken
settings = load_settings()
settings['volume_day'] = settings.get('volume_day', 15)
settings['volume_night'] = settings.get('volume_night', 8)
settings['night_start'] = settings.get('night_start', '20:00')
@ -373,10 +441,14 @@ def instellingen():
# Probeer alsnog door te gaan zonder de Pi volume instelling
return redirect('/instellingen')
# Haal alle data parallel op
data = fetch_data_parallel()
return render_template('settings.html',
settings=settings,
alle_zones=alle_zones,
clips=clips)
settings=load_settings(),
alle_zones=data['sonos'],
date_info=data['date'],
weather=data['weather'])
@app.route('/api/hadith')
def api_hadith():

View File

@ -13,3 +13,6 @@ Mon May 26 18:17:57 CEST 2025: Tijdzone probleem opgelost - Container gebruikt n
2025-05-28 03:49:24 - Adzkaar fullscreen functionaliteit geïmplementeerd: nieuwe /adzkaar route, instellingen, debug knoppen, automatische trigger na gebedstijden, Nederlandse/Arabische dhikr content
2025-05-28 03:55:38 - Adzkaar scherm verbeterd naar kaart-voor-kaart weergave met navigatie knoppen en toetsenbord besturing
Wed May 28 14:09:12 CEST 2025: Sonos debug tijd synchronisatie geïmplementeerd - get_current_volume functie en cron script gebruiken nu debug tijd API, volume bepaling werkt correct in debug mode
2025-05-29 21:24:37 - Performance optimalisaties toegevoegd: caching voor API calls en instellingen
2025-05-29 21:27:02 - UI optimalisaties toegevoegd: lazy loading, parallelle API calls en caching
2025-05-29 21:30:24 - Geavanceerde optimalisaties toegevoegd: async/await, debouncing en DOM caching

View File

@ -3,6 +3,8 @@
<head>
<meta charset="UTF-8">
<title>Adzkaar na het Gebed</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="/static/colors.css">
<link rel="stylesheet" href="/static/style.css">
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&family=Lato:wght@300;400;600&display=swap" rel="stylesheet">
@ -208,40 +210,28 @@
</head>
<body>
<div class="adzkaar-container">
<button class="close-button" onclick="closeAdzkaar()" title="Sluiten">
<span class="material-icons">close</span>
</button>
<div class="countdown-timer" id="countdownTimer">
5:00
</div>
<div class="adzkaar-header">
<h1 class="adzkaar-title">أذكار بعد الصلاة</h1>
<p class="adzkaar-subtitle">Adzkaar na het Gebed</p>
<h1 class="adzkaar-title">Adzkaar na het Gebed</h1>
<p class="adzkaar-subtitle">Herinneringen voor na het gebed</p>
</div>
<div class="adzkaar-content">
<div class="dhikr-navigation">
<button class="nav-btn" id="prevBtn" onclick="previousDhikr()" disabled>
<button class="nav-btn" id="prevBtn" disabled>
<span class="material-icons">arrow_back</span>
</button>
<div class="dhikr-counter">
<span id="currentDhikr">1</span> / <span id="totalDhikr">6</span>
</div>
<button class="nav-btn" id="nextBtn" onclick="nextDhikr()">
<span class="dhikr-counter" id="counter">1/33</span>
<button class="nav-btn" id="nextBtn">
<span class="material-icons">arrow_forward</span>
</button>
</div>
<div class="dhikr-container" id="dhikrContainer">
<div class="dhikr-item active" data-index="0">
<div class="dhikr-arabic">أَسْتَغْفِرُ اللَّهَ</div>
<div class="dhikr-transliteration">Astaghfirullah</div>
<div class="dhikr-translation">Ik vraag Allah om vergeving</div>
<div class="dhikr-count">3x</div>
<div class="dhikr-container">
<div class="dhikr-item active" id="dhikr-1">
<div class="dhikr-arabic">سُبْحَانَ اللَّهِ</div>
<div class="dhikr-transliteration">Subhanallah</div>
<div class="dhikr-translation">Glorie zij Allah</div>
<div class="dhikr-count">33x</div>
</div>
<div class="dhikr-item" data-index="1">
@ -252,27 +242,20 @@
</div>
<div class="dhikr-item" data-index="2">
<div class="dhikr-arabic">سُبْحَانَ اللَّهِ</div>
<div class="dhikr-transliteration">Subhan Allah</div>
<div class="dhikr-translation">Glorie zij Allah</div>
<div class="dhikr-count">33x</div>
</div>
<div class="dhikr-item" data-index="3">
<div class="dhikr-arabic">الْحَمْدُ لِلَّهِ</div>
<div class="dhikr-transliteration">Alhamdulillah</div>
<div class="dhikr-translation">Alle lof zij Allah</div>
<div class="dhikr-count">33x</div>
</div>
<div class="dhikr-item" data-index="4">
<div class="dhikr-item" data-index="3">
<div class="dhikr-arabic">اللَّهُ أَكْبَرُ</div>
<div class="dhikr-transliteration">Allahu Akbar</div>
<div class="dhikr-translation">Allah is de Grootste</div>
<div class="dhikr-count">34x</div>
</div>
<div class="dhikr-item" data-index="5">
<div class="dhikr-item" data-index="4">
<div class="dhikr-arabic">لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ</div>
<div class="dhikr-transliteration">La ilaha illa Allah wahdahu la sharika lah, lahu al-mulku wa lahu al-hamdu wa huwa 'ala kulli shay'in qadir</div>
<div class="dhikr-translation">Er is geen god behalve Allah alleen, Hij heeft geen partner. Aan Hem behoort de heerschappij en aan Hem behoort alle lof, en Hij heeft macht over alle dingen</div>
@ -280,109 +263,85 @@
</div>
</div>
</div>
<div class="countdown-timer" id="countdown">00:00</div>
<button class="close-button" id="closeBtn">
<span class="material-icons">close</span>
</button>
</div>
<script>
let countdownSeconds = {{ duration_minutes }} * 60;
// Cache DOM elementen
const dhikrItems = document.querySelectorAll('.dhikr-item');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const counter = document.getElementById('counter');
const closeBtn = document.getElementById('closeBtn');
const countdownEl = document.getElementById('countdown');
let currentIndex = 0;
const totalItems = dhikrItems.length;
let countdownInterval;
let currentDhikrIndex = 0;
const totalDhikrs = 6;
let autoSwitchInterval;
function updateCountdown() {
const minutes = Math.floor(countdownSeconds / 60);
const seconds = countdownSeconds % 60;
const display = `${minutes}:${seconds.toString().padStart(2, '0')}`;
document.getElementById('countdownTimer').textContent = display;
if (countdownSeconds <= 0) {
closeAdzkaar();
return;
}
countdownSeconds--;
}
function closeAdzkaar() {
clearInterval(countdownInterval);
clearInterval(autoSwitchInterval);
window.close();
// Als window.close() niet werkt (bijv. in kiosk mode), ga terug naar hoofdpagina
setTimeout(() => {
window.location.href = '/';
}, 100);
// Update counter display
function updateCounter() {
counter.textContent = `${currentIndex + 1}/${totalItems}`;
}
// Show dhikr item
function showDhikr(index) {
// Verberg alle dhikr items
document.querySelectorAll('.dhikr-item').forEach(item => {
item.classList.remove('active');
});
// Toon de geselecteerde dhikr
const targetDhikr = document.querySelector(`[data-index="${index}"]`);
if (targetDhikr) {
targetDhikr.classList.add('active');
dhikrItems.forEach(item => item.classList.remove('active'));
dhikrItems[index].classList.add('active');
currentIndex = index;
updateCounter();
updateNavigationButtons();
}
// Update counter
document.getElementById('currentDhikr').textContent = index + 1;
// Update navigation buttons
document.getElementById('prevBtn').disabled = index === 0;
document.getElementById('nextBtn').disabled = index === totalDhikrs - 1;
function updateNavigationButtons() {
prevBtn.disabled = currentIndex === 0;
nextBtn.disabled = currentIndex === totalItems - 1;
}
function nextDhikr() {
if (currentDhikrIndex < totalDhikrs - 1) {
currentDhikrIndex++;
showDhikr(currentDhikrIndex);
}
}
function previousDhikr() {
if (currentDhikrIndex > 0) {
currentDhikrIndex--;
showDhikr(currentDhikrIndex);
}
}
function autoSwitchDhikr() {
if (currentDhikrIndex < totalDhikrs - 1) {
currentDhikrIndex++;
showDhikr(currentDhikrIndex);
} else {
// Als we bij de laatste dhikr zijn, begin opnieuw
currentDhikrIndex = 0;
showDhikr(currentDhikrIndex);
}
}
// Keyboard navigation
document.addEventListener('keydown', function(event) {
if (event.key === 'ArrowRight' || event.key === ' ') {
event.preventDefault();
nextDhikr();
} else if (event.key === 'ArrowLeft') {
event.preventDefault();
previousDhikr();
} else if (event.key === 'Escape') {
closeAdzkaar();
// Navigation event handlers
prevBtn.addEventListener('click', () => {
if (currentIndex > 0) {
showDhikr(currentIndex - 1);
}
});
nextBtn.addEventListener('click', () => {
if (currentIndex < totalItems - 1) {
showDhikr(currentIndex + 1);
}
});
// Close button handler
closeBtn.addEventListener('click', () => {
window.close();
});
// Countdown timer
function startCountdown(duration) {
let timer = duration;
clearInterval(countdownInterval);
countdownInterval = setInterval(() => {
const minutes = parseInt(timer / 60, 10);
const seconds = parseInt(timer % 60, 10);
countdownEl.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
if (--timer < 0) {
clearInterval(countdownInterval);
window.close();
}
}, 1000);
}
// Initialize
showDhikr(0);
// Start countdown
countdownInterval = setInterval(updateCountdown, 1000);
updateCountdown();
// Start automatische wissel elke 30 seconden
autoSwitchInterval = setInterval(autoSwitchDhikr, 30000);
// Auto-close na ingestelde tijd
setTimeout(closeAdzkaar, countdownSeconds * 1000);
startCountdown(300); // 5 minuten countdown
</script>
</body>
</html>

View File

@ -4,8 +4,17 @@
<meta charset="UTF-8">
<title>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>
<div class="app">
@ -164,6 +173,14 @@
</div>
<script>
// Cache DOM elementen
const currentTimeEl = document.getElementById('current-time');
const countdownEl = document.getElementById('countdown');
const muteBtn = document.getElementById('muteBtn');
const muteIcon = document.getElementById('muteIcon');
const adhanAudio = document.getElementById('adhanAudio');
const adzkaarModal = document.getElementById('adzkaarModal');
// Prayer times data from server
window.prayerTimes = [
{% for naam, tijd in gebedstijden.items() %}
@ -176,11 +193,45 @@
{% endfor %}
];
const nextPrayerTime = "{{ next_time }}";
const hadithInterval = {{ settings.hadith_interval_seconds or 30 }} * 1000; // Convert to milliseconds
</script>
<script src="/static/countdown.js"></script>
<script>
window.onload = function () {
const hadithInterval = {{ settings.hadith_interval_seconds or 30 }} * 1000;
// 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);
};
}
// Optimize weather updates
const updateWeather = debounce(async function() {
try {
const response = await fetch('/api/weather');
const data = await response.json();
updateWeatherUI(data);
} catch (error) {
console.error('Fout bij ophalen weerdata:', error);
}
}, 1000);
// Optimize hadith updates
const updateHadith = debounce(async function() {
try {
const response = await fetch('/api/hadith');
const data = await response.json();
updateHadithUI(data);
} catch (error) {
console.error('Fout bij ophalen hadith:', error);
}
}, 1000);
// Initialize on load
window.addEventListener('load', function() {
updateCurrentTime();
setInterval(updateCurrentTime, 60000);
setupThemeToggle();
@ -188,44 +239,35 @@
// Update weerdata elke 10 minuten
updateWeather();
setInterval(updateWeather, 600000); // 10 minuten
setInterval(updateWeather, 600000);
// Update hadith elke X seconden (instelbaar)
// Update hadith elke X seconden
updateHadith();
setInterval(updateHadith, hadithInterval);
};
// Functie om weerdata bij te werken
function updateWeather() {
fetch('/api/weather')
.then(response => response.json())
.then(data => {
document.querySelector('.weather-section-left .weather-temp').textContent = data.temperature + '°C';
document.querySelector('.weather-section-left .weather-desc').textContent = data.description;
document.querySelector('.weather-section-left .weather-detail:nth-child(1)').textContent = `Voelt als ${data.feels_like}°C`;
document.querySelector('.weather-section-left .weather-detail:nth-child(2)').textContent = `Vochtigheid ${data.humidity}%`;
document.querySelector('.weather-section-left .weather-detail:nth-child(3)').textContent = `Wind ${data.wind_speed} km/h`;
})
.catch(error => {
console.log('Fout bij bijwerken weerdata:', error);
// Setup event listeners
setupEventListeners();
});
// Setup event listeners
function setupEventListeners() {
if (muteBtn) {
muteBtn.addEventListener('click', toggleMute);
}
}
// Functie om hadith bij te werken
function updateHadith() {
fetch('/api/hadith')
.then(response => response.json())
.then(data => {
const hadithTextElement = document.querySelector('.hadith-center blockquote');
// Toggle mute state
function toggleMute() {
const isMuted = adhanAudio.muted;
adhanAudio.muted = !isMuted;
muteIcon.textContent = isMuted ? 'volume_off' : 'volume_up';
if (hadithTextElement) {
// Update de tekst en bron
hadithTextElement.innerHTML = `"${data.text}"<footer>— ${data.bron}</footer>`;
}
})
.catch(error => {
console.log('Fout bij bijwerken hadith:', error);
});
// Update mute state in settings
fetch('/api/mute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mute: !isMuted })
}).catch(error => console.error('Fout bij updaten mute state:', error));
}
// Theme toggle icon dynamisch aanpassen
@ -241,25 +283,6 @@
updateThemeIcon();
document.getElementById('themeToggle').addEventListener('click', updateThemeIcon);
// Mute functionaliteit
let muteStatus = false;
function setMuteIcon() {
const icon = document.getElementById('muteIcon');
icon.textContent = muteStatus ? 'volume_off' : 'volume_up';
}
function toggleMute(e) {
e.preventDefault();
muteStatus = !muteStatus;
setMuteIcon();
fetch('/api/mute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mute: muteStatus })
});
}
document.getElementById('muteBtn').addEventListener('click', toggleMute);
setMuteIcon();
// Adzkaar Modal Functionaliteit
let countdownSeconds = 5 * 60; // 5 minuten standaard
let countdownInterval;
@ -375,5 +398,6 @@
window.nextDhikr = nextDhikr;
window.previousDhikr = previousDhikr;
</script>
<script src="/static/countdown.js"></script>
</body>
</html>

View File

@ -4,8 +4,17 @@
<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">
@ -318,37 +327,134 @@
</div>
<script>
function previewAudio() {
const select = document.getElementById('audioSelect');
const audio = document.getElementById('previewAudio');
const selectedClip = select.value;
// 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);
};
}
audio.src = `/static/clips/${selectedClip}`;
audio.play().catch(e => {
// 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.');
});
}
// 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}`;
// Gebruik event delegation voor betere performance
document.addEventListener('change', function(e) {
if (e.target === audioSelect && previewAudio) {
previewAudio.src = `/static/clips/${e.target.value}`;
}
});
// Set initial audio source
window.onload = function() {
const select = document.getElementById('audioSelect');
const audio = document.getElementById('previewAudio');
audio.src = `/static/clips/${select.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}`;
}
// Setup theme toggle
setupThemeToggle();
updateThemeIcon();
// Setup tabs
setupTabs();
};
});
// Theme toggle functionaliteit (gekopieerd van index.html)
function setupThemeToggle() {
@ -378,85 +484,6 @@
}
}
}
// 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>