From 13832dc13a6a56b690ce4365184c08d6424d4e28 Mon Sep 17 00:00:00 2001 From: Santiago Lo Coco Date: Tue, 23 Apr 2024 22:52:33 +0200 Subject: [PATCH] Save theme in cookies (avoid flickering) --- src/app.d.ts | 6 +++ src/app.html | 2 +- src/hooks.server.ts | 24 +++++++++ src/lib/components/NavBar.svelte | 74 +++++++++++++++++++++++++++ src/lib/components/ThemePicker.svelte | 3 ++ src/routes/+layout.js | 0 src/routes/+layout.server.ts | 7 +++ src/routes/+layout.svelte | 12 ++++- src/routes/+page.server.ts | 19 +++++++ src/service-worker.js | 67 ++++++++++++++++++++++++ 10 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 src/hooks.server.ts create mode 100644 src/lib/components/NavBar.svelte delete mode 100644 src/routes/+layout.js create mode 100644 src/routes/+layout.server.ts create mode 100644 src/routes/+page.server.ts create mode 100644 src/service-worker.js diff --git a/src/app.d.ts b/src/app.d.ts index 367926a..d8b4864 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -4,6 +4,12 @@ declare global { namespace App { // interface Error {} // interface Locals {} + interface Locals { + theme: Theme + } + interface PageData { + theme: Theme + } // interface PageData {} // interface PageState {} // interface Platform {} diff --git a/src/app.html b/src/app.html index 84ffad1..98dfe47 100644 --- a/src/app.html +++ b/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..8a60dd8 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,24 @@ +import type { Handle } from "@sveltejs/kit" + +export type Theme = "light" | "dark" | "auto" + +export const isValidTheme = ( + theme: FormDataEntryValue | null +): theme is Theme => + !!theme && (theme === "light" || theme === "dark" || theme === "auto") + +export const handle: Handle = async ({ event, resolve }) => { + const theme = event.cookies.get("theme") ?? "auto" + if (isValidTheme(theme)) { + event.locals.theme = theme + } + + event.setHeaders({ + "cache-control": `private, max-age=${5 * 60}`, + }) + const response = await resolve(event, { + transformPageChunk: ({ html }) => html.replace("%THEME%", theme), + }) + + return response +} diff --git a/src/lib/components/NavBar.svelte b/src/lib/components/NavBar.svelte new file mode 100644 index 0000000..67a982c --- /dev/null +++ b/src/lib/components/NavBar.svelte @@ -0,0 +1,74 @@ + + + + +
+ + + +
diff --git a/src/lib/components/ThemePicker.svelte b/src/lib/components/ThemePicker.svelte index c69aed6..6cb2e2a 100644 --- a/src/lib/components/ThemePicker.svelte +++ b/src/lib/components/ThemePicker.svelte @@ -10,6 +10,9 @@ function toggleTheme() { const currentIndex = themes.indexOf(theme) theme = themes[(currentIndex + 1) % themes.length] + // if (typeof window !== "undefined" && typeof localStorage !== "undefined") { + // localStorage.setItem("theme", theme) + // } } let icon = $derived.by(() => { diff --git a/src/routes/+layout.js b/src/routes/+layout.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..134cc66 --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,7 @@ +import type { LayoutServerLoad } from "./$types" + +export const load: LayoutServerLoad = async ({ locals }) => { + const { theme } = locals + + return { theme } +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3fd29cc..1d255e3 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,14 +1,22 @@
- + +
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..15ff7c0 --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,19 @@ +import { fail, type Actions } from "@sveltejs/kit" +import { isValidTheme } from "../hooks.server" + +const TEN_YEARS_IN_SECONDS = 10 * 365 * 24 * 60 * 60 + +export const actions: Actions = { + theme: async ({ cookies, request }) => { + const data = await request.formData() + const theme = data.get("theme") + + if (!isValidTheme(theme)) { + return fail(400, { theme, missing: true }) + } + + cookies.set("theme", theme, { path: "/", maxAge: TEN_YEARS_IN_SECONDS }) + + return { success: true } + }, +} diff --git a/src/service-worker.js b/src/service-worker.js new file mode 100644 index 0000000..3a3cdd5 --- /dev/null +++ b/src/service-worker.js @@ -0,0 +1,67 @@ +/// +import { build, files, version } from "$service-worker" + +const CACHE = `cache-${version}` + +const ASSETS = [...build, ...files] + +self.addEventListener("install", (event) => { + async function addFilesToCache() { + const cache = await caches.open(CACHE) + await cache.addAll(ASSETS) + } + + event.waitUntil(addFilesToCache()) +}) + +self.addEventListener("activate", (event) => { + async function deleteOldCaches() { + for (const key of await caches.keys()) { + if (key !== CACHE) await caches.delete(key) + } + } + + event.waitUntil(deleteOldCaches()) +}) + +self.addEventListener("fetch", (event) => { + if (event.request.method !== "GET") return + + async function respond() { + const url = new URL(event.request.url) + const cache = await caches.open(CACHE) + + if (ASSETS.includes(url.pathname)) { + return cache.match(event.request) + } + + try { + const response = await fetch(event.request) + + if (response.status === 200) { + cache.put(event.request, response.clone()) + } + + return response + } catch { + return cache.match(event.request) + } + } + + event.respondWith(respond()) +}) + +// self.addEventListener('storage', event => { +// if (event.key === 'theme') { +// const newCacheName = event.newValue === 'dark' ? 'my-cache-dark' : 'my-cache'; + +// caches.keys().then(cacheNames => { +// return Promise.all( +// cacheNames.filter(cacheName => cacheName === CACHE) +// .map(cacheName => caches.delete(cacheName)) +// ); +// }).then(() => { +// return caches.open(newCacheName); +// }) +// } +// })