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'
This commit is contained in:
catarrh committed 2026-08-23 22:05:58 +03:00
1 parent 142b95d226
commit 623c56a96f
3 files changed
+41 -13

No files matched your search

+30 -3
View File
@@ -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<string[]> {
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<string[]> {
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,
});
+3 -1
View File
@@ -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();
+8 -9
View File
@@ -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) {