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
};