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:
411
admin-ui/js/components/tools/ds-figma-status.js
Normal file
411
admin-ui/js/components/tools/ds-figma-status.js
Normal file
@@ -0,0 +1,411 @@
|
||||
/**
|
||||
* ds-figma-status.js
|
||||
* Figma integration status and sync controls
|
||||
*/
|
||||
|
||||
import toolBridge from '../../services/tool-bridge.js';
|
||||
import { ComponentHelpers } from '../../utils/component-helpers.js';
|
||||
|
||||
class DSFigmaStatus extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.figmaToken = null;
|
||||
this.figmaFileKey = null;
|
||||
this.connectionStatus = 'unknown';
|
||||
this.lastSync = null;
|
||||
this.isConfiguring = false;
|
||||
this.isSyncing = false;
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
await this.checkConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Figma is configured and test connection
|
||||
*/
|
||||
async checkConfiguration() {
|
||||
const statusContent = this.querySelector('#figma-status-content');
|
||||
if (!statusContent) return;
|
||||
|
||||
try {
|
||||
// Check for stored file key in localStorage (not token - that's server-side)
|
||||
this.figmaFileKey = localStorage.getItem('figma_file_key');
|
||||
|
||||
if (!this.figmaFileKey) {
|
||||
this.connectionStatus = 'not_configured';
|
||||
this.renderStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Test connection by calling sync with dry-run check
|
||||
// Note: Backend checks for FIGMA_TOKEN env variable
|
||||
statusContent.innerHTML = ComponentHelpers.renderLoading('Checking Figma connection...');
|
||||
|
||||
try {
|
||||
// Try to get Figma file info (will fail if token not configured)
|
||||
const result = await toolBridge.syncFigma(this.figmaFileKey);
|
||||
|
||||
if (result && result.tokens) {
|
||||
this.connectionStatus = 'connected';
|
||||
this.lastSync = new Date();
|
||||
} else {
|
||||
this.connectionStatus = 'error';
|
||||
}
|
||||
} catch (error) {
|
||||
// Token not configured on backend
|
||||
if (error.message.includes('FIGMA_TOKEN')) {
|
||||
this.connectionStatus = 'token_missing';
|
||||
} else {
|
||||
this.connectionStatus = 'error';
|
||||
}
|
||||
console.error('Figma connection check failed:', error);
|
||||
}
|
||||
|
||||
this.renderStatus();
|
||||
} catch (error) {
|
||||
console.error('Failed to check Figma configuration:', error);
|
||||
statusContent.innerHTML = ComponentHelpers.renderError('Failed to check configuration', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Configure button
|
||||
const configureBtn = this.querySelector('#figma-configure-btn');
|
||||
if (configureBtn) {
|
||||
configureBtn.addEventListener('click', () => this.showConfiguration());
|
||||
}
|
||||
|
||||
// Sync button
|
||||
const syncBtn = this.querySelector('#figma-sync-btn');
|
||||
if (syncBtn) {
|
||||
syncBtn.addEventListener('click', () => this.syncFromFigma());
|
||||
}
|
||||
}
|
||||
|
||||
showConfiguration() {
|
||||
this.isConfiguring = true;
|
||||
this.renderStatus();
|
||||
|
||||
// Setup save handler
|
||||
const saveBtn = this.querySelector('#figma-save-config-btn');
|
||||
const cancelBtn = this.querySelector('#figma-cancel-config-btn');
|
||||
|
||||
if (saveBtn) {
|
||||
saveBtn.addEventListener('click', () => this.saveConfiguration());
|
||||
}
|
||||
|
||||
if (cancelBtn) {
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
this.isConfiguring = false;
|
||||
this.renderStatus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async saveConfiguration() {
|
||||
const fileKeyInput = this.querySelector('#figma-file-key-input');
|
||||
const tokenInput = this.querySelector('#figma-token-input');
|
||||
|
||||
if (!fileKeyInput || !tokenInput) return;
|
||||
|
||||
const fileKey = fileKeyInput.value.trim();
|
||||
const token = tokenInput.value.trim();
|
||||
|
||||
if (!fileKey) {
|
||||
ComponentHelpers.showToast?.('Please enter a Figma file key', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
ComponentHelpers.showToast?.('Please enter a Figma access token', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Store file key in localStorage (client-side)
|
||||
localStorage.setItem('figma_file_key', fileKey);
|
||||
this.figmaFileKey = fileKey;
|
||||
|
||||
// Display warning about backend token configuration
|
||||
ComponentHelpers.showToast?.('File key saved. Please configure FIGMA_TOKEN environment variable on the backend.', 'info');
|
||||
|
||||
this.isConfiguring = false;
|
||||
this.connectionStatus = 'token_missing';
|
||||
this.renderStatus();
|
||||
} catch (error) {
|
||||
console.error('Failed to save Figma configuration:', error);
|
||||
ComponentHelpers.showToast?.(`Failed to save configuration: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async syncFromFigma() {
|
||||
if (this.isSyncing || !this.figmaFileKey) return;
|
||||
|
||||
this.isSyncing = true;
|
||||
const syncBtn = this.querySelector('#figma-sync-btn');
|
||||
|
||||
if (syncBtn) {
|
||||
syncBtn.disabled = true;
|
||||
syncBtn.textContent = '🔄 Syncing...';
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await toolBridge.syncFigma(this.figmaFileKey);
|
||||
|
||||
if (result && result.tokens) {
|
||||
this.lastSync = new Date();
|
||||
this.connectionStatus = 'connected';
|
||||
|
||||
ComponentHelpers.showToast?.(
|
||||
`Synced ${Object.keys(result.tokens).length} tokens from Figma`,
|
||||
'success'
|
||||
);
|
||||
|
||||
this.renderStatus();
|
||||
} else {
|
||||
throw new Error('No tokens returned from Figma');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to sync from Figma:', error);
|
||||
ComponentHelpers.showToast?.(`Sync failed: ${error.message}`, 'error');
|
||||
this.connectionStatus = 'error';
|
||||
this.renderStatus();
|
||||
} finally {
|
||||
this.isSyncing = false;
|
||||
if (syncBtn) {
|
||||
syncBtn.disabled = false;
|
||||
syncBtn.textContent = '🔄 Sync Now';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getStatusBadge() {
|
||||
const badges = {
|
||||
connected: ComponentHelpers.createBadge('Connected', 'success'),
|
||||
not_configured: ComponentHelpers.createBadge('Not Configured', 'info'),
|
||||
token_missing: ComponentHelpers.createBadge('Token Required', 'warning'),
|
||||
error: ComponentHelpers.createBadge('Error', 'error'),
|
||||
unknown: ComponentHelpers.createBadge('Unknown', 'info')
|
||||
};
|
||||
|
||||
return badges[this.connectionStatus] || badges.unknown;
|
||||
}
|
||||
|
||||
renderStatus() {
|
||||
const statusContent = this.querySelector('#figma-status-content');
|
||||
if (!statusContent) return;
|
||||
|
||||
// Configuration form
|
||||
if (this.isConfiguring) {
|
||||
statusContent.innerHTML = `
|
||||
<div style="padding: 16px; background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px;">
|
||||
<h4 style="font-size: 12px; font-weight: 600; margin-bottom: 12px;">Configure Figma Integration</h4>
|
||||
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label style="display: block; font-size: 11px; margin-bottom: 4px; color: var(--vscode-text-dim);">
|
||||
Figma File Key
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="figma-file-key-input"
|
||||
class="input"
|
||||
placeholder="e.g., abc123xyz456"
|
||||
value="${ComponentHelpers.escapeHtml(this.figmaFileKey || '')}"
|
||||
style="width: 100%; font-size: 11px; font-family: 'Courier New', monospace;"
|
||||
/>
|
||||
<div style="font-size: 10px; color: var(--vscode-text-dim); margin-top: 4px;">
|
||||
Find this in your Figma file URL: figma.com/file/<strong>FILE_KEY</strong>/...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 16px;">
|
||||
<label style="display: block; font-size: 11px; margin-bottom: 4px; color: var(--vscode-text-dim);">
|
||||
Figma Access Token
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="figma-token-input"
|
||||
class="input"
|
||||
placeholder="figd_..."
|
||||
style="width: 100%; font-size: 11px; font-family: 'Courier New', monospace;"
|
||||
/>
|
||||
<div style="font-size: 10px; color: var(--vscode-text-dim); margin-top: 4px;">
|
||||
Generate at: <a href="https://www.figma.com/developers/api#access-tokens" target="_blank" style="color: var(--vscode-accent);">figma.com/developers/api</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 12px; background-color: rgba(255, 191, 0, 0.1); border-radius: 4px; margin-bottom: 16px;">
|
||||
<div style="font-size: 11px; color: #ffbf00;">
|
||||
⚠️ <strong>Security Note:</strong> The Figma token must be configured as the <code>FIGMA_TOKEN</code> environment variable on the backend server. This UI only stores the file key locally.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 8px; justify-content: flex-end;">
|
||||
<button id="figma-cancel-config-btn" class="button" style="padding: 6px 12px; font-size: 11px;">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="figma-save-config-btn" class="button" style="padding: 6px 12px; font-size: 11px;">
|
||||
Save Configuration
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Not configured state
|
||||
if (this.connectionStatus === 'not_configured') {
|
||||
statusContent.innerHTML = `
|
||||
<div style="text-align: center; padding: 32px;">
|
||||
<div style="font-size: 48px; margin-bottom: 16px;">🎨</div>
|
||||
<h3 style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">Figma Not Configured</h3>
|
||||
<p style="font-size: 12px; color: var(--vscode-text-dim); margin-bottom: 16px;">
|
||||
Connect your Figma file to sync design tokens automatically.
|
||||
</p>
|
||||
<button id="figma-configure-btn" class="button" style="padding: 8px 16px; font-size: 12px;">
|
||||
Configure Figma
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const configureBtn = statusContent.querySelector('#figma-configure-btn');
|
||||
if (configureBtn) {
|
||||
configureBtn.addEventListener('click', () => this.showConfiguration());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Token missing state
|
||||
if (this.connectionStatus === 'token_missing') {
|
||||
statusContent.innerHTML = `
|
||||
<div style="padding: 16px; background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<h4 style="font-size: 12px; font-weight: 600;">Figma Configuration</h4>
|
||||
${this.getStatusBadge()}
|
||||
</div>
|
||||
|
||||
<div style="padding: 12px; background-color: rgba(255, 191, 0, 0.1); border: 1px solid #ffbf00; border-radius: 4px; margin-bottom: 12px;">
|
||||
<div style="font-size: 11px; color: #ffbf00;">
|
||||
⚠️ <strong>Backend Configuration Required</strong><br/>
|
||||
Please set the <code>FIGMA_TOKEN</code> environment variable on the backend server and restart.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 8px;">
|
||||
<strong>File Key:</strong> <code style="background-color: var(--vscode-bg); padding: 2px 6px; border-radius: 2px;">${ComponentHelpers.escapeHtml(this.figmaFileKey || 'N/A')}</code>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 8px; margin-top: 12px;">
|
||||
<button id="figma-configure-btn" class="button" style="padding: 4px 12px; font-size: 11px; flex: 1;">
|
||||
Reconfigure
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const configureBtn = statusContent.querySelector('#figma-configure-btn');
|
||||
if (configureBtn) {
|
||||
configureBtn.addEventListener('click', () => this.showConfiguration());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Connected state
|
||||
if (this.connectionStatus === 'connected') {
|
||||
statusContent.innerHTML = `
|
||||
<div style="padding: 16px; background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<h4 style="font-size: 12px; font-weight: 600;">Figma Sync</h4>
|
||||
${this.getStatusBadge()}
|
||||
</div>
|
||||
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 8px;">
|
||||
<strong>File Key:</strong> <code style="background-color: var(--vscode-bg); padding: 2px 6px; border-radius: 2px;">${ComponentHelpers.escapeHtml(this.figmaFileKey || 'N/A')}</code>
|
||||
</div>
|
||||
|
||||
${this.lastSync ? `
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 12px;">
|
||||
<strong>Last Sync:</strong> ${ComponentHelpers.formatRelativeTime(this.lastSync)}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div style="display: flex; gap: 8px; margin-top: 12px;">
|
||||
<button id="figma-sync-btn" class="button" style="padding: 4px 12px; font-size: 11px; flex: 1;">
|
||||
🔄 Sync Now
|
||||
</button>
|
||||
<button id="figma-configure-btn" class="button" style="padding: 4px 12px; font-size: 11px;">
|
||||
⚙️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const syncBtn = statusContent.querySelector('#figma-sync-btn');
|
||||
const configureBtn = statusContent.querySelector('#figma-configure-btn');
|
||||
|
||||
if (syncBtn) {
|
||||
syncBtn.addEventListener('click', () => this.syncFromFigma());
|
||||
}
|
||||
|
||||
if (configureBtn) {
|
||||
configureBtn.addEventListener('click', () => this.showConfiguration());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (this.connectionStatus === 'error') {
|
||||
statusContent.innerHTML = `
|
||||
<div style="padding: 16px; background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<h4 style="font-size: 12px; font-weight: 600;">Figma Sync</h4>
|
||||
${this.getStatusBadge()}
|
||||
</div>
|
||||
|
||||
<div style="padding: 12px; background-color: rgba(244, 135, 113, 0.1); border: 1px solid #f48771; border-radius: 4px; margin-bottom: 12px;">
|
||||
<div style="font-size: 11px; color: #f48771;">
|
||||
❌ Failed to connect to Figma. Please check your configuration.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button id="figma-configure-btn" class="button" style="padding: 4px 12px; font-size: 11px; flex: 1;">
|
||||
Reconfigure
|
||||
</button>
|
||||
<button id="figma-sync-btn" class="button" style="padding: 4px 12px; font-size: 11px;">
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const configureBtn = statusContent.querySelector('#figma-configure-btn');
|
||||
const syncBtn = statusContent.querySelector('#figma-sync-btn');
|
||||
|
||||
if (configureBtn) {
|
||||
configureBtn.addEventListener('click', () => this.showConfiguration());
|
||||
}
|
||||
|
||||
if (syncBtn) {
|
||||
syncBtn.addEventListener('click', () => this.checkConfiguration());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 16px; height: 100%; display: flex; flex-direction: column;">
|
||||
<div id="figma-status-content" style="flex: 1;">
|
||||
${ComponentHelpers.renderLoading('Checking Figma configuration...')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-figma-status', DSFigmaStatus);
|
||||
|
||||
export default DSFigmaStatus;
|
||||
Reference in New Issue
Block a user