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:
229
admin-ui/js/services/figma-service.js
Normal file
229
admin-ui/js/services/figma-service.js
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Design System Server (DSS) - Figma Service
|
||||
*
|
||||
* Client-side interface to Figma API tools.
|
||||
* No mocks - requires backend connection.
|
||||
*/
|
||||
import notificationService from './notification-service.js';
|
||||
|
||||
class FigmaService {
|
||||
constructor() {
|
||||
this.apiBase = '/api/figma';
|
||||
this.connected = null; // null = unknown, true/false = checked
|
||||
this.listeners = new Set();
|
||||
}
|
||||
|
||||
// === Connection ===
|
||||
|
||||
async checkConnection() {
|
||||
try {
|
||||
const response = await fetch('/health', { method: 'GET' });
|
||||
this.connected = response.ok;
|
||||
} catch {
|
||||
this.connected = false;
|
||||
}
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
_requireConnection() {
|
||||
if (this.connected === false) {
|
||||
throw new Error('API unavailable. Start DSS server first.');
|
||||
}
|
||||
}
|
||||
|
||||
// === Event System ===
|
||||
|
||||
on(event, callback) {
|
||||
this.listeners.add({ event, callback });
|
||||
return () => this.listeners.delete({ event, callback });
|
||||
}
|
||||
|
||||
emit(event, data) {
|
||||
this.listeners.forEach(l => {
|
||||
if (l.event === event) l.callback(data);
|
||||
});
|
||||
}
|
||||
|
||||
// === API Methods ===
|
||||
|
||||
async extractVariables(fileKey, format = 'css') {
|
||||
this._requireConnection();
|
||||
this.emit('loading', { operation: 'extractVariables' });
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiBase}/extract-variables`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ file_key: fileKey, format })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Failed to extract variables');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
this.emit('complete', { operation: 'extractVariables', result });
|
||||
|
||||
notificationService.create({
|
||||
title: 'Variables Extracted',
|
||||
message: `Successfully extracted ${result.variable_count || 'variables'} from Figma.`,
|
||||
type: 'success',
|
||||
source: 'Figma',
|
||||
actions: [
|
||||
{ label: 'View Tokens', event: 'navigate', payload: { page: 'tokens' } }
|
||||
]
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
notificationService.create({
|
||||
title: 'Extraction Failed',
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
source: 'Figma'
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async extractComponents(fileKey) {
|
||||
this._requireConnection();
|
||||
this.emit('loading', { operation: 'extractComponents' });
|
||||
|
||||
const response = await fetch(`${this.apiBase}/extract-components`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ file_key: fileKey })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Failed to extract components');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
this.emit('complete', { operation: 'extractComponents', result });
|
||||
return result;
|
||||
}
|
||||
|
||||
async extractStyles(fileKey) {
|
||||
this._requireConnection();
|
||||
this.emit('loading', { operation: 'extractStyles' });
|
||||
|
||||
const response = await fetch(`${this.apiBase}/extract-styles`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ file_key: fileKey })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Failed to extract styles');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async syncTokens(fileKey, targetPath, format = 'css') {
|
||||
this._requireConnection();
|
||||
this.emit('loading', { operation: 'syncTokens' });
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiBase}/sync-tokens`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ file_key: fileKey, target_path: targetPath, format })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Sync failed');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
this.emit('complete', { operation: 'syncTokens', result });
|
||||
|
||||
notificationService.create({
|
||||
title: 'Tokens Synced',
|
||||
message: `Tokens successfully written to ${result.path || targetPath}.`,
|
||||
type: 'success',
|
||||
source: 'Figma',
|
||||
actions: [
|
||||
{ label: 'View Tokens', event: 'navigate', payload: { page: 'tokens' } }
|
||||
]
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
notificationService.create({
|
||||
title: 'Token Sync Failed',
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
source: 'Figma',
|
||||
actions: [
|
||||
{ label: 'Retry', event: 'figma:sync', payload: { fileKey } }
|
||||
]
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async visualDiff(fileKey, baselineVersion = 'latest') {
|
||||
this._requireConnection();
|
||||
this.emit('loading', { operation: 'visualDiff' });
|
||||
|
||||
const response = await fetch(`${this.apiBase}/visual-diff`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ file_key: fileKey, baseline_version: baselineVersion })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Visual diff failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async validateComponents(fileKey) {
|
||||
this._requireConnection();
|
||||
this.emit('loading', { operation: 'validateComponents' });
|
||||
|
||||
const response = await fetch(`${this.apiBase}/validate-components`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ file_key: fileKey })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Validation failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async generateCode(fileKey, componentName, framework = 'webcomponent') {
|
||||
this._requireConnection();
|
||||
this.emit('loading', { operation: 'generateCode' });
|
||||
|
||||
const response = await fetch(`${this.apiBase}/generate-code`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ file_key: fileKey, component_name: componentName, framework })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Code generation failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
|
||||
const figmaService = new FigmaService();
|
||||
export { FigmaService };
|
||||
export default figmaService;
|
||||
Reference in New Issue
Block a user