Merge conflict resolved in done file
This commit is contained in:
commit
7a74b43b09
@ -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():
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user