// ========================================== // Switchboard Core — Dashboard Package (v0.31.1) // ========================================== // SDK Exercise Surface. Exercises every sw.* primitive with ZERO // component CSS overrides. All styling comes from the SDK itself. // // Primitives exercised: // sw.userMenu, sw.menu, sw.toolbar, sw.tabs, sw.dropdown, // sw.chat, sw.notes, sw.modal, sw.confirm, sw.toast, // sw.on/emit, sw.api, sw.user/isAdmin, sw.theme, sw.pipe // // Dependencies (loaded by base.html): // API, UI, App, sw, esc (ui-primitives) // ========================================== (function () { 'use strict'; const SURFACE_ID = 'dashboard'; if (window.__SURFACE__ !== SURFACE_ID) return; const base = window.__BASE__ || ''; // ── Init ───────────────────────────────────── async function _init() { const mount = document.getElementById('extension-mount'); if (!mount) return; const surface = document.createElement('div'); surface.className = 'surface-dashboard'; surface.id = 'dashboardSurface'; mount.appendChild(surface); // ── Topbar ── const topbar = _buildTopbar(); surface.appendChild(topbar); // sw.userMenu — flyout: 'down' from topbar sw.userMenu(topbar, { flyout: 'down' }); // ── Body ── const body = document.createElement('div'); body.className = 'dashboard-body'; surface.appendChild(body); // ── Sidebar (tabs: activity + notes) ── const sidebar = document.createElement('div'); sidebar.className = 'dashboard-sidebar'; body.appendChild(sidebar); // sw.tabs — two tabs const tabs = sw.tabs(sidebar, { tabs: [ { id: 'activity', label: 'Activity' }, { id: 'notes', label: 'Notes' }, ], activeTab: 'activity', onActivate: function (id) { if (id === 'notes' && !_notesLoaded) { _notesLoaded = true; if (_notePanel) { _notePanel.loadNotesList(); _notePanel.loadNoteFolders(); } } }, }); // sw.chat — standalone in activity tab const activityPanel = tabs.getPanel('activity'); if (activityPanel) { sw.chat(activityPanel, { id: 'dash', standalone: true }); } // sw.notes — lazy-loaded in notes tab let _notePanel = null; let _notesLoaded = false; const notesPanel = tabs.getPanel('notes'); if (notesPanel) { _notePanel = sw.notes(notesPanel, { projectId: null }); } // ── Main content area ── const main = document.createElement('div'); main.className = 'dashboard-main'; body.appendChild(main); _buildMainContent(main); // ── Theme reactivity ── sw.theme.on('change', function (resolved) { const cards = main.querySelectorAll('.dashboard-card'); cards.forEach(function (c) { c.style.borderColor = ''; // reset to CSS default for new theme }); }); // ── Cross-component events ── sw.on('dashboard.filter.changed', function (payload) { _loadChannels(main.querySelector('.dashboard-cards'), payload.value); }); console.log('[DashboardPkg] Mounted'); } // ── Topbar ────────────────────────────────── function _buildTopbar() { const el = document.createElement('div'); el.className = 'dashboard-topbar'; el.innerHTML = '' + '' + 'Back' + '' + '
' + 'Dashboard' + '
'; // sw.toolbar — action buttons const toolbarItems = [ { id: 'refresh', icon: '', title: 'Refresh', onClick: function () { const cards = document.querySelector('.dashboard-cards'); if (cards) _loadChannels(cards, _currentFilter); sw.toast('Refreshed', 'success'); }, }, ]; // sw.isAdmin — conditionally show admin action if (sw.isAdmin) { toolbarItems.push({ id: 'admin', icon: '', title: 'Admin Panel', onClick: function () { window.location.href = base + '/admin'; }, }); } sw.toolbar(el, { items: toolbarItems }); return el; } // ── Main Content ──────────────────────────── let _currentFilter = ''; function _buildMainContent(main) { // sw.user — greeting const user = sw.user; const greeting = document.createElement('div'); greeting.className = 'dashboard-greeting'; greeting.textContent = 'Welcome back' + (user ? ', ' + (user.display_name || user.username) : ''); main.appendChild(greeting); const subtitle = document.createElement('div'); subtitle.className = 'dashboard-subtitle'; subtitle.textContent = 'Your recent conversations' + (sw.isAdmin ? ' \u00b7 Admin' : ''); main.appendChild(subtitle); // ── Filter bar ── const filterBar = document.createElement('div'); filterBar.className = 'dashboard-filter-bar'; main.appendChild(filterBar); const filterLabel = document.createElement('span'); filterLabel.className = 'dashboard-filter-label'; filterLabel.textContent = 'Filter'; filterBar.appendChild(filterLabel); // sw.dropdown — channel type filter sw.dropdown(filterBar, { items: [ { value: '', label: 'All channels' }, { value: 'channel', label: 'Team channels' }, { value: 'direct', label: 'Direct chats' }, { value: 'workflow', label: 'Workflows' }, ], value: '', onChange: function (value) { _currentFilter = value; // sw.emit — cross-component event sw.emit('dashboard.filter.changed', { value: value }); }, }); // ── Cards ── const cards = document.createElement('div'); cards.className = 'dashboard-cards'; main.appendChild(cards); _loadChannels(cards, ''); // ── Admin section (sw.isAdmin) ── if (sw.isAdmin) { const adminSection = document.createElement('div'); adminSection.className = 'dashboard-admin-section'; main.appendChild(adminSection); const sectionTitle = document.createElement('div'); sectionTitle.className = 'dashboard-section-title'; sectionTitle.textContent = 'Administration'; adminSection.appendChild(sectionTitle); const adminCards = document.createElement('div'); adminCards.className = 'dashboard-cards'; adminSection.appendChild(adminCards); _loadAdminCards(adminCards); } } // ── Channel Cards ─────────────────────────── async function _loadChannels(container, typeFilter) { if (!container) return; container.innerHTML = '
Loading\u2026
'; try { // sw.api — real API call let url = '/api/v1/channels?page=1&per_page=12'; if (typeFilter) url += '&type=' + encodeURIComponent(typeFilter); const resp = await sw.api.get(url); const channels = resp || []; container.innerHTML = ''; if (!channels.length) { container.innerHTML = '
No channels found
'; return; } channels.forEach(function (ch) { container.appendChild(_buildChannelCard(ch)); }); } catch (e) { container.innerHTML = '
Failed to load: ' + (typeof esc === 'function' ? esc(e.message) : e.message) + '
'; } } function _buildChannelCard(ch) { const card = document.createElement('div'); card.className = 'dashboard-card'; const header = document.createElement('div'); header.className = 'dashboard-card-header'; card.appendChild(header); const title = document.createElement('div'); title.className = 'dashboard-card-title'; title.textContent = ch.title || ch.name || 'Untitled'; header.appendChild(title); const meta = document.createElement('div'); meta.className = 'dashboard-card-meta'; meta.textContent = ch.type || ''; card.appendChild(meta); if (ch.description) { const desc = document.createElement('div'); desc.className = 'dashboard-card-desc'; desc.textContent = ch.description.slice(0, 120); card.appendChild(desc); } const updated = ch.updated_at || ch.created_at; if (updated) { const date = document.createElement('div'); date.className = 'dashboard-card-meta'; date.textContent = new Date(updated).toLocaleDateString(); card.appendChild(date); } // ── Card action menu ── const actions = document.createElement('div'); actions.className = 'dashboard-card-actions'; card.appendChild(actions); const menuBtn = document.createElement('button'); menuBtn.className = 'icon-btn'; menuBtn.title = 'Actions'; menuBtn.innerHTML = ''; actions.appendChild(menuBtn); // sw.menu — per-card action flyout sw.menu(menuBtn, { items: [ { label: 'Open', icon: '', onClick: function () { window.location.href = base + '/chat/' + ch.id; }, }, { label: 'Details', icon: '', onClick: function () { // sw.modal — show channel details _showDetailModal(ch); }, }, { divider: true }, { label: 'Delete', danger: true, icon: '', onClick: function () { _deleteChannel(ch); }, }, ], position: 'down', }); return card; } // ── Detail Modal ──────────────────────────── function _showDetailModal(ch) { // Build modal content let existing = document.getElementById('dashDetailModal'); if (existing) existing.remove(); const modal = document.createElement('div'); modal.id = 'dashDetailModal'; modal.className = 'modal-overlay'; modal.style.cssText = 'position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:var(--overlay)'; const box = document.createElement('div'); box.style.cssText = 'background:var(--bg-raised);border:1px solid var(--border);border-radius:var(--radius-lg);padding:24px;max-width:400px;width:90%;max-height:80vh;overflow-y:auto;'; box.innerHTML = '

' + (typeof esc === 'function' ? esc(ch.title || 'Untitled') : (ch.title || 'Untitled')) + '

' + '
Type: ' + (typeof esc === 'function' ? esc(ch.type || 'unknown') : (ch.type || 'unknown')) + '
' + '
ID: ' + (typeof esc === 'function' ? esc(ch.id || '') : (ch.id || '')) + '
' + (ch.description ? '
Description: ' + (typeof esc === 'function' ? esc(ch.description) : ch.description) + '
' : '') + (ch.created_at ? '
Created: ' + new Date(ch.created_at).toLocaleString() + '
' : '') + '
' + '' + '' + '
'; modal.appendChild(box); document.body.appendChild(modal); modal.addEventListener('click', function (e) { if (e.target === modal) modal.remove(); }); document.getElementById('dashDetailClose').addEventListener('click', function () { modal.remove(); }); document.getElementById('dashDetailOpen').addEventListener('click', function () { window.location.href = base + '/chat/' + ch.id; }); } // ── Delete Channel ────────────────────────── async function _deleteChannel(ch) { // sw.confirm — destructive action guard const ok = await sw.confirm('Delete "' + (ch.title || 'Untitled') + '"? This cannot be undone.'); if (!ok) return; try { // sw.api.del — real DELETE call await sw.api.del('/api/v1/channels/' + ch.id); // sw.toast — success feedback sw.toast('Channel deleted', 'success'); // Refresh cards const cards = document.querySelector('.dashboard-cards'); if (cards) _loadChannels(cards, _currentFilter); } catch (e) { // sw.toast — error feedback sw.toast('Delete failed: ' + e.message, 'error'); } } // ── Admin Cards ───────────────────────────── async function _loadAdminCards(container) { try { const resp = await sw.api.get('/api/v1/admin/packages'); const packages = resp || []; container.innerHTML = ''; packages.forEach(function (pkg) { const card = document.createElement('div'); card.className = 'dashboard-card'; card.innerHTML = '
' + '
' + (typeof esc === 'function' ? esc(pkg.title || pkg.id) : (pkg.title || pkg.id)) + '
' + '
' + '
' + (typeof esc === 'function' ? esc(pkg.type || '') : (pkg.type || '')) + ' \u00b7 ' + (typeof esc === 'function' ? esc(pkg.tier || '') : (pkg.tier || '')) + '
' + '
' + (typeof esc === 'function' ? esc(pkg.description || '') : (pkg.description || '')) + '
'; container.appendChild(card); }); if (!packages.length) { container.innerHTML = '
No packages installed
'; } } catch (_) { container.innerHTML = '
Failed to load packages
'; } } // ── Boot ───────────────────────────────────── document.addEventListener('DOMContentLoaded', _init); })();