offline manager + settings UI for preloading data

This commit is contained in:
catarrh committed 2026-08-01 21:43:18 +03:00
1 parent 8b27bd4767
commit 465e2664cc
2 files changed
+311

No files matched your search

+130
View File
@@ -3,6 +3,7 @@
*/
import { getTranslations, type LanguageCode } from '../core/i18n';
import { OfflineManager } from '../core/offline-manager';
const LANGUAGES = [
{ code: 'en', name: 'English', local: 'English' },
@@ -235,6 +236,64 @@ export class SettingsView {
</button>
</div>
<!-- Offline Content -->
<div class="mb-6">
<h3 class="text-lg font-bold text-red mb-3">Offline Content</h3>
<!-- Language selection for offline -->
<div class="mb-3">
<label class="block text-sm font-bold text-navy mb-2">Languages to cache</label>
<div id="offline-langs" class="flex flex-wrap gap-3">
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" value="ru" class="offline-lang accent-gold"> <span class="text-sm">Русский</span>
</label>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" value="cu" class="offline-lang accent-gold"> <span class="text-sm">Церковнославянский</span>
</label>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" value="en" class="offline-lang accent-gold"> <span class="text-sm">English</span>
</label>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" value="el" class="offline-lang accent-gold"> <span class="text-sm">Ελληνικά</span>
</label>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" value="fr" class="offline-lang accent-gold"> <span class="text-sm">Français</span>
</label>
</div>
</div>
<!-- Data type selection -->
<div class="mb-3">
<label class="block text-sm font-bold text-navy mb-2">Data to cache</label>
<div id="offline-types" class="flex flex-wrap gap-3">
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" value="lives" checked class="offline-type accent-gold"> <span class="text-sm">Lives</span>
</label>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" value="calendar" checked class="offline-type accent-gold"> <span class="text-sm">Calendar</span>
</label>
<label class="flex items-center gap-1 cursor-pointer">
<input type="checkbox" value="menaion" class="offline-type accent-gold"> <span class="text-sm">Menaion</span>
</label>
</div>
</div>
<!-- Storage info and controls -->
<div class="flex items-center justify-between gap-3 mb-2">
<span id="offline-stats" class="text-sm text-navy-light">Calculating storage...</span>
<div class="flex gap-2">
<button id="offline-preload" class="bg-gold text-navy rounded px-3 py-1 text-sm font-bold hover:bg-gold-dark transition-colors">Preload</button>
<button id="offline-clear" class="bg-red text-parchment rounded px-3 py-1 text-sm hover:bg-red-dark transition-colors">Clear cache</button>
</div>
</div>
<div id="offline-progress" class="hidden">
<div class="w-full bg-parchment-dark rounded-full h-2 mb-1">
<div id="offline-progress-bar" class="bg-gold h-2 rounded-full" style="width: 0%"></div>
</div>
<p id="offline-progress-text" class="text-xs text-navy-light">0 / 0 files</p>
</div>
</div>
<!-- About -->
<div class="mt-8 pt-6 border-t border-gold/20">
<h3 class="text-lg font-bold text-red mb-2">${t.settings.about}</h3>
@@ -303,6 +362,77 @@ export class SettingsView {
document.getElementById('install-pwa')?.remove();
}
});
// Offline content - auto-select ru+cu when ru is selected
document.querySelectorAll('.offline-lang').forEach(el => {
el.addEventListener('change', () => {
const ruChecked = (document.querySelector('.offline-lang[value="ru"]') as HTMLInputElement)?.checked;
const cuCheckbox = document.querySelector('.offline-lang[value="cu"]') as HTMLInputElement;
if (ruChecked && cuCheckbox) {
cuCheckbox.checked = true;
}
});
});
// Preload button
document.getElementById('offline-preload')?.addEventListener('click', async () => {
const selectedLangs = Array.from(document.querySelectorAll('.offline-lang:checked'))
.map(el => (el as HTMLInputElement).value);
const selectedTypes = Array.from(document.querySelectorAll('.offline-type:checked'))
.map(el => (el as HTMLInputElement).value) as ('lives' | 'bible' | 'calendar' | 'menaion')[];
if (selectedLangs.length === 0) {
const progressText = document.getElementById('offline-progress-text');
if (progressText) progressText.textContent = 'Please select at least one language.';
return;
}
const progressDiv = document.getElementById('offline-progress');
const progressBar = document.getElementById('offline-progress-bar');
const progressText = document.getElementById('offline-progress-text');
if (progressDiv) progressDiv.classList.remove('hidden');
// Start preloading in background
OfflineManager.preload({ languages: selectedLangs, types: selectedTypes }).then(() => {
if (progressBar) progressBar.style.width = '100%';
if (progressText) progressText.textContent = 'Done! Data cached for offline use.';
OfflineManager.getStats().then(stats => {
const statsEl = document.getElementById('offline-stats');
if (statsEl) {
statsEl.textContent = `Cache: ${OfflineManager.formatBytes(stats.usageBytes)} / ${OfflineManager.formatBytes(stats.quotaBytes)} (${stats.cachedFiles} files)`;
}
});
});
// Poll progress
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} files — ${prog.file.substring(0, 60)}...`;
}
if (!prog || prog.done) clearInterval(pollInterval);
}, 200);
});
// Clear cache button
document.getElementById('offline-clear')?.addEventListener('click', async () => {
await OfflineManager.clearCache();
const statsEl = document.getElementById('offline-stats');
if (statsEl) statsEl.textContent = 'Cache cleared.';
const progressDiv = document.getElementById('offline-progress');
if (progressDiv) progressDiv.classList.add('hidden');
});
// Show initial storage stats
OfflineManager.getStats().then(stats => {
const statsEl = document.getElementById('offline-stats');
if (statsEl) {
statsEl.textContent = `Cache: ${OfflineManager.formatBytes(stats.usageBytes)} / ${OfflineManager.formatBytes(stats.quotaBytes)} (${stats.cachedFiles} files)`;
}
});
this.updateFontPreview();
}