// Drives "Save for offline": one download at a time, with progress for the button to show, // a cancel, and a toast when it finishes. import { useCallback, useEffect, useRef, useState } from 'react'; import { useToast } from '../components/Toasts.jsx'; import { bibleRefs, bookRefs, downloadChapters, loadSavedBooks, markBookSaved } from '../utils/offline.js'; export function useOnline() { const [online, setOnline] = useState(() => (typeof navigator === 'undefined' ? true : navigator.onLine !== false)); useEffect(() => { const up = () => setOnline(true); const down = () => setOnline(false); window.addEventListener('online', up); window.addEventListener('offline', down); return () => { window.removeEventListener('online', up); window.removeEventListener('offline', down); }; }, []); return online; } export function useOfflineSave() { const { toast } = useToast(); const [saved, setSaved] = useState(loadSavedBooks); const [job, setJob] = useState(null); // { label, done, total } while downloading const controller = useRef(null); useEffect(() => () => controller.current?.abort(), []); const run = useCallback(async (label, translation, refs, { bookKeys = [] } = {}) => { if (controller.current) return; // one at a time const abort = new AbortController(); controller.current = abort; setJob({ label, done: 0, total: refs.length }); const result = await downloadChapters(translation, refs, { signal: abort.signal, onProgress: (done, total) => setJob({ label, done, total }) }); controller.current = null; setJob(null); if (abort.signal.aborted) { toast('Download stopped'); return; } if (result.failed === 0) { bookKeys.forEach((b) => markBookSaved(translation, b)); setSaved(loadSavedBooks()); toast(`Saved ${label} for offline`); } else { toast(`Saved ${result.done} of ${refs.length} chapters — check your connection and try again`, { tone: 'error' }); } }, [toast]); return { saved, job, isSaved: (translation, book) => (saved[translation] ?? []).includes(book), saveBook: (translation, book, name) => run(name, translation, bookRefs(book), { bookKeys: [book] }), saveBible: (translation) => run(`The whole ${translation}`, translation, bibleRefs(), { bookKeys: [...new Set(bibleRefs().map((r) => r.book))] }), saveRefs: (label, translation, refs) => run(label, translation, refs), cancel: () => controller.current?.abort(), }; }