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",
|
"@codemirror/lang-less": "^6.0.2",
|
||||||
"@heroicons/react": "^2.1.3",
|
"@heroicons/react": "^2.1.3",
|
||||||
"@million/lint": "latest",
|
"@million/lint": "latest",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
"@types/color": "^3.0.6",
|
"@types/color": "^3.0.6",
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
"react-toastify": "^10.0.5",
|
"react-toastify": "^10.0.5",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"sortablejs": "^1.15.2",
|
"sortablejs": "^1.15.2",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^5.0.0-next.243",
|
||||||
"svelte-hash-router": "^1.0.1",
|
"svelte-hash-router": "^1.0.1",
|
||||||
"svelte-motion": "^0.12.2",
|
"svelte-motion": "^0.12.2",
|
||||||
"swiper": "latest",
|
"swiper": "latest",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ var IsSEQTAPage = false
|
|||||||
const hasSEQTAText = document.childNodes[1].textContent?.includes('Copyright (c) SEQTA Software')
|
const hasSEQTAText = document.childNodes[1].textContent?.includes('Copyright (c) SEQTA Software')
|
||||||
init()
|
init()
|
||||||
|
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
CheckForMenuList()
|
CheckForMenuList()
|
||||||
const hasSEQTATitle = document.title.includes('SEQTA Learn')
|
const hasSEQTATitle = document.title.includes('SEQTA Learn')
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import browser from 'webextension-polyfill'
|
import browser from 'webextension-polyfill'
|
||||||
import { SettingsState } from "@/types/storage";
|
import type { SettingsState } from "@/types/storage";
|
||||||
|
|
||||||
export const openDB = () => {
|
export const openDB = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ import browser from 'webextension-polyfill';
|
|||||||
import type { SettingsState } from '@/types/storage';
|
import type { SettingsState } from '@/types/storage';
|
||||||
|
|
||||||
type ChangeListener = (newValue: any, oldValue: any) => void;
|
type ChangeListener = (newValue: any, oldValue: any) => void;
|
||||||
|
type GlobalChangeListener = (newValue: any, oldValue: any, key: string) => void;
|
||||||
|
|
||||||
class StorageManager {
|
class StorageManager {
|
||||||
private static instance: StorageManager;
|
private static instance: StorageManager;
|
||||||
private data: SettingsState;
|
private data: SettingsState;
|
||||||
private listeners: { [key: string]: ChangeListener[] };
|
private listeners: { [key: string]: ChangeListener[] };
|
||||||
|
private globalListeners: GlobalChangeListener[];
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.data = {} as SettingsState;
|
this.data = {} as SettingsState;
|
||||||
this.listeners = {};
|
this.listeners = {};
|
||||||
|
this.globalListeners = [];
|
||||||
this.loadFromStorage();
|
this.loadFromStorage();
|
||||||
|
|
||||||
const handler: ProxyHandler<StorageManager> = {
|
const handler: ProxyHandler<StorageManager> = {
|
||||||
@@ -58,6 +61,11 @@ class StorageManager {
|
|||||||
return instance;
|
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> {
|
private async loadFromStorage(): Promise<void> {
|
||||||
const result = await browser.storage.local.get();
|
const result = await browser.storage.local.get();
|
||||||
this.data = { ...this.data, ...result };
|
this.data = { ...this.data, ...result };
|
||||||
@@ -85,6 +93,9 @@ class StorageManager {
|
|||||||
listener(newValue, oldValue);
|
listener(newValue, oldValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const listener of this.globalListeners) {
|
||||||
|
listener(newValue, oldValue, key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -101,6 +112,22 @@ class StorageManager {
|
|||||||
}
|
}
|
||||||
this.listeners[prop].push(listener);
|
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();
|
export const settingsState = StorageManager.getInstance();
|
||||||
|
|||||||
@@ -1,64 +1,63 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { settingsState } from '../state/SettingsState.ts';
|
|
||||||
import { animate, spring } from 'motion';
|
import { animate, spring } from 'motion';
|
||||||
import './Switch.css'
|
import { onMount } from "svelte";
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { delay } from "../../seqta/utils/delay"
|
|
||||||
|
|
||||||
export let setting;
|
let { state, onChange } = $props<{ state: boolean, onChange: (newState: boolean) => void }>();
|
||||||
export let onChange = () => {}
|
|
||||||
|
let handle: HTMLElement | null = null;
|
||||||
|
|
||||||
const toggleSwitch = () => {
|
const toggleSwitch = () => {
|
||||||
const newIsOn = !$settingsState[setting]
|
onChange(!state);
|
||||||
onChange(newIsOn)
|
};
|
||||||
}
|
|
||||||
|
$effect(() => {
|
||||||
|
console.log('state', state);
|
||||||
|
});
|
||||||
|
|
||||||
const springParams = {
|
const springParams = {
|
||||||
type: 'spring',
|
|
||||||
stiffness: 700,
|
stiffness: 700,
|
||||||
damping: 30,
|
damping: 30,
|
||||||
}
|
};
|
||||||
|
|
||||||
let handle;
|
const animateSwitch = (enabled: boolean) => {
|
||||||
|
|
||||||
const animation = (enabled) => {
|
|
||||||
if (handle) {
|
if (handle) {
|
||||||
animate(
|
animate(
|
||||||
handle,
|
handle,
|
||||||
|
{ x: enabled ? 20 : 0 },
|
||||||
{
|
{
|
||||||
x: enabled ? 24 : 0,
|
easing: spring(springParams),
|
||||||
},
|
|
||||||
{
|
|
||||||
easing: spring({ stiffness: 500, damping: 30 })
|
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
$: ((enabled) => {
|
// Trigger animation whenever state changes
|
||||||
if (handle) {
|
$effect(() => animateSwitch(state));
|
||||||
animate(
|
|
||||||
handle,
|
|
||||||
{ x: enabled ? 24 : 0 },
|
|
||||||
{ easing: spring({ stiffness: 500, damping: 30 }) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})($settingsState[setting])
|
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// Initialize the position of the switch
|
||||||
|
animateSwitch(state);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id={setting}
|
|
||||||
class="flex w-14 p-1 cursor-pointer transition rounded-full dark:bg-[#38373D] bg-[#DDDDDD] switch"
|
class="flex w-14 p-1 cursor-pointer transition rounded-full dark:bg-[#38373D] bg-[#DDDDDD] switch"
|
||||||
data-ison={$settingsState[setting]}
|
data-ison={state}
|
||||||
on:click={toggleSwitch}
|
onclick={toggleSwitch}
|
||||||
on:keydown={(e) => e.key === "Enter" && toggleSwitch()}
|
onkeydown={(e) => e.key === "Enter" && toggleSwitch()}
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={$settingsState[setting]}
|
aria-checked={state}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
bind:this={handle}
|
bind:this={handle}
|
||||||
class="w-6 h-6 bg-white dark:bg-[#FEFEFE] rounded-full drop-shadow-md"
|
class="w-6 h-6 bg-white dark:bg-[#FEFEFE] rounded-full drop-shadow-md"
|
||||||
/>
|
></div>
|
||||||
</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 { MotionDiv } from 'svelte-motion';
|
||||||
import { onMount, onDestroy } from 'svelte';
|
|
||||||
import { writable, derived } from 'svelte/store';
|
|
||||||
import './TabbedContainer.css';
|
import './TabbedContainer.css';
|
||||||
|
import type { Component } from 'svelte'
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let tabs = [];
|
let { tabs } = $props<{ tabs: { title: string, Content: Component }[] }>();
|
||||||
|
let activeTab = $state(0);
|
||||||
let activeTab = writable(0);
|
let hoveredTab = $state<number | null>(null);
|
||||||
const hoveredTab = writable(null);
|
let containerRef: HTMLElement | null = null;
|
||||||
const position = writable(0);
|
let tabWidth = $state(0);
|
||||||
let tabWidth = 0;
|
|
||||||
let containerRef;
|
|
||||||
|
|
||||||
const springTransition = { type: 'spring', stiffness: 250, damping: 25 };
|
const springTransition = { type: 'spring', stiffness: 250, damping: 25 };
|
||||||
|
|
||||||
// Calculate tabWidth dynamically based on tabs length
|
const updateTabWidth = () => {
|
||||||
onMount(() => {
|
tabWidth = tabs.length > 0 ? 100 / tabs.length : 0;
|
||||||
|
if (!containerRef) return;
|
||||||
|
containerRef.style.setProperty('--tab-width', `${tabWidth}%`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const calcXPos = (index: number | null) => {
|
||||||
if (containerRef) {
|
if (containerRef) {
|
||||||
|
return tabWidth * (index !== null ? index : activeTab) * containerRef.getBoundingClientRect().width / 100;
|
||||||
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 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Listen for messages
|
$effect(() => {
|
||||||
const handleMessage = (event) => {
|
calcXPos(hoveredTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
updateTabWidth();
|
||||||
|
|
||||||
|
const handleMessage = (event: MessageEvent) => {
|
||||||
if (event.data === "popupClosed") {
|
if (event.data === "popupClosed") {
|
||||||
activeTab.set(0);
|
activeTab = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener("message", handleMessage);
|
window.addEventListener("message", handleMessage);
|
||||||
@@ -37,23 +45,28 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let calcXPos = (index) => tabWidth * (index !== null ? index : $activeTab);
|
/* $effect(() => {
|
||||||
|
if (tabs.length > 0) {
|
||||||
|
updateTabWidth();
|
||||||
|
}
|
||||||
|
}); */
|
||||||
</script>
|
</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="hidden tab-width"></div>
|
||||||
<div class="relative flex">
|
<div class="relative flex">
|
||||||
<MotionDiv
|
<MotionDiv
|
||||||
class="absolute top-0 left-0 z-0 h-full bg-[#DDDDDD] dark:bg-[#38373D] tab-width rounded-full opacity-40"
|
class="absolute top-0 left-0 z-0 h-full bg-[#DDDDDD] dark:bg-[#38373D] rounded-full opacity-40"
|
||||||
animate={{ x: calcXPos($hoveredTab) }}
|
animate={{ x: calcXPos(hoveredTab) }}
|
||||||
|
style="width: var(--tab-width)"
|
||||||
transition={springTransition}
|
transition={springTransition}
|
||||||
/>
|
/>
|
||||||
{#each tabs as { title }, index}
|
{#each tabs as { title }, index}
|
||||||
<button
|
<button
|
||||||
class="relative z-10 flex-1 px-4 py-2"
|
class="relative z-10 flex-1 px-4 py-2"
|
||||||
on:click={() => activeTab.set(index)}
|
onclick={() => activeTab = index}
|
||||||
on:mouseenter={() => hoveredTab.set(index)}
|
onmouseenter={() => hoveredTab = index}
|
||||||
on:mouseleave={() => hoveredTab.set(null)}
|
onmouseleave={() => hoveredTab = null}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</button>
|
</button>
|
||||||
@@ -62,22 +75,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="h-full px-4 overflow-y-scroll overflow-x-clip">
|
<div class="h-full px-4 overflow-y-scroll overflow-x-clip">
|
||||||
<MotionDiv
|
<MotionDiv
|
||||||
animate={{ x: `${-$activeTab * 100}%` }}
|
animate={{ x: `${-activeTab * 100}%` }}
|
||||||
transition={springTransition}
|
transition={springTransition}
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
{#each tabs as { content }, index}
|
{#each tabs as { Content }, index}
|
||||||
<div class="absolute w-full transition-opacity duration-300 pb-4 {$activeTab === index ? 'opacity-100' : 'opacity-0'}"
|
<div class="absolute w-full transition-opacity duration-300 pb-4 {activeTab === index ? 'opacity-100' : 'opacity-0'}"
|
||||||
style="left: {index * 100}%;">
|
style="left: {index * 100}%;">
|
||||||
<svelte:component this={content} />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</MotionDiv>
|
</MotionDiv>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
.tab-width {
|
||||||
--tab-width: 0px;
|
width: var(--tab-width);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
// @ts-expect-error - Svelte Hash Router is not typed (yet)
|
// @ts-expect-error - Svelte Hash Router is not typed (yet)
|
||||||
import { routes } from 'svelte-hash-router'
|
import { routes } from 'svelte-hash-router'
|
||||||
import App from './+layout.svelte';
|
//import App from './+layout.svelte';
|
||||||
import Settings from './pages/settings.svelte';
|
import Settings from './pages/settings.svelte';
|
||||||
import styles from './index.css?inline';
|
import styles from './index.css?inline';
|
||||||
|
import { mount } from 'svelte';
|
||||||
|
|
||||||
export default function initSvelteInterface(shadow: ShadowRoot) {
|
export default function initSvelteInterface(shadow: ShadowRoot) {
|
||||||
console.log(shadow)
|
console.log(shadow)
|
||||||
|
|
||||||
routes.set({
|
/* routes.set({
|
||||||
'settings': Settings,
|
'settings': Settings,
|
||||||
'*': Settings
|
'*': Settings
|
||||||
})
|
}) */
|
||||||
|
|
||||||
const app = new App({
|
const app = mount(Settings, {
|
||||||
target: shadow,
|
target: shadow,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let standalone = false;
|
let standalone = false;
|
||||||
|
|
||||||
// Define the tabs array
|
|
||||||
const tabs = [
|
|
||||||
{ title: 'Settings', content: Settings },
|
|
||||||
{ title: 'Shortcuts', content: Shortcuts },
|
|
||||||
{ title: 'Themes', content: Theme },
|
|
||||||
];
|
|
||||||
</script>
|
</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">
|
<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>
|
<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>
|
</div>
|
||||||
<!-- <Picker /> -->
|
<!-- <Picker /> -->
|
||||||
<TabbedContainer {tabs} />
|
<TabbedContainer tabs={[
|
||||||
|
{ title: 'Settings', Content: Settings },
|
||||||
|
{ title: 'Shortcuts', Content: Shortcuts },
|
||||||
|
{ title: 'Themes', Content: Theme },
|
||||||
|
]} />
|
||||||
</div>
|
</div>
|
||||||
@@ -1,66 +1,62 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Switch from "../../components/Switch.svelte"
|
import Switch from "../../components/Switch.svelte"
|
||||||
import Button from "../../components/Button.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 Slider from "../../components/Slider.svelte"
|
||||||
|
|
||||||
import browser from "webextension-polyfill"
|
import browser from "webextension-polyfill"
|
||||||
|
|
||||||
import type { SettingsList } from "../../types/SettingsProps"
|
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[] = [
|
const settings: SettingsList[] = [
|
||||||
{
|
{
|
||||||
title: "Transparency Effects",
|
title: "Transparency Effects",
|
||||||
description: "Enables transparency effects on certain elements such as blur. (May impact battery life)",
|
description: "Enables transparency effects on certain elements such as blur. (May impact battery life)",
|
||||||
id: 1,
|
id: 1,
|
||||||
component: Switch,
|
Component: Switch,
|
||||||
props: {
|
props: {
|
||||||
state: 'transparencyEffects',
|
/* state: $settingsStore.transparencyEffects,
|
||||||
onChange: (isOn: boolean) => setSettingsValue('transparencyEffects', isOn)
|
onChange: (isOn: boolean) => settingsStore.setKey('transparencyEffects', isOn) */
|
||||||
|
state: test,
|
||||||
|
onChange: (isOn: boolean) => test = isOn
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Animated Background",
|
title: "Animated Background",
|
||||||
description: "Adds an animated background to BetterSEQTA. (May impact battery life)",
|
description: "Adds an animated background to BetterSEQTA. (May impact battery life)",
|
||||||
id: 2,
|
id: 2,
|
||||||
component: Switch,
|
Component: Switch as any,
|
||||||
props: {
|
props: {
|
||||||
state: 'animatedBackground',
|
state: $settingsStore.animatedbk,
|
||||||
onChange: (isOn: boolean) => setSettingsValue('animatedBackground', isOn)
|
onChange: (isOn: boolean) => settingsStore.setKey('animatedbk', isOn)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Animated Background Speed",
|
title: "Animated Background Speed",
|
||||||
description: "Controls the speed of the animated background.",
|
description: "Controls the speed of the animated background.",
|
||||||
id: 3,
|
id: 3,
|
||||||
component: Slider,
|
Component: Slider,
|
||||||
props: {
|
props: {
|
||||||
state: 'animatedBackgroundSpeed',
|
state: $settingsStore.bksliderinput,
|
||||||
onChange: (value: number) => setSettingsValue('animatedBackgroundSpeed', `${value}`)
|
onChange: (value: number) => settingsStore.setKey('bksliderinput', `${value}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
/* {
|
||||||
title: "Custom Theme Colour",
|
title: "Custom Theme Colour",
|
||||||
description: "Customise the overall theme colour of SEQTA Learn.",
|
description: "Customise the overall theme colour of SEQTA Learn.",
|
||||||
id: 4,
|
id: 4,
|
||||||
component: PickerSwatch
|
Component: PickerSwatch
|
||||||
},
|
}, */
|
||||||
{
|
|
||||||
title: "Telemetry",
|
|
||||||
description: "Enables/disables error collecting.",
|
|
||||||
id: 5,
|
|
||||||
component: Switch,
|
|
||||||
props: {
|
|
||||||
state: 'telemetry',
|
|
||||||
onChange: (isOn: boolean) => setSettingsValue('telemetry', isOn)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Edit Sidebar Layout",
|
title: "Edit Sidebar Layout",
|
||||||
description: "Customise the sidebar layout.",
|
description: "Customise the sidebar layout.",
|
||||||
id: 6,
|
id: 6,
|
||||||
component: Button,
|
Component: Button,
|
||||||
props: {
|
props: {
|
||||||
onClick: () => browser.runtime.sendMessage({ type: 'currentTab', info: 'EditSidebar' }),
|
onClick: () => browser.runtime.sendMessage({ type: 'currentTab', info: 'EditSidebar' }),
|
||||||
text: "Edit"
|
text: "Edit"
|
||||||
@@ -70,49 +66,48 @@
|
|||||||
title: "Notification Collector",
|
title: "Notification Collector",
|
||||||
description: "Uncaps the 9+ limit for notifications, showing the real number.",
|
description: "Uncaps the 9+ limit for notifications, showing the real number.",
|
||||||
id: 7,
|
id: 7,
|
||||||
component: Switch,
|
Component: Switch,
|
||||||
props: {
|
props: {
|
||||||
state: 'notificationCollector',
|
state: $settingsStore.notificationcollector,
|
||||||
onChange: (isOn: boolean) => setSettingsValue('notificationCollector', isOn)
|
onChange: (isOn: boolean) => settingsStore.setKey('notificationcollector', isOn)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Lesson Alerts",
|
title: "Lesson Alerts",
|
||||||
description: "Sends a native browser notification ~5 minutes prior to lessons.",
|
description: "Sends a native browser notification ~5 minutes prior to lessons.",
|
||||||
id: 8,
|
id: 8,
|
||||||
component: Switch,
|
Component: Switch,
|
||||||
props: {
|
props: {
|
||||||
state: 'lessonAlerts',
|
state: $settingsStore.lessonalert,
|
||||||
onChange: (isOn: boolean) => setSettingsValue('lessonAlerts', isOn)
|
onChange: (isOn: boolean) => settingsStore.setKey('lessonalert', isOn)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "BetterSEQTA+",
|
title: "BetterSEQTA+",
|
||||||
description: "Enables BetterSEQTA+ features",
|
description: "Enables BetterSEQTA+ features",
|
||||||
id: 9,
|
id: 9,
|
||||||
component: Switch,
|
Component: Switch,
|
||||||
props: {
|
props: {
|
||||||
state: 'betterSEQTAPlus',
|
state: $settingsStore.onoff,
|
||||||
onChange: (isOn: boolean) => setSettingsValue('betterSEQTAPlus', isOn)
|
onChange: (isOn: boolean) => settingsStore.setKey('onoff', isOn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col -mt-4 overflow-y-scroll divide-y divide-zinc-100 dark:divide-zinc-700">
|
<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)}
|
<Switch state={$settingsStore.DarkMode} onChange={(isOn: boolean) => settingsStore.setKey('DarkMode', isOn)} />
|
||||||
<div class="flex items-center justify-between px-4 py-3">
|
{#if settings}
|
||||||
<div class="pr-4">
|
{#each settings as { title, description, Component, props, id } (id)}
|
||||||
<h2 class="text-sm font-bold">{title}</h2>
|
<div class="flex items-center justify-between px-4 py-3">
|
||||||
<p class="text-xs">{description}</p>
|
<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>
|
||||||
<div>
|
{/each}
|
||||||
{#if props?.state !== undefined}
|
{/if}
|
||||||
<svelte:component this={Component} {...props} bind:setting={props.state} />
|
|
||||||
{:else}
|
|
||||||
<svelte:component this={Component} {...props} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</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 type { SettingsState } from './AppProps';
|
||||||
import { ComponentType } from 'svelte';
|
import type { Component } from 'svelte';
|
||||||
|
|
||||||
export interface SettingsList {
|
export interface SettingsList {
|
||||||
title: string;
|
title: string;
|
||||||
id: number;
|
id: number;
|
||||||
description: string;
|
description: string;
|
||||||
component: ComponentType;
|
Component: Component;
|
||||||
props?: any;
|
props?: any;
|
||||||
}
|
}
|
||||||
export interface SettingsProps {
|
export interface SettingsProps {
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
@@ -25,5 +25,5 @@
|
|||||||
"@/*": ["./src/*"]
|
"@/*": ["./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,
|
base64Loader,
|
||||||
//react(),
|
//react(),
|
||||||
svelte({
|
svelte({
|
||||||
emitCss: false,
|
emitCss: false
|
||||||
}),
|
}),
|
||||||
//million.vite({ auto: true }),
|
//million.vite({ auto: true }),
|
||||||
//MillionLint.vite(), /* enable for testing and debugging performance */
|
//MillionLint.vite(), /* enable for testing and debugging performance */
|
||||||
@@ -54,7 +54,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: resolve(__dirname, 'dist', mode),
|
outDir: resolve(__dirname, 'dist', mode),
|
||||||
emptyOutDir: true,
|
emptyOutDir: false,
|
||||||
minify: false,
|
minify: false,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
|
|||||||
Reference in New Issue
Block a user