/** Dashboard: workspace controls rendering (buttons, tables, labels, displays, machines). */ import { esc } from './util.js'; import { addTrace } from './trace.js'; import { setDashboard } from './chat.js'; let _ws = null; export function setWs(ws) { _ws = ws; } export function dockControls(controls) { setDashboard(controls); // S3*: remember what's rendered const body = document.getElementById('workspace-body'); if (!body) return; body.innerHTML = ''; const container = document.createElement('div'); container.className = 'controls-container'; for (const ctrl of controls) { if (ctrl.type === 'button') { const btn = document.createElement('button'); btn.className = 'control-btn'; btn.textContent = ctrl.label; btn.onclick = () => { if (_ws && _ws.readyState === 1) { _ws.send(JSON.stringify({ type: 'action', action: ctrl.action, data: ctrl.payload || ctrl.data || {} })); addTrace('runtime', 'action', ctrl.action); } }; container.appendChild(btn); } else if (ctrl.type === 'table') { const table = document.createElement('table'); table.className = 'control-table'; if (ctrl.columns) { const thead = document.createElement('tr'); for (const col of ctrl.columns) { const th = document.createElement('th'); th.textContent = col; thead.appendChild(th); } table.appendChild(thead); } for (const row of (ctrl.data || [])) { const tr = document.createElement('tr'); if (Array.isArray(row)) { for (const cell of row) { const td = document.createElement('td'); td.textContent = cell; tr.appendChild(td); } } else if (typeof row === 'object') { for (const col of (ctrl.columns || Object.keys(row))) { const td = document.createElement('td'); td.textContent = row[col] ?? ''; tr.appendChild(td); } } table.appendChild(tr); } container.appendChild(table); } else if (ctrl.type === 'label') { const lbl = document.createElement('div'); lbl.className = 'control-label'; lbl.innerHTML = '' + esc(ctrl.text || '') + '' + esc(String(ctrl.value ?? '')) + ''; container.appendChild(lbl); } else if (ctrl.type === 'display') { const disp = document.createElement('div'); const dt = ctrl.display_type || 'text'; const style = ctrl.style ? ' display-' + ctrl.style : ''; disp.className = 'control-display display-' + dt + style; if (dt === 'progress') { const pct = Math.min(100, Math.max(0, Number(ctrl.value) || 0)); disp.innerHTML = '' + esc(ctrl.label) + '' + '
' + '' + pct + '%'; } else if (dt === 'status') { disp.innerHTML = '' + (ctrl.style === 'success' ? '\u2713' : ctrl.style === 'error' ? '\u2717' : '\u2139') + '' + '' + esc(ctrl.label) + ''; } else { disp.innerHTML = '' + esc(ctrl.label) + '' + (ctrl.value ? '' + esc(String(ctrl.value)) + '' : ''); } container.appendChild(disp); } else if (ctrl.type === 'card') { container.appendChild(renderCard(ctrl)); } else if (ctrl.type === 'list') { const listEl = document.createElement('div'); listEl.className = 'ws-list'; if (ctrl.title) { const h = document.createElement('div'); h.className = 'ws-list-title'; h.textContent = ctrl.title; listEl.appendChild(h); } for (const item of (ctrl.items || [])) { item.type = item.type || 'card'; listEl.appendChild(renderCard(item)); } container.appendChild(listEl); } } body.appendChild(container); } function renderCard(card) { const el = document.createElement('div'); el.className = 'ws-card'; if (card.action) { el.classList.add('ws-card-clickable'); el.onclick = () => { if (_ws && _ws.readyState === 1) { _ws.send(JSON.stringify({ type: 'action', action: card.action, data: card.payload || {} })); addTrace('runtime', 'action', card.action); } }; } let html = ''; if (card.title) html += '
' + esc(card.title) + '
'; if (card.subtitle) html += '
' + esc(card.subtitle) + '
'; if (card.fields && card.fields.length) { html += '
'; for (const f of card.fields) { const val = f.action ? '' + esc(String(f.value ?? '')) + '' : '' + esc(String(f.value ?? '')) + ''; html += '
' + esc(f.label || '') + '' + val + '
'; } html += '
'; } if (card.actions && card.actions.length) { html += '
'; for (const a of card.actions) { html += ''; } html += '
'; } el.innerHTML = html; // Wire up field links and action buttons el.querySelectorAll('.ws-card-link').forEach(link => { link.onclick = (e) => { e.stopPropagation(); const action = link.dataset.action; if (_ws && _ws.readyState === 1) { _ws.send(JSON.stringify({ type: 'action', action, data: {} })); addTrace('runtime', 'action', action); } }; }); el.querySelectorAll('.ws-card-btn').forEach(btn => { btn.onclick = (e) => { e.stopPropagation(); const action = btn.dataset.action; if (_ws && _ws.readyState === 1) { _ws.send(JSON.stringify({ type: 'action', action, data: {} })); addTrace('runtime', 'action', action); } }; }); return el; } export function clearDashboard() { const body = document.getElementById('workspace-body'); if (body) body.innerHTML = ''; }