mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
dev
This commit is contained in:
@@ -41,15 +41,28 @@ function createSEQTAAPI(): SEQTAAPI {
|
||||
|
||||
function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): SettingsAPI<T> & { loaded: Promise<void> } {
|
||||
const storageKey = `plugin.${plugin.id}.settings`;
|
||||
const listeners = new Map<keyof T, Set<(value: any) => void>>();
|
||||
let settings: { [K in keyof T]: T[K]['default'] };
|
||||
|
||||
// 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]> };
|
||||
const storageListeners = new Set<(changes: { [key: string]: any }, area: string) => void>();
|
||||
|
||||
// Initialize settings with defaults
|
||||
// Initialize settings with defaults and proper typing
|
||||
settings = Object.entries(plugin.settings).reduce((acc, [key, setting]) => {
|
||||
acc[key as keyof T] = setting.default;
|
||||
// Extract the value from the default based on the setting type
|
||||
if (setting.type === 'boolean') {
|
||||
acc[key as keyof T] = setting.default as SettingValue<T[keyof T]>;
|
||||
} else if (setting.type === 'number') {
|
||||
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]: T[K]['default'] });
|
||||
}, {} as { [K in keyof T]: SettingValue<T[K]> });
|
||||
|
||||
// Create a promise that resolves when settings are loaded
|
||||
const loaded = (async () => {
|
||||
@@ -58,9 +71,22 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
||||
if (stored[storageKey]) {
|
||||
Object.entries(stored[storageKey]).forEach(([key, value]) => {
|
||||
if (key in settings) {
|
||||
settings[key as keyof T] = value as any;
|
||||
// Use proper type assertion based on the setting type
|
||||
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
|
||||
listeners.get(key as keyof T)?.forEach(callback => callback(value));
|
||||
listeners.get(key as keyof T)?.forEach(callback =>
|
||||
callback(settings[key as keyof T] as SettingValue<T[keyof T]>)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -76,8 +102,24 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
||||
if (newValue) {
|
||||
// Update settings and notify listeners
|
||||
Object.entries(newValue).forEach(([key, value]) => {
|
||||
settings[key as keyof T] = value as any;
|
||||
listeners.get(key as keyof T)?.forEach(callback => callback(value));
|
||||
if (key in settings) {
|
||||
// Use proper type assertion based on the setting type
|
||||
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]>)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -89,14 +131,14 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
||||
const proxy = new Proxy(settings, {
|
||||
get(target, prop: string) {
|
||||
if (prop === 'onChange') {
|
||||
return (key: keyof T, callback: (value: any) => void) => {
|
||||
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);
|
||||
listeners.get(key)!.add(callback as (value: SettingValue<T[keyof T]>) => void);
|
||||
return {
|
||||
unregister: () => {
|
||||
listeners.get(key)?.delete(callback);
|
||||
listeners.get(key)?.delete(callback as (value: SettingValue<T[keyof T]>) => void);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -108,7 +150,20 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
||||
},
|
||||
set(target, prop: string, value: any) {
|
||||
if (prop === 'onChange' || prop === 'offChange' || prop === 'loaded') return false;
|
||||
target[prop as keyof T] = value;
|
||||
|
||||
// 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({
|
||||
@@ -116,7 +171,9 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
||||
});
|
||||
|
||||
// Notify listeners
|
||||
listeners.get(prop as keyof T)?.forEach(callback => callback(value));
|
||||
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> };
|
||||
|
||||
@@ -19,6 +19,7 @@ export class PluginManager {
|
||||
private eventBacklog: Map<string, any[]> = new Map();
|
||||
private cleanupFunctions: Map<string, () => void> = new Map();
|
||||
private listeners: Map<string, Set<(...args: any[]) => void>> = new Map();
|
||||
private styleElements: Map<string, HTMLStyleElement> = new Map();
|
||||
|
||||
private constructor() {
|
||||
this.setupPluginStateListener();
|
||||
@@ -89,6 +90,14 @@ export class PluginManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Inject plugin styles if provided
|
||||
if (plugin.styles) {
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.textContent = plugin.styles;
|
||||
document.head.appendChild(styleElement);
|
||||
this.styleElements.set(pluginId, styleElement);
|
||||
}
|
||||
|
||||
// Wait for both settings and storage to be loaded before starting the plugin
|
||||
await Promise.all([
|
||||
@@ -123,6 +132,13 @@ export class PluginManager {
|
||||
}
|
||||
|
||||
public async stopPlugin(pluginId: string): Promise<void> {
|
||||
// Remove plugin styles
|
||||
const styleElement = this.styleElements.get(pluginId);
|
||||
if (styleElement) {
|
||||
styleElement.remove();
|
||||
this.styleElements.delete(pluginId);
|
||||
}
|
||||
|
||||
const cleanup = this.cleanupFunctions.get(pluginId);
|
||||
if (cleanup) {
|
||||
cleanup();
|
||||
|
||||
@@ -37,9 +37,9 @@ export function BooleanSetting(options: BooleanSettingOptions): PropertyDecorato
|
||||
proto.settings = {};
|
||||
}
|
||||
|
||||
// Add the setting to the prototype's settings object
|
||||
// Add the setting to the prototype's settings object with const assertion
|
||||
proto.settings[propertyKey] = {
|
||||
type: 'boolean',
|
||||
type: 'boolean' as const,
|
||||
...options
|
||||
};
|
||||
};
|
||||
@@ -53,9 +53,9 @@ export function StringSetting(options: StringSettingOptions): PropertyDecorator
|
||||
proto.settings = {};
|
||||
}
|
||||
|
||||
// Add the setting to the prototype's settings object
|
||||
// Add the setting to the prototype's settings object with const assertion
|
||||
proto.settings[propertyKey] = {
|
||||
type: 'string',
|
||||
type: 'string' as const,
|
||||
...options
|
||||
};
|
||||
};
|
||||
@@ -69,9 +69,9 @@ export function NumberSetting(options: NumberSettingOptions): PropertyDecorator
|
||||
proto.settings = {};
|
||||
}
|
||||
|
||||
// Add the setting to the prototype's settings object
|
||||
// Add the setting to the prototype's settings object with const assertion
|
||||
proto.settings[propertyKey] = {
|
||||
type: 'number',
|
||||
type: 'number' as const,
|
||||
...options
|
||||
};
|
||||
};
|
||||
@@ -85,9 +85,9 @@ export function SelectSetting<T extends string>(options: SelectSettingOptions<T>
|
||||
proto.settings = {};
|
||||
}
|
||||
|
||||
// Add the setting to the prototype's settings object
|
||||
// Add the setting to the prototype's settings object with const assertion
|
||||
proto.settings[propertyKey] = {
|
||||
type: 'select',
|
||||
type: 'select' as const,
|
||||
...options
|
||||
};
|
||||
};
|
||||
@@ -96,13 +96,13 @@ export function SelectSetting<T extends string>(options: SelectSettingOptions<T>
|
||||
// Base plugin class that handles settings
|
||||
export abstract class BasePlugin<T extends PluginSettings = PluginSettings> {
|
||||
// The settings property will be populated by decorators
|
||||
settings: T = {} as T;
|
||||
settings!: T;
|
||||
|
||||
constructor() {
|
||||
// Copy settings from the prototype to the instance
|
||||
// This ensures that each instance has its own settings object
|
||||
if (this.constructor.prototype.settings) {
|
||||
this.settings = { ...this.constructor.prototype.settings };
|
||||
this.settings = { ...this.constructor.prototype.settings } as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export type PluginSettings = {
|
||||
}
|
||||
|
||||
// Helper type to extract the actual value type from a setting
|
||||
type SettingValue<T extends PluginSetting> = T extends BooleanSetting ? boolean :
|
||||
export type SettingValue<T extends PluginSetting> = T extends BooleanSetting ? boolean :
|
||||
T extends StringSetting ? string :
|
||||
T extends NumberSetting ? number :
|
||||
T extends SelectSetting<infer O> ? O :
|
||||
@@ -50,7 +50,7 @@ type SettingValue<T extends PluginSetting> = T extends BooleanSetting ? boolean
|
||||
export type SettingsAPI<T extends PluginSettings> = {
|
||||
[K in keyof T]: SettingValue<T[K]>;
|
||||
} & {
|
||||
onChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => 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;
|
||||
loaded: Promise<void>; // Promise that resolves when settings are loaded
|
||||
}
|
||||
@@ -96,6 +96,7 @@ export interface Plugin<T extends PluginSettings = PluginSettings, S = any> {
|
||||
description: string;
|
||||
version: string;
|
||||
settings: T;
|
||||
styles?: string; // Optional CSS styles for the plugin
|
||||
disableToggle?: boolean; // Optional flag to show/hide the plugin's enable/disable toggle in settings
|
||||
run: (api: PluginAPI<T, S>) => void | Promise<void> | (() => void) | Promise<(() => void)>;
|
||||
}
|
||||
Reference in New Issue
Block a user