mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-05 19:24:39 +00:00
fix: svelte settings Sync
This commit is contained in:
+2
-2
@@ -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",
|
||||
|
||||
@@ -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')
|
||||
|
||||
+1
-1
@@ -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) => {
|
||||
|
||||
@@ -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<StorageManager> = {
|
||||
@@ -58,6 +61,11 @@ class StorageManager {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public setKey<K extends keyof SettingsState>(key: K, value: SettingsState[K]): void {
|
||||
this.data[key] = value;
|
||||
this.saveToStorage();
|
||||
}
|
||||
|
||||
private async loadFromStorage(): Promise<void> {
|
||||
const result = await browser.storage.local.get();
|
||||
this.data = { ...this.data, ...result };
|
||||
@@ -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();
|
||||
|
||||
@@ -1,64 +1,63 @@
|
||||
<script>
|
||||
import { settingsState } from '../state/SettingsState.ts';
|
||||
<script lang="ts">
|
||||
import { animate, spring } from 'motion';
|
||||
import './Switch.css'
|
||||
import { onMount } from "svelte"
|
||||
import { delay } from "../../seqta/utils/delay"
|
||||
import { onMount } from "svelte";
|
||||
|
||||
export let setting;
|
||||
export let onChange = () => {}
|
||||
let { state, onChange } = $props<{ state: boolean, onChange: (newState: boolean) => void }>();
|
||||
|
||||
let handle: HTMLElement | null = null;
|
||||
|
||||
const toggleSwitch = () => {
|
||||
const newIsOn = !$settingsState[setting]
|
||||
onChange(newIsOn)
|
||||
}
|
||||
onChange(!state);
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
console.log('state', state);
|
||||
});
|
||||
|
||||
const springParams = {
|
||||
type: 'spring',
|
||||
stiffness: 700,
|
||||
damping: 30,
|
||||
}
|
||||
};
|
||||
|
||||
let handle;
|
||||
|
||||
const animation = (enabled) => {
|
||||
const animateSwitch = (enabled: boolean) => {
|
||||
if (handle) {
|
||||
animate(
|
||||
handle,
|
||||
{ x: enabled ? 20 : 0 },
|
||||
{
|
||||
x: enabled ? 24 : 0,
|
||||
},
|
||||
{
|
||||
easing: spring({ stiffness: 500, damping: 30 })
|
||||
easing: spring(springParams),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$: ((enabled) => {
|
||||
if (handle) {
|
||||
animate(
|
||||
handle,
|
||||
{ x: enabled ? 24 : 0 },
|
||||
{ easing: spring({ stiffness: 500, damping: 30 }) }
|
||||
)
|
||||
}
|
||||
})($settingsState[setting])
|
||||
// Trigger animation whenever state changes
|
||||
$effect(() => animateSwitch(state));
|
||||
|
||||
onMount(() => {
|
||||
// Initialize the position of the switch
|
||||
animateSwitch(state);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
id={setting}
|
||||
class="flex w-14 p-1 cursor-pointer transition rounded-full dark:bg-[#38373D] bg-[#DDDDDD] switch"
|
||||
data-ison={$settingsState[setting]}
|
||||
on:click={toggleSwitch}
|
||||
on:keydown={(e) => 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"
|
||||
>
|
||||
<div
|
||||
bind:this={handle}
|
||||
class="w-6 h-6 bg-white dark:bg-[#FEFEFE] rounded-full drop-shadow-md"
|
||||
/>
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dark .switch[data-ison="true"],
|
||||
.switch[data-ison="true"] {
|
||||
background-color: #30D259;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
// @ts-expect-error umm idk
|
||||
import { MotionDiv } from 'svelte-motion';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { writable, derived } from 'svelte/store';
|
||||
import './TabbedContainer.css';
|
||||
import type { Component } from 'svelte'
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let tabs = [];
|
||||
|
||||
let activeTab = writable(0);
|
||||
const hoveredTab = writable(null);
|
||||
const position = writable(0);
|
||||
let tabWidth = 0;
|
||||
let containerRef;
|
||||
let { tabs } = $props<{ tabs: { title: string, Content: Component }[] }>();
|
||||
let activeTab = $state(0);
|
||||
let hoveredTab = $state<number | null>(null);
|
||||
let containerRef: HTMLElement | null = null;
|
||||
let tabWidth = $state(0);
|
||||
|
||||
const springTransition = { type: 'spring', stiffness: 250, damping: 25 };
|
||||
|
||||
// Calculate tabWidth dynamically based on tabs length
|
||||
onMount(() => {
|
||||
const updateTabWidth = () => {
|
||||
tabWidth = tabs.length > 0 ? 100 / tabs.length : 0;
|
||||
if (!containerRef) return;
|
||||
containerRef.style.setProperty('--tab-width', `${tabWidth}%`);
|
||||
};
|
||||
|
||||
const calcXPos = (index: number | null) => {
|
||||
if (containerRef) {
|
||||
|
||||
tabWidth = 100 / tabs.length;
|
||||
document.documentElement.style.setProperty('--tab-width', `${tabWidth}%`);
|
||||
|
||||
calcXPos = (index) => tabWidth * (index !== null ? index : $activeTab) * (containerRef !== null ? containerRef.getBoundingClientRect().width : 0) / 100;
|
||||
return tabWidth * (index !== null ? index : activeTab) * containerRef.getBoundingClientRect().width / 100;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Listen for messages
|
||||
const handleMessage = (event) => {
|
||||
$effect(() => {
|
||||
calcXPos(hoveredTab);
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
updateTabWidth();
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.data === "popupClosed") {
|
||||
activeTab.set(0);
|
||||
activeTab = 0;
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", handleMessage);
|
||||
@@ -37,23 +45,28 @@
|
||||
};
|
||||
});
|
||||
|
||||
let calcXPos = (index) => tabWidth * (index !== null ? index : $activeTab);
|
||||
/* $effect(() => {
|
||||
if (tabs.length > 0) {
|
||||
updateTabWidth();
|
||||
}
|
||||
}); */
|
||||
</script>
|
||||
|
||||
<div bind:this={containerRef} class="top-0 z-10 text-[0.875rem] pb-0.5 mx-4">
|
||||
<div bind:this={containerRef} class="top-0 z-10 text-[0.875rem] pb-0.5 mx-4 tab-width-container">
|
||||
<div class="hidden tab-width"></div>
|
||||
<div class="relative flex">
|
||||
<MotionDiv
|
||||
class="absolute top-0 left-0 z-0 h-full bg-[#DDDDDD] dark:bg-[#38373D] tab-width rounded-full opacity-40"
|
||||
animate={{ x: calcXPos($hoveredTab) }}
|
||||
class="absolute top-0 left-0 z-0 h-full bg-[#DDDDDD] dark:bg-[#38373D] rounded-full opacity-40"
|
||||
animate={{ x: calcXPos(hoveredTab) }}
|
||||
style="width: var(--tab-width)"
|
||||
transition={springTransition}
|
||||
/>
|
||||
{#each tabs as { title }, index}
|
||||
<button
|
||||
class="relative z-10 flex-1 px-4 py-2"
|
||||
on:click={() => activeTab.set(index)}
|
||||
on:mouseenter={() => hoveredTab.set(index)}
|
||||
on:mouseleave={() => hoveredTab.set(null)}
|
||||
onclick={() => activeTab = index}
|
||||
onmouseenter={() => hoveredTab = index}
|
||||
onmouseleave={() => hoveredTab = null}
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
@@ -62,22 +75,22 @@
|
||||
</div>
|
||||
<div class="h-full px-4 overflow-y-scroll overflow-x-clip">
|
||||
<MotionDiv
|
||||
animate={{ x: `${-$activeTab * 100}%` }}
|
||||
animate={{ x: `${-activeTab * 100}%` }}
|
||||
transition={springTransition}
|
||||
>
|
||||
<div class="flex">
|
||||
{#each tabs as { content }, index}
|
||||
<div class="absolute w-full transition-opacity duration-300 pb-4 {$activeTab === index ? 'opacity-100' : 'opacity-0'}"
|
||||
style="left: {index * 100}%;">
|
||||
<svelte:component this={content} />
|
||||
</div>
|
||||
{#each tabs as { Content }, index}
|
||||
<div class="absolute w-full transition-opacity duration-300 pb-4 {activeTab === index ? 'opacity-100' : 'opacity-0'}"
|
||||
style="left: {index * 100}%;">
|
||||
<Content />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</MotionDiv>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--tab-width: 0px;
|
||||
.tab-width {
|
||||
width: var(--tab-width);
|
||||
}
|
||||
</style>
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="relative flex flex-col w-[384px] shadow-2xl gap-2 bg-white {standalone ? '' : 'rounded-xl'} h-[100vh] overflow-clip dark:bg-zinc-800 dark:text-white">
|
||||
@@ -27,5 +20,9 @@
|
||||
<button on:click={openChangelog} class="absolute w-8 h-8 text-lg rounded-xl font-IconFamily top-1 right-1 bg-zinc-100 dark:bg-zinc-700"></button>
|
||||
</div>
|
||||
<!-- <Picker /> -->
|
||||
<TabbedContainer {tabs} />
|
||||
<TabbedContainer tabs={[
|
||||
{ title: 'Settings', Content: Settings },
|
||||
{ title: 'Shortcuts', Content: Shortcuts },
|
||||
{ title: 'Themes', Content: Theme },
|
||||
]} />
|
||||
</div>
|
||||
@@ -1,66 +1,62 @@
|
||||
<script lang="ts">
|
||||
import Switch from "../../components/Switch.svelte"
|
||||
import Button from "../../components/Button.svelte"
|
||||
import PickerSwatch from "../../components/PickerSwatch.svelte"
|
||||
//import PickerSwatch from "../../components/PickerSwatch.svelte"
|
||||
import Slider from "../../components/Slider.svelte"
|
||||
|
||||
import browser from "webextension-polyfill"
|
||||
|
||||
import type { SettingsList } from "../../types/SettingsProps"
|
||||
import { setSettingsValue } from "../../state/SettingsState"
|
||||
import { createSettingsState } from "../../state/SettingsStore.svelte.ts"
|
||||
|
||||
const settingsStore = createSettingsState();
|
||||
|
||||
let test = $state(false);
|
||||
|
||||
const settings: SettingsList[] = [
|
||||
{
|
||||
title: "Transparency Effects",
|
||||
description: "Enables transparency effects on certain elements such as blur. (May impact battery life)",
|
||||
id: 1,
|
||||
component: Switch,
|
||||
Component: Switch,
|
||||
props: {
|
||||
state: 'transparencyEffects',
|
||||
onChange: (isOn: boolean) => setSettingsValue('transparencyEffects', isOn)
|
||||
/* state: $settingsStore.transparencyEffects,
|
||||
onChange: (isOn: boolean) => settingsStore.setKey('transparencyEffects', isOn) */
|
||||
state: test,
|
||||
onChange: (isOn: boolean) => test = isOn
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Animated Background",
|
||||
description: "Adds an animated background to BetterSEQTA. (May impact battery life)",
|
||||
id: 2,
|
||||
component: Switch,
|
||||
Component: Switch as any,
|
||||
props: {
|
||||
state: 'animatedBackground',
|
||||
onChange: (isOn: boolean) => setSettingsValue('animatedBackground', isOn)
|
||||
state: $settingsStore.animatedbk,
|
||||
onChange: (isOn: boolean) => settingsStore.setKey('animatedbk', isOn)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Animated Background Speed",
|
||||
description: "Controls the speed of the animated background.",
|
||||
id: 3,
|
||||
component: Slider,
|
||||
Component: Slider,
|
||||
props: {
|
||||
state: 'animatedBackgroundSpeed',
|
||||
onChange: (value: number) => setSettingsValue('animatedBackgroundSpeed', `${value}`)
|
||||
state: $settingsStore.bksliderinput,
|
||||
onChange: (value: number) => settingsStore.setKey('bksliderinput', `${value}`)
|
||||
}
|
||||
},
|
||||
{
|
||||
/* {
|
||||
title: "Custom Theme Colour",
|
||||
description: "Customise the overall theme colour of SEQTA Learn.",
|
||||
id: 4,
|
||||
component: PickerSwatch
|
||||
},
|
||||
{
|
||||
title: "Telemetry",
|
||||
description: "Enables/disables error collecting.",
|
||||
id: 5,
|
||||
component: Switch,
|
||||
props: {
|
||||
state: 'telemetry',
|
||||
onChange: (isOn: boolean) => setSettingsValue('telemetry', isOn)
|
||||
}
|
||||
},
|
||||
Component: PickerSwatch
|
||||
}, */
|
||||
{
|
||||
title: "Edit Sidebar Layout",
|
||||
description: "Customise the sidebar layout.",
|
||||
id: 6,
|
||||
component: Button,
|
||||
Component: Button,
|
||||
props: {
|
||||
onClick: () => browser.runtime.sendMessage({ type: 'currentTab', info: 'EditSidebar' }),
|
||||
text: "Edit"
|
||||
@@ -70,49 +66,48 @@
|
||||
title: "Notification Collector",
|
||||
description: "Uncaps the 9+ limit for notifications, showing the real number.",
|
||||
id: 7,
|
||||
component: Switch,
|
||||
Component: Switch,
|
||||
props: {
|
||||
state: 'notificationCollector',
|
||||
onChange: (isOn: boolean) => setSettingsValue('notificationCollector', isOn)
|
||||
state: $settingsStore.notificationcollector,
|
||||
onChange: (isOn: boolean) => settingsStore.setKey('notificationcollector', isOn)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Lesson Alerts",
|
||||
description: "Sends a native browser notification ~5 minutes prior to lessons.",
|
||||
id: 8,
|
||||
component: Switch,
|
||||
Component: Switch,
|
||||
props: {
|
||||
state: 'lessonAlerts',
|
||||
onChange: (isOn: boolean) => setSettingsValue('lessonAlerts', isOn)
|
||||
state: $settingsStore.lessonalert,
|
||||
onChange: (isOn: boolean) => settingsStore.setKey('lessonalert', isOn)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "BetterSEQTA+",
|
||||
description: "Enables BetterSEQTA+ features",
|
||||
id: 9,
|
||||
component: Switch,
|
||||
Component: Switch,
|
||||
props: {
|
||||
state: 'betterSEQTAPlus',
|
||||
onChange: (isOn: boolean) => setSettingsValue('betterSEQTAPlus', isOn)
|
||||
state: $settingsStore.onoff,
|
||||
onChange: (isOn: boolean) => settingsStore.setKey('onoff', isOn)
|
||||
}
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col -mt-4 overflow-y-scroll divide-y divide-zinc-100 dark:divide-zinc-700">
|
||||
{#each settings as { title, description, component: Component, props, id } (id)}
|
||||
<div class="flex items-center justify-between px-4 py-3">
|
||||
<div class="pr-4">
|
||||
<h2 class="text-sm font-bold">{title}</h2>
|
||||
<p class="text-xs">{description}</p>
|
||||
<Switch state={$settingsStore.DarkMode} onChange={(isOn: boolean) => settingsStore.setKey('DarkMode', isOn)} />
|
||||
{#if settings}
|
||||
{#each settings as { title, description, Component, props, id } (id)}
|
||||
<div class="flex items-center justify-between px-4 py-3">
|
||||
<div class="pr-4">
|
||||
<h2 class="text-sm font-bold">{title}</h2>
|
||||
<p class="text-xs">{description}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Component {...props} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{#if props?.state !== undefined}
|
||||
<svelte:component this={Component} {...props} bind:setting={props.state} />
|
||||
{:else}
|
||||
<svelte:component this={Component} {...props} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -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 = <K extends keyof SettingsState>(key: K, value: SettingsState[K]) => {
|
||||
settingsState.update((prevState) => ({ ...prevState, [key]: value }));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
import type { SettingsState } from '@/types/storage';
|
||||
|
||||
export function createSettingsState() {
|
||||
let settings = $state<SettingsState>(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<K extends keyof SettingsState>(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);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
+2
-2
@@ -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"]
|
||||
}
|
||||
|
||||
+2
-2
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user