diff --git a/static/app.css b/static/app.css
index b4b64da..969105c 100644
--- a/static/app.css
+++ b/static/app.css
@@ -8,13 +8,14 @@ html, body {
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background-color: #f4f7f9;
- padding: 0;
+ padding-top: 70px; /* Adjust to your header height */
color: #333;
}
/* Header styles */
.site-header {
- position: sticky;
+ position: fixed;
top: 0;
+ width: 94%;
z-index: 1000;
display: flex;
align-items: center;
@@ -52,7 +53,7 @@ body {
/* Breadcrumb Styles */
.breadcrumb {
margin-bottom: 15px;
- font-size: 18px;
+ font-size: 22px;
}
.breadcrumb a {
text-decoration: none;
@@ -64,6 +65,11 @@ body {
margin-right: 5px;
}
+.separator::before {
+ content: "\A"; /* Inserts a line break */
+ white-space: pre; /* Ensures the newline is honored */
+}
+
/* List Styles */
ul {
list-style-type: none;
diff --git a/static/app.js b/static/app.js
index 6c352aa..37a76d9 100644
--- a/static/app.js
+++ b/static/app.js
@@ -22,7 +22,7 @@ function renderContent(data) {
data.breadcrumbs.forEach((crumb, index) => {
breadcrumbHTML += `${crumb.name}`;
if (index < data.breadcrumbs.length - 1) {
- breadcrumbHTML += `>`;
+ breadcrumbHTML += `>`;
}
});
document.getElementById('breadcrumbs').innerHTML = breadcrumbHTML;
@@ -50,6 +50,7 @@ function renderContent(data) {
}
// Render files.
+ currentMusicFiles = []; // Reset the music files array.
if (data.files.length > 0) {
contentHTML += '
';
data.files.forEach((file, idx) => {
@@ -61,7 +62,9 @@ function renderContent(data) {
symbol = '🖼️';
}
const indexAttr = file.file_type === 'music' ? ` data-index="${currentMusicFiles.length - 1}"` : '';
- contentHTML += `-
+ // preserve currently-playing class during reloads
+ const isCurrentlyPlaying = file.file_type === 'music' && currentMusicIndex === currentMusicFiles.length - 1 ? ' currently-playing' : '';
+ contentHTML += `
-
${symbol} ${file.name.replace('.mp3', '')}`;
if (file.has_transcript) {
contentHTML += `📄`;
@@ -125,14 +128,16 @@ function renderContent(data) {
function loadDirectory(subpath) {
const encodedPath = encodeSubpath(subpath);
const apiUrl = '/api/path/' + encodedPath;
- fetch(apiUrl)
+ return fetch(apiUrl)
.then(response => response.json())
.then(data => {
renderContent(data);
+ return data; // return data for further chaining
})
.catch(error => {
console.error('Error loading directory:', error);
- document.getElementById('content').innerHTML = '
Error loading directory!
'+error+'
';
+ document.getElementById('content').innerHTML = `Error loading directory!
${error}
`;
+ throw error; // Propagate the error
});
}
@@ -144,10 +149,10 @@ function preload_audio() {
const nextMediaUrl = '/media/' + nextFile.path;
// Use a HEAD request so that the backend reads and caches the file without returning the full content.
fetch(nextMediaUrl, { method: 'HEAD' })
- .then(response => {
- console.log('Backend diskcache initiated for next file:', nextFile.path);
- })
- .catch(error => console.error('Error initiating backend diskcache for next file:', error));
+ // .then(response => {
+ // console.log('Backend diskcache initiated for next file:', nextFile.path);
+ // })
+ // .catch(error => console.error('Error initiating backend diskcache for next file:', error));
}
}
@@ -340,31 +345,33 @@ window.addEventListener('popstate', function (event) {
let isReloadButtonVisible = true; // Boolean to track the visibility of the reload button.
function reloadDirectory() {
- if (!isReloadButtonVisible) return; // Exit if the reload button is hidden.
+ if (!isReloadButtonVisible) return Promise.resolve();
- // Extract the current path from the URL, similar to your initial load code.
+ // Extract the current path from the URL.
let currentSubpath = '';
if (window.location.pathname.indexOf('/path/') === 0) {
- currentSubpath = window.location.pathname.substring(6); // Remove "/path/"
+ currentSubpath = window.location.pathname.substring(6);
}
- loadDirectory(currentSubpath);
+
+ // Return the promise so that we can chain actions after the directory has reloaded.
+ return loadDirectory(currentSubpath)
+ .then(() => {
+ // Animate the reload button as before.
+ const reloadBtn = document.querySelector('#reload-button');
+ const reloadBtnSVG = document.querySelector('#reload-button svg');
- const reloadBtn = document.querySelector('#reload-button');
- const reloadBtnSVG = document.querySelector('#reload-button svg');
+ void reloadBtnSVG.offsetWidth; // Force reflow to reset the animation
+ reloadBtnSVG.classList.add("rotate");
+ isReloadButtonVisible = false;
- // Force reflow to reset the animation
- void reloadBtnSVG.offsetWidth;
- reloadBtnSVG.classList.add("rotate");
- isReloadButtonVisible = false;
-
- // Gradually fade back in after 10 seconds
- setTimeout(() => {
- reloadBtnSVG.classList.remove("rotate");
- reloadBtn.style.transition = 'opacity 0.5s ease';
- reloadBtn.style.opacity = '1';
- reloadBtn.style.pointer = 'cursor';
- isReloadButtonVisible = true;
- }, 10000);
+ setTimeout(() => {
+ reloadBtnSVG.classList.remove("rotate");
+ reloadBtn.style.transition = 'opacity 0.5s ease';
+ reloadBtn.style.opacity = '1';
+ reloadBtn.style.pointer = 'cursor';
+ isReloadButtonVisible = true;
+ }, 10000);
+ });
}
@@ -404,17 +411,29 @@ if ('mediaSession' in navigator) {
});
}
-// auto-play on audio 'ended' event logic
document.getElementById('globalAudio').addEventListener('ended', () => {
- // Check if there's a next file in the array.
- if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) {
- const nextFile = currentMusicFiles[currentMusicIndex + 1];
- // Find the corresponding play link (or directly trigger playback).
- const nextLink = document.querySelector(`.play-file[data-url="${nextFile.path}"]`);
- if (nextLink) {
- nextLink.click();
+ // Save the current track's path (if any)
+ const currentTrackPath = currentMusicFiles[currentMusicIndex] ? currentMusicFiles[currentMusicIndex].path : null;
+
+ reloadDirectory().then(() => {
+
+ // If we had a track playing, try to find it in the updated list.
+ if (currentTrackPath) {
+ const newIndex = currentMusicFiles.findIndex(file => file.path === currentTrackPath);
+ currentMusicIndex = newIndex;
}
- }
+
+ // Now, if there's a next track, auto-play it.
+ if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) {
+ const nextFile = currentMusicFiles[currentMusicIndex + 1];
+ const nextLink = document.querySelector(`.play-file[data-url="${nextFile.path}"]`);
+ if (nextLink) {
+ nextLink.click();
+ }
+ }
+ }).catch(error => {
+ console.error('Error during reload:', error);
+ });
});
document.addEventListener("DOMContentLoaded", function() {
diff --git a/static/audioplayer.js b/static/audioplayer.js
index 5044457..2e258db 100644
--- a/static/audioplayer.js
+++ b/static/audioplayer.js
@@ -34,36 +34,24 @@ let isSeeking = false;
// --- Slider (Timeline) events ---
// Mouse events
timeline.addEventListener('mousedown', () => { isSeeking = true; });
-timeline.addEventListener('mouseup', () => {
- isSeeking = false;
+timeline.addEventListener('mouseup', () => { isSeeking = false;
changeSeek(); // Update the audio currentTime based on the slider position
});
-timeline.addEventListener('change', changeSeek);
// Touch events
timeline.addEventListener('touchstart', () => { isSeeking = true; });
-timeline.addEventListener('touchend', () => {
- isSeeking = false;
+timeline.addEventListener('touchend', () => { isSeeking = false;
changeSeek();
});
timeline.addEventListener('touchcancel', () => { isSeeking = false; });
// --- When metadata is loaded, set slider range in seconds ---
audio.addEventListener('loadedmetadata', () => {
- updateTimeInfo();
timeline.min = 0;
timeline.max = audio.duration;
- timeline.value = 0; // Ensures the thumb starts at the left.
});
-// --- Update the slider thumb position (in seconds) while playing ---
-function changeTimelinePosition() {
- if (!isSeeking) {
- timeline.value = audio.currentTime;
- const percentagePosition = (audio.currentTime / audio.duration) * 100;
- timeline.style.backgroundSize = `${percentagePosition}% 100%`;
- }
-}
+
// --- Seek function: directly set audio.currentTime using slider's value (in seconds) ---
function changeSeek() {
@@ -77,17 +65,22 @@ function formatTime(seconds) {
return `${minutes < 10 ? '0' : ''}${minutes}:${secs < 10 ? '0' : ''}${secs}`;
}
-// --- Update the time display ---
-function updateTimeInfo() {
- const currentTimeFormatted = formatTime(audio.currentTime);
- const durationFormatted = isNaN(audio.duration) ? "00:00" : formatTime(audio.duration);
- timeInfo.innerHTML = `${currentTimeFormatted} / ${durationFormatted}`;
-}
// --- Update timeline, time info, and media session on each time update ---
audio.ontimeupdate = function() {
- changeTimelinePosition();
- updateTimeInfo();
+
+ // --- Update the slider thumb position (in seconds) while playing ---
+ if (!isSeeking) {
+ timeline.value = audio.currentTime;
+ const percentagePosition = (audio.currentTime / audio.duration) * 100;
+ timeline.style.backgroundSize = `${percentagePosition}% 100%`;
+ }
+
+ // --- Update the time display ---
+ const currentTimeFormatted = formatTime(audio.currentTime);
+ const durationFormatted = isNaN(audio.duration) ? "00:00" : formatTime(audio.duration);
+ timeInfo.innerHTML = `${currentTimeFormatted} / ${durationFormatted}`;
+
if ('mediaSession' in navigator &&
'setPositionState' in navigator.mediaSession &&
!isNaN(audio.duration)) {
@@ -99,30 +92,6 @@ audio.ontimeupdate = function() {
}
};
-// --- Also update media session on play (in case timeupdate lags) ---
-audio.onplay = function() {
- if ('mediaSession' in navigator &&
- typeof navigator.mediaSession.setPositionState === 'function' &&
- !isNaN(audio.duration)) {
- navigator.mediaSession.setPositionState({
- duration: audio.duration,
- playbackRate: audio.playbackRate,
- position: audio.currentTime
- });
- }
-};
-
-// --- Handle external seek requests (e.g. from Android or Windows system controls) ---
-if ('mediaSession' in navigator) {
- navigator.mediaSession.setActionHandler('seekto', function(details) {
- if (details.fastSeek && 'fastSeek' in audio) {
- audio.fastSeek(details.seekTime);
- } else {
- audio.currentTime = details.seekTime;
- }
- changeTimelinePosition();
- });
-}
// --- When audio ends ---
audio.onended = function() {
diff --git a/static/sw.js b/static/sw.js
index 47ae981..cfb6a83 100644
--- a/static/sw.js
+++ b/static/sw.js
@@ -1,12 +1,16 @@
-const cacheName = 'gottesdienste-v1.1';
+const cacheName = 'gottesdienste-v1.2';
const assets = [
'/',
- '/static/styles.css',
- '/static/gallery.css',
+ '/static/app.css',
'/static/app.js',
+ '/static/gallery.css',
'/static/gallery.js',
+ '/static/audioplayer.css',
+ '/static/audioplayer.js',
'/static/icons/logo-192x192.png',
- '/static/icons/logo-512x512.png'
+ '/static/icons/logo-512x512.png',
+ '/static/logo.png',
+ '/static/logoW.png'
];
self.addEventListener('install', e => {
diff --git a/templates/app.html b/templates/app.html
index 44caf98..52ab32d 100644
--- a/templates/app.html
+++ b/templates/app.html
@@ -10,6 +10,8 @@
Gottesdienste
+
+
@@ -45,9 +47,9 @@
@@ -57,7 +59,7 @@