Spaces:
Running
Running
| class LogConsole extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.attachShadow({ mode: 'open' }); | |
| this.entries = []; | |
| this.maxEntries = 100; | |
| } | |
| connectedCallback() { | |
| this.render(); | |
| } | |
| render() { | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: flex; | |
| flex-direction: column; | |
| flex: 1; | |
| overflow: hidden; | |
| background: #0f172a; | |
| font-family: 'JetBrains Mono', 'Fira Code', monospace; | |
| font-size: 0.875rem; | |
| } | |
| .console-content { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 16px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| .entry { | |
| display: flex; | |
| gap: 12px; | |
| line-height: 1.5; | |
| animation: fadeIn 0.2s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateX(-10px); } | |
| to { opacity: 1; transform: translateX(0); } | |
| } | |
| .timestamp { | |
| color: #6b7280; | |
| user-select: none; | |
| white-space: nowrap; | |
| } | |
| .message { | |
| color: #e5e7eb; | |
| word-break: break-all; | |
| } | |
| .type-system { color: #60a5fa; } | |
| .type-info { color: #34d399; } | |
| .type-success { color: #10b981; } | |
| .type-warning { color: #fbbf24; } | |
| .type-error { color: #ef4444; } | |
| .type-process { color: #a78bfa; } | |
| .type-network { color: #22d3ee; } | |
| .type-debug { color: #9ca3af; font-style: italic; } | |
| .empty-state { | |
| color: #4b5563; | |
| text-align: center; | |
| padding: 40px; | |
| font-style: italic; | |
| } | |
| .scroll-indicator { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| height: 40px; | |
| background: linear-gradient(transparent, #0f172a); | |
| pointer-events: none; | |
| } | |
| </style> | |
| <div class="console-content" id="consoleContent"> | |
| <div class="empty-state">Waiting for process output...</div> | |
| </div> | |
| `; | |
| this.contentEl = this.shadowRoot.getElementById('consoleContent'); | |
| } | |
| addEntry(text, type = 'info') { | |
| // Remove empty state if present | |
| if (this.entries.length === 0) { | |
| this.contentEl.innerHTML = ''; | |
| } | |
| const entry = document.createElement('div'); | |
| entry.className = 'entry'; | |
| const typeClass = `type-${type}`; | |
| // Parse timestamp if included in text | |
| let displayText = text; | |
| let timestamp = ''; | |
| if (text.startsWith('[') && text.includes(']')) { | |
| const match = text.match(/\[(.*?)\](.*)/); | |
| if (match) { | |
| timestamp = `[${match[1]}]`; | |
| displayText = match[2].trim(); | |
| } | |
| } | |
| entry.innerHTML = ` | |
| <span class="timestamp ${typeClass}">${timestamp || new Date().toLocaleTimeString('en-US', { hour12: false })}</span> | |
| <span class="message ${typeClass}">${this.escapeHtml(displayText)}</span> | |
| `; | |
| this.contentEl.appendChild(entry); | |
| this.entries.push(entry); | |
| // Limit entries | |
| if (this.entries.length > this.maxEntries) { | |
| this.entries.shift().remove(); | |
| } | |
| // Auto-scroll to bottom | |
| this.contentEl.scrollTop = this.contentEl.scrollHeight; | |
| } | |
| clear() { | |
| this.entries = []; | |
| this.contentEl.innerHTML = '<div class="empty-state">Console cleared. Waiting for process output...</div>'; | |
| } | |
| escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| } | |
| customElements.define('log-console', LogConsole); |