diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..97da04c --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,29 @@ +# fdo-sdk Development Guidelines + +Auto-generated from all feature plans. Last updated: 2025-10-27 + +## Active Technologies + +- TypeScript 5.7.3 (matching existing SDK codebase) + @anikitenko/fdo-sdk (the SDK itself), electron ^35.0.0, goober ^2.1.16 (CSS-in-JS), winston ^3.17.0 (logging) (002-plugin-examples) + +## Project Structure + +```text +src/ +tests/ +``` + +## Commands + +npm test && npm run lint + +## Code Style + +TypeScript 5.7.3 (matching existing SDK codebase): Follow standard conventions + +## Recent Changes + +- 002-plugin-examples: Added TypeScript 5.7.3 (matching existing SDK codebase) + @anikitenko/fdo-sdk (the SDK itself), electron ^35.0.0, goober ^2.1.16 (CSS-in-JS), winston ^3.17.0 (logging) + + + diff --git a/examples/01-basic-plugin.ts b/examples/01-basic-plugin.ts new file mode 100644 index 0000000..ed51fe0 --- /dev/null +++ b/examples/01-basic-plugin.ts @@ -0,0 +1,172 @@ +/** + * Example 1: Basic Plugin Creation + * + * This example demonstrates the minimal requirements to create a working FDO plugin. + * It covers the core plugin lifecycle (initialization and rendering) and basic metadata structure. + * + * Compatible with SDK v1.x + * + * Learning Objectives: + * - Understand the FDO_SDK base class and FDOInterface + * - Learn how to implement required lifecycle methods (init and render) + * - See how to structure plugin metadata + * - Use the built-in logging system + * + * Expected Output: + * When this plugin runs in the FDO application, it will: + * 1. Log "BasicPlugin initialized!" to the console during initialization + * 2. Display a simple welcome message with the plugin name and version + * 3. Show a brief description of what the plugin does + */ + +import { FDO_SDK, FDOInterface, PluginMetadata, DOMText, DOMNested } from "../src"; + +/** + * BasicPlugin demonstrates the minimal plugin structure. + * + * All plugins must: + * 1. Extend the FDO_SDK base class + * 2. Implement the FDOInterface interface + * 3. Define metadata (name, version, author, description, icon) + * 4. Implement init() and render() methods + */ +export default class BasicPlugin extends FDO_SDK implements FDOInterface { + /** + * Plugin metadata - required by FDOInterface. + * This information is used by the FDO application to identify and describe your plugin. + * + * CUSTOMIZE HERE: Replace these values with your own plugin information + */ + private readonly _metadata: PluginMetadata = { + name: "Basic Plugin Example", + version: "1.0.0", + author: "FDO SDK Team", + description: "A minimal example demonstrating basic plugin creation and lifecycle", + icon: "icon.png" + }; + + /** + * Getter for plugin metadata. + * Required by FDOInterface. + */ + get metadata(): PluginMetadata { + return this._metadata; + } + + /** + * Initialize the plugin. + * + * This method is called once when the plugin is loaded by the FDO application. + * Use this method to: + * - Set up initial state + * - Register message handlers (covered in example 02) + * - Initialize storage (covered in example 03) + * - Perform any setup tasks + * + * COMMON PITFALL: Do not perform long-running operations in init(). + * The init() method should complete quickly to avoid blocking the application startup. + * For async operations, consider using promises or deferring work to handlers. + */ + init(): void { + try { + this.log("BasicPlugin initialized!"); + + + } catch (error) { + this.error(error as Error); + + } + } + + /** + * Render the plugin UI. + * + * This method is called when the plugin needs to display its user interface. + * It should return an HTML string that defines the plugin's UI. + * + * The returned HTML will be rendered in the FDO application's plugin container. + * + * CUSTOMIZE HERE: Replace this simple HTML with your own UI + * + * COMMON PITFALL: The render() method should return quickly. + * For complex UIs, consider using the DOM helper classes (see example 05) + * or breaking the UI into smaller components. + * + * @returns HTML string representing the plugin's user interface + */ + render(): string { + try { + const domText = new DOMText(); + const domNested = new DOMNested(); + + // Create main container + const mainContent = domNested.createBlockDiv([ + // Header section + domText.createHText(1, `Welcome to ${this._metadata.name}`), + domText.createPText([ + domText.createStrongText('Version:'), + ` ${this._metadata.version}` + ].join('')), + domText.createPText([ + domText.createStrongText('Author:'), + ` ${this._metadata.author}` + ].join('')), + domText.createPText(this._metadata.description), + + // What's Next section + domNested.createBlockDiv([ + domText.createHText(3, 'What\'s Next?'), + domText.createPText('This is a basic plugin example. To learn more advanced features:'), + domNested.createList([ + domNested.createListItem(['See example 02 for interactive UI with buttons and forms']), + domNested.createListItem(['See example 03 for data persistence']), + domNested.createListItem(['See example 04 for UI extensions (quick actions and side panels)']), + domNested.createListItem(['See example 05 for advanced DOM generation and styling']) + ]) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#f0f0f0', + borderRadius: '5px' + } + }) + ], { + style: { + padding: '20px', + fontFamily: 'Arial, sans-serif' + } + }); + + return mainContent; + + } catch (error) { + this.error(error as Error); + + return ` +
+

Error rendering plugin

+

An error occurred while rendering the plugin UI. Check the console for details.

