Files
BetterSEQTA-Plus/docs/settings
2025-03-18 22:15:44 +11:00
..
2025-03-18 22:15:44 +11:00

BetterSEQTA+ Settings System

BetterSEQTA+ includes a powerful, type-safe settings system that uses TypeScript decorators to create a seamless API for plugin developers. This document explains how the settings system works and how to extend it.

Table of Contents

Overview

The settings system is built around TypeScript decorators and uses TypeScript's type system to provide type safety for plugin settings. The system consists of a few key components:

  1. Setting Type Interfaces in src/plugins/core/types.ts - Define the structure of the setting
  2. Setting Decorator Options in src/plugins/core/settings.ts - Define the options for the decorator
  3. Setting Decorators in src/plugins/core/settings.ts - Register the setting in the plugin
  4. BasePlugin Class in src/plugins/core/settings.ts - Base class that handles the settings

Existing Setting Types

BetterSEQTA+ currently supports the following setting types:

  • Boolean Settings - Simple on/off toggle
  • String Settings - Text input with optional validation
  • Number Settings - Numeric input with optional min/max/step
  • Select Settings - Dropdown selection from predefined options

Each setting type has a corresponding interface, options interface, and decorator.

Using Settings in Plugins

Here's how to use the settings system in a plugin:

import { BasePlugin, BooleanSetting, StringSetting } from '../../core/settings';

// Define the plugin settings class
class MyPluginClass extends BasePlugin {
  @BooleanSetting({
    default: true,
    title: "Enable Feature",
    description: "Enables the awesome feature."
  })
  enabled!: boolean;

  @StringSetting({
    default: "Default Value",
    title: "Custom Text",
    description: "Enter your custom text here.",
    maxLength: 100
  })
  customText!: string;
}

// Create an instance to extract settings
const settingsInstance = new MyPluginClass();

// Use in plugin definition
const myPlugin = {
  id: 'my-plugin',
  name: 'My Plugin',
  description: 'Does awesome things',
  version: '1.0.0',
  settings: settingsInstance.settings,
  run: async (api) => {
    // Access settings via api.settings
    if (api.settings.enabled) {
      console.log(api.settings.customText);
    }
    
    // Listen for settings changes
    api.settings.onChange('enabled', (value) => {
      console.log(`Enabled changed to: ${value}`);
    });
  }
};

Adding New Setting Types

To add a new setting type, you need to follow these steps:

1. Define the Setting Interface in src/plugins/core/types.ts

export interface ColorSetting {
  type: 'color';
  default: string; // HEX color code
  title: string;
  description?: string;
  presets?: string[]; // Optional color presets
}

// Update the PluginSetting type to include the new setting type
export type PluginSetting = BooleanSetting | StringSetting | NumberSetting | 
  SelectSetting<string> | ColorSetting;

// Update the SettingValue type helper
type SettingValue<T extends PluginSetting> = T extends BooleanSetting ? boolean :
  T extends StringSetting ? string :
  T extends NumberSetting ? number :
  T extends SelectSetting<infer O> ? O :
  T extends ColorSetting ? string : // Add this line
  never;

2. Define the Options Interface in src/plugins/core/settings.ts

interface ColorSettingOptions extends BaseSettingOptions {
  default: string; // HEX color
  presets?: string[];
}

3. Create the Decorator Function in src/plugins/core/settings.ts

export function ColorSetting(options: ColorSettingOptions): 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
    proto.settings[propertyKey] = {
      type: 'color',
      ...options
    };
  };
}

4. Create a Corresponding UI Component (if needed)

If your setting type needs a custom UI component, create one in the src/interface/components directory.

For example, you might create a ColorPicker.svelte component.

5. Update the Settings UI in src/interface/pages/settings/general.svelte

Update the getPluginSettingEntries function to handle your new setting type:

entries.push({
  title: setting.title || key,
  description: setting.description || '',
  id,
  Component: setting.type === 'boolean' ? Switch :
            setting.type === 'select' ? Select :
            setting.type === 'number' ? Slider : 
            setting.type === 'color' ? ColorPicker : // Add this line
            setting.type === 'string' ? (setting.options ? Select : null) : Switch,
  props: {
    state: pluginSettingsValues[plugin.pluginId]?.[key] ?? setting.default,
    onChange: (value: any) => {
      updatePluginSetting(plugin.pluginId, key, value);
    },
    options: setting.options,
    presets: setting.presets // Add this line if needed for your component
  }
});

Rendering in the UI

The settings UI is handled in src/interface/pages/settings/general.svelte. This file does a few key things:

  1. Loads settings for all plugins from storage
  2. Maps setting types to UI components
  3. Handles updating settings when users interact with the UI

For most setting types, you'll need to ensure there's a corresponding Svelte component in the src/interface/components directory that can render and edit the setting value.

Example: Adding a Color Setting

Here's a complete example of adding a color setting type:

  1. Define the setting interface in types.ts:
export interface ColorSetting {
  type: 'color';
  default: string;
  title: string;
  description?: string;
  presets?: string[];
}

export type PluginSetting = BooleanSetting | StringSetting | NumberSetting | 
  SelectSetting<string> | ColorSetting;
  
type SettingValue<T extends PluginSetting> = T extends BooleanSetting ? boolean :
  T extends StringSetting ? string :
  T extends NumberSetting ? number :
  T extends SelectSetting<infer O> ? O :
  T extends ColorSetting ? string :
  never;
  1. Create the options interface and decorator in settings.ts:
interface ColorSettingOptions extends BaseSettingOptions {
  default: string;
  presets?: string[];
}

export function ColorSetting(options: ColorSettingOptions): PropertyDecorator {
  return (target: Object, propertyKey: string | symbol) => {
    const proto = target.constructor.prototype;
    if (!proto.hasOwnProperty('settings')) {
      proto.settings = {};
    }
    
    proto.settings[propertyKey] = {
      type: 'color',
      ...options
    };
  };
}
  1. Create a ColorPicker component in src/interface/components/ColorPicker.svelte:
<script lang="ts">
  export let state = "#000000";
  export let onChange = (value: string) => {};
  export let presets: string[] = ["#ff0000", "#00ff00", "#0000ff"];
</script>

<div class="color-picker">
  <input 
    type="color" 
    value={state} 
    on:change={(e) => onChange(e.currentTarget.value)} 
  />
  <div class="presets">
    {#each presets as preset}
      <button 
        class="preset" 
        style="background-color: {preset}" 
        on:click={() => onChange(preset)}
      ></button>
    {/each}
  </div>
</div>

<style>
  .color-picker {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }
  
  .presets {
    display: flex;
    gap: 0.25rem;
  }
  
  .preset {
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    border: 1px solid #ccc;
    cursor: pointer;
  }
</style>
  1. Update the UI renderer in general.svelte:
Component: setting.type === 'boolean' ? Switch :
          setting.type === 'select' ? Select :
          setting.type === 'number' ? Slider : 
          setting.type === 'color' ? ColorPicker :
          setting.type === 'string' ? (setting.options ? Select : null) : Switch,
  1. Use the new setting type in a plugin:
class ThemePlugin extends BasePlugin {
  @ColorSetting({
    default: "#4285f4",
    title: "Primary Color",
    description: "The main color for the theme",
    presets: ["#4285f4", "#ea4335", "#fbbc05", "#34a853"]
  })
  primaryColor!: string;
}

With these steps, you've added a completely new setting type to the BetterSEQTA+ plugin system!