fix: IDB resilience, direct autopreload, nav delegation, Bible bounds
This commit is contained in:
1 parent
c10c219736
commit
ea09993df9
5 files changed
+82
-13
No files matched your search
+13
-2
@@ -39,8 +39,19 @@ function getDB(): Promise<IDBDatabase> {
|
|||||||
store.createIndex('storedAt', 'storedAt', { unique: false });
|
store.createIndex('storedAt', 'storedAt', { unique: false });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
request.onsuccess = () => resolve(request.result);
|
request.onsuccess = () => {
|
||||||
request.onerror = () => reject(request.error);
|
const db = request.result;
|
||||||
|
db.onclose = () => { dbPromise = null; };
|
||||||
|
db.onversionchange = () => {
|
||||||
|
db.close();
|
||||||
|
dbPromise = null;
|
||||||
|
};
|
||||||
|
resolve(db);
|
||||||
|
};
|
||||||
|
request.onerror = () => {
|
||||||
|
dbPromise = null;
|
||||||
|
reject(request.error);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return dbPromise;
|
return dbPromise;
|
||||||
|
|||||||
@@ -64,6 +64,19 @@ function loadManifest(): Promise<Record<string, Record<string, string[]>> | null
|
|||||||
return manifestPromise;
|
return manifestPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFallbackCalendarFiles(lang: string): string[] {
|
||||||
|
const files = [
|
||||||
|
`/data/${lang}/menaion-bundle.json`,
|
||||||
|
'/data/shared/fasting.json',
|
||||||
|
'/data/shared/menaion-daily/index.json',
|
||||||
|
];
|
||||||
|
for (let m = 1; m <= 12; m++) {
|
||||||
|
const mm = String(m).padStart(2, '0');
|
||||||
|
files.push(`/data/${lang}/lives/${mm}.json`);
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
export class OfflineManager {
|
export class OfflineManager {
|
||||||
private static _progress: PreloadProgress | null = null;
|
private static _progress: PreloadProgress | null = null;
|
||||||
|
|
||||||
@@ -104,7 +117,7 @@ export class OfflineManager {
|
|||||||
for (const lang of options.languages) {
|
for (const lang of options.languages) {
|
||||||
for (const type of options.types) {
|
for (const type of options.types) {
|
||||||
if (type === 'calendar') {
|
if (type === 'calendar') {
|
||||||
const list = m?.[lang]?.calendar ?? [];
|
const list = m?.[lang]?.calendar ?? getFallbackCalendarFiles(lang);
|
||||||
filesToFetch.push(...list);
|
filesToFetch.push(...list);
|
||||||
} else if (type === 'bible') {
|
} else if (type === 'bible') {
|
||||||
filesToFetch.push('/data/bible/versions.json');
|
filesToFetch.push('/data/bible/versions.json');
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ export class App {
|
|||||||
document.addEventListener('touchstart', closeOpenDropdowns, { passive: true });
|
document.addEventListener('touchstart', closeOpenDropdowns, { passive: true });
|
||||||
|
|
||||||
// Close library dropdown when a nav link inside it is clicked
|
// Close library dropdown when a nav link inside it is clicked
|
||||||
document.querySelector('nav')?.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
const link = target.closest('.nav-link');
|
const link = target.closest('.nav-link');
|
||||||
if (link) {
|
if (link) {
|
||||||
|
|||||||
@@ -236,6 +236,9 @@ export class BibleView {
|
|||||||
const select = document.getElementById('book-select') as HTMLSelectElement;
|
const select = document.getElementById('book-select') as HTMLSelectElement;
|
||||||
if (!select) return;
|
if (!select) return;
|
||||||
const books = this.getBooksForVersion();
|
const books = this.getBooksForVersion();
|
||||||
|
if (!this.currentBook || !books.some(b => b.id === this.currentBook)) {
|
||||||
|
this.currentBook = books[0]?.id || 'Gen';
|
||||||
|
}
|
||||||
select.innerHTML = books.map(b =>
|
select.innerHTML = books.map(b =>
|
||||||
`<option value="${b.id}" ${b.id === this.currentBook ? 'selected' : ''}>${b.name}</option>`
|
`<option value="${b.id}" ${b.id === this.currentBook ? 'selected' : ''}>${b.name}</option>`
|
||||||
).join('');
|
).join('');
|
||||||
@@ -247,12 +250,14 @@ export class BibleView {
|
|||||||
if (!select) return;
|
if (!select) return;
|
||||||
const books = this.getBooksForVersion();
|
const books = this.getBooksForVersion();
|
||||||
const book = books.find(b => b.id === this.currentBook);
|
const book = books.find(b => b.id === this.currentBook);
|
||||||
const chapters = book?.chapters ?? 50;
|
const chapters = (book && Number.isInteger(book.chapters) && book.chapters > 0) ? book.chapters : 50;
|
||||||
|
const parsedCh = parseInt(this.currentPassage, 10);
|
||||||
|
const selectedCh = isNaN(parsedCh) || parsedCh < 1 ? 1 : parsedCh;
|
||||||
select.innerHTML = Array.from({ length: chapters }, (_, i) =>
|
select.innerHTML = Array.from({ length: chapters }, (_, i) =>
|
||||||
`<option value="${i + 1}" ${i + 1 === parseInt(this.currentPassage, 10) ? 'selected' : ''}>${i + 1}</option>`
|
`<option value="${i + 1}" ${i + 1 === selectedCh ? 'selected' : ''}>${i + 1}</option>`
|
||||||
).join('');
|
).join('');
|
||||||
// Sync passage to selected chapter
|
// Sync passage to selected chapter
|
||||||
this.currentPassage = select.value;
|
this.currentPassage = select.value || '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadReading() {
|
private async loadReading() {
|
||||||
|
|||||||
+46
-6
@@ -636,12 +636,52 @@ export class SettingsView {
|
|||||||
if (this.action === 'autopreload') {
|
if (this.action === 'autopreload') {
|
||||||
document.querySelectorAll('.offline-lang').forEach(el => (el as HTMLInputElement).checked = true);
|
document.querySelectorAll('.offline-lang').forEach(el => (el as HTMLInputElement).checked = true);
|
||||||
document.querySelectorAll('.offline-type').forEach(el => (el as HTMLInputElement).checked = true);
|
document.querySelectorAll('.offline-type').forEach(el => (el as HTMLInputElement).checked = true);
|
||||||
const bibleChevron = document.getElementById('bible-chevron');
|
const progressDiv = document.getElementById('offline-progress');
|
||||||
if (bibleChevron) bibleChevron.click();
|
const progressBar = document.getElementById('offline-progress-bar');
|
||||||
setTimeout(() => {
|
const progressText = document.getElementById('offline-progress-text');
|
||||||
document.querySelectorAll('.bible-trans').forEach(el => (el as HTMLInputElement).checked = true);
|
if (progressDiv) progressDiv.classList.remove('hidden');
|
||||||
document.getElementById('offline-preload')?.click();
|
|
||||||
}, 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,
|
||||||
|
}).then((prog) => {
|
||||||
|
if (progressBar) progressBar.style.width = '100%';
|
||||||
|
if (progressText) {
|
||||||
|
progressText.textContent = prog.failed > 0
|
||||||
|
? `${t.settings.offlineDone} ${prog.failed} ${t.settings.offlineFailed}`
|
||||||
|
: t.settings.offlineDone;
|
||||||
|
}
|
||||||
|
OfflineManager.getStats().then(stats => {
|
||||||
|
const statsEl = document.getElementById('offline-stats');
|
||||||
|
if (statsEl) {
|
||||||
|
statsEl.textContent = t.settings.offlineCacheInfo
|
||||||
|
.replace('{0}', OfflineManager.formatBytes(stats.usageBytes))
|
||||||
|
.replace('{2}', String(stats.cachedFiles));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const pollInterval = setInterval(() => {
|
||||||
|
const prog = OfflineManager.getProgress();
|
||||||
|
if (prog && prog.total > 0) {
|
||||||
|
const pct = Math.round((prog.current / prog.total) * 100);
|
||||||
|
if (progressBar) progressBar.style.width = `${pct}%`;
|
||||||
|
if (progressText) progressText.textContent = `${prog.current} / ${prog.total} ${t.settings.offlineDataTypes} — ${prog.file.substring(0, 60)}...`;
|
||||||
|
}
|
||||||
|
if (!prog || prog.done) clearInterval(pollInterval);
|
||||||
|
}, 200);
|
||||||
|
|
||||||
window.history.replaceState(null, '', '#settings');
|
window.history.replaceState(null, '', '#settings');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in new issue
Block a user