From 623c56a96f54b0cc852e5c52717c3175966c8d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sun, 23 Aug 2026 22:05:58 +0300 Subject: [PATCH] fix: robust first-launch auto-preload (mobile bible/download) - OfflineManager.preload accepts bibleTranslations: 'all' and resolves translation ids internally from versions.json (single source), removing the fragile settings-side fetch that could throw and abort the whole download on mobile first launch - Decouple trigger from '#settings/autopreload' route param via one-shot requestAutoPreload()/consumeAutoPreload() flag - Auto-expand + tick Bible variant checkboxes for UI consistency - Offer modal sets the flag and navigates to plain '#settings' --- src/core/offline-manager.ts | 33 ++++++++++++++++++++++++++++++--- src/ui/app.ts | 4 +++- src/ui/settings-view.ts | 17 ++++++++--------- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/core/offline-manager.ts b/src/core/offline-manager.ts index 17421b0..ebef103 100644 --- a/src/core/offline-manager.ts +++ b/src/core/offline-manager.ts @@ -20,7 +20,7 @@ import { diagLog } from './diag-log'; export interface PreloadOptions { languages: string[]; types: ('calendar' | 'bible')[]; - bibleTranslations?: string[]; + bibleTranslations?: string[] | 'all'; } export interface PreloadProgress { @@ -89,12 +89,36 @@ function getFallbackCalendarFiles(lang: string): string[] { export class OfflineManager { private static _progress: PreloadProgress | null = null; + private static _autoPreloadRequested = false; + + /** Request an automatic "preload everything" once the Settings view opens. */ + static requestAutoPreload() { + OfflineManager._autoPreloadRequested = true; + } + + /** Consume (one-shot) the automatic preload request. */ + static consumeAutoPreload(): boolean { + const v = OfflineManager._autoPreloadRequested; + OfflineManager._autoPreloadRequested = false; + return v; + } /** Get size string for a Bible translation. */ static getBibleSize(translationPath: string): string { return BIBLE_SIZES[translationPath] || '?'; } + /** Resolve every Bible translation id from versions.json (single source). */ + static async getAllBibleTranslationIds(): Promise { + try { + const resp = await fetch('/data/bible/versions.json'); + const versions = await resp.json() as any[]; + return versions.map((v: any) => v.id); + } catch { + return []; + } + } + /** Discover all book files for a Bible translation from versions.json. */ static async getBibleFiles(translationPath: string): Promise { const files: string[] = []; @@ -140,7 +164,10 @@ export class OfflineManager { const uniqueFiles = [...new Set(filesToFetch)]; // Add Bible translation files - for (const trans of options.bibleTranslations || []) { + const bibleTranslations = options.bibleTranslations === 'all' + ? await OfflineManager.getAllBibleTranslationIds() + : (options.bibleTranslations || []); + for (const trans of bibleTranslations) { const bibleFiles = await OfflineManager.getBibleFiles(trans); for (const f of bibleFiles) { if (!uniqueFiles.includes(f)) uniqueFiles.push(f); @@ -162,7 +189,7 @@ export class OfflineManager { diagLog('preload-start', { langs: options.languages.join(','), types: options.types.join(','), - bibles: (options.bibleTranslations || []).length, + bibles: bibleTranslations.length, total: progress.total, manifest: m !== null, }); diff --git a/src/ui/app.ts b/src/ui/app.ts index 5dafd8a..4ff7171 100644 --- a/src/ui/app.ts +++ b/src/ui/app.ts @@ -12,6 +12,7 @@ import { loadSettings, type Settings } from './settings-view'; import { getTranslations, type LanguageCode } from '../core/i18n'; import { DataCache } from '../core/data-cache'; import { diagLog } from '../core/diag-log'; +import { OfflineManager } from '../core/offline-manager'; import { markEverInstalled, hasEverInstalledMarker } from '../core/pwa-install'; type View = 'calendar' | 'bible' | 'prayer' | 'akathists' | 'parimii' | 'horologion' | 'sbornik' | 'paraclete' | 'irmologion' | 'menaion' | 'triodion' | 'lives' | 'settings'; @@ -201,7 +202,8 @@ export class App { document.getElementById('offline-offer-all')?.addEventListener('click', () => { overlay.remove(); - window.location.hash = '#settings/autopreload'; + OfflineManager.requestAutoPreload(); + window.location.hash = '#settings'; }); document.getElementById('offline-offer-choose')?.addEventListener('click', () => { overlay.remove(); diff --git a/src/ui/settings-view.ts b/src/ui/settings-view.ts index f6a1a39..21ac3d1 100644 --- a/src/ui/settings-view.ts +++ b/src/ui/settings-view.ts @@ -672,7 +672,7 @@ export class SettingsView { } // Auto-preload everything if triggered from first-launch offer - if (this.action === 'autopreload') { + if (OfflineManager.consumeAutoPreload() || this.action === 'autopreload') { document.querySelectorAll('.offline-lang').forEach(el => (el as HTMLInputElement).checked = true); document.querySelectorAll('.offline-type').forEach(el => (el as HTMLInputElement).checked = true); const progressDiv = document.getElementById('offline-progress'); @@ -680,20 +680,19 @@ export class SettingsView { const progressText = document.getElementById('offline-progress-text'); if (progressDiv) progressDiv.classList.remove('hidden'); + // Reveal + select all Bible variants for UI consistency + document.getElementById('bible-row')?.dispatchEvent(new Event('click')); + setTimeout(() => { + document.querySelectorAll('.bible-trans').forEach(el => (el as HTMLInputElement).checked = true); + }, 300); + const selectedLangs = LANGUAGES.map(l => l.code); const selectedTypes: ('calendar' | 'bible')[] = ['calendar', 'bible']; - let bibleTrans: string[] = []; - try { - const resp = await fetch('/data/bible/versions.json'); - const versions = await resp.json(); - bibleTrans = versions.map((v: any) => v.id); - } catch {} - OfflineManager.preload({ languages: selectedLangs, types: selectedTypes, - bibleTranslations: bibleTrans, + bibleTranslations: 'all', }).then((prog) => { if (progressBar) progressBar.style.width = '100%'; if (progressText) {