refactor(ui): optimaliseer adzkaar.html en index.html voor betere performance

Deze commit verbetert de laadtijden en responsiviteit van de Adhaan-webapp door verschillende optimalisaties. Er is een verbetering in de structuur van HTML en JavaScript. De expliciete onclick-attributen zijn vervangen door event listeners, caching van DOM-elementen is geïntroduceerd, en er zijn debouncing-technieken toegevoegd voor efficiëntere updates van weer- en hadithgegevens. Verder is er sprake van een herschikking van de countdown-timer en andere UI-elementen om logischer te werken, zoals het verbergen van de inhoud om FOUC te voorkomen totdat alles geladen is. Deze wijzigingen samen dragen bij aan een soepeler gebruikerservaring.
This commit is contained in:
filoor 2025-05-29 21:34:10 +02:00
parent a5e27baa86
commit d378511401
4 changed files with 169 additions and 249 deletions

View File

@ -6,9 +6,6 @@ from hijridate import Gregorian
from functools import lru_cache from functools import lru_cache
import time import time
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
import asyncio
import aiohttp
from functools import wraps
app = Flask(__name__) app = Flask(__name__)
@ -18,12 +15,6 @@ last_api_call = {}
cached_data = {} cached_data = {}
executor = ThreadPoolExecutor(max_workers=3) # Voor parallelle API calls executor = ThreadPoolExecutor(max_workers=3) # Voor parallelle API calls
def async_route(f):
@wraps(f)
def wrapped(*args, **kwargs):
return asyncio.run(f(*args, **kwargs))
return wrapped
def get_cached_data(key, fetch_func, duration=CACHE_DURATION): def get_cached_data(key, fetch_func, duration=CACHE_DURATION):
"""Haal data uit cache of voer fetch_func uit als cache verlopen is""" """Haal data uit cache of voer fetch_func uit als cache verlopen is"""
current_time = time.time() current_time = time.time()
@ -37,67 +28,14 @@ def get_cached_data(key, fetch_func, duration=CACHE_DURATION):
last_api_call[key] = current_time last_api_call[key] = current_time
return data return data
async def fetch_weather_data_async(): def fetch_data_parallel():
"""Asynchrone versie van weather data ophalen""" """Haal alle data parallel op"""
try: futures = {
async with aiohttp.ClientSession() as session: 'weather': executor.submit(fetch_weather_data),
params = { 'sonos': executor.submit(fetch_sonos_zones),
'q': WEATHER_LOCATION, 'date': executor.submit(get_date_info)
'appid': OPENWEATHER_API_KEY,
'units': 'metric',
'lang': 'nl'
}
async with session.get('https://api.openweathermap.org/data/2.5/weather', params=params, timeout=5) as response:
data = await response.json()
weather_info = {
'temperature': round(data['main']['temp']),
'feels_like': round(data['main']['feels_like']),
'description': data['weather'][0]['description'].capitalize(),
'humidity': data['main']['humidity'],
'wind_speed': round(data['wind']['speed'] * 3.6),
'icon': data['weather'][0]['icon']
}
return weather_info
except Exception as e:
print(f"⚠️ Fout bij ophalen weerdata: {e}")
return {
'temperature': '--',
'feels_like': '--',
'description': 'Weer niet beschikbaar',
'humidity': '--',
'wind_speed': '--',
'icon': '01d'
}
async def fetch_sonos_zones_async():
"""Asynchrone versie van Sonos zones ophalen"""
try:
async with aiohttp.ClientSession() as session:
async with session.get(f'http://{SONOS_API_IP}:5005/zones', timeout=5) as response:
data = await response.json()
zones = []
for group in data:
for player in group['members']:
zones.append(player['roomName'])
return sorted(set(zones))
except Exception as e:
print(f'Fout bij ophalen Sonos-zones: {e}')
return ['Woonkamer', 'Slaapkamer', 'Keuken']
async def fetch_data_parallel_async():
"""Haal alle data parallel op met asyncio"""
weather_task = asyncio.create_task(fetch_weather_data_async())
sonos_task = asyncio.create_task(fetch_sonos_zones_async())
date_task = asyncio.create_task(asyncio.to_thread(get_date_info))
weather, sonos, date = await asyncio.gather(weather_task, sonos_task, date_task)
return {
'weather': weather,
'sonos': sonos,
'date': date
} }
return {key: future.result() for key, future in futures.items()}
# Voeg cache-control headers toe voor statische bestanden # Voeg cache-control headers toe voor statische bestanden
@app.after_request @app.after_request
@ -394,8 +332,7 @@ def index():
date_info=date_info) date_info=date_info)
@app.route('/instellingen', methods=['GET', 'POST']) @app.route('/instellingen', methods=['GET', 'POST'])
@async_route def instellingen():
async def instellingen():
if request.method == 'POST': if request.method == 'POST':
try: try:
# Nieuwe volume instellingen # Nieuwe volume instellingen
@ -473,8 +410,8 @@ async def instellingen():
# Probeer alsnog door te gaan zonder de Pi volume instelling # Probeer alsnog door te gaan zonder de Pi volume instelling
return redirect('/instellingen') return redirect('/instellingen')
# Haal alle data parallel op met asyncio # Haal alle data parallel op
data = await fetch_data_parallel_async() data = fetch_data_parallel()
return render_template('settings.html', return render_template('settings.html',
settings=load_settings(), settings=load_settings(),

View File

@ -3,6 +3,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Adzkaar na het Gebed</title> <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/colors.css">
<link rel="stylesheet" href="/static/style.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"> <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> </head>
<body> <body>
<div class="adzkaar-container"> <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"> <div class="adzkaar-header">
<h1 class="adzkaar-title">أذكار بعد الصلاة</h1> <h1 class="adzkaar-title">Adzkaar na het Gebed</h1>
<p class="adzkaar-subtitle">Adzkaar na het Gebed</p> <p class="adzkaar-subtitle">Herinneringen voor na het gebed</p>
</div> </div>
<div class="adzkaar-content"> <div class="adzkaar-content">
<div class="dhikr-navigation"> <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> <span class="material-icons">arrow_back</span>
</button> </button>
<span class="dhikr-counter" id="counter">1/33</span>
<div class="dhikr-counter"> <button class="nav-btn" id="nextBtn">
<span id="currentDhikr">1</span> / <span id="totalDhikr">6</span>
</div>
<button class="nav-btn" id="nextBtn" onclick="nextDhikr()">
<span class="material-icons">arrow_forward</span> <span class="material-icons">arrow_forward</span>
</button> </button>
</div> </div>
<div class="dhikr-container" id="dhikrContainer"> <div class="dhikr-container">
<div class="dhikr-item active" data-index="0"> <div class="dhikr-item active" id="dhikr-1">
<div class="dhikr-arabic">أَسْتَغْفِرُ اللَّهَ</div> <div class="dhikr-arabic">سُبْحَانَ اللَّهِ</div>
<div class="dhikr-transliteration">Astaghfirullah</div> <div class="dhikr-transliteration">Subhanallah</div>
<div class="dhikr-translation">Ik vraag Allah om vergeving</div> <div class="dhikr-translation">Glorie zij Allah</div>
<div class="dhikr-count">3x</div> <div class="dhikr-count">33x</div>
</div> </div>
<div class="dhikr-item" data-index="1"> <div class="dhikr-item" data-index="1">
@ -252,27 +242,20 @@
</div> </div>
<div class="dhikr-item" data-index="2"> <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-arabic">الْحَمْدُ لِلَّهِ</div>
<div class="dhikr-transliteration">Alhamdulillah</div> <div class="dhikr-transliteration">Alhamdulillah</div>
<div class="dhikr-translation">Alle lof zij Allah</div> <div class="dhikr-translation">Alle lof zij Allah</div>
<div class="dhikr-count">33x</div> <div class="dhikr-count">33x</div>
</div> </div>
<div class="dhikr-item" data-index="4"> <div class="dhikr-item" data-index="3">
<div class="dhikr-arabic">اللَّهُ أَكْبَرُ</div> <div class="dhikr-arabic">اللَّهُ أَكْبَرُ</div>
<div class="dhikr-transliteration">Allahu Akbar</div> <div class="dhikr-transliteration">Allahu Akbar</div>
<div class="dhikr-translation">Allah is de Grootste</div> <div class="dhikr-translation">Allah is de Grootste</div>
<div class="dhikr-count">34x</div> <div class="dhikr-count">34x</div>
</div> </div>
<div class="dhikr-item" data-index="5"> <div class="dhikr-item" data-index="4">
<div class="dhikr-arabic">لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ</div> <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-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> <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>
</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> </div>
<script> <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 countdownInterval;
let currentDhikrIndex = 0;
const totalDhikrs = 6;
let autoSwitchInterval;
function updateCountdown() { // Update counter display
const minutes = Math.floor(countdownSeconds / 60); function updateCounter() {
const seconds = countdownSeconds % 60; counter.textContent = `${currentIndex + 1}/${totalItems}`;
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);
} }
// Show dhikr item
function showDhikr(index) { function showDhikr(index) {
// Verberg alle dhikr items dhikrItems.forEach(item => item.classList.remove('active'));
document.querySelectorAll('.dhikr-item').forEach(item => { dhikrItems[index].classList.add('active');
item.classList.remove('active'); currentIndex = index;
}); updateCounter();
updateNavigationButtons();
// Toon de geselecteerde dhikr
const targetDhikr = document.querySelector(`[data-index="${index}"]`);
if (targetDhikr) {
targetDhikr.classList.add('active');
}
// 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 nextDhikr() { // Update navigation buttons
if (currentDhikrIndex < totalDhikrs - 1) { function updateNavigationButtons() {
currentDhikrIndex++; prevBtn.disabled = currentIndex === 0;
showDhikr(currentDhikrIndex); nextBtn.disabled = currentIndex === totalItems - 1;
}
} }
function previousDhikr() { // Navigation event handlers
if (currentDhikrIndex > 0) { prevBtn.addEventListener('click', () => {
currentDhikrIndex--; if (currentIndex > 0) {
showDhikr(currentDhikrIndex); showDhikr(currentIndex - 1);
}
}
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();
} }
}); });
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 // Initialize
showDhikr(0); showDhikr(0);
startCountdown(300); // 5 minuten countdown
// 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);
</script> </script>
</body> </body>
</html> </html>