+
+ `; + } + } +} + +/** + * Key Takeaways: + * + * 1. All plugins must extend FDO_SDK and implement FDOInterface + * 2. The metadata property is required and must include name, version, author, description, and icon + * 3. The init() method is called once during plugin load - use it for setup + * 4. The render() method returns HTML that defines the plugin's UI + * 5. Use this.log() for informational messages and this.error() for error logging + * 6. Always handle errors in init() and render() to prevent plugin failures + * + * Next Steps: + * - Copy this file to your project + * - Customize the metadata with your plugin information + * - Modify the render() method to display your own UI + * - Add your initialization logic to the init() method + * - Test your plugin in the FDO application + */ diff --git a/examples/02-interactive-plugin.ts b/examples/02-interactive-plugin.ts new file mode 100644 index 0000000..b84d3ca --- /dev/null +++ b/examples/02-interactive-plugin.ts @@ -0,0 +1,419 @@ +/** + * Example 2: Interactive Plugin with UI Actions + * + * This example demonstrates how to create interactive functionality where users can + * click buttons, fill forms, and trigger custom actions through the message-based + * communication system. + * + * Compatible with SDK v1.x + * + * Learning Objectives: + * - Register and handle custom message handlers + * - Create interactive UI elements (buttons, forms) + * - Process user input and update the UI + * - Handle asynchronous operations + * - Implement proper error handling for user interactions + * + * Expected Output: + * When this plugin runs in the FDO application, it will: + * 1. Display a form with a text input and submit button + * 2. Show a counter with increment/decrement buttons + * 3. Process button clicks and form submissions + * 4. Update the UI dynamically based on user actions + * 5. Log all user interactions to the console + */ + +import { FDO_SDK, FDOInterface, PluginMetadata, PluginRegistry, DOMText, DOMNested, DOMButton, DOMInput } from "../src"; + +declare global { + interface Window { + fdoSDK: { + sendMessage: (handler: string, data: any) => Promise; + }; + } +} + +/** + * InteractivePlugin demonstrates user interaction handling. + * + * Key concepts: + * - Message handlers: Functions that respond to UI events + * - Handler registration: Done in init() using PluginRegistry.registerHandler() + * - Async patterns: Handlers can be async for long-running operations + */ +export default class InteractivePlugin extends FDO_SDK implements FDOInterface { + /** + * Plugin metadata. + * + * CUSTOMIZE HERE: Replace with your plugin information + */ + private readonly _metadata: PluginMetadata = { + name: "Interactive Plugin Example", + version: "1.0.0", + author: "FDO SDK Team", + description: "Demonstrates interactive UI with buttons, forms, and message handlers", + icon: "icon.png" + }; + + /** + * Internal state for the counter example. + * In a real plugin, you might use the storage system (see example 03) for persistence. + */ + private counter: number = 0; + + get metadata(): PluginMetadata { + return this._metadata; + } + + /** + * Initialize the plugin and register message handlers. + * + * Message handlers are functions that respond to UI events. + * They are registered by name and can be triggered from UI elements. + * + * COMMON PITFALL: Always register handlers in init(), not in render(). + * Handlers registered in render() will be re-registered every time the UI updates, + * potentially causing duplicate handler registrations. + */ + init(): void { + try { + this.log("InteractivePlugin initialized!"); + + PluginRegistry.registerHandler("incrementCounter", (data: any) => { + return this.handleIncrement(data); + }); + + PluginRegistry.registerHandler("decrementCounter", (data: any) => { + return this.handleDecrement(data); + }); + + PluginRegistry.registerHandler("submitForm", async (data: any) => { + return await this.handleFormSubmit(data); + }); + + + } catch (error) { + this.error(error as Error); + } + } + + /** + * Handle increment button click. + * + * @param data - Data passed from the UI (can include button context, user info, etc.) + * @returns Result object that can be used to update the UI + */ + private handleIncrement(data: any): any { + try { + this.counter++; + this.log(`Counter incremented to ${this.counter}`); + + return { + success: true, + counter: this.counter, + message: `Counter is now ${this.counter}` + }; + + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to increment counter" + }; + } + } + + /** + * Handle decrement button click. + * + * @param data - Data passed from the UI + * @returns Result object + */ + private handleDecrement(data: any): any { + try { + this.counter--; + this.log(`Counter decremented to ${this.counter}`); + + return { + success: true, + counter: this.counter, + message: `Counter is now ${this.counter}` + }; + + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to decrement counter" + }; + } + } + + /** + * Handle form submission. + * + * This demonstrates async handler patterns for operations that might take time + * (e.g., API calls, file operations, complex computations). + * + * COMMON PITFALL: For async operations, always use async/await or promises. + * Don't block the UI with synchronous long-running operations. + * + * @param data - Form data from the UI + * @returns Promise resolving to result object + */ + private async handleFormSubmit(data: any): Promise { + try { + this.log(`Form submitted with data: ${JSON.stringify(data)}`); + + await new Promise(resolve => setTimeout(resolve, 500)); + + + const userName = data.userName || "Guest"; + + return { + success: true, + message: `Welcome, ${userName}! Your form has been processed.`, + timestamp: new Date().toISOString() + }; + + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to process form submission" + }; + } + } + + /** + * Render the interactive UI. + * + * This example shows how to create buttons and forms that trigger message handlers. + * + * CUSTOMIZE HERE: Replace with your own interactive UI + * + * @returns HTML string with interactive elements + */ + render(): string { + try { + const domText = new DOMText(); + const domNested = new DOMNested(); + const domButton = new DOMButton(); + const domInput = new DOMInput("userName", { + style: { + padding: '8px', + width: '300px', + marginBottom: '10px' + } + }); + + // Create main container + const mainContent = domNested.createBlockDiv([ + // Header section + domText.createHText(1, this._metadata.name), + domText.createPText(this._metadata.description), + + // Counter Example + domNested.createBlockDiv([ + domText.createHText(3, 'Counter Example'), + domText.createPText([ + 'Current count: ', + domText.createStrongText(this.counter.toString()) + ].join('')), + + domButton.createButton('Increment', + () => window.fdoSDK.sendMessage('incrementCounter', {}), + { + style: { + padding: '10px 20px', + marginRight: '10px', + cursor: 'pointer' + } + } + ), + + domButton.createButton('Decrement', + () => window.fdoSDK.sendMessage('decrementCounter', {}), + { + style: { + padding: '10px 20px', + cursor: 'pointer' + } + } + ), + + domText.createPText( + 'Click the buttons to increment or decrement the counter. The counter value is maintained in plugin state.', + { + style: { + marginTop: '10px', + fontSize: '12px', + color: '#666' + } + } + ) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#f0f0f0', + borderRadius: '5px' + } + }), + + // Form Example + domNested.createBlockDiv([ + domText.createHText(3, 'Form Example'), + domNested.createForm([ + domText.createLabelText('Enter your name:', 'userName', { + style: { + display: 'block', + marginBottom: '5px' + } + }), + + domInput.createInput('text'), + + domButton.createButton('Submit', + () => handleFormSubmit(), + { + style: { + padding: '10px 20px', + cursor: 'pointer', + backgroundColor: '#007bff', + color: 'white', + border: 'none', + borderRadius: '3px' + } + }) + ], { + customAttributes: { + onsubmit: 'event.preventDefault(); handleFormSubmit();' + } + }), + + domNested.createBlockDiv([], { + customAttributes: { + id: 'form-result' + }, + style: { + marginTop: '10px' + } + }), + + domText.createPText( + 'Enter your name and submit the form to see async handler processing.', + { + style: { + marginTop: '10px', + fontSize: '12px', + color: '#666' + } + } + ) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#e8f4f8', + borderRadius: '5px' + } + }), + + ``, + + // Key Concepts + domNested.createBlockDiv([ + domText.createHText(3, 'Key Concepts'), + domNested.createList([ + domNested.createListItem([ + domText.createStrongText('Message Handlers:'), + ' Functions registered in init() that respond to UI events' + ]), + domNested.createListItem([ + domText.createStrongText('Handler Registration:'), + ' Use PluginRegistry.registerHandler(name, function)' + ]), + domNested.createListItem([ + domText.createStrongText('Async Patterns:'), + ' Handlers can be async for long-running operations' + ]), + domNested.createListItem([ + domText.createStrongText('Error Handling:'), + ' Always wrap handler logic in try-catch blocks' + ]), + domNested.createListItem([ + domText.createStrongText('State Management:'), + ' Use class properties for temporary state, storage for persistence' + ]) + ]) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#fff3cd', + borderRadius: '5px' + } + }) + ], { + style: { + padding: '20px', + fontFamily: 'Arial, sans-serif' + } + }); + + return mainContent; + + } catch (error) { + this.error(error as Error); + return ` +
+

