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:
249
admin-ui/js/components/tools/ds-token-inspector.js
Normal file
249
admin-ui/js/components/tools/ds-token-inspector.js
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* ds-token-inspector.js
|
||||
* Token inspector for viewing and searching design tokens
|
||||
*/
|
||||
|
||||
import toolBridge from '../../services/tool-bridge.js';
|
||||
import { ComponentHelpers } from '../../utils/component-helpers.js';
|
||||
|
||||
class DSTokenInspector extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.tokens = null;
|
||||
this.filteredTokens = null;
|
||||
this.searchTerm = '';
|
||||
this.currentCategory = 'all';
|
||||
this.manifestPath = '/home/overbits/dss/admin-ui/ds.config.json';
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
this.loadTokens();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
const refreshBtn = this.querySelector('#token-refresh-btn');
|
||||
if (refreshBtn) {
|
||||
refreshBtn.addEventListener('click', () => this.loadTokens(true));
|
||||
}
|
||||
|
||||
const searchInput = this.querySelector('#token-search');
|
||||
if (searchInput) {
|
||||
const debouncedSearch = ComponentHelpers.debounce((term) => {
|
||||
this.searchTerm = term.toLowerCase();
|
||||
this.filterTokens();
|
||||
}, 300);
|
||||
|
||||
searchInput.addEventListener('input', (e) => debouncedSearch(e.target.value));
|
||||
}
|
||||
|
||||
const categoryFilter = this.querySelector('#token-category');
|
||||
if (categoryFilter) {
|
||||
categoryFilter.addEventListener('change', (e) => {
|
||||
this.currentCategory = e.target.value;
|
||||
this.filterTokens();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async loadTokens(forceRefresh = false) {
|
||||
const content = this.querySelector('#token-content');
|
||||
if (!content) return;
|
||||
|
||||
content.innerHTML = ComponentHelpers.renderLoading('Loading tokens from Context Compiler...');
|
||||
|
||||
try {
|
||||
const result = await toolBridge.getTokens(this.manifestPath);
|
||||
|
||||
if (result && result.tokens) {
|
||||
this.tokens = this.flattenTokens(result.tokens);
|
||||
this.filterTokens();
|
||||
} else {
|
||||
content.innerHTML = ComponentHelpers.renderEmpty('No tokens found', '🎨');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load tokens:', error);
|
||||
content.innerHTML = ComponentHelpers.renderError('Failed to load tokens', error);
|
||||
}
|
||||
}
|
||||
|
||||
flattenTokens(tokens, prefix = '') {
|
||||
const flattened = [];
|
||||
|
||||
for (const [key, value] of Object.entries(tokens)) {
|
||||
const path = prefix ? `${prefix}.${key}` : key;
|
||||
|
||||
if (value && typeof value === 'object' && !value.$value) {
|
||||
// Nested object - recurse
|
||||
flattened.push(...this.flattenTokens(value, path));
|
||||
} else {
|
||||
// Token leaf node
|
||||
flattened.push({
|
||||
path,
|
||||
value: value.$value || value,
|
||||
type: value.$type || this.inferType(value.$value || value),
|
||||
description: value.$description || '',
|
||||
category: this.extractCategory(path)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return flattened;
|
||||
}
|
||||
|
||||
extractCategory(path) {
|
||||
const parts = path.split('.');
|
||||
return parts[0] || 'other';
|
||||
}
|
||||
|
||||
inferType(value) {
|
||||
if (typeof value === 'string') {
|
||||
if (value.startsWith('#') || value.startsWith('rgb')) return 'color';
|
||||
if (value.endsWith('px') || value.endsWith('rem') || value.endsWith('em')) return 'dimension';
|
||||
return 'string';
|
||||
}
|
||||
if (typeof value === 'number') return 'number';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
filterTokens() {
|
||||
if (!this.tokens) return;
|
||||
|
||||
let filtered = [...this.tokens];
|
||||
|
||||
// Filter by category
|
||||
if (this.currentCategory !== 'all') {
|
||||
filtered = filtered.filter(token => token.category === this.currentCategory);
|
||||
}
|
||||
|
||||
// Filter by search term
|
||||
if (this.searchTerm) {
|
||||
filtered = filtered.filter(token =>
|
||||
token.path.toLowerCase().includes(this.searchTerm) ||
|
||||
String(token.value).toLowerCase().includes(this.searchTerm) ||
|
||||
token.description.toLowerCase().includes(this.searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
this.filteredTokens = filtered;
|
||||
this.renderTokens();
|
||||
}
|
||||
|
||||
getCategories() {
|
||||
if (!this.tokens) return [];
|
||||
const categories = new Set(this.tokens.map(t => t.category));
|
||||
return Array.from(categories).sort();
|
||||
}
|
||||
|
||||
renderTokens() {
|
||||
const content = this.querySelector('#token-content');
|
||||
if (!content) return;
|
||||
|
||||
if (!this.filteredTokens || this.filteredTokens.length === 0) {
|
||||
content.innerHTML = ComponentHelpers.renderEmpty(
|
||||
this.searchTerm ? 'No tokens match your search' : 'No tokens available',
|
||||
'🔍'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenRows = this.filteredTokens.map(token => {
|
||||
const colorPreview = token.type === 'color' ? `
|
||||
<div style="
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: ${token.value};
|
||||
border: 1px solid var(--vscode-border);
|
||||
border-radius: 2px;
|
||||
margin-right: 8px;
|
||||
"></div>
|
||||
` : '';
|
||||
|
||||
return `
|
||||
<tr style="border-bottom: 1px solid var(--vscode-border);">
|
||||
<td style="padding: 12px 16px; font-family: 'Courier New', monospace; font-size: 11px; color: var(--vscode-accent);">
|
||||
${ComponentHelpers.escapeHtml(token.path)}
|
||||
</td>
|
||||
<td style="padding: 12px 16px;">
|
||||
<div style="display: flex; align-items: center;">
|
||||
${colorPreview}
|
||||
<span style="font-size: 12px; font-family: 'Courier New', monospace;">
|
||||
${ComponentHelpers.escapeHtml(String(token.value))}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding: 12px 16px;">
|
||||
${ComponentHelpers.createBadge(token.type, 'info')}
|
||||
</td>
|
||||
<td style="padding: 12px 16px; font-size: 11px; color: var(--vscode-text-dim);">
|
||||
${ComponentHelpers.escapeHtml(token.description || '-')}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
content.innerHTML = `
|
||||
<div style="margin-bottom: 12px; padding: 12px; background-color: var(--vscode-sidebar); border-radius: 4px;">
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim);">
|
||||
Showing ${this.filteredTokens.length} of ${this.tokens.length} tokens
|
||||
</div>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse; background-color: var(--vscode-sidebar);">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid var(--vscode-border);">
|
||||
<th style="padding: 12px 16px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--vscode-text-dim);">
|
||||
Token Path
|
||||
</th>
|
||||
<th style="padding: 12px 16px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--vscode-text-dim);">
|
||||
Value
|
||||
</th>
|
||||
<th style="padding: 12px 16px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--vscode-text-dim);">
|
||||
Type
|
||||
</th>
|
||||
<th style="padding: 12px 16px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--vscode-text-dim);">
|
||||
Description
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${tokenRows}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 16px; height: 100%; display: flex; flex-direction: column;">
|
||||
<div style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center;">
|
||||
<input
|
||||
type="text"
|
||||
id="token-search"
|
||||
placeholder="Search tokens..."
|
||||
class="input"
|
||||
style="flex: 1; min-width: 200px;"
|
||||
/>
|
||||
<select id="token-category" class="input" style="width: 150px;">
|
||||
<option value="all">All Categories</option>
|
||||
${this.getCategories().map(cat =>
|
||||
`<option value="${cat}">${cat}</option>`
|
||||
).join('')}
|
||||
</select>
|
||||
<button id="token-refresh-btn" class="button" style="padding: 4px 12px; font-size: 11px;">
|
||||
🔄 Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div id="token-content" style="flex: 1; overflow-y: auto;">
|
||||
${ComponentHelpers.renderLoading('Initializing...')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-token-inspector', DSTokenInspector);
|
||||
|
||||
export default DSTokenInspector;
|
||||
Reference in New Issue
Block a user