je commitbericht
This commit is contained in:
parent
bd98a2b2f1
commit
df8f4c7d9b
6
.lh/.lhignore
Normal file
6
.lh/.lhignore
Normal file
@ -0,0 +1,6 @@
|
||||
# list file to not track by the local-history extension. comment line starts with a '#' character
|
||||
# each line describe a regular expression pattern (search for 'Javascript regex')
|
||||
# it will relate to the workspace directory root. for example:
|
||||
# '.*\.txt' ignores any file with 'txt' extension
|
||||
# '/test/.*' ignores all the files under the 'test' directory
|
||||
# '.*/test/.*' ignores all the files under any 'test' directory (even under sub-folders)
|
||||
30
.lh/adhan-webapp/Dockerfile.json
Normal file
30
.lh/adhan-webapp/Dockerfile.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"sourceFile": "adhan-webapp/Dockerfile",
|
||||
"activeCommit": 0,
|
||||
"commits": [
|
||||
{
|
||||
"activePatchIndex": 3,
|
||||
"patches": [
|
||||
{
|
||||
"date": 1748182920273,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
||||
},
|
||||
{
|
||||
"date": 1748183154468,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -10,10 +10,7 @@\n \n # Kopieer de rest van de bestanden\n COPY . .\n \n-# Voeg cache-control headers toe aan statische bestanden\n-RUN echo \"location /static/ { add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; }\" > /etc/nginx/conf.d/static.conf\n-\n EXPOSE 80\n \n CMD [\"python\", \"app.py\"]\n"
|
||||
},
|
||||
{
|
||||
"date": 1748183197708,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,1 +1,20 @@\n-\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\n\\ No newline at end of file\n+FROM python:3.11-slim\n+\n+WORKDIR /app\n+\n+# Installeer alleen de benodigde packages\n+RUN pip install --no-cache-dir flask requests\n+\n+# Kopieer de applicatie bestanden\n+COPY . .\n+\n+# Maak de benodigde directories aan\n+RUN mkdir -p /app/static/clips\n+\n+# Stel de juiste permissies in\n+RUN chmod -R 755 /app\n+\n+EXPOSE 80\n+\n+# Start de applicatie\n+CMD [\"python\", \"app.py\"]\n"
|
||||
},
|
||||
{
|
||||
"date": 1748199351044,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,1 +1,20 @@\n-\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\n\\ No newline at end of file\n+FROM python:3.11-slim\n+\n+WORKDIR /app\n+\n+# Installeer alleen de benodigde packages\n+RUN pip install --no-cache-dir flask requests\n+\n+# Kopieer de applicatie bestanden\n+COPY . .\n+\n+# Maak de benodigde directories aan\n+RUN mkdir -p /app/static/clips\n+\n+# Stel de juiste permissies in\n+RUN chmod -R 755 /app\n+\n+EXPOSE 80\n+\n+# Start de applicatie\n+CMD [\"python\", \"start_all.py\"]\n"
|
||||
}
|
||||
],
|
||||
"date": 1748182920273,
|
||||
"name": "Commit-0",
|
||||
"content": "FROM python:3.11-slim\n\nWORKDIR /app\n\nRUN pip install flask requests\n\n# Kopieer eerst alleen de requirements om caching te optimaliseren\nCOPY requirements.txt .\nRUN pip install -r requirements.txt\n\n# Kopieer de rest van de bestanden\nCOPY . .\n\n# Voeg cache-control headers toe aan statische bestanden\nRUN echo \"location /static/ { add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; }\" > /etc/nginx/conf.d/static.conf\n\nEXPOSE 80\n\nCMD [\"python\", \"app.py\"]\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.lh/adhan-webapp/README.md.json
Normal file
18
.lh/adhan-webapp/README.md.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"sourceFile": "adhan-webapp/README.md",
|
||||
"activeCommit": 0,
|
||||
"commits": [
|
||||
{
|
||||
"activePatchIndex": 0,
|
||||
"patches": [
|
||||
{
|
||||
"date": 1748203692581,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
||||
}
|
||||
],
|
||||
"date": 1748203692581,
|
||||
"name": "Commit-0",
|
||||
"content": "# Adhaan Gebedstijden Display\n\nEen moderne, islamitische gebedstijden display applicatie met automatische adhaan, weersinformatie en Sonos integratie.\n\n## ✨ Nieuwe Functies\n\n### 🌤️ Weersinformatie\n- Actuele temperatuur en weersomstandigheden\n- Automatische updates elke 10 minuten\n- Nederlandse weerbeschrijvingen\n- Windsnelheid en luchtvochtigheid\n\n### 🎵 Uitgebreide Audio Opties\n- Meerdere adhaan-stijlen beschikbaar\n- Audio preview functie in instellingen\n- Verbeterde instellingen interface\n- Test functionaliteit voor Sonos\n\n## 🚀 Installatie\n\n### 1. OpenWeatherMap API Key verkrijgen\n\n1. Ga naar [OpenWeatherMap](https://openweathermap.org/api)\n2. Maak een gratis account aan\n3. Verkrijg je API key (1000 gratis calls per dag)\n\n### 2. Configuratie\n\nBewerk `config.py` en vervang `YOUR_API_KEY_HERE` met je OpenWeatherMap API key:\n\n```python\nOPENWEATHER_API_KEY = 'jouw_api_key_hier'\n```\n\nOptioneel kun je ook de locatie aanpassen:\n\n```python\nWEATHER_LOCATION = 'Amsterdam,NL' # Vervang met jouw stad\n```\n\n### 3. Docker opstarten\n\n```bash\ndocker-compose up -d\n```\n\n## 📱 Gebruik\n\n### Hoofdscherm\n- **Links**: Huidige tijd, datum en countdown naar volgende gebed\n- **Rechts**: Hadith, gebedstijden en weersinformatie\n- **Knoppen**: Dark/light mode, instellingen, mute\n\n### Instellingen\n- **Volume**: Sonos volume instelling (0-100)\n- **Zones**: Selecteer welke Sonos speakers gebruikt worden\n- **Audio**: Kies adhaan-stijl met preview functie\n- **Test**: Test audio op Sonos of lokaal\n\n## 🎵 Audio Bestanden Toevoegen\n\n1. Plaats MP3 bestanden in `static/clips/`\n2. Bestanden worden automatisch beschikbaar in instellingen\n3. Aanbevolen: max 5MB per bestand\n\n## 🔧 API Endpoints\n\n- `GET /api/weather` - Actuele weerdata\n- `GET /api/hadith` - Willekeurige hadith\n- `POST /api/mute` - Toggle mute status\n- `POST /api/test-audio` - Test audio bestand\n\n## 🌍 Functies\n\n- ✅ Automatische adhaan op gebedstijden\n- ✅ Sonos integratie met meerdere zones\n- ✅ Nederlandse hadiths die automatisch wisselen\n- ✅ Dark/light theme toggle\n- ✅ Mute functionaliteit\n- ✅ Responsive design\n- ✅ Weersinformatie met automatische updates\n- ✅ Meerdere adhaan-stijlen met preview\n- ✅ Moderne, islamitische UI\n\n## 🛠️ Technische Details\n\n- **Backend**: Flask (Python)\n- **Frontend**: HTML5, CSS3, JavaScript\n- **Audio**: MP3 ondersteuning\n- **API's**: VUMG (gebedstijden), OpenWeatherMap (weer), Sonos\n- **Deployment**: Docker + TrueNAS\n\n## 📝 Changelog\n\n### v2.0 - Nieuwe Functies\n- ➕ Weersinformatie toegevoegd\n- ➕ Meerdere adhaan-stijlen\n- ➕ Audio preview functie\n- ➕ Verbeterde instellingen interface\n- ➕ API endpoints voor dynamische updates\n- ➕ Configuratie bestand voor eenvoudige setup\n\n### v1.0 - Basis Functionaliteit\n- ✅ Gebedstijden display\n- ✅ Automatische adhaan\n- ✅ Sonos integratie\n- ✅ Hadith rotatie\n- ✅ Dark/light theme "
|
||||
}
|
||||
]
|
||||
}
|
||||
42
.lh/adhan-webapp/adhan_cron.py.json
Normal file
42
.lh/adhan-webapp/adhan_cron.py.json
Normal file
File diff suppressed because one or more lines are too long
34
.lh/adhan-webapp/app.py.json
Normal file
34
.lh/adhan-webapp/app.py.json
Normal file
File diff suppressed because one or more lines are too long
22
.lh/adhan-webapp/config.py.json
Normal file
22
.lh/adhan-webapp/config.py.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"sourceFile": "adhan-webapp/config.py",
|
||||
"activeCommit": 0,
|
||||
"commits": [
|
||||
{
|
||||
"activePatchIndex": 1,
|
||||
"patches": [
|
||||
{
|
||||
"date": 1748203602606,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
||||
},
|
||||
{
|
||||
"date": 1748203978113,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,9 +1,9 @@\n # Configuratie bestand voor de Adhaan applicatie\n \n # OpenWeatherMap API configuratie\n # Verkrijg een gratis API key op: https://openweathermap.org/api\n-OPENWEATHER_API_KEY = 'YOUR_API_KEY_HERE'\n+OPENWEATHER_API_KEY = '99caf7628fd7197acd71df3fc2cafbdb'\n \n # Locatie voor weersinformatie\n WEATHER_LOCATION = 'Amsterdam,NL'\n \n"
|
||||
}
|
||||
],
|
||||
"date": 1748203602606,
|
||||
"name": "Commit-0",
|
||||
"content": "# Configuratie bestand voor de Adhaan applicatie\n\n# OpenWeatherMap API configuratie\n# Verkrijg een gratis API key op: https://openweathermap.org/api\nOPENWEATHER_API_KEY = 'YOUR_API_KEY_HERE'\n\n# Locatie voor weersinformatie\nWEATHER_LOCATION = 'Amsterdam,NL'\n\n# Sonos API configuratie\nSONOS_API_IP = '192.168.0.114'\n\n# VUMG API voor gebedstijden\nVUMG_API_URL = 'https://www.vumg.nl/?rest_route=/dpt/v1/prayertime&filter=today'\n\n# Bestandspaden\nimport os\nBASE_DIR = os.path.dirname(os.path.abspath(__file__))\nSETTINGS_PATH = os.path.join(BASE_DIR, 'settings.json')\nHADITHS_PATH = os.path.join(BASE_DIR, 'static', 'hadiths.json')\nCLIPS_PATH = os.path.join(BASE_DIR, 'static', 'clips') "
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.lh/adhan-webapp/requirements.txt.json
Normal file
18
.lh/adhan-webapp/requirements.txt.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"sourceFile": "adhan-webapp/requirements.txt",
|
||||
"activeCommit": 0,
|
||||
"commits": [
|
||||
{
|
||||
"activePatchIndex": 0,
|
||||
"patches": [
|
||||
{
|
||||
"date": 1748204335831,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
||||
}
|
||||
],
|
||||
"date": 1748204335831,
|
||||
"name": "Commit-0",
|
||||
"content": "Flask==3.0.0\nrequests==2.31.0\nhijridate==2.5.0 "
|
||||
}
|
||||
]
|
||||
}
|
||||
26
.lh/adhan-webapp/settings.json.json
Normal file
26
.lh/adhan-webapp/settings.json.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"sourceFile": "adhan-webapp/settings.json",
|
||||
"activeCommit": 0,
|
||||
"commits": [
|
||||
{
|
||||
"activePatchIndex": 2,
|
||||
"patches": [
|
||||
{
|
||||
"date": 1748201503324,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
||||
},
|
||||
{
|
||||
"date": 1748275757237,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,8 +1,9 @@\n {\n- \"volume\": 20,\n+ \"volume\": 15,\n \"zones\": [\n- \"Keuken\"\n+ \"Keuken\",\n+ \"Slaapkamer\"\n ],\n \"audio_clip\": \"adhan1.mp3\",\n \"mute\": false\n }\n\\ No newline at end of file\n"
|
||||
},
|
||||
{
|
||||
"date": 1748276087388,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,1 +1,9 @@\n-\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\n\\ No newline at end of file\n+{\n+ \"volume\": 15,\n+ \"zones\": [\n+ \"Keuken\",\n+ \"Slaapkamer\"\n+ ],\n+ \"audio_clip\": \"adhan1.mp3\",\n+ \"mute\": false\n+}\n\\ No newline at end of file\n"
|
||||
}
|
||||
],
|
||||
"date": 1748201503324,
|
||||
"name": "Commit-0",
|
||||
"content": "{\n \"volume\": 20,\n \"zones\": [\n \"Keuken\"\n ],\n \"audio_clip\": \"adhan1.mp3\",\n \"mute\": false\n}"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.lh/adhan-webapp/start_all.py.json
Normal file
18
.lh/adhan-webapp/start_all.py.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"sourceFile": "adhan-webapp/start_all.py",
|
||||
"activeCommit": 0,
|
||||
"commits": [
|
||||
{
|
||||
"activePatchIndex": 0,
|
||||
"patches": [
|
||||
{
|
||||
"date": 1748199344007,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
||||
}
|
||||
],
|
||||
"date": 1748199344007,
|
||||
"name": "Commit-0",
|
||||
"content": "import subprocess\nimport sys\nimport time\n\n# Start Flask-app\nflask_proc = subprocess.Popen([sys.executable, 'app.py'])\n\n# Start adhan_cron.py elke minuut in een loop\ntry:\n while True:\n cron_proc = subprocess.Popen([sys.executable, 'adhan_cron.py'])\n cron_proc.wait()\n time.sleep(60)\nexcept KeyboardInterrupt:\n flask_proc.terminate()\n sys.exit(0) "
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.lh/adhan-webapp/static/clips/README.md.json
Normal file
18
.lh/adhan-webapp/static/clips/README.md.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"sourceFile": "adhan-webapp/static/clips/README.md",
|
||||
"activeCommit": 0,
|
||||
"commits": [
|
||||
{
|
||||
"activePatchIndex": 0,
|
||||
"patches": [
|
||||
{
|
||||
"date": 1748203518364,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
||||
}
|
||||
],
|
||||
"date": 1748203518364,
|
||||
"name": "Commit-0",
|
||||
"content": "# Adhaan Audio Bestanden\n\nDeze directory bevat verschillende adhaan audio bestanden voor de gebedstijden applicatie.\n\n## Beschikbare Adhaan Stijlen:\n\n1. **adhan1.mp3** - Klassieke adhaan (origineel)\n2. **adhan_mekka.mp3** - Mekka stijl adhaan\n3. **adhan_medina.mp3** - Medina stijl adhaan\n4. **adhan_kort.mp3** - Korte versie adhaan\n5. **adhan_traditioneel.mp3** - Traditionele Nederlandse moskee stijl\n\n## Gebruik:\n\nDe gebruiker kan in de instellingen kiezen welke adhaan-stijl gebruikt wordt.\nAlle bestanden moeten in MP3 formaat zijn voor compatibiliteit met de browser en Sonos systemen.\n\n## Toevoegen van nieuwe bestanden:\n\n1. Plaats het MP3 bestand in deze directory\n2. Het bestand wordt automatisch beschikbaar in de instellingen\n3. Zorg ervoor dat het bestand niet te groot is (max 5MB aanbevolen) "
|
||||
}
|
||||
]
|
||||
}
|
||||
26
.lh/adhan-webapp/static/countdown.js.json
Normal file
26
.lh/adhan-webapp/static/countdown.js.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"sourceFile": "adhan-webapp/static/countdown.js",
|
||||
"activeCommit": 0,
|
||||
"commits": [
|
||||
{
|
||||
"activePatchIndex": 2,
|
||||
"patches": [
|
||||
{
|
||||
"date": 1748182675995,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
||||
},
|
||||
{
|
||||
"date": 1748202535810,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -3,20 +3,57 @@\n const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n document.getElementById(\"current-time\").textContent = timeStr;\n }\n \n-function startCountdown(targetTimeStr) {\n+// Gebedstijden ophalen uit een globale variabele (wordt in de template gezet)\n+let prayerTimes = window.prayerTimes || [];\n+let prayerNames = window.prayerNames || [];\n+let currentPrayerIndex = 0;\n+\n+function startCountdowns(times, names) {\n+ prayerTimes = times;\n+ prayerNames = names;\n+ currentPrayerIndex = getNextPrayerIndex();\n+ startCountdown(prayerTimes[currentPrayerIndex], prayerNames[currentPrayerIndex]);\n+}\n+\n+function getNextPrayerIndex() {\n+ const now = new Date();\n+ for (let i = 0; i < prayerTimes.length; i++) {\n+ const [h, m] = prayerTimes[i].split(\":\");\n+ const t = new Date();\n+ t.setHours(parseInt(h));\n+ t.setMinutes(parseInt(m));\n+ t.setSeconds(0);\n+ if (t > now) return i;\n+ }\n+ return 0; // fallback: eerste gebed\n+}\n+\n+function startCountdown(targetTimeStr, prayerName) {\n const parts = targetTimeStr.split(\":\");\n- const target = new Date();\n+ let target = new Date();\n target.setHours(parseInt(parts[0]));\n target.setMinutes(parseInt(parts[1]));\n- target.setSeconds(parseInt(parts[2]) || 0);\n+ target.setSeconds(0);\n \n+ document.querySelector('.huidig-gebed .naam').textContent = prayerName;\n+\n function update() {\n const now = new Date();\n let diff = Math.floor((target - now) / 1000);\n \n- if (diff < 0) diff = 0;\n+ if (diff <= 0) {\n+ // Speel adhaan af via browser\n+ const audio = document.getElementById('adhanAudio');\n+ if (audio) audio.play();\n+ // Ga naar volgende gebed\n+ currentPrayerIndex = (currentPrayerIndex + 1) % prayerTimes.length;\n+ const nextTime = prayerTimes[currentPrayerIndex];\n+ const nextName = prayerNames[currentPrayerIndex];\n+ startCountdown(nextTime, nextName);\n+ return;\n+ }\n \n const h = String(Math.floor(diff / 3600)).padStart(2, '0');\n const m = String(Math.floor((diff % 3600) / 60)).padStart(2, '0');\n const s = String(diff % 60).padStart(2, '0');\n@@ -24,9 +61,10 @@\n document.getElementById(\"countdown\").textContent = `${h}:${m}:${s}`;\n }\n \n update();\n- setInterval(update, 1000);\n+ window._countdownInterval && clearInterval(window._countdownInterval);\n+ window._countdownInterval = setInterval(update, 1000);\n }\n \n function setupThemeToggle() {\n const toggleBtn = document.getElementById(\"themeToggle\");\n"
|
||||
},
|
||||
{
|
||||
"date": 1748204399019,
|
||||
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,10 +1,30 @@\n function updateCurrentTime() {\n const now = new Date();\n const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n document.getElementById(\"current-time\").textContent = timeStr;\n+ \n+ // Update datum informatie elke minuut\n+ updateDateInfo();\n }\n \n+function updateDateInfo() {\n+ fetch('/api/date-info')\n+ .then(response => response.json())\n+ .then(data => {\n+ const gregorianElement = document.querySelector('.gregorian-datum');\n+ const hijriArabicElement = document.querySelector('.hijri-datum-arabic');\n+ const hijriDutchElement = document.querySelector('.hijri-datum-dutch');\n+ \n+ if (gregorianElement) gregorianElement.textContent = data.gregorian_full;\n+ if (hijriArabicElement) hijriArabicElement.textContent = data.hijri_arabic;\n+ if (hijriDutchElement) hijriDutchElement.textContent = data.hijri_dutch;\n+ })\n+ .catch(error => {\n+ console.log('Fout bij bijwerken datum:', error);\n+ });\n+}\n+\n // Gebedstijden ophalen uit een globale variabele (wordt in de template gezet)\n let prayerTimes = window.prayerTimes || [];\n let prayerNames = window.prayerNames || [];\n let currentPrayerIndex = 0;\n"
|
||||
}
|
||||
],
|
||||
"date": 1748182675995,
|
||||
"name": "Commit-0",
|
||||
"content": "function updateCurrentTime() {\n const now = new Date();\n const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n document.getElementById(\"current-time\").textContent = timeStr;\n}\n\nfunction startCountdown(targetTimeStr) {\n const parts = targetTimeStr.split(\":\");\n const target = new Date();\n target.setHours(parseInt(parts[0]));\n target.setMinutes(parseInt(parts[1]));\n target.setSeconds(parseInt(parts[2]) || 0);\n\n function update() {\n const now = new Date();\n let diff = Math.floor((target - now) / 1000);\n\n if (diff < 0) diff = 0;\n\n const h = String(Math.floor(diff / 3600)).padStart(2, '0');\n const m = String(Math.floor((diff % 3600) / 60)).padStart(2, '0');\n const s = String(diff % 60).padStart(2, '0');\n\n document.getElementById(\"countdown\").textContent = `${h}:${m}:${s}`;\n }\n\n update();\n setInterval(update, 1000);\n}\n\nfunction setupThemeToggle() {\n const toggleBtn = document.getElementById(\"themeToggle\");\n const html = document.documentElement;\n\n function applyTheme(theme) {\n html.className = theme;\n localStorage.setItem('theme', theme);\n }\n\n const saved = localStorage.getItem('theme') || 'light';\n applyTheme(saved);\n\n toggleBtn.addEventListener(\"click\", () => {\n const current = html.className === 'light' ? 'dark' : 'light';\n applyTheme(current);\n });\n}\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
34
.lh/adhan-webapp/static/hadiths.json.json
Normal file
34
.lh/adhan-webapp/static/hadiths.json.json
Normal file
File diff suppressed because one or more lines are too long
50
.lh/adhan-webapp/static/quran.css.json
Normal file
50
.lh/adhan-webapp/static/quran.css.json
Normal file
File diff suppressed because one or more lines are too long
26
.lh/adhan-webapp/static/quran.js.json
Normal file
26
.lh/adhan-webapp/static/quran.js.json
Normal file
File diff suppressed because one or more lines are too long
26
.lh/adhan-webapp/static/style.css.json
Normal file
26
.lh/adhan-webapp/static/style.css.json
Normal file
File diff suppressed because one or more lines are too long
26
.lh/adhan-webapp/templates/debug.html.json
Normal file
26
.lh/adhan-webapp/templates/debug.html.json
Normal file
File diff suppressed because one or more lines are too long
26
.lh/adhan-webapp/templates/index.html.json
Normal file
26
.lh/adhan-webapp/templates/index.html.json
Normal file
File diff suppressed because one or more lines are too long
58
.lh/adhan-webapp/templates/quran.html.json
Normal file
58
.lh/adhan-webapp/templates/quran.html.json
Normal file
File diff suppressed because one or more lines are too long
38
.lh/adhan-webapp/templates/settings.html.json
Normal file
38
.lh/adhan-webapp/templates/settings.html.json
Normal file
File diff suppressed because one or more lines are too long
26
.lh/docker-compose.yml.json
Normal file
26
.lh/docker-compose.yml.json
Normal file
File diff suppressed because one or more lines are too long
BIN
adhan-webapp/.DS_Store
vendored
Normal file
BIN
adhan-webapp/.DS_Store
vendored
Normal file
Binary file not shown.
23
adhan-webapp/Dockerfile
Normal file
23
adhan-webapp/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Kopieer requirements eerst voor betere caching
|
||||
COPY requirements.txt .
|
||||
|
||||
# Installeer dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Kopieer de applicatie bestanden
|
||||
COPY . .
|
||||
|
||||
# Maak de benodigde directories aan
|
||||
RUN mkdir -p /app/static/clips
|
||||
|
||||
# Stel de juiste permissies in
|
||||
RUN chmod -R 755 /app
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
# Start de applicatie
|
||||
CMD ["python", "start_all.py"]
|
||||
108
adhan-webapp/README.md
Normal file
108
adhan-webapp/README.md
Normal file
@ -0,0 +1,108 @@
|
||||
# Adhaan Gebedstijden Display
|
||||
|
||||
Een moderne, islamitische gebedstijden display applicatie met automatische adhaan, weersinformatie en Sonos integratie.
|
||||
|
||||
## ✨ Nieuwe Functies
|
||||
|
||||
### 🌤️ Weersinformatie
|
||||
- Actuele temperatuur en weersomstandigheden
|
||||
- Automatische updates elke 10 minuten
|
||||
- Nederlandse weerbeschrijvingen
|
||||
- Windsnelheid en luchtvochtigheid
|
||||
|
||||
### 🎵 Uitgebreide Audio Opties
|
||||
- Meerdere adhaan-stijlen beschikbaar
|
||||
- Audio preview functie in instellingen
|
||||
- Verbeterde instellingen interface
|
||||
- Test functionaliteit voor Sonos
|
||||
|
||||
## 🚀 Installatie
|
||||
|
||||
### 1. OpenWeatherMap API Key verkrijgen
|
||||
|
||||
1. Ga naar [OpenWeatherMap](https://openweathermap.org/api)
|
||||
2. Maak een gratis account aan
|
||||
3. Verkrijg je API key (1000 gratis calls per dag)
|
||||
|
||||
### 2. Configuratie
|
||||
|
||||
Bewerk `config.py` en vervang `YOUR_API_KEY_HERE` met je OpenWeatherMap API key:
|
||||
|
||||
```python
|
||||
OPENWEATHER_API_KEY = 'jouw_api_key_hier'
|
||||
```
|
||||
|
||||
Optioneel kun je ook de locatie aanpassen:
|
||||
|
||||
```python
|
||||
WEATHER_LOCATION = 'Amsterdam,NL' # Vervang met jouw stad
|
||||
```
|
||||
|
||||
### 3. Docker opstarten
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 📱 Gebruik
|
||||
|
||||
### Hoofdscherm
|
||||
- **Links**: Huidige tijd, datum en countdown naar volgende gebed
|
||||
- **Rechts**: Hadith, gebedstijden en weersinformatie
|
||||
- **Knoppen**: Dark/light mode, instellingen, mute
|
||||
|
||||
### Instellingen
|
||||
- **Volume**: Sonos volume instelling (0-100)
|
||||
- **Zones**: Selecteer welke Sonos speakers gebruikt worden
|
||||
- **Audio**: Kies adhaan-stijl met preview functie
|
||||
- **Test**: Test audio op Sonos of lokaal
|
||||
|
||||
## 🎵 Audio Bestanden Toevoegen
|
||||
|
||||
1. Plaats MP3 bestanden in `static/clips/`
|
||||
2. Bestanden worden automatisch beschikbaar in instellingen
|
||||
3. Aanbevolen: max 5MB per bestand
|
||||
|
||||
## 🔧 API Endpoints
|
||||
|
||||
- `GET /api/weather` - Actuele weerdata
|
||||
- `GET /api/hadith` - Willekeurige hadith
|
||||
- `POST /api/mute` - Toggle mute status
|
||||
- `POST /api/test-audio` - Test audio bestand
|
||||
|
||||
## 🌍 Functies
|
||||
|
||||
- ✅ Automatische adhaan op gebedstijden
|
||||
- ✅ Sonos integratie met meerdere zones
|
||||
- ✅ Nederlandse hadiths die automatisch wisselen
|
||||
- ✅ Dark/light theme toggle
|
||||
- ✅ Mute functionaliteit
|
||||
- ✅ Responsive design
|
||||
- ✅ Weersinformatie met automatische updates
|
||||
- ✅ Meerdere adhaan-stijlen met preview
|
||||
- ✅ Moderne, islamitische UI
|
||||
|
||||
## 🛠️ Technische Details
|
||||
|
||||
- **Backend**: Flask (Python)
|
||||
- **Frontend**: HTML5, CSS3, JavaScript
|
||||
- **Audio**: MP3 ondersteuning
|
||||
- **API's**: VUMG (gebedstijden), OpenWeatherMap (weer), Sonos
|
||||
- **Deployment**: Docker + TrueNAS
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
### v2.0 - Nieuwe Functies
|
||||
- ➕ Weersinformatie toegevoegd
|
||||
- ➕ Meerdere adhaan-stijlen
|
||||
- ➕ Audio preview functie
|
||||
- ➕ Verbeterde instellingen interface
|
||||
- ➕ API endpoints voor dynamische updates
|
||||
- ➕ Configuratie bestand voor eenvoudige setup
|
||||
|
||||
### v1.0 - Basis Functionaliteit
|
||||
- ✅ Gebedstijden display
|
||||
- ✅ Automatische adhaan
|
||||
- ✅ Sonos integratie
|
||||
- ✅ Hadith rotatie
|
||||
- ✅ Dark/light theme
|
||||
BIN
adhan-webapp/__pycache__/config.cpython-311.pyc
Normal file
BIN
adhan-webapp/__pycache__/config.cpython-311.pyc
Normal file
Binary file not shown.
172
adhan-webapp/adhan_cron.py
Normal file
172
adhan-webapp/adhan_cron.py
Normal file
@ -0,0 +1,172 @@
|
||||
import requests, json, os
|
||||
from datetime import datetime, date
|
||||
import time
|
||||
|
||||
SETTINGS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings.json')
|
||||
VUMG_API = 'https://www.vumg.nl/?rest_route=/dpt/v1/prayertime&filter=today'
|
||||
SONOS_API_IP = '192.168.0.114' # Pas aan indien nodig
|
||||
LOG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'adhan_cron.log')
|
||||
LAST_PLAYED_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'last_played.json')
|
||||
|
||||
def logregel(message):
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
log_message = f"[{timestamp}] {message}"
|
||||
print(log_message) # Voor Docker logs
|
||||
with open(LOG_PATH, 'a') as log:
|
||||
log.write(log_message + '\n')
|
||||
|
||||
def get_current_volume(settings):
|
||||
"""Bepaal het juiste volume op basis van de huidige tijd"""
|
||||
# Fallback naar oude volume als nieuwe instellingen niet bestaan
|
||||
if 'volume_day' not in settings or 'volume_night' not in settings:
|
||||
return settings.get('volume', 30)
|
||||
|
||||
current_time = datetime.now().strftime('%H:%M')
|
||||
night_start = settings.get('night_start', '20:00')
|
||||
day_start = settings.get('day_start', '07:00')
|
||||
|
||||
# Converteer tijden naar vergelijkbare format
|
||||
current_minutes = int(current_time[:2]) * 60 + int(current_time[3:5])
|
||||
night_minutes = int(night_start[:2]) * 60 + int(night_start[3:5])
|
||||
day_minutes = int(day_start[:2]) * 60 + int(day_start[3:5])
|
||||
|
||||
# Bepaal of het avond is
|
||||
if night_minutes > day_minutes: # Normale dag/nacht cyclus (bijv. dag 07:00-20:00)
|
||||
is_night = current_minutes >= night_minutes or current_minutes < day_minutes
|
||||
else: # Avond gaat over middernacht (bijv. avond 20:00-07:00)
|
||||
is_night = current_minutes >= night_minutes and current_minutes < day_minutes
|
||||
|
||||
if is_night:
|
||||
volume = settings.get('volume_night', settings.get('volume', 30) // 2)
|
||||
logregel(f"🌙 Avond volume gebruikt: {volume}")
|
||||
return volume
|
||||
else:
|
||||
volume = settings.get('volume_day', settings.get('volume', 30))
|
||||
logregel(f"☀️ Dag volume gebruikt: {volume}")
|
||||
return volume
|
||||
|
||||
def check_recent_play(prayer_name):
|
||||
"""Controleer of deze adhaan recent is afgespeeld (binnen 2 minuten)"""
|
||||
try:
|
||||
if os.path.exists(LAST_PLAYED_PATH):
|
||||
with open(LAST_PLAYED_PATH, 'r') as f:
|
||||
last_played = json.load(f)
|
||||
|
||||
last_time = last_played.get(prayer_name)
|
||||
if last_time:
|
||||
last_datetime = datetime.fromisoformat(last_time)
|
||||
time_diff = (datetime.now() - last_datetime).total_seconds()
|
||||
|
||||
if time_diff < 120: # 2 minuten
|
||||
logregel(f"⏭️ {prayer_name} recent afgespeeld ({int(time_diff)}s geleden), overslaan")
|
||||
return True
|
||||
except Exception as e:
|
||||
logregel(f"Fout bij controleren recent play: {e}")
|
||||
|
||||
return False
|
||||
|
||||
def mark_as_played(prayer_name):
|
||||
"""Markeer deze adhaan als afgespeeld"""
|
||||
try:
|
||||
last_played = {}
|
||||
if os.path.exists(LAST_PLAYED_PATH):
|
||||
with open(LAST_PLAYED_PATH, 'r') as f:
|
||||
last_played = json.load(f)
|
||||
|
||||
last_played[prayer_name] = datetime.now().isoformat()
|
||||
|
||||
with open(LAST_PLAYED_PATH, 'w') as f:
|
||||
json.dump(last_played, f)
|
||||
|
||||
except Exception as e:
|
||||
logregel(f"Fout bij markeren als afgespeeld: {e}")
|
||||
|
||||
def play_sonos_clip(zone, audio_clip, volume, prayer_name):
|
||||
"""Speel een clip af op een specifieke Sonos zone met error handling"""
|
||||
try:
|
||||
# Stop eerst alle audio op deze zone
|
||||
stop_url = f"http://{SONOS_API_IP}:5005/{zone}/pause"
|
||||
requests.get(stop_url, timeout=2)
|
||||
time.sleep(0.5) # Korte pauze
|
||||
|
||||
# Speel de clip af
|
||||
clip_url = f"http://{SONOS_API_IP}:5005/{zone}/clip/{audio_clip}/{volume}"
|
||||
response = requests.get(clip_url, timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
logregel(f"✅ AFGESPEELD {prayer_name} op {zone} ({audio_clip}, volume {volume})")
|
||||
return True
|
||||
else:
|
||||
logregel(f"❌ HTTP {response.status_code} voor {zone}: {response.text[:100]}")
|
||||
return False
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
logregel(f"⏱️ TIMEOUT bij {zone} voor {prayer_name}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logregel(f"❌ FOUT Sonos {zone} {prayer_name}: {e}")
|
||||
return False
|
||||
|
||||
now = datetime.now().strftime('%H:%M')
|
||||
logregel(f"CRON gestart, huidige tijd: {now}")
|
||||
|
||||
# Laad settings
|
||||
try:
|
||||
with open(SETTINGS_PATH) as f:
|
||||
settings = json.load(f)
|
||||
except Exception as e:
|
||||
logregel(f"Fout bij laden settings: {e}")
|
||||
exit(1)
|
||||
|
||||
# Controleer mute-status
|
||||
if settings.get('mute', False):
|
||||
logregel("Adhaan niet afgespeeld: mute staat aan.")
|
||||
exit(0)
|
||||
|
||||
# Haal gebedstijden op
|
||||
try:
|
||||
res = requests.get(VUMG_API, timeout=10)
|
||||
res.raise_for_status()
|
||||
data = res.json()[0]
|
||||
gebedstijden = {
|
||||
"Fajr": data.get("fajr_jamah", "00:00"),
|
||||
"Zuhr": data.get("zuhr_jamah", "00:00"),
|
||||
"Asr": data.get("asr_jamah", "00:00"),
|
||||
"Maghrib": data.get("maghrib_jamah", "00:00"),
|
||||
"Isha": data.get("isha_jamah", "00:00")
|
||||
}
|
||||
except Exception as e:
|
||||
logregel(f"Fout bij ophalen gebedstijden: {e}")
|
||||
exit(1)
|
||||
|
||||
# Check voor gebedstijd
|
||||
for naam, tijd in gebedstijden.items():
|
||||
if tijd[:5] == now: # Alleen uren:minuten vergelijken
|
||||
# Controleer of recent afgespeeld
|
||||
if check_recent_play(naam):
|
||||
continue
|
||||
|
||||
logregel(f"🕌 GEBEDSTIJD GEDETECTEERD: {naam} om {tijd}")
|
||||
|
||||
# Markeer als afgespeeld VOOR het afspelen
|
||||
mark_as_played(naam)
|
||||
|
||||
# Speel af op alle zones
|
||||
success_count = 0
|
||||
zones = settings.get('zones', ['Woonkamer'])
|
||||
audio_clip = settings.get('audio_clip', 'adhan1.mp3')
|
||||
volume = get_current_volume(settings)
|
||||
|
||||
for zone in zones:
|
||||
if play_sonos_clip(zone, audio_clip, volume, naam):
|
||||
success_count += 1
|
||||
time.sleep(1) # Pauze tussen zones
|
||||
|
||||
if success_count > 0:
|
||||
logregel(f"🎵 Adhaan succesvol afgespeeld op {success_count}/{len(zones)} zones")
|
||||
else:
|
||||
logregel(f"❌ Adhaan kon niet worden afgespeeld op geen enkele zone")
|
||||
|
||||
break # Stop na eerste match
|
||||
|
||||
logregel(f"CRON voltooid om {datetime.now().strftime('%H:%M:%S')}")
|
||||
508
adhan-webapp/app.py
Normal file
508
adhan-webapp/app.py
Normal file
@ -0,0 +1,508 @@
|
||||
from flask import Flask, render_template, request, redirect, send_from_directory, jsonify, request as flask_request
|
||||
import requests, json, os, random
|
||||
from datetime import datetime
|
||||
from config import *
|
||||
from hijridate import Gregorian
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Voeg cache-control headers toe voor statische bestanden
|
||||
@app.after_request
|
||||
def add_header(response):
|
||||
if 'Cache-Control' not in response.headers:
|
||||
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
||||
return response
|
||||
|
||||
# Specifieke route voor statische bestanden met cache-control
|
||||
@app.route('/static/<path:filename>')
|
||||
def serve_static(filename):
|
||||
return send_from_directory('static', filename, cache_timeout=0)
|
||||
|
||||
SETTINGS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings.json')
|
||||
HADITHS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'hadiths.json')
|
||||
VUMG_API = 'https://www.vumg.nl/?rest_route=/dpt/v1/prayertime&filter=today'
|
||||
CLIPS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'clips')
|
||||
SONOS_API_IP = '192.168.0.114' # IP van host waar Sonos API draait
|
||||
|
||||
def fetch_weather_data():
|
||||
"""Haalt weersinformatie op voor de geconfigureerde locatie"""
|
||||
try:
|
||||
params = {
|
||||
'q': WEATHER_LOCATION,
|
||||
'appid': OPENWEATHER_API_KEY,
|
||||
'units': 'metric',
|
||||
'lang': 'nl'
|
||||
}
|
||||
response = requests.get('https://api.openweathermap.org/data/2.5/weather', params=params, timeout=5)
|
||||
response.raise_for_status()
|
||||
data = 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), # m/s naar km/h
|
||||
'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'
|
||||
}
|
||||
|
||||
def load_hadith():
|
||||
try:
|
||||
with open(HADITHS_PATH) as f:
|
||||
hadiths = json.load(f)['hadiths']
|
||||
return random.choice(hadiths)
|
||||
except Exception as e:
|
||||
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
|
||||
|
||||
# Fallback naar oude volume als nieuwe instellingen niet bestaan
|
||||
if 'volume_day' not in settings or 'volume_night' not in settings:
|
||||
return settings.get('volume', 30)
|
||||
|
||||
current_time = datetime.now().strftime('%H:%M')
|
||||
night_start = settings.get('night_start', '20:00')
|
||||
day_start = settings.get('day_start', '07:00')
|
||||
|
||||
# Converteer tijden naar vergelijkbare format
|
||||
current_minutes = int(current_time[:2]) * 60 + int(current_time[3:5])
|
||||
night_minutes = int(night_start[:2]) * 60 + int(night_start[3:5])
|
||||
day_minutes = int(day_start[:2]) * 60 + int(day_start[3:5])
|
||||
|
||||
# Bepaal of het avond is
|
||||
if night_minutes > day_minutes: # Normale dag/nacht cyclus (bijv. dag 07:00-20:00)
|
||||
is_night = current_minutes >= night_minutes or current_minutes < day_minutes
|
||||
else: # Avond gaat over middernacht (bijv. avond 20:00-07:00)
|
||||
is_night = current_minutes >= night_minutes and current_minutes < day_minutes
|
||||
|
||||
if is_night:
|
||||
return settings.get('volume_night', settings.get('volume', 30) // 2)
|
||||
else:
|
||||
return settings.get('volume_day', settings.get('volume', 30))
|
||||
|
||||
def fetch_sonos_zones():
|
||||
try:
|
||||
res = requests.get(f'http://{SONOS_API_IP}:5005/zones', timeout=5)
|
||||
res.raise_for_status()
|
||||
data = res.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']
|
||||
|
||||
def get_date_info():
|
||||
"""Genereert zowel Gregoriaanse als Hijri datum informatie"""
|
||||
now = datetime.now()
|
||||
|
||||
# Gregoriaanse datum
|
||||
gregorian_date = now.strftime('%A %d %B %Y')
|
||||
gregorian_short = now.strftime('%d-%m-%Y')
|
||||
|
||||
# Nederlandse maandnamen
|
||||
dutch_months = {
|
||||
'January': 'januari', 'February': 'februari', 'March': 'maart',
|
||||
'April': 'april', 'May': 'mei', 'June': 'juni',
|
||||
'July': 'juli', 'August': 'augustus', 'September': 'september',
|
||||
'October': 'oktober', 'November': 'november', 'December': 'december'
|
||||
}
|
||||
|
||||
# Nederlandse dagnamen
|
||||
dutch_days = {
|
||||
'Monday': 'maandag', 'Tuesday': 'dinsdag', 'Wednesday': 'woensdag',
|
||||
'Thursday': 'donderdag', 'Friday': 'vrijdag', 'Saturday': 'zaterdag', 'Sunday': 'zondag'
|
||||
}
|
||||
|
||||
# Vervang Engelse namen door Nederlandse
|
||||
for eng, nl in dutch_months.items():
|
||||
gregorian_date = gregorian_date.replace(eng, nl)
|
||||
for eng, nl in dutch_days.items():
|
||||
gregorian_date = gregorian_date.replace(eng, nl)
|
||||
|
||||
try:
|
||||
# Converteer naar Hijri datum
|
||||
hijri_date = Gregorian(now.year, now.month, now.day).to_hijri()
|
||||
|
||||
# Arabische maandnamen
|
||||
hijri_months = [
|
||||
'محرم', 'صفر', 'ربيع الأول', 'ربيع الثاني', 'جمادى الأولى', 'جمادى الثانية',
|
||||
'رجب', 'شعبان', 'رمضان', 'شوال', 'ذو القعدة', 'ذو الحجة'
|
||||
]
|
||||
|
||||
# Nederlandse maandnamen voor Hijri
|
||||
hijri_months_nl = [
|
||||
'Muharram', 'Safar', 'Rabi al-Awwal', 'Rabi al-Thani', 'Jumada al-Awwal', 'Jumada al-Thani',
|
||||
'Rajab', 'Sha\'ban', 'Ramadan', 'Shawwal', 'Dhu al-Qi\'dah', 'Dhu al-Hijjah'
|
||||
]
|
||||
|
||||
hijri_arabic = f"{hijri_date.day} {hijri_months[hijri_date.month - 1]} {hijri_date.year} هـ"
|
||||
hijri_dutch = f"{hijri_date.day} {hijri_months_nl[hijri_date.month - 1]} {hijri_date.year} AH"
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Fout bij Hijri conversie: {e}")
|
||||
hijri_arabic = "التاريخ الهجري غير متاح"
|
||||
hijri_dutch = "Hijri datum niet beschikbaar"
|
||||
|
||||
return {
|
||||
'gregorian_full': gregorian_date,
|
||||
'gregorian_short': gregorian_short,
|
||||
'hijri_arabic': hijri_arabic,
|
||||
'hijri_dutch': hijri_dutch,
|
||||
'current_time': now.strftime('%H:%M')
|
||||
}
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
settings = load_settings()
|
||||
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"),
|
||||
"Zuhr": data.get("zuhr_jamah", "00:00"),
|
||||
"Asr": data.get("asr_jamah", "00:00"),
|
||||
"Maghrib": data.get("maghrib_jamah", "00:00"),
|
||||
"Isha": data.get("isha_jamah", "00:00")
|
||||
}
|
||||
|
||||
now = datetime.now().strftime('%H:%M')
|
||||
for naam, tijd in gebedstijden.items():
|
||||
if tijd > now:
|
||||
next_time = tijd
|
||||
next_name = naam
|
||||
break
|
||||
else:
|
||||
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,
|
||||
gebedstijden=gebedstijden,
|
||||
debug=debug_data,
|
||||
settings=settings,
|
||||
weather=weather,
|
||||
date_info=date_info)
|
||||
|
||||
@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':
|
||||
# Nieuwe volume instellingen
|
||||
if 'volume_day' in request.form and 'volume_night' in request.form:
|
||||
settings['volume_day'] = int(request.form['volume_day'])
|
||||
settings['volume_night'] = int(request.form['volume_night'])
|
||||
settings['night_start'] = request.form['night_start']
|
||||
settings['day_start'] = request.form['day_start']
|
||||
# Update oude volume voor backward compatibility
|
||||
settings['volume'] = settings['volume_day']
|
||||
else:
|
||||
# Fallback: gebruik default waarden als velden ontbreken
|
||||
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')
|
||||
settings['day_start'] = settings.get('day_start', '07:00')
|
||||
settings['volume'] = settings['volume_day']
|
||||
|
||||
settings['zones'] = request.form.getlist('zones')
|
||||
settings['audio_clip'] = request.form['audio_clip']
|
||||
|
||||
with open(SETTINGS_PATH, 'w') as f:
|
||||
json.dump(settings, f, indent=2)
|
||||
|
||||
if 'test_clip' in request.form:
|
||||
# Gebruik het juiste volume voor de test
|
||||
test_volume = get_current_volume(settings)
|
||||
for zone in settings['zones']:
|
||||
url = f"http://{SONOS_API_IP}:5005/{zone}/clip/{settings['audio_clip']}/{test_volume}"
|
||||
try:
|
||||
r = requests.get(url, timeout=3)
|
||||
print(f"🎵 Test clip verzonden naar {zone}, status: {r.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ Fout bij afspelen op zone {zone}: {e}")
|
||||
|
||||
return redirect('/instellingen')
|
||||
|
||||
return render_template('settings.html',
|
||||
settings=settings,
|
||||
alle_zones=alle_zones,
|
||||
clips=clips)
|
||||
|
||||
@app.route('/api/hadith')
|
||||
def api_hadith():
|
||||
hadith = load_hadith()
|
||||
return jsonify(hadith)
|
||||
|
||||
@app.route('/api/mute', methods=['POST'])
|
||||
def toggle_mute():
|
||||
try:
|
||||
data = flask_request.get_json()
|
||||
mute = data.get('mute', False)
|
||||
with open(SETTINGS_PATH, 'r+') as f:
|
||||
settings = json.load(f)
|
||||
settings['mute'] = mute
|
||||
f.seek(0)
|
||||
json.dump(settings, f, indent=2)
|
||||
f.truncate()
|
||||
return jsonify({'success': True, 'mute': mute})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/weather')
|
||||
def api_weather():
|
||||
"""API endpoint voor het ophalen van actuele weerdata"""
|
||||
weather = fetch_weather_data()
|
||||
return jsonify(weather)
|
||||
|
||||
@app.route('/api/date-info')
|
||||
def api_date_info():
|
||||
"""API endpoint voor het ophalen van actuele datum informatie"""
|
||||
date_info = get_date_info()
|
||||
return jsonify(date_info)
|
||||
|
||||
@app.route('/api/test-audio', methods=['POST'])
|
||||
def test_audio():
|
||||
"""Test een audio bestand lokaal in de browser"""
|
||||
try:
|
||||
data = flask_request.get_json()
|
||||
audio_file = data.get('audio_file', 'adhan1.mp3')
|
||||
|
||||
# Controleer of het bestand bestaat
|
||||
audio_path = os.path.join(CLIPS_PATH, audio_file)
|
||||
if not os.path.exists(audio_path):
|
||||
return jsonify({'success': False, 'error': 'Audio bestand niet gevonden'}), 404
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Audio bestand {audio_file} is beschikbaar',
|
||||
'url': f'/static/clips/{audio_file}'
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@app.route('/debug')
|
||||
def debug():
|
||||
"""Debug pagina voor het testen van adhaan functionaliteit"""
|
||||
settings = load_settings()
|
||||
gebedstijden = {}
|
||||
|
||||
try:
|
||||
res = requests.get(VUMG_API)
|
||||
res.raise_for_status()
|
||||
data = res.json()[0]
|
||||
|
||||
gebedstijden = {
|
||||
"Fajr": data.get("fajr_jamah", "00:00"),
|
||||
"Zuhr": data.get("zuhr_jamah", "00:00"),
|
||||
"Asr": data.get("asr_jamah", "00:00"),
|
||||
"Maghrib": data.get("maghrib_jamah", "00:00"),
|
||||
"Isha": data.get("isha_jamah", "00:00")
|
||||
}
|
||||
except Exception as e:
|
||||
print("❌ Fout bij ophalen gebedstijden voor debug:", e)
|
||||
# Fallback tijden voor testing
|
||||
gebedstijden = {
|
||||
"Fajr": "06:00",
|
||||
"Zuhr": "12:30",
|
||||
"Asr": "15:00",
|
||||
"Maghrib": "17:30",
|
||||
"Isha": "19:00"
|
||||
}
|
||||
|
||||
return render_template('debug.html',
|
||||
gebedstijden=gebedstijden,
|
||||
settings=settings)
|
||||
|
||||
@app.route('/api/debug-adhaan', methods=['POST'])
|
||||
def debug_adhaan():
|
||||
"""API endpoint voor debug adhaan testing"""
|
||||
import time
|
||||
|
||||
try:
|
||||
data = flask_request.get_json()
|
||||
prayer_name = data.get('prayer', 'Test')
|
||||
test_time = data.get('time', 'Onbekend')
|
||||
|
||||
settings = load_settings()
|
||||
|
||||
# Check mute status
|
||||
if settings.get('mute', False):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Adhaan niet afgespeeld: mute staat aan',
|
||||
'muted': True
|
||||
})
|
||||
|
||||
# Verbeterde Sonos afspeling
|
||||
success_count = 0
|
||||
error_messages = []
|
||||
zones = settings.get('zones', ['Woonkamer'])
|
||||
audio_clip = settings.get('audio_clip', 'adhan1.mp3')
|
||||
volume = get_current_volume(settings)
|
||||
|
||||
for zone in zones:
|
||||
try:
|
||||
# Stop eerst alle audio op deze zone
|
||||
stop_url = f"http://{SONOS_API_IP}:5005/{zone}/pause"
|
||||
requests.get(stop_url, timeout=2)
|
||||
time.sleep(0.5) # Korte pauze
|
||||
|
||||
# Speel de clip af
|
||||
clip_url = f"http://{SONOS_API_IP}:5005/{zone}/clip/{audio_clip}/{volume}"
|
||||
response = requests.get(clip_url, timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
success_count += 1
|
||||
print(f"🔧 DEBUG: Adhaan afgespeeld op {zone} voor {prayer_name} om {test_time}")
|
||||
else:
|
||||
error_messages.append(f"{zone}: HTTP {response.status_code}")
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
error_messages.append(f"{zone}: Timeout")
|
||||
except Exception as e:
|
||||
error_messages.append(f"{zone}: {str(e)}")
|
||||
|
||||
# Pauze tussen zones
|
||||
if len(zones) > 1:
|
||||
time.sleep(1)
|
||||
|
||||
if success_count > 0:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Adhaan afgespeeld op {success_count}/{len(zones)} zone(s)',
|
||||
'errors': error_messages if error_messages else None
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Geen zones bereikbaar: {", ".join(error_messages)}'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/sonos-status')
|
||||
def sonos_status():
|
||||
"""API endpoint voor Sonos status controle"""
|
||||
try:
|
||||
settings = load_settings()
|
||||
zones = settings.get('zones', ['Woonkamer'])
|
||||
status_info = {}
|
||||
|
||||
for zone in zones:
|
||||
try:
|
||||
# Haal zone status op
|
||||
status_url = f"http://{SONOS_API_IP}:5005/{zone}/state"
|
||||
response = requests.get(status_url, timeout=3)
|
||||
|
||||
if response.status_code == 200:
|
||||
zone_data = response.json()
|
||||
status_info[zone] = {
|
||||
'status': 'online',
|
||||
'playbackState': zone_data.get('playbackState', 'unknown'),
|
||||
'currentTrack': zone_data.get('currentTrack', {}).get('title', 'Geen track'),
|
||||
'volume': zone_data.get('volume', 0)
|
||||
}
|
||||
else:
|
||||
status_info[zone] = {'status': 'error', 'error': f'HTTP {response.status_code}'}
|
||||
|
||||
except Exception as e:
|
||||
status_info[zone] = {'status': 'offline', 'error': str(e)}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'zones': status_info,
|
||||
'api_ip': SONOS_API_IP
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/sonos-stop', methods=['POST'])
|
||||
def sonos_stop():
|
||||
"""API endpoint om alle Sonos zones te stoppen"""
|
||||
try:
|
||||
settings = load_settings()
|
||||
zones = settings.get('zones', ['Woonkamer'])
|
||||
stopped_zones = []
|
||||
|
||||
for zone in zones:
|
||||
try:
|
||||
stop_url = f"http://{SONOS_API_IP}:5005/{zone}/pause"
|
||||
response = requests.get(stop_url, timeout=3)
|
||||
|
||||
if response.status_code == 200:
|
||||
stopped_zones.append(zone)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fout bij stoppen {zone}: {e}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Audio gestopt op {len(stopped_zones)} zone(s)',
|
||||
'stopped_zones': stopped_zones
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@app.route('/quran')
|
||||
def quran():
|
||||
"""Quran speler pagina"""
|
||||
return render_template('quran.html')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=80)
|
||||
21
adhan-webapp/config.py
Normal file
21
adhan-webapp/config.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Configuratie bestand voor de Adhaan applicatie
|
||||
|
||||
# OpenWeatherMap API configuratie
|
||||
# Verkrijg een gratis API key op: https://openweathermap.org/api
|
||||
OPENWEATHER_API_KEY = '99caf7628fd7197acd71df3fc2cafbdb'
|
||||
|
||||
# Locatie voor weersinformatie
|
||||
WEATHER_LOCATION = 'Amsterdam,NL'
|
||||
|
||||
# Sonos API configuratie
|
||||
SONOS_API_IP = '192.168.0.114'
|
||||
|
||||
# VUMG API voor gebedstijden
|
||||
VUMG_API_URL = 'https://www.vumg.nl/?rest_route=/dpt/v1/prayertime&filter=today'
|
||||
|
||||
# Bestandspaden
|
||||
import os
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SETTINGS_PATH = os.path.join(BASE_DIR, 'settings.json')
|
||||
HADITHS_PATH = os.path.join(BASE_DIR, 'static', 'hadiths.json')
|
||||
CLIPS_PATH = os.path.join(BASE_DIR, 'static', 'clips')
|
||||
3
adhan-webapp/done
Normal file
3
adhan-webapp/done
Normal file
@ -0,0 +1,3 @@
|
||||
Mon May 26 16:08:33 CEST 2025: Hadith auto-update functionaliteit toegevoegd - JavaScript code toegevoegd om elke 30 seconden een nieuwe hadith op te halen via /api/hadith endpoint
|
||||
Mon May 26 16:19:32 CEST 2025: Instellingen pagina design aangepast - Hetzelfde design als rest van applicatie met theme ondersteuning, Material Icons en consistente styling
|
||||
Mon May 26 18:17:57 CEST 2025: Tijdzone probleem opgelost - Container gebruikt nu Nederlandse tijd (18:17), volgende test bij Maghrib om 21:45
|
||||
3
adhan-webapp/requirements.txt
Normal file
3
adhan-webapp/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
Flask==3.0.0
|
||||
requests==2.31.0
|
||||
hijridate==2.5.0
|
||||
13
adhan-webapp/settings.json
Normal file
13
adhan-webapp/settings.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"volume": 15,
|
||||
"volume_day": 15,
|
||||
"volume_night": 8,
|
||||
"night_start": "00:00",
|
||||
"day_start": "07:00",
|
||||
"zones": [
|
||||
"Keuken",
|
||||
"Slaapkamer"
|
||||
],
|
||||
"audio_clip": "adhan1.mp3",
|
||||
"mute": false
|
||||
}
|
||||
16
adhan-webapp/start_all.py
Normal file
16
adhan-webapp/start_all.py
Normal file
@ -0,0 +1,16 @@
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
# Start Flask-app
|
||||
flask_proc = subprocess.Popen([sys.executable, 'app.py'])
|
||||
|
||||
# Start adhan_cron.py elke minuut in een loop
|
||||
try:
|
||||
while True:
|
||||
cron_proc = subprocess.Popen([sys.executable, 'adhan_cron.py'])
|
||||
cron_proc.wait()
|
||||
time.sleep(60)
|
||||
except KeyboardInterrupt:
|
||||
flask_proc.terminate()
|
||||
sys.exit(0)
|
||||
BIN
adhan-webapp/static/.DS_Store
vendored
Normal file
BIN
adhan-webapp/static/.DS_Store
vendored
Normal file
Binary file not shown.
22
adhan-webapp/static/clips/README.md
Normal file
22
adhan-webapp/static/clips/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Adhaan Audio Bestanden
|
||||
|
||||
Deze directory bevat verschillende adhaan audio bestanden voor de gebedstijden applicatie.
|
||||
|
||||
## Beschikbare Adhaan Stijlen:
|
||||
|
||||
1. **adhan1.mp3** - Klassieke adhaan (origineel)
|
||||
2. **adhan_mekka.mp3** - Mekka stijl adhaan
|
||||
3. **adhan_medina.mp3** - Medina stijl adhaan
|
||||
4. **adhan_kort.mp3** - Korte versie adhaan
|
||||
5. **adhan_traditioneel.mp3** - Traditionele Nederlandse moskee stijl
|
||||
|
||||
## Gebruik:
|
||||
|
||||
De gebruiker kan in de instellingen kiezen welke adhaan-stijl gebruikt wordt.
|
||||
Alle bestanden moeten in MP3 formaat zijn voor compatibiliteit met de browser en Sonos systemen.
|
||||
|
||||
## Toevoegen van nieuwe bestanden:
|
||||
|
||||
1. Plaats het MP3 bestand in deze directory
|
||||
2. Het bestand wordt automatisch beschikbaar in de instellingen
|
||||
3. Zorg ervoor dat het bestand niet te groot is (max 5MB aanbevolen)
|
||||
BIN
adhan-webapp/static/clips/adhan1.mp3
Normal file
BIN
adhan-webapp/static/clips/adhan1.mp3
Normal file
Binary file not shown.
1
adhan-webapp/static/clips/done
Normal file
1
adhan-webapp/static/clips/done
Normal file
@ -0,0 +1 @@
|
||||
Sun May 25 22:08:19 CEST 2025: Weersinformatie toegevoegd met OpenWeatherMap API integratie
|
||||
105
adhan-webapp/static/countdown.js
Normal file
105
adhan-webapp/static/countdown.js
Normal file
@ -0,0 +1,105 @@
|
||||
function updateCurrentTime() {
|
||||
const now = new Date();
|
||||
const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
document.getElementById("current-time").textContent = timeStr;
|
||||
|
||||
// Update datum informatie elke minuut
|
||||
updateDateInfo();
|
||||
}
|
||||
|
||||
function updateDateInfo() {
|
||||
fetch('/api/date-info')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const gregorianElement = document.querySelector('.gregorian-datum');
|
||||
const hijriArabicElement = document.querySelector('.hijri-datum-arabic');
|
||||
const hijriDutchElement = document.querySelector('.hijri-datum-dutch');
|
||||
|
||||
if (gregorianElement) gregorianElement.textContent = data.gregorian_full;
|
||||
if (hijriArabicElement) hijriArabicElement.textContent = data.hijri_arabic;
|
||||
if (hijriDutchElement) hijriDutchElement.textContent = data.hijri_dutch;
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Fout bij bijwerken datum:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Gebedstijden ophalen uit een globale variabele (wordt in de template gezet)
|
||||
let prayerTimes = window.prayerTimes || [];
|
||||
let prayerNames = window.prayerNames || [];
|
||||
let currentPrayerIndex = 0;
|
||||
|
||||
function startCountdowns(times, names) {
|
||||
prayerTimes = times;
|
||||
prayerNames = names;
|
||||
currentPrayerIndex = getNextPrayerIndex();
|
||||
startCountdown(prayerTimes[currentPrayerIndex], prayerNames[currentPrayerIndex]);
|
||||
}
|
||||
|
||||
function getNextPrayerIndex() {
|
||||
const now = new Date();
|
||||
for (let i = 0; i < prayerTimes.length; i++) {
|
||||
const [h, m] = prayerTimes[i].split(":");
|
||||
const t = new Date();
|
||||
t.setHours(parseInt(h));
|
||||
t.setMinutes(parseInt(m));
|
||||
t.setSeconds(0);
|
||||
if (t > now) return i;
|
||||
}
|
||||
return 0; // fallback: eerste gebed
|
||||
}
|
||||
|
||||
function startCountdown(targetTimeStr, prayerName) {
|
||||
const parts = targetTimeStr.split(":");
|
||||
let target = new Date();
|
||||
target.setHours(parseInt(parts[0]));
|
||||
target.setMinutes(parseInt(parts[1]));
|
||||
target.setSeconds(0);
|
||||
|
||||
document.querySelector('.huidig-gebed .naam').textContent = prayerName;
|
||||
|
||||
function update() {
|
||||
const now = new Date();
|
||||
let diff = Math.floor((target - now) / 1000);
|
||||
|
||||
if (diff <= 0) {
|
||||
// Speel adhaan af via browser
|
||||
const audio = document.getElementById('adhanAudio');
|
||||
if (audio) audio.play();
|
||||
// Ga naar volgende gebed
|
||||
currentPrayerIndex = (currentPrayerIndex + 1) % prayerTimes.length;
|
||||
const nextTime = prayerTimes[currentPrayerIndex];
|
||||
const nextName = prayerNames[currentPrayerIndex];
|
||||
startCountdown(nextTime, nextName);
|
||||
return;
|
||||
}
|
||||
|
||||
const h = String(Math.floor(diff / 3600)).padStart(2, '0');
|
||||
const m = String(Math.floor((diff % 3600) / 60)).padStart(2, '0');
|
||||
const s = String(diff % 60).padStart(2, '0');
|
||||
|
||||
document.getElementById("countdown").textContent = `${h}:${m}:${s}`;
|
||||
}
|
||||
|
||||
update();
|
||||
window._countdownInterval && clearInterval(window._countdownInterval);
|
||||
window._countdownInterval = setInterval(update, 1000);
|
||||
}
|
||||
|
||||
function setupThemeToggle() {
|
||||
const toggleBtn = document.getElementById("themeToggle");
|
||||
const html = document.documentElement;
|
||||
|
||||
function applyTheme(theme) {
|
||||
html.className = theme;
|
||||
localStorage.setItem('theme', theme);
|
||||
}
|
||||
|
||||
const saved = localStorage.getItem('theme') || 'light';
|
||||
applyTheme(saved);
|
||||
|
||||
toggleBtn.addEventListener("click", () => {
|
||||
const current = html.className === 'light' ? 'dark' : 'light';
|
||||
applyTheme(current);
|
||||
});
|
||||
}
|
||||
104
adhan-webapp/static/hadiths.json
Normal file
104
adhan-webapp/static/hadiths.json
Normal file
@ -0,0 +1,104 @@
|
||||
{
|
||||
"hadiths": [
|
||||
{
|
||||
"text": "De daden die Allah het meest liefheeft zijn degenen die regelmatig worden verricht, zelfs als ze klein zijn.",
|
||||
"bron": "Sahih al-Bukhari"
|
||||
},
|
||||
{
|
||||
"text": "Wie gelooft in Allah en de Laatste Dag, laat hem zijn buur goed behandelen.",
|
||||
"bron": "Sahih Muslim"
|
||||
},
|
||||
{
|
||||
"text": "De sterkste onder jullie is degene die zijn woede kan beheersen.",
|
||||
"bron": "Sahih al-Bukhari"
|
||||
},
|
||||
{
|
||||
"text": "Een glimlach naar je broeder is een daad van liefdadigheid.",
|
||||
"bron": "At-Tirmidhi"
|
||||
},
|
||||
{
|
||||
"text": "Allah is Barmhartig en houdt van barmhartigheid. Hij geeft barmhartigheid aan degenen die barmhartig zijn.",
|
||||
"bron": "Sahih al-Bukhari"
|
||||
},
|
||||
{
|
||||
"text": "Wie niet dankbaar is tegenover de mensen, is niet dankbaar tegenover Allah.",
|
||||
"bron": "At-Tirmidhi"
|
||||
},
|
||||
{
|
||||
"text": "De beste onder jullie zijn degenen die het beste karakter hebben.",
|
||||
"bron": "Sahih al-Bukhari"
|
||||
},
|
||||
{
|
||||
"text": "Allah kijkt niet naar jullie uiterlijk of jullie bezittingen, maar Hij kijkt naar jullie harten en jullie daden.",
|
||||
"bron": "Sahih Muslim"
|
||||
},
|
||||
{
|
||||
"text": "Wie zijn gebed verwaarloost, heeft de band met zijn religie verbroken.",
|
||||
"bron": "Musnad Ahmad"
|
||||
},
|
||||
{
|
||||
"text": "De gelovige die met mensen omgaat en hun lasten verdraagt, is beter dan de gelovige die niet met mensen omgaat en hun lasten niet verdraagt.",
|
||||
"bron": "At-Tirmidhi"
|
||||
},
|
||||
{
|
||||
"text": "Weet dat met elke moeilijkheid verlichting komt.",
|
||||
"bron": "Koran 94:5-6"
|
||||
},
|
||||
{
|
||||
"text": "De sterkste mens is niet degene die anderen overwint, maar degene die zichzelf overwint wanneer hij boos is.",
|
||||
"bron": "Sahih al-Bukhari"
|
||||
},
|
||||
{
|
||||
"text": "Allah belast een ziel niet boven haar vermogen.",
|
||||
"bron": "Koran 2:286"
|
||||
},
|
||||
{
|
||||
"text": "Allah heeft de slechte gedachten van mijn ummah vergeven, zolang ze er niet naar handelen of erover spreken.",
|
||||
"bron": "Sahih al-Bukhari"
|
||||
},
|
||||
{
|
||||
"text": "Shaytaan komt tot een van jullie en zegt: 'Wie heeft dit geschapen? Wie heeft dat geschapen?' totdat hij zegt: 'Wie heeft Allah geschapen?' Wanneer dit punt wordt bereikt, zoek dan toevlucht bij Allah en stop ermee.",
|
||||
"bron": "Sahih al-Bukhari"
|
||||
},
|
||||
{
|
||||
"text": "De gelovigen klaagden bij de Profeet over dwanggedachten. Hij zei: 'Dat is duidelijk geloof.' - Het feit dat je je zorgen maakt over deze gedachten toont je geloof aan.",
|
||||
"bron": "Sahih Muslim"
|
||||
},
|
||||
{
|
||||
"text": "Wanneer een van jullie getroffen wordt door dwanggedachten in zijn geloof, laat hem zeggen: 'Ik geloof in Allah en Zijn boodschappers.'",
|
||||
"bron": "Sahih Muslim"
|
||||
},
|
||||
{
|
||||
"text": "Allah heeft mijn ummah vergeven voor wat hun harten ingeven, zolang ze er niet naar handelen of erover spreken.",
|
||||
"bron": "Sahih al-Bukhari"
|
||||
},
|
||||
{
|
||||
"text": "Zoek toevlucht bij Allah tegen de ingeefdheid van shaytaan. Zeg: 'A'udhu billahi min ash-shaytaan ir-rajeem.'",
|
||||
"bron": "Sahih Muslim"
|
||||
},
|
||||
{
|
||||
"text": "Wanneer shaytaan jullie probeert af te leiden tijdens het gebed, spuug dan drie keer naar links en zoek toevlucht bij Allah.",
|
||||
"bron": "Sahih al-Bukhari"
|
||||
},
|
||||
{
|
||||
"text": "De harten van de kinderen van Adam zijn allemaal tussen twee vingers van de Barmhartige. Hij keert ze om zoals Hij wil.",
|
||||
"bron": "Sahih Muslim"
|
||||
},
|
||||
{
|
||||
"text": "O Allah, Gij die de harten doet keren, keer mijn hart naar Uw gehoorzaamheid.",
|
||||
"bron": "At-Tirmidhi"
|
||||
},
|
||||
{
|
||||
"text": "Degene die zegt: 'La hawla wa la quwwata illa billah' (Er is geen macht behalve bij Allah), wordt geholpen tegen zijn zorgen en verdriet.",
|
||||
"bron": "Abu Dawud"
|
||||
},
|
||||
{
|
||||
"text": "Wanneer je bezorgd of angstig bent, zeg dan veel: 'La hawla wa la quwwata illa billahi al-'ali al-'azeem.'",
|
||||
"bron": "Al-Hakim"
|
||||
},
|
||||
{
|
||||
"text": "Allah zal voor degene die Hem vreest een uitweg maken en hem voorzien van waar hij het niet verwacht.",
|
||||
"bron": "Koran 65:2-3"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
adhan-webapp/static/moskee.png
Normal file
BIN
adhan-webapp/static/moskee.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 629 KiB |
402
adhan-webapp/static/quran.css
Normal file
402
adhan-webapp/static/quran.css
Normal file
@ -0,0 +1,402 @@
|
||||
/* Quran pagina - volledige breedte met smal zijmenu en correcte theme ondersteuning */
|
||||
|
||||
/* Default (dark) theme variabelen voor Quran pagina */
|
||||
:root {
|
||||
--quran-bg: #111;
|
||||
--quran-text: #fff;
|
||||
--quran-panel-bg: rgba(24, 24, 24, 0.95);
|
||||
--quran-panel-text: #fff;
|
||||
--quran-accent: #fff;
|
||||
--quran-border: rgba(255, 255, 255, 0.1);
|
||||
--quran-card-bg: rgba(40, 40, 40, 0.95);
|
||||
--quran-text-secondary: #bbb;
|
||||
--quran-hover-bg: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Light theme variabelen voor Quran pagina */
|
||||
html.light {
|
||||
--quran-bg: #f7fafc;
|
||||
--quran-text: #222;
|
||||
--quran-panel-bg: #fff;
|
||||
--quran-panel-text: #222;
|
||||
--quran-accent: #d4af37;
|
||||
--quran-border: #e0e6ed;
|
||||
--quran-card-bg: #ffffff;
|
||||
--quran-text-secondary: #666;
|
||||
--quran-hover-bg: rgba(212, 175, 55, 0.1);
|
||||
}
|
||||
|
||||
/* Zorg ervoor dat het zijmenu de Quran theme variabelen gebruikt */
|
||||
.app .right.new-layout {
|
||||
min-width: 120px !important;
|
||||
max-width: 120px !important;
|
||||
flex: 0 0 120px !important;
|
||||
padding: 0 !important;
|
||||
background: var(--quran-panel-bg) !important;
|
||||
color: var(--quran-text) !important;
|
||||
border: 1px solid var(--quran-border) !important;
|
||||
}
|
||||
|
||||
/* Zorg dat alleen vertical-tijden wordt getoond */
|
||||
.app .hadith-tijden-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--quran-panel-bg);
|
||||
}
|
||||
|
||||
.app .hadith-center {
|
||||
display: none !important; /* Verberg de hadith sectie */
|
||||
}
|
||||
|
||||
.app .vertical-tijden {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 120px;
|
||||
width: 100%;
|
||||
background: none;
|
||||
height: 100%;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
/* Zorg dat zijmenu items de Quran theme variabelen gebruiken */
|
||||
.app .tijden-en-icoontjes {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.7rem;
|
||||
}
|
||||
|
||||
.app .tijden-en-icoontjes li {
|
||||
font-size: 1.25rem;
|
||||
color: var(--quran-text) !important;
|
||||
opacity: 0.85;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 0.2s, opacity 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app .tijden-rij {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: auto;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.app .tijden-rij .naam {
|
||||
font-size: 1.05rem;
|
||||
color: var(--quran-text) !important;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
opacity: 0.85;
|
||||
letter-spacing: 0.04em;
|
||||
text-align: center;
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.app .tijden-rij .tijd {
|
||||
font-size: 1.15rem;
|
||||
color: var(--quran-text) !important;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
opacity: 0.85;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app .tijden-en-icoontjes .icoon-link {
|
||||
font-size: 1.35rem;
|
||||
color: var(--quran-text) !important;
|
||||
text-decoration: none;
|
||||
background: none;
|
||||
border: none;
|
||||
opacity: 0.85;
|
||||
transition: color 0.2s, opacity 0.2s;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.app .tijden-en-icoontjes .icoon-link:hover {
|
||||
color: var(--quran-accent) !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.app .icoon-link .material-icons {
|
||||
font-size: 1.7rem;
|
||||
color: var(--quran-text) !important;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
transition: color 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.app .icoon-link:hover .material-icons {
|
||||
color: var(--quran-accent) !important;
|
||||
}
|
||||
|
||||
/* Main content area - twee kolommen layout */
|
||||
.quran-full-content {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 500px 1fr;
|
||||
gap: 0;
|
||||
height: 100vh;
|
||||
background: var(--quran-bg);
|
||||
color: var(--quran-text);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Linker kolom - Player */
|
||||
.quran-left-column {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
border-right: 1px solid var(--quran-border);
|
||||
background: var(--quran-panel-bg);
|
||||
}
|
||||
|
||||
/* Rechter kolom - Zoeken en Sura lijst */
|
||||
.quran-right-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--quran-bg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Header styling */
|
||||
.quran-header {
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.quran-header h1 {
|
||||
font-family: 'Cairo', sans-serif;
|
||||
font-size: 2.2em;
|
||||
color: var(--quran-accent);
|
||||
margin: 0;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid var(--quran-border);
|
||||
}
|
||||
|
||||
/* Reciter Selection */
|
||||
.reciter-selection {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.reciter-selection label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
color: var(--quran-text);
|
||||
font-size: 0.9em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--quran-border);
|
||||
border-radius: 8px;
|
||||
background: var(--quran-card-bg);
|
||||
color: var(--quran-text);
|
||||
font-size: 1em;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.custom-select:hover {
|
||||
background: var(--quran-hover-bg);
|
||||
}
|
||||
|
||||
/* Player Section */
|
||||
.player-section {
|
||||
background: var(--quran-card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
margin-top: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sura-info h2 {
|
||||
font-size: 1.8em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sura-info p {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Player Controls */
|
||||
.player-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
border: 2px solid var(--quran-accent);
|
||||
color: var(--quran-accent);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: var(--quran-accent);
|
||||
color: var(--quran-bg);
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
transform: scale(1.1);
|
||||
background: var(--quran-accent);
|
||||
color: var(--quran-bg);
|
||||
}
|
||||
|
||||
/* Progress Bar */
|
||||
.progress-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--quran-border);
|
||||
}
|
||||
|
||||
/* Search Section */
|
||||
.search-section {
|
||||
padding: 20px;
|
||||
background: var(--quran-panel-bg);
|
||||
border-bottom: 1px solid var(--quran-border);
|
||||
}
|
||||
|
||||
.search-section label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
color: var(--quran-text);
|
||||
font-size: 0.9em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 97%;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--quran-border);
|
||||
border-radius: 8px;
|
||||
background: var(--quran-card-bg);
|
||||
color: var(--quran-text);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Sura List */
|
||||
.sura-list-section {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
background: var(--quran-bg);
|
||||
}
|
||||
|
||||
.sura-list-section h3 {
|
||||
font-size: 1.4em;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--quran-border);
|
||||
}
|
||||
|
||||
.sura-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 15px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sura-item {
|
||||
background: var(--quran-card-bg);
|
||||
border: 1px solid var(--quran-border);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.sura-item:hover {
|
||||
transform: translateY(-2px);
|
||||
background: var(--quran-hover-bg);
|
||||
border-color: var(--quran-accent);
|
||||
}
|
||||
|
||||
.sura-number {
|
||||
font-size: 0.8em;
|
||||
opacity: 0.7;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.sura-name-arabic {
|
||||
font-size: 1.4em;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.sura-name-dutch {
|
||||
font-size: 0.9em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 1200px) {
|
||||
.quran-full-content {
|
||||
grid-template-columns: 1fr;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.quran-left-column {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--quran-border);
|
||||
}
|
||||
|
||||
.quran-right-column {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.quran-header h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.sura-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.player-section {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
524
adhan-webapp/static/quran.js
Normal file
524
adhan-webapp/static/quran.js
Normal file
@ -0,0 +1,524 @@
|
||||
// Quran Speler JavaScript
|
||||
class QuranPlayer {
|
||||
constructor() {
|
||||
console.log('QuranPlayer constructor called');
|
||||
this.currentIndex = 0;
|
||||
this.isPlaying = false;
|
||||
this.currentReciter = '';
|
||||
this.audio = document.getElementById('quranAudio');
|
||||
this.suras = this.getSuraList();
|
||||
this.reciters = this.getReciterList();
|
||||
this.loadingTimeout = null;
|
||||
|
||||
console.log('Suras loaded:', this.suras.length);
|
||||
console.log('Reciters loaded:', this.reciters.length);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log('Initializing QuranPlayer...');
|
||||
this.setupEventListeners();
|
||||
this.populateReciters();
|
||||
this.renderSuraGrid();
|
||||
this.setupAudioEvents();
|
||||
console.log('QuranPlayer initialized successfully');
|
||||
}
|
||||
|
||||
getSuraList() {
|
||||
return [
|
||||
{ number: 1, arabic: "الفاتحة", dutch: "Al-Faatihah", file: "001" },
|
||||
{ number: 2, arabic: "البقرة", dutch: "Al-Baqarah", file: "002" },
|
||||
{ number: 3, arabic: "آل عمران", dutch: "Aali-Imraan", file: "003" },
|
||||
{ number: 4, arabic: "النساء", dutch: "An-Nisaa", file: "004" },
|
||||
{ number: 5, arabic: "المائدة", dutch: "Al-Ma'idah", file: "005" },
|
||||
{ number: 6, arabic: "الأنعام", dutch: "Al-An'aam", file: "006" },
|
||||
{ number: 7, arabic: "الأعراف", dutch: "Al-A'raaf", file: "007" },
|
||||
{ number: 8, arabic: "الأنفال", dutch: "Al-Anfaal", file: "008" },
|
||||
{ number: 9, arabic: "التوبة", dutch: "At-Tawbah", file: "009" },
|
||||
{ number: 10, arabic: "يونس", dutch: "Yoonus", file: "010" },
|
||||
{ number: 11, arabic: "هود", dutch: "Hood", file: "011" },
|
||||
{ number: 12, arabic: "يوسف", dutch: "Yoosuf", file: "012" },
|
||||
{ number: 13, arabic: "الرعد", dutch: "Ar-Ra'd", file: "013" },
|
||||
{ number: 14, arabic: "إبراهيم", dutch: "Ibraheem", file: "014" },
|
||||
{ number: 15, arabic: "الحجر", dutch: "Al-Hijr", file: "015" },
|
||||
{ number: 16, arabic: "النحل", dutch: "An-Nahl", file: "016" },
|
||||
{ number: 17, arabic: "الإسراء", dutch: "Al-Israa", file: "017" },
|
||||
{ number: 18, arabic: "الكهف", dutch: "Al-Kahf", file: "018" },
|
||||
{ number: 19, arabic: "مريم", dutch: "Maryam", file: "019" },
|
||||
{ number: 20, arabic: "طه", dutch: "Taa Haa", file: "020" },
|
||||
{ number: 21, arabic: "الأنبياء", dutch: "Al-Anbiyaa", file: "021" },
|
||||
{ number: 22, arabic: "الحج", dutch: "Al-Hajj", file: "022" },
|
||||
{ number: 23, arabic: "المؤمنون", dutch: "Al-Mu'minoon", file: "023" },
|
||||
{ number: 24, arabic: "النور", dutch: "An-Noor", file: "024" },
|
||||
{ number: 25, arabic: "الفرقان", dutch: "Al-Furqaan", file: "025" },
|
||||
{ number: 26, arabic: "الشعراء", dutch: "Ash-Shu'araa", file: "026" },
|
||||
{ number: 27, arabic: "النمل", dutch: "An-Naml", file: "027" },
|
||||
{ number: 28, arabic: "القصص", dutch: "Al-Qasas", file: "028" },
|
||||
{ number: 29, arabic: "العنكبوت", dutch: "Al-Ankaboot", file: "029" },
|
||||
{ number: 30, arabic: "الروم", dutch: "Ar-Room", file: "030" },
|
||||
{ number: 31, arabic: "لقمان", dutch: "Luqmaan", file: "031" },
|
||||
{ number: 32, arabic: "السجدة", dutch: "As-Sajdah", file: "032" },
|
||||
{ number: 33, arabic: "الأحزاب", dutch: "Al-Ahzaab", file: "033" },
|
||||
{ number: 34, arabic: "سبأ", dutch: "Saba", file: "034" },
|
||||
{ number: 35, arabic: "فاطر", dutch: "Faatir", file: "035" },
|
||||
{ number: 36, arabic: "يس", dutch: "Yaa Seen", file: "036" },
|
||||
{ number: 37, arabic: "الصافات", dutch: "As-Saaffaat", file: "037" },
|
||||
{ number: 38, arabic: "ص", dutch: "Saad", file: "038" },
|
||||
{ number: 39, arabic: "الزمر", dutch: "Az-Zumar", file: "039" },
|
||||
{ number: 40, arabic: "غافر", dutch: "Ghaafir", file: "040" },
|
||||
{ number: 41, arabic: "فصلت", dutch: "Fussilat", file: "041" },
|
||||
{ number: 42, arabic: "الشورى", dutch: "Ash-Shooraa", file: "042" },
|
||||
{ number: 43, arabic: "الزخرف", dutch: "Az-Zukhruf", file: "043" },
|
||||
{ number: 44, arabic: "الدخان", dutch: "Ad-Dukhaan", file: "044" },
|
||||
{ number: 45, arabic: "الجاثية", dutch: "Al-Jaathiyah", file: "045" },
|
||||
{ number: 46, arabic: "الأحقاف", dutch: "Al-Ahqaaf", file: "046" },
|
||||
{ number: 47, arabic: "محمد", dutch: "Muhammad", file: "047" },
|
||||
{ number: 48, arabic: "الفتح", dutch: "Al-Fath", file: "048" },
|
||||
{ number: 49, arabic: "الحجرات", dutch: "Al-Hujuraat", file: "049" },
|
||||
{ number: 50, arabic: "ق", dutch: "Qaaf", file: "050" },
|
||||
{ number: 51, arabic: "الذاريات", dutch: "Adh-Dhaariyaat", file: "051" },
|
||||
{ number: 52, arabic: "الطور", dutch: "At-Toor", file: "052" },
|
||||
{ number: 53, arabic: "النجم", dutch: "An-Najm", file: "053" },
|
||||
{ number: 54, arabic: "القمر", dutch: "Al-Qamar", file: "054" },
|
||||
{ number: 55, arabic: "الرحمن", dutch: "Ar-Rahmaan", file: "055" },
|
||||
{ number: 56, arabic: "الواقعة", dutch: "Al-Waaqi'ah", file: "056" },
|
||||
{ number: 57, arabic: "الحديد", dutch: "Al-Hadeed", file: "057" },
|
||||
{ number: 58, arabic: "المجادلة", dutch: "Al-Mujaadilah", file: "058" },
|
||||
{ number: 59, arabic: "الحشر", dutch: "Al-Hashr", file: "059" },
|
||||
{ number: 60, arabic: "الممتحنة", dutch: "Al-Mumtahanah", file: "060" },
|
||||
{ number: 61, arabic: "الصف", dutch: "As-Saff", file: "061" },
|
||||
{ number: 62, arabic: "الجمعة", dutch: "Al-Jumu'ah", file: "062" },
|
||||
{ number: 63, arabic: "المنافقون", dutch: "Al-Munaafiqoon", file: "063" },
|
||||
{ number: 64, arabic: "التغابن", dutch: "At-Taghaabun", file: "064" },
|
||||
{ number: 65, arabic: "الطلاق", dutch: "At-Talaaq", file: "065" },
|
||||
{ number: 66, arabic: "التحريم", dutch: "At-Tahreem", file: "066" },
|
||||
{ number: 67, arabic: "الملك", dutch: "Al-Mulk", file: "067" },
|
||||
{ number: 68, arabic: "القلم", dutch: "Al-Qalam", file: "068" },
|
||||
{ number: 69, arabic: "الحاقة", dutch: "Al-Haaqqah", file: "069" },
|
||||
{ number: 70, arabic: "المعارج", dutch: "Al-Ma'aarij", file: "070" },
|
||||
{ number: 71, arabic: "نوح", dutch: "Nooh", file: "071" },
|
||||
{ number: 72, arabic: "الجن", dutch: "Al-Jinn", file: "072" },
|
||||
{ number: 73, arabic: "المزمل", dutch: "Al-Muzzammil", file: "073" },
|
||||
{ number: 74, arabic: "المدثر", dutch: "Al-Muddaththir", file: "074" },
|
||||
{ number: 75, arabic: "القيامة", dutch: "Al-Qiyaamah", file: "075" },
|
||||
{ number: 76, arabic: "الإنسان", dutch: "Al-Insaan", file: "076" },
|
||||
{ number: 77, arabic: "المرسلات", dutch: "Al-Mursalaat", file: "077" },
|
||||
{ number: 78, arabic: "النبأ", dutch: "An-Naba", file: "078" },
|
||||
{ number: 79, arabic: "النازعات", dutch: "An-Naazi'aat", file: "079" },
|
||||
{ number: 80, arabic: "عبس", dutch: "Abasa", file: "080" },
|
||||
{ number: 81, arabic: "التكوير", dutch: "At-Takweer", file: "081" },
|
||||
{ number: 82, arabic: "الانفطار", dutch: "Al-Infitaar", file: "082" },
|
||||
{ number: 83, arabic: "المطففين", dutch: "Al-Mutaffifeen", file: "083" },
|
||||
{ number: 84, arabic: "الانشقاق", dutch: "Al-Inshiqaaq", file: "084" },
|
||||
{ number: 85, arabic: "البروج", dutch: "Al-Burooj", file: "085" },
|
||||
{ number: 86, arabic: "الطارق", dutch: "At-Taariq", file: "086" },
|
||||
{ number: 87, arabic: "الأعلى", dutch: "Al-A'laa", file: "087" },
|
||||
{ number: 88, arabic: "الغاشية", dutch: "Al-Ghaashiyah", file: "088" },
|
||||
{ number: 89, arabic: "الفجر", dutch: "Al-Fajr", file: "089" },
|
||||
{ number: 90, arabic: "البلد", dutch: "Al-Balad", file: "090" },
|
||||
{ number: 91, arabic: "الشمس", dutch: "Ash-Shams", file: "091" },
|
||||
{ number: 92, arabic: "الليل", dutch: "Al-Layl", file: "092" },
|
||||
{ number: 93, arabic: "الضحى", dutch: "Ad-Duhaa", file: "093" },
|
||||
{ number: 94, arabic: "الشرح", dutch: "Ash-Sharh", file: "094" },
|
||||
{ number: 95, arabic: "التين", dutch: "At-Teen", file: "095" },
|
||||
{ number: 96, arabic: "العلق", dutch: "Al-Alaq", file: "096" },
|
||||
{ number: 97, arabic: "القدر", dutch: "Al-Qadr", file: "097" },
|
||||
{ number: 98, arabic: "البينة", dutch: "Al-Bayyinah", file: "098" },
|
||||
{ number: 99, arabic: "الزلزلة", dutch: "Az-Zalzalah", file: "099" },
|
||||
{ number: 100, arabic: "العاديات", dutch: "Al-Aadiyaat", file: "100" },
|
||||
{ number: 101, arabic: "القارعة", dutch: "Al-Qaari'ah", file: "101" },
|
||||
{ number: 102, arabic: "التكاثر", dutch: "At-Takaathur", file: "102" },
|
||||
{ number: 103, arabic: "العصر", dutch: "Al-Asr", file: "103" },
|
||||
{ number: 104, arabic: "الهمزة", dutch: "Al-Humazah", file: "104" },
|
||||
{ number: 105, arabic: "الفيل", dutch: "Al-Feel", file: "105" },
|
||||
{ number: 106, arabic: "قريش", dutch: "Quraysh", file: "106" },
|
||||
{ number: 107, arabic: "الماعون", dutch: "Al-Maa'oon", file: "107" },
|
||||
{ number: 108, arabic: "الكوثر", dutch: "Al-Kawthar", file: "108" },
|
||||
{ number: 109, arabic: "الكافرون", dutch: "Al-Kaafiroon", file: "109" },
|
||||
{ number: 110, arabic: "النصر", dutch: "An-Nasr", file: "110" },
|
||||
{ number: 111, arabic: "المسد", dutch: "Al-Masad", file: "111" },
|
||||
{ number: 112, arabic: "الإخلاص", dutch: "Al-Ikhlaas", file: "112" },
|
||||
{ number: 113, arabic: "الفلق", dutch: "Al-Falaq", file: "113" },
|
||||
{ number: 114, arabic: "الناس", dutch: "An-Naas", file: "114" }
|
||||
];
|
||||
}
|
||||
|
||||
getReciterList() {
|
||||
return [
|
||||
{ id: "abdulbaset_mujawwad", name: "AbdulBaset AbdulSamad", arabic: "عبد الباسط عبد الصمد" },
|
||||
{ id: "muhammad_ayyoob_hq", name: "Muhammad Ayyoob", arabic: "محمد أيوب" },
|
||||
{ id: "abdurrahmaan_as-sudays", name: "Abdur-Rahman as-Sudais", arabic: "عبد الرحمن السديس" },
|
||||
{ id: "maher_almu3aiqly/year1440", name: "Maher Al-Muaiqly", arabic: "ماهر المعيقلي" },
|
||||
{ id: "fares", name: "Fares Abbad", arabic: "فارس عباد" },
|
||||
{ id: "ahmed_ibn_3ali_al-3ajamy", name: "Ahmed ibn Ali al-Ajmy", arabic: "أحمد بن علي العجمي" },
|
||||
{ id: "sa3d_al-ghaamidi/complete", name: "Saad Al-Ghamdi", arabic: "سعد الغامدي" },
|
||||
{ id: "sa3ood_al-shuraym", name: "Sa'ud ash-Shuraym", arabic: "سعود الشريم" },
|
||||
{ id: "khaalid_al-qahtaanee", name: "Khalid al-Qahtani", arabic: "خالد القحطاني" },
|
||||
{ id: "mahmood_khaleel_al-husaree_iza3a", name: "Mahmoud Khaleel Al-Husary", arabic: "محمود خليل الحصري" },
|
||||
{ id: "minshawi_mujawwad", name: "Muhammad Siddiq al-Minshawi", arabic: "محمد صديق المنشاوي" },
|
||||
{ id: "abu_bakr_ash-shatri_tarawee7", name: "Abu Bakr al-Shatri", arabic: "أبو بكر الشاطري" }
|
||||
];
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Reciteur selectie
|
||||
document.getElementById('reciterSelect').addEventListener('change', (e) => {
|
||||
this.currentReciter = e.target.value;
|
||||
this.updateReciterDisplay();
|
||||
console.log('Reciter selected:', this.currentReciter);
|
||||
});
|
||||
|
||||
// Zoekfunctionaliteit
|
||||
document.getElementById('suraSearch').addEventListener('input', (e) => {
|
||||
this.filterSuras(e.target.value);
|
||||
});
|
||||
|
||||
// Player controls
|
||||
document.getElementById('playBtn').addEventListener('click', () => this.togglePlay());
|
||||
document.getElementById('prevBtn').addEventListener('click', () => this.previousSura());
|
||||
document.getElementById('nextBtn').addEventListener('click', () => this.nextSura());
|
||||
|
||||
// Direct play button
|
||||
document.getElementById('directPlayBtn').addEventListener('click', () => this.directPlay());
|
||||
|
||||
// Progress bar
|
||||
document.getElementById('progressBar').addEventListener('input', (e) => {
|
||||
this.seekTo(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
populateReciters() {
|
||||
const select = document.getElementById('reciterSelect');
|
||||
console.log('Populating reciters...', this.reciters);
|
||||
|
||||
this.reciters.forEach(reciter => {
|
||||
const option = document.createElement('option');
|
||||
option.value = reciter.id;
|
||||
option.textContent = `${reciter.name} - ${reciter.arabic}`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
console.log('Reciters populated, total options:', select.children.length);
|
||||
}
|
||||
|
||||
renderSuraGrid() {
|
||||
const grid = document.getElementById('suraGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
this.suras.forEach((sura, index) => {
|
||||
const suraItem = document.createElement('div');
|
||||
suraItem.className = 'sura-item';
|
||||
suraItem.dataset.index = index;
|
||||
|
||||
suraItem.innerHTML = `
|
||||
<div class="sura-number">Sura ${sura.number}</div>
|
||||
<div class="sura-name-arabic">${sura.arabic}</div>
|
||||
<div class="sura-name-dutch">${sura.dutch}</div>
|
||||
`;
|
||||
|
||||
suraItem.addEventListener('click', () => this.selectSura(index));
|
||||
grid.appendChild(suraItem);
|
||||
});
|
||||
}
|
||||
|
||||
filterSuras(searchTerm) {
|
||||
const items = document.querySelectorAll('.sura-item');
|
||||
const term = searchTerm.toLowerCase();
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const sura = this.suras[index];
|
||||
const matches = sura.dutch.toLowerCase().includes(term) ||
|
||||
sura.arabic.includes(term) ||
|
||||
sura.number.toString().includes(term);
|
||||
|
||||
item.style.display = matches ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
selectSura(index) {
|
||||
if (!this.currentReciter) {
|
||||
alert('Selecteer eerst een reciteur');
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentIndex = index;
|
||||
this.updateActiveSura();
|
||||
this.loadCurrentSura();
|
||||
}
|
||||
|
||||
updateActiveSura() {
|
||||
document.querySelectorAll('.sura-item').forEach((item, index) => {
|
||||
item.classList.toggle('active', index === this.currentIndex);
|
||||
});
|
||||
}
|
||||
|
||||
clearLoadingTimeout() {
|
||||
if (this.loadingTimeout) {
|
||||
clearTimeout(this.loadingTimeout);
|
||||
this.loadingTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
hideLoading() {
|
||||
this.clearLoadingTimeout();
|
||||
document.querySelector('.player-section').classList.remove('loading');
|
||||
}
|
||||
|
||||
showLoading() {
|
||||
document.querySelector('.player-section').classList.add('loading');
|
||||
|
||||
// Timeout na 10 seconden
|
||||
this.loadingTimeout = setTimeout(() => {
|
||||
console.log('Loading timeout - hiding loading state');
|
||||
this.hideLoading();
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
loadCurrentSura() {
|
||||
const sura = this.suras[this.currentIndex];
|
||||
const url = `https://download.quranicaudio.com/quran/${this.currentReciter}/${sura.file}.mp3`;
|
||||
|
||||
console.log('Loading sura:', url);
|
||||
|
||||
// Reset audio element
|
||||
this.audio.pause();
|
||||
this.audio.currentTime = 0;
|
||||
|
||||
// Show loading state
|
||||
this.showLoading();
|
||||
|
||||
// Set crossorigin attribute voor CORS
|
||||
this.audio.crossOrigin = "anonymous";
|
||||
this.audio.preload = "metadata";
|
||||
this.audio.src = url;
|
||||
|
||||
this.updateSuraInfo();
|
||||
|
||||
// Force load
|
||||
this.audio.load();
|
||||
|
||||
// Auto-play als er al iets speelde
|
||||
if (this.isPlaying) {
|
||||
// Wacht even voordat we proberen af te spelen
|
||||
setTimeout(() => {
|
||||
this.audio.play().catch(e => {
|
||||
console.log('Auto-play geblokkeerd:', e);
|
||||
this.isPlaying = false;
|
||||
this.updatePlayButton();
|
||||
this.hideLoading();
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
updateSuraInfo() {
|
||||
const sura = this.suras[this.currentIndex];
|
||||
const reciter = this.reciters.find(r => r.id === this.currentReciter);
|
||||
|
||||
document.getElementById('currentSuraName').textContent = `${sura.arabic} - ${sura.dutch}`;
|
||||
document.getElementById('currentReciter').textContent = reciter ? reciter.name : 'Onbekende reciteur';
|
||||
}
|
||||
|
||||
updateReciterDisplay() {
|
||||
const reciter = this.reciters.find(r => r.id === this.currentReciter);
|
||||
if (reciter && this.suras[this.currentIndex]) {
|
||||
this.updateSuraInfo();
|
||||
}
|
||||
}
|
||||
|
||||
togglePlay() {
|
||||
if (!this.currentReciter || !this.audio.src) {
|
||||
alert('Selecteer eerst een reciteur en sura');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isPlaying) {
|
||||
this.audio.pause();
|
||||
} else {
|
||||
// Toon loading als audio nog niet klaar is
|
||||
if (this.audio.readyState < 3) {
|
||||
this.showLoading();
|
||||
}
|
||||
|
||||
this.audio.play().catch(e => {
|
||||
console.error('Fout bij afspelen:', e);
|
||||
this.hideLoading();
|
||||
alert('Kon audio niet afspelen. Controleer je internetverbinding.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updatePlayButton() {
|
||||
const playBtn = document.getElementById('playBtn');
|
||||
const icon = playBtn.querySelector('.material-icons');
|
||||
icon.textContent = this.isPlaying ? 'pause' : 'play_arrow';
|
||||
}
|
||||
|
||||
previousSura() {
|
||||
if (this.currentIndex > 0) {
|
||||
this.selectSura(this.currentIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
nextSura() {
|
||||
if (this.currentIndex < this.suras.length - 1) {
|
||||
this.selectSura(this.currentIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
seekTo(percentage) {
|
||||
if (this.audio.duration) {
|
||||
this.audio.currentTime = (this.audio.duration * percentage) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
directPlay() {
|
||||
if (!this.currentReciter || this.currentIndex === undefined) {
|
||||
alert('Selecteer eerst een reciteur en sura');
|
||||
return;
|
||||
}
|
||||
|
||||
const sura = this.suras[this.currentIndex];
|
||||
const url = `https://download.quranicaudio.com/quran/${this.currentReciter}/${sura.file}.mp3`;
|
||||
|
||||
console.log('Opening direct play:', url);
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
showDirectPlayOption() {
|
||||
document.getElementById('directPlayBtn').style.display = 'flex';
|
||||
}
|
||||
|
||||
setupAudioEvents() {
|
||||
this.audio.addEventListener('loadstart', () => {
|
||||
console.log('Audio loading started');
|
||||
this.showLoading();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('loadedmetadata', () => {
|
||||
console.log('Audio metadata loaded');
|
||||
this.hideLoading();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('loadeddata', () => {
|
||||
console.log('Audio data loaded');
|
||||
this.hideLoading();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('canplay', () => {
|
||||
console.log('Audio can play');
|
||||
this.hideLoading();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('canplaythrough', () => {
|
||||
console.log('Audio can play through');
|
||||
this.hideLoading();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('play', () => {
|
||||
console.log('Audio started playing');
|
||||
this.isPlaying = true;
|
||||
this.updatePlayButton();
|
||||
this.hideLoading();
|
||||
document.querySelectorAll('.sura-item')[this.currentIndex]?.classList.add('playing');
|
||||
});
|
||||
|
||||
this.audio.addEventListener('pause', () => {
|
||||
console.log('Audio paused');
|
||||
this.isPlaying = false;
|
||||
this.updatePlayButton();
|
||||
document.querySelectorAll('.sura-item')[this.currentIndex]?.classList.remove('playing');
|
||||
});
|
||||
|
||||
this.audio.addEventListener('timeupdate', () => {
|
||||
if (this.audio.duration) {
|
||||
const progress = (this.audio.currentTime / this.audio.duration) * 100;
|
||||
document.getElementById('progressBar').value = progress;
|
||||
|
||||
document.getElementById('currentTime').textContent = this.formatTime(this.audio.currentTime);
|
||||
document.getElementById('totalTime').textContent = this.formatTime(this.audio.duration);
|
||||
}
|
||||
});
|
||||
|
||||
this.audio.addEventListener('ended', () => {
|
||||
console.log('Audio ended');
|
||||
this.nextSura();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('error', (e) => {
|
||||
console.error('Audio error:', e, this.audio.error);
|
||||
this.hideLoading();
|
||||
this.showDirectPlayOption(); // Toon direct play optie bij fout
|
||||
|
||||
let errorMsg = 'Fout bij laden van audio.';
|
||||
if (this.audio.error) {
|
||||
switch(this.audio.error.code) {
|
||||
case 1:
|
||||
errorMsg = 'Audio laden afgebroken.';
|
||||
break;
|
||||
case 2:
|
||||
errorMsg = 'Netwerk fout bij laden audio.';
|
||||
break;
|
||||
case 3:
|
||||
errorMsg = 'Audio formaat niet ondersteund.';
|
||||
break;
|
||||
case 4:
|
||||
errorMsg = 'Audio bestand niet gevonden.';
|
||||
break;
|
||||
}
|
||||
}
|
||||
alert(errorMsg + ' Probeer de "Direct afspelen" knop of een andere reciteur.');
|
||||
});
|
||||
|
||||
this.audio.addEventListener('waiting', () => {
|
||||
console.log('Audio waiting/buffering');
|
||||
this.showLoading();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('playing', () => {
|
||||
console.log('Audio playing (after buffering)');
|
||||
this.hideLoading();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('stalled', () => {
|
||||
console.log('Audio stalled');
|
||||
this.hideLoading();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('suspend', () => {
|
||||
console.log('Audio suspended');
|
||||
this.hideLoading();
|
||||
});
|
||||
|
||||
this.audio.addEventListener('abort', () => {
|
||||
console.log('Audio aborted');
|
||||
this.hideLoading();
|
||||
});
|
||||
|
||||
// Timeout voor loading state
|
||||
this.audio.addEventListener('loadstart', () => {
|
||||
setTimeout(() => {
|
||||
if (document.querySelector('.player-section').classList.contains('loading')) {
|
||||
console.log('Loading took too long, showing direct play option');
|
||||
this.hideLoading();
|
||||
this.showDirectPlayOption();
|
||||
}
|
||||
}, 8000); // 8 seconden timeout
|
||||
});
|
||||
}
|
||||
|
||||
formatTime(seconds) {
|
||||
if (isNaN(seconds)) return '0:00';
|
||||
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialiseer de Quran speler wanneer de pagina geladen is
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('DOM loaded, initializing QuranPlayer...');
|
||||
new QuranPlayer();
|
||||
});
|
||||
956
adhan-webapp/static/style.css
Normal file
956
adhan-webapp/static/style.css
Normal file
@ -0,0 +1,956 @@
|
||||
:root {
|
||||
--bg-light: #f7fafc;
|
||||
--bg-dark: #162447;
|
||||
--text-light: #222;
|
||||
--text-dark: #fff;
|
||||
--accent: #fff; /* goud */
|
||||
--accent-blue: #222; /* zwart voor knoppen */
|
||||
--panel-radius: 0rem;
|
||||
--panel-shadow: 0 8px 32px 0 rgba(0,0,0,0.25);
|
||||
--tijdkleur-light: #222;
|
||||
--tijdkleur-dark: #fff;
|
||||
--icoon-light: #222;
|
||||
--icoon-dark: #fff;
|
||||
}
|
||||
|
||||
html.light {
|
||||
--bg: #f7fafc;
|
||||
--weather-temp: #222;
|
||||
--panel-bg: #fff;
|
||||
--panel-text: #222;
|
||||
--accent: #fff;
|
||||
--accent-blue: #f0f0f0;
|
||||
--panel-border: #e0e6ed;
|
||||
--list-bg: #f7fafc;
|
||||
--list-border: #e0e6ed;
|
||||
--active-bg: #fffbe6;
|
||||
--tijdkleur: var(--tijdkleur-light);
|
||||
--icoon-kleur: var(--icoon-light);
|
||||
--card-bg: #ffffff;
|
||||
--border-color: #e0e6ed;
|
||||
--primary-color: #fff;
|
||||
--text-color: #222;
|
||||
--text-secondary: #666;
|
||||
}
|
||||
|
||||
html.dark, body.dark {
|
||||
--bg: #111;
|
||||
--text: #fff;
|
||||
--panel-bg: #181818cc;
|
||||
--panel-text: #fff;
|
||||
--accent: #fff;
|
||||
--accent-blue: #222;
|
||||
--panel-border: #222;
|
||||
--list-bg: rgba(255,255,255,0.04);
|
||||
--list-border: #222;
|
||||
--active-bg: rgba(234, 179, 8, 0.08);
|
||||
--tijdkleur: var(--tijdkleur-dark);
|
||||
--icoon-kleur: var(--icoon-dark);
|
||||
--card-bg: rgba(255, 255, 255, 0.1);
|
||||
--border-color: rgba(255, 255, 255, 0.2);
|
||||
--primary-color: #fff;
|
||||
--text-color: #fff;
|
||||
--text-secondary: #bbb;
|
||||
}
|
||||
|
||||
body, html {
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Specifieke styling voor settings pagina */
|
||||
body.settings-page {
|
||||
overflow-y: auto;
|
||||
height: auto;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
background: url('/static/moskee.png') center/cover no-repeat;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
color: var(--text);
|
||||
height: 100%;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tijdstip {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.tijdstip #current-time {
|
||||
font-family: 'Cairo', Arial, sans-serif;
|
||||
font-size: 6rem;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.1em;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--accent);
|
||||
text-shadow: 2px 2px 8px #000;
|
||||
}
|
||||
|
||||
.tijdstip .datum {
|
||||
font-size: 1.5rem;
|
||||
color: #e0e6ed;
|
||||
}
|
||||
|
||||
.datum-sectie {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gregorian-datum {
|
||||
font-size: 1.4rem;
|
||||
color: #e0e6ed;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.hijri-datum-arabic {
|
||||
font-size: 1.3rem;
|
||||
color: var(--accent);
|
||||
font-family: 'Cairo', Arial, sans-serif;
|
||||
font-weight: 600;
|
||||
direction: rtl;
|
||||
text-align: left;
|
||||
text-shadow: 1px 1px 3px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.hijri-datum-dutch {
|
||||
font-size: 1.1rem;
|
||||
color: #c0c6d0;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.huidig-gebed {
|
||||
margin-top: auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
z-index: 2;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.huidig-gebed .naam {
|
||||
font-family: 'Cairo', Arial, sans-serif;
|
||||
font-size: 4rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
#countdown {
|
||||
font-size: 6rem;
|
||||
font-weight: bold;
|
||||
margin-top: 0.5rem;
|
||||
color: #fff;
|
||||
text-shadow: 2px 2px 8px #000;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
background: var(--panel-bg);
|
||||
color: var(--panel-text);
|
||||
border-radius: var(--panel-radius) 0 0 var(--panel-radius);
|
||||
box-shadow: var(--panel-shadow);
|
||||
min-width: 350px;
|
||||
/* max-width: 600px; */
|
||||
border: 1px solid var(--panel-border);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#hadith-dua-pane {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-style: italic;
|
||||
font-size: 2.4rem;
|
||||
background: none;
|
||||
border: none;
|
||||
margin: 2rem 0 0.5rem 0;
|
||||
padding: 0;
|
||||
color: var(--text);
|
||||
text-align: center;
|
||||
font-family: 'Cairo', Arial, sans-serif;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
blockquote footer {
|
||||
font-size: 1.3rem;
|
||||
color: var(--accent);
|
||||
margin-top: 1rem;
|
||||
text-align: right;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.gebedstijden {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 2rem 0;
|
||||
border-radius: 1rem;
|
||||
background: var(--list-bg);
|
||||
box-shadow: 0 2px 8px 0 rgba(31, 64, 104, 0.10);
|
||||
}
|
||||
|
||||
.gebedstijden li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.7rem 1.2rem;
|
||||
border-bottom: 1px solid var(--list-border);
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.gebedstijden li.active {
|
||||
font-weight: bold;
|
||||
color: var(--accent);
|
||||
background: var(--active-bg);
|
||||
border-left: 4px solid var(--accent);
|
||||
}
|
||||
|
||||
.icoontjes {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
font-size: 1.7rem;
|
||||
margin-top: auto;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#themeToggle {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: transparent;
|
||||
font-size: 1.5rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
z-index: 99;
|
||||
color: var(--accent);
|
||||
text-shadow: 1px 1px 4px #000;
|
||||
}
|
||||
|
||||
.switch-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.switch-buttons button {
|
||||
background: var(--accent-blue);
|
||||
color: var(--text);
|
||||
border: none;
|
||||
padding: 0.7rem 2rem;
|
||||
border-radius: 1.2rem 1.2rem 0 0;
|
||||
font-size: 1.1rem;
|
||||
font-family: 'Cairo', Arial, sans-serif;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
transition: background 0.2s, opacity 0.2s, color 0.2s;
|
||||
box-shadow: 0 2px 8px 0 rgba(31, 64, 104, 0.10);
|
||||
}
|
||||
|
||||
.switch-buttons button.active {
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.app {
|
||||
flex-direction: column;
|
||||
}
|
||||
.right {
|
||||
border-radius: var(--panel-radius) var(--panel-radius) 0 0;
|
||||
min-width: unset;
|
||||
max-width: unset;
|
||||
width: 100%;
|
||||
padding: 1.2rem;
|
||||
}
|
||||
.left {
|
||||
min-height: 40vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.tijdstip #current-time, #countdown {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
.huidig-gebed .naam {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.right {
|
||||
padding: 0.7rem;
|
||||
}
|
||||
blockquote {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.gregorian-datum {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.hijri-datum-arabic {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.hijri-datum-dutch {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.right.new-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
background: var(--panel-bg);
|
||||
color: var(--panel-text);
|
||||
border-radius: var(--panel-radius) 0 0 var(--panel-radius);
|
||||
box-shadow: var(--panel-shadow);
|
||||
min-width: 350px;
|
||||
/* max-width: 600px; */
|
||||
border: 1px solid var(--panel-border);
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hadith-tijden-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hadith-center {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 2.5rem 2rem 2.5rem 2.5rem;
|
||||
}
|
||||
|
||||
.hadith-center blockquote {
|
||||
font-size: 2.2rem;
|
||||
text-align: left;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
font-family: 'Cairo', Arial, sans-serif;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.hadith-center blockquote footer {
|
||||
font-size: 1.1rem;
|
||||
color: var(--accent);
|
||||
margin-top: 1.2rem;
|
||||
text-align: left;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.vertical-tijden {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 90px;
|
||||
background: none;
|
||||
/* padding: 2.5rem 1.2rem 2.5rem 0; */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.gebedstijden-verticaal {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 2.5rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gebedstijden-verticaal li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
padding: 0.3rem 0;
|
||||
font-size: 1.1rem;
|
||||
color: #bbb;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
transition: color 0.2s, font-weight 0.2s;
|
||||
}
|
||||
|
||||
.gebedstijden-verticaal li.active {
|
||||
color: var(--accent);
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.gebedstijden-verticaal .naam {
|
||||
font-size: 0.95em;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.gebedstijden-verticaal .tijd {
|
||||
font-size: 0.95em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.vertical-icoontjes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
margin-top: 1.5rem;
|
||||
font-size: 1.6rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.hadith-tijden-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
.vertical-tijden {
|
||||
flex-direction: row;
|
||||
min-width: unset;
|
||||
padding: 1.2rem 0 0 0;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
.gebedstijden-verticaal {
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
width: auto;
|
||||
margin: 0 1.5rem 0 0;
|
||||
}
|
||||
.gebedstijden-verticaal li {
|
||||
align-items: center;
|
||||
padding: 0 0.7rem;
|
||||
}
|
||||
.vertical-icoontjes {
|
||||
flex-direction: row;
|
||||
gap: 1.2rem;
|
||||
margin-top: 0;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.tijden-en-icoontjes {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.7rem;
|
||||
}
|
||||
|
||||
.tijden-en-icoontjes li {
|
||||
font-size: 1.25rem;
|
||||
color: #fff;
|
||||
opacity: 0.85;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 0.2s, opacity 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tijden-en-icoontjes .tijd {
|
||||
font-size: 1.25rem;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
color: #fff;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.tijden-en-icoontjes .icoon-link {
|
||||
font-size: 1.35rem;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
background: none;
|
||||
border: none;
|
||||
opacity: 0.85;
|
||||
transition: color 0.2s, opacity 0.2s;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tijden-en-icoontjes .icoon-link:hover {
|
||||
color: var(--accent);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tijden-rij {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: auto;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.tijden-rij .naam {
|
||||
font-size: 1.05rem;
|
||||
color: var(--tijdkleur) !important;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
opacity: 0.85;
|
||||
letter-spacing: 0.04em;
|
||||
text-align: center;
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.tijden-rij .tijd {
|
||||
font-size: 1.15rem;
|
||||
color: var(--tijdkleur) !important;
|
||||
font-family: 'Lato', Arial, sans-serif;
|
||||
opacity: 0.85;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.icoon-link .material-icons {
|
||||
font-size: 1.7rem;
|
||||
color: var(--icoon-kleur) !important;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
transition: color 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.icoon-link:hover .material-icons {
|
||||
color: #1e7d4d;
|
||||
}
|
||||
|
||||
#themeToggle .material-icons {
|
||||
color: var(--icoon-kleur) !important;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
/* Weer sectie styling */
|
||||
.weather-section {
|
||||
margin-top: auto;
|
||||
padding: 1.2rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0;
|
||||
font-family: 'Cairo', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.weather-info {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.weather-main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.weather-temp {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--tijdkleur);
|
||||
font-family: 'Cairo', sans-serif;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.weather-desc {
|
||||
font-size: 1rem;
|
||||
color: var(--tijdkleur);
|
||||
opacity: 0.7;
|
||||
font-family: 'Lato', sans-serif;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.weather-details {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-top: 0.8rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: 0.8rem;
|
||||
}
|
||||
|
||||
.weather-detail {
|
||||
font-size: 0.9rem;
|
||||
color: var(--tijdkleur);
|
||||
opacity: 0.7;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-family: 'Lato', sans-serif;
|
||||
}
|
||||
|
||||
/* Responsive aanpassingen voor weer */
|
||||
@media (max-width: 768px) {
|
||||
.weather-section {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.weather-temp {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.weather-details {
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Instellingen pagina styling */
|
||||
.settings-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 1.5rem;
|
||||
background: var(--panel-bg);
|
||||
border-radius: var(--panel-radius);
|
||||
border: 1px solid var(--panel-border);
|
||||
box-shadow: var(--panel-shadow);
|
||||
min-height: auto;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.settings-header h1 {
|
||||
font-family: 'Cairo', sans-serif;
|
||||
font-size: 2rem;
|
||||
color: var(--primary-color);
|
||||
text-align: center;
|
||||
margin: 0.5rem 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: var(--accent);
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
|
||||
.back-link .material-icons {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.form-group label .material-icons {
|
||||
font-size: 1.3rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.form-group input[type="number"],
|
||||
.form-group input[type="time"],
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 0.7rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input[type="number"]:focus,
|
||||
.form-group input[type="time"]:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.audio-section {
|
||||
background: var(--list-bg);
|
||||
padding: 1.2rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.audio-preview-section {
|
||||
background: var(--list-bg);
|
||||
padding: 1.2rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.audio-row {
|
||||
align-items: stretch;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.audio-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.preview-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
background: var(--accent);
|
||||
color: var(--accent-blue);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.preview-btn:hover {
|
||||
background: var(--primary-color);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.preview-btn .material-icons {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.checkboxes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 0.8rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.8rem;
|
||||
background: var(--card-bg);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.checkbox-item:hover {
|
||||
background: var(--active-bg);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.checkbox-item label {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.8rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
color: var(--accent-blue);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--primary-color);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--active-bg);
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-primary .material-icons,
|
||||
.btn-secondary .material-icons {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Responsive design voor instellingen */
|
||||
@media (max-width: 768px) {
|
||||
.settings-container {
|
||||
margin: 0.5rem;
|
||||
padding: 1rem;
|
||||
min-height: auto;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.settings-header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.audio-row {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.audio-section,
|
||||
.audio-preview-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.checkboxes {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
473
adhan-webapp/templates/debug.html
Normal file
473
adhan-webapp/templates/debug.html
Normal file
@ -0,0 +1,473 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Debug - Gebedstijden Tester</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<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">
|
||||
<style>
|
||||
.debug-container {
|
||||
max-width: 1200px;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
background: var(--panel-bg);
|
||||
border-radius: 15px;
|
||||
border: 1px solid var(--panel-border);
|
||||
}
|
||||
|
||||
.debug-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.debug-header h1 {
|
||||
color: var(--accent);
|
||||
font-family: 'Cairo', sans-serif;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.debug-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.debug-panel {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.debug-panel h3 {
|
||||
color: var(--accent);
|
||||
margin-bottom: 1rem;
|
||||
font-family: 'Cairo', sans-serif;
|
||||
}
|
||||
|
||||
.time-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.time-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.time-input input {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 5px;
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
font-size: 1.1rem;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.7rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
margin: 0.2rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.prayer-times-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.prayer-times-list li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.7rem;
|
||||
margin-bottom: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.prayer-times-list li.active {
|
||||
background: rgba(234, 179, 8, 0.2);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.status-display {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: #b8941f;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.debug-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.debug-container {
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="debug-container">
|
||||
<a href="/" class="back-link">← Terug naar hoofdpagina</a>
|
||||
|
||||
<div class="debug-header">
|
||||
<h1>🔧 Debug Mode</h1>
|
||||
<p>Test de adhaan functionaliteit door de tijd aan te passen</p>
|
||||
</div>
|
||||
|
||||
<div class="debug-grid">
|
||||
<div class="debug-panel">
|
||||
<h3>⏰ Tijd Controles</h3>
|
||||
|
||||
<div class="status-display" id="statusDisplay">
|
||||
Huidige tijd: <span id="currentDebugTime">--:--</span><br>
|
||||
Volgende gebed: <span id="nextPrayerDebug">--</span><br>
|
||||
Status: <span id="debugStatus">Normaal</span>
|
||||
</div>
|
||||
|
||||
<div class="time-controls">
|
||||
<div class="time-input">
|
||||
<label>Stel tijd in:</label>
|
||||
<input type="time" id="manualTime" />
|
||||
<button class="btn btn-primary" onclick="setManualTime()">Zet Tijd</button>
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<button class="btn btn-secondary" onclick="adjustTime(-60)">-1 min</button>
|
||||
<button class="btn btn-secondary" onclick="adjustTime(-10)">-10 sec</button>
|
||||
<button class="btn btn-secondary" onclick="adjustTime(10)">+10 sec</button>
|
||||
<button class="btn btn-secondary" onclick="adjustTime(60)">+1 min</button>
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<button class="btn btn-primary" onclick="setTimeToNextPrayer(-1)">1 min voor gebed</button>
|
||||
<button class="btn btn-primary" onclick="setTimeToNextPrayer(0)">Exact gebedstijd</button>
|
||||
<button class="btn btn-danger" onclick="resetTime()">Reset naar echte tijd</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="debug-panel">
|
||||
<h3>🕌 Gebedstijden Vandaag</h3>
|
||||
<ul class="prayer-times-list" id="prayerTimesList">
|
||||
{% for naam, tijd in gebedstijden.items() %}
|
||||
<li data-prayer="{{ naam }}" data-time="{{ tijd[:5] }}">
|
||||
<span>{{ naam }}</span>
|
||||
<span>{{ tijd[:5] }}</span>
|
||||
<button class="btn btn-secondary" onclick="setTimeToPrayer('{{ tijd[:5] }}', -1)">-1 min</button>
|
||||
<button class="btn btn-primary" onclick="setTimeToPrayer('{{ tijd[:5] }}', 0)">Nu</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="debug-panel">
|
||||
<h3>📊 Debug Log</h3>
|
||||
<div class="status-display" id="debugLog" style="height: 200px; overflow-y: auto;">
|
||||
Debug log wordt hier weergegeven...<br>
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="clearLog()">Wis Log</button>
|
||||
</div>
|
||||
|
||||
<div class="debug-panel">
|
||||
<h3>🔊 Sonos Status</h3>
|
||||
<div class="status-display" id="sonosStatus" style="height: 150px; overflow-y: auto;">
|
||||
Sonos status wordt geladen...<br>
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<button class="btn btn-primary" onclick="checkSonosStatus()">🔄 Ververs Status</button>
|
||||
<button class="btn btn-danger" onclick="stopAllSonos()">⏹️ Stop Alle Audio</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let debugTimeOffset = 0; // Offset in seconden
|
||||
let isDebugMode = false;
|
||||
|
||||
function log(message) {
|
||||
const logElement = document.getElementById('debugLog');
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
logElement.innerHTML += `[${timestamp}] ${message}<br>`;
|
||||
logElement.scrollTop = logElement.scrollHeight;
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
document.getElementById('debugLog').innerHTML = 'Debug log gewist...<br>';
|
||||
}
|
||||
|
||||
function getDebugTime() {
|
||||
const now = new Date();
|
||||
if (isDebugMode) {
|
||||
now.setTime(now.getTime() + (debugTimeOffset * 1000));
|
||||
}
|
||||
return now;
|
||||
}
|
||||
|
||||
function updateDebugDisplay() {
|
||||
const debugTime = getDebugTime();
|
||||
const timeStr = debugTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
document.getElementById('currentDebugTime').textContent = timeStr;
|
||||
|
||||
// Update status
|
||||
const statusElement = document.getElementById('debugStatus');
|
||||
statusElement.textContent = isDebugMode ? 'Debug Mode Actief' : 'Normaal';
|
||||
statusElement.style.color = isDebugMode ? '#ff6b6b' : '#00ff00';
|
||||
|
||||
// Check voor gebedstijd
|
||||
checkPrayerTime(timeStr);
|
||||
}
|
||||
|
||||
function checkPrayerTime(currentTime) {
|
||||
const prayerTimes = {{ gebedstijden | tojson }};
|
||||
let nextPrayer = null;
|
||||
let isExactTime = false;
|
||||
|
||||
for (const [name, time] of Object.entries(prayerTimes)) {
|
||||
const prayerTime = time.substring(0, 5);
|
||||
if (prayerTime === currentTime) {
|
||||
isExactTime = true;
|
||||
log(`🕌 ADHAAN TIJD! ${name} (${prayerTime})`);
|
||||
// Trigger adhaan
|
||||
triggerAdhaan(name);
|
||||
}
|
||||
|
||||
if (prayerTime > currentTime && !nextPrayer) {
|
||||
nextPrayer = `${name} (${prayerTime})`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextPrayer) {
|
||||
// Volgende dag
|
||||
const firstPrayer = Object.entries(prayerTimes)[0];
|
||||
nextPrayer = `${firstPrayer[0]} (${firstPrayer[1].substring(0, 5)}) - morgen`;
|
||||
}
|
||||
|
||||
document.getElementById('nextPrayerDebug').textContent = nextPrayer;
|
||||
|
||||
// Update active prayer in list
|
||||
document.querySelectorAll('.prayer-times-list li').forEach(li => {
|
||||
li.classList.remove('active');
|
||||
if (li.dataset.time === currentTime) {
|
||||
li.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAdhaan(prayerName) {
|
||||
// Speel adhaan af
|
||||
const audio = document.getElementById('adhanAudio');
|
||||
if (audio) {
|
||||
audio.play().catch(e => log(`Fout bij afspelen audio: ${e.message}`));
|
||||
}
|
||||
|
||||
// Verstuur naar Sonos (als niet gemute)
|
||||
fetch('/api/debug-adhaan', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ prayer: prayerName, time: getDebugTime().toLocaleTimeString() })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
log(`✅ Adhaan verzonden naar Sonos voor ${prayerName}`);
|
||||
} else {
|
||||
log(`❌ Fout bij Sonos: ${data.error}`);
|
||||
}
|
||||
})
|
||||
.catch(e => log(`❌ Netwerk fout: ${e.message}`));
|
||||
}
|
||||
|
||||
function setManualTime() {
|
||||
const timeInput = document.getElementById('manualTime');
|
||||
if (!timeInput.value) return;
|
||||
|
||||
const [hours, minutes] = timeInput.value.split(':');
|
||||
const now = new Date();
|
||||
const targetTime = new Date();
|
||||
targetTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
|
||||
|
||||
debugTimeOffset = Math.floor((targetTime - now) / 1000);
|
||||
isDebugMode = true;
|
||||
|
||||
log(`⏰ Tijd handmatig ingesteld op ${timeInput.value}`);
|
||||
updateDebugDisplay();
|
||||
}
|
||||
|
||||
function adjustTime(seconds) {
|
||||
debugTimeOffset += seconds;
|
||||
isDebugMode = true;
|
||||
|
||||
const sign = seconds > 0 ? '+' : '';
|
||||
log(`⏰ Tijd aangepast: ${sign}${seconds} seconden`);
|
||||
updateDebugDisplay();
|
||||
}
|
||||
|
||||
function setTimeToPrayer(prayerTime, offsetMinutes) {
|
||||
const [hours, minutes] = prayerTime.split(':');
|
||||
const now = new Date();
|
||||
const targetTime = new Date();
|
||||
targetTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
|
||||
targetTime.setTime(targetTime.getTime() + (offsetMinutes * 60 * 1000));
|
||||
|
||||
debugTimeOffset = Math.floor((targetTime - now) / 1000);
|
||||
isDebugMode = true;
|
||||
|
||||
const offsetText = offsetMinutes === 0 ? 'exact' : `${offsetMinutes} min ervoor`;
|
||||
log(`🕌 Tijd ingesteld op ${prayerTime} (${offsetText})`);
|
||||
updateDebugDisplay();
|
||||
}
|
||||
|
||||
function setTimeToNextPrayer(offsetMinutes) {
|
||||
const prayerTimes = {{ gebedstijden | tojson }};
|
||||
const currentTime = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
for (const [name, time] of Object.entries(prayerTimes)) {
|
||||
const prayerTime = time.substring(0, 5);
|
||||
if (prayerTime > currentTime) {
|
||||
setTimeToPrayer(prayerTime, offsetMinutes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Als geen gebed meer vandaag, neem eerste van morgen
|
||||
const firstPrayer = Object.entries(prayerTimes)[0];
|
||||
setTimeToPrayer(firstPrayer[1].substring(0, 5), offsetMinutes);
|
||||
}
|
||||
|
||||
function resetTime() {
|
||||
debugTimeOffset = 0;
|
||||
isDebugMode = false;
|
||||
log(`🔄 Tijd gereset naar echte tijd`);
|
||||
updateDebugDisplay();
|
||||
}
|
||||
|
||||
function checkSonosStatus() {
|
||||
log('🔄 Sonos status controleren...');
|
||||
fetch('/api/sonos-status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const statusElement = document.getElementById('sonosStatus');
|
||||
if (data.success) {
|
||||
let statusHtml = `Sonos API: ${data.api_ip}<br><br>`;
|
||||
|
||||
for (const [zone, info] of Object.entries(data.zones)) {
|
||||
const statusColor = info.status === 'online' ? '#00ff00' : '#ff6b6b';
|
||||
statusHtml += `<span style="color: ${statusColor}">● ${zone}</span><br>`;
|
||||
statusHtml += ` Status: ${info.status}<br>`;
|
||||
|
||||
if (info.status === 'online') {
|
||||
statusHtml += ` Afspelen: ${info.playbackState}<br>`;
|
||||
statusHtml += ` Track: ${info.currentTrack}<br>`;
|
||||
statusHtml += ` Volume: ${info.volume}%<br>`;
|
||||
} else if (info.error) {
|
||||
statusHtml += ` Fout: ${info.error}<br>`;
|
||||
}
|
||||
statusHtml += '<br>';
|
||||
}
|
||||
|
||||
statusElement.innerHTML = statusHtml;
|
||||
log('✅ Sonos status bijgewerkt');
|
||||
} else {
|
||||
statusElement.innerHTML = `❌ Fout: ${data.error}`;
|
||||
log(`❌ Sonos status fout: ${data.error}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
log(`❌ Netwerk fout bij Sonos status: ${error.message}`);
|
||||
document.getElementById('sonosStatus').innerHTML = `❌ Netwerk fout: ${error.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
function stopAllSonos() {
|
||||
log('⏹️ Alle Sonos audio stoppen...');
|
||||
fetch('/api/sonos-stop', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
log(`✅ ${data.message}`);
|
||||
// Ververs status na stoppen
|
||||
setTimeout(checkSonosStatus, 1000);
|
||||
} else {
|
||||
log(`❌ Fout bij stoppen: ${data.error}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
log(`❌ Netwerk fout bij stoppen: ${error.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Update elke seconde
|
||||
setInterval(updateDebugDisplay, 1000);
|
||||
|
||||
// Initiële updates
|
||||
updateDebugDisplay();
|
||||
checkSonosStatus();
|
||||
log('🔧 Debug mode gestart');
|
||||
</script>
|
||||
|
||||
<!-- Hidden audio element for testing -->
|
||||
<audio id="adhanAudio" src="/static/clips/{{ settings['audio_clip'] }}" preload="auto"></audio>
|
||||
</body>
|
||||
</html>
|
||||
173
adhan-webapp/templates/index.html
Normal file
173
adhan-webapp/templates/index.html
Normal file
@ -0,0 +1,173 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Gebedstijden Display</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<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">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<!-- Dark/Light toggle -->
|
||||
<button id="themeToggle" title="Schakel modus"><span id="themeIcon" class="material-icons">brightness_6</span></button>
|
||||
|
||||
<div class="left">
|
||||
<div class="overlay">
|
||||
<div class="tijdstip">
|
||||
<div id="current-time">--:--</div>
|
||||
<div class="datum-sectie">
|
||||
<div class="gregorian-datum">{{ date_info.gregorian_full }}</div>
|
||||
<div class="hijri-datum-arabic">{{ date_info.hijri_arabic }}</div>
|
||||
<div class="hijri-datum-dutch">{{ date_info.hijri_dutch }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="huidig-gebed">
|
||||
<div class="naam">{{ next_name }}</div>
|
||||
<div id="countdown">--:--:--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right new-layout">
|
||||
<div class="hadith-tijden-row">
|
||||
<div class="hadith-center">
|
||||
<blockquote>
|
||||
"{{ hadith.text }}"
|
||||
<footer>— {{ hadith.bron }}</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
<div class="vertical-tijden">
|
||||
<ul class="tijden-en-icoontjes">
|
||||
{% for naam, tijd in gebedstijden.items() %}
|
||||
<li class="tijden-rij">
|
||||
<span class="naam">{{ naam }}</span>
|
||||
<span class="tijd">{{ tijd[:5] }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li><a href="/instellingen" title="Instellingen" class="icoon-link"><span class="material-icons">settings</span></a></li>
|
||||
<li><a href="#" id="muteBtn" title="Mute" class="icoon-link"><span id="muteIcon" class="material-icons">volume_off</span></a></li>
|
||||
<li><a href="/debug" title="Debug Mode" class="icoon-link"><span class="material-icons">bug_report</span></a></li>
|
||||
<li><a href="/quran" title="Quran Speler" class="icoon-link"><span class="material-icons">menu_book</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Weer sectie -->
|
||||
<div class="weather-section">
|
||||
<div class="weather-info">
|
||||
<div class="weather-main">
|
||||
<span class="weather-temp">{{ weather.temperature }}°C</span>
|
||||
<span class="weather-desc">{{ weather.description }}</span>
|
||||
</div>
|
||||
<div class="weather-details">
|
||||
<span class="weather-detail">Voelt als {{ weather.feels_like }}°C</span>
|
||||
<span class="weather-detail">Vochtigheid {{ weather.humidity }}%</span>
|
||||
<span class="weather-detail">Wind {{ weather.wind_speed }} km/h</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<audio id="adhanAudio" src="/static/clips/{{ settings['audio_clip'] }}" preload="auto"></audio>
|
||||
|
||||
<script>
|
||||
// Prayer times data from server
|
||||
window.prayerTimes = [
|
||||
{% for naam, tijd in gebedstijden.items() %}
|
||||
"{{ tijd[:5] }}"{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
window.prayerNames = [
|
||||
{% for naam, tijd in gebedstijden.items() %}
|
||||
"{{ naam }}"{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
const nextPrayerTime = "{{ next_time }}";
|
||||
</script>
|
||||
<script src="/static/countdown.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
updateCurrentTime();
|
||||
setInterval(updateCurrentTime, 60000);
|
||||
setupThemeToggle();
|
||||
startCountdowns(window.prayerTimes, window.prayerNames);
|
||||
|
||||
// Update weerdata elke 10 minuten
|
||||
updateWeather();
|
||||
setInterval(updateWeather, 600000); // 10 minuten
|
||||
|
||||
// Update hadith elke 30 seconden
|
||||
updateHadith();
|
||||
setInterval(updateHadith, 30000); // 30 seconden
|
||||
};
|
||||
|
||||
// Functie om weerdata bij te werken
|
||||
function updateWeather() {
|
||||
fetch('/api/weather')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.querySelector('.weather-temp').textContent = data.temperature + '°C';
|
||||
document.querySelector('.weather-desc').textContent = data.description;
|
||||
document.querySelector('.weather-detail:nth-child(1)').textContent = `Voelt als ${data.feels_like}°C`;
|
||||
document.querySelector('.weather-detail:nth-child(2)').textContent = `Vochtigheid ${data.humidity}%`;
|
||||
document.querySelector('.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
|
||||
function updateHadith() {
|
||||
fetch('/api/hadith')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const hadithTextElement = document.querySelector('.hadith-center blockquote');
|
||||
const hadithFooterElement = document.querySelector('.hadith-center blockquote footer');
|
||||
|
||||
if (hadithTextElement && hadithFooterElement) {
|
||||
// Update de tekst en bron
|
||||
hadithTextElement.innerHTML = `"${data.text}"<footer>— ${data.bron}</footer>`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Fout bij bijwerken hadith:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Theme toggle icon dynamisch aanpassen
|
||||
function updateThemeIcon() {
|
||||
const html = document.documentElement;
|
||||
const icon = document.getElementById('themeIcon');
|
||||
if (html.className === 'light') {
|
||||
icon.textContent = 'dark_mode';
|
||||
} else {
|
||||
icon.textContent = 'light_mode';
|
||||
}
|
||||
}
|
||||
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();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
152
adhan-webapp/templates/quran.html
Normal file
152
adhan-webapp/templates/quran.html
Normal file
@ -0,0 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Quran Speler - Gebedstijden Display</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<link rel="stylesheet" href="/static/quran.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;700&family=Lato:wght@400;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<!-- Dark/Light toggle -->
|
||||
<button id="themeToggle" title="Schakel modus"><span id="themeIcon" class="material-icons">brightness_6</span></button>
|
||||
|
||||
<!-- Main Quran Content (twee kolommen) -->
|
||||
<div class="quran-full-content">
|
||||
<!-- Linker kolom - Player -->
|
||||
<div class="quran-left-column">
|
||||
<!-- Header -->
|
||||
<div class="quran-header">
|
||||
<h1>القرآن الكريم - Quran Speler</h1>
|
||||
</div>
|
||||
|
||||
<!-- Reciter Selection -->
|
||||
<div class="reciter-selection">
|
||||
<label for="reciterSelect">Reciteur / القارئ:</label>
|
||||
<select id="reciterSelect" class="custom-select">
|
||||
<option value="">Selecteer een reciteur...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Player Section -->
|
||||
<div class="player-section">
|
||||
<div class="current-playing">
|
||||
<div class="sura-info">
|
||||
<h2 id="currentSuraName">Selecteer een sura om af te spelen</h2>
|
||||
<p id="currentReciter">Geen reciteur geselecteerd</p>
|
||||
</div>
|
||||
<div class="player-controls">
|
||||
<button id="prevBtn" class="control-btn" title="Vorige">
|
||||
<span class="material-icons">skip_previous</span>
|
||||
</button>
|
||||
<button id="playBtn" class="control-btn play-btn" title="Afspelen/Pauzeren">
|
||||
<span class="material-icons">play_arrow</span>
|
||||
</button>
|
||||
<button id="nextBtn" class="control-btn" title="Volgende">
|
||||
<span class="material-icons">skip_next</span>
|
||||
</button>
|
||||
<button id="directPlayBtn" class="control-btn" title="Direct afspelen in nieuw tabblad" style="display: none;">
|
||||
<span class="material-icons">open_in_new</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="progress-section">
|
||||
<span id="currentTime">0:00</span>
|
||||
<input type="range" id="progressBar" min="0" max="100" value="0" class="progress-bar">
|
||||
<span id="totalTime">0:00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rechter kolom - Zoeken en Sura lijst -->
|
||||
<div class="quran-right-column">
|
||||
<!-- Search Section -->
|
||||
<div class="search-section">
|
||||
<label for="suraSearch">Zoek Sura / البحث عن السورة:</label>
|
||||
<input type="text" id="suraSearch" placeholder="Zoek naar sura naam..." class="search-input">
|
||||
</div>
|
||||
|
||||
<!-- Sura List -->
|
||||
<div class="sura-list-section">
|
||||
<h3>Sura's / السور</h3>
|
||||
<div class="sura-grid" id="suraGrid">
|
||||
<!-- Sura's worden hier dynamisch geladen -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zijmenu (exact hetzelfde als hoofdpagina) -->
|
||||
<div class="right new-layout">
|
||||
<div class="hadith-tijden-row">
|
||||
<div class="vertical-tijden">
|
||||
<ul class="tijden-en-icoontjes">
|
||||
{% for naam, tijd in gebedstijden.items() %}
|
||||
<li class="tijden-rij">
|
||||
<span class="naam">{{ naam }}</span>
|
||||
<span class="tijd">{{ tijd[:5] }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li><a href="/instellingen" title="Instellingen" class="icoon-link"><span class="material-icons">settings</span></a></li>
|
||||
<li><a href="#" id="muteBtn" title="Mute" class="icoon-link"><span id="muteIcon" class="material-icons">volume_off</span></a></li>
|
||||
<li><a href="/debug" title="Debug Mode" class="icoon-link"><span class="material-icons">bug_report</span></a></li>
|
||||
<li><a href="/" title="Terug naar hoofdpagina" class="icoon-link"><span class="material-icons">home</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Audio Element -->
|
||||
<audio id="quranAudio" preload="none"></audio>
|
||||
</div>
|
||||
|
||||
<script src="/static/quran.js"></script>
|
||||
<script>
|
||||
// Theme toggle icon dynamisch aanpassen
|
||||
function updateThemeIcon() {
|
||||
const html = document.documentElement;
|
||||
const icon = document.getElementById('themeIcon');
|
||||
if (html.className === 'light') {
|
||||
icon.textContent = 'dark_mode';
|
||||
} else {
|
||||
icon.textContent = 'light_mode';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('themeToggle').addEventListener('click', function() {
|
||||
const html = document.documentElement;
|
||||
html.className = html.className === 'light' ? '' : 'light';
|
||||
localStorage.setItem('theme', html.className);
|
||||
updateThemeIcon();
|
||||
});
|
||||
|
||||
// Laad opgeslagen theme
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme) {
|
||||
document.documentElement.className = savedTheme;
|
||||
}
|
||||
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();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
199
adhan-webapp/templates/settings.html
Normal file
199
adhan-webapp/templates/settings.html
Normal file
@ -0,0 +1,199 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Instellingen - Gebedstijden Display</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<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">
|
||||
</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">
|
||||
<div class="settings-section">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<span class="material-icons">info</span>
|
||||
Volume Instellingen:
|
||||
</label>
|
||||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0.5rem 0;">
|
||||
Stel verschillende volumes in voor overdag en 's avonds. Het systeem wisselt automatisch tussen deze volumes op basis van de ingestelde tijden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="volume_day">
|
||||
<span class="material-icons">volume_up</span>
|
||||
Volume Overdag (0–100):
|
||||
</label>
|
||||
<input type="number" name="volume_day" value="{{ settings.volume_day or settings.volume }}" min="0" max="100" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="volume_night">
|
||||
<span class="material-icons">volume_down</span>
|
||||
Volume Avond (0–100):
|
||||
</label>
|
||||
<input type="number" name="volume_night" value="{{ settings.volume_night or (settings.volume // 2) }}" min="0" max="100" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-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 class="button-group">
|
||||
<button type="submit" name="test_clip" class="btn-secondary">
|
||||
<span class="material-icons">speaker</span>
|
||||
Test op Sonos
|
||||
</button>
|
||||
<button type="submit" class="btn-primary">
|
||||
<span class="material-icons">save</span>
|
||||
Instellingen Opslaan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function previewAudio() {
|
||||
const select = document.getElementById('audioSelect');
|
||||
const audio = document.getElementById('previewAudio');
|
||||
const selectedClip = select.value;
|
||||
|
||||
audio.src = `/static/clips/${selectedClip}`;
|
||||
audio.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}`;
|
||||
});
|
||||
|
||||
// Set initial audio source
|
||||
window.onload = function() {
|
||||
const select = document.getElementById('audioSelect');
|
||||
const audio = document.getElementById('previewAudio');
|
||||
audio.src = `/static/clips/${select.value}`;
|
||||
|
||||
// Setup theme toggle
|
||||
setupThemeToggle();
|
||||
updateThemeIcon();
|
||||
};
|
||||
|
||||
// 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>
|
||||
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
@ -0,0 +1,30 @@
|
||||
services:
|
||||
adhan-webapp:
|
||||
build:
|
||||
context: /mnt/Main/config/adhan/adhan-webapp
|
||||
container_name: adhan-webapp
|
||||
ports:
|
||||
- '5090:80'
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/Main/config/adhan/adhan-webapp/settings.json:/app/settings.json
|
||||
- /mnt/Main/config/adhan/adhan-webapp/static:/app/static:ro
|
||||
- /mnt/Main/config/adhan/adhan-webapp/templates:/app/templates:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
environment:
|
||||
- FLASK_ENV=development
|
||||
- FLASK_DEBUG=1
|
||||
- PYTHONUNBUFFERED=1
|
||||
- TZ=Europe/Amsterdam
|
||||
sonos-api:
|
||||
container_name: sonos-api
|
||||
image: chrisns/docker-node-sonos-http-api
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/Main/config/adhan/sonos/settings.json:/app/settings.json:ro
|
||||
- /mnt/Main/config/adhan/sonos/presets:/app/presets:ro
|
||||
- /mnt/Main/config/adhan/sonos/cache:/app/cache:ro
|
||||
- /mnt/Main/config/adhan/sonos/clips:/app/static/clips:ro
|
||||
version: '3.8'
|
||||
48
done
Normal file
48
done
Normal file
@ -0,0 +1,48 @@
|
||||
2025-05-25 16:18:07 - Afteller probleem opgelost: JavaScript initialisatie aangepast om gebedstijd correct door te geven
|
||||
2025-05-25 16:22:29 - Docker configuratie aangepast voor betere caching en live updates: Dockerfile en docker-compose.yml bijgewerkt
|
||||
2025-05-25 16:26:20 - Docker en Flask configuratie aangepast: nginx configuratie verwijderd en cache-control headers toegevoegd via Flask
|
||||
2025-05-25 16:26:51 - Configuratie vereenvoudigd voor TrueNAS: Dockerfile, docker-compose.yml en app.py aangepast voor betere bestandspaden en permissies
|
||||
2025-05-25 16:28:49 - Nederlandse hadiths toegevoegd: hadiths.json bestand gemaakt, app.py aangepast voor willekeurige hadith selectie, template en styling bijgewerkt
|
||||
2025-05-25 16:31:42 - Tijd en afteller links vergroot en countdown hoger geplaatst via CSS-update
|
||||
2025-05-25 16:32:44 - Afteltijd en gebedsnaam helemaal onderaan links geplaatst via CSS-update
|
||||
2025-05-25 16:33:25 - CSS aangepast zodat afteller altijd volledig zichtbaar is onderaan, ook op kleinere schermen
|
||||
2025-05-25 16:35:05 - CSS aangepast: .huidig-gebed nu onderaan via flexbox, altijd volledig zichtbaar binnen overlay
|
||||
2025-05-25 16:39:20 - Switch functionaliteit toegevoegd voor hadith/dua en tijden aan de rechterkant, inclusief styling en JS
|
||||
2025-05-25 16:40:13 - Hadith-tekst en bron vergroot en blockquote opmaak vereenvoudigd (geen achtergrond/rand meer)
|
||||
2025-05-25 16:43:08 - Endpoint toegevoegd voor willekeurige hadith en JavaScript toegevoegd om elke 30 seconden een nieuwe hadith te tonen
|
||||
2025-05-25 16:46:19 - Design vernieuwd: islamitische sfeer met warme kleuren, Google Fonts, afgeronde panelen, goudaccenten en moderne typografie
|
||||
2025-05-25 16:47:43 - CSS volledig themavriendelijk gemaakt: dark/light mode werkt nu met alle kleuren en accenten
|
||||
2025-05-25 20:46:58 - Thema aangepast: alle blauw-tinten vervangen door zwart/grijs, nu echt zwart-goud-wit design
|
||||
2025-05-25 20:54:07 - adhan_cron.py script aangemaakt voor automatische adhaan via Sonos met settings.json
|
||||
2025-05-25 20:55:59 - start_all.py toegevoegd en Dockerfile aangepast zodat Flask en adhan_cron samen in één container draaien
|
||||
2025-05-25 21:00:34 - adhan_cron.py uitgebreid: logt nu altijd wanneer de cronjob draait en met welk tijdstip
|
||||
2025-05-25 21:06:27 - adhan_cron.py aangepast: logt nu naar stdout én bestand, zichtbaar in Docker/TrueNAS logs
|
||||
2025-05-25 21:15:35 - Hadith verticaal gecentreerd in het rechterpaneel via CSS (#hadith-dua-pane)
|
||||
2025-05-25 21:21:26 - Layout aangepast: gebedstijden nu verticaal rechts van de hadith, mute en instellingen als verticale icoontjes eronder
|
||||
2025-05-25 21:22:56 - Zijmenu aangepast: alleen tijden zonder seconden, alles in één uniforme verticale lijst met dezelfde stijl
|
||||
2025-05-25 21:23:53 - Gebedsnamen toegevoegd boven de tijden in het zijmenu, alles netjes uitgelijnd in één verticale lijst
|
||||
2025-05-25 21:24:55 - Breedte van tijden-rij vergroot en white-space toegevoegd zodat naam en tijd altijd naast elkaar staan
|
||||
2025-05-25 21:25:39 - Tijden-rij aangepast: gebedsnaam nu boven de tijd, alles gecentreerd en gestapeld
|
||||
2025-05-25 21:28:35 - Material Icons toegevoegd en icoon-stijl aangepast naar modern Android/Google stijl
|
||||
2025-05-25 21:29:10 - Theme-toggle icoon vervangen door Material Icon, wisselt nu automatisch tussen dark_mode en light_mode
|
||||
2025-05-25 21:30:31 - Kleur van theme-icoon, tijden en namen nu afhankelijk van dark/light theme via CSS-variabelen
|
||||
2025-05-25 21:32:38 - Mute functionaliteit toegevoegd: icoon wisselt, status wordt opgeslagen, adhaan wordt niet afgespeeld als mute aan staat
|
||||
2025-05-25 22:00:35 - Sonos-zones nu dynamisch opgehaald via de Sonos-API in instellingenpagina
|
||||
Sun May 25 22:10:59 CEST 2025: Uitgebreide audio opties toegevoegd met preview functie
|
||||
Sun May 25 22:13:01 CEST 2025: Instellingen pagina volledig vernieuwd met moderne UI
|
||||
Sun May 25 22:13:01 CEST 2025: API endpoints toegevoegd voor dynamische updates
|
||||
Sun May 25 22:13:01 CEST 2025: Configuratie bestand (config.py) aangemaakt voor eenvoudige setup
|
||||
Sun May 25 22:13:01 CEST 2025: README.md bijgewerkt met installatie instructies
|
||||
Sun May 25 22:18:12 CEST 2025: Weer-sectie verbeterd - details nu horizontaal naast elkaar met mooie styling
|
||||
Sun May 25 22:20:27 CEST 2025: Islamitische kalender toegevoegd - toont nu zowel Gregoriaanse als Hijri datum in Nederlands en Arabisch
|
||||
Sun May 25 22:25:25 CEST 2025: Debug scherm toegevoegd - tijd manipulatie voor adhaan testing met +/- knoppen en directe gebedstijd triggers
|
||||
Sun May 25 22:34:29 CEST 2025: Sonos herstart probleem opgelost - debounce mechanisme toegevoegd, pause voor clip, betere error handling en Sonos status monitoring
|
||||
2025-05-25 23:59:21 - Quran pagina layout aangepast naar twee-kolommen structuur met speler links en sura lijst rechts
|
||||
2025-05-26 00:01:43 - Quran pagina layout en styling verbeterd om meer op het ontwerp te lijken
|
||||
2025-05-26 00:03:05 - Rechtermenu styling aangepast om Quran theme variabelen te gebruiken voor correcte dark/light mode
|
||||
2025-05-26 15:14:18 - Hadiths toegevoegd over dwanggedachten, obsessieve gedachten en geestelijke rust
|
||||
2025-05-26 20:55:37 - Volume instellingen uitgebreid met dag/avond volumes en tijdsinstellingen
|
||||
2025-05-26 20:58:10 - Settings layout verbeterd: elementen naast elkaar geplaatst voor beter gebruik van schermbreedte
|
||||
2025-05-26 20:59:32 - Settings layout verder verfijnd: betere spacing, uitlijning en responsive design
|
||||
2025-05-26 21:04:05 - Layout compacter gemaakt en scrolling toegevoegd aan settings pagina
|
||||
2025-05-26 21:06:51 - Volume error opgelost in backend - oude volume veld vervangen door volume_day/volume_night
|
||||
BIN
sonos/clips/adhan1.mp3
Normal file
BIN
sonos/clips/adhan1.mp3
Normal file
Binary file not shown.
1
sonos/settings.json
Normal file
1
sonos/settings.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
Loading…
x
Reference in New Issue
Block a user