offline: Infinity TTL, skip cached files, data version, first-launch preload offer, cache stats
This commit is contained in:
1 parent
ea947ede7a
commit
e4ddee924b
7 files changed
+121
-15
No files matched your search
@@ -34,6 +34,8 @@ npm run dev
|
|||||||
|
|
||||||
Data files in `static/data/` are tracked in git. The only gitignored output is `scripts/output/minei.json` (33MB Chetyi-Minei source).
|
Data files in `static/data/` are tracked in git. The only gitignored output is `scripts/output/minei.json` (33MB Chetyi-Minei source).
|
||||||
|
|
||||||
|
**Data version**: `DATA_VERSION` in `src/ui/app.ts` controls cache invalidation. Bump it when restructuring data (new fields, reorganized bundles, updated sources) to force users to re-fetch cached data on next app load.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build # production build
|
npm run build # production build
|
||||||
npm run preview # preview production build
|
npm run preview # preview production build
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ npm run dev
|
|||||||
|
|
||||||
Файлы данных в `static/data/` отслеживаются в git. Единственный игнорируемый файл вывода — `scripts/output/minei.json` (33МБ источник Четьи-Миней).
|
Файлы данных в `static/data/` отслеживаются в git. Единственный игнорируемый файл вывода — `scripts/output/minei.json` (33МБ источник Четьи-Миней).
|
||||||
|
|
||||||
|
**Версия данных**: `DATA_VERSION` в `src/ui/app.ts` управляет инвалидацией кэша. Увеличивайте её при реструктуризации данных (новые поля, перегруппированные пачки, обновлённые источники), чтобы пользователи получили свежие данные при следующей загрузке приложения.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build # продуктивная сборка
|
npm run build # продуктивная сборка
|
||||||
npm run preview # предпросмотр продуктивной сборки
|
npm run preview # предпросмотр продуктивной сборки
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
const DB_NAME = 'ponomar-cache';
|
const DB_NAME = 'ponomar-cache';
|
||||||
const DB_VERSION = 1;
|
const DB_VERSION = 1;
|
||||||
const STORE_NAME = 'data';
|
const STORE_NAME = 'data';
|
||||||
const DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
const DEFAULT_TTL_MS = Infinity; // never expire — static data
|
||||||
|
|
||||||
interface CacheEntry {
|
interface CacheEntry {
|
||||||
key: string;
|
key: string;
|
||||||
|
|||||||
+30
-6
@@ -492,8 +492,14 @@ export interface Translations {
|
|||||||
gregorian: string;
|
gregorian: string;
|
||||||
about: string;
|
about: string;
|
||||||
aboutText: string;
|
aboutText: string;
|
||||||
|
aboutAppName: string;
|
||||||
aboutLicense: string;
|
aboutLicense: string;
|
||||||
installPwa: string;
|
installPwa: string;
|
||||||
|
offlineOfferTitle: string;
|
||||||
|
offlineOfferText: string;
|
||||||
|
offlineOfferPreloadAll: string;
|
||||||
|
offlineOfferChoose: string;
|
||||||
|
offlineOfferSkip: string;
|
||||||
offlineContent: string;
|
offlineContent: string;
|
||||||
offlineLangs: string;
|
offlineLangs: string;
|
||||||
offlineDataTypes: string;
|
offlineDataTypes: string;
|
||||||
@@ -1046,9 +1052,15 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
julian: 'Julian',
|
julian: 'Julian',
|
||||||
gregorian: 'Gregorian',
|
gregorian: 'Gregorian',
|
||||||
about: 'About Ponomar',
|
about: 'About Ponomar',
|
||||||
aboutText: 'Ponomar is an Orthodox Church calendar application providing liturgical information, service texts, and Bible readings in multiple languages.',
|
aboutText: ' is an Orthodox Church calendar application providing liturgical information, service texts, and Bible readings in multiple languages.',
|
||||||
|
aboutAppName: 'Ponomar',
|
||||||
aboutLicense: 'Based on the Ponomar Java desktop application by Aleksandr Andreev and Yuri Shardt. Licensed under GPL v3.',
|
aboutLicense: 'Based on the Ponomar Java desktop application by Aleksandr Andreev and Yuri Shardt. Licensed under GPL v3.',
|
||||||
installPwa: 'Install Ponomar',
|
installPwa: 'Install Ponomar',
|
||||||
|
offlineOfferTitle: 'Offline Data',
|
||||||
|
offlineOfferText: 'If you plan to use the application without internet access, you should preload data for offline usage.',
|
||||||
|
offlineOfferPreloadAll: 'Pre-load everything',
|
||||||
|
offlineOfferChoose: 'Choose data to preload',
|
||||||
|
offlineOfferSkip: 'Don\'t preload',
|
||||||
offlineContent: 'Offline Content',
|
offlineContent: 'Offline Content',
|
||||||
offlineLangs: 'Languages to cache',
|
offlineLangs: 'Languages to cache',
|
||||||
offlineDataTypes: 'Data to cache',
|
offlineDataTypes: 'Data to cache',
|
||||||
@@ -1063,7 +1075,7 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
offlineSelectLang: 'Please select at least one language.',
|
offlineSelectLang: 'Please select at least one language.',
|
||||||
offlineSelectAll: 'Select all',
|
offlineSelectAll: 'Select all',
|
||||||
offlineCleared: 'Cache cleared.',
|
offlineCleared: 'Cache cleared.',
|
||||||
offlineCacheInfo: 'Cache: {0} / {1} ({2} files)',
|
offlineCacheInfo: '{0}, {2} files',
|
||||||
locationTitle: 'Location',
|
locationTitle: 'Location',
|
||||||
latitude: 'Latitude',
|
latitude: 'Latitude',
|
||||||
longitude: 'Longitude',
|
longitude: 'Longitude',
|
||||||
@@ -1599,9 +1611,15 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
julian: 'Юлианский',
|
julian: 'Юлианский',
|
||||||
gregorian: 'Григорианский',
|
gregorian: 'Григорианский',
|
||||||
about: 'О Понома́ре',
|
about: 'О Понома́ре',
|
||||||
aboutText: 'Понома́рь — приложение православного календаря, предоставляющее литургическую информацию, тексты служб и библейские чтения на нескольких языках.',
|
aboutText: ' — приложение православного календаря, предоставляющее литургическую информацию, тексты служб и библейские чтения на нескольких языках.',
|
||||||
|
aboutAppName: 'Пономарь',
|
||||||
aboutLicense: 'Основано на Java-приложении Понома́рь от Александра Андреева и Юрия Шардт. Лицензия GPL v3.',
|
aboutLicense: 'Основано на Java-приложении Понома́рь от Александра Андреева и Юрия Шардт. Лицензия GPL v3.',
|
||||||
installPwa: 'Установить Пономарь',
|
installPwa: 'Установить Пономарь',
|
||||||
|
offlineOfferTitle: 'Офлайн-данные',
|
||||||
|
offlineOfferText: 'Если вы планируете использовать приложение без интернета, предзагрузите данные для офлайн-использования.',
|
||||||
|
offlineOfferPreloadAll: 'Загрузить всё',
|
||||||
|
offlineOfferChoose: 'Выбрать данные',
|
||||||
|
offlineOfferSkip: 'Не загружать',
|
||||||
offlineContent: 'Офлайн-контент',
|
offlineContent: 'Офлайн-контент',
|
||||||
offlineLangs: 'Языки для кэширования',
|
offlineLangs: 'Языки для кэширования',
|
||||||
offlineDataTypes: 'Типы данных',
|
offlineDataTypes: 'Типы данных',
|
||||||
@@ -1616,7 +1634,7 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
offlineSelectLang: 'Пожалуйста, выберите хотя бы один язык.',
|
offlineSelectLang: 'Пожалуйста, выберите хотя бы один язык.',
|
||||||
offlineSelectAll: 'Выбрать все',
|
offlineSelectAll: 'Выбрать все',
|
||||||
offlineCleared: 'Кэш очищен.',
|
offlineCleared: 'Кэш очищен.',
|
||||||
offlineCacheInfo: 'Кэш: {0} / {1} ({2} файлов)',
|
offlineCacheInfo: '{0}, {2} файлов',
|
||||||
locationTitle: 'Местоположение',
|
locationTitle: 'Местоположение',
|
||||||
latitude: 'Широта',
|
latitude: 'Широта',
|
||||||
longitude: 'Долгота',
|
longitude: 'Долгота',
|
||||||
@@ -2152,9 +2170,15 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
julian: 'Юліанскій',
|
julian: 'Юліанскій',
|
||||||
gregorian: 'Григоріанскій',
|
gregorian: 'Григоріанскій',
|
||||||
about: 'О Понома́ри',
|
about: 'О Понома́ри',
|
||||||
aboutText: 'Понома́рь — приложенiе православнаго календаря, предоставляющее литургическiю информацию, тексты службъ и библейскія чтения на нескольких языкахъ.',
|
aboutText: ' — приложенiе православнаго календаря, предоставляющее литургическiю информацию, тексты службъ и библейскія чтения на нескольких языкахъ.',
|
||||||
|
aboutAppName: 'Пономарь',
|
||||||
aboutLicense: 'Основано на Java-приложенiи Понома́рь отъ Александра Андреева и Юрия Шардт. Лицензия GPL v3.',
|
aboutLicense: 'Основано на Java-приложенiи Понома́рь отъ Александра Андреева и Юрия Шардт. Лицензия GPL v3.',
|
||||||
installPwa: 'Оустановити Понома́рь',
|
installPwa: 'Оустановити Понома́рь',
|
||||||
|
offlineOfferTitle: 'Офлайн-данныѧ',
|
||||||
|
offlineOfferText: 'А҆́ще плани́рꙋете и҆спо́льзовати приложе́нїе без̾ и҆нтернета, предзагризите да́нныѧ длѧ офлайн-потребле́нїѧ.',
|
||||||
|
offlineOfferPreloadAll: 'Загризи́ти всѧ̑',
|
||||||
|
offlineOfferChoose: 'И҆збра́ти да́нныѧ',
|
||||||
|
offlineOfferSkip: 'Не загрꙋжа́ти',
|
||||||
offlineContent: 'Офлайн-контентъ',
|
offlineContent: 'Офлайн-контентъ',
|
||||||
offlineLangs: 'Языки для кеширования',
|
offlineLangs: 'Языки для кеширования',
|
||||||
offlineDataTypes: 'Типы данныхъ',
|
offlineDataTypes: 'Типы данныхъ',
|
||||||
@@ -2169,7 +2193,7 @@ const translations: Record<LanguageCode, Translations> = {
|
|||||||
offlineSelectLang: 'Пожалуйста, изберите хотя бы единъ языкъ.',
|
offlineSelectLang: 'Пожалуйста, изберите хотя бы единъ языкъ.',
|
||||||
offlineSelectAll: 'И҆збра́ти всѧ̑',
|
offlineSelectAll: 'И҆збра́ти всѧ̑',
|
||||||
offlineCleared: 'Кешъ очищенъ.',
|
offlineCleared: 'Кешъ очищенъ.',
|
||||||
offlineCacheInfo: 'Кешъ: {0} / {1} ({2} фа́йлѡвъ)',
|
offlineCacheInfo: '{0}, {2} фа́йловъ',
|
||||||
locationTitle: 'Мѣстоположе́нїе',
|
locationTitle: 'Мѣстоположе́нїе',
|
||||||
latitude: 'Широта̀',
|
latitude: 'Широта̀',
|
||||||
longitude: 'Долгота̀',
|
longitude: 'Долгота̀',
|
||||||
|
|||||||
@@ -146,6 +146,10 @@ export class OfflineManager {
|
|||||||
progress.current++;
|
progress.current++;
|
||||||
progress.file = file;
|
progress.file = file;
|
||||||
|
|
||||||
|
// Skip if already cached
|
||||||
|
const cacheKey = `url:${file}`;
|
||||||
|
if (await DataCache.get(cacheKey) !== null) continue;
|
||||||
|
|
||||||
// Use DataCache.fetch to cache the file in IndexedDB
|
// Use DataCache.fetch to cache the file in IndexedDB
|
||||||
// The service worker will also cache it via runtime caching
|
// The service worker will also cache it via runtime caching
|
||||||
try {
|
try {
|
||||||
|
|||||||
+63
-5
@@ -12,13 +12,14 @@ import { loadSettings, type Settings } from './settings-view';
|
|||||||
import { getTranslations, type LanguageCode } from '../core/i18n';
|
import { getTranslations, type LanguageCode } from '../core/i18n';
|
||||||
import { DataCache } from '../core/data-cache';
|
import { DataCache } from '../core/data-cache';
|
||||||
|
|
||||||
type View = 'calendar' | 'bible' | 'prayer' | 'akathists' | 'parimii' | 'horologion' | 'sbornik' | 'paraclete' | 'irmologion' | 'menaion' | 'triodion' | 'settings';
|
type View = 'calendar' | 'bible' | 'prayer' | 'akathists' | 'parimii' | 'horologion' | 'sbornik' | 'paraclete' | 'irmologion' | 'menaion' | 'triodion' | 'lives' | 'settings';
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
private container: HTMLElement;
|
private container: HTMLElement;
|
||||||
private currentView: View = 'calendar';
|
private currentView: View = 'calendar';
|
||||||
private currentDate: JDate;
|
private currentDate: JDate;
|
||||||
private settings: Settings;
|
private settings: Settings;
|
||||||
|
private routeParam: string = '';
|
||||||
|
|
||||||
constructor(container: HTMLElement) {
|
constructor(container: HTMLElement) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
@@ -36,12 +37,28 @@ export class App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async checkDataVersion() {
|
||||||
|
const DATA_VERSION = '2026-08-04'; // bump when data structure changes
|
||||||
|
try {
|
||||||
|
const cachedVer = await DataCache.get('__data_version__') as string | null;
|
||||||
|
if (cachedVer !== DATA_VERSION) {
|
||||||
|
await DataCache.clear();
|
||||||
|
await DataCache.set('__data_version__', DATA_VERSION);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Silently fail — cache is best-effort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.container.innerHTML = '';
|
this.container.innerHTML = '';
|
||||||
this.render();
|
this.render();
|
||||||
this.handleRoute();
|
this.handleRoute();
|
||||||
window.addEventListener('hashchange', () => this.handleRoute());
|
window.addEventListener('hashchange', () => this.handleRoute());
|
||||||
|
|
||||||
|
// Data version check — bump DATA_VERSION when static data structure changes
|
||||||
|
this.checkDataVersion();
|
||||||
|
|
||||||
// Close dropdown menus on click/touch outside
|
// Close dropdown menus on click/touch outside
|
||||||
const closeOpenDropdowns = (e: Event) => {
|
const closeOpenDropdowns = (e: Event) => {
|
||||||
const target = e.target as Node;
|
const target = e.target as Node;
|
||||||
@@ -71,6 +88,18 @@ export class App {
|
|||||||
(window as any).__deferredPrompt = e;
|
(window as any).__deferredPrompt = e;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear first-launch flag on re-install
|
||||||
|
window.addEventListener('appinstalled', () => {
|
||||||
|
localStorage.removeItem('ponomar-installed');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show offline offer on first standalone launch
|
||||||
|
const isStandalone = () => window.matchMedia('(display-mode: standalone)').matches || (window.navigator as any).standalone;
|
||||||
|
if (isStandalone() && !localStorage.getItem('ponomar-installed')) {
|
||||||
|
localStorage.setItem('ponomar-installed', '1');
|
||||||
|
this.showOfflineOfferModal();
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-preload essential data for the current language
|
// Auto-preload essential data for the current language
|
||||||
this.autoPreload();
|
this.autoPreload();
|
||||||
}
|
}
|
||||||
@@ -82,13 +111,10 @@ export class App {
|
|||||||
const lang = this.settings.language as LanguageCode;
|
const lang = this.settings.language as LanguageCode;
|
||||||
const languages = lang === 'ru' ? ['ru', 'cu'] : [lang];
|
const languages = lang === 'ru' ? ['ru', 'cu'] : [lang];
|
||||||
for (const l of languages) {
|
for (const l of languages) {
|
||||||
// Preload menaion bundle
|
|
||||||
await DataCache.fetch(`/data/${l}/menaion-bundle.json`).catch(() => {});
|
await DataCache.fetch(`/data/${l}/menaion-bundle.json`).catch(() => {});
|
||||||
// Preload current month lives
|
|
||||||
const mm = String(JDate.today().getMonth()).padStart(2, '0');
|
const mm = String(JDate.today().getMonth()).padStart(2, '0');
|
||||||
await DataCache.fetch(`/data/${l}/lives/${mm}.json`).catch(() => {});
|
await DataCache.fetch(`/data/${l}/lives/${mm}.json`).catch(() => {});
|
||||||
}
|
}
|
||||||
// Preload calendar data
|
|
||||||
await DataCache.fetch('/data/shared/fasting.json').catch(() => {});
|
await DataCache.fetch('/data/shared/fasting.json').catch(() => {});
|
||||||
localStorage.setItem(key, '1');
|
localStorage.setItem(key, '1');
|
||||||
} catch {
|
} catch {
|
||||||
@@ -96,10 +122,42 @@ export class App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private showOfflineOfferModal() {
|
||||||
|
const t = getTranslations(this.settings.language as LanguageCode);
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.id = 'offline-offer-overlay';
|
||||||
|
overlay.className = 'fixed inset-0 z-50 flex items-center justify-center bg-black/50';
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div class="bg-surface rounded-xl shadow-2xl p-6 max-w-md mx-4">
|
||||||
|
<h3 class="text-lg font-bold text-navy mb-2">${t.settings.offlineOfferTitle}</h3>
|
||||||
|
<p class="text-sm text-navy mb-4">${t.settings.offlineOfferText}</p>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<button id="offline-offer-all" class="w-full bg-navy text-parchment rounded-lg px-4 py-2 font-bold hover:bg-navy-light transition-colors">${t.settings.offlineOfferPreloadAll}</button>
|
||||||
|
<button id="offline-offer-choose" class="w-full bg-gold text-navy rounded-lg px-4 py-2 font-bold hover:bg-gold-light transition-colors">${t.settings.offlineOfferChoose}</button>
|
||||||
|
<button id="offline-offer-skip" class="w-full text-sm text-navy-light underline hover:text-navy text-center cursor-pointer">${t.settings.offlineOfferSkip}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
document.getElementById('offline-offer-all')?.addEventListener('click', () => {
|
||||||
|
overlay.remove();
|
||||||
|
window.location.hash = '#settings/autopreload';
|
||||||
|
});
|
||||||
|
document.getElementById('offline-offer-choose')?.addEventListener('click', () => {
|
||||||
|
overlay.remove();
|
||||||
|
window.location.hash = '#settings';
|
||||||
|
});
|
||||||
|
document.getElementById('offline-offer-skip')?.addEventListener('click', () => {
|
||||||
|
overlay.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private handleRoute() {
|
private handleRoute() {
|
||||||
const hash = window.location.hash.slice(1) || 'calendar';
|
const hash = window.location.hash.slice(1) || 'calendar';
|
||||||
const parts = hash.split('/');
|
const parts = hash.split('/');
|
||||||
this.currentView = parts[0] as View || 'calendar';
|
this.currentView = parts[0] as View || 'calendar';
|
||||||
|
this.routeParam = parts[1] || '';
|
||||||
this.renderView();
|
this.renderView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +292,7 @@ export class App {
|
|||||||
new SettingsView(viewContainer, () => {
|
new SettingsView(viewContainer, () => {
|
||||||
this.render();
|
this.render();
|
||||||
this.renderView();
|
this.renderView();
|
||||||
}).render();
|
}, this.routeParam).render();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-3
@@ -108,11 +108,13 @@ export class SettingsView {
|
|||||||
private container: HTMLElement;
|
private container: HTMLElement;
|
||||||
private settings: Settings;
|
private settings: Settings;
|
||||||
private onLanguageChange?: () => void;
|
private onLanguageChange?: () => void;
|
||||||
|
private action?: string;
|
||||||
|
|
||||||
constructor(container: HTMLElement, onLanguageChange?: () => void) {
|
constructor(container: HTMLElement, onLanguageChange?: () => void, action?: string) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.settings = loadSettings();
|
this.settings = loadSettings();
|
||||||
this.onLanguageChange = onLanguageChange;
|
this.onLanguageChange = onLanguageChange;
|
||||||
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -336,7 +338,7 @@ export class SettingsView {
|
|||||||
<div class="mt-8 pt-6 border-t border-gold/20">
|
<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>
|
<h3 class="text-lg font-bold text-red mb-2">${t.settings.about}</h3>
|
||||||
<p class="text-sm text-navy-light">
|
<p class="text-sm text-navy-light">
|
||||||
${t.settings.aboutText}
|
<a href="https://github.com/anttro/ponomar-pwa" target="_blank" rel="noopener" class="underline hover:text-gold">${t.settings.aboutAppName}</a>${t.settings.aboutText}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-navy-light mt-2">
|
<p class="text-xs text-navy-light mt-2">
|
||||||
<a href="https://github.com/typiconman/ponomar" target="_blank" rel="noopener" class="underline hover:text-gold">
|
<a href="https://github.com/typiconman/ponomar" target="_blank" rel="noopener" class="underline hover:text-gold">
|
||||||
@@ -367,6 +369,7 @@ export class SettingsView {
|
|||||||
this.settings.fontSize = parseInt((e.target as HTMLInputElement).value, 10);
|
this.settings.fontSize = parseInt((e.target as HTMLInputElement).value, 10);
|
||||||
document.getElementById('font-size-value')!.textContent = `${this.settings.fontSize}px`;
|
document.getElementById('font-size-value')!.textContent = `${this.settings.fontSize}px`;
|
||||||
document.documentElement.style.setProperty('--liturgical-font-size', this.settings.fontSize + 'px');
|
document.documentElement.style.setProperty('--liturgical-font-size', this.settings.fontSize + 'px');
|
||||||
|
this.updateFontPreview();
|
||||||
saveSettings(this.settings);
|
saveSettings(this.settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -533,7 +536,6 @@ export class SettingsView {
|
|||||||
if (statsEl) {
|
if (statsEl) {
|
||||||
statsEl.textContent = t.settings.offlineCacheInfo
|
statsEl.textContent = t.settings.offlineCacheInfo
|
||||||
.replace('{0}', OfflineManager.formatBytes(stats.usageBytes))
|
.replace('{0}', OfflineManager.formatBytes(stats.usageBytes))
|
||||||
.replace('{1}', OfflineManager.formatBytes(stats.quotaBytes))
|
|
||||||
.replace('{2}', String(stats.cachedFiles));
|
.replace('{2}', String(stats.cachedFiles));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -622,6 +624,19 @@ export class SettingsView {
|
|||||||
if (ruChecked && cuCheckbox) {
|
if (ruChecked && cuCheckbox) {
|
||||||
cuCheckbox.checked = true;
|
cuCheckbox.checked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-preload everything if triggered from first-launch offer
|
||||||
|
if (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 bibleChevron = document.getElementById('bible-chevron');
|
||||||
|
if (bibleChevron) bibleChevron.click();
|
||||||
|
setTimeout(() => {
|
||||||
|
document.querySelectorAll('.bible-trans').forEach(el => (el as HTMLInputElement).checked = true);
|
||||||
|
document.getElementById('offline-preload')?.click();
|
||||||
|
}, 300);
|
||||||
|
window.history.replaceState(null, '', '#settings');
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
this.updateFontPreview();
|
this.updateFontPreview();
|
||||||
@@ -631,6 +646,7 @@ export class SettingsView {
|
|||||||
const preview = document.getElementById('font-preview');
|
const preview = document.getElementById('font-preview');
|
||||||
if (preview) {
|
if (preview) {
|
||||||
preview.className = `${fontClass(this.settings.cuFont)} text-lg`;
|
preview.className = `${fontClass(this.settings.cuFont)} text-lg`;
|
||||||
|
preview.style.fontSize = `${this.settings.fontSize}px`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user