Trek_CN/client/src/sync/offlinePrefs.ts
jubnl 1922251fb3
Some checks are pending
Security Scan / scout (push) Waiting to run
chore: remove offline XYZ tile prefetcher and cache
- Remove tilePrefetcher.ts and its test file
- Remove tile prefetch calls from tripSyncManager
- Remove map tiles toggle from OfflineTab settings
- Remove cacheTiles preference from offlinePrefs
- Map tiles offline caching is no longer needed with AMap
2026-07-11 18:29:17 +08:00

91 lines
3.3 KiB
TypeScript

/**
* Offline preferences — device-local choices about WHAT gets stored offline and
* HOW sync conflicts are resolved (discussion #1135, asks 2 and 3).
*
* These live in localStorage rather than the server user-settings because they
* are inherently per-device: how much storage a phone should spend or which
* trips to keep on this particular device has nothing to do with the account
* and everything to do with the hardware in the user's hand.
*
* disabledTripIds — trips the user explicitly excluded from offline storage.
* Everything else that is date-eligible is cached.
* conflictStrategy — what to do when an offline edit collides with a newer
* server change: 'ask' surfaces a per-conflict picker,
* 'mine'/'server' resolve automatically.
*/
export type ConflictStrategy = 'ask' | 'mine' | 'server'
export interface OfflinePrefs {
disabledTripIds: number[]
conflictStrategy: ConflictStrategy
}
const STORAGE_KEY = 'trek_offline_prefs'
const DEFAULTS: OfflinePrefs = {
disabledTripIds: [],
conflictStrategy: 'ask',
}
let _prefs: OfflinePrefs = read()
const listeners = new Set<() => void>()
function read(): OfflinePrefs {
try {
const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(STORAGE_KEY) : null
if (!raw) return { ...DEFAULTS }
const parsed = JSON.parse(raw) as Partial<OfflinePrefs & { cacheTiles?: boolean }>
return {
disabledTripIds: Array.isArray(parsed.disabledTripIds) ? parsed.disabledTripIds.filter(n => typeof n === 'number') : [],
conflictStrategy: parsed.conflictStrategy === 'mine' || parsed.conflictStrategy === 'server' ? parsed.conflictStrategy : 'ask',
}
} catch {
return { ...DEFAULTS }
}
}
function write(next: OfflinePrefs): void {
_prefs = next
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(next)) } catch { /* best-effort */ }
listeners.forEach(fn => { try { fn() } catch { /* isolate listeners */ } })
}
/** Current snapshot (a copy — callers must not mutate it in place). */
export function getOfflinePrefs(): OfflinePrefs {
return { ..._prefs, disabledTripIds: [..._prefs.disabledTripIds] }
}
export function setConflictStrategy(strategy: ConflictStrategy): void {
if (_prefs.conflictStrategy === strategy) return
write({ ..._prefs, conflictStrategy: strategy })
}
/** True when this trip should be cached offline (i.e. not explicitly disabled). */
export function isTripOfflineEnabled(tripId: number): boolean {
return !_prefs.disabledTripIds.includes(tripId)
}
/** Turn offline storage for a single trip on or off. */
export function setTripOfflineEnabled(tripId: number, on: boolean): void {
const has = _prefs.disabledTripIds.includes(tripId)
if (on && !has) return
if (!on && has) return
const disabledTripIds = on
? _prefs.disabledTripIds.filter(id => id !== tripId)
: [..._prefs.disabledTripIds, tripId]
write({ ..._prefs, disabledTripIds })
}
/** Subscribe to preference changes. Returns an unsubscribe function. */
export function onOfflinePrefsChange(fn: () => void): () => void {
listeners.add(fn)
return () => listeners.delete(fn)
}
/** Reset to defaults — test helper only. */
export function _resetOfflinePrefs(): void {
_prefs = { ...DEFAULTS }
listeners.clear()
}