Error rendering plugin

+

An error occurred while rendering the plugin UI. Check the console for details.

+
+ `; + } + } +} + +/** + * Key Takeaways: + * + * 1. Register message handlers in init() using PluginRegistry.registerHandler() + * 2. Handlers can be synchronous or asynchronous (use async/await for long operations) + * 3. Always handle errors in handlers to prevent UI failures + * 4. Use class properties for temporary state, storage system for persistence + * 5. Validate user input before processing to prevent security issues + * 6. Return structured data from handlers to enable UI updates + * + * Common Pitfalls to Avoid: + * - Don't register handlers in render() - do it in init() + * - Don't forget error handling in handlers + * - Don't block the UI with synchronous long-running operations + * - Don't trust user input - always validate + * + * Next Steps: + * - See example 03 for data persistence with the storage system + * - See example 04 for UI extensions (quick actions and side panels) + * - See example 05 for advanced DOM generation with styling + */ diff --git a/examples/03-persistence-plugin.ts b/examples/03-persistence-plugin.ts new file mode 100644 index 0000000..9e8b85f --- /dev/null +++ b/examples/03-persistence-plugin.ts @@ -0,0 +1,556 @@ +/** + * Example 3: Data Persistence Plugin + * + * This example demonstrates how to save and retrieve user data across application sessions + * using the FDO SDK storage system. It covers both in-memory (StoreDefault) and file-based + * (StoreJson) storage backends. + * + * Compatible with SDK v1.x + * + * Learning Objectives: + * - Use StoreDefault for temporary data (in-memory) + * - Use StoreJson for persistent data (file-based) + * - Implement proper key naming conventions + * - Handle storage errors gracefully + * - Save and retrieve different data types + * + * Expected Output: + * When this plugin runs in the FDO application, it will: + * 1. Display saved user preferences (name, theme, notifications) + * 2. Provide forms to update preferences + * 3. Show temporary session data (visit count, last action) + * 4. Persist preference changes across application restarts + * 5. Clear temporary data on each session + */ + +import { FDO_SDK, FDOInterface, PluginMetadata, PluginRegistry, DOMText, DOMNested, DOMButton, DOMInput } from "../src"; + +declare global { + interface Window { + fdoSDK: { + sendMessage: (handler: string, data: any) => Promise; + }; + } +} + +/** + * PersistencePlugin demonstrates data storage and retrieval. + * + * Key concepts: + * - StoreDefault: In-memory storage (data lost on restart) + * - StoreJson: File-based storage (data persists across restarts) + * - Key naming: Use namespaced keys to avoid conflicts + * - Error handling: Storage operations can fail + */ +export default class PersistencePlugin extends FDO_SDK implements FDOInterface { + /** + * Plugin metadata. + * + * CUSTOMIZE HERE: Replace with your plugin information + */ + private readonly _metadata: PluginMetadata = { + name: "Persistence Plugin Example", + version: "1.0.0", + author: "FDO SDK Team", + description: "Demonstrates data persistence with StoreDefault and StoreJson", + icon: "icon.png" + }; + + /** + * Storage instances. + * StoreDefault is automatically available without registration. + * StoreJson must be registered before use. + */ + private tempStore: any; // StoreDefault for temporary data + private persistentStore: any; // StoreJson for persistent data + + /** + * Key naming convention. + * Use namespaced keys to avoid conflicts with other plugins. + * Format: "pluginName:category:keyName" + * + * COMMON PITFALL: Always namespace your keys to prevent conflicts. + * Without namespacing, multiple plugins might overwrite each other's data. + */ + private readonly KEYS = { + USER_NAME: "persistencePlugin:prefs:userName", + USER_THEME: "persistencePlugin:prefs:theme", + NOTIFICATIONS_ENABLED: "persistencePlugin:prefs:notifications", + + VISIT_COUNT: "persistencePlugin:session:visitCount", + LAST_ACTION: "persistencePlugin:session:lastAction", + SESSION_START: "persistencePlugin:session:startTime" + }; + + get metadata(): PluginMetadata { + return this._metadata; + } + + /** + * Initialize the plugin and set up storage. + * + * COMMON PITFALL: Always initialize storage in init(), not in render(). + * Storage operations in render() can cause performance issues and data inconsistencies. + */ + init(): void { + try { + this.log("PersistencePlugin initialized!"); + + this.tempStore = PluginRegistry.useStore("default"); + + try { + this.persistentStore = PluginRegistry.useStore("json"); + } catch (error) { + this.log("StoreJson not available, using StoreDefault for all storage"); + this.persistentStore = this.tempStore; + } + + this.initializeSessionData(); + + PluginRegistry.registerHandler("savePreferences", (data: any) => { + return this.handleSavePreferences(data); + }); + + PluginRegistry.registerHandler("clearPreferences", (data: any) => { + return this.handleClearPreferences(data); + }); + + PluginRegistry.registerHandler("recordAction", (data: any) => { + return this.handleRecordAction(data); + }); + + + } catch (error) { + this.error(error as Error); + } + } + + /** + * Initialize session data in temporary storage. + * This data is cleared on each application restart. + */ + private initializeSessionData(): void { + try { + const visitCount = this.tempStore.get(this.KEYS.VISIT_COUNT) || 0; + this.tempStore.set(this.KEYS.VISIT_COUNT, visitCount + 1); + + this.tempStore.set(this.KEYS.SESSION_START, new Date().toISOString()); + + this.log(`Session initialized. Visit count: ${visitCount + 1}`); + + } catch (error) { + this.error(error as Error); + } + } + + /** + * Handle saving user preferences to persistent storage. + * + * @param data - Preference data from the UI + * @returns Result object + */ + private handleSavePreferences(data: any): any { + try { + if (!data) { + return { + success: false, + error: "No data provided" + }; + } + + if (data.userName !== undefined) { + this.persistentStore.set(this.KEYS.USER_NAME, data.userName); + } + + if (data.theme !== undefined) { + this.persistentStore.set(this.KEYS.USER_THEME, data.theme); + } + + if (data.notificationsEnabled !== undefined) { + this.persistentStore.set(this.KEYS.NOTIFICATIONS_ENABLED, data.notificationsEnabled); + } + + this.log("Preferences saved successfully"); + + return { + success: true, + message: "Preferences saved successfully", + savedData: data + }; + + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to save preferences" + }; + } + } + + /** + * Handle clearing all preferences. + * + * @param data - Optional data + * @returns Result object + */ + private handleClearPreferences(data: any): any { + try { + this.persistentStore.remove(this.KEYS.USER_NAME); + this.persistentStore.remove(this.KEYS.USER_THEME); + this.persistentStore.remove(this.KEYS.NOTIFICATIONS_ENABLED); + + this.log("Preferences cleared"); + + return { + success: true, + message: "All preferences have been cleared" + }; + + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to clear preferences" + }; + } + } + + /** + * Handle recording a user action to temporary storage. + * + * @param data - Action data + * @returns Result object + */ + private handleRecordAction(data: any): any { + try { + const action = data.action || "unknown"; + const timestamp = new Date().toISOString(); + + this.tempStore.set(this.KEYS.LAST_ACTION, { + action, + timestamp + }); + + this.log(`Action recorded: ${action}`); + + return { + success: true, + action, + timestamp + }; + + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to record action" + }; + } + } + + /** + * Render the persistence UI. + * + * CUSTOMIZE HERE: Replace with your own storage UI + * + * @returns HTML string + */ + render(): string { + try { + const userName = this.persistentStore.get(this.KEYS.USER_NAME) || "Not set"; + const theme = this.persistentStore.get(this.KEYS.USER_THEME) || "light"; + const notificationsEnabled = this.persistentStore.get(this.KEYS.NOTIFICATIONS_ENABLED) ?? true; + + const visitCount = this.tempStore.get(this.KEYS.VISIT_COUNT) || 0; + const lastAction = this.tempStore.get(this.KEYS.LAST_ACTION); + const sessionStart = this.tempStore.get(this.KEYS.SESSION_START); + + const domText = new DOMText(); + const domNested = new DOMNested(); + const domButton = new DOMButton(); + const domInput = new DOMInput("", {}); + + // Create main container + const mainContent = domNested.createBlockDiv([ + // Header section + domText.createHText(1, this._metadata.name), + domText.createPText(this._metadata.description), + + // Persistent Data Section + domNested.createBlockDiv([ + domText.createHText(3, "Persistent Preferences (StoreJson)"), + domText.createPText("These preferences are saved to a file and persist across application restarts.", { + style: { + fontSize: '12px', + color: '#666', + marginBottom: '15px' + } + }), + + // Current values display + domNested.createBlockDiv([ + domText.createPText([ + domText.createStrongText("User Name:"), + ` ${userName}` + ].join(''), { style: { marginBottom: '10px' } }), + + domText.createPText([ + domText.createStrongText("Theme:"), + ` ${theme}` + ].join(''), { style: { marginBottom: '10px' } }), + + domText.createPText([ + domText.createStrongText("Notifications:"), + ` ${notificationsEnabled ? "Enabled" : "Disabled"}` + ].join(''), { style: { marginBottom: '10px' } }) + ]), + + // Preferences form + domNested.createForm([ + // Username input + domNested.createBlockDiv([ + domText.createLabelText("User Name:", "userName", { + style: { display: 'block', marginBottom: '5px' } + }), + new DOMInput("userName", { + style: { padding: '8px', width: '300px' } + }).createInput("text") + ], { style: { marginBottom: '10px' } }), + + // Theme select + domNested.createBlockDiv([ + domText.createLabelText("Theme:", "theme", { + style: { display: 'block', marginBottom: '5px' } + }), + new DOMInput("theme", { + style: { padding: '8px', width: '316px' } + }).createSelect([ + domInput.createOption("Light", "light", theme === 'light'), + domInput.createOption("Dark", "dark", theme === 'dark'), + domInput.createOption("Auto", "auto", theme === 'auto') + ]) + ], { style: { marginBottom: '10px' } }), + + // Notifications checkbox + domNested.createBlockDiv([ + domText.createLabelText([ + new DOMInput("notifications", {}).createInput("checkbox"), + " Enable Notifications" + ].join(''), "notifications") + ], { style: { marginBottom: '15px' } }), + + // Form buttons + domNested.createBlockDiv([ + domButton.createButton("Save Preferences", + () => { + const form = document.querySelector('form'); + if (form) form.dispatchEvent(new Event('submit')); + }, + { + style: { + padding: '10px 20px', + marginRight: '10px', + cursor: 'pointer', + backgroundColor: '#007bff', + color: 'white', + border: 'none', + borderRadius: '3px' + } + } + ), + + domButton.createButton("Clear All", + () => window.fdoSDK.sendMessage('clearPreferences', {}), + { + style: { + padding: '10px 20px', + cursor: 'pointer', + backgroundColor: '#dc3545', + color: 'white', + border: 'none', + borderRadius: '3px' + } + } + ) + ]) + ], { + customAttributes: { + onsubmit: 'event.preventDefault(); savePreferences();' + }, + style: { marginTop: '15px' } + }), + + // Result area + domNested.createBlockDiv([], { + customAttributes: { id: 'prefs-result' }, + style: { marginTop: '10px' } + }) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#e8f4f8', + borderRadius: '5px' + } + }), + + // Temporary Data Section + domNested.createBlockDiv([ + domText.createHText(3, "Session Data (StoreDefault)"), + domText.createPText("This data is stored in memory and cleared when the application restarts.", { + style: { + fontSize: '12px', + color: '#666', + marginBottom: '15px' + } + }), + + domNested.createBlockDiv([ + domText.createPText([ + domText.createStrongText("Visit Count:"), + ` ${visitCount}` + ].join(''), { style: { marginBottom: '10px' } }), + + domText.createPText([ + domText.createStrongText("Session Started:"), + ` ${sessionStart ? new Date(sessionStart).toLocaleString() : 'N/A'}` + ].join(''), { style: { marginBottom: '10px' } }), + + domText.createPText([ + domText.createStrongText("Last Action:"), + ` ${lastAction ? `${lastAction.action} at ${new Date(lastAction.timestamp).toLocaleTimeString()}` : 'None'}` + ].join(''), { style: { marginBottom: '15px' } }) + ]), + + domButton.createButton("Record Action", + () => window.fdoSDK.sendMessage('recordAction', { action: 'Button Click' }), + { + style: { + padding: '10px 20px', + cursor: 'pointer' + } + } + ) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#f0f0f0', + borderRadius: '5px' + } + }), + + // Key Concepts Section + domNested.createBlockDiv([ + domText.createHText(3, "Storage Concepts"), + domNested.createList([ + domNested.createListItem([ + domText.createStrongText("StoreDefault:"), + " In-memory storage, data cleared on restart" + ]), + domNested.createListItem([ + domText.createStrongText("StoreJson:"), + " File-based storage, data persists across restarts" + ]), + domNested.createListItem([ + domText.createStrongText("Key Naming:"), + " Use namespaced keys (pluginName:category:key)" + ]), + domNested.createListItem([ + domText.createStrongText("Error Handling:"), + " Always wrap storage operations in try-catch" + ]), + domNested.createListItem([ + domText.createStrongText("Data Types:"), + " Stores support strings, numbers, booleans, objects, arrays" + ]) + ]) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#fff3cd', + borderRadius: '5px' + } + }), + + // JavaScript for handling preferences + domText.createText(` + + `) + ], { + style: { + padding: '20px', + fontFamily: 'Arial, sans-serif' + } + }); + + return mainContent; + + } catch (error) { + this.error(error as Error); + return ` +
+

Error rendering plugin

+

An error occurred while rendering the plugin UI. Check the console for details.

+
+ `; + } + } +} + +/** + * Key Takeaways: + * + * 1. Use StoreDefault for temporary data (in-memory, cleared on restart) + * 2. Use StoreJson for persistent data (file-based, survives restarts) + * 3. Always namespace your keys to avoid conflicts (pluginName:category:key) + * 4. Wrap all storage operations in try-catch blocks + * 5. Initialize storage in init(), not in render() + * 6. Storage supports all JSON-serializable types (strings, numbers, objects, arrays) + * + * Common Pitfalls to Avoid: + * - Don't use generic key names - always namespace them + * - Don't forget error handling for storage operations + * - Don't perform storage operations in render() - do it in handlers + * - Don't assume StoreJson is always available - have a fallback + * + * Next Steps: + * - See example 04 for UI extensions (quick actions and side panels) + * - See example 05 for advanced DOM generation with styling + */ diff --git a/examples/04-ui-extensions-plugin.ts b/examples/04-ui-extensions-plugin.ts new file mode 100644 index 0000000..1a13082 --- /dev/null +++ b/examples/04-ui-extensions-plugin.ts @@ -0,0 +1,733 @@ +/** + * Example 4: Quick Actions and Side Panel Integration + * + * This example demonstrates how to extend the FDO application's UI by adding quick action + * shortcuts and side panel menu items using mixins. These UI extensions make your plugin + * more discoverable and accessible to users. + * + * Compatible with SDK v1.x + * + * Learning Objectives: + * - Use QuickActionMixin to add quick action shortcuts + * - Use SidePanelMixin to add side panel menu items + * - Route messages from UI extensions to plugin handlers + * - Configure icons and labels for UI extensions + * - Handle errors in UI extension operations + * + * Expected Output: + * When this plugin runs in the FDO application, it will: + * 1. Add quick actions to the application's quick action menu + * 2. Add a side panel with multiple menu items + * 3. Route quick action and side panel selections to appropriate handlers + * 4. Display different content based on which UI extension was triggered + * 5. Log all UI extension interactions + */ + +import { + FDO_SDK, + FDOInterface, + PluginMetadata, + PluginRegistry, + QuickActionMixin, + SidePanelMixin, + QuickAction, + SidePanelConfig, + DOMText, + DOMNested, + DOMButton, + DOMInput, + DOMLink +} from "../src"; + +/** + * UIExtensionsPlugin demonstrates UI extension capabilities. + * + * Key concepts: + * - Mixins: Add functionality to plugin classes + * - QuickActionMixin: Adds defineQuickActions() method + * - SidePanelMixin: Adds defineSidePanel() method + * - Message routing: UI extensions trigger message handlers + * + * COMMON PITFALL: Mixins must be applied using the mixin pattern. + * This example shows the correct way to apply multiple mixins. + */ + +const UIExtensionsPluginBase = SidePanelMixin(QuickActionMixin(FDO_SDK)); + +export default class UIExtensionsPlugin extends UIExtensionsPluginBase implements FDOInterface { + /** + * Plugin metadata. + * + * CUSTOMIZE HERE: Replace with your plugin information + */ + private readonly _metadata: PluginMetadata = { + name: "UI Extensions Plugin Example", + version: "1.0.0", + author: "FDO SDK Team", + description: "Demonstrates quick actions and side panel integration using mixins", + icon: "icon.png" + }; + + /** + * Current view state. + * Used to track which UI extension was triggered. + */ + private currentView: string = "default"; + + get metadata(): PluginMetadata { + return this._metadata; + } + + /** + * Initialize the plugin and register message handlers. + * + * COMMON PITFALL: Register handlers for all message types used in UI extensions. + * If a handler is missing, clicking the UI extension will result in an error. + */ + init(): void { + try { + this.log("UIExtensionsPlugin initialized!"); + + PluginRegistry.registerHandler("quickSearch", (data: any) => { + return this.handleQuickSearch(data); + }); + + PluginRegistry.registerHandler("quickCreate", (data: any) => { + return this.handleQuickCreate(data); + }); + + PluginRegistry.registerHandler("quickSettings", (data: any) => { + return this.handleQuickSettings(data); + }); + + PluginRegistry.registerHandler("showDashboard", (data: any) => { + return this.handleShowDashboard(data); + }); + + PluginRegistry.registerHandler("showReports", (data: any) => { + return this.handleShowReports(data); + }); + + PluginRegistry.registerHandler("showSettings", (data: any) => { + return this.handleShowSettings(data); + }); + + + } catch (error) { + this.error(error as Error); + } + } + + /** + * Define quick actions for the plugin. + * + * Quick actions appear in the FDO application's quick action menu and provide + * shortcuts to common plugin functionality. + * + * CUSTOMIZE HERE: Define your own quick actions + * + * @returns Array of QuickAction objects + */ + defineQuickActions(): QuickAction[] { + try { + return [ + { + name: "Search Plugin Data", + message_type: "quickSearch", + subtitle: "Search through plugin data", + icon: "search.png" + }, + { + name: "Create New Item", + message_type: "quickCreate", + subtitle: "Create a new item in the plugin", + icon: "create.png" + }, + { + name: "Plugin Settings", + message_type: "quickSettings", + icon: "settings.png" + } + ]; + } catch (error) { + this.error(error as Error); + return []; + } + } + + /** + * Define side panel configuration for the plugin. + * + * The side panel provides a persistent menu in the FDO application's UI + * for accessing plugin features. + * + * CUSTOMIZE HERE: Define your own side panel structure + * + * @returns SidePanelConfig object + */ + defineSidePanel(): SidePanelConfig { + try { + return { + icon: "panel.png", + label: "UI Extensions", + submenu_list: [ + { + id: "dashboard", + name: "Dashboard", + message_type: "showDashboard" + }, + { + id: "reports", + name: "Reports", + message_type: "showReports" + }, + { + id: "settings", + name: "Settings", + message_type: "showSettings" + } + ] + }; + } catch (error) { + this.error(error as Error); + return { + icon: "panel.png", + label: "UI Extensions", + submenu_list: [] + }; + } + } + + /** + * Handle quick search action. + * + * @param data - Data from the quick action + * @returns Result object + */ + private handleQuickSearch(data: any): any { + try { + this.currentView = "search"; + this.log("Quick search triggered"); + + return { + success: true, + view: "search", + message: "Search view activated" + }; + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to activate search" + }; + } + } + + /** + * Handle quick create action. + * + * @param data - Data from the quick action + * @returns Result object + */ + private handleQuickCreate(data: any): any { + try { + this.currentView = "create"; + this.log("Quick create triggered"); + + return { + success: true, + view: "create", + message: "Create view activated" + }; + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to activate create" + }; + } + } + + /** + * Handle quick settings action. + * + * @param data - Data from the quick action + * @returns Result object + */ + private handleQuickSettings(data: any): any { + try { + this.currentView = "settings"; + this.log("Quick settings triggered"); + + return { + success: true, + view: "settings", + message: "Settings view activated" + }; + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to activate settings" + }; + } + } + + /** + * Handle show dashboard from side panel. + * + * @param data - Data from the side panel + * @returns Result object + */ + private handleShowDashboard(data: any): any { + try { + this.currentView = "dashboard"; + this.log("Dashboard view triggered from side panel"); + + return { + success: true, + view: "dashboard", + message: "Dashboard view activated" + }; + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to show dashboard" + }; + } + } + + /** + * Handle show reports from side panel. + * + * @param data - Data from the side panel + * @returns Result object + */ + private handleShowReports(data: any): any { + try { + this.currentView = "reports"; + this.log("Reports view triggered from side panel"); + + return { + success: true, + view: "reports", + message: "Reports view activated" + }; + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to show reports" + }; + } + } + + /** + * Handle show settings from side panel. + * + * @param data - Data from the side panel + * @returns Result object + */ + private handleShowSettings(data: any): any { + try { + this.currentView = "settings"; + this.log("Settings view triggered from side panel"); + + return { + success: true, + view: "settings", + message: "Settings view activated" + }; + } catch (error) { + this.error(error as Error); + return { + success: false, + error: "Failed to show settings" + }; + } + } + + /** + * Render the UI based on current view. + * + * CUSTOMIZE HERE: Replace with your own view rendering logic + * + * @returns HTML string + */ + render(): string { + try { + const domText = new DOMText(); + const domNested = new DOMNested(); + + // Main container + const mainContent = domNested.createBlockDiv([ + domText.createHText(1, this._metadata.name), + domText.createPText(this._metadata.description), + + // Dynamic content based on current view + this.renderCurrentView(), + + // UI Extensions Info + domNested.createBlockDiv([ + domText.createHText(3, "UI Extensions"), + domText.createPText([ + domText.createStrongText("Quick Actions:"), + " Access quick actions from the FDO application's quick action menu" + ].join('')), + domNested.createList([ + "Search Plugin Data", + "Create New Item", + "Plugin Settings" + ]), + + domText.createPText([ + domText.createStrongText("Side Panel:"), + " Access features from the side panel menu" + ].join(''), { style: { marginTop: '15px' } }), + domNested.createList([ + "Dashboard", + "Reports", + "Settings" + ]) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#fff3cd', + borderRadius: '5px' + } + }) + ], { + style: { + padding: '20px', + fontFamily: 'Arial, sans-serif' + } + }); + + return mainContent; + + } catch (error) { + this.error(error as Error); + const errorDomText = new DOMText(); + const errorDomNested = new DOMNested(); + return errorDomNested.createBlockDiv([ + errorDomText.createHText(2, "Error rendering plugin"), + errorDomText.createPText("An error occurred while rendering the plugin UI. Check the console for details.") + ], { + style: { + padding: '20px', + color: 'red' + } + }); + } + } + + /** + * Render the current view based on the view state + */ + private renderCurrentView(): string { + switch (this.currentView) { + case "search": + return this.renderSearchView(); + case "create": + return this.renderCreateView(); + case "dashboard": + return this.renderDashboardView(); + case "reports": + return this.renderReportsView(); + case "settings": + return this.renderSettingsView(); + default: + return this.renderDefaultView(); + } + } + + /** + * Render the default view. + */ + private renderDefaultView(): string { + const domText = new DOMText(); + const domNested = new DOMNested(); + + return domNested.createBlockDiv([ + domText.createHText(3, "Welcome to UI Extensions Example"), + domText.createPText("This plugin demonstrates quick actions and side panel integration."), + domText.createPText("Try the following:"), + domNested.createList([ + "Use the quick action menu to trigger quick actions", + "Use the side panel to navigate between views", + "See how different views are rendered based on UI extension triggers" + ]) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#f0f0f0', + borderRadius: '5px' + } + }); + } + + /** + * Render the search view. + */ + private renderSearchView(): string { + const domText = new DOMText(); + const domNested = new DOMNested(); + const domInput = new DOMInput("search", { + style: { + padding: '10px', + width: '400px', + marginTop: '10px' + } + }); + const domButton = new DOMButton(); + + return domNested.createBlockDiv([ + domText.createHText(3, "Search View"), + domText.createPText("This view was triggered by the \"Search Plugin Data\" quick action."), + domNested.createBlockDiv([ + domInput.createInput("text"), + domButton.createButton("Search", () => {}, { + style: { + padding: '10px 20px', + marginLeft: '10px', + cursor: 'pointer' + } + }) + ], { + style: { + display: 'flex', + alignItems: 'center' + } + }) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#e8f4f8', + borderRadius: '5px' + } + }); + } + + /** + * Render the create view. + */ + private renderCreateView(): string { + const domText = new DOMText(); + const domNested = new DOMNested(); + const domInput = new DOMInput("create", { + style: { + padding: '10px', + width: '400px', + marginBottom: '10px' + } + }); + const domButton = new DOMButton(); + + return domNested.createBlockDiv([ + domText.createHText(3, "Create View"), + domText.createPText("This view was triggered by the \"Create New Item\" quick action."), + domNested.createForm([ + domNested.createBlockDiv([ + domInput.createInput("text") + ], { + style: { + marginBottom: '10px' + } + }), + domNested.createBlockDiv([ + domInput.createTextarea() + ], { + style: { + marginBottom: '10px' + } + }), + domButton.createButton("Create", () => {}, { + style: { + padding: '10px 20px', + cursor: 'pointer' + } + }) + ], { + style: { marginTop: '10px' } + }) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#d4edda', + borderRadius: '5px' + } + }); + } + + /** + * Render the dashboard view. + */ + private renderDashboardView(): string { + const domText = new DOMText(); + const domNested = new DOMNested(); + + const createMetricBlock = (title: string, value: string) => { + return domNested.createBlockDiv([ + domText.createHText(4, title), + domText.createPText(value, { + style: { + fontSize: '24px', + fontWeight: 'bold' + } + }) + ], { + style: { + padding: '15px', + backgroundColor: 'white', + borderRadius: '5px' + } + }); + }; + + return domNested.createBlockDiv([ + domText.createHText(3, "Dashboard View"), + domText.createPText("This view was triggered from the side panel \"Dashboard\" menu item."), + domNested.createBlockDiv([ + createMetricBlock("Metric 1", "42"), + createMetricBlock("Metric 2", "87%") + ], { + style: { + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gap: '15px', + marginTop: '15px' + } + }) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#cfe2ff', + borderRadius: '5px' + } + }); + } + + /** + * Render the reports view. + */ + private renderReportsView(): string { + const domText = new DOMText(); + const domNested = new DOMNested(); + const domLink = new DOMLink("reports", { + style: { color: '#007bff' } + }); + + const createReportItem = (title: string) => { + return domNested.createBlockDiv([ + domText.createText(`${title} - `), + domText.createText(domLink.createLink("#", "View")) + ]); + }; + + return domNested.createBlockDiv([ + domText.createHText(3, "Reports View"), + domText.createPText("This view was triggered from the side panel \"Reports\" menu item."), + domNested.createList([ + createReportItem("Monthly Report"), + createReportItem("Quarterly Report"), + createReportItem("Annual Report") + ], { + style: { marginTop: '10px' } + }) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#f8d7da', + borderRadius: '5px' + } + }); + } + + /** + * Render the settings view. + */ + private renderSettingsView(): string { + const domText = new DOMText(); + const domNested = new DOMNested(); + const domInput = new DOMInput("settings", {}); + const domButton = new DOMButton(); + + const createCheckboxOption = (label: string, checked: boolean = false) => { + return domNested.createBlockDiv([ + domText.createLabelText(label, "settings-" + label.toLowerCase().replace(/\s+/g, '-'), { + style: { display: 'block', marginBottom: '10px' } + }), + domInput.createInput(checked ? "checkbox" : "checkbox") + ]); + }; + + return domNested.createBlockDiv([ + domText.createHText(3, "Settings View"), + domText.createPText("This view can be triggered from either the quick action or side panel."), + domNested.createForm([ + createCheckboxOption("Enable notifications", true), + createCheckboxOption("Auto-save"), + + domNested.createBlockDiv([ + domText.createLabelText("Theme:", "settings-theme", { + style: { marginRight: '10px' } + }), + domInput.createSelect([ + "Light", + "Dark", + "Auto" + ]) + ], { + style: { marginBottom: '10px' } + }), + + domButton.createButton("Save Settings", () => {}, { + style: { + padding: '10px 20px', + marginTop: '10px', + cursor: 'pointer' + } + }) + ], { + style: { marginTop: '10px' } + }) + ], { + style: { + marginTop: '20px', + padding: '15px', + backgroundColor: '#e2e3e5', + borderRadius: '5px' + } + }); + } +} + +/** + * Key Takeaways: + * + * 1. Use QuickActionMixin to add quick action shortcuts to the FDO application + * 2. Use SidePanelMixin to add side panel menu items + * 3. Apply mixins using the mixin pattern: SidePanelMixin(QuickActionMixin(FDO_SDK)) + * 4. Define quick actions in defineQuickActions() method + * 5. Define side panel structure in defineSidePanel() method + * 6. Register handlers for all message types used in UI extensions + * 7. Always handle errors in defineQuickActions() and defineSidePanel() + * + * Common Pitfalls to Avoid: + * - Don't forget to register handlers for all UI extension message types + * - Don't throw errors in defineQuickActions() or defineSidePanel() + * - Don't forget to apply mixins to the base class + * - Don't use the same message_type for different actions without proper routing + * + * Next Steps: + * - See example 05 for advanced DOM generation with styling + * - Combine UI extensions with storage (example 03) for stateful plugins + * - Use interactive handlers (example 02) with UI extensions for dynamic behavior + */ diff --git a/examples/05-advanced-dom-plugin.ts b/examples/05-advanced-dom-plugin.ts new file mode 100644 index 0000000..8acb924 --- /dev/null +++ b/examples/05-advanced-dom-plugin.ts @@ -0,0 +1,562 @@ +/** + * Example 5: Advanced DOM Generation + * + * This example demonstrates how to create rich, styled UI components using the SDK's + * DOM generation classes. It covers the various DOM helper classes (DOMText, DOMButton, + * DOMInput, etc.) and shows how to build complex interfaces with custom styling using + * CSS-in-JS via goober. + * + * Compatible with SDK v1.x + * + * Learning Objectives: + * - Use DOM helper classes for programmatic HTML generation + * - Apply CSS-in-JS styling with goober integration + * - Build complex nested UI structures + * - Create forms with multiple input types + * - Compose reusable UI components + * + * Expected Output: + * When this plugin runs in the FDO application, it will: + * 1. Display a styled card layout with custom CSS + * 2. Show a complex form with multiple input types + * 3. Demonstrate nested UI structures + * 4. Apply custom styles using CSS-in-JS + * 5. Render all elements using DOM helper classes + */ + +import { + FDO_SDK, + FDOInterface, + PluginMetadata, + DOM, + DOMText, + DOMButton, + DOMInput, + DOMLink, + DOMNested, + DOMMisc +} from "@anikitenko/fdo-sdk"; + +/** + * AdvancedDOMPlugin demonstrates advanced DOM generation capabilities. + * + * Key concepts: + * - DOM helper classes: Programmatic HTML generation + * - CSS-in-JS: Style objects converted to CSS classes + * - Component composition: Building complex UIs from simple elements + * - Nested structures: Using DOMNested for containers + */ +export default class AdvancedDOMPlugin extends FDO_SDK implements FDOInterface { + /** + * Plugin metadata. + * + * CUSTOMIZE HERE: Replace with your plugin information + */ + private readonly _metadata: PluginMetadata = { + name: "Advanced DOM Plugin Example", + version: "1.0.0", + author: "FDO SDK Team", + description: "Demonstrates advanced DOM generation with helper classes and CSS-in-JS styling", + icon: "icon.png" + }; + + /** + * DOM helper instances. + * These provide methods for creating HTML elements programmatically. + */ + private dom: DOM; + private domText: DOMText; + private domButton: DOMButton; + private domInput: DOMInput; + private domLink: DOMLink; + private domNested: DOMNested; + private domMisc: DOMMisc; + + constructor() { + super(); + + this.dom = new DOM(); + this.domText = new DOMText(); + this.domButton = new DOMButton(); + this.domInput = new DOMInput(); + this.domLink = new DOMLink(); + this.domNested = new DOMNested(); + this.domMisc = new DOMMisc(); + } + + get metadata(): PluginMetadata { + return this._metadata; + } + + /** + * Initialize the plugin. + * + * COMMON PITFALL: DOM helper classes should be instantiated in the constructor, + * not in init() or render(), to avoid unnecessary object creation. + */ + init(): void { + try { + this.log("AdvancedDOMPlugin initialized!"); + + + } catch (error) { + this.error(error as Error); + } + } + + /** + * Render the plugin UI using DOM helper classes. + * + * This example demonstrates: + * - Creating styled elements with CSS-in-JS + * - Building complex nested structures + * - Composing reusable UI components + * - Using all major DOM helper classes + * + * CUSTOMIZE HERE: Replace with your own DOM generation logic + * + * @returns HTML string with embedded CSS + */ + render(): string { + try { + const containerStyle = { + padding: "20px", + fontFamily: "Arial, sans-serif", + maxWidth: "800px", + margin: "0 auto" + }; + + const header = this.createHeader(); + const infoCard = this.createInfoCard(); + const formSection = this.createFormSection(); + const buttonSection = this.createButtonSection(); + const conceptsSection = this.createConceptsSection(); + + const content = this.domNested.createBlockDiv( + { style: containerStyle }, + undefined, + header, + infoCard, + formSection, + buttonSection, + conceptsSection + ); + + return this.dom.renderHTML(content); + + } catch (error) { + this.error(error as Error); + + return ` +
+

Error rendering plugin

+

An error occurred while rendering the plugin UI. Check the console for details.

+
+ `; + } + } + + /** + * Create the header section. + * Demonstrates text element creation with styling. + */ + private createHeader(): string { + const titleStyle = { + color: "#333", + marginBottom: "10px" + }; + + const subtitleStyle = { + color: "#666", + fontSize: "14px", + marginBottom: "20px" + }; + + const title = this.domText.createHText( + 1, + this._metadata.name, + { style: titleStyle } + ); + + const subtitle = this.domText.createPText( + this._metadata.description, + { style: subtitleStyle } + ); + + return title + subtitle; + } + + /** + * Create an info card with styled content. + * Demonstrates nested structures and CSS-in-JS styling. + */ + private createInfoCard(): string { + const cardStyle = { + backgroundColor: "#f8f9fa", + padding: "20px", + borderRadius: "8px", + marginBottom: "20px", + boxShadow: "0 2px 4px rgba(0,0,0,0.1)" + }; + + const cardTitleStyle = { + color: "#007bff", + marginTop: "0", + marginBottom: "15px" + }; + + const listStyle = { + lineHeight: "1.8", + color: "#495057" + }; + + const cardTitle = this.domText.createHText( + 3, + "DOM Helper Classes", + { style: cardTitleStyle } + ); + + const description = this.domText.createPText( + "This example uses the following DOM helper classes to generate HTML programmatically:", + { style: { marginBottom: "10px" } } + ); + + const listItems = [ + this.domText.createLiText("DOMText - Text elements (h1-h6, p, span, strong, etc.)"), + this.domText.createLiText("DOMButton - Button elements with click handlers"), + this.domText.createLiText("DOMInput - Form input elements (text, checkbox, radio, etc.)"), + this.domText.createLiText("DOMLink - Anchor elements for navigation"), + this.domText.createLiText("DOMNested - Container elements (div, ul, form, etc.)"), + this.domText.createLiText("DOMMisc - Miscellaneous elements (hr, etc.)") + ]; + + const list = this.domNested.createList( + listItems, + { style: listStyle } + ); + + return this.domNested.createBlockDiv( + { style: cardStyle }, + undefined, + cardTitle, + description, + list + ); + } + + /** + * Create a complex form section. + * Demonstrates form composition with multiple input types. + */ + private createFormSection(): string { + const formStyle = { + backgroundColor: "#e8f4f8", + padding: "20px", + borderRadius: "8px", + marginBottom: "20px" + }; + + const formTitle = this.domText.createHText( + 3, + "Form Example", + { style: { marginTop: "0", marginBottom: "15px" } } + ); + + const labelStyle = { + display: "block", + marginBottom: "5px", + fontWeight: "bold", + color: "#333" + }; + + const inputStyle = { + padding: "8px", + width: "100%", + marginBottom: "15px", + border: "1px solid #ced4da", + borderRadius: "4px", + boxSizing: "border-box" + }; + + const nameLabel = this.domText.createLabelText( + "Name:", + { style: labelStyle } + ); + + const nameInput = this.domInput.createInput( + "text", + { + style: inputStyle, + placeholder: "Enter your name", + id: "name-input" + } + ); + + const emailLabel = this.domText.createLabelText( + "Email:", + { style: labelStyle } + ); + + const emailInput = this.domInput.createInput( + "email", + { + style: inputStyle, + placeholder: "Enter your email", + id: "email-input" + } + ); + + const messageLabel = this.domText.createLabelText( + "Message:", + { style: labelStyle } + ); + + const messageInput = this.domInput.createTextarea( + { + style: { ...inputStyle, height: "100px", resize: "vertical" }, + placeholder: "Enter your message", + id: "message-input" + } + ); + + const notifyCheckbox = this.domInput.createInput( + "checkbox", + { id: "notify-checkbox", style: { marginRight: "8px" } } + ); + + const notifyLabel = this.domText.createLabelText( + "Send me notifications", + { style: { display: "inline", fontWeight: "normal" } } + ); + + const checkboxContainer = this.domNested.createBlockDiv( + { style: { marginBottom: "15px" } }, + undefined, + notifyCheckbox + notifyLabel + ); + + const submitButton = this.domButton.createButton( + "Submit Form", + () => { /* Handler would be registered separately */ }, + { + style: { + padding: "10px 20px", + backgroundColor: "#007bff", + color: "white", + border: "none", + borderRadius: "4px", + cursor: "pointer", + fontSize: "14px" + } + } + ); + + const formContent = this.domNested.createForm( + { style: { marginTop: "15px" } }, + undefined, + nameLabel, + nameInput, + emailLabel, + emailInput, + messageLabel, + messageInput, + checkboxContainer, + submitButton + ); + + return this.domNested.createBlockDiv( + { style: formStyle }, + undefined, + formTitle, + formContent + ); + } + + /** + * Create a button section. + * Demonstrates button styling and composition. + */ + private createButtonSection(): string { + const sectionStyle = { + backgroundColor: "#d4edda", + padding: "20px", + borderRadius: "8px", + marginBottom: "20px" + }; + + const sectionTitle = this.domText.createHText( + 3, + "Button Examples", + { style: { marginTop: "0", marginBottom: "15px" } } + ); + + const primaryButton = this.domButton.createButton( + "Primary Action", + () => {}, + { + style: { + padding: "10px 20px", + backgroundColor: "#007bff", + color: "white", + border: "none", + borderRadius: "4px", + cursor: "pointer", + marginRight: "10px", + marginBottom: "10px" + } + } + ); + + const secondaryButton = this.domButton.createButton( + "Secondary Action", + () => {}, + { + style: { + padding: "10px 20px", + backgroundColor: "#6c757d", + color: "white", + border: "none", + borderRadius: "4px", + cursor: "pointer", + marginRight: "10px", + marginBottom: "10px" + } + } + ); + + const dangerButton = this.domButton.createButton( + "Danger Action", + () => {}, + { + style: { + padding: "10px 20px", + backgroundColor: "#dc3545", + color: "white", + border: "none", + borderRadius: "4px", + cursor: "pointer", + marginBottom: "10px" + } + } + ); + + const buttonContainer = this.domNested.createBlockDiv( + { style: { marginTop: "10px" } }, + undefined, + primaryButton, + secondaryButton, + dangerButton + ); + + return this.domNested.createBlockDiv( + { style: sectionStyle }, + undefined, + sectionTitle, + buttonContainer + ); + } + + /** + * Create a concepts section. + * Demonstrates links and dividers. + */ + private createConceptsSection(): string { + const sectionStyle = { + backgroundColor: "#fff3cd", + padding: "20px", + borderRadius: "8px" + }; + + const sectionTitle = this.domText.createHText( + 3, + "Key Concepts", + { style: { marginTop: "0", marginBottom: "15px" } } + ); + + const concepts = [ + this.domText.createPText( + this.domText.createStrongText("CSS-in-JS: ") + + "Style objects are converted to CSS classes using goober. This provides scoped styling and prevents conflicts." + ), + this.domText.createPText( + this.domText.createStrongText("Component Composition: ") + + "Complex UIs are built by composing simple elements. Each DOM helper returns an HTML string that can be combined." + ), + this.domText.createPText( + this.domText.createStrongText("Nested Structures: ") + + "Use DOMNested.createBlockDiv() to create containers that hold multiple child elements." + ), + this.domText.createPText( + this.domText.createStrongText("renderHTML(): ") + + "This method wraps your content with a