/** * 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' ? `
` : ''; return ` ${ComponentHelpers.escapeHtml(token.path)}
${colorPreview} ${ComponentHelpers.escapeHtml(String(token.value))}
${ComponentHelpers.createBadge(token.type, 'info')} ${ComponentHelpers.escapeHtml(token.description || '-')} `; }).join(''); content.innerHTML = `
Showing ${this.filteredTokens.length} of ${this.tokens.length} tokens
${tokenRows}
Token Path Value Type Description
`; } render() { this.innerHTML = `
${ComponentHelpers.renderLoading('Initializing...')}
`; } } customElements.define('ds-token-inspector', DSTokenInspector); export default DSTokenInspector;