mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
fix: settings props not being correctly set
This commit is contained in:
@@ -1,13 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let { state, onChange } = $props<{ state: number, onChange: (value: number) => void }>();
|
let { state, onChange, min = 0, max = 100, step = 1 } = $props<{
|
||||||
let percentage = $derived((state / 100) * 100);
|
state: number,
|
||||||
|
onChange: (value: number) => void,
|
||||||
|
min?: number,
|
||||||
|
max?: number,
|
||||||
|
step?: number
|
||||||
|
}>();
|
||||||
|
let percentage = $derived(((state - min) / (max - min)) * 100);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
console.log('min / max / step', min, max, step);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative w-full max-w-lg mx-auto">
|
<div class="relative mx-auto w-full max-w-lg">
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min={min}
|
||||||
max="100"
|
max={max}
|
||||||
|
step={step}
|
||||||
bind:value={state}
|
bind:value={state}
|
||||||
style={`background: linear-gradient(to right, #30D259 ${percentage}%, #dddddd ${percentage}%)`}
|
style={`background: linear-gradient(to right, #30D259 ${percentage}%, #dddddd ${percentage}%)`}
|
||||||
onchange={(e) => onChange(Number(e.currentTarget.value))}
|
onchange={(e) => onChange(Number(e.currentTarget.value))}
|
||||||
|
|||||||
@@ -12,21 +12,24 @@
|
|||||||
import hideSensitiveContent from "@/seqta/ui/dev/hideSensitiveContent"
|
import hideSensitiveContent from "@/seqta/ui/dev/hideSensitiveContent"
|
||||||
|
|
||||||
import { getAllPluginSettings } from "@/plugins"
|
import { getAllPluginSettings } from "@/plugins"
|
||||||
|
import type { BooleanSetting, StringSetting, NumberSetting, SelectSetting } from "@/plugins/core/types"
|
||||||
|
|
||||||
interface PluginSetting {
|
// Union type representing all possible settings
|
||||||
id: string;
|
type SettingType =
|
||||||
title: string;
|
(Omit<BooleanSetting, 'type'> & { type: 'boolean', id: string }) |
|
||||||
description?: string;
|
(Omit<StringSetting, 'type'> & { type: 'string', id: string }) |
|
||||||
type: string;
|
(Omit<NumberSetting, 'type'> & { type: 'number', id: string }) |
|
||||||
default: any;
|
(Omit<SelectSetting<string>, 'type'> & {
|
||||||
options?: Array<{value: string, label: string}>;
|
type: 'select',
|
||||||
}
|
id: string,
|
||||||
|
options: Array<{ value: string, label: string }>
|
||||||
|
});
|
||||||
|
|
||||||
interface Plugin {
|
interface Plugin {
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
settings: Record<string, PluginSetting>;
|
settings: Record<string, SettingType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginSettings = getAllPluginSettings() as Plugin[];
|
const pluginSettings = getAllPluginSettings() as Plugin[];
|
||||||
@@ -90,7 +93,6 @@
|
|||||||
state: pluginSettingsValues[plugin.pluginId]?.enabled ?? true,
|
state: pluginSettingsValues[plugin.pluginId]?.enabled ?? true,
|
||||||
onChange: (value: boolean) => {
|
onChange: (value: boolean) => {
|
||||||
updatePluginSetting(plugin.pluginId, 'enabled', value);
|
updatePluginSetting(plugin.pluginId, 'enabled', value);
|
||||||
// The plugin manager will handle the actual enabling/disabling
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -99,24 +101,49 @@
|
|||||||
Object.entries(plugin.settings).forEach(([key, setting]) => {
|
Object.entries(plugin.settings).forEach(([key, setting]) => {
|
||||||
const id = getPluginSettingId(plugin.pluginId, key);
|
const id = getPluginSettingId(plugin.pluginId, key);
|
||||||
|
|
||||||
|
const props: Record<string, any> = {
|
||||||
|
state: pluginSettingsValues[plugin.pluginId]?.[key] ?? setting.default,
|
||||||
|
onChange: (value: any) => {
|
||||||
|
if (setting.type === 'number' && typeof value === 'string') {
|
||||||
|
value = parseFloat(value);
|
||||||
|
}
|
||||||
|
updatePluginSetting(plugin.pluginId, key, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (setting.type === 'number') {
|
||||||
|
if (typeof setting.min === 'number') props.min = setting.min;
|
||||||
|
if (typeof setting.max === 'number') props.max = setting.max;
|
||||||
|
if (typeof setting.step === 'number') props.step = setting.step;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setting.type === 'select') {
|
||||||
|
props.options = setting.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Component;
|
||||||
|
switch (setting.type) {
|
||||||
|
case 'boolean':
|
||||||
|
Component = Switch;
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
Component = Slider;
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
Component = Select;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Component = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Component) return;
|
||||||
|
|
||||||
entries.push({
|
entries.push({
|
||||||
title: setting.title || key,
|
title: setting.title || key,
|
||||||
description: setting.description || '',
|
description: setting.description || '',
|
||||||
id,
|
id,
|
||||||
Component: setting.type === 'boolean' ? Switch :
|
Component,
|
||||||
setting.type === 'select' ? Select :
|
props
|
||||||
setting.type === 'number' ? Slider :
|
|
||||||
setting.type === 'string' ? (setting.options ? Select : null) : Switch,
|
|
||||||
props: {
|
|
||||||
state: pluginSettingsValues[plugin.pluginId]?.[key] ?? setting.default,
|
|
||||||
onChange: (value: any) => {
|
|
||||||
if (setting.type === 'number' && typeof value === 'string') {
|
|
||||||
value = parseFloat(value);
|
|
||||||
}
|
|
||||||
updatePluginSetting(plugin.pluginId, key, value);
|
|
||||||
},
|
|
||||||
options: setting.options
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -155,26 +182,6 @@
|
|||||||
onChange: (isOn: boolean) => settingsState.transparencyEffects = isOn
|
onChange: (isOn: boolean) => settingsState.transparencyEffects = isOn
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Animated Background",
|
|
||||||
description: "Adds an animated background to BetterSEQTA. (May impact battery life)",
|
|
||||||
id: 2,
|
|
||||||
Component: Switch,
|
|
||||||
props: {
|
|
||||||
state: $settingsState.animatedbk,
|
|
||||||
onChange: (isOn: boolean) => settingsState.animatedbk = isOn
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Animated Background Speed",
|
|
||||||
description: "Controls the speed of the animated background.",
|
|
||||||
id: 3,
|
|
||||||
Component: Slider,
|
|
||||||
props: {
|
|
||||||
state: $settingsState.bksliderinput,
|
|
||||||
onChange: (value: number) => settingsState.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.",
|
||||||
|
|||||||
@@ -1,28 +1,34 @@
|
|||||||
import type { Plugin } from '../../core/types';
|
import { BasePlugin } from '../../core/settings';
|
||||||
|
import { type Plugin } from '@/plugins/core/types';
|
||||||
|
import { defineSettings, numberSetting, Setting } from '@/plugins/core/settingsHelpers';
|
||||||
import styles from './styles.css?inline';
|
import styles from './styles.css?inline';
|
||||||
import { BasePlugin, NumberSetting } from '../../core/settings';
|
|
||||||
|
|
||||||
class AnimatedBackgroundPluginClass extends BasePlugin {
|
const settings = defineSettings({
|
||||||
@NumberSetting({
|
speed: numberSetting({
|
||||||
default: 1,
|
default: 1,
|
||||||
title: "Animation Speed",
|
title: "Animation Speed",
|
||||||
description: "Controls the speed of the animated background",
|
description: "Controls how fast the background moves",
|
||||||
min: 0.1,
|
min: 0.1,
|
||||||
max: 2
|
max: 2,
|
||||||
|
step: 0.05
|
||||||
})
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
class AnimatedBackgroundPluginClass extends BasePlugin<typeof settings> {
|
||||||
|
@Setting(settings.speed)
|
||||||
speed!: number;
|
speed!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsInstance = new AnimatedBackgroundPluginClass();
|
const instance = new AnimatedBackgroundPluginClass();
|
||||||
|
|
||||||
const animatedBackgroundPlugin: Plugin<typeof settingsInstance.settings> = {
|
const animatedBackgroundPlugin: Plugin<typeof settings> = {
|
||||||
id: 'animated-background',
|
id: 'animated-background',
|
||||||
name: 'Animated Background',
|
name: 'Animated Background',
|
||||||
description: 'Adds an animated background to BetterSEQTA+',
|
description: 'Adds an animated background to BetterSEQTA+',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
styles,
|
styles: styles,
|
||||||
settings: settingsInstance.settings,
|
settings: instance.settings,
|
||||||
|
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
// Create the background elements
|
// Create the background elements
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
import type { Plugin } from '../../core/types';
|
import type { Plugin } from '@/plugins/core/types';
|
||||||
import { BasePlugin, BooleanSetting } from '../../core/settings';
|
import { BasePlugin } from '@/plugins/core/settings';
|
||||||
|
import { defineSettings, booleanSetting, Setting } from '@/plugins/core/settingsHelpers';
|
||||||
|
|
||||||
class TestPluginClass extends BasePlugin {
|
// Step 1: Define settings with proper typing
|
||||||
@BooleanSetting({
|
const settings = defineSettings({
|
||||||
|
someSetting: booleanSetting({
|
||||||
default: true,
|
default: true,
|
||||||
title: "Test Plugin",
|
title: "Test Plugin",
|
||||||
description: "Some random setting",
|
description: "Some random setting",
|
||||||
})
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Step 2: Create the plugin class with @Setting decorators
|
||||||
|
class TestPluginClass extends BasePlugin<typeof settings> {
|
||||||
|
@Setting(settings.someSetting)
|
||||||
someSetting!: boolean;
|
someSetting!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 3: Instantiate and plug it in
|
||||||
const settingsInstance = new TestPluginClass();
|
const settingsInstance = new TestPluginClass();
|
||||||
|
|
||||||
const testPlugin: Plugin<typeof settingsInstance.settings> = {
|
const testPlugin: Plugin<typeof settings> = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
name: 'Test Plugin',
|
name: 'Test Plugin',
|
||||||
description: 'A test plugin for BetterSEQTA+',
|
description: 'A test plugin for BetterSEQTA+',
|
||||||
@@ -24,6 +32,8 @@ const testPlugin: Plugin<typeof settingsInstance.settings> = {
|
|||||||
|
|
||||||
const { unregister } = api.seqta.onPageChange((page) => {
|
const { unregister } = api.seqta.onPageChange((page) => {
|
||||||
console.log('Page changed to', page);
|
console.log('Page changed to', page);
|
||||||
|
|
||||||
|
console.log('Current setting value:', api.settings.someSetting);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { EventsAPI, Plugin, PluginAPI, PluginSettings, SEQTAAPI, SettingsAPI, StorageAPI } from './types';
|
import type { EventsAPI, Plugin, PluginAPI, PluginSettings, SEQTAAPI, SettingsAPI, SettingValue, StorageAPI } from './types';
|
||||||
import { eventManager } from '@/seqta/utils/listeners/EventManager';
|
import { eventManager } from '@/seqta/utils/listeners/EventManager';
|
||||||
import ReactFiber from '@/seqta/utils/ReactFiber';
|
import ReactFiber from '@/seqta/utils/ReactFiber';
|
||||||
import browser from 'webextension-polyfill';
|
import browser from 'webextension-polyfill';
|
||||||
@@ -41,28 +41,17 @@ function createSEQTAAPI(): SEQTAAPI {
|
|||||||
|
|
||||||
function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): SettingsAPI<T> & { loaded: Promise<void> } {
|
function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): SettingsAPI<T> & { loaded: Promise<void> } {
|
||||||
const storageKey = `plugin.${plugin.id}.settings`;
|
const storageKey = `plugin.${plugin.id}.settings`;
|
||||||
|
const listeners = new Map<keyof T, Set<(value: any) => void>>();
|
||||||
// Use SettingValue to properly type the listeners
|
|
||||||
// This ensures callbacks get correctly typed parameters
|
|
||||||
const listeners = new Map<keyof T, Set<(value: SettingValue<T[keyof T]>) => void>>();
|
|
||||||
|
|
||||||
let settings: { [K in keyof T]: SettingValue<T[K]> };
|
let settings: { [K in keyof T]: SettingValue<T[K]> };
|
||||||
const storageListeners = new Set<(changes: { [key: string]: any }, area: string) => void>();
|
const storageListeners = new Set<(changes: { [key: string]: any }, area: string) => void>();
|
||||||
|
|
||||||
// Initialize settings with defaults and proper typing
|
// Initialize settings with defaults
|
||||||
settings = Object.entries(plugin.settings).reduce((acc, [key, setting]) => {
|
const defaultSettings = {} as { [K in keyof T]: SettingValue<T[K]> };
|
||||||
// Extract the value from the default based on the setting type
|
for (const key in plugin.settings) {
|
||||||
if (setting.type === 'boolean') {
|
defaultSettings[key] = plugin.settings[key].default as SettingValue<T[typeof key]>;
|
||||||
acc[key as keyof T] = setting.default as SettingValue<T[keyof T]>;
|
}
|
||||||
} else if (setting.type === 'number') {
|
settings = defaultSettings;
|
||||||
acc[key as keyof T] = setting.default as SettingValue<T[keyof T]>;
|
|
||||||
} else if (setting.type === 'string') {
|
|
||||||
acc[key as keyof T] = setting.default as SettingValue<T[keyof T]>;
|
|
||||||
} else if (setting.type === 'select') {
|
|
||||||
acc[key as keyof T] = setting.default as SettingValue<T[keyof T]>;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {} as { [K in keyof T]: SettingValue<T[K]> });
|
|
||||||
|
|
||||||
// Create a promise that resolves when settings are loaded
|
// Create a promise that resolves when settings are loaded
|
||||||
const loaded = (async () => {
|
const loaded = (async () => {
|
||||||
@@ -71,22 +60,9 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
|||||||
if (stored[storageKey]) {
|
if (stored[storageKey]) {
|
||||||
Object.entries(stored[storageKey]).forEach(([key, value]) => {
|
Object.entries(stored[storageKey]).forEach(([key, value]) => {
|
||||||
if (key in settings) {
|
if (key in settings) {
|
||||||
// Use proper type assertion based on the setting type
|
settings[key as keyof T] = value as any;
|
||||||
const settingType = plugin.settings[key as keyof T].type;
|
|
||||||
if (settingType === 'boolean' && typeof value === 'boolean') {
|
|
||||||
settings[key as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
} else if (settingType === 'number' && typeof value === 'number') {
|
|
||||||
settings[key as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
} else if (settingType === 'string' && typeof value === 'string') {
|
|
||||||
settings[key as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
} else if (settingType === 'select' && typeof value === 'string') {
|
|
||||||
settings[key as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify any listeners that might have been registered already
|
// Notify any listeners that might have been registered already
|
||||||
listeners.get(key as keyof T)?.forEach(callback =>
|
listeners.get(key as keyof T)?.forEach(callback => callback(value));
|
||||||
callback(settings[key as keyof T] as SettingValue<T[keyof T]>)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -102,24 +78,8 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
|||||||
if (newValue) {
|
if (newValue) {
|
||||||
// Update settings and notify listeners
|
// Update settings and notify listeners
|
||||||
Object.entries(newValue).forEach(([key, value]) => {
|
Object.entries(newValue).forEach(([key, value]) => {
|
||||||
if (key in settings) {
|
settings[key as keyof T] = value as any;
|
||||||
// Use proper type assertion based on the setting type
|
listeners.get(key as keyof T)?.forEach(callback => callback(value));
|
||||||
const settingType = plugin.settings[key as keyof T].type;
|
|
||||||
if (settingType === 'boolean' && typeof value === 'boolean') {
|
|
||||||
settings[key as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
} else if (settingType === 'number' && typeof value === 'number') {
|
|
||||||
settings[key as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
} else if (settingType === 'string' && typeof value === 'string') {
|
|
||||||
settings[key as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
} else if (settingType === 'select' && typeof value === 'string') {
|
|
||||||
settings[key as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify listeners with the correctly typed value
|
|
||||||
listeners.get(key as keyof T)?.forEach(callback =>
|
|
||||||
callback(settings[key as keyof T] as SettingValue<T[keyof T]>)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,56 +87,44 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
|||||||
browser.storage.onChanged.addListener(handleStorageChange);
|
browser.storage.onChanged.addListener(handleStorageChange);
|
||||||
storageListeners.add(handleStorageChange);
|
storageListeners.add(handleStorageChange);
|
||||||
|
|
||||||
// Create a proxy to handle direct property access
|
const baseSettings = {} as { [K in keyof T]: SettingValue<T[K]> };
|
||||||
const proxy = new Proxy(settings, {
|
for (const key in plugin.settings) {
|
||||||
get(target, prop: string) {
|
baseSettings[key] = plugin.settings[key].default as SettingValue<T[typeof key]>;
|
||||||
if (prop === 'onChange') {
|
}
|
||||||
return <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => {
|
|
||||||
if (!listeners.has(key)) {
|
const settingsWithMeta = {
|
||||||
listeners.set(key, new Set());
|
...baseSettings,
|
||||||
}
|
onChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => {
|
||||||
listeners.get(key)!.add(callback as (value: SettingValue<T[keyof T]>) => void);
|
if (!listeners.has(key)) {
|
||||||
return {
|
listeners.set(key, new Set());
|
||||||
unregister: () => {
|
|
||||||
listeners.get(key)?.delete(callback as (value: SettingValue<T[keyof T]>) => void);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if (prop === 'loaded') {
|
listeners.get(key)!.add(callback);
|
||||||
return loaded;
|
return {
|
||||||
}
|
unregister: () => {
|
||||||
return target[prop as keyof T];
|
listeners.get(key)!.delete(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
set(target, prop: string, value: any) {
|
offChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => {
|
||||||
|
listeners.get(key)?.delete(callback);
|
||||||
|
},
|
||||||
|
loaded
|
||||||
|
};
|
||||||
|
|
||||||
|
const proxy = new Proxy(settingsWithMeta, {
|
||||||
|
get(target, prop) {
|
||||||
|
return target[prop as keyof typeof target];
|
||||||
|
},
|
||||||
|
set(target, prop, value) {
|
||||||
if (prop === 'onChange' || prop === 'offChange' || prop === 'loaded') return false;
|
if (prop === 'onChange' || prop === 'offChange' || prop === 'loaded') return false;
|
||||||
|
|
||||||
// Try to apply the right type based on the setting definition
|
target[prop as keyof T] = value;
|
||||||
if (prop in plugin.settings) {
|
browser.storage.local.set({ [storageKey]: baseSettings }); // Only store base settings
|
||||||
const settingType = plugin.settings[prop as keyof T].type;
|
listeners.get(prop as keyof T)?.forEach(callback => callback(value));
|
||||||
if (settingType === 'boolean' && typeof value === 'boolean') {
|
|
||||||
target[prop as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
} else if (settingType === 'number' && typeof value === 'number') {
|
|
||||||
target[prop as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
} else if (settingType === 'string' && typeof value === 'string') {
|
|
||||||
target[prop as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
} else if (settingType === 'select' && typeof value === 'string') {
|
|
||||||
target[prop as keyof T] = value as SettingValue<T[keyof T]>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store all settings under the plugin's settings key
|
|
||||||
browser.storage.local.set({
|
|
||||||
[storageKey]: target
|
|
||||||
});
|
|
||||||
|
|
||||||
// Notify listeners
|
|
||||||
listeners.get(prop as keyof T)?.forEach(callback =>
|
|
||||||
callback(target[prop as keyof T] as SettingValue<T[keyof T]>)
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
},
|
}
|
||||||
}) as SettingsAPI<T> & { loaded: Promise<void> };
|
}) as SettingsAPI<T>;
|
||||||
|
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-15
@@ -1,4 +1,4 @@
|
|||||||
import type { Plugin, PluginSettings } from './types';
|
import type { Plugin, PluginSettings, BooleanSetting, StringSetting, NumberSetting, SelectSetting } from './types';
|
||||||
import { createPluginAPI } from './createAPI';
|
import { createPluginAPI } from './createAPI';
|
||||||
import browser from 'webextension-polyfill';
|
import browser from 'webextension-polyfill';
|
||||||
|
|
||||||
@@ -164,25 +164,29 @@ export class PluginManager {
|
|||||||
public getAllPluginSettings(): Array<{
|
public getAllPluginSettings(): Array<{
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
description: string;
|
||||||
settings: {
|
settings: {
|
||||||
[key: string]: {
|
[key: string]: (Omit<BooleanSetting, 'type'> & { type: 'boolean', id: string }) |
|
||||||
id: string;
|
(Omit<StringSetting, 'type'> & { type: 'string', id: string }) |
|
||||||
title: string;
|
(Omit<NumberSetting, 'type'> & { type: 'number', id: string }) |
|
||||||
description?: string;
|
(Omit<SelectSetting<string>, 'type'> & { type: 'select', id: string, options: Array<{ value: string, label: string }> });
|
||||||
type: string;
|
|
||||||
default: any;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}> {
|
}> {
|
||||||
return Array.from(this.plugins.entries()).map(([id, plugin]) => {
|
return Array.from(this.plugins.entries()).map(([id, plugin]) => {
|
||||||
const settingsEntries = Object.entries(plugin.settings).map(([key, setting]) => {
|
const settingsEntries = Object.entries(plugin.settings).map(([key, setting]) => {
|
||||||
return [key, {
|
const settingObj = setting as any;
|
||||||
id: key,
|
// Create a copy of the setting object without any functions
|
||||||
title: (setting as any).title || key,
|
const result: any = Object.fromEntries(
|
||||||
description: (setting as any).description || '',
|
Object.entries(settingObj)
|
||||||
type: (setting as any).type,
|
.filter(([_, value]) => typeof value !== 'function')
|
||||||
default: (setting as any).default
|
);
|
||||||
}];
|
|
||||||
|
// Ensure required properties are present
|
||||||
|
result.id = key;
|
||||||
|
result.title = result.title || key;
|
||||||
|
result.description = result.description || '';
|
||||||
|
|
||||||
|
return [key, result];
|
||||||
});
|
});
|
||||||
|
|
||||||
if (plugin.disableToggle) {
|
if (plugin.disableToggle) {
|
||||||
|
|||||||
@@ -1,108 +1,39 @@
|
|||||||
import type { PluginSettings } from './types';
|
import type { PluginSettings } from './types';
|
||||||
|
|
||||||
// Base interfaces for our settings
|
export function Setting(settingDef: any): PropertyDecorator {
|
||||||
interface BaseSettingOptions {
|
return (target, propertyKey) => {
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BooleanSettingOptions extends BaseSettingOptions {
|
|
||||||
default: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface StringSettingOptions extends BaseSettingOptions {
|
|
||||||
default: string;
|
|
||||||
maxLength?: number;
|
|
||||||
pattern?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NumberSettingOptions extends BaseSettingOptions {
|
|
||||||
default: number;
|
|
||||||
min?: number;
|
|
||||||
max?: number;
|
|
||||||
step?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectSettingOptions<T extends string> extends BaseSettingOptions {
|
|
||||||
default: T;
|
|
||||||
options: readonly T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// The actual decorators
|
|
||||||
export function BooleanSetting(options: BooleanSettingOptions): PropertyDecorator {
|
|
||||||
return (target: Object, propertyKey: string | symbol) => {
|
|
||||||
// Ensure the settings property exists on the constructor's prototype
|
|
||||||
const proto = target.constructor.prototype;
|
const proto = target.constructor.prototype;
|
||||||
if (!proto.hasOwnProperty('settings')) {
|
if (!proto.hasOwnProperty('settings')) {
|
||||||
proto.settings = {};
|
Object.defineProperty(proto, 'settings', {
|
||||||
|
value: {},
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the setting to the prototype's settings object with const assertion
|
proto.settings[propertyKey] = settingDef;
|
||||||
proto.settings[propertyKey] = {
|
|
||||||
type: 'boolean' as const,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function StringSetting(options: StringSettingOptions): PropertyDecorator {
|
|
||||||
return (target: Object, propertyKey: string | symbol) => {
|
|
||||||
// Ensure the settings property exists on the constructor's prototype
|
|
||||||
const proto = target.constructor.prototype;
|
|
||||||
if (!proto.hasOwnProperty('settings')) {
|
|
||||||
proto.settings = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the setting to the prototype's settings object with const assertion
|
|
||||||
proto.settings[propertyKey] = {
|
|
||||||
type: 'string' as const,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NumberSetting(options: NumberSettingOptions): PropertyDecorator {
|
|
||||||
return (target: Object, propertyKey: string | symbol) => {
|
|
||||||
// Ensure the settings property exists on the constructor's prototype
|
|
||||||
const proto = target.constructor.prototype;
|
|
||||||
if (!proto.hasOwnProperty('settings')) {
|
|
||||||
proto.settings = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the setting to the prototype's settings object with const assertion
|
|
||||||
proto.settings[propertyKey] = {
|
|
||||||
type: 'number' as const,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SelectSetting<T extends string>(options: SelectSettingOptions<T>): PropertyDecorator {
|
|
||||||
return (target: Object, propertyKey: string | symbol) => {
|
|
||||||
// Ensure the settings property exists on the constructor's prototype
|
|
||||||
const proto = target.constructor.prototype;
|
|
||||||
if (!proto.hasOwnProperty('settings')) {
|
|
||||||
proto.settings = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the setting to the prototype's settings object with const assertion
|
|
||||||
proto.settings[propertyKey] = {
|
|
||||||
type: 'select' as const,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base plugin class that handles settings
|
// Base plugin class that handles settings
|
||||||
export abstract class BasePlugin<T extends PluginSettings = PluginSettings> {
|
export abstract class BasePlugin<T extends PluginSettings = PluginSettings> {
|
||||||
// The settings property will be populated by decorators
|
// The settings property will be populated by decorators
|
||||||
settings!: T;
|
// Keep the instance property and constructor logic as is,
|
||||||
|
// as changing it would require changing animated-background/index.ts
|
||||||
|
settings!: T; // Use definite assignment assertion
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Copy settings from the prototype to the instance
|
// Copy settings from the prototype to the instance
|
||||||
// This ensures that each instance has its own settings object
|
// This ensures that each instance has its own settings object
|
||||||
if (this.constructor.prototype.settings) {
|
// IMPORTANT: Ensure the prototype actually HAS settings before copying
|
||||||
|
if (this.constructor.prototype.hasOwnProperty('settings')) {
|
||||||
|
// Deep clone might be safer if settings objects become complex,
|
||||||
|
// but a shallow clone is usually fine for this structure.
|
||||||
this.settings = { ...this.constructor.prototype.settings } as T;
|
this.settings = { ...this.constructor.prototype.settings } as T;
|
||||||
|
} else {
|
||||||
|
// Fallback if decorators somehow didn't run or add the property
|
||||||
|
this.settings = {} as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { NumberSetting, BooleanSetting, StringSetting, SelectSetting } from './types';
|
||||||
|
|
||||||
|
export function numberSetting(options: Omit<NumberSetting, 'type'>): NumberSetting {
|
||||||
|
return {
|
||||||
|
type: 'number',
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function booleanSetting(options: Omit<BooleanSetting, 'type'>): BooleanSetting {
|
||||||
|
return {
|
||||||
|
type: 'boolean',
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringSetting(options: Omit<StringSetting, 'type'>): StringSetting {
|
||||||
|
return {
|
||||||
|
type: 'string',
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectSetting<T extends string>(options: Omit<SelectSetting<T>, 'type'>): SelectSetting<T> {
|
||||||
|
return {
|
||||||
|
type: 'select',
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defineSettings<T extends Record<string, any>>(settings: T): T {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Setting(settingDef: any): PropertyDecorator {
|
||||||
|
return (target, propertyKey) => {
|
||||||
|
const proto = target.constructor.prototype;
|
||||||
|
if (!proto.hasOwnProperty('settings')) {
|
||||||
|
Object.defineProperty(proto, 'settings', {
|
||||||
|
value: {},
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
proto.settings[propertyKey] = settingDef;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ export type SettingsAPI<T extends PluginSettings> = {
|
|||||||
} & {
|
} & {
|
||||||
onChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => { unregister: () => void };
|
onChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => { unregister: () => void };
|
||||||
offChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => void;
|
offChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => void;
|
||||||
loaded: Promise<void>; // Promise that resolves when settings are loaded
|
loaded: Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SEQTAAPI {
|
export interface SEQTAAPI {
|
||||||
|
|||||||
Reference in New Issue
Block a user