From 1105a9f7c52f884612b619b45dbd932defae2afc Mon Sep 17 00:00:00 2001 From: Fred Boniface Date: Thu, 14 May 2026 19:43:22 +0100 Subject: [PATCH] Add extra message to +error.svelte to handle, and clarify 'OFFLINE' error. --- .../station-board/StaffServicesTable.svelte | 8 +- src/lib/preferences.svelte.ts | 48 +++---- src/lib/quick-links.svelte.ts | 38 ++--- src/routes/+error.svelte | 1 + src/routes/+layout.svelte | 4 +- src/service-worker.ts | 132 ++++++++---------- 6 files changed, 111 insertions(+), 120 deletions(-) diff --git a/src/lib/components/ui/station-board/StaffServicesTable.svelte b/src/lib/components/ui/station-board/StaffServicesTable.svelte index 1cb02b4..a26ec87 100644 --- a/src/lib/components/ui/station-board/StaffServicesTable.svelte +++ b/src/lib/components/ui/station-board/StaffServicesTable.svelte @@ -248,10 +248,10 @@ font-size: 1.18rem; } } - @media (min-width: 620px) { + @media (min-width: 620px) { .cancel-row td, .delay-row td { - font-size: 1.20rem; + font-size: 1.2rem; } } @@ -261,12 +261,12 @@ padding: 0; color: rgb(187, 187, 255); } - @media(min-width: 400px) { + @media (min-width: 400px) { .toc-coach-row td { font-size: 0.8rem; } } - @media(min-width: 550px) { + @media (min-width: 550px) { .toc-coach-row td { font-size: 0.85rem; } diff --git a/src/lib/preferences.svelte.ts b/src/lib/preferences.svelte.ts index 6de0e82..9fe05db 100644 --- a/src/lib/preferences.svelte.ts +++ b/src/lib/preferences.svelte.ts @@ -1,43 +1,43 @@ import { browser } from '$app/environment'; export interface Preferences { - locationEnabled: boolean; + locationEnabled: boolean; } const STORAGE_KEY = 'ob_pref'; const DEFAULT_PREFS: Preferences = { - locationEnabled: false, + locationEnabled: false }; function loadPrefs(): Preferences { - if (!browser) return DEFAULT_PREFS; - const stored = localStorage.getItem(STORAGE_KEY); - if (!stored) return DEFAULT_PREFS; + 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; - } + try { + return { ...DEFAULT_PREFS, ...JSON.parse(stored) }; + } catch { + return DEFAULT_PREFS; + } } class PreferencesStore { - current = $state(loadPrefs()); + current = $state(loadPrefs()); - constructor() { - $effect.root(() => { - $effect(() => { - if (browser) { - localStorage.setItem(STORAGE_KEY, JSON.stringify(this.current)); - } - }); - }); - } + constructor() { + $effect.root(() => { + $effect(() => { + if (browser) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(this.current)); + } + }); + }); + } - toggleLocation(enabled: boolean) { - this.current.locationEnabled = enabled; - } + toggleLocation(enabled: boolean) { + this.current.locationEnabled = enabled; + } } -export const prefs = new PreferencesStore(); \ No newline at end of file +export const prefs = new PreferencesStore(); diff --git a/src/lib/quick-links.svelte.ts b/src/lib/quick-links.svelte.ts index 95d948e..fba7009 100644 --- a/src/lib/quick-links.svelte.ts +++ b/src/lib/quick-links.svelte.ts @@ -11,18 +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) { - try { - this.#links = JSON.parse(saved); - } catch { - this.#links = []; - } - } - } - } + 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); @@ -58,16 +58,16 @@ constructor() { this.#links = sorted.slice(0, MAX_ENTRIES); if (typeof window !== 'undefined') { - localStorage.setItem('ql', JSON.stringify(this.#links)); - } + localStorage.setItem('ql', JSON.stringify(this.#links)); + } } reset() { - this.#links = []; - if (typeof window !== 'undefined') { - localStorage.removeItem('ql'); - } - } + this.#links = []; + if (typeof window !== 'undefined') { + localStorage.removeItem('ql'); + } + } } export const quickLinks = new QuickLinksService(); diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte index 258d192..8984186 100644 --- a/src/routes/+error.svelte +++ b/src/routes/+error.svelte @@ -12,6 +12,7 @@ {:else if page.status == 499} + OFFLINE! {:else} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f11608e..0b7970b 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -22,11 +22,11 @@ if (browser && 'serviceWorker' in navigator) { try { const registration = await navigator.serviceWorker.register('/service-worker.js', { - type: 'module', + type: 'module' }); console.info('OwlBoard Service Worker registered', registration.scope); } catch (error) { - console.error('Service Worker installation failed: ', error) + console.error('Service Worker installation failed: ', error); } } }); diff --git a/src/service-worker.ts b/src/service-worker.ts index 4873a8a..ea87721 100644 --- a/src/service-worker.ts +++ b/src/service-worker.ts @@ -7,87 +7,77 @@ import { build, files, version } from '$service-worker'; const sw = self as unknown as ServiceWorkerGlobalScope; const CACHE_NAME = `owlboard-cache-${version}`; const ASSETS = [ - ...build, - ...files.filter(file => - !file.endsWith('manifest.json') && - !file.startsWith('/icons/') - ), - '/', + ...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)) - ); + sw.skipWaiting(); + event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))); }); sw.addEventListener('activate', (event) => { - event.waitUntil( - Promise.all([ - caches.keys().then((keys) => { - return Promise.all( - keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) - ); - }), - sw.clients.claim() - ]) - ); + event.waitUntil( + Promise.all([ + caches.keys().then((keys) => { + return Promise.all( + keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) + ); + }), + sw.clients.claim() + ]) + ); }); sw.addEventListener('fetch', (event) => { - const { request } = event; - if (request.method !== 'GET') return; - - const url = new URL(request.url); + const { request } = event; + if (request.method !== 'GET') return; - // Uncached static assets - if (url.pathname.endsWith('manifest.json') || url.pathname.includes('/icons/')) { - event.respondWith(fetch(request)); - return; - } + const url = new URL(request.url); - // 1. Static Assets (Cache-First) - if (ASSETS.includes(url.pathname) || url.pathname.startsWith('/_app/')) { - event.respondWith( - caches.match(request).then((res) => res || fetch(request)) - ); - return; - } + // Uncached static assets + if (url.pathname.endsWith('manifest.json') || url.pathname.includes('/icons/')) { + event.respondWith(fetch(request)); + return; + } - // 2. Offline API Fallback (Network-First) - if (url.pathname.startsWith('/api') || url.hostname.includes('api')) { - event.respondWith( - fetch(request).catch(() => { - const errorObject: ApiEnvelope.Envelope = { - e: { - code: "NETWORK_DISCONNECTED", - msg: "Cannot connect to the OwlBoard server" - }, - t: Math.floor(Date.now() / 1000) - }; - return new Response( - JSON.stringify(errorObject), - { - status: 503, - headers: { 'Content-Type': 'application/json' } - } - ); - }) - ); - return; - } + // 1. Static Assets (Cache-First) + if (ASSETS.includes(url.pathname) || url.pathname.startsWith('/_app/')) { + event.respondWith(caches.match(request).then((res) => res || fetch(request))); + return; + } - // 3. Navigation Fallback (The Offline 404 Catch-All) - // This catches top-level page navigations and hard refreshes - if (request.mode === 'navigate' || request.headers.get('accept')?.includes('text/html')) { - event.respondWith( - fetch(request).catch(() => { - // The network request failed entirely (offline). - // Serve the cached root shell so SvelteKit can boot. - return caches.match('/') as Promise; - }) - ); - return; - } -}); \ No newline at end of file + // 2. Offline API Fallback (Network-First) + if (url.pathname.startsWith('/api') || url.hostname.includes('api')) { + event.respondWith( + fetch(request).catch(() => { + const errorObject: ApiEnvelope.Envelope = { + e: { + code: 'NETWORK_DISCONNECTED', + msg: 'Cannot connect to the OwlBoard server' + }, + t: Math.floor(Date.now() / 1000) + }; + return new Response(JSON.stringify(errorObject), { + status: 503, + headers: { 'Content-Type': 'application/json' } + }); + }) + ); + return; + } + + // 3. Navigation Fallback (The Offline 404 Catch-All) + // This catches top-level page navigations and hard refreshes + if (request.mode === 'navigate' || request.headers.get('accept')?.includes('text/html')) { + event.respondWith( + fetch(request).catch(() => { + // The network request failed entirely (offline). + // Serve the cached root shell so SvelteKit can boot. + return caches.match('/') as Promise; + }) + ); + return; + } +});