UNPKG

@plotinus/matrix-package-observable-coordinator

Version:

Observable coordinator pattern components using IntrospectableBaseCommunicationComponent and proper presentation architecture

323 lines (319 loc) β€’ 22.7 kB
/** * ======================================================================================================== * 🎯 APP PRESENTATION ELEMENT - REAL-TIME INTROSPECTION UI COMPONENT * ======================================================================================================== * * This custom HTML element demonstrates the PRESENTATION LAYER of the Matrix Framework's * IntrospectableBaseCommunicationComponent pattern. It provides real-time visualization * of observable state changes and events from the AppComponent communication layer. * * πŸ”₯ INTROSPECTION PATTERNS DEMONSTRATED: * ──────────────────────────────────────────────────────────────────────────────────────────────────── * β€’ Real-time State Observation: Listens to _stateChanged events for automatic UI updates * β€’ Property-Level Tracking: Responds to _propertyChanged events for granular change detection * β€’ Event Emission Monitoring: Observes _eventEmitted events for complete system transparency * β€’ Two-Way Communication: UI interactions trigger communication component methods * β€’ Presentation Layer Separation: Pure presentation logic separate from business logic * * 🎭 PRESENTATION RESPONSIBILITIES: * ──────────────────────────────────────────────────────────────────────────────────────────────────── * β€’ Subscribe to introspection events from bound communication component * β€’ Provide real-time UI updates based on observable state changes * β€’ Display system status: coordinator readiness, processing state, job completion * β€’ Offer UI controls for triggering system operations (Start Processing button) * β€’ Maintain presentation-specific state (log messages, UI interactions) * * πŸš€ EVENT-DRIVEN UI LIFECYCLE: * ──────────────────────────────────────────────────────────────────────────────────────────────────── * 1. bindTo() β†’ Subscribe to communication component's introspection events * 2. onStateChanged() β†’ Re-render UI when observable state changes * 3. onPropertyChanged() β†’ Update specific UI elements when individual properties change * 4. User interactions β†’ Trigger communication component methods via direct calls * 5. Log display β†’ Track all events and state changes for debugging visibility * * ======================================================================================================== */ /** * 🎯 AppPresentationElement - Custom Element for Observable State Visualization * * Extends HTMLElement to provide: * β€’ Shadow DOM encapsulation for isolated presentation styling * β€’ Event-driven UI updates based on communication component introspection * β€’ Real-time logging and state display for system transparency * β€’ User interaction controls that trigger communication component methods */ export class AppPresentationElement extends HTMLElement { /** * πŸ—οΈ Element Construction - Setting Up Presentation Infrastructure * ══════════════════════════════════════════════════════════════════════════════════════════════════ * * Initializes the custom element with Shadow DOM and presentation-specific state. * This demonstrates clean separation between presentation state (logMessages) * and communication component state (managed by AppComponent). */ constructor() { super(); this.logMessages = []; // πŸ“ PRESENTATION STATE: UI-specific data separate from communication component this.logMessages = []; // Array of log messages for UI display // πŸ” DEBUGGING: Track element lifecycle for transparency console.log('πŸ—οΈ AppPresentationElement: Constructor called'); // 🎭 SHADOW DOM SETUP: Encapsulated styling and structure this.attachShadow({ mode: 'open' }); // πŸ“‹ INITIAL LOG: Track element creation in UI log this.logToUI('πŸ—οΈ AppPresentation element constructed'); } /** * πŸ”— Component Binding - The Core Introspection Connection * ══════════════════════════════════════════════════════════════════════════════════════════════════ * * This method establishes the CRITICAL connection between the presentation layer and the * communication component's introspection system. It subscribes to three key introspection * events that enable real-time UI updates based on observable state changes. * * INTROSPECTION EVENTS SUBSCRIBED: * β€’ _stateChanged: Full state object updates (triggers complete UI re-render) * β€’ _propertyChanged: Individual property updates (triggers targeted UI updates) * β€’ _eventEmitted: Component event emissions (for debugging and system transparency) */ bindTo(commComponent) { console.log(`πŸ”— AppPresentation: bindTo() called with:`, commComponent?.id || 'undefined'); this.logToUI(`πŸ”— bindTo() called with: ${commComponent?.id || 'undefined'}`); // 🎯 STORE COMMUNICATION COMPONENT REFERENCE: Enable direct method calls from UI this.commComponent = commComponent; // 🎧 INTROSPECTION EVENT SUBSCRIPTIONS: The heart of observable UI patterns // ────────────────────────────────────────────────────────────────────────────────────────────── if (commComponent.eventBus) { console.log(`πŸ‘‚ AppPresentation: Setting up event listeners for ${commComponent.id}`); // πŸ”„ STATE CHANGE SUBSCRIPTION: Complete state object updates // ────────────────────────────────────────────────────────────────────────────────────────── // Triggered when AppComponent calls setState() with full or partial state updates // Results in complete UI re-render to reflect new system status commComponent.eventBus.on(`cmp:${commComponent.id}:_stateChanged`, (data) => { console.log(`🎯 AppPresentation: Received _stateChanged event:`, data); this.onStateChanged(data.state); }); // πŸ“ PROPERTY CHANGE SUBSCRIPTION: Individual property updates // ────────────────────────────────────────────────────────────────────────────────────────── // Triggered for each individual property when setState() modifies specific fields // Enables granular UI updates and detailed change tracking commComponent.eventBus.on(`cmp:${commComponent.id}:_propertyChanged`, (data) => { console.log(`🎯 AppPresentation: Received _propertyChanged event:`, data); this.onPropertyChanged(data.propertyName, data.newValue, data.oldValue); }); // πŸ“‘ EVENT EMISSION SUBSCRIPTION: Component event tracking // ────────────────────────────────────────────────────────────────────────────────────────── // Triggered when AppComponent calls emitEvent() for inter-component communication // Provides complete system transparency for debugging and monitoring commComponent.eventBus.on(`cmp:${commComponent.id}:_eventEmitted`, (data) => { console.log(`🎯 AppPresentation: Received _eventEmitted event:`, data); // Could add UI indicators for events emitted, notifications, etc. }); console.log(`βœ… AppPresentation: Event listeners registered for ${commComponent.id}`); } else { console.warn(`⚠️ AppPresentation: No eventBus found on communication component!`); } // 🎨 INITIAL RENDER: Display current state immediately after binding this.render(); } /** * 🎨 UI Rendering - State-Driven Dynamic Display * ══════════════════════════════════════════════════════════════════════════════════════════════════ * * This method demonstrates REACTIVE UI PATTERNS where the presentation layer * automatically updates based on observable state changes from the communication component. * It transforms abstract state data into visual indicators and user-friendly status displays. * * RENDERING PATTERNS DEMONSTRATED: * β€’ State-to-UI Mapping: Convert boolean/array state to visual status indicators * β€’ Conditional UI Elements: Show/hide elements based on state values * β€’ Real-time Data Binding: Automatic UI updates when state changes via introspection * β€’ Presentation State Integration: Combine communication state with UI-specific data */ render() { console.log(`🎨 AppPresentation: Rendering with current state:`, this.commComponent?.state); // πŸ” AVOID INFINITE LOOPS: Don't call logToUI here as render() is called from onStateChanged() // πŸ“Š STATE EXTRACTION: Get current observable state from communication component const state = this.commComponent?.state || {}; // 🎯 STATE-TO-UI MAPPING: Transform abstract state into visual indicators // ────────────────────────────────────────────────────────────────────────────────────────────── const coordinatorStatus = state.coordinatorReady ? 'βœ… Ready' : '⏳ Waiting'; const processingStatus = state.processingStarted ? 'πŸ”„ Processing' : '⏸️ Idle'; const completionStatus = state.systemCompleted ? 'βœ… Complete' : 'πŸ”„ In Progress'; const jobCount = state.completedJobs?.length || 0; // 🎭 SHADOW DOM CONTENT: Complete UI structure with state-driven content // ────────────────────────────────────────────────────────────────────────────────────────────── // This demonstrates how presentation layer renders dynamic content based on // observable state from communication components, creating a real-time dashboard this.shadowRoot.innerHTML = ` <style> /* 🎨 COMPONENT STYLING: Encapsulated styles for presentation layer */ :host { display: block; } .app { border: 2px solid #8b5cf6; padding: 16px; border-radius: 8px; background: #1f2937; color: #e5e7eb; } .app h3 { color: #8b5cf6; margin-top: 0; } .status { margin: 8px 0; padding: 8px; background: #374151; border-radius: 4px; } .status-item { margin: 4px 0; } .logger { background: #111827; color: #10b981; padding: 8px; margin: 8px 0; border-radius: 4px; font-family: monospace; font-size: 11px; max-height: 150px; overflow-y: auto; border: 1px solid #374151; } .log-entry { margin: 2px 0; word-break: break-all; } button { background: #8b5cf6; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; margin: 4px; transition: background-color 0.2s; } button:hover { background: #7c3aed; } .jobs { font-family: monospace; font-size: 12px; color: #fbbf24; } </style> <div class="app"> <h3>🎯 Observable App: ${this.commComponent?.id}</h3> <!-- πŸ“Š REAL-TIME STATUS DISPLAY: Observable state visualized as status indicators --> <div class="status"> <div class="status-item"><strong>Coordinator:</strong> ${coordinatorStatus}</div> <div class="status-item"><strong>Processing:</strong> ${processingStatus}</div> <div class="status-item"><strong>System:</strong> ${completionStatus}</div> <div class="status-item"><strong>Completed Jobs:</strong> ${jobCount}</div> ${state.completedJobs?.length ? `<div class="jobs">Jobs: ${state.completedJobs.join(', ')}</div>` : ''} </div> <!-- πŸ“‹ INTROSPECTION LOG: Real-time event and state change tracking --> <div class="logger"> <div style="color: #fbbf24; font-weight: bold;">πŸ“‹ APP PRESENTATION LOG:</div> ${this.logMessages.map(msg => `<div class="log-entry">${new Date().toLocaleTimeString()}: ${msg}</div>`).join('')} </div> <!-- πŸš€ UI INTERACTION: Button triggers communication component method --> <button onclick="this.getRootNode().host.startProcessing()">πŸš€ Start Processing</button> <slot></slot> </div> `; } /** * πŸš€ User Interaction Handler - UI-to-Communication Bridge * ══════════════════════════════════════════════════════════════════════════════════════════════════ * * This method demonstrates the TWO-WAY COMMUNICATION pattern where UI interactions * trigger communication component methods. The presentation layer acts as a bridge * between user actions and the observable communication layer. * * INTERACTION FLOW: * Button click β†’ startProcessing() β†’ commComponent.handleStartProcessing() β†’ setState() β†’ introspection events β†’ UI updates */ startProcessing() { console.log(`πŸš€ AppPresentation: Start processing button clicked`); this.logToUI('πŸš€ Button clicked!'); // 🎯 UI-TO-COMMUNICATION BRIDGE: Direct method call on communication component // This triggers the observable state changes that will flow back to the UI via introspection events this.commComponent?.handleStartProcessing(); } /** * πŸ“‹ Presentation Logging - UI-Specific State Management * ══════════════════════════════════════════════════════════════════════════════════════════════════ * * This method demonstrates PRESENTATION-SPECIFIC STATE management separate from * communication component observable state. It maintains UI-only data (log messages) * and provides targeted DOM updates to avoid infinite render loops. * * PRESENTATION STATE PATTERNS: * β€’ UI-Only Data: logMessages array is presentation-specific, not part of communication state * β€’ Targeted Updates: Update only the logger section to avoid triggering full re-renders * β€’ Circular Reference Prevention: Careful not to call this from render() method */ logToUI(message) { // πŸ“ PRESENTATION STATE UPDATE: Add to UI-specific log array this.logMessages.push(message); // 🧹 MEMORY MANAGEMENT: Prevent unlimited log accumulation if (this.logMessages.length > 20) this.logMessages.shift(); // Keep last 20 messages // 🎯 TARGETED DOM UPDATE: Update only logger section to avoid infinite render loops // ────────────────────────────────────────────────────────────────────────────────────────────── // This demonstrates how to update specific UI sections without triggering // full re-renders that could cause circular update loops if (this.shadowRoot?.innerHTML) { const loggerDiv = this.shadowRoot.querySelector('.logger'); if (loggerDiv) { loggerDiv.innerHTML = ` <div style="color: #fbbf24; font-weight: bold;">πŸ“‹ APP PRESENTATION LOG:</div> ${this.logMessages.map(msg => `<div class="log-entry">${new Date().toLocaleTimeString()}: ${msg}</div>`).join('')} `; } } } /** * πŸ”„ State Change Handler - Observable State Response * ══════════════════════════════════════════════════════════════════════════════════════════════════ * * This method is called automatically when the communication component's setState() method * is called, demonstrating the REACTIVE UI PATTERN core to the Matrix Framework's * introspection system. It shows how presentation layers stay synchronized with business logic. * * INTROSPECTION EVENT FLOW: * setState() in AppComponent β†’ _stateChanged event β†’ this.onStateChanged() β†’ render() β†’ UI updates */ onStateChanged(newState) { console.log(`πŸ”„ AppPresentation: State changed, re-rendering with state:`, newState); // πŸ“‹ LOG STATE CHANGE: Track state transitions for debugging transparency this.logToUI(`πŸ”„ State changed: ${JSON.stringify(newState)}`); // 🎨 TRIGGER RE-RENDER: Update entire UI to reflect new state // This demonstrates the core reactive pattern where state changes automatically update UI this.render(); } /** * πŸ“ Property Change Handler - Granular State Tracking * ══════════════════════════════════════════════════════════════════════════════════════════════════ * * This method provides GRANULAR CHANGE TRACKING for individual properties within the * observable state. It's called for each property that changes when setState() is called, * enabling detailed monitoring and potentially targeted UI updates. * * GRANULAR INTROSPECTION PATTERN: * setState({prop1: val1, prop2: val2}) β†’ triggers onPropertyChanged() twice β†’ individual property tracking */ onPropertyChanged(propertyName, newValue, oldValue) { console.log(`πŸ“ AppPresentation: Property ${propertyName} changed from ${oldValue} to ${newValue}, re-rendering`); // πŸ“‹ LOG PROPERTY CHANGE: Track individual property changes for detailed debugging this.logToUI(`πŸ“ Property ${propertyName}: ${oldValue} β†’ ${newValue}`); // 🎨 TRIGGER RE-RENDER: Could be optimized to update only affected UI sections // This demonstrates the basic reactive pattern; advanced implementations might // update only the UI elements affected by specific property changes this.render(); } /** * πŸ”Œ DOM Connection Lifecycle - Web Component Integration * ══════════════════════════════════════════════════════════════════════════════════════════════════ * * This Web Components lifecycle method is called when the element is added to the DOM. * It demonstrates the integration between Matrix Framework components and standard * Web Components lifecycle, ensuring proper initialization and rendering. * * WEB COMPONENT LIFECYCLE INTEGRATION: * Element creation β†’ constructor() β†’ bindTo() β†’ connectedCallback() β†’ DOM ready */ connectedCallback() { console.log('πŸ”Œ AppPresentationElement: Connected to DOM'); // πŸ“‹ LOG DOM CONNECTION: Track element lifecycle for debugging this.logToUI('πŸ”Œ Connected to DOM'); // 🎨 ENSURE RENDERING: Make sure UI is displayed when element enters DOM // This provides a safety net for rendering in case bindTo() was called before DOM connection this.render(); } }