diff --git a/src/app.html b/src/app.html index c7e23ff..283010a 100644 --- a/src/app.html +++ b/src/app.html @@ -3,12 +3,14 @@ - + + + diff --git a/src/lib/assets/apple-touch-icon.png b/src/lib/assets/apple-touch-icon.png deleted file mode 100644 index fbdf197..0000000 Binary files a/src/lib/assets/apple-touch-icon.png and /dev/null differ diff --git a/src/lib/assets/owlboard-logo-square.svg b/src/lib/assets/owlboard-logo-square.svg new file mode 100644 index 0000000..c24503c --- /dev/null +++ b/src/lib/assets/owlboard-logo-square.svg @@ -0,0 +1 @@ + diff --git a/src/lib/preferences.svelte.ts b/src/lib/preferences.svelte.ts new file mode 100644 index 0000000..6de0e82 --- /dev/null +++ b/src/lib/preferences.svelte.ts @@ -0,0 +1,43 @@ +import { browser } from '$app/environment'; + +export interface Preferences { + locationEnabled: boolean; +} + +const STORAGE_KEY = 'ob_pref'; + +const DEFAULT_PREFS: Preferences = { + locationEnabled: false, +}; + +function loadPrefs(): Preferences { + if (!browser) return DEFAULT_PREFS; + const stored = localStorage.getItem(STORAGE_KEY); + if (!stored) return DEFAULT_PREFS; + + try { + return { ...DEFAULT_PREFS, ...JSON.parse(stored) }; + } catch { + return DEFAULT_PREFS; + } +} + +class PreferencesStore { + current = $state(loadPrefs()); + + constructor() { + $effect.root(() => { + $effect(() => { + if (browser) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(this.current)); + } + }); + }); + } + + toggleLocation(enabled: boolean) { + this.current.locationEnabled = enabled; + } +} + +export const prefs = new PreferencesStore(); \ No newline at end of file diff --git a/src/lib/quick-links.svelte.ts b/src/lib/quick-links.svelte.ts index e897e22..95d948e 100644 --- a/src/lib/quick-links.svelte.ts +++ b/src/lib/quick-links.svelte.ts @@ -11,14 +11,18 @@ const MAX_ENTRIES: number = RETURNED_LENGTH * 4; class QuickLinksService { #links = $state([]); - constructor() { - if (typeof window !== 'undefined') { - const saved = localStorage.getItem('ql'); - if (saved) { - this.#links = JSON.parse(saved); - } - } - } +constructor() { + if (typeof window !== 'undefined') { + const saved = localStorage.getItem('ql'); + if (saved) { + try { + this.#links = JSON.parse(saved); + } catch { + this.#links = []; + } + } + } + } get list(): QuickLink[] { return this.#links.slice(0, RETURNED_LENGTH); @@ -53,8 +57,17 @@ class QuickLinksService { this.#links = sorted.slice(0, MAX_ENTRIES); - localStorage.setItem('ql', JSON.stringify(this.#links)); + if (typeof window !== 'undefined') { + localStorage.setItem('ql', JSON.stringify(this.#links)); + } } + + reset() { + this.#links = []; + if (typeof window !== 'undefined') { + localStorage.removeItem('ql'); + } + } } export const quickLinks = new QuickLinksService(); diff --git a/src/service-worker.ts b/src/service-worker.ts index 6b137ee..4873a8a 100644 --- a/src/service-worker.ts +++ b/src/service-worker.ts @@ -6,9 +6,17 @@ import { build, files, version } from '$service-worker'; const sw = self as unknown as ServiceWorkerGlobalScope; const CACHE_NAME = `owlboard-cache-${version}`; -const ASSETS = [...build, ...files, '/']; +const ASSETS = [ + ...build, + ...files.filter(file => + !file.endsWith('manifest.json') && + !file.startsWith('/icons/') + ), + '/', +]; sw.addEventListener('install', (event) => { + sw.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS)) ); @@ -16,11 +24,14 @@ sw.addEventListener('install', (event) => { sw.addEventListener('activate', (event) => { event.waitUntil( - caches.keys().then((keys) => { - return Promise.all( - keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) - ); - }) + Promise.all([ + caches.keys().then((keys) => { + return Promise.all( + keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) + ); + }), + sw.clients.claim() + ]) ); }); @@ -30,6 +41,12 @@ sw.addEventListener('fetch', (event) => { const url = new URL(request.url); + // Uncached static assets + if (url.pathname.endsWith('manifest.json') || url.pathname.includes('/icons/')) { + event.respondWith(fetch(request)); + return; + } + // 1. Static Assets (Cache-First) if (ASSETS.includes(url.pathname) || url.pathname.startsWith('/_app/')) { event.respondWith( diff --git a/static/icons/apple-touch-icon.png b/static/icons/apple-touch-icon.png new file mode 100644 index 0000000..3d8e628 Binary files /dev/null and b/static/icons/apple-touch-icon.png differ diff --git a/static/icons/icon-192.png b/static/icons/icon-192.png new file mode 100644 index 0000000..3987fd6 Binary files /dev/null and b/static/icons/icon-192.png differ diff --git a/static/icons/icon-512.png b/static/icons/icon-512.png new file mode 100644 index 0000000..e750475 Binary files /dev/null and b/static/icons/icon-512.png differ diff --git a/static/icons/maskable-512.png b/static/icons/maskable-512.png new file mode 100644 index 0000000..48e9d67 Binary files /dev/null and b/static/icons/maskable-512.png differ diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 0000000..435f0f5 --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,62 @@ +{ + "name": "OwlBoard", + "short_name": "OwlBoard", + "description": "Real-time UK rail data and operational tools for staff and enthusiasts.", + "id": "/", + "start_url": "/", + "scope": "/", + "display": "standalone", + "display_override": [ + "window-controls-overlay", + "standalone", + "minimal-ui" + ], + "background_color": "#404c55", + "theme_color": "#4fd1d1", + "orientation": "portrait", + "categories": [ + "transportation", + "utilities" + ], + "dir": "ltr", + "lang": "en-GB", + "prefer_related_applications": false, + "icons": [ + { + "src": "/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icons/maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "shortcuts": [ + { + "name": "Find PIS", + "short_name": "PIS", + "description": "Find PIS Codes fast", + "url": "/pis", + "icons": [ + { + "src": "/icons/icon-192.png", + "sizes": "192x192" + } + ] + } + ], + "handle_links": "preferred", + "launch_handler": { + "client_mode": "focus-existing" + } +} \ No newline at end of file