"use client"; /** * /downloads — the reader's device-local offline library. * * Lists everything saved via the header's "Save offline" button and lets them * open (offline-capable) or remove each item. Items link with a plain (a * hard navigation) so the service worker can serve the cached HTML document * when there's no network — a soft client navigation would try the network and * fail. This page also doubles as the SW's offline navigation fallback. */ import { useCallback, useEffect, useState, useSyncExternalStore } from "react"; import { BookOpen, FileText, HardDriveDownload, Mail, Mic, Newspaper, Trash2, Wifi, WifiOff, } from "lucide-react"; import { listOffline, offlineSupported, removeOffline, usageBytes, type OfflineItem, type OfflineType, } from "@/lib/offline"; import { cn } from "@/lib/utils"; const TYPE_META: Record = { verse: { label: "Verse", icon: }, prose: { label: "Chapter", icon: }, letter: { label: "Letter", icon: }, magazine: { label: "Magazine", icon: }, transcript: { label: "Transcript", icon: }, journal: { label: "Journal", icon: }, essay: { label: "Essay", icon: }, document: { label: "Document", icon: }, }; function formatBytes(n: number): string { if (!n || n < 1024) return `${n || 0} B`; const units = ["KB", "MB", "GB"]; let v = n / 1024; let i = 0; while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; } return `${v.toFixed(v >= 10 ? 0 : 1)} ${units[i]}`; } function formatDate(ms: number): string { try { return new Date(ms).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); } catch { return ""; } } // External-store readers for the two client-only, non-React values. Using // useSyncExternalStore (instead of setState-in-an-effect) is SSR-safe and // avoids the "setState synchronously within an effect" cascade warning. function subscribeOnline(cb: () => void) { window.addEventListener("online", cb); window.addEventListener("offline", cb); return () => { window.removeEventListener("online", cb); window.removeEventListener("offline", cb); }; } const noopSubscribe = () => () => {}; export default function DownloadsPage() { const [items, setItems] = useState(null); const [usage, setUsage] = useState(null); // Client-only reads via external stores (SSR snapshot → server-safe default). const online = useSyncExternalStore( subscribeOnline, () => navigator.onLine, () => true, ); const supported = useSyncExternalStore( noopSubscribe, () => offlineSupported(), () => false, ); const refresh = useCallback(async () => { setItems(await listOffline()); setUsage(await usageBytes()); }, []); // Load the library once mounted. The setState calls happen after an await // (asynchronously), not synchronously in the effect body. useEffect(() => { if (!offlineSupported()) return; void refresh(); }, [refresh]); async function remove(key: string) { await removeOffline(key); await refresh(); } return ( <>

Offline library

Pages and lectures you’ve saved to read or listen to without a connection. Stored on this device.

{online ? ( ) : ( )} {online ? "Online" : "Offline"} {usage !== null && ( {formatBytes(usage)} used )}
{!supported ? (

This browser doesn’t support offline saving.

) : items === null ? (

Loading…

) : items.length === 0 ? (

Nothing saved yet. Tap{" "} Save offline{" "} on any chapter, letter, magazine, or transcript to keep it here.

) : (
)}
); }