Books: offline UX — Banner, Auto-Tab, klare Fehlermeldung
- Bei Offline-Start oder Wechsel in Flugzeugmodus: roter Banner unterhalb der Navbar, automatischer Wechsel zum Books-Tab (einziger sinnvoller Tab offline), Upload-Zone wird ausgeblendet - Bei Rückkehr online: Banner verschwindet, Upload-Zone erscheint wieder, Buchliste wird neu synchronisiert - openBook unterscheidet jetzt zwischen "nicht gecacht" (Toast-Hinweis zum einmaligen Online-Öffnen) und echtem Defekt (markBookBroken wie bisher) — kein stilles Scheitern mehr Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a96591c63f
commit
aa562f8b4d
2 changed files with 61 additions and 3 deletions
|
|
@ -1486,6 +1486,20 @@ body.dnd-mode .timer-display {
|
||||||
padding: 4px 0 8px;
|
padding: 4px 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.offline-banner {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--nav-h);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
z-index: 250;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.book-key-bar {
|
.book-key-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -1039,6 +1039,25 @@ function editNotes(pk, current) {
|
||||||
const TOP_TABS = ['radio', 'focus', 'podcasts', 'books'];
|
const TOP_TABS = ['radio', 'focus', 'podcasts', 'books'];
|
||||||
const RADIO_SUB_TABS = ['search', 'saved', 'history'];
|
const RADIO_SUB_TABS = ['search', 'saved', 'history'];
|
||||||
|
|
||||||
|
function showOfflineBanner(offline) {
|
||||||
|
let banner = $('offline-banner');
|
||||||
|
if (offline) {
|
||||||
|
if (!banner) {
|
||||||
|
banner = document.createElement('div');
|
||||||
|
banner.id = 'offline-banner';
|
||||||
|
banner.className = 'offline-banner';
|
||||||
|
banner.textContent = 'Offline — nur gecachte Bücher verfügbar';
|
||||||
|
document.body.prepend(banner);
|
||||||
|
}
|
||||||
|
const uploadArea = $('book-upload-area');
|
||||||
|
if (uploadArea) uploadArea.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
if (banner) banner.remove();
|
||||||
|
const uploadArea = $('book-upload-area');
|
||||||
|
if (uploadArea) uploadArea.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showTab(name) {
|
function showTab(name) {
|
||||||
TOP_TABS.forEach(p => {
|
TOP_TABS.forEach(p => {
|
||||||
const panel = $(`tab-${p}`);
|
const panel = $(`tab-${p}`);
|
||||||
|
|
@ -3380,7 +3399,19 @@ async function openBook(bookId) {
|
||||||
if (cached) {
|
if (cached) {
|
||||||
({data_ct, data_iv} = cached);
|
({data_ct, data_iv} = cached);
|
||||||
} else {
|
} else {
|
||||||
const res = await fetch(`/books/${bookId}/data/`);
|
let res;
|
||||||
|
try {
|
||||||
|
res = await fetch(`/books/${bookId}/data/`);
|
||||||
|
} catch {
|
||||||
|
// offline and book not cached yet
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'reader-toast';
|
||||||
|
toast.textContent = 'Buch nicht offline verfügbar — einmal online öffnen zum Cachen.';
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
setTimeout(() => toast.remove(), 3500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
({data_ct, data_iv} = await res.json());
|
({data_ct, data_iv} = await res.json());
|
||||||
_setCachedBook(bookId, data_ct, data_iv); // fire-and-forget
|
_setCachedBook(bookId, data_ct, data_iv); // fire-and-forget
|
||||||
}
|
}
|
||||||
|
|
@ -5053,12 +5084,25 @@ function openRadioSidebar() {
|
||||||
// Init book drop zone
|
// Init book drop zone
|
||||||
initBookDropZone();
|
initBookDropZone();
|
||||||
|
|
||||||
// Restore last active tab
|
// Restore last active tab — when offline, always go to books (most useful)
|
||||||
const savedTab = localStorage.getItem('diora_active_tab') || 'radio';
|
const savedTab = localStorage.getItem('diora_active_tab') || 'radio';
|
||||||
const savedRadioTab = localStorage.getItem('diora_active_radio_tab') || 'saved';
|
const savedRadioTab = localStorage.getItem('diora_active_radio_tab') || 'saved';
|
||||||
showTab(savedTab);
|
showTab(!navigator.onLine && IS_AUTHENTICATED ? 'books' : savedTab);
|
||||||
showRadioTab(savedRadioTab);
|
showRadioTab(savedRadioTab);
|
||||||
|
|
||||||
|
// React to connectivity changes
|
||||||
|
window.addEventListener('offline', () => {
|
||||||
|
showOfflineBanner(true);
|
||||||
|
if (IS_AUTHENTICATED) showTab('books');
|
||||||
|
});
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
showOfflineBanner(false);
|
||||||
|
loadBookList(); // sync any queued changes
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show banner immediately if already offline at startup
|
||||||
|
if (!navigator.onLine) showOfflineBanner(true);
|
||||||
|
|
||||||
// Hourly background feed refresh (only when authenticated)
|
// Hourly background feed refresh (only when authenticated)
|
||||||
if (IS_AUTHENTICATED) {
|
if (IS_AUTHENTICATED) {
|
||||||
setInterval(refreshAllFeeds, 60 * 60 * 1000);
|
setInterval(refreshAllFeeds, 60 * 60 * 1000);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue