/** * ComponentPlayground * * Interactive development tool for LiveComponents. * * Features: * - Component browser with search and filtering * - Live component preview with auto-refresh * - JSON state editor with validation * - Action tester with parameter support * - Performance metrics visualization * - Template code generator * * Usage: * import { ComponentPlayground } from './modules/livecomponent/ComponentPlayground'; * * const playground = new ComponentPlayground('#playground-container'); * playground.init(); */ export class ComponentPlayground { /** * @param {string} containerSelector - Container element selector */ constructor(containerSelector) { this.container = document.querySelector(containerSelector); if (!this.container) { throw new Error(`Container not found: ${containerSelector}`); } // State this.components = []; this.selectedComponent = null; this.componentMetadata = null; this.currentState = {}; this.previewInstanceId = `playground-${Date.now()}`; // UI Elements (will be created in init()) this.componentList = null; this.componentSearch = null; this.stateEditor = null; this.previewContainer = null; this.actionTester = null; this.metricsDisplay = null; // Performance tracking this.metrics = { renderTime: 0, stateSize: 0, actionExecutions: 0 }; } /** * Initialize playground */ async init() { this.buildUI(); await this.loadComponents(); this.attachEventListeners(); } /** * Build playground UI structure */ buildUI() { this.container.innerHTML = `

LiveComponent Playground

Interactive development tool for testing LiveComponents

Component State

Live Preview

Select a component to preview

Actions

Select a component to test actions

Template Code

Select a component to generate code
`; // Cache UI elements this.componentList = this.container.querySelector('#component-list'); this.componentSearch = this.container.querySelector('#component-search'); this.stateEditor = this.container.querySelector('#state-editor'); this.previewContainer = this.container.querySelector('#preview-container'); this.actionTester = this.container.querySelector('#action-tester'); this.metricsDisplay = this.container.querySelector('#metrics-display'); } /** * Load all available components */ async loadComponents() { try { const response = await fetch('/playground/api/components'); const data = await response.json(); this.components = data.components || []; this.renderComponentList(this.components); } catch (error) { this.componentList.innerHTML = `
Failed to load components: ${error.message}
`; } } /** * Render component list */ renderComponentList(components) { if (components.length === 0) { this.componentList.innerHTML = '
No components found
'; return; } this.componentList.innerHTML = components.map(component => `
${component.name}
${component.properties} props ${component.actions} actions ${component.has_cache ? 'cached' : ''}
`).join(''); } /** * Attach event listeners */ attachEventListeners() { // Component selection this.componentList.addEventListener('click', (e) => { const item = e.target.closest('.playground__component-item'); if (item) { this.selectComponent(item.dataset.component); } }); // Component search this.componentSearch.addEventListener('input', (e) => { this.filterComponents(e.target.value); }); // State editor actions this.container.querySelector('#apply-state').addEventListener('click', () => { this.applyState(); }); this.container.querySelector('#reset-state').addEventListener('click', () => { this.resetState(); }); this.container.querySelector('#format-json').addEventListener('click', () => { this.formatJSON(); }); // Copy code this.container.querySelector('#copy-code').addEventListener('click', () => { this.copyCode(); }); } /** * Filter components by search term */ filterComponents(searchTerm) { const term = searchTerm.toLowerCase().trim(); if (!term) { this.renderComponentList(this.components); return; } const filtered = this.components.filter(component => component.name.toLowerCase().includes(term) || component.class.toLowerCase().includes(term) ); this.renderComponentList(filtered); } /** * Select component */ async selectComponent(componentName) { // Update active state this.componentList.querySelectorAll('.playground__component-item').forEach(item => { item.classList.remove('playground__component-item--active'); }); const selectedItem = this.componentList.querySelector(`[data-component="${componentName}"]`); if (selectedItem) { selectedItem.classList.add('playground__component-item--active'); } this.selectedComponent = componentName; // Load metadata await this.loadComponentMetadata(componentName); // Reset state this.resetState(); // Render actions this.renderActions(); // Generate code this.updateGeneratedCode(); } /** * Load component metadata */ async loadComponentMetadata(componentName) { try { const response = await fetch(`/playground/api/component/${componentName}`); const data = await response.json(); if (data.success) { this.componentMetadata = data.data; } else { console.error('Failed to load metadata:', data.error); } } catch (error) { console.error('Error loading metadata:', error); } } /** * Apply state from editor */ async applyState() { const jsonText = this.stateEditor.value.trim(); const validationEl = this.container.querySelector('#state-validation'); // Validate JSON try { const state = jsonText ? JSON.parse(jsonText) : {}; this.currentState = state; validationEl.innerHTML = '✓ Valid JSON'; // Preview component with new state await this.previewComponent(); } catch (error) { validationEl.innerHTML = `✗ Invalid JSON: ${error.message}`; } } /** * Reset state to default */ resetState() { this.currentState = {}; this.stateEditor.value = '{}'; this.container.querySelector('#state-validation').innerHTML = ''; if (this.selectedComponent) { this.previewComponent(); } } /** * Format JSON in editor */ formatJSON() { try { const json = JSON.parse(this.stateEditor.value || '{}'); this.stateEditor.value = JSON.stringify(json, null, 2); this.container.querySelector('#state-validation').innerHTML = '✓ Formatted'; } catch (error) { this.container.querySelector('#state-validation').innerHTML = `✗ Invalid JSON`; } } /** * Preview component with current state */ async previewComponent() { if (!this.selectedComponent) return; this.previewContainer.innerHTML = '
Loading preview...
'; try { const startTime = performance.now(); const response = await fetch('/playground/api/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ component_name: this.selectedComponent, state: this.currentState, instance_id: this.previewInstanceId }) }); const data = await response.json(); const endTime = performance.now(); if (data.success) { this.previewContainer.innerHTML = data.html; // Update metrics this.metrics.renderTime = data.render_time_ms; this.metrics.stateSize = JSON.stringify(data.state).length; this.updateMetrics(); // Initialize LiveComponent in preview if (window.LiveComponent) { window.LiveComponent.initComponent(this.previewContainer.firstElementChild); } } else { this.previewContainer.innerHTML = `
Preview failed: ${data.error}
`; } } catch (error) { this.previewContainer.innerHTML = `
Error: ${error.message}
`; } } /** * Render actions for selected component */ renderActions() { if (!this.componentMetadata || !this.componentMetadata.actions) { this.actionTester.innerHTML = '
No actions available
'; return; } const actions = this.componentMetadata.actions.filter(action => !['onMount', 'onUpdated', 'onDestroy'].includes(action.name) ); if (actions.length === 0) { this.actionTester.innerHTML = '
No actions available
'; return; } this.actionTester.innerHTML = actions.map(action => `
${action.parameters.length > 0 ? `
${action.parameters.map(param => ` `).join('')}
` : ''}
`).join(''); // Attach action button listeners this.actionTester.querySelectorAll('[data-action]').forEach(button => { button.addEventListener('click', () => { this.executeAction(button.dataset.action, button.closest('.playground__action')); }); }); } /** * Execute component action */ async executeAction(actionName, actionElement) { // Get parameters const parameters = {}; if (actionElement) { actionElement.querySelectorAll('[data-param]').forEach(input => { const paramName = input.dataset.param; let value = input.value; // Try to parse as number or boolean if (value === 'true') value = true; else if (value === 'false') value = false; else if (!isNaN(value) && value !== '') value = Number(value); parameters[paramName] = value; }); } try { const response = await fetch('/playground/api/action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ component_id: `${this.selectedComponent}:${this.previewInstanceId}`, action_name: actionName, parameters: parameters, current_state: this.currentState }) }); const data = await response.json(); if (data.success) { // Update current state this.currentState = data.new_state; this.stateEditor.value = JSON.stringify(this.currentState, null, 2); // Update preview this.previewContainer.innerHTML = data.html; // Update metrics this.metrics.actionExecutions++; this.updateMetrics(); // Re-initialize LiveComponent if (window.LiveComponent) { window.LiveComponent.initComponent(this.previewContainer.firstElementChild); } } else { alert(`Action failed: ${data.error}`); } } catch (error) { alert(`Error executing action: ${error.message}`); } } /** * Update metrics display */ updateMetrics() { this.metricsDisplay.innerHTML = `
Render Time ${this.metrics.renderTime.toFixed(2)}ms
State Size ${this.metrics.stateSize} bytes
Actions Executed ${this.metrics.actionExecutions}
`; } /** * Update generated template code */ updateGeneratedCode() { if (!this.selectedComponent) return; const code = `\n{{{ ${this.selectedComponent} }}}`; this.container.querySelector('#generated-code code').textContent = code; } /** * Copy generated code to clipboard */ async copyCode() { const code = this.container.querySelector('#generated-code code').textContent; try { await navigator.clipboard.writeText(code); const button = this.container.querySelector('#copy-code'); const originalText = button.textContent; button.textContent = '✓ Copied!'; setTimeout(() => { button.textContent = originalText; }, 2000); } catch (error) { alert('Failed to copy code to clipboard'); } } } export default ComponentPlayground;