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

8.6 KiB

Creating Plugins with Decorator-Based Settings

This guide will walk you through creating a BetterSEQTA+ plugin using the new decorator-based settings system.

Prerequisites

  • Understand basic TypeScript concepts (classes, interfaces, decorators)
  • Familiarity with the BetterSEQTA+ plugin system

Plugin Structure

A typical plugin consists of:

  1. A settings class that defines the plugin's settings using decorators
  2. The plugin definition object
  3. The actual plugin functionality

Step by Step Guide

1. Create a Plugin File

Start by creating a new file in the src/plugins/built-in directory. For example, myFeature/index.ts.

2. Define Storage Type (Optional)

If your plugin needs to store data, define a storage interface:

interface MyFeatureStorage {
  lastUsed: string;
  favoriteItems: string[];
}

3. Create a Settings Class

Create a class that extends BasePlugin and use decorators to define settings:

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

class MyFeaturePluginClass extends BasePlugin {
  @BooleanSetting({
    default: true,
    title: "Enable My Feature",
    description: "Enables the awesome new feature."
  })
  enabled!: boolean;
  
  @StringSetting({
    default: "Default text",
    title: "Custom Message",
    description: "Sets a custom message for the feature",
    maxLength: 100
  })
  message!: string;
  
  @NumberSetting({
    default: 5,
    title: "Refresh Interval",
    description: "How often to refresh the data (in seconds)",
    min: 1,
    max: 60,
    step: 1
  })
  refreshInterval!: number;
  
  @SelectSetting({
    default: "small",
    options: ["small", "medium", "large"] as const,
    title: "Display Size",
    description: "Control how large the feature appears"
  })
  displaySize!: "small" | "medium" | "large";
}

4. Create a Plugin Instance

Create an instance of your settings class and define the plugin object:

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

const myFeaturePlugin: Plugin<typeof settingsInstance.settings, MyFeatureStorage> = {
  id: 'myFeature',
  name: 'My Awesome Feature',
  description: 'Adds an awesome new feature to SEQTA',
  version: '1.0.0',
  settings: settingsInstance.settings,
  run: async (api) => {
    // Plugin implementation goes here
  }
};

export default myFeaturePlugin;

5. Implement Plugin Functionality

Implement your plugin's functionality in the run function:

run: async (api) => {
  // Initialize storage with defaults if needed
  if (api.storage.lastUsed === undefined) {
    api.storage.lastUsed = new Date().toISOString();
  }
  
  if (api.storage.favoriteItems === undefined) {
    api.storage.favoriteItems = [];
  }
  
  // Only run if enabled
  if (!api.settings.enabled) return;
  
  // Main plugin logic
  const initializeFeature = () => {
    console.log(`Initializing feature with message: ${api.settings.message}`);
    console.log(`Using display size: ${api.settings.displaySize}`);
    
    // Set up refreshing
    const intervalId = setInterval(() => {
      refreshData();
    }, api.settings.refreshInterval * 1000);
    
    // Clean up function returned here
    return () => {
      clearInterval(intervalId);
      console.log('Feature cleaned up');
    };
  };
  
  const refreshData = () => {
    console.log('Refreshing data...');
    api.storage.lastUsed = new Date().toISOString();
  };
  
  // Listen for elements we need
  api.seqta.onMount('.some-element', (element) => {
    // Do something when element appears
  });
  
  // Listen for settings changes
  api.settings.onChange('refreshInterval', (newValue) => {
    console.log(`Refresh interval changed to ${newValue} seconds`);
  });
  
  // Return cleanup function
  return initializeFeature();
}

6. Register the Plugin

Make sure your plugin is registered in the plugin system. In the src/plugins/index.ts file, add your plugin to the list of built-in plugins:

import myFeaturePlugin from './built-in/myFeature';

// Add your plugin to this array
const builtInPlugins = [
  // ... other plugins
  myFeaturePlugin,
];

Advanced Features

Reacting to Settings Changes

You can listen for settings changes with the onChange method:

api.settings.onChange('enabled', (value) => {
  if (value) {
    // Setting was turned on
    initialize();
  } else {
    // Setting was turned off
    cleanup();
  }
});

