From e529d5e12fdebcd7ba7e198e372b20b26f7cc041 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 9 Feb 2024 23:02:56 +0600 Subject: [PATCH] feat: add darkmode toggle for website --- package-lock.json | 248 ++++++++++++++++++ package.json | 6 + website/.gitignore | 1 + website/netlify.toml | 3 + website/package-lock.json | 23 +- website/package.json | 6 +- .../components/navbar/darkmode-toggle.svelte | 32 +++ website/src/components/navbar/navbar.svelte | 6 +- .../src/components/navdrawer/navdrawer.svelte | 2 + website/src/lib/persisted-store.ts | 106 ++++++++ website/src/routes/+layout.svelte | 2 +- website/src/routes/+page.svelte | 10 + website/svelte.config.js | 7 +- website/tailwind.config.ts | 2 +- website/tsconfig.json | 34 +-- 15 files changed, 456 insertions(+), 32 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 website/netlify.toml create mode 100644 website/src/components/navbar/darkmode-toggle.svelte create mode 100644 website/src/lib/persisted-store.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..9331b5f7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,248 @@ +{ + "name": "spotube", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "svelte-media-queries": "^1.6.2", + "svelte-persisted-store": "^0.9.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "peer": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/code-red": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", + "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "peer": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "peer": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "peer": true + }, + "node_modules/magic-string": { + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "peer": true + }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.10.tgz", + "integrity": "sha512-Ep06yCaCdgG1Mafb/Rx8sJ1QS3RW2I2BxGp2Ui9LBHSZ2/tO/aGLc5WqPjgiAP6KAnLJGaIr/zzwQlOo1b8MxA==", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.4", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/svelte-media-queries": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/svelte-media-queries/-/svelte-media-queries-1.6.2.tgz", + "integrity": "sha512-SMz6od/vIeZEGlc4P0HKJK4G0fZotuwFhCSpBQaPqh75h6sL6sNf+4+IjbegFKXbP7b+SOfyzVOIMXTr8jynkA==" + }, + "node_modules/svelte-persisted-store": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/svelte-persisted-store/-/svelte-persisted-store-0.9.1.tgz", + "integrity": "sha512-l00I8Dy5GKjdjnE9ZcMeXLLMhvgV0+Iuru0Mue7eU3tB+pHBwBB2RVVqw2uC2Hbrf7cyZtsV/lnPKhjTHIWphQ==", + "engines": { + "node": ">=0.14" + }, + "peerDependencies": { + "svelte": "^3.48.0 || ^4.0.0 || ^5.0.0-next.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..56158b26 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "svelte-media-queries": "^1.6.2", + "svelte-persisted-store": "^0.9.1" + } +} diff --git a/website/.gitignore b/website/.gitignore index 6635cf55..c1f6d69f 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -8,3 +8,4 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* +.netlify diff --git a/website/netlify.toml b/website/netlify.toml new file mode 100644 index 00000000..9cb4a9bf --- /dev/null +++ b/website/netlify.toml @@ -0,0 +1,3 @@ +[build] + command = "npm run build" + publish = "build" \ No newline at end of file diff --git a/website/package-lock.json b/website/package-lock.json index 9aa4c4ad..fe1b962b 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -24,7 +24,7 @@ "@playwright/test": "^1.28.1", "@skeletonlabs/skeleton": "2.8.0", "@skeletonlabs/tw-plugin": "0.3.1", - "@sveltejs/adapter-static": "^3.0.1", + "@sveltejs/adapter-netlify": "^4.1.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", "@tailwindcss/typography": "0.5.10", @@ -626,6 +626,12 @@ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "dev": true + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1112,13 +1118,18 @@ "tailwindcss": ">=3.0.0" } }, - "node_modules/@sveltejs/adapter-static": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz", - "integrity": "sha512-6lMvf7xYEJ+oGeR5L8DFJJrowkefTK6ZgA4JiMqoClMkKq0s6yvsd3FZfCFvX1fQ0tpCD7fkuRVHsnUVgsHyNg==", + "node_modules/@sveltejs/adapter-netlify": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-netlify/-/adapter-netlify-4.1.0.tgz", + "integrity": "sha512-TGv14O/9xumJv/q9G57ZCzl+LUWHZ9dDleugT5QQD87zJE3Y98AKyU5Z7c7WlBTjKYKEQEeOxjnSdOpMRuhd6g==", "dev": true, + "dependencies": { + "@iarna/toml": "^2.2.5", + "esbuild": "^0.19.11", + "set-cookie-parser": "^2.6.0" + }, "peerDependencies": { - "@sveltejs/kit": "^2.0.0" + "@sveltejs/kit": "^2.4.0" } }, "node_modules/@sveltejs/kit": { diff --git a/website/package.json b/website/package.json index f39f01e5..ac817fd3 100644 --- a/website/package.json +++ b/website/package.json @@ -2,6 +2,7 @@ "name": "website", "version": "0.0.1", "private": true, + "type": "module", "scripts": { "dev": "vite dev", "build": "vite build", @@ -16,7 +17,7 @@ "@playwright/test": "^1.28.1", "@skeletonlabs/skeleton": "2.8.0", "@skeletonlabs/tw-plugin": "0.3.1", - "@sveltejs/adapter-static": "^3.0.1", + "@sveltejs/adapter-netlify": "^4.1.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", "@tailwindcss/typography": "0.5.10", @@ -40,7 +41,6 @@ "vite": "^5.0.3", "vite-plugin-tailwind-purgecss": "0.2.0" }, - "type": "module", "dependencies": { "@floating-ui/dom": "1.6.1", "@fortawesome/free-brands-svg-icons": "^6.5.1", @@ -54,4 +54,4 @@ "svelte-fa": "^4.0.2", "svelte-markdown": "^0.4.1" } -} +} \ No newline at end of file diff --git a/website/src/components/navbar/darkmode-toggle.svelte b/website/src/components/navbar/darkmode-toggle.svelte new file mode 100644 index 00000000..ec4f757c --- /dev/null +++ b/website/src/components/navbar/darkmode-toggle.svelte @@ -0,0 +1,32 @@ + + +
+ {#if label} + + {/if} + { + isDark.update((prev) => !prev); + }} + /> +
diff --git a/website/src/components/navbar/navbar.svelte b/website/src/components/navbar/navbar.svelte index 59a8d4b1..a37f815e 100644 --- a/website/src/components/navbar/navbar.svelte +++ b/website/src/components/navbar/navbar.svelte @@ -2,9 +2,10 @@ import { page } from '$app/stores'; import { routes } from '$lib'; import { faGithub } from '@fortawesome/free-brands-svg-icons'; - import { getDrawerStore } from '@skeletonlabs/skeleton'; + import { SlideToggle, getDrawerStore } from '@skeletonlabs/skeleton'; import { Menu } from 'lucide-svelte'; import Fa from 'svelte-fa'; + import DarkmodeToggle from './darkmode-toggle.svelte'; const drawerStore = getDrawerStore(); @@ -35,7 +36,7 @@ - diff --git a/website/src/lib/persisted-store.ts b/website/src/lib/persisted-store.ts new file mode 100644 index 00000000..0581fc1d --- /dev/null +++ b/website/src/lib/persisted-store.ts @@ -0,0 +1,106 @@ +import { writable as internal, type Writable } from 'svelte/store'; + +declare type Updater = (value: T) => T; +declare type StoreDict = { [key: string]: Writable }; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +interface Stores { + local: StoreDict; + session: StoreDict; +} + +const stores: Stores = { + local: {}, + session: {} +}; + +export interface Serializer { + parse(text: string): T; + stringify(object: T): string; +} + +export type StorageType = 'local' | 'session'; + +export interface Options { + serializer?: Serializer; + storage?: StorageType; + syncTabs?: boolean; + onError?: (e: unknown) => void; +} + +function getStorage(type: StorageType) { + return type === 'local' ? localStorage : sessionStorage; +} + +/** @deprecated `writable()` has been renamed to `persisted()` */ +export function writable(key: string, initialValue: T, options?: Options): Writable { + console.warn( + "writable() has been deprecated. Please use persisted() instead.\n\nchange:\n\nimport { writable } from 'svelte-persisted-store'\n\nto:\n\nimport { persisted } from 'svelte-persisted-store'" + ); + return persisted(key, initialValue, options); +} +export function persisted(key: string, initialValue: T, options?: Options): Writable { + const serializer = options?.serializer ?? JSON; + const storageType = options?.storage ?? 'local'; + const syncTabs = options?.syncTabs ?? true; + const onError = + options?.onError ?? + ((e) => + console.error(`Error when writing value from persisted store "${key}" to ${storageType}`, e)); + const browser = typeof window !== 'undefined' && typeof document !== 'undefined'; + const storage = browser ? getStorage(storageType) : null; + + function updateStorage(key: string, value: T) { + try { + storage?.setItem(key, serializer.stringify(value)); + } catch (e) { + onError(e); + } + } + + function maybeLoadInitial(): T { + const json = storage?.getItem(key); + + if (json) { + return serializer.parse(json); + } + + return initialValue; + } + + if (!stores[storageType][key]) { + const initial = maybeLoadInitial(); + const store = internal(initial, (set) => { + if (browser && storageType == 'local' && syncTabs) { + const handleStorage = (event: StorageEvent) => { + if (event.key === key) set(event.newValue ? serializer.parse(event.newValue) : null); + }; + + window.addEventListener('storage', handleStorage); + + return () => window.removeEventListener('storage', handleStorage); + } + }); + + const { subscribe, set } = store; + + stores[storageType][key] = { + set(value: T) { + set(value); + updateStorage(key, value); + }, + update(callback: Updater) { + return store.update((last) => { + const value = callback(last); + + updateStorage(key, value); + + return value; + }); + }, + subscribe + }; + } + + return stores[storageType][key]; +} diff --git a/website/src/routes/+layout.svelte b/website/src/routes/+layout.svelte index 2b65b7b6..6ad81d31 100644 --- a/website/src/routes/+layout.svelte +++ b/website/src/routes/+layout.svelte @@ -41,7 +41,7 @@

-