From 973f1b7446308f0ffb5941d53a87d34b08c163ee Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 2 Sep 2022 23:32:34 +0530 Subject: [PATCH] Update SvelteKit --- package.json | 2 +- src/hooks.ts | 5 +- src/lib/components/history/history.ts | 2 +- src/lib/util/migrations.ts | 2 +- src/lib/util/persist.ts | 265 ++++++++++++++++++ src/lib/util/state.ts | 2 +- src/lib/util/theme.ts | 2 +- src/routes/+layout.js | 3 + .../[fallback]/manifest.json/+server.ts | 4 - src/routes/manifest.json/+server.ts | 29 -- static/manifest.json | 23 ++ tsconfig.json | 3 +- vite.config.js | 3 - yarn.lock | 26 -- 14 files changed, 299 insertions(+), 72 deletions(-) create mode 100644 src/lib/util/persist.ts create mode 100644 src/routes/+layout.js delete mode 100644 src/routes/[fallback]/manifest.json/+server.ts delete mode 100644 src/routes/manifest.json/+server.ts create mode 100644 static/manifest.json diff --git a/package.json b/package.json index ec5189477..25e861708 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "eslint-plugin-svelte3": "4.0.0", "eslint-plugin-tailwindcss": "3.6.1", "eslint-plugin-vitest": "0.0.8", + "esserializer": "^1.3.2", "husky": "8.0.1", "jsdom": "20.0.0", "lint-staged": "13.0.3", @@ -64,7 +65,6 @@ }, "dependencies": { "@analytics/google-analytics": "1.0.3", - "@macfja/svelte-persistent-store": "1.3.0", "analytics": "0.8.1", "analytics-plugin-plausible": "^0.0.6", "daisyui": "2.24.0", diff --git a/src/hooks.ts b/src/hooks.ts index 916b2c818..75d6e91ac 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,9 +1,6 @@ import type { Handle } from '@sveltejs/kit'; export const handle: Handle = async ({ event, resolve }) => { - const response = await resolve(event, { - ssr: false - }); - + const response = await resolve(event, {}); return response; }; diff --git a/src/lib/components/history/history.ts b/src/lib/components/history/history.ts index 923a1d2e4..e5f53c2d7 100644 --- a/src/lib/components/history/history.ts +++ b/src/lib/components/history/history.ts @@ -1,6 +1,6 @@ import { derived, writable, get } from 'svelte/store'; import type { Readable, Writable } from 'svelte/store'; -import { persist, localStorage } from '@macfja/svelte-persistent-store'; +import { persist, localStorage } from '$lib/util/persist'; import { generateSlug } from 'random-word-slugs'; import type { HistoryEntry, HistoryType, Optional } from '$lib/types'; import { v4 as uuidV4 } from 'uuid'; diff --git a/src/lib/util/migrations.ts b/src/lib/util/migrations.ts index 40b6a1bc5..cd57c7327 100644 --- a/src/lib/util/migrations.ts +++ b/src/lib/util/migrations.ts @@ -1,5 +1,5 @@ import { writable, get, type Writable } from 'svelte/store'; -import { persist, localStorage } from '@macfja/svelte-persistent-store'; +import { persist, localStorage } from '$lib/util/persist'; import { injectHistoryIDs } from '$lib/components/history/history'; import { logEvent } from './stats'; diff --git a/src/lib/util/persist.ts b/src/lib/util/persist.ts new file mode 100644 index 000000000..ef275f49f --- /dev/null +++ b/src/lib/util/persist.ts @@ -0,0 +1,265 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// Copied from https://github.com/MacFJA/svelte-persistent-store +// # The MIT License (MIT) + +// Copyright (c) 2021 [MacFJA](https://github.com/MacFJA) + +// > Permission is hereby granted, free of charge, to any person obtaining a copy +// > of this software and associated documentation files (the "Software"), to deal +// > in the Software without restriction, including without limitation the rights +// > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// > copies of the Software, and to permit persons to whom the Software is +// > furnished to do so, subject to the following conditions: +// > +// > The above copyright notice and this permission notice shall be included in +// > all copies or substantial portions of the Software. +// > +// > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// > THE SOFTWARE. + +import ESSerializer from 'esserializer'; +import type { Writable } from 'svelte/store'; + +/** + * Disabled warnings about missing/unavailable storages + */ +export function disableWarnings(): void { + noWarnings = true; +} +/** + * If set to true, no warning will be emitted if the requested Storage is not found. + * This option can be useful when the lib is used on a server. + */ +let noWarnings = false; +/** + * List of storages where the warning have already been displayed. + */ +const alreadyWarnFor: Array = []; + +/** + * Add a log to indicate that the requested Storage have not been found. + * @param {string} storageName + */ +const warnStorageNotFound = (storageName) => { + const isProduction = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production'; + + if (!noWarnings && alreadyWarnFor.indexOf(storageName) === -1 && !isProduction) { + let message = `Unable to find the ${storageName}. No data will be persisted.`; + if (typeof window === 'undefined') { + message += + '\n' + + 'Are you running on a server? Most of storages are not available while running on a server.'; + } + console.warn(message); + alreadyWarnFor.push(storageName); + } +}; + +const allowedClasses = []; +/** + * Add a class to the allowed list of classes to be serialized + * @param classDef The class to add to the list + */ +export const addSerializableClass = (classDef: () => unknown): void => { + allowedClasses.push(classDef); +}; + +const serialize = (value: unknown): string => ESSerializer.serialize(value); +const deserialize = (value: string): unknown => { + // @TODO: to remove in the next major + if (value === 'undefined') { + return undefined; + } + + if (value !== null && value !== undefined) { + try { + return ESSerializer.deserialize(value, allowedClasses); + } catch (e) { + // Do nothing + // use the value "as is" + } + try { + return JSON.parse(value); + } catch (e) { + // Do nothing + // use the value "as is" + } + } + return value; +}; + +/** + * A store that keep it's value in time. + */ +export interface PersistentStore extends Writable { + /** + * Delete the store value from the persistent storage + */ + delete(): void; +} + +/** + * Storage interface + */ +export interface StorageInterface { + /** + * Get a value from the storage. + * + * If the value doesn't exists in the storage, `null` should be returned. + * This method MUST be synchronous. + * @param key The key/name of the value to retrieve + */ + getValue(key: string): T | null; + + /** + * Save a value in the storage. + * @param key The key/name of the value to save + * @param value The value to save + */ + setValue(key: string, value: T): void; + + /** + * Remove a value from the storage + * @param key The key/name of the value to remove + */ + deleteValue(key: string): void; +} + +export interface SelfUpdateStorageInterface extends StorageInterface { + /** + * Add a listener to the storage values changes + * @param {string} key The key to listen + * @param {(newValue: T) => void} listener The listener callback function + */ + addListener(key: string, listener: (newValue: T) => void): void; + /** + * Remove a listener from the storage values changes + * @param {string} key The key that was listened + * @param {(newValue: T) => void} listener The listener callback function to remove + */ + removeListener(key: string, listener: (newValue: T) => void): void; +} + +/** + * Make a store persistent + * @param {Writable<*>} store The store to enhance + * @param {StorageInterface} storage The storage to use + * @param {string} key The name of the data key + */ +export function persist( + store: Writable, + storage: StorageInterface, + key: string +): PersistentStore { + const initialValue = storage.getValue(key); + + if (null !== initialValue) { + store.set(initialValue); + } + + if ((storage as SelfUpdateStorageInterface).addListener) { + (storage as SelfUpdateStorageInterface).addListener(key, (newValue) => { + store.set(newValue); + }); + } + + store.subscribe((value) => { + storage.setValue(key, value); + }); + + return { + ...store, + delete() { + storage.deleteValue(key); + } + }; +} + +function getBrowserStorage( + browserStorage: Storage, + listenExternalChanges = false +): SelfUpdateStorageInterface { + const listeners: Array<{ key: string; listener: (newValue: any) => void }> = []; + const listenerFunction = (event: StorageEvent) => { + const eventKey = event.key; + if (event.storageArea === browserStorage) { + listeners + .filter(({ key }) => key === eventKey) + .forEach(({ listener }) => { + listener(deserialize(event.newValue)); + }); + } + }; + const connect = () => { + if (listenExternalChanges && typeof window !== 'undefined' && window?.addEventListener) { + window.addEventListener('storage', listenerFunction); + } + }; + const disconnect = () => { + if (listenExternalChanges && typeof window !== 'undefined' && window?.removeEventListener) { + window.removeEventListener('storage', listenerFunction); + } + }; + + return { + addListener(key: string, listener: (newValue: any) => void) { + listeners.push({ key, listener }); + if (listeners.length === 1) { + connect(); + } + }, + removeListener(key: string, listener: (newValue: any) => void) { + const index = listeners.indexOf({ key, listener }); + if (index !== -1) { + listeners.splice(index, 1); + } + if (listeners.length === 0) { + disconnect(); + } + }, + getValue(key: string): any | null { + const value = browserStorage.getItem(key); + return deserialize(value); + }, + deleteValue(key: string) { + browserStorage.removeItem(key); + }, + setValue(key: string, value: any) { + browserStorage.setItem(key, serialize(value)); + } + }; +} + +/** + * Storage implementation that use the browser local storage + * @param {boolean} listenExternalChanges - Update the store if the localStorage is updated from another page + */ +export function localStorage(listenExternalChanges = false): StorageInterface { + if (typeof window !== 'undefined' && window?.localStorage) { + return getBrowserStorage(window.localStorage, listenExternalChanges); + } + warnStorageNotFound('window.localStorage'); + return noopStorage(); +} + +/** + * Storage implementation that do nothing + */ +export function noopStorage(): StorageInterface { + return { + getValue(): null { + return null; + }, + deleteValue() { + // Do nothing + }, + setValue() { + // Do nothing + } + }; +} diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index 4294f39a9..789f91111 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -1,5 +1,5 @@ import { writable, get, derived } from 'svelte/store'; -import { persist, localStorage } from '@macfja/svelte-persistent-store'; +import { persist, localStorage } from './persist'; import { saveStatistics } from './stats'; import { serializeState, deserializeState } from './serde'; import { cmdKey } from './util'; diff --git a/src/lib/util/theme.ts b/src/lib/util/theme.ts index 76a7524a4..24ae78fd9 100644 --- a/src/lib/util/theme.ts +++ b/src/lib/util/theme.ts @@ -1,6 +1,6 @@ import { writable } from 'svelte/store'; import type { Writable } from 'svelte/store'; -import { persist, localStorage } from '@macfja/svelte-persistent-store'; +import { persist, localStorage } from '$lib/util/persist'; import { logEvent } from './stats'; export interface ThemeConfig { diff --git a/src/routes/+layout.js b/src/routes/+layout.js new file mode 100644 index 000000000..212e3ae32 --- /dev/null +++ b/src/routes/+layout.js @@ -0,0 +1,3 @@ +export const prerender = true; +export const csr = true; +export const ssr = false; diff --git a/src/routes/[fallback]/manifest.json/+server.ts b/src/routes/[fallback]/manifest.json/+server.ts deleted file mode 100644 index b03705c81..000000000 --- a/src/routes/[fallback]/manifest.json/+server.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { RequestHandler } from './$types'; -import { GET as manifestGet } from '../../manifest.json/+server'; - -export const GET: RequestHandler = manifestGet; diff --git a/src/routes/manifest.json/+server.ts b/src/routes/manifest.json/+server.ts deleted file mode 100644 index fec24441d..000000000 --- a/src/routes/manifest.json/+server.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { json } from '@sveltejs/kit'; -import { base } from '$app/paths'; -import type { RequestHandler } from './$types'; - -export const GET: RequestHandler = () => { - return json({ - short_name: 'Mermaid', - name: 'Mermaid Live Editor', - icons: [ - { - src: `${base}/icon-192.png`, - type: 'image/png', - sizes: '192x192' - }, - { - src: `${base}/icon-512.png`, - type: 'image/png', - sizes: '512x512' - } - ], - start_url: `${base}/edit/`, - background_color: '#6366F1', - display: 'standalone', - scope: `${base}/edit/`, - theme_color: '#6366F1', - description: 'FlowChart & Diagrams Editor.', - orientation: 'landscape' - }); -}; diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 000000000..ba584bf80 --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,23 @@ +{ + "short_name": "Mermaid", + "name": "Mermaid Live Editor", + "icons": [ + { + "src": "/icon-192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "/icon-512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": "/edit/", + "background_color": "#6366F1", + "display": "standalone", + "scope": "/edit/", + "theme_color": "#6366F1", + "description": "FlowChart & Diagrams Editor.", + "orientation": "landscape" +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 120d65a21..87bfd16bd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "src/**/*.svelte", "cypress/**/*.ts", "cypress/**/*.js", - "static/**/*.js" + "static/**/*.js", + "static/**/*.json" ], "compilerOptions": { "resolveJsonModule": true, diff --git a/vite.config.js b/vite.config.js index 27500d09a..4aee28c24 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,9 +4,6 @@ const config = { plugins: [sveltekit()], envPrefix: 'MERMAID_', optimizeDeps: { include: ['mermaid'] }, - ssr: { - noExternal: ['@macfja/svelte-persistent-store'] - }, server: { port: 3000, host: true diff --git a/yarn.lock b/yarn.lock index f781bf63c..c8640c08f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -255,15 +255,6 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@macfja/svelte-persistent-store@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@macfja/svelte-persistent-store/-/svelte-persistent-store-1.3.0.tgz#e3ef6440cde04657ab77284dacd447b12a89cb1e" - integrity sha512-3lPsEAFe28zCNTyA+/pYfUgzRrH6KZvoG0i4IHVhYOs3Xzaqg9hN7LdHa+Qrv7GncXn/6XhDEpb36hB0f7CzXA== - dependencies: - browser-cookies "^1.2.0" - esserializer "^1.3.2" - idb-keyval "^5.1.3" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -932,11 +923,6 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browser-cookies@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browser-cookies/-/browser-cookies-1.2.0.tgz#fca3ffb9b6a63aadc4d8c0999c6b57d0fa7d29b5" - integrity sha1-/KP/ubamOq3E2MCZnGtX0Pp9KbU= - browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -3224,13 +3210,6 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -idb-keyval@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.1.3.tgz#6ef5dff371897c23f144322dc6374eadd6a345d9" - integrity sha512-N9HbCK/FaXSRVI+k6Xq4QgWxbcZRUv+SfG1y7HJ28JdV8yEJu6k+C/YLea7npGckX2DQJeEVuMc4bKOBeU/2LQ== - dependencies: - safari-14-idb-fix "^1.0.4" - ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -4704,11 +4683,6 @@ sade@^1.8.1: dependencies: mri "^1.1.0" -safari-14-idb-fix@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-1.0.4.tgz#5c68ba63e2a8ae0d89a0aa1e13fe89e3aef7da19" - integrity sha512-4+Y2baQdgJpzu84d0QjySl70Kyygzf0pepVg8NVg4NnQEPpfC91fAn0baNvtStlCjUUxxiu0BOMiafa98fRRuA== - safe-buffer@^5.0.1, safe-buffer@^5.1.2: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"