Using Storage

The storage API lets you persist data between sessions:

// Read from storage
const favorites = api.storage.favoriteItems;

// Write to storage
api.storage.favoriteItems = [...favorites, 'new item'];

// Listen for storage changes
api.storage.onChange('favoriteItems', (newValue) => {
  console.log('Favorites updated:', newValue);
});

Cleaning Up

Always return a cleanup function from your plugin's run method if you have any resources to clean up:

run: async (api) => {
  // Set up resources
  const intervalId = setInterval(() => {
    // Do something
  }, 1000);
  
  // Return cleanup function
  return () => {
    clearInterval(intervalId);
    // Clean up any other resources
  };
}

Best Practices

  1. Initialize Storage Values: Always check if storage values are undefined and set defaults
  2. Handle Enabled State: Check if your plugin is enabled before running main functionality
  3. Use TypeScript: Take advantage of TypeScript's type system to ensure type safety
  4. Clean Up Resources: Always clean up resources when a plugin is disabled
  5. Document Settings: Use clear titles and descriptions for your settings

Complete Example

Here's a complete example of a simple plugin that changes the color of elements:

import { BasePlugin, BooleanSetting, ColorSetting } from '../../core/settings';
import type { Plugin } from '../../core/types';

interface ColorChangerStorage {
  lastApplied: string;
}

class ColorChangerPluginClass extends BasePlugin {
  @BooleanSetting({
    default: true,
    title: "Enable Color Changer",
    description: "Applies custom colors to elements on the page."
  })
  enabled!: boolean;
  
  @ColorSetting({
    default: "#4285f4",
    title: "Heading Color",
    description: "Color for headings on the page",
    presets: ["#4285f4", "#ea4335", "#fbbc05", "#34a853"]
  })
  headingColor!: string;
  
  @ColorSetting({
    default: "#34a853",
    title: "Button Color",
    description: "Color for buttons on the page",
    presets: ["#4285f4", "#ea4335", "#fbbc05", "#34a853"]
  })
  buttonColor!: string;
}

const settingsInstance = new ColorChangerPluginClass();

const colorChangerPlugin: Plugin<typeof settingsInstance.settings, ColorChangerStorage> = {
  id: 'colorChanger',
  name: 'Color Changer',
  description: 'Changes colors of various elements on the page',
  version: '1.0.0',
  settings: settingsInstance.settings,
  run: async (api) => {
    if (api.storage.lastApplied === undefined) {
      api.storage.lastApplied = new Date().toISOString();
    }
    
    const applyColors = () => {
      if (!api.settings.enabled) return;
      
      // Apply heading color
      document.querySelectorAll('h1, h2, h3').forEach(heading => {
        (heading as HTMLElement).style.color = api.settings.headingColor;
      });
      
      // Apply button color
      document.querySelectorAll('button').forEach(button => {
        (button as HTMLElement).style.backgroundColor = api.settings.buttonColor;
      });
      
      api.storage.lastApplied = new Date().toISOString();
    };
    
    // Apply colors initially
    applyColors();
    
    // Apply colors when DOM changes
    api.seqta.onMount('h1, h2, h3, button', applyColors);
    
    // Listen for color changes
    api.settings.onChange('headingColor', applyColors);
    api.settings.onChange('buttonColor', applyColors);
    api.settings.onChange('enabled', (enabled) => {
      if (enabled) {
        applyColors();
      } else {
        // Reset colors
        document.querySelectorAll('h1, h2, h3').forEach(heading => {
          (heading as HTMLElement).style.color = '';
        });
        
        document.querySelectorAll('button').forEach(button => {
          (button as HTMLElement).style.backgroundColor = '';
        });
      }
    });
    
    // No cleanup needed for this plugin
    return () => {};
  }
};

export default colorChangerPlugin;

This plugin demonstrates:

  • Using multiple setting types including a custom color setting
  • Handling the enabled state
  • Initializing storage
  • Listening for setting changes
  • Applying and resetting styles based on settings
  • Proper cleanup when disabled