FigmaMind MCP Server

by joao-loker
Verified
// Processor for Figma data const utils = require('./utils'); /** * Identifies the type of component * @param {object} node - Figma node * @returns {string} Component type */ function identifyComponentType(node) { const name = (node.name || '').toLowerCase(); if (name.includes('button') || node.type === 'INSTANCE' && node.mainComponent?.name?.toLowerCase().includes('button')) { return 'button'; } if (name.includes('input') || name.includes('field') || name.includes('text field')) { return 'input'; } if (name.includes('header') || name.includes('navbar')) { return 'header'; } if (name.includes('footer')) { return 'footer'; } if (name.includes('card')) { return 'card'; } if (name.includes('icon')) { return 'icon'; } // Default to generic component return 'component'; } /** * Extract bounding box information * @param {object} node - Figma node * @returns {object} Size and position */ function extractBounds(node) { const bounds = node.absoluteBoundingBox || { x: 0, y: 0, width: 0, height: 0 }; return { size: { width: bounds.width || 0, height: bounds.height || 0 }, position: { x: bounds.x || 0, y: bounds.y || 0 } }; } /** * Process a Figma node into a component * @param {object} node - Figma node * @returns {object} Processed component */ function processNode(node) { const type = identifyComponentType(node); const { size, position } = extractBounds(node); // Basic component structure return { id: node.id, name: node.name, type, size, position, properties: { visible: node.visible !== false, opacity: node.opacity || 1 } }; } /** * Extract all components from a Figma document * @param {object} document - Figma document * @returns {Array} Array of components */ function extractComponents(document) { const components = []; // Function to traverse the node tree function traverseNodes(node) { // Process current node if it's a valid component if (node.type && node.id && node.name) { // Skip nodes that are clearly not components const skipTypes = ['DOCUMENT', 'CANVAS', 'GROUP', 'FRAME']; if (!skipTypes.includes(node.type)) { components.push(processNode(node)); } } // Process children if (node.children && Array.isArray(node.children)) { node.children.forEach(child => traverseNodes(child)); } } // Start traversal from document traverseNodes(document); return components; } /** * Process Figma data into a structured format * @param {object} figmaData - Raw Figma API response * @param {string} fileKey - Figma file key * @returns {object} Processed data structure */ async function processData(figmaData, fileKey) { try { utils.log('Starting to process Figma data', 'debug'); // Extract components const components = extractComponents(figmaData.document); utils.log(`Extracted ${components.length} components`, 'debug'); // Create screen information from the first canvas found const firstCanvas = figmaData.document.children && figmaData.document.children[0]; const screen = { name: firstCanvas?.name || 'Screen', size: { width: firstCanvas?.absoluteBoundingBox?.width || 0, height: firstCanvas?.absoluteBoundingBox?.height || 0 }, layout: { sections: [], orderedElements: components }, elements: { buttons: components.filter(c => c.type === 'button'), inputs: components.filter(c => c.type === 'input'), header: components.find(c => c.type === 'header'), other: components.filter(c => !['button', 'input', 'header'].includes(c.type)) } }; // Build the final data structure const result = { source: figmaData.name, lastModified: figmaData.lastModified, version: figmaData.version, componentsCount: components.length, fileKey: fileKey, processedAt: new Date().toISOString(), components, styles: {}, assets: {}, screen }; utils.log('Finished processing Figma data', 'debug'); return result; } catch (error) { utils.log(`Error processing data: ${error.message}`, 'error'); throw new Error(`Failed to process Figma data: ${error.message}`); } } module.exports = { processData, extractComponents };