fix: settings props not being correctly set

This commit is contained in:
SethBurkart123
2025-03-30 12:04:39 +11:00
parent 19cc1a5600
commit 647a32fbac
9 changed files with 247 additions and 280 deletions
+16 -5
View File
@@ -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))}
+56 -49
View File
@@ -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[];
@@ -75,10 +78,10 @@
function getPluginSettingEntries() { function getPluginSettingEntries() {
const entries: any[] = []; const entries: any[] = [];
pluginSettings.forEach(plugin => { pluginSettings.forEach(plugin => {
if (Object.keys(plugin.settings).length === 0) return; if (Object.keys(plugin.settings).length === 0) return;
// Add enable/disable toggle if plugin has disableToggle set // Add enable/disable toggle if plugin has disableToggle set
if ((plugin as any).disableToggle) { if ((plugin as any).disableToggle) {
entries.push({ entries.push({
@@ -90,37 +93,61 @@
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
} }
} }
}); });
} }
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
}
}); });
}); });
}); });
return entries; return entries;
} }
@@ -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
+16 -6
View File
@@ -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 () => {
@@ -33,4 +43,4 @@ const testPlugin: Plugin<typeof settingsInstance.settings> = {
} }
}; };
export default testPlugin; export default testPlugin;
+51 -103
View File
@@ -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,57 +87,45 @@ 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)) {
listeners.set(key, new Set());
}
listeners.get(key)!.add(callback as (value: SettingValue<T[keyof T]>) => void);
return {
unregister: () => {
listeners.get(key)?.delete(callback as (value: SettingValue<T[keyof T]>) => void);
}
};
};
}
if (prop === 'loaded') {
return loaded;
}
return target[prop as keyof T];
},
set(target, prop: string, value: any) {
if (prop === 'onChange' || prop === 'offChange' || prop === 'loaded') return false;
// Try to apply the right type based on the setting definition
if (prop in plugin.settings) {
const settingType = plugin.settings[prop as keyof T].type;
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;
},
}) as SettingsAPI<T> & { loaded: Promise<void> };
const settingsWithMeta = {
...baseSettings,
onChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => {
if (!listeners.has(key)) {
listeners.set(key, new Set());
}
listeners.get(key)!.add(callback);
return {
unregister: () => {
listeners.get(key)!.delete(callback);
}
};
},
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;
target[prop as keyof T] = value;
browser.storage.local.set({ [storageKey]: baseSettings }); // Only store base settings
listeners.get(prop as keyof T)?.forEach(callback => callback(value));
return true;
}
}) as SettingsAPI<T>;
return proxy; return proxy;
} }
+19 -15
View File
@@ -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) {
+21 -90
View File
@@ -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] = {
type: 'boolean' as const,
...options
};
};
}
export function StringSetting(options: StringSettingOptions): PropertyDecorator { proto.settings[propertyKey] = settingDef;
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;
} }
} }
} }
+50
View File
@@ -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;
};
}
+2 -2
View File
@@ -52,8 +52,8 @@ 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 {
onMount: (selector: string, callback: (element: Element) => void) => { unregister: () => void }; onMount: (selector: string, callback: (element: Element) => void) => { unregister: () => void };