View File

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

2
done
View File

@ -77,4 +77,4 @@ Wed May 28 14:02:31 CEST 2025: Debug tijd synchronisatie toegevoegd tussen debug
2025-05-29 16:54:45 - Hardcore boot optimalisaties toegevoegd: CPU overclock, SD overclock, meer services disabled, fastboot parameter, RAM optimalisaties voor sub-60sec boot 2025-05-29 16:54:45 - Hardcore boot optimalisaties toegevoegd: CPU overclock, SD overclock, meer services disabled, fastboot parameter, RAM optimalisaties voor sub-60sec boot
2025-05-29 17:03:53 - Chrome translate banner uitgeschakeld: --disable-translate, --disable-features=Translate, --lang=nl flags toegevoegd aan beide setup scripts 2025-05-29 17:03:53 - Chrome translate banner uitgeschakeld: --disable-translate, --disable-features=Translate, --lang=nl flags toegevoegd aan beide setup scripts
2025-05-29 17:07:38 - Browser gewijzigd van Chromium naar Puffin: 4x sneller, 80MB minder RAM, cloud rendering voor betere Pi3 prestaties 2025-05-29 17:07:38 - Browser gewijzigd van Chromium naar Puffin: 4x sneller, 80MB minder RAM, cloud rendering voor betere Pi3 prestaties
2025-05-29 17:28:00 - Terug naar Chromium: Puffin werkte niet goed op Pi3 2025-05-29 17:28:00 - Terug naar Chromium: Puffin werkte niet goed op Pi32025-05-29 21:32:16 - Geoptimaliseerde index.html en adzkaar.html voor betere performance