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:
297
admin-ui/js/components/tools/ds-figma-extraction.js
Normal file
297
admin-ui/js/components/tools/ds-figma-extraction.js
Normal file
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* ds-figma-extraction.js
|
||||
* Interface for extracting design tokens from Figma files
|
||||
* UI Team Tool #3
|
||||
*/
|
||||
|
||||
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';
|
||||
import apiClient from '../../services/api-client.js';
|
||||
|
||||
class DSFigmaExtraction extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.figmaFileKey = '';
|
||||
this.figmaToken = '';
|
||||
this.extractionResults = null;
|
||||
this.isExtracting = false;
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
await this.loadProjectConfig();
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
async loadProjectConfig() {
|
||||
try {
|
||||
const context = contextStore.getMCPContext();
|
||||
if (!context.project_id) return;
|
||||
|
||||
const project = await apiClient.getProject(context.project_id);
|
||||
const figmaUrl = project.figma_ui_file || '';
|
||||
|
||||
// Extract file key from Figma URL
|
||||
const match = figmaUrl.match(/file\/([^/]+)/);
|
||||
if (match) {
|
||||
this.figmaFileKey = match[1];
|
||||
}
|
||||
|
||||
// Check for stored Figma token
|
||||
this.figmaToken = localStorage.getItem('figma_token') || '';
|
||||
} catch (error) {
|
||||
console.error('[DSFigmaExtraction] Failed to load project config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
const fileKeyInput = this.querySelector('#figma-file-key');
|
||||
const tokenInput = this.querySelector('#figma-token');
|
||||
const extractBtn = this.querySelector('#extract-btn');
|
||||
const saveTokenCheckbox = this.querySelector('#save-token');
|
||||
|
||||
if (fileKeyInput) {
|
||||
fileKeyInput.value = this.figmaFileKey;
|
||||
}
|
||||
|
||||
if (tokenInput) {
|
||||
tokenInput.value = this.figmaToken;
|
||||
}
|
||||
|
||||
if (extractBtn) {
|
||||
extractBtn.addEventListener('click', () => this.extractTokens());
|
||||
}
|
||||
|
||||
if (saveTokenCheckbox && tokenInput) {
|
||||
tokenInput.addEventListener('change', () => {
|
||||
if (saveTokenCheckbox.checked) {
|
||||
localStorage.setItem('figma_token', tokenInput.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async extractTokens() {
|
||||
const fileKeyInput = this.querySelector('#figma-file-key');
|
||||
const tokenInput = this.querySelector('#figma-token');
|
||||
|
||||
this.figmaFileKey = fileKeyInput?.value.trim() || '';
|
||||
this.figmaToken = tokenInput?.value.trim() || '';
|
||||
|
||||
if (!this.figmaFileKey) {
|
||||
ComponentHelpers.showToast?.('Please enter a Figma file key', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.figmaToken) {
|
||||
ComponentHelpers.showToast?.('Please enter a Figma API token', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isExtracting = true;
|
||||
this.updateLoadingState();
|
||||
|
||||
try {
|
||||
// Set Figma token as environment variable for MCP tool
|
||||
// In real implementation, this would be securely stored
|
||||
process.env.FIGMA_TOKEN = this.figmaToken;
|
||||
|
||||
// Call dss_sync_figma MCP tool
|
||||
const result = await toolBridge.executeTool('dss_sync_figma', {
|
||||
file_key: this.figmaFileKey
|
||||
});
|
||||
|
||||
this.extractionResults = result;
|
||||
this.renderResults();
|
||||
|
||||
ComponentHelpers.showToast?.('Tokens extracted successfully', 'success');
|
||||
} catch (error) {
|
||||
console.error('[DSFigmaExtraction] Extraction failed:', error);
|
||||
ComponentHelpers.showToast?.(`Extraction failed: ${error.message}`, 'error');
|
||||
|
||||
const resultsContainer = this.querySelector('#results-container');
|
||||
if (resultsContainer) {
|
||||
resultsContainer.innerHTML = ComponentHelpers.renderError('Token extraction failed', error);
|
||||
}
|
||||
} finally {
|
||||
this.isExtracting = false;
|
||||
this.updateLoadingState();
|
||||
}
|
||||
}
|
||||
|
||||
updateLoadingState() {
|
||||
const extractBtn = this.querySelector('#extract-btn');
|
||||
if (!extractBtn) return;
|
||||
|
||||
if (this.isExtracting) {
|
||||
extractBtn.disabled = true;
|
||||
extractBtn.textContent = '⏳ Extracting...';
|
||||
} else {
|
||||
extractBtn.disabled = false;
|
||||
extractBtn.textContent = '🎨 Extract Tokens';
|
||||
}
|
||||
}
|
||||
|
||||
renderResults() {
|
||||
const resultsContainer = this.querySelector('#results-container');
|
||||
if (!resultsContainer || !this.extractionResults) return;
|
||||
|
||||
const tokenCount = Object.keys(this.extractionResults.tokens || {}).length;
|
||||
|
||||
resultsContainer.innerHTML = `
|
||||
<div style="padding: 16px;">
|
||||
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px; margin-bottom: 16px;">
|
||||
<h4 style="font-size: 12px; font-weight: 600; margin-bottom: 12px;">Extraction Summary</h4>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 16px; font-size: 11px;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 24px; font-weight: 600; color: var(--vscode-text);">${tokenCount}</div>
|
||||
<div style="color: var(--vscode-text-dim); margin-top: 4px;">Tokens Found</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 24px; font-weight: 600; color: #89d185;">✓</div>
|
||||
<div style="color: var(--vscode-text-dim); margin-top: 4px;">Success</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 12px;">
|
||||
<button id="export-json-btn" class="button" style="font-size: 11px;">
|
||||
📥 Export JSON
|
||||
</button>
|
||||
<button id="export-css-btn" class="button" style="font-size: 11px;">
|
||||
📥 Export CSS
|
||||
</button>
|
||||
<button id="view-tokens-btn" class="button" style="font-size: 11px;">
|
||||
👁️ View Tokens
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Setup export handlers
|
||||
const exportJsonBtn = resultsContainer.querySelector('#export-json-btn');
|
||||
const exportCssBtn = resultsContainer.querySelector('#export-css-btn');
|
||||
const viewTokensBtn = resultsContainer.querySelector('#view-tokens-btn');
|
||||
|
||||
if (exportJsonBtn) {
|
||||
exportJsonBtn.addEventListener('click', () => this.exportTokens('json'));
|
||||
}
|
||||
|
||||
if (exportCssBtn) {
|
||||
exportCssBtn.addEventListener('click', () => this.exportTokens('css'));
|
||||
}
|
||||
|
||||
if (viewTokensBtn) {
|
||||
viewTokensBtn.addEventListener('click', () => {
|
||||
// Switch to Token Inspector panel
|
||||
const panel = document.querySelector('ds-panel');
|
||||
if (panel) {
|
||||
panel.switchTab('tokens');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exportTokens(format) {
|
||||
if (!this.extractionResults) return;
|
||||
|
||||
const filename = `figma-tokens-${this.figmaFileKey}.${format}`;
|
||||
let content = '';
|
||||
|
||||
if (format === 'json') {
|
||||
content = JSON.stringify(this.extractionResults.tokens, null, 2);
|
||||
} else if (format === 'css') {
|
||||
// Convert tokens to CSS custom properties
|
||||
const tokens = this.extractionResults.tokens;
|
||||
content = ':root {\n';
|
||||
for (const [key, value] of Object.entries(tokens)) {
|
||||
content += ` --${key}: ${value};\n`;
|
||||
}
|
||||
content += '}\n';
|
||||
}
|
||||
|
||||
// Create download
|
||||
const blob = new Blob([content], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
ComponentHelpers.showToast?.(`Exported as ${filename}`, 'success');
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%;">
|
||||
<!-- Configuration Panel -->
|
||||
<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: 12px;">Figma Token Extraction</h3>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 2fr 3fr auto; gap: 12px; align-items: end;">
|
||||
<div>
|
||||
<label style="display: block; font-size: 11px; font-weight: 600; margin-bottom: 4px; color: var(--vscode-text-dim);">
|
||||
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>
|
||||
|
||||
<div>
|
||||
<label style="display: block; font-size: 11px; font-weight: 600; margin-bottom: 4px; color: var(--vscode-text-dim);">
|
||||
Figma API Token
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="figma-token"
|
||||
placeholder="figd_..."
|
||||
class="input"
|
||||
style="width: 100%; font-size: 11px; font-family: monospace;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button id="extract-btn" class="button" style="font-size: 11px; padding: 6px 16px;">
|
||||
🎨 Extract Tokens
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 8px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<label style="font-size: 10px; color: var(--vscode-text-dim); display: flex; align-items: center; gap: 6px;">
|
||||
<input type="checkbox" id="save-token" />
|
||||
Remember Figma token (stored locally)
|
||||
</label>
|
||||
<a href="https://www.figma.com/developers/api#authentication" target="_blank" style="font-size: 10px; color: var(--vscode-link);">
|
||||
Get API Token →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Container -->
|
||||
<div id="results-container" style="flex: 1; overflow: auto;">
|
||||
<div style="display: flex; align-items: center; justify-content: center; height: 100%; text-align: center; padding: 48px;">
|
||||
<div>
|
||||
<div style="font-size: 48px; margin-bottom: 16px;">🎨</div>
|
||||
<h3 style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">Ready to Extract Tokens</h3>
|
||||
<p style="font-size: 12px; color: var(--vscode-text-dim);">
|
||||
Enter your Figma file key and API token above to extract design tokens
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-figma-extraction', DSFigmaExtraction);
|
||||
|
||||
export default DSFigmaExtraction;
|
||||
Reference in New Issue
Block a user