support caching map data when map_data_fetch_proxy is set true

This commit is contained in:
MaysWind 2026-02-28 18:16:34 +08:00
parent d5dfdc8c05
commit 247181830c
35 changed files with 740 additions and 179 deletions

View file

@ -34,6 +34,7 @@ import { ThemeType } from '@/core/theme.ts';
import { isProduction } from '@/lib/version.ts';
import { initMapProvider } from '@/lib/map/index.ts';
import { isUserLogined, isUserUnlocked } from '@/lib/userstate.ts';
import { updateMapCacheExpiration } from '@/lib/cache.ts';
import { getSystemTheme, setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
const { tt, getCurrentLanguageInfo, setLanguage, initLocale } = useI18n();
@ -77,6 +78,16 @@ onMounted(() => {
const languageInfo = getCurrentLanguageInfo();
initMapProvider(languageInfo?.alternativeLanguageTag);
});
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('controllerchange', () => {
updateMapCacheExpiration(settingsStore.appSettings.mapCacheExpiration);
});
if (navigator.serviceWorker.controller) {
updateMapCacheExpiration(settingsStore.appSettings.mapCacheExpiration);
}
}
});
watch(currentNotificationContent, (newValue) => {

View file

@ -26,6 +26,7 @@ import { isProduction } from '@/lib/version.ts';
import { getTheme, isEnableSwipeBack, isEnableAnimate } from '@/lib/settings.ts';
import { initMapProvider } from '@/lib/map/index.ts';
import { isUserLogined, isUserUnlocked } from '@/lib/userstate.ts';
import { updateMapCacheExpiration } from '@/lib/cache.ts';
import { setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts';
import { isiOSHomeScreenMode, isModalShowing, setAppFontSize } from '@/lib/ui/mobile.ts';
@ -180,6 +181,16 @@ onMounted(() => {
const languageInfo = getCurrentLanguageInfo();
initMapProvider(languageInfo?.alternativeLanguageTag);
});
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('controllerchange', () => {
updateMapCacheExpiration(settingsStore.appSettings.mapCacheExpiration);
});
if (navigator.serviceWorker.controller) {
updateMapCacheExpiration(settingsStore.appSettings.mapCacheExpiration);
}
}
});
watch(currentNotificationContent, (newValue) => {

View file

@ -3,3 +3,8 @@ export const SW_RUNTIME_CACHE_NAME_PREFIX: string = 'workbox-runtime-';
export const SW_ASSETS_CACHE_NAME: string = 'ezbookkeeping-assets-cache';
export const SW_CODE_CACHE_NAME: string = 'ezbookkeeping-code-cache';
export const SW_MAP_CACHE_NAME: string = 'ezbookkeeping-map-cache';
export const SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG: string = 'UPDATE_MAP_CACHE_CONFIG';
export const SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG_RESPONSE: string = 'UPDATE_MAP_CACHE_CONFIG_RESPONSE';
export const MAP_CACHE_MAX_ENTRIES: number = 1000;

View file

@ -65,12 +65,17 @@ export function* values<K extends string | number | symbol, V>(obj: Record<K, V>
}
}
export interface NameValue {
export interface GenericNameValue<T> {
readonly name: string;
readonly value: T;
}
export interface NameValue extends GenericNameValue<string> {
readonly name: string;
readonly value: string;
}
export interface NameNumeralValue {
export interface NameNumeralValue extends GenericNameValue<number> {
readonly name: string;
readonly value: number;
}

View file

@ -5,3 +5,10 @@ export interface BrowserCacheStatistics {
readonly mapCacheSize: number;
readonly othersCacheSize: number;
}
export interface SWMapCacheConfig {
enabled: boolean;
patterns: string[];
maxEntries: number;
maxAgeMilliseconds: number;
}

View file

@ -62,6 +62,8 @@ export interface ApplicationSettings extends BaseApplicationSetting {
hideCategoriesWithoutAccounts: boolean;
// Exchange Rates Data Page
currencySortByInExchangeRatesPage: number;
// Browser Cache Settings
mapCacheExpiration: number,
// Statistics Settings
statistics: {
defaultChartDataType: number;
@ -187,6 +189,8 @@ export const DEFAULT_APPLICATION_SETTINGS: ApplicationSettings = {
hideCategoriesWithoutAccounts: false,
// Exchange Rates Data Page
currencySortByInExchangeRatesPage: CurrencySortingType.Default.type,
// Browser Cache Settings
mapCacheExpiration: -1,
// Statistics Settings
statistics: {
defaultChartDataType: ChartDataType.Default.type,

View file

@ -1,5 +1,6 @@
import type {
BrowserCacheStatistics
BrowserCacheStatistics,
SWMapCacheConfig
} from '@/core/cache.ts';
import {
@ -7,10 +8,14 @@ import {
SW_RUNTIME_CACHE_NAME_PREFIX,
SW_ASSETS_CACHE_NAME,
SW_CODE_CACHE_NAME,
SW_MAP_CACHE_NAME
SW_MAP_CACHE_NAME,
SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG,
SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG_RESPONSE,
MAP_CACHE_MAX_ENTRIES
} from '@/consts/cache.ts';
import { isFunction, isObject, isNumber } from './common.ts';
import services from './services.ts';
import logger from './logger.ts';
function findFirstCacheName(prefix: string): Promise<string> {
@ -113,6 +118,63 @@ export function loadBrowserCacheStatistics(): Promise<BrowserCacheStatistics> {
});
}
export function updateMapCacheExpiration(expireSeconds: number): Promise<void> {
const config: SWMapCacheConfig = {
enabled: expireSeconds >= 0,
patterns: services.getMapProxyTileImageAndAnnotationImageUrlPatterns(),
maxEntries: MAP_CACHE_MAX_ENTRIES,
maxAgeMilliseconds: expireSeconds * 1000
};
return new Promise((resolve, reject) => {
if (!navigator.serviceWorker || !navigator.serviceWorker.controller) {
reject(new Error('Service worker is not supported or not active'));
return;
}
const controller = navigator.serviceWorker.controller;
navigator.serviceWorker.ready.then(() => {
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = (event) => {
if (event.data && event.data.type === SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG_RESPONSE) {
logger.info('Map cache config updated successfully in service worker: ' + JSON.stringify(event.data.payload));
resolve();
} else {
logger.error('cannot update map cache config, invalid response from service worker', event);
reject(new Error('Invalid response from service worker'));
}
};
controller.postMessage({
type: SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG,
payload: config
}, [messageChannel.port2]);
}).catch(error => {
logger.error('failed to update map cache config', error);
reject(error);
});
});
}
export function clearMapDataCache(): Promise<void> {
if (!window.caches) {
logger.error('caches API is not supported in this browser');
return Promise.reject();
}
return window.caches.delete(SW_MAP_CACHE_NAME).then(success => {
if (success) {
logger.info(`cache "${SW_MAP_CACHE_NAME}" cleared successfully`);
} else {
logger.warn(`failed to clear cache "${SW_MAP_CACHE_NAME}"`);
}
}).catch(error => {
logger.error(`failed to clear cache "${SW_MAP_CACHE_NAME}"`, error);
});
}
export function clearAllBrowserCaches(): Promise<void> {
if (!window.caches) {
logger.error('caches API is not supported in this browser');
@ -142,7 +204,7 @@ export function clearAllBrowserCaches(): Promise<void> {
resolve();
});
}).catch(error => {
logger.warn("failed to clear cache", error);
logger.error("failed to clear cache", error);
reject(error);
});
});

View file

@ -1,5 +1,11 @@
import { keys, keysIfValueEquals, values } from '@/core/base.ts';
import type { NameValue, TypeAndName, TypeAndDisplayName} from '@/core/base.ts';
import {
type GenericNameValue,
type TypeAndName,
type TypeAndDisplayName,
keys,
keysIfValueEquals,
values
} from '@/core/base.ts';
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export function isFunction(val: unknown): val is Function {
@ -285,7 +291,7 @@ export function getItemByKeyValue<T>(src: Record<string, T>[] | Record<string, R
return null;
}
export function findNameByValue(items: NameValue[], value: string): string | null {
export function findNameByValue<T>(items: GenericNameValue<T>[], value: T): string | null {
for (const item of items) {
if (item.value === value) {
return item.name;

View file

@ -27,6 +27,20 @@ export function initMapProvider(language?: string): void {
}
}
export function isMapProviderUseExternalSDK(): boolean {
const mapProviderType = getMapProvider();
if (mapProviderType === 'googlemap') {
return true;
} else if (mapProviderType === 'baidumap') {
return true;
} else if (mapProviderType === 'amap') {
return true;
} else {
return false;
}
}
export function getMapWebsite(): string {
return mapProvider?.getWebsite() || '';
}

View file

@ -831,6 +831,12 @@ export default {
generateQrCodeUrl: (qrCodeName: string): string => {
return `${getBasePath()}${BASE_QRCODE_PATH}/${qrCodeName}.png`;
},
getMapProxyTileImageAndAnnotationImageUrlPatterns(): string[] {
return [
`.*${BASE_PROXY_URL_PATH}/map/tile/[^/]+/[^/]+/[^/]+\\.png\\?provider=[^&]+.*$`,
`.*${BASE_PROXY_URL_PATH}/map/annotation/[^/]+/[^/]+/[^/]+\\.png\\?provider=[^&]+.*$`
];
},
generateMapProxyTileImageUrl: (mapProvider: string, language: string): string => {
const token = getCurrentToken();
let url = `${getBasePath()}${BASE_PROXY_URL_PATH}/map/tile/{z}/{x}/{y}.png?provider=${mapProvider}&token=${token}`;

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Sind Sie sicher, dass Sie sich erneut anmelden möchten?",
"Exchange Rates Data": "Wechselkursdaten",
"User Custom": "User Custom",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Are you sure you want to re-login?",
"Exchange Rates Data": "Exchange Rates Data",
"User Custom": "User Custom",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "¿Seguro que deseas volver a iniciar sesión?",
"Exchange Rates Data": "Tipos de Cambio",
"User Custom": "User Custom",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Êtes-vous sûr de vouloir vous reconnecter ?",
"Exchange Rates Data": "Données de taux de change",
"User Custom": "Personnalisé utilisateur",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Sei sicuro di voler accedere di nuovo?",
"Exchange Rates Data": "Dati tassi di cambio",
"User Custom": "User Custom",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "再ログインしますか?",
"Exchange Rates Data": "為替レートデータ",
"User Custom": "User Custom",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "ಮತ್ತೆ ಲಾಗಿನ್ ಮಾಡಲು ನೀವು ಖಚಿತವೇ?",
"Exchange Rates Data": "ವಿನಿಮಯ ದರ ಡೇಟಾ",
"User Custom": "ಬಳಕೆದಾರ ಕಸ್ಟಮ್",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "다시 로그인하시겠습니까?",
"Exchange Rates Data": "환율 데이터",
"User Custom": "사용자 정의",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Weet je zeker dat je opnieuw wilt inloggen?",
"Exchange Rates Data": "Wisselkoersgegevens",
"User Custom": "Gebruiker aangepast",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Tem certeza de que deseja fazer login novamente?",
"Exchange Rates Data": "Dados de Taxas de Câmbio",
"User Custom": "Personalização do Usuário",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Вы уверены, что хотите войти снова?",
"Exchange Rates Data": "Данные о курсах валют",
"User Custom": "Пользовательские настройки",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Ali ste prepričani, da se želite ponovno prijaviti?",
"Exchange Rates Data": "Podatki o menjalnih tečajih",
"User Custom": "Uporabniške nastavitve po meri",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "மீண்டும் உள்நுழை செய்ய நீங்கள் உறுதியா?",
"Exchange Rates Data": "மாற்று விகிதம் தரவு",
"User Custom": "தனிப்பயன்",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "คุณแน่ใจหรือว่าต้องการเข้าสู่ระบบอีกครั้ง?",
"Exchange Rates Data": "ข้อมูลอัตราแลกเปลี่ยน",
"User Custom": "กำหนดเองโดยผู้ใช้",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Tekrar giriş yapmak istediğinize emin misiniz?",
"Exchange Rates Data": "Döviz Kuru Verileri",
"User Custom": "Kullanıcı Özel",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Ви впевнені, що хочете увійти знову?",
"Exchange Rates Data": "Дані про курси валют",
"User Custom": "User Custom",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "Resource Files",
"Map Data": "Map Data",
"Others": "Others",
"Failed to load browser cache statistics": "Failed to load browser cache statistics",
"Clear File Cache": "Clear File Cache",
"Are you sure you want to clear file cache?": "Are you sure you want to clear file cache?",
"Cache Expiration Time": "Cache Expiration Time",
"Cache Expiration for Map Data": "Cache Expiration for Map Data",
"Disable Map Cache": "Disable Map Cache",
"Clear All File Cache": "Clear All File Cache",
"Are you sure you want to clear all file cache?": "Are you sure you want to clear all file cache?",
"Clear Map Data Cache": "Clear Map Data Cache",
"Are you sure you want to clear map data cache?": "Are you sure you want to clear map data cache?",
"Are you sure you want to re-login?": "Bạn có chắc chắn muốn đăng nhập lại không?",
"Exchange Rates Data": "Dữ liệu tỷ giá hối đoái",
"User Custom": "User Custom",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "资源文件",
"Map Data": "地图数据",
"Others": "其他",
"Failed to load browser cache statistics": "加载浏览器缓存统计数据失败",
"Clear File Cache": "清除文件缓存",
"Are you sure you want to clear file cache?": "您确定要清除文件缓存?",
"Cache Expiration Time": "缓存过期时间",
"Cache Expiration for Map Data": "地图数据缓存过期时间",
"Disable Map Cache": "禁用地图缓存",
"Clear All File Cache": "清除所有文件缓存",
"Are you sure you want to clear all file cache?": "您确定要清除所有文件缓存?",
"Clear Map Data Cache": "清除地图数据缓存",
"Are you sure you want to clear map data cache?": "您确定要清除地图数据缓存?",
"Are you sure you want to re-login?": "您确定要重新登录?",
"Exchange Rates Data": "汇率数据",
"User Custom": "用户自定义",

View file

@ -2512,9 +2512,13 @@
"Resource Files": "資源檔案",
"Map Data": "地圖資料",
"Others": "其他",
"Failed to load browser cache statistics": "載入瀏覽器快取統計資料失敗",
"Clear File Cache": "清除檔案快取",
"Are you sure you want to clear file cache?": "您確定要清除檔案快取?",
"Cache Expiration Time": "快取過期時間",
"Cache Expiration for Map Data": "地圖資料快取過期時間",
"Disable Map Cache": "停用地圖快取",
"Clear All File Cache": "清除所有檔案快取",
"Are you sure you want to clear all file cache?": "您確定要清除所有檔案快取?",
"Clear Map Data Cache": "清除地圖資料快取",
"Are you sure you want to clear map data cache?": "您確定要清除地圖資料快取?",
"Are you sure you want to re-login?": "您確定要重新登入?",
"Exchange Rates Data": "匯率資料",
"User Custom": "使用者自訂",

View file

@ -310,6 +310,12 @@ export const useSettingsStore = defineStore('settings', () => {
updateUserApplicationCloudSettingValue('currencySortByInExchangeRatesPage', value);
}
// Browser Cache Settings
function setMapCacheExpiration(value: number): void {
updateApplicationSettingsValue('mapCacheExpiration', value);
appSettings.value.mapCacheExpiration = value;
}
// Statistics Settings
function setStatisticsDefaultChartDataType(value: number): void {
updateApplicationSettingsSubValue('statistics', 'defaultChartDataType', value);
@ -531,6 +537,8 @@ export const useSettingsStore = defineStore('settings', () => {
setHideCategoriesWithoutAccounts,
// -- Exchange Rates Data Page
setCurrencySortByInExchangeRatesPage,
// -- Browser Cache Settings
setMapCacheExpiration,
// -- Statistics Settings
setStatisticsDefaultChartDataType,
setStatisticsDefaultTimezoneType,

309
src/sw.ts Normal file
View file

@ -0,0 +1,309 @@
import { clientsClaim } from 'workbox-core';
import type {
WorkboxPlugin,
CacheDidUpdateCallbackParam,
CacheKeyWillBeUsedCallbackParam,
CacheWillUpdateCallbackParam,
CachedResponseWillBeUsedCallbackParam
} from 'workbox-core/types';
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
interface CacheTimestampEntry {
request: Request;
time: number;
}
class DynamicExpirationPlugin implements WorkboxPlugin {
private static readonly SW_CACHE_TIME_HEADER: string = 'ezbookkeeping-sw-cache-time';
private maxEntries: number;
private maxAgeMilliseconds: number;
private cleaningCache: boolean = false;
constructor(maxEntries: number, maxAgeMilliseconds: number) {
this.maxEntries = maxEntries;
this.maxAgeMilliseconds = maxAgeMilliseconds;
}
public getMaxEntries(): number {
return this.maxEntries;
}
public setMaxEntries(maxEntries: number): void {
this.maxEntries = maxEntries;
}
public getMaxAgeMilliseconds(): number {
return this.maxAgeMilliseconds;
}
public setMaxAgeMilliseconds(maxAgeMilliseconds: number): void {
this.maxAgeMilliseconds = maxAgeMilliseconds;
}
public async cacheWillUpdate(param: CacheWillUpdateCallbackParam): Promise<Response | null> {
const response = param.response;
if (!response || response.status < 200 || response.status >= 300 || response.type === 'opaque') {
return null;
}
const body = await response.blob();
const headers = new Headers(response.headers);
headers.set(DynamicExpirationPlugin.SW_CACHE_TIME_HEADER, Date.now().toString());
return new Response(body, {
status: response.status,
statusText: response.statusText,
headers: headers
});
}
public async cachedResponseWillBeUsed(param: CachedResponseWillBeUsedCallbackParam): Promise<Response | null> {
const cachedResponse = param.cachedResponse;
if (!cachedResponse) {
return null;
}
const cacheTime: string | null = cachedResponse.headers.get(DynamicExpirationPlugin.SW_CACHE_TIME_HEADER);
if (!cacheTime) {
return cachedResponse;
}
const age: number = Date.now() - Number(cacheTime);
if (this.maxAgeMilliseconds > 0 && age >= this.maxAgeMilliseconds) {
if (param.cacheName) {
const cache = await caches.open(param.cacheName);
await cache.delete(param.request);
}
return null;
}
return cachedResponse;
}
public async cacheDidUpdate(param: CacheDidUpdateCallbackParam): Promise<void> {
if (this.cleaningCache || !param.cacheName) {
return;
}
this.cleaningCache = true;
const cache: Cache = await caches.open(param.cacheName);
const requests: readonly Request[] = await cache.keys();
if (requests.length <= this.maxEntries) {
this.cleaningCache = false;
return;
}
const entries: CacheTimestampEntry[] = [];
for (const request of requests) {
const response: Response | undefined = await cache.match(request);
if (!response) {
continue;
}
const cacheTime: string | null = response.headers.get(DynamicExpirationPlugin.SW_CACHE_TIME_HEADER);
let time: number = cacheTime ? Number(cacheTime) : 0;
if (Number.isFinite(time)) {
const age: number = Date.now() - time;
if (this.maxAgeMilliseconds > 0 && age >= this.maxAgeMilliseconds) {
await cache.delete(request);
continue;
}
} else {
time = 0;
}
entries.push({
request: request,
time: time
});
}
if (entries.length <= this.maxEntries) {
this.cleaningCache = false;
return;
}
entries.sort((a, b) => a.time - b.time);
const removeCount: number = entries.length - this.maxEntries;
for (let i = 0; i < removeCount; i++) {
const entry = entries[i];
if (entry && entry.request) {
await cache.delete(entry.request);
}
}
this.cleaningCache = false;
}
}
class MapDataRequestStripTokenPlugin implements WorkboxPlugin {
public async cacheKeyWillBeUsed(param: CacheKeyWillBeUsedCallbackParam): Promise<Request> {
const url = new URL(param.request.url);
if (url.searchParams.has('token')) {
url.searchParams.delete('token');
return new Request(url.href, param.request);
}
return param.request;
}
}
interface MapCacheConfig {
enabled: boolean;
patterns: RegExp[];
mapDataRequestStripTokenPlugin: MapDataRequestStripTokenPlugin;
expirationPlugin: DynamicExpirationPlugin;
}
declare const self: ServiceWorkerGlobalScope;
const SW_ASSETS_CACHE_NAME: string = 'ezbookkeeping-assets-cache';
const SW_CODE_CACHE_NAME: string = 'ezbookkeeping-code-cache';
const SW_MAP_CACHE_NAME: string = 'ezbookkeeping-map-cache';
const SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG: string = 'UPDATE_MAP_CACHE_CONFIG';
const SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG_RESPONSE: string = 'UPDATE_MAP_CACHE_CONFIG_RESPONSE';
const DEFAULT_MAP_CACHE_MAX_ENTRIES: number = 1000;
const DEFAULT_MAP_CACHE_MAX_AGE_MILLISECONDS: number = 30 * 24 * 60 * 60 * 1000;
const mapCacheConfig: MapCacheConfig = {
enabled: false,
patterns: [],
mapDataRequestStripTokenPlugin: new MapDataRequestStripTokenPlugin(),
expirationPlugin: new DynamicExpirationPlugin(DEFAULT_MAP_CACHE_MAX_ENTRIES, DEFAULT_MAP_CACHE_MAX_AGE_MILLISECONDS)
};
self.skipWaiting();
clientsClaim();
precacheAndRoute(self.__WB_MANIFEST);
cleanupOutdatedCaches();
registerRoute(
/.*\/img\/desktop\/.*\.(png|jpg|jpeg|gif|tiff|bmp|svg)/,
new StaleWhileRevalidate({
cacheName: SW_ASSETS_CACHE_NAME,
})
);
registerRoute(
/.*\/fonts\/.*\.(eot|ttf|svg|woff)/,
new CacheFirst({
cacheName: SW_ASSETS_CACHE_NAME,
})
);
registerRoute(
/.*\/(mobile|mobile\/|desktop|desktop\/)$/,
new NetworkFirst({
cacheName: SW_CODE_CACHE_NAME,
})
);
registerRoute(
/.*\/(mobile|mobile\/)#!\//,
new NetworkFirst({
cacheName: SW_CODE_CACHE_NAME,
})
);
registerRoute(
/.*\/(desktop|desktop\/)#\//,
new NetworkFirst({
cacheName: SW_CODE_CACHE_NAME,
})
);
registerRoute(
/.*\/(index\.html|mobile\.html|desktop\.html)/,
new NetworkFirst({
cacheName: SW_CODE_CACHE_NAME,
})
);
registerRoute(
/.*\/css\/.*\.css/,
new CacheFirst({
cacheName: SW_CODE_CACHE_NAME,
})
);
registerRoute(
/.*\/js\/.*\.js/,
new CacheFirst({
cacheName: SW_CODE_CACHE_NAME,
})
);
registerRoute(
({ url }) => {
if (!mapCacheConfig.enabled || mapCacheConfig.patterns.length < 1) {
return false;
}
for (const pattern of mapCacheConfig.patterns) {
if (pattern.test && pattern.test(url.href)) {
return true;
}
}
return false;
},
new CacheFirst({
cacheName: SW_MAP_CACHE_NAME,
plugins: [
mapCacheConfig.mapDataRequestStripTokenPlugin,
mapCacheConfig.expirationPlugin
]
})
);
self.addEventListener('message', (event: ExtendableMessageEvent) => {
try {
if (event.data && event.data.type === SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG && 'payload' in event.data) {
mapCacheConfig.enabled = !!event.data.payload['enabled'];
mapCacheConfig.patterns = [];
mapCacheConfig.expirationPlugin.setMaxEntries(event.data.payload['maxEntries'] ?? DEFAULT_MAP_CACHE_MAX_ENTRIES);
mapCacheConfig.expirationPlugin.setMaxAgeMilliseconds(event.data.payload['maxAgeMilliseconds'] ?? DEFAULT_MAP_CACHE_MAX_AGE_MILLISECONDS);
if (event.data.payload['patterns'] && Array.isArray(event.data.payload['patterns'])) {
for (const pattern of event.data.payload['patterns']) {
if (pattern) {
mapCacheConfig.patterns.push(new RegExp(pattern as string));
}
}
}
if (event.ports && event.ports[0] && typeof event.ports[0].postMessage === 'function') {
event.ports[0].postMessage({
type: SW_MESSAGE_TYPE_UPDATE_MAP_CACHE_CONFIG_RESPONSE,
payload: {
enabled: mapCacheConfig.enabled,
patterns: event.data.payload['patterns'],
maxEntries: mapCacheConfig.expirationPlugin.getMaxEntries(),
maxAgeMilliseconds: mapCacheConfig.expirationPlugin.getMaxAgeMilliseconds()
}
});
}
}
} catch (ex) {
console.error('failed to process message in service worker', ex);
}
});

View file

@ -1,12 +1,12 @@
<template>
<v-row>
<v-col cols="12">
<v-card :class="{ 'disabled': loadingCacheStatistics }">
<v-col cols="12" v-if="isSupportedFileCache">
<v-card :class="{ 'disabled': loading }">
<template #title>
<div class="d-flex align-center">
<span>{{ tt('File Cache') }}</span>
<v-btn density="compact" color="default" variant="text" size="24"
class="ms-2" :icon="true" :loading="loadingCacheStatistics" @click="reloadCacheStatistics()">
class="ms-2" :icon="true" :loading="loading" @click="loadCacheStatistics()">
<template #loader>
<v-progress-circular indeterminate size="20"/>
</template>
@ -16,14 +16,10 @@
</div>
</template>
<v-card-text class="mt-1" v-if="loadingCacheStatistics">
<v-card-text class="d-flex align-end" style="height: 3rem">
<span class="text-body-1">{{ tt('Used storage') }}</span>
<v-skeleton-loader class="d-inline-block skeleton-no-margin ml-1 pt-1 pb-1" type="text" style="width: 100px; height: 24px" :loading="true" v-if="loadingCacheStatistics"></v-skeleton-loader>
</v-card-text>
<v-card-text v-else-if="!loadingCacheStatistics">
<span class="text-body-1">{{ tt('Used storage') }}</span>
<span class="text-xl ml-1" v-if="!loadingCacheStatistics">{{ fileCacheStatistics ? formatVolumeToLocalizedNumerals(fileCacheStatistics.totalCacheSize, 2) : '-' }}</span>
<v-skeleton-loader class="d-inline-block skeleton-no-margin ms-1 pt-1 pb-1" type="text" style="width: 100px" :loading="true" v-if="loading"></v-skeleton-loader>
<span class="text-xl ms-1" v-if="!loading">{{ fileCacheStatistics ? formatVolumeToLocalizedNumerals(fileCacheStatistics.totalCacheSize, 2) : '-' }}</span>
</v-card-text>
<v-card-text>
@ -63,8 +59,8 @@
<div class="d-flex flex-column">
<span class="text-caption">{{ tt(item.title) }}</span>
<v-skeleton-loader class="skeleton-no-margin pt-2 pb-2" type="text" style="width: 100px" :loading="true" v-if="loadingCacheStatistics"></v-skeleton-loader>
<span class="text-xl" v-if="!loadingCacheStatistics">{{ item.count }}</span>
<v-skeleton-loader class="skeleton-no-margin pt-2 pb-2" type="text" style="width: 100px" :loading="true" v-if="loading"></v-skeleton-loader>
<span class="text-xl" v-if="!loading">{{ item.count }}</span>
</div>
</div>
</v-col>
@ -72,9 +68,13 @@
</v-card-text>
<v-card-text class="mt-2">
<v-btn color="gray" variant="tonal"
:disabled="loadingCacheStatistics || !fileCacheStatistics" @click="clearFileCache()">
{{ tt('Clear File Cache') }}
<v-btn color="secondary" variant="tonal"
:disabled="loading || !isSupportedFileCache || !fileCacheStatistics" @click="clearMapCache()">
{{ tt('Clear Map Data Cache') }}
</v-btn>
<v-btn class="ms-2" color="secondary" variant="tonal"
:disabled="loading || !isSupportedFileCache || !fileCacheStatistics" @click="clearAllFileCache()">
{{ tt('Clear All File Cache') }}
</v-btn>
</v-card-text>
</v-card>
@ -90,29 +90,49 @@
<v-card-text>
<span class="text-body-1">{{ tt('Used storage') }}</span>
<span class="text-xl ml-1">{{ formatVolumeToLocalizedNumerals(exchangeRatesCacheSize ?? 0, 2) }}</span>
<span class="text-xl ms-1">{{ formatVolumeToLocalizedNumerals(exchangeRatesCacheSize ?? 0, 2) }}</span>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12">
<v-card :title="tt('Cache Expiration Time')">
<v-form>
<v-card-text>
<v-row>
<v-col cols="12" sm="6" v-if="getMapProvider()">
<v-select
item-title="name"
item-value="value"
persistent-placeholder
:disabled="loading || !isSupportedFileCache || !fileCacheStatistics || isMapProviderUseExternalSDK() || !isMapDataFetchProxyEnabled()"
:label="tt('Cache Expiration for Map Data')"
:placeholder="tt('Cache Expiration for Map Data')"
:items="allMapCacheExpirationOptions"
v-model="mapCacheExpiration"
/>
</v-col>
</v-row>
</v-card-text>
</v-form>
</v-card>
</v-col>
</v-row>
<confirm-dialog ref="confirmDialog"/>
<snack-bar ref="snackbar" />
</template>
<script setup lang="ts">
import ConfirmDialog from '@/components/desktop/ConfirmDialog.vue';
import SnackBar from '@/components/desktop/SnackBar.vue';
import { ref, useTemplateRef } from 'vue';
import { useTemplateRef } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useExchangeRatesStore } from '@/stores/exchangeRates.ts';
import { useAppBrowserCacheSettingPageBase } from '@/views/base/settings/AppBrowserCacheSettingPageBase.ts';
import { type BrowserCacheStatistics } from '@/core/cache.ts';
import { loadBrowserCacheStatistics, clearAllBrowserCaches } from '@/lib/cache.ts';
import { isMapProviderUseExternalSDK } from '@/lib/map/index.ts';
import { getMapProvider, isMapDataFetchProxyEnabled } from '@/lib/server_settings.ts';
import {
mdiRefresh,
@ -123,39 +143,41 @@ import {
} from '@mdi/js';
type ConfirmDialogType = InstanceType<typeof ConfirmDialog>;
type SnackBarType = InstanceType<typeof SnackBar>;
const { tt, formatVolumeToLocalizedNumerals } = useI18n();
const {
tt,
formatVolumeToLocalizedNumerals
} = useI18n();
const exchangeRatesStore = useExchangeRatesStore();
const {
isSupportedFileCache,
loading,
fileCacheStatistics,
exchangeRatesCacheSize,
allMapCacheExpirationOptions,
mapCacheExpiration,
loadCacheStatistics,
clearMapDataCache,
clearAllBrowserCaches
} = useAppBrowserCacheSettingPageBase();
const confirmDialog = useTemplateRef<ConfirmDialogType>('confirmDialog');
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const loadingCacheStatistics = ref<boolean>(true);
const fileCacheStatistics = ref<BrowserCacheStatistics | undefined>(undefined);
const exchangeRatesCacheSize = ref<number | undefined>(undefined);
function reloadCacheStatistics(): void {
loadingCacheStatistics.value = true;
loadBrowserCacheStatistics().then(statistics => {
fileCacheStatistics.value = statistics;
exchangeRatesCacheSize.value = exchangeRatesStore.getExchangeRatesCacheSize();
loadingCacheStatistics.value = false;
}).catch(() => {
loadingCacheStatistics.value = false;
snackbar.value?.showError('Failed to load browser cache statistics');
function clearMapCache(): void {
confirmDialog.value?.open('Are you sure you want to clear map data cache?').then(() => {
clearMapDataCache().then(() => {
loadCacheStatistics();
});
});
}
function clearFileCache(): void {
confirmDialog.value?.open('Are you sure you want to clear file cache?').then(() => {
function clearAllFileCache(): void {
confirmDialog.value?.open('Are you sure you want to clear all file cache?').then(() => {
clearAllBrowserCaches().then(() => {
location.reload();
});
});
}
reloadCacheStatistics();
loadCacheStatistics();
</script>

View file

@ -1,14 +1,14 @@
<template>
<f7-page>
<f7-page ptr @ptr:refresh="reloadCacheStatistics">
<f7-navbar>
<f7-nav-left :class="{ 'disabled': loading }" :back-link="tt('Back')"></f7-nav-left>
<f7-nav-title :title="tt('Browser Cache Management')"></f7-nav-title>
<f7-nav-right :class="{ 'disabled': loading }">
<f7-link icon-f7="ellipsis" :class="{ 'disabled': loading || !fileCacheStatistics }" @click="showMoreActionSheet = true"></f7-link>
<f7-link icon-f7="ellipsis" :class="{ 'disabled': loading || !isSupportedFileCache || !fileCacheStatistics }" @click="showMoreActionSheet = true"></f7-link>
</f7-nav-right>
</f7-navbar>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading && isSupportedFileCache">
<f7-list-item group-title :sortable="false">
<small>{{ tt('File Cache') }}</small>
</f7-list-item>
@ -26,7 +26,14 @@
<f7-list-item title="Used storage" after="Count"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-if="!loading">
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-item group-title :sortable="false">
<small>{{ tt('Cache Expiration Time') }}</small>
</f7-list-item>
<f7-list-item title="Map Data" after="Disable Map Cache" v-if="getMapProvider()"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-if="!loading && isSupportedFileCache">
<f7-list-item group-title :sortable="false">
<small>{{ tt('File Cache') }}</small>
</f7-list-item>
@ -44,10 +51,38 @@
<f7-list-item :title="tt('Used storage')" :after="formatVolumeToLocalizedNumerals(exchangeRatesCacheSize ?? 0, 2)"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-if="!loading">
<f7-list-item group-title :sortable="false">
<small>{{ tt('Cache Expiration Time') }}</small>
</f7-list-item>
<f7-list-item
link="#"
:class="{ 'disabled': loading || !isSupportedFileCache || !fileCacheStatistics || isMapProviderUseExternalSDK() || !isMapDataFetchProxyEnabled() }"
:title="tt('Map Data')"
:after="findNameByValue(allMapCacheExpirationOptions, mapCacheExpiration)"
@click="showMapDataCacheExpirationPopup = true"
v-if="getMapProvider()"
>
<list-item-selection-popup value-type="item"
key-field="value" value-field="value"
title-field="name"
:title="tt('Cache Expiration for Map Data')"
:enable-filter="true"
:filter-placeholder="tt('Expiration Time')"
:filter-no-items-text="tt('No results')"
:items="allMapCacheExpirationOptions"
v-model:show="showMapDataCacheExpirationPopup"
v-model="mapCacheExpiration">
</list-item-selection-popup>
</f7-list-item>
</f7-list>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
<f7-actions-button :class="{ 'disabled': loading || !fileCacheStatistics }"
@click="clearFileCache">{{ tt('Clear File Cache') }}</f7-actions-button>
<f7-actions-group v-if="isSupportedFileCache && fileCacheStatistics">
<f7-actions-button :class="{ 'disabled': loading || !isSupportedFileCache || !fileCacheStatistics }"
@click="clearMapCache">{{ tt('Clear Map Data Cache') }}</f7-actions-button>
<f7-actions-button :class="{ 'disabled': loading || !isSupportedFileCache || !fileCacheStatistics }"
@click="clearAllFileCache">{{ tt('Clear All File Cache') }}</f7-actions-button>
</f7-actions-group>
<f7-actions-group>
<f7-actions-button bold close>{{ tt('Cancel') }}</f7-actions-button>
@ -62,42 +97,57 @@ import { ref } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useI18nUIComponents } from '@/lib/ui/mobile.ts';
import { useExchangeRatesStore } from '@/stores/exchangeRates.ts';
import { useAppBrowserCacheSettingPageBase } from '@/views/base/settings/AppBrowserCacheSettingPageBase.ts';
import { type BrowserCacheStatistics } from '@/core/cache.ts';
import { loadBrowserCacheStatistics, clearAllBrowserCaches } from '@/lib/cache.ts';
import { findNameByValue } from '@/lib/common.ts';
import { isMapProviderUseExternalSDK } from '@/lib/map/index.ts';
import { getMapProvider, isMapDataFetchProxyEnabled } from '@/lib/server_settings.ts';
const { tt, formatVolumeToLocalizedNumerals } = useI18n();
const { showConfirm, showToast } = useI18nUIComponents();
const { showConfirm } = useI18nUIComponents();
const exchangeRatesStore = useExchangeRatesStore();
const {
isSupportedFileCache,
loading,
fileCacheStatistics,
exchangeRatesCacheSize,
allMapCacheExpirationOptions,
mapCacheExpiration,
loadCacheStatistics,
clearMapDataCache,
clearAllBrowserCaches
} = useAppBrowserCacheSettingPageBase();
const loading = ref<boolean>(true);
const showMapDataCacheExpirationPopup = ref<boolean>(false);
const showMoreActionSheet = ref<boolean>(false);
const fileCacheStatistics = ref<BrowserCacheStatistics | undefined>(undefined);
const exchangeRatesCacheSize = ref<number | undefined>(undefined);
function reloadCacheStatistics(): void {
loading.value = true;
loadBrowserCacheStatistics().then(statistics => {
fileCacheStatistics.value = statistics;
exchangeRatesCacheSize.value = exchangeRatesStore.getExchangeRatesCacheSize();
loading.value = false;
function reloadCacheStatistics(done?: () => void): void {
loadCacheStatistics().then(() => {
if (done) {
done();
}
}).catch(() => {
loading.value = false;
showToast('Failed to load browser cache statistics');
if (done) {
done();
}
});
}
function clearFileCache(): void {
showConfirm('Are you sure you want to clear file cache?', () => {
function clearMapCache(): void {
showConfirm('Are you sure you want to clear map data cache?', () => {
clearMapDataCache().then(() => {
loadCacheStatistics();
});
});
}
function clearAllFileCache(): void {
showConfirm('Are you sure you want to clear all file cache?', () => {
clearAllBrowserCaches().then(() => {
location.reload();
});
});
}
reloadCacheStatistics();
loadCacheStatistics();
</script>

View file

@ -2,7 +2,7 @@
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"target": "ES2022",
"lib": ["DOM", "ES2022"],
"lib": ["DOM", "ES2022", "WebWorker"],
"types": ["vite/client"],
"strict": true,
"noImplicitReturns": true,

View file

@ -112,10 +112,11 @@ export default defineConfig(() => {
vueTsc: true
}),
VitePWA({
filename: 'sw.js',
manifestFilename: 'manifest.json',
strategies: 'generateSW',
strategies: 'injectManifest',
srcDir: './',
filename: 'sw.ts',
injectRegister: false,
manifestFilename: 'manifest.json',
manifest: {
name: 'ezBookkeeping',
short_name: 'ezBookkeeping',
@ -140,7 +141,7 @@ export default defineConfig(() => {
}
]
},
workbox: {
injectManifest: {
globDirectory: 'dist/',
globPatterns: ['**/*.{js,css,html,ico,png,jpg,jpeg,gif,tiff,bmp,ttf,woff,woff2,svg,eot}'],
globIgnores: [
@ -156,43 +157,7 @@ export default defineConfig(() => {
'css/*.css',
'js/*.js'
],
runtimeCaching: [
{
urlPattern: /.*\/(mobile|mobile\/|desktop|desktop\/)$/,
handler: 'NetworkFirst'
},
{
urlPattern: /.*\/(mobile|mobile\/)#!\//,
handler: 'NetworkFirst'
},
{
urlPattern: /.*\/(desktop|desktop\/)#\//,
handler: 'NetworkFirst'
},
{
urlPattern: /.*\/(index\.html|mobile\.html|desktop\.html)/,
handler: 'NetworkFirst'
},
{
urlPattern: /.*\/img\/desktop\/.*\.(png|jpg|jpeg|gif|tiff|bmp|svg)/,
handler: 'StaleWhileRevalidate'
},
{
urlPattern: /.*\/fonts\/.*\.(eot|ttf|svg|woff)/,
handler: 'CacheFirst'
},
{
urlPattern: /.*\/css\/.*\.css/,
handler: 'CacheFirst'
},
{
urlPattern: /.*\/js\/.*\.js/,
handler: 'CacheFirst'
}
],
navigateFallback: '',
skipWaiting: true,
clientsClaim: true
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5 MB
}
})
],