From c008b32efada38c270b6156eb28697a4b1aa84c9 Mon Sep 17 00:00:00 2001 From: sethburkart123 Date: Wed, 4 Sep 2024 09:42:07 +1000 Subject: [PATCH] fix: svelte settings Sync --- package.json | 4 +- src/SEQTA.ts | 1 + src/background.ts | 2 +- src/seqta/utils/listeners/SettingsState.ts | 27 ++++++ .../{+layout.svelte => +layout.svel} | 0 src/svelte-interface/components/Switch.svelte | 73 +++++++-------- .../components/TabbedContainer.svelte | 83 ++++++++++------- src/svelte-interface/main.ts | 9 +- src/svelte-interface/pages/settings.svelte | 13 +-- .../pages/settings/general.svelte | 93 +++++++++---------- src/svelte-interface/state/SettingsState.ts | 91 ------------------ .../state/SettingsStore.svelte.ts | 45 +++++++++ src/svelte-interface/types/SettingsProps.ts | 4 +- tsconfig.json | 4 +- vite.config.ts | 4 +- 15 files changed, 220 insertions(+), 233 deletions(-) rename src/svelte-interface/{+layout.svelte => +layout.svel} (100%) delete mode 100644 src/svelte-interface/state/SettingsState.ts create mode 100644 src/svelte-interface/state/SettingsStore.svelte.ts diff --git a/package.json b/package.json index b63e874e..6f016cb7 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@codemirror/lang-less": "^6.0.2", "@heroicons/react": "^2.1.3", "@million/lint": "latest", - "@sveltejs/vite-plugin-svelte": "^3.1.2", + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6", "@tailwindcss/forms": "^0.5.7", "@tsconfig/svelte": "^5.0.4", "@types/color": "^3.0.6", @@ -88,7 +88,7 @@ "react-toastify": "^10.0.5", "rimraf": "^5.0.5", "sortablejs": "^1.15.2", - "svelte": "^4.2.19", + "svelte": "^5.0.0-next.243", "svelte-hash-router": "^1.0.1", "svelte-motion": "^0.12.2", "swiper": "latest", diff --git a/src/SEQTA.ts b/src/SEQTA.ts index b7593c7b..d147a86c 100644 --- a/src/SEQTA.ts +++ b/src/SEQTA.ts @@ -48,6 +48,7 @@ var IsSEQTAPage = false const hasSEQTAText = document.childNodes[1].textContent?.includes('Copyright (c) SEQTA Software') init() + async function init() { CheckForMenuList() const hasSEQTATitle = document.title.includes('SEQTA Learn') diff --git a/src/background.ts b/src/background.ts index 4c41dffe..e95640ce 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,5 +1,5 @@ import browser from 'webextension-polyfill' -import { SettingsState } from "@/types/storage"; +import type { SettingsState } from "@/types/storage"; export const openDB = () => { return new Promise((resolve, reject) => { diff --git a/src/seqta/utils/listeners/SettingsState.ts b/src/seqta/utils/listeners/SettingsState.ts index 057afb54..77c02e58 100644 --- a/src/seqta/utils/listeners/SettingsState.ts +++ b/src/seqta/utils/listeners/SettingsState.ts @@ -2,15 +2,18 @@ import browser from 'webextension-polyfill'; import type { SettingsState } from '@/types/storage'; type ChangeListener = (newValue: any, oldValue: any) => void; +type GlobalChangeListener = (newValue: any, oldValue: any, key: string) => void; class StorageManager { private static instance: StorageManager; private data: SettingsState; private listeners: { [key: string]: ChangeListener[] }; + private globalListeners: GlobalChangeListener[]; private constructor() { this.data = {} as SettingsState; this.listeners = {}; + this.globalListeners = []; this.loadFromStorage(); const handler: ProxyHandler = { @@ -57,6 +60,11 @@ class StorageManager { await instance.loadFromStorage(); return instance; } + + public setKey(key: K, value: SettingsState[K]): void { + this.data[key] = value; + this.saveToStorage(); + } private async loadFromStorage(): Promise { const result = await browser.storage.local.get(); @@ -85,6 +93,9 @@ class StorageManager { listener(newValue, oldValue); } } + for (const listener of this.globalListeners) { + listener(newValue, oldValue, key); + } } } }); @@ -101,6 +112,22 @@ class StorageManager { } this.listeners[prop].push(listener); } + + /** + * Register a listener for any setting. + * @param listener The listener to call when any setting changes -> takes two arguments, (newValue, oldValue) + */ + public registerGlobal(listener: GlobalChangeListener): void { + this.globalListeners.push(listener); + } + + /** + * Get all settings. + * @returns All settings. + */ + public getAll(): SettingsState { + return this.data; + } } export const settingsState = StorageManager.getInstance(); diff --git a/src/svelte-interface/+layout.svelte b/src/svelte-interface/+layout.svel similarity index 100% rename from src/svelte-interface/+layout.svelte rename to src/svelte-interface/+layout.svel diff --git a/src/svelte-interface/components/Switch.svelte b/src/svelte-interface/components/Switch.svelte index d41ca9fd..92d9b718 100644 --- a/src/svelte-interface/components/Switch.svelte +++ b/src/svelte-interface/components/Switch.svelte @@ -1,64 +1,63 @@ -
e.key === "Enter" && toggleSwitch()} + data-ison={state} + onclick={toggleSwitch} + onkeydown={(e) => e.key === "Enter" && toggleSwitch()} role="switch" - aria-checked={$settingsState[setting]} + aria-checked={state} tabindex="0" >
-
\ No newline at end of file + >
+ + + diff --git a/src/svelte-interface/components/TabbedContainer.svelte b/src/svelte-interface/components/TabbedContainer.svelte index 6b7a0b5f..339a532c 100644 --- a/src/svelte-interface/components/TabbedContainer.svelte +++ b/src/svelte-interface/components/TabbedContainer.svelte @@ -1,33 +1,41 @@ - -
+
{#each tabs as { title }, index} @@ -62,22 +75,22 @@
- {#each tabs as { content }, index} -
- -
+ {#each tabs as { Content }, index} +
+ +
{/each}
\ No newline at end of file diff --git a/src/svelte-interface/main.ts b/src/svelte-interface/main.ts index fed4601c..009aed39 100644 --- a/src/svelte-interface/main.ts +++ b/src/svelte-interface/main.ts @@ -1,18 +1,19 @@ // @ts-expect-error - Svelte Hash Router is not typed (yet) import { routes } from 'svelte-hash-router' -import App from './+layout.svelte'; +//import App from './+layout.svelte'; import Settings from './pages/settings.svelte'; import styles from './index.css?inline'; +import { mount } from 'svelte'; export default function initSvelteInterface(shadow: ShadowRoot) { console.log(shadow) - routes.set({ + /* routes.set({ 'settings': Settings, '*': Settings - }) + }) */ - const app = new App({ + const app = mount(Settings, { target: shadow, }); diff --git a/src/svelte-interface/pages/settings.svelte b/src/svelte-interface/pages/settings.svelte index 099d2f7c..e2ffd556 100644 --- a/src/svelte-interface/pages/settings.svelte +++ b/src/svelte-interface/pages/settings.svelte @@ -11,13 +11,6 @@ }; let standalone = false; - - // Define the tabs array - const tabs = [ - { title: 'Settings', content: Settings }, - { title: 'Shortcuts', content: Shortcuts }, - { title: 'Themes', content: Theme }, - ];
@@ -27,5 +20,9 @@
- +
\ No newline at end of file diff --git a/src/svelte-interface/pages/settings/general.svelte b/src/svelte-interface/pages/settings/general.svelte index 4ff8e5ef..003e58f4 100644 --- a/src/svelte-interface/pages/settings/general.svelte +++ b/src/svelte-interface/pages/settings/general.svelte @@ -1,66 +1,62 @@
- {#each settings as { title, description, component: Component, props, id } (id)} -
-
-

{title}

-

{description}

+ settingsStore.setKey('DarkMode', isOn)} /> + {#if settings} + {#each settings as { title, description, Component, props, id } (id)} +
+
+

{title}

+

{description}

+
+
+ +
-
- {#if props?.state !== undefined} - - {:else} - - {/if} -
-
- {/each} + {/each} + {/if}
\ No newline at end of file diff --git a/src/svelte-interface/state/SettingsState.ts b/src/svelte-interface/state/SettingsState.ts deleted file mode 100644 index 98bce624..00000000 --- a/src/svelte-interface/state/SettingsState.ts +++ /dev/null @@ -1,91 +0,0 @@ -import browser from "webextension-polyfill"; -import type { MainConfig, SettingsState } from "../types/AppProps"; -import { writable } from "svelte/store"; - -const initialState: SettingsState = { - notificationCollector: false, - lessonAlerts: false, - telemetry: false, - animatedBackground: false, - animatedBackgroundSpeed: '0', - customThemeColor: '', - betterSEQTAPlus: false, - shortcuts: [], - customshortcuts: [], - transparencyEffects: false, - theme: '' -}; - -const keyToStateMap: { [key: string]: string } = { - notificationcollector: 'notificationCollector', - lessonalert: 'lessonAlerts', - telemetry: 'telemetry', - animatedbk: 'animatedBackground', - bksliderinput: 'animatedBackgroundSpeed', - selectedColor: 'customThemeColor', - onoff: 'betterSEQTAPlus', - shortcuts: 'shortcuts', - customshortcuts: 'customshortcuts', - transparencyEffects: 'transparencyEffects' -}; - -const stateToKeyMap = Object.fromEntries( - Object.entries(keyToStateMap).map(([key, value]) => [value, key]) -); - -const storageChangeListener = async (changes: browser.Storage.StorageChange) => { - for (const [key, { newValue }] of Object.entries(changes)) { - const stateKey = keyToStateMap[key] || key; - - if (stateKey === 'DarkMode') { - if (newValue) { - document.body.classList.add('dark'); - } else { - document.body.classList.remove('dark'); - } - } - settingsState.update((prevState) => ({ ...prevState, [stateKey]: newValue })); - } -} - -const initialStorageLoad = async (storage: MainConfig) => { - for (const [key, value] of Object.entries(storage)) { - const stateKey = keyToStateMap[key] || key; - - if (stateKey === 'DarkMode') { - if (value) { - document.body.classList.add('dark'); - } else { - document.body.classList.remove('dark'); - } - } - settingsState.update((prevState) => ({ ...prevState, [stateKey]: value })); - } -} - -const settingsSubscription = (/* set: (value: SettingsState) => void */) => { - settingsState.subscribe((newState) => { - const stateToSave = Object.fromEntries( - Object.entries(newState).map(([key, value]) => [stateToKeyMap[key] || key, value]) - ); - browser.storage.local.set(stateToSave); - /* set(newState); */ - }); -} - -export const initializeListeners = async () => { - const result = await browser.storage.local.get() as MainConfig; - - await initialStorageLoad(result); - - settingsSubscription(); - - browser.storage.onChanged.addListener(storageChangeListener); -} - - -export const settingsState = writable(initialState); - -export const setSettingsValue = (key: K, value: SettingsState[K]) => { - settingsState.update((prevState) => ({ ...prevState, [key]: value })); -} \ No newline at end of file diff --git a/src/svelte-interface/state/SettingsStore.svelte.ts b/src/svelte-interface/state/SettingsStore.svelte.ts new file mode 100644 index 00000000..7c253d0c --- /dev/null +++ b/src/svelte-interface/state/SettingsStore.svelte.ts @@ -0,0 +1,45 @@ +import { settingsState } from "@/seqta/utils/listeners/SettingsState"; +import type { SettingsState } from '@/types/storage'; + +export function createSettingsState() { + let settings = $state(settingsState); + + const subscribers = new Set<(value: SettingsState) => void>(); + + // Register a global listener to notify subscribers on any change + settingsState.registerGlobal((newValue, oldValue, key) => { + console.log('Global listener triggered:', { newValue, oldValue, key }); + if (newValue !== undefined) { + settings = { ...settings, [key]: newValue }; + notifySubscribers(settings); + } + }); + + function notifySubscribers(newValue: SettingsState) { + console.log('Notifying subscribers with:', newValue); + subscribers.forEach(subscriber => subscriber(newValue)); + } + + return { + get settings() { return settings; }, + set(newSettings: SettingsState) { + settings = newSettings; + notifySubscribers(settings); + }, + setKey(key: K, value: SettingsState[K]) { + settings[key] = value; + settingsState.setKey(key, value); + notifySubscribers(settings); + }, + subscribe(callback: (value: SettingsState) => void) { + subscribers.add(callback); + // Immediately call the callback with the current value + callback(settings); + + // Return an unsubscribe function + return () => { + subscribers.delete(callback); + }; + } + }; +} \ No newline at end of file diff --git a/src/svelte-interface/types/SettingsProps.ts b/src/svelte-interface/types/SettingsProps.ts index 418e4049..8c598d9b 100644 --- a/src/svelte-interface/types/SettingsProps.ts +++ b/src/svelte-interface/types/SettingsProps.ts @@ -1,11 +1,11 @@ import type { SettingsState } from './AppProps'; -import { ComponentType } from 'svelte'; +import type { Component } from 'svelte'; export interface SettingsList { title: string; id: number; description: string; - component: ComponentType; + Component: Component; props?: any; } export interface SettingsProps { diff --git a/tsconfig.json b/tsconfig.json index 075b026a..fdfa5b31 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ /* Bundler mode */ "moduleResolution": "bundler", - "allowImportingTsExtensions": false, + "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": false, @@ -25,5 +25,5 @@ "@/*": ["./src/*"] } }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte", "src/svelte-interface/+layout.sveltes"] } diff --git a/vite.config.ts b/vite.config.ts index 3a8c88fa..22d8c776 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -29,7 +29,7 @@ export default defineConfig({ base64Loader, //react(), svelte({ - emitCss: false, + emitCss: false }), //million.vite({ auto: true }), //MillionLint.vite(), /* enable for testing and debugging performance */ @@ -54,7 +54,7 @@ export default defineConfig({ }, build: { outDir: resolve(__dirname, 'dist', mode), - emptyOutDir: true, + emptyOutDir: false, minify: false, rollupOptions: { input: {