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
267 lines
9.6 KiB
JavaScript
267 lines
9.6 KiB
JavaScript
/**
|
|
* ds-figma-plugin.js
|
|
* Interface for Figma plugin export and token management
|
|
* UX Team Tool #1
|
|
*/
|
|
|
|
import { createFormView, setupFormHandlers } from '../../utils/tool-templates.js';
|
|
import { ComponentHelpers } from '../../utils/component-helpers.js';
|
|
import contextStore from '../../stores/context-store.js';
|
|
import toolBridge from '../../services/tool-bridge.js';
|
|
|
|
class DSFigmaPlugin extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.exportHistory = [];
|
|
}
|
|
|
|
async connectedCallback() {
|
|
this.render();
|
|
this.setupEventListeners();
|
|
await this.loadExportHistory();
|
|
}
|
|
|
|
async loadExportHistory() {
|
|
try {
|
|
const context = contextStore.getMCPContext();
|
|
if (!context.project_id) return;
|
|
|
|
const cached = localStorage.getItem(`figma_exports_${context.project_id}`);
|
|
if (cached) {
|
|
this.exportHistory = JSON.parse(cached);
|
|
this.renderHistory();
|
|
}
|
|
} catch (error) {
|
|
console.error('[DSFigmaPlugin] Failed to load history:', error);
|
|
}
|
|
}
|
|
|
|
setupEventListeners() {
|
|
const exportBtn = this.querySelector('#export-figma-btn');
|
|
const fileKeyInput = this.querySelector('#figma-file-key');
|
|
const exportTypeSelect = this.querySelector('#export-type-select');
|
|
|
|
if (exportBtn) {
|
|
exportBtn.addEventListener('click', () => this.exportFromFigma());
|
|
}
|
|
}
|
|
|
|
async exportFromFigma() {
|
|
const fileKeyInput = this.querySelector('#figma-file-key');
|
|
const exportTypeSelect = this.querySelector('#export-type-select');
|
|
const formatSelect = this.querySelector('#export-format-select');
|
|
|
|
const fileKey = fileKeyInput?.value.trim() || '';
|
|
const exportType = exportTypeSelect?.value || 'tokens';
|
|
const format = formatSelect?.value || 'json';
|
|
|
|
if (!fileKey) {
|
|
ComponentHelpers.showToast?.('Please enter a Figma file key', 'error');
|
|
return;
|
|
}
|
|
|
|
const exportBtn = this.querySelector('#export-figma-btn');
|
|
if (exportBtn) {
|
|
exportBtn.disabled = true;
|
|
exportBtn.textContent = '⏳ Exporting...';
|
|
}
|
|
|
|
try {
|
|
let result;
|
|
|
|
if (exportType === 'tokens') {
|
|
// Export design tokens
|
|
result = await toolBridge.executeTool('dss_sync_figma', {
|
|
file_key: fileKey
|
|
});
|
|
} else if (exportType === 'assets') {
|
|
// Export assets (icons, images)
|
|
const response = await fetch('/api/figma/export-assets', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
projectId: contextStore.get('projectId'),
|
|
fileKey,
|
|
format
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Asset export failed: ${response.statusText}`);
|
|
}
|
|
|
|
result = await response.json();
|
|
} else if (exportType === 'components') {
|
|
// Export component definitions
|
|
const response = await fetch('/api/figma/export-components', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
projectId: contextStore.get('projectId'),
|
|
fileKey,
|
|
format
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Component export failed: ${response.statusText}`);
|
|
}
|
|
|
|
result = await response.json();
|
|
}
|
|
|
|
// Add to history
|
|
const exportEntry = {
|
|
timestamp: new Date().toISOString(),
|
|
fileKey,
|
|
type: exportType,
|
|
format,
|
|
itemCount: result.count || Object.keys(result.tokens || result.assets || result.components || {}).length
|
|
};
|
|
|
|
this.exportHistory.unshift(exportEntry);
|
|
this.exportHistory = this.exportHistory.slice(0, 10); // Keep last 10
|
|
|
|
// Cache history
|
|
const context = contextStore.getMCPContext();
|
|
if (context.project_id) {
|
|
localStorage.setItem(`figma_exports_${context.project_id}`, JSON.stringify(this.exportHistory));
|
|
}
|
|
|
|
this.renderHistory();
|
|
ComponentHelpers.showToast?.(`Exported ${exportEntry.itemCount} ${exportType}`, 'success');
|
|
} catch (error) {
|
|
console.error('[DSFigmaPlugin] Export failed:', error);
|
|
ComponentHelpers.showToast?.(`Export failed: ${error.message}`, 'error');
|
|
} finally {
|
|
if (exportBtn) {
|
|
exportBtn.disabled = false;
|
|
exportBtn.textContent = '📤 Export from Figma';
|
|
}
|
|
}
|
|
}
|
|
|
|
renderHistory() {
|
|
const historyContainer = this.querySelector('#export-history');
|
|
if (!historyContainer) return;
|
|
|
|
if (this.exportHistory.length === 0) {
|
|
historyContainer.innerHTML = ComponentHelpers.renderEmpty('No export history', '📋');
|
|
return;
|
|
}
|
|
|
|
historyContainer.innerHTML = `
|
|
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
${this.exportHistory.map((entry, idx) => `
|
|
<div style="background: var(--vscode-bg); border: 1px solid var(--vscode-border); border-radius: 2px; padding: 12px;">
|
|
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 6px;">
|
|
<div style="flex: 1;">
|
|
<div style="font-size: 11px; font-weight: 600; margin-bottom: 4px;">
|
|
${ComponentHelpers.escapeHtml(entry.type)} Export
|
|
</div>
|
|
<div style="font-size: 10px; color: var(--vscode-text-dim); font-family: monospace;">
|
|
${ComponentHelpers.escapeHtml(entry.fileKey)}
|
|
</div>
|
|
</div>
|
|
<div style="text-align: right;">
|
|
<div style="font-size: 10px; color: var(--vscode-text-dim);">
|
|
${ComponentHelpers.formatRelativeTime(new Date(entry.timestamp))}
|
|
</div>
|
|
<div style="font-size: 11px; font-weight: 600; margin-top: 2px;">
|
|
${entry.itemCount} items
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div style="display: flex; gap: 6px;">
|
|
<span style="padding: 2px 6px; background: var(--vscode-sidebar); border-radius: 2px; font-size: 9px;">
|
|
${entry.format.toUpperCase()}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
render() {
|
|
this.innerHTML = `
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; height: 100%;">
|
|
<!-- Export Panel -->
|
|
<div style="display: flex; flex-direction: column; height: 100%; border-right: 1px solid var(--vscode-border);">
|
|
<div style="padding: 16px; border-bottom: 1px solid var(--vscode-border); background: var(--vscode-sidebar);">
|
|
<h3 style="font-size: 12px; font-weight: 600; margin-bottom: 4px;">Figma Export</h3>
|
|
<p style="font-size: 10px; color: var(--vscode-text-dim);">
|
|
Export tokens, assets, or components from Figma files
|
|
</p>
|
|
</div>
|
|
|
|
<div style="flex: 1; overflow: auto; padding: 16px;">
|
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
|
<div>
|
|
<label style="display: block; font-size: 11px; font-weight: 600; margin-bottom: 6px;">
|
|
Figma File Key
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="figma-file-key"
|
|
placeholder="abc123def456..."
|
|
class="input"
|
|
style="width: 100%; font-size: 11px; font-family: monospace;"
|
|
/>
|
|
<div style="font-size: 10px; color: var(--vscode-text-dim); margin-top: 4px;">
|
|
Find this in your Figma file URL
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; font-size: 11px; font-weight: 600; margin-bottom: 6px;">
|
|
Export Type
|
|
</label>
|
|
<select id="export-type-select" class="input" style="width: 100%; font-size: 11px;">
|
|
<option value="tokens">Design Tokens</option>
|
|
<option value="assets">Assets (Icons, Images)</option>
|
|
<option value="components">Component Definitions</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; font-size: 11px; font-weight: 600; margin-bottom: 6px;">
|
|
Export Format
|
|
</label>
|
|
<select id="export-format-select" class="input" style="width: 100%; font-size: 11px;">
|
|
<option value="json">JSON</option>
|
|
<option value="css">CSS</option>
|
|
<option value="scss">SCSS</option>
|
|
<option value="js">JavaScript</option>
|
|
</select>
|
|
</div>
|
|
|
|
<button id="export-figma-btn" class="button" style="font-size: 12px; padding: 8px;">
|
|
📤 Export from Figma
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- History Panel -->
|
|
<div style="display: flex; flex-direction: column; height: 100%;">
|
|
<div style="padding: 16px; border-bottom: 1px solid var(--vscode-border); background: var(--vscode-sidebar);">
|
|
<h3 style="font-size: 12px; font-weight: 600; margin-bottom: 4px;">Export History</h3>
|
|
<p style="font-size: 10px; color: var(--vscode-text-dim);">
|
|
Recent Figma exports for this project
|
|
</p>
|
|
</div>
|
|
|
|
<div id="export-history" style="flex: 1; overflow: auto; padding: 16px;">
|
|
${ComponentHelpers.renderLoading('Loading history...')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
customElements.define('ds-figma-plugin', DSFigmaPlugin);
|
|
|
|
export default DSFigmaPlugin;
|