// Settings → "Your data": download everything as one file, restore from one, and save Bibles for offline. import { useRef, useState } from 'react'; import { useApp } from '../../context/AppContext.js'; import { useToast } from '../Toasts.jsx'; import { DownloadProgress } from '../OfflineSave.jsx'; import { useOfflineSave } from '../../hooks/useOfflineSave.js'; import { backupFileName, buildBackup, markBackedUp, restoreProjects, validateBackup } from '../../utils/backup.js'; import { saveRemoteProject } from '../../syncService.js'; const btn = 'rounded-xl border border-[var(--r-line)] px-4 py-2 text-sm hover:bg-black/5 disabled:opacity-50'; const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`; export default function DataSection() { const { authUser, readerBookmarks, readerBookmarkNotes, readerTextHighlights, readerColorLabels, readerInkByPage, readingPlansState, importReaderMarks, importReadingPlans, refreshProjectIndex, prayers, importPrayers, } = useApp(); const { toast } = useToast(); const offline = useOfflineSave(); const fileRef = useRef(null); const [pending, setPending] = useState(null); // { name, backup, summary } waiting for "Restore" const [error, setError] = useState(''); const [busy, setBusy] = useState(false); const download = () => { const backup = buildBackup({ email: authUser?.email ?? null, reader: { bookmarks: readerBookmarks, bookmarkNotes: readerBookmarkNotes, highlights: readerTextHighlights, colorLabels: readerColorLabels, ink: readerInkByPage }, readingPlans: readingPlansState, prayers, }); const url = URL.createObjectURL(new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' })); const link = document.createElement('a'); link.href = url; link.download = backupFileName(); document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); markBackedUp(); toast(`Backup downloaded: ${plural(backup.projects.length, 'study', 'studies')} and your reading marks`); }; const choose = async (e) => { const file = e.target.files?.[0]; e.target.value = ''; setError(''); setPending(null); if (!file) return; let data; try { data = JSON.parse(await file.text()); } catch { setError('That file couldn’t be read as a backup.'); return; } const checked = validateBackup(data); if (!checked.ok) { setError(checked.error); return; } setPending({ name: file.name, ...checked }); }; const restore = async () => { setBusy(true); const { backup } = pending; const written = restoreProjects(backup.projects); importReaderMarks(backup.reader); importReadingPlans(backup.readingPlans); importPrayers?.(backup.prayers); refreshProjectIndex(); // Studies restored here also go to the account, so they follow you to other devices if (authUser) await Promise.all(written.map((p) => saveRemoteProject(p))); setBusy(false); setPending(null); toast(`Restored ${plural(written.length, 'study', 'studies')}, plus your bookmarks, highlights and plans`); }; const s = pending?.summary; return (

Your data

Everything you’ve made — studies and notes, bookmarks, highlights, ink and reading plans — in one file you keep.{authUser ? ' “Download backup” is what’s on this device; “Download from my account” is everything the server holds. The server also keeps a daily snapshot for the administrator.' : ''}

{authUser && ( markBackedUp()} className={`${btn} inline-block`} title="Everything saved to your account on the server, including studies from other devices">Download from my account )}
{error &&

{error}

} {pending && (

{pending.name}

Contains {plural(s.projects, 'study', 'studies')}, {plural(s.bookmarks, 'bookmark')}, {plural(s.highlights, 'highlight')}, {plural(s.inkPages, 'ink page')}, {plural(s.plans, 'reading plan')} and {plural(s.prayers ?? 0, 'prayer')}. Restoring adds these to what you have — nothing is deleted, and a study on this device is only replaced if the backup’s copy is newer.

)}

Read offline

Chapters you read are kept automatically. To read anywhere, save a whole Bible while you’re connected (about 4 MB). The ESV, NLT, CSB and NASB can’t be saved: their licences don’t allow it.

{offline.job ? : (
{['BSB', 'NET', 'KJV', 'ASV', 'LSV'].map((t) => ( ))}
)}
); }