UNPKG

@plotinus/matrix-package-observable-coordinator

Version:

Observable coordinator pattern components using IntrospectableBaseCommunicationComponent and proper presentation architecture

558 lines (527 loc) 24 kB
// dist/browser/component-sources.js var componentSources = { // App component sources appCommunication: `class AppComponent extends window.Matrix.BaseCommunicationComponent { static dslTag = 'app'; static isMatrixComponent = true; startExecution() { console.log(\`App \${this.id} started\`); this.setState({ status: 'ready' }); } handleStartProcessing() { console.log('App: Starting processing'); this.sendCommand('myCoordinator', 'StartProcessing', { jobCount: 5 }); } handleCoordinatorReady() { console.log('App: Coordinator is ready, sending StartProcessing command'); this.sendCommand('myCoordinator', 'StartProcessing', { jobCount: 5 }); } handleCoordinatorDone(data) { console.log('App: All done!', data); this.setState({ completedJobs: data.completedJobIds }); } }`, appPresentation: `class AppPresentationElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } bindTo(commComponent) { this.commComponent = commComponent; this.render(); } render() { this.shadowRoot.innerHTML = \` <style> :host { display: block; } .app { border: 2px solid #8b5cf6; padding: 16px; border-radius: 8px; } button { background: #8b5cf6; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; } </style> <div class="app"> <h3>App: \${this.commComponent?.id}</h3> <button onclick="this.getRootNode().host.startProcessing()">Start Processing</button> <slot></slot> </div> \`; } startProcessing() { this.commComponent?.handleStartProcessing(); } }`, // Coordinator component sources coordinatorCommunication: `class CoordinatorComponent extends window.Matrix.BaseCommunicationComponent { static dslTag = 'coordinator'; static isMatrixComponent = true; constructor(id, eventBus) { super(id, eventBus); this.workers = new Set(); this.completedJobs = new Set(); this.jobQueue = []; } startExecution() { this.setState({ status: 'ready' }); this.emitEvent('CoordinatorReady'); } startProcessingHandler(data) { console.log('Coordinator: Starting processing'); console.log('Coordinator: Creating jobs'); for (let i = 0; i < (data.jobCount || 5); i++) { this.jobQueue.push({ id: \`job-\${i + 1}\` }); } this.distributeJobs(); } distributeJobs() { this.workers.forEach(workerId => { if (this.jobQueue.length > 0) { const job = this.jobQueue.shift(); this.sendCommand(workerId, 'ProcessJob', job); } }); } handleWorkerRegistered(data) { this.workers.add(data.workerId); this.setState({ workerCount: this.workers.size }); } handleWorkerJobCompleted(data) { this.completedJobs.add(data.jobId); if (this.jobQueue.length > 0) { const job = this.jobQueue.shift(); this.sendCommand(data.workerId, 'ProcessJob', job); } else if (this.completedJobs.size >= 5) { this.emitEvent('CoordinatorDone', { completedJobIds: Array.from(this.completedJobs) }); } } }`, coordinatorPresentation: `class CoordinatorPresentationElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } bindTo(commComponent) { this.commComponent = commComponent; // Subscribe to state changes if (commComponent.eventBus) { const stateKey = \`cmp:\${commComponent.id}:_stateChanged\`; commComponent.eventBus.on(stateKey, () => this.render()); } this.render(); } render() { const state = this.commComponent?.state || {}; this.shadowRoot.innerHTML = \` <style> :host { display: block; } .coordinator { border: 2px solid #58a6ff; padding: 16px; margin: 8px; border-radius: 8px; } </style> <div class="coordinator"> <h3>Coordinator: \${this.commComponent?.id}</h3> <div>Workers: \${state.workerCount || 0}</div> <slot></slot> </div> \`; } }`, // Worker component sources workerCommunication: `class WorkerComponent extends window.Matrix.BaseCommunicationComponent { static dslTag = 'worker'; static isMatrixComponent = true; startExecution() { this.setState({ jobsProcessed: 0 }); this.emitEvent('Registered', { workerId: this.id }); } processJobHandler(job) { console.log(\`Worker \${this.id}: Processing \${job.id}\`); setTimeout(() => { this.setState({ jobsProcessed: (this.state.jobsProcessed || 0) + 1 }); this.emitEvent('JobCompleted', { workerId: this.id, jobId: job.id }); }, 1000); } }`, workerPresentation: `class WorkerPresentationElement extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } bindTo(commComponent) { this.commComponent = commComponent; // Subscribe to state changes if (commComponent.eventBus) { const stateKey = \`cmp:\${commComponent.id}:_stateChanged\`; commComponent.eventBus.on(stateKey, () => this.render()); } this.render(); } render() { const state = this.commComponent?.state || {}; this.shadowRoot.innerHTML = \` <style> :host { display: block; } .worker { border: 2px solid #3fb950; padding: 12px; margin: 8px; border-radius: 6px; } </style> <div class="worker"> <h4>Worker: \${this.commComponent?.id}</h4> <div>Jobs: \${state.jobsProcessed || 0}</div> </div> \`; } }` }; // dist/presentation/app-presentation.js var AppPresentationElement = class 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 = []; this.logMessages = []; console.log("\u{1F3D7}\uFE0F AppPresentationElement: Constructor called"); this.attachShadow({ mode: "open" }); this.logToUI("\u{1F3D7}\uFE0F 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(`\u{1F517} AppPresentation: bindTo() called with:`, commComponent?.id || "undefined"); this.logToUI(`\u{1F517} bindTo() called with: ${commComponent?.id || "undefined"}`); this.commComponent = commComponent; if (commComponent.eventBus) { console.log(`\u{1F442} AppPresentation: Setting up event listeners for ${commComponent.id}`); commComponent.eventBus.on(`cmp:${commComponent.id}:_stateChanged`, (data) => { console.log(`\u{1F3AF} AppPresentation: Received _stateChanged event:`, data); this.onStateChanged(data.state); }); commComponent.eventBus.on(`cmp:${commComponent.id}:_propertyChanged`, (data) => { console.log(`\u{1F3AF} AppPresentation: Received _propertyChanged event:`, data); this.onPropertyChanged(data.propertyName, data.newValue, data.oldValue); }); commComponent.eventBus.on(`cmp:${commComponent.id}:_eventEmitted`, (data) => { console.log(`\u{1F3AF} AppPresentation: Received _eventEmitted event:`, data); }); console.log(`\u2705 AppPresentation: Event listeners registered for ${commComponent.id}`); } else { console.warn(`\u26A0\uFE0F AppPresentation: No eventBus found on communication component!`); } 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(`\u{1F3A8} AppPresentation: Rendering with current state:`, this.commComponent?.state); const state = this.commComponent?.state || {}; const coordinatorStatus = state.coordinatorReady ? "\u2705 Ready" : "\u23F3 Waiting"; const processingStatus = state.processingStarted ? "\u{1F504} Processing" : "\u23F8\uFE0F Idle"; const completionStatus = state.systemCompleted ? "\u2705 Complete" : "\u{1F504} In Progress"; const jobCount = state.completedJobs?.length || 0; this.shadowRoot.innerHTML = ` <style> /* \u{1F3A8} 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>\u{1F3AF} Observable App: ${this.commComponent?.id}</h3> <!-- \u{1F4CA} 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> <!-- \u{1F4CB} INTROSPECTION LOG: Real-time event and state change tracking --> <div class="logger"> <div style="color: #fbbf24; font-weight: bold;">\u{1F4CB} APP PRESENTATION LOG:</div> ${this.logMessages.map((msg) => `<div class="log-entry">${(/* @__PURE__ */ new Date()).toLocaleTimeString()}: ${msg}</div>`).join("")} </div> <!-- \u{1F680} UI INTERACTION: Button triggers communication component method --> <button onclick="this.getRootNode().host.startProcessing()">\u{1F680} 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(`\u{1F680} AppPresentation: Start processing button clicked`); this.logToUI("\u{1F680} Button clicked!"); 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) { this.logMessages.push(message); if (this.logMessages.length > 20) this.logMessages.shift(); if (this.shadowRoot?.innerHTML) { const loggerDiv = this.shadowRoot.querySelector(".logger"); if (loggerDiv) { loggerDiv.innerHTML = ` <div style="color: #fbbf24; font-weight: bold;">\u{1F4CB} APP PRESENTATION LOG:</div> ${this.logMessages.map((msg) => `<div class="log-entry">${(/* @__PURE__ */ 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(`\u{1F504} AppPresentation: State changed, re-rendering with state:`, newState); this.logToUI(`\u{1F504} State changed: ${JSON.stringify(newState)}`); 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(`\u{1F4DD} AppPresentation: Property ${propertyName} changed from ${oldValue} to ${newValue}, re-rendering`); this.logToUI(`\u{1F4DD} Property ${propertyName}: ${oldValue} \u2192 ${newValue}`); 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("\u{1F50C} AppPresentationElement: Connected to DOM"); this.logToUI("\u{1F50C} Connected to DOM"); this.render(); } }; // dist/presentation/coordinator-presentation.js var CoordinatorPresentationElement = class extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); } bindTo(commComponent) { this.commComponent = commComponent; if (commComponent.eventBus) { const stateKey = `cmp:${commComponent.id}:_stateChanged`; commComponent.eventBus.on(stateKey, () => this.render()); } this.render(); } render() { const state = this.commComponent?.state || {}; this.shadowRoot.innerHTML = ` <style> :host { display: block; } .coordinator { border: 2px solid #58a6ff; padding: 16px; margin: 8px; border-radius: 8px; } </style> <div class="coordinator"> <h3>Coordinator: ${this.commComponent?.id}</h3> <div>Workers: ${state.workerCount || 0}</div> <slot></slot> </div> `; } }; // dist/presentation/worker-presentation.js var WorkerPresentationElement = class extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); } bindTo(commComponent) { this.commComponent = commComponent; if (commComponent.eventBus) { const stateKey = `cmp:${commComponent.id}:_stateChanged`; commComponent.eventBus.on(stateKey, () => this.render()); } this.render(); } render() { const state = this.commComponent?.state || {}; this.shadowRoot.innerHTML = ` <style> :host { display: block; } .worker { border: 2px solid #3fb950; padding: 12px; margin: 8px; border-radius: 6px; } </style> <div class="worker"> <h4>Worker: ${this.commComponent?.id}</h4> <div>Jobs: ${state.jobsProcessed || 0}</div> </div> `; } }; // dist/browser-entry.js function registerObservableCoordinatorComponents(Matrix) { if (!Matrix || !Matrix.BaseCommunicationComponent) { throw new Error("Matrix framework not found"); } const components = { app: { name: "AppComponent", source: componentSources.appCommunication, tag: "app" }, coordinator: { name: "CoordinatorComponent", source: componentSources.coordinatorCommunication, tag: "coordinator" }, worker: { name: "WorkerComponent", source: componentSources.workerCommunication, tag: "worker" } }; Object.values(components).forEach((comp) => { try { const ComponentClass = new Function( "BaseCommunicationComponent", comp.source + "\nreturn " + comp.name )(Matrix.BaseCommunicationComponent); ComponentClass.dslTag = comp.tag; ComponentClass.isMatrixComponent = true; if (!window.MatrixComponents) { window.MatrixComponents = {}; } window.MatrixComponents[comp.name] = ComponentClass; Matrix.register(ComponentClass); console.log("Registered communication component:", comp.tag); } catch (error) { console.error("Failed to register component:", comp.name, error); } }); if (!customElements.get("app-presentation")) { customElements.define("app-presentation", AppPresentationElement); } if (!customElements.get("coordinator-presentation")) { customElements.define("coordinator-presentation", CoordinatorPresentationElement); } if (!customElements.get("worker-presentation")) { customElements.define("worker-presentation", WorkerPresentationElement); } console.log("All observable coordinator components registered"); } if (typeof window !== "undefined") { window.MatrixPackageObservableCoordinator = { componentSources, AppPresentationElement, CoordinatorPresentationElement, WorkerPresentationElement, registerObservableCoordinatorComponents }; } export { AppPresentationElement, CoordinatorPresentationElement, WorkerPresentationElement, componentSources, registerObservableCoordinatorComponents }; //# sourceMappingURL=browser-bundle.js.map