Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
133
admin-ui/js/workdesks/admin-workdesk.js
Normal file
133
admin-ui/js/workdesks/admin-workdesk.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* admin-workdesk.js
|
||||
* Admin Team workdesk - System settings and project management
|
||||
*/
|
||||
|
||||
import BaseWorkdesk from './base-workdesk.js';
|
||||
import '../components/admin/ds-admin-settings.js';
|
||||
import '../components/admin/ds-project-list.js';
|
||||
|
||||
export default class AdminWorkdesk extends BaseWorkdesk {
|
||||
constructor(shell) {
|
||||
super(shell);
|
||||
this.teamId = 'admin';
|
||||
this.teamName = 'Admin';
|
||||
this.tools = [
|
||||
{
|
||||
id: 'settings',
|
||||
name: 'System Settings',
|
||||
description: 'Configure DSS hostname, port, and setup type',
|
||||
component: 'ds-admin-settings'
|
||||
},
|
||||
{
|
||||
id: 'projects',
|
||||
name: 'Projects',
|
||||
description: 'Create and manage design system projects',
|
||||
component: 'ds-project-list'
|
||||
}
|
||||
];
|
||||
this.currentTab = 'settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin sidebar shows tab navigation for Settings and Projects
|
||||
*/
|
||||
renderSidebar() {
|
||||
const sidebar = this.shell.sidebarContent;
|
||||
if (!sidebar) return;
|
||||
|
||||
sidebar.innerHTML = `
|
||||
<div style="padding: 12px; display: flex; flex-direction: column; gap: 8px;">
|
||||
${this.tools.map(tool => `
|
||||
<button class="tab-btn" data-tab="${tool.id}" style="
|
||||
padding: 8px 12px;
|
||||
border: 1px solid ${this.currentTab === tool.id ? 'var(--vscode-focusBorder)' : 'var(--vscode-border)'};
|
||||
background: ${this.currentTab === tool.id ? 'var(--vscode-selection)' : 'var(--vscode-sidebar)'};
|
||||
color: var(--vscode-foreground);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: ${this.currentTab === tool.id ? '600' : '500'};
|
||||
text-align: left;
|
||||
transition: all 0.2s;
|
||||
" title="${tool.description}">
|
||||
${tool.name}
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Setup tab click handlers
|
||||
sidebar.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const tabId = btn.dataset.tab;
|
||||
this.switchTab(tabId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin stage shows tab content - either Settings or Projects
|
||||
*/
|
||||
renderStage() {
|
||||
const stage = this.shell.stageContent;
|
||||
if (!stage) return;
|
||||
|
||||
stage.innerHTML = `
|
||||
<div style="height: 100%; overflow-y: auto;">
|
||||
${this.renderTabContent()}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Trigger connectedCallback on custom elements
|
||||
this.hydrateComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render content based on current tab
|
||||
*/
|
||||
renderTabContent() {
|
||||
switch (this.currentTab) {
|
||||
case 'settings':
|
||||
return '<ds-admin-settings></ds-admin-settings>';
|
||||
case 'projects':
|
||||
return '<ds-project-list></ds-project-list>';
|
||||
default:
|
||||
return '<ds-admin-settings></ds-admin-settings>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a different tab and re-render
|
||||
*/
|
||||
switchTab(tabId) {
|
||||
this.currentTab = tabId;
|
||||
this.renderSidebar();
|
||||
this.renderStage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate custom elements after rendering
|
||||
*/
|
||||
hydrateComponents() {
|
||||
const stage = this.shell.stageContent;
|
||||
if (!stage) return;
|
||||
|
||||
// Find and trigger connectedCallback on custom elements
|
||||
stage.querySelectorAll('ds-admin-settings, ds-project-list').forEach(el => {
|
||||
// Web components auto-trigger connectedCallback when inserted into DOM
|
||||
// No manual trigger needed
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin panel shows minimal system log
|
||||
*/
|
||||
renderPanel() {
|
||||
const panel = this.shell.querySelector('ds-panel');
|
||||
if (panel) {
|
||||
// Configure panel with admin config
|
||||
panel.configure('admin', false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
210
admin-ui/js/workdesks/base-workdesk.js
Normal file
210
admin-ui/js/workdesks/base-workdesk.js
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* base-workdesk.js
|
||||
* Abstract base class for all team workdesks
|
||||
* Refactored: A11y improvements, extracted styles, event delegation
|
||||
*/
|
||||
|
||||
import '../components/tools/ds-metrics-panel.js';
|
||||
|
||||
export default class BaseWorkdesk {
|
||||
constructor(shell) {
|
||||
if (new.target === BaseWorkdesk) {
|
||||
throw new Error('Cannot instantiate abstract class BaseWorkdesk');
|
||||
}
|
||||
|
||||
this.shell = shell;
|
||||
this.teamId = '';
|
||||
this.teamName = '';
|
||||
this.tools = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the workdesk - must be implemented by subclasses
|
||||
*/
|
||||
render() {
|
||||
this.renderSidebar();
|
||||
this.renderStage();
|
||||
this.renderPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render sidebar content
|
||||
* Improved: Uses semantic <button> elements, injected styles, and keyboard support
|
||||
*/
|
||||
renderSidebar() {
|
||||
const sidebar = this.shell.sidebarContent;
|
||||
if (!sidebar) return;
|
||||
|
||||
// We define styles here to keep BaseWorkdesk self-contained without external CSS dependencies
|
||||
const css = `
|
||||
.workdesk-sidebar-container {
|
||||
padding: 16px;
|
||||
}
|
||||
.workdesk-header {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
margin-bottom: 12px;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tools-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.tool-btn {
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: var(--vscode-foreground);
|
||||
transition: background-color 0.1s, border-color 0.1s;
|
||||
}
|
||||
.tool-btn:hover {
|
||||
background-color: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
.tool-btn:focus-visible {
|
||||
outline: 2px solid var(--vscode-focusBorder);
|
||||
outline-offset: -2px;
|
||||
background-color: var(--vscode-list-activeSelectionBackground);
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
}
|
||||
.tool-name {
|
||||
font-size: 13px;
|
||||
margin-bottom: 2px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tool-desc {
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
/* A11y focus support for description inside button */
|
||||
.tool-btn:focus-visible .tool-desc {
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
opacity: 0.9;
|
||||
}
|
||||
`;
|
||||
|
||||
sidebar.innerHTML = `
|
||||
<style>${css}</style>
|
||||
<div class="workdesk-sidebar-container">
|
||||
<div class="workdesk-header" id="tools-heading">
|
||||
${this.teamName} Tools
|
||||
</div>
|
||||
<div class="tools-list" role="group" aria-labelledby="tools-heading">
|
||||
${this.tools.map(tool => `
|
||||
<button class="tool-btn" data-tool="${tool.id}" type="button">
|
||||
<div class="tool-name">${tool.name}</div>
|
||||
<div class="tool-desc">${tool.description}</div>
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Event delegation for better performance and lifecycle management
|
||||
const toolsList = sidebar.querySelector('.tools-list');
|
||||
if (toolsList) {
|
||||
toolsList.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.tool-btn');
|
||||
if (btn) {
|
||||
this.onToolClick(btn.dataset.tool);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render stage content - must be implemented by subclasses
|
||||
*/
|
||||
renderStage() {
|
||||
throw new Error('renderStage() must be implemented by subclass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render panel content - DEPRECATED
|
||||
* Panel is now configured by ds-shell.js via panel.configure() during team switch
|
||||
* Subclasses should not override this method
|
||||
*/
|
||||
renderPanel() {
|
||||
// Panel configuration is now handled by ds-shell.js
|
||||
// This method is kept for backwards compatibility but does nothing
|
||||
console.log(`[${this.teamId}] Panel configured by shell via panel-config.js`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tool click - can be overridden by subclasses
|
||||
*/
|
||||
onToolClick(toolId) {
|
||||
console.log(`Tool clicked: ${toolId}`);
|
||||
const tool = this.tools.find(t => t.id === toolId);
|
||||
if (tool) {
|
||||
this.loadTool(tool);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a tool into the stage - can be overridden by subclasses
|
||||
* Improved: Loading spinner using CSS instead of Emoji
|
||||
*/
|
||||
loadTool(tool) {
|
||||
const stage = this.shell.stageContent;
|
||||
if (!stage) return;
|
||||
|
||||
// Use a clean CSS spinner with codicon if available
|
||||
stage.innerHTML = `
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; padding: 48px;">
|
||||
<div class="codicon codicon-loading codicon-modifier-spin" style="font-size: 24px; margin-bottom: 12px;"></div>
|
||||
<div style="font-size: 12px; color: var(--vscode-descriptionForeground);">Loading ${tool.name}...</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up workdesk - can be overridden by subclasses
|
||||
*/
|
||||
destroy() {
|
||||
if (this.shell.sidebarContent) {
|
||||
this.shell.sidebarContent.innerHTML = '';
|
||||
}
|
||||
if (this.shell.stageContent) {
|
||||
this.shell.stageContent.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility: Create a card element
|
||||
* NOTE: Deprecated - use ds-metric-card web component instead
|
||||
*/
|
||||
createCard(title, content) {
|
||||
return `
|
||||
<div style="
|
||||
background-color: var(--vscode-sidebar);
|
||||
border: 1px solid var(--vscode-border);
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
">
|
||||
<h3 style="margin-bottom: 12px; font-size: 14px;">${title}</h3>
|
||||
<div style="font-size: 12px; color: var(--vscode-text-dim);">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility: Create a button
|
||||
*/
|
||||
createButton(label, onClick) {
|
||||
const button = document.createElement('button');
|
||||
button.className = 'button';
|
||||
button.textContent = label;
|
||||
button.addEventListener('click', onClick);
|
||||
return button;
|
||||
}
|
||||
}
|
||||
175
admin-ui/js/workdesks/qa-workdesk.js
Normal file
175
admin-ui/js/workdesks/qa-workdesk.js
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* qa-workdesk.js
|
||||
* QA Team workdesk - Console logs, network monitoring, DOM inspection
|
||||
* MVP2: Added metrics frontpage as default view
|
||||
*/
|
||||
|
||||
import BaseWorkdesk from './base-workdesk.js';
|
||||
import { hydrateComponent } from '../config/component-registry.js';
|
||||
import '../components/tools/ds-console-viewer.js';
|
||||
|
||||
export default class QAWorkdesk extends BaseWorkdesk {
|
||||
constructor(shell) {
|
||||
super(shell);
|
||||
this.teamId = 'qa';
|
||||
this.teamName = 'QA Team';
|
||||
this.tools = [
|
||||
{
|
||||
id: 'frontpage',
|
||||
name: 'Dashboard',
|
||||
description: 'Team metrics and quick actions',
|
||||
component: 'ds-frontpage'
|
||||
},
|
||||
{
|
||||
id: 'figma-live-compare',
|
||||
name: 'Figma vs Live',
|
||||
description: 'QA validation: Figma design vs live implementation',
|
||||
component: 'ds-figma-live-compare'
|
||||
},
|
||||
{
|
||||
id: 'esre-editor',
|
||||
name: 'ESRE Editor',
|
||||
description: 'Edit Explicit Style Requirements and Expectations',
|
||||
component: 'ds-esre-editor'
|
||||
},
|
||||
{
|
||||
id: 'console-viewer',
|
||||
name: 'Console Viewer',
|
||||
description: 'Monitor browser console logs',
|
||||
mcpTool: 'browser_get_logs'
|
||||
},
|
||||
{
|
||||
id: 'network-monitor',
|
||||
name: 'Network Monitor',
|
||||
description: 'Track network requests',
|
||||
mcpTool: 'devtools_network_requests'
|
||||
},
|
||||
{
|
||||
id: 'error-tracker',
|
||||
name: 'Error Tracker',
|
||||
description: 'Track uncaught exceptions',
|
||||
mcpTool: 'browser_get_errors'
|
||||
}
|
||||
];
|
||||
this.currentTool = 'frontpage';
|
||||
}
|
||||
|
||||
async loadTool(tool) {
|
||||
const stage = this.shell.stageContent;
|
||||
if (!stage) return;
|
||||
|
||||
// For tools with components, load them dynamically
|
||||
if (tool.component) {
|
||||
// Show loading state
|
||||
stage.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: center; height: 100%; padding: 48px;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 24px; margin-bottom: 12px;">⏳</div>
|
||||
<div style="font-size: 12px; color: var(--vscode-text-dim);">Loading ${tool.name}...</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
// Clear the stage and hydrate the component
|
||||
stage.innerHTML = '';
|
||||
await hydrateComponent(tool.component, stage);
|
||||
console.log(`[QAWorkdesk] Loaded component: ${tool.component}`);
|
||||
} catch (error) {
|
||||
console.error(`[QAWorkdesk] Failed to load tool ${tool.component}:`, error);
|
||||
stage.innerHTML = `
|
||||
<div style="padding: 24px;">
|
||||
<h2 style="margin-bottom: 16px; color: var(--vscode-error);">Error Loading Tool</h2>
|
||||
<p style="color: var(--vscode-text-dim);">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
// Fall back to base implementation for MCP tools
|
||||
super.loadTool(tool);
|
||||
}
|
||||
}
|
||||
|
||||
renderStage() {
|
||||
const stage = this.shell.stageContent;
|
||||
if (!stage) return;
|
||||
|
||||
stage.innerHTML = `
|
||||
<div style="padding: 24px;">
|
||||
<h1 style="margin-bottom: 8px; font-size: 24px;">QA Team Workdesk</h1>
|
||||
<p style="color: var(--vscode-text-dim); margin-bottom: 32px;">
|
||||
QA validation, testing, and debugging tools
|
||||
</p>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px;">
|
||||
${this.createCard('QA Validation', `
|
||||
<p>Compare and validate implementations:</p>
|
||||
<ul style="margin-top: 8px; padding-left: 20px;">
|
||||
<li>Figma vs Live comparison</li>
|
||||
<li>Screenshot capture</li>
|
||||
<li>Visual validation</li>
|
||||
<li>Style requirements (ESRE)</li>
|
||||
</ul>
|
||||
`)}
|
||||
|
||||
${this.createCard('Console Monitoring', `
|
||||
<p>Real-time console log streaming:</p>
|
||||
<ul style="margin-top: 8px; padding-left: 20px;">
|
||||
<li>Log, warn, error levels</li>
|
||||
<li>Stack trace analysis</li>
|
||||
<li>Filter by severity</li>
|
||||
<li>Export logs</li>
|
||||
</ul>
|
||||
`)}
|
||||
|
||||
${this.createCard('Network Debugging', `
|
||||
<p>Track all HTTP requests:</p>
|
||||
<ul style="margin-top: 8px; padding-left: 20px;">
|
||||
<li>Request/response headers</li>
|
||||
<li>Payload inspection</li>
|
||||
<li>Timing analysis</li>
|
||||
<li>Failed requests</li>
|
||||
</ul>
|
||||
`)}
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 32px;">
|
||||
<h2 style="margin-bottom: 16px; font-size: 16px;">Quick Actions</h2>
|
||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<button class="button" id="compare-btn">Figma vs Live</button>
|
||||
<button class="button" id="esre-btn">Edit ESRE</button>
|
||||
<button class="button" id="console-btn">Open Console</button>
|
||||
<button class="button" id="network-btn">Network Monitor</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Setup button handlers
|
||||
stage.querySelector('#compare-btn')?.addEventListener('click', () => {
|
||||
this.onToolClick('figma-live-compare');
|
||||
});
|
||||
|
||||
stage.querySelector('#esre-btn')?.addEventListener('click', () => {
|
||||
this.onToolClick('esre-editor');
|
||||
});
|
||||
|
||||
stage.querySelector('#console-btn')?.addEventListener('click', () => {
|
||||
this.onToolClick('console-viewer');
|
||||
// Switch panel to console tab (component is already configured via panel-config.js)
|
||||
const panel = this.shell.querySelector('ds-panel');
|
||||
if (panel) {
|
||||
panel.switchTab('console');
|
||||
}
|
||||
});
|
||||
|
||||
stage.querySelector('#network-btn')?.addEventListener('click', () => {
|
||||
this.onToolClick('network-monitor');
|
||||
// Switch panel to network tab (component is already configured via panel-config.js)
|
||||
const panel = this.shell.querySelector('ds-panel');
|
||||
if (panel) {
|
||||
panel.switchTab('network');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
115
admin-ui/js/workdesks/ui-workdesk.js
Normal file
115
admin-ui/js/workdesks/ui-workdesk.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* ui-workdesk.js
|
||||
* UI Team workdesk - Token management, Figma sync, code generation
|
||||
* Refactored: Simplified renderStage to act as controller delegation
|
||||
*/
|
||||
|
||||
import BaseWorkdesk from './base-workdesk.js';
|
||||
import { hydrateComponent } from '../config/component-registry.js';
|
||||
|
||||
export default class UIWorkdesk extends BaseWorkdesk {
|
||||
constructor(shell) {
|
||||
super(shell);
|
||||
this.teamId = 'ui';
|
||||
this.teamName = 'UI Team';
|
||||
this.tools = [
|
||||
{
|
||||
id: 'frontpage',
|
||||
name: 'Dashboard',
|
||||
description: 'Team metrics and quick actions',
|
||||
component: 'ds-frontpage'
|
||||
},
|
||||
{
|
||||
id: 'storybook-figma-compare',
|
||||
name: 'Storybook vs Figma',
|
||||
description: 'Compare Storybook and Figma side by side',
|
||||
component: 'ds-storybook-figma-compare'
|
||||
},
|
||||
{
|
||||
id: 'storybook-live-compare',
|
||||
name: 'Storybook vs Live',
|
||||
description: 'Compare Storybook and live app for drift detection',
|
||||
component: 'ds-storybook-live-compare'
|
||||
},
|
||||
{
|
||||
id: 'figma-extraction',
|
||||
name: 'Figma Token Extraction',
|
||||
description: 'Extract design tokens from Figma',
|
||||
component: 'ds-figma-extraction'
|
||||
},
|
||||
{
|
||||
id: 'project-analysis',
|
||||
name: 'Project Analysis',
|
||||
description: 'Analyze design system adoption',
|
||||
component: 'ds-project-analysis'
|
||||
},
|
||||
{
|
||||
id: 'quick-wins',
|
||||
name: 'Quick Wins',
|
||||
description: 'Find low-effort improvements',
|
||||
component: 'ds-quick-wins'
|
||||
},
|
||||
{
|
||||
id: 'regression-testing',
|
||||
name: 'Regression Testing',
|
||||
description: 'Visual regression testing',
|
||||
component: 'ds-regression-testing'
|
||||
}
|
||||
];
|
||||
this.currentTool = 'frontpage';
|
||||
}
|
||||
|
||||
async loadTool(tool) {
|
||||
const stage = this.shell.stageContent;
|
||||
if (!stage) return;
|
||||
|
||||
// Show loading state with codicon spinner
|
||||
stage.innerHTML = `
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%;">
|
||||
<div class="codicon codicon-loading codicon-modifier-spin" style="font-size: 24px; margin-bottom: 12px;"></div>
|
||||
<div style="color: var(--vscode-descriptionForeground);">Loading ${tool.name}...</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
stage.innerHTML = '';
|
||||
await hydrateComponent(tool.component, stage);
|
||||
console.log(`[UIWorkdesk] Loaded component: ${tool.component}`);
|
||||
|
||||
// Update internal state tracking
|
||||
this.currentTool = tool.id;
|
||||
} catch (error) {
|
||||
console.error(`[UIWorkdesk] Failed to load tool ${tool.component}:`, error);
|
||||
stage.innerHTML = `
|
||||
<div style="padding: 24px; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="margin-bottom: 16px; color: var(--vscode-errorForeground);">Error Loading Tool</h2>
|
||||
<p style="color: var(--vscode-descriptionForeground); margin-bottom: 16px;">${error.message}</p>
|
||||
<button id="retry-btn" class="button" type="button"
|
||||
style="padding: 8px 16px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; border-radius: 4px; cursor: pointer;">
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add event listener without inline handler
|
||||
const retryBtn = stage.querySelector('#retry-btn');
|
||||
if (retryBtn) {
|
||||
retryBtn.addEventListener('click', () => location.reload());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render stage content
|
||||
* REFACTORED: Now strictly delegates to the default tool (Dashboard)
|
||||
* instead of maintaining a hardcoded duplicate view.
|
||||
*/
|
||||
renderStage() {
|
||||
const defaultTool = this.tools.find(t => t.id === this.currentTool || t.id === 'frontpage');
|
||||
if (defaultTool) {
|
||||
this.loadTool(defaultTool);
|
||||
} else {
|
||||
console.error('Default tool "frontpage" not found in configuration');
|
||||
}
|
||||
}
|
||||
}
|
||||
158
admin-ui/js/workdesks/ux-workdesk.js
Normal file
158
admin-ui/js/workdesks/ux-workdesk.js
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* ux-workdesk.js
|
||||
* UX Team workdesk - Visual diff, accessibility audits, component gallery
|
||||
* MVP2: Added metrics frontpage as default view
|
||||
*/
|
||||
|
||||
import BaseWorkdesk from './base-workdesk.js';
|
||||
import { hydrateComponent } from '../config/component-registry.js';
|
||||
|
||||
export default class UXWorkdesk extends BaseWorkdesk {
|
||||
constructor(shell) {
|
||||
super(shell);
|
||||
this.teamId = 'ux';
|
||||
this.teamName = 'UX Team';
|
||||
this.tools = [
|
||||
{
|
||||
id: 'frontpage',
|
||||
name: 'Dashboard',
|
||||
description: 'Team metrics and quick actions',
|
||||
component: 'ds-frontpage'
|
||||
},
|
||||
{
|
||||
id: 'figma-plugin',
|
||||
name: 'Figma Plugin',
|
||||
description: 'Export tokens/assets/components from Figma',
|
||||
component: 'ds-figma-plugin'
|
||||
},
|
||||
{
|
||||
id: 'token-list',
|
||||
name: 'Token List',
|
||||
description: 'View all design tokens',
|
||||
component: 'ds-token-list'
|
||||
},
|
||||
{
|
||||
id: 'asset-list',
|
||||
name: 'Asset List',
|
||||
description: 'Gallery of design assets',
|
||||
component: 'ds-asset-list'
|
||||
},
|
||||
{
|
||||
id: 'component-list',
|
||||
name: 'Component List',
|
||||
description: 'Design system components',
|
||||
component: 'ds-component-list'
|
||||
},
|
||||
{
|
||||
id: 'navigation-demos',
|
||||
name: 'Navigation Demos',
|
||||
description: 'Generate navigation flow demos',
|
||||
component: 'ds-navigation-demos'
|
||||
}
|
||||
];
|
||||
this.currentTool = 'frontpage';
|
||||
}
|
||||
|
||||
async loadTool(tool) {
|
||||
const stage = this.shell.stageContent;
|
||||
if (!stage) return;
|
||||
|
||||
// Show loading state
|
||||
stage.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: center; height: 100%; padding: 48px;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 24px; margin-bottom: 12px;">⏳</div>
|
||||
<div style="font-size: 12px; color: var(--vscode-text-dim);">Loading ${tool.name}...</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
// Clear the stage and hydrate the component
|
||||
stage.innerHTML = '';
|
||||
await hydrateComponent(tool.component, stage);
|
||||
console.log(`[UXWorkdesk] Loaded component: ${tool.component}`);
|
||||
} catch (error) {
|
||||
console.error(`[UXWorkdesk] Failed to load tool ${tool.component}:`, error);
|
||||
stage.innerHTML = `
|
||||
<div style="padding: 24px;">
|
||||
<h2 style="margin-bottom: 16px; color: var(--vscode-error);">Error Loading Tool</h2>
|
||||
<p style="color: var(--vscode-text-dim);">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
renderStage() {
|
||||
const stage = this.shell.stageContent;
|
||||
if (!stage) return;
|
||||
|
||||
stage.innerHTML = `
|
||||
<div style="padding: 24px;">
|
||||
<h1 style="margin-bottom: 8px; font-size: 24px;">UX Team Workdesk</h1>
|
||||
<p style="color: var(--vscode-text-dim); margin-bottom: 32px;">
|
||||
Design system assets, tokens, and component management
|
||||
</p>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px;">
|
||||
${this.createCard('Figma Integration', `
|
||||
<p>Export from Figma:</p>
|
||||
<ul style="margin-top: 8px; padding-left: 20px;">
|
||||
<li>Design tokens</li>
|
||||
<li>Asset export</li>
|
||||
<li>Component definitions</li>
|
||||
<li>Multiple format support</li>
|
||||
</ul>
|
||||
`)}
|
||||
|
||||
${this.createCard('Asset Management', `
|
||||
<p>Manage design assets:</p>
|
||||
<ul style="margin-top: 8px; padding-left: 20px;">
|
||||
<li>Token library</li>
|
||||
<li>Asset gallery</li>
|
||||
<li>Component catalog</li>
|
||||
<li>Visual preview</li>
|
||||
</ul>
|
||||
`)}
|
||||
|
||||
${this.createCard('Navigation', `
|
||||
<p>Navigation flow demos:</p>
|
||||
<ul style="margin-top: 8px; padding-left: 20px;">
|
||||
<li>Generate flow demos</li>
|
||||
<li>Interactive HTML</li>
|
||||
<li>Flow library</li>
|
||||
<li>Quick preview</li>
|
||||
</ul>
|
||||
`)}
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 32px;">
|
||||
<h2 style="margin-bottom: 16px; font-size: 16px;">Quick Actions</h2>
|
||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<button class="button" id="figma-btn">Figma Export</button>
|
||||
<button class="button" id="tokens-btn">View Tokens</button>
|
||||
<button class="button" id="assets-btn">Asset Gallery</button>
|
||||
<button class="button" id="components-btn">Components</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Setup button handlers
|
||||
stage.querySelector('#figma-btn')?.addEventListener('click', () => {
|
||||
this.onToolClick('figma-plugin');
|
||||
});
|
||||
|
||||
stage.querySelector('#tokens-btn')?.addEventListener('click', () => {
|
||||
this.onToolClick('token-list');
|
||||
});
|
||||
|
||||
stage.querySelector('#assets-btn')?.addEventListener('click', () => {
|
||||
this.onToolClick('asset-list');
|
||||
});
|
||||
|
||||
stage.querySelector('#components-btn')?.addEventListener('click', () => {
|
||||
this.onToolClick('component-list');
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user