node-processor.ts•7.34 kB
import type {
GetFileNodesResponse,
Node,
Component,
ComponentSet,
Style,
} from "@figma/rest-api-spec";
import type { SimplifiedNode } from "../types/figma.js";
import {
isVisible,
sanitizeComponents,
sanitizeComponentSets,
} from "../utils/figma-utils.js";
/**
* Transforms a raw Figma node into a simplified representation.
* Filters out unnecessary properties and normalizes the structure.
*/
export const parseNode = ({ node }: { node: Node }): SimplifiedNode | null => {
if (node.visible === false) {
return null;
}
const simplified: SimplifiedNode = {
id: node.id,
name: node.name,
type: node.type,
};
if ("absoluteBoundingBox" in node) {
simplified.absoluteBoundingBox = node.absoluteBoundingBox;
}
if (node.type === "TEXT" || node.type === "TEXT_PATH") {
if ("characters" in node) {
simplified.text = node.characters;
simplified.characters = node.characters;
}
if ("style" in node) {
simplified.textStyle = node.style;
simplified.style = node.style;
}
}
if ("fills" in node) simplified.fills = node.fills;
if ("styles" in node) simplified.styles = node.styles;
if ("strokes" in node) simplified.strokes = node.strokes;
if ("effects" in node) simplified.effects = node.effects;
if ("opacity" in node) simplified.opacity = node.opacity;
if ("cornerRadius" in node) simplified.borderRadius = node.cornerRadius;
if ("rectangleCornerRadii" in node && node.rectangleCornerRadii)
simplified.borderRadius = node.rectangleCornerRadii;
if ("strokeWeight" in node) {
simplified.strokeWeight = node.strokeWeight;
}
if ("strokeAlign" in node) simplified.strokeAlign = node.strokeAlign;
if ("strokeDashes" in node) simplified.strokeDashes = node.strokeDashes;
if ("cornerSmoothing" in node)
simplified.cornerSmoothing = node.cornerSmoothing;
if ("individualStrokeWeights" in node)
simplified.individualStrokeWeights = node.individualStrokeWeights;
if ("layoutMode" in node) simplified.layoutMode = node.layoutMode;
if ("layoutAlign" in node) simplified.layoutAlign = node.layoutAlign;
if ("layoutGrow" in node) simplified.layoutGrow = node.layoutGrow;
if ("layoutPositioning" in node)
simplified.layoutPositioning = node.layoutPositioning;
if ("layoutSizingHorizontal" in node)
simplified.layoutSizingHorizontal = node.layoutSizingHorizontal;
if ("layoutSizingVertical" in node)
simplified.layoutSizingVertical = node.layoutSizingVertical;
if ("constraints" in node) simplified.constraints = node.constraints;
if ("minWidth" in node) simplified.minWidth = node.minWidth;
if ("maxWidth" in node) simplified.maxWidth = node.maxWidth;
if ("minHeight" in node) simplified.minHeight = node.minHeight;
if ("maxHeight" in node) simplified.maxHeight = node.maxHeight;
if ("clipsContent" in node) simplified.clipsContent = node.clipsContent;
if ("overflowDirection" in node)
simplified.overflowDirection = node.overflowDirection;
if ("primaryAxisSizingMode" in node)
simplified.primaryAxisSizingMode = node.primaryAxisSizingMode;
if ("counterAxisSizingMode" in node)
simplified.counterAxisSizingMode = node.counterAxisSizingMode;
if ("primaryAxisAlignItems" in node)
simplified.primaryAxisAlignItems = node.primaryAxisAlignItems;
if ("counterAxisAlignItems" in node)
simplified.counterAxisAlignItems = node.counterAxisAlignItems;
if ("paddingLeft" in node) simplified.paddingLeft = node.paddingLeft;
if ("paddingRight" in node) simplified.paddingRight = node.paddingRight;
if ("paddingTop" in node) simplified.paddingTop = node.paddingTop;
if ("paddingBottom" in node) simplified.paddingBottom = node.paddingBottom;
if ("itemSpacing" in node) simplified.itemSpacing = node.itemSpacing;
if ("layoutWrap" in node) simplified.layoutWrap = node.layoutWrap;
if ("counterAxisSpacing" in node)
simplified.counterAxisSpacing = node.counterAxisSpacing;
if ("size" in node && node.size) {
simplified.size = {
width: node.size.x,
height: node.size.y,
};
}
if ("componentId" in node) simplified.componentId = node.componentId;
if ("componentSetId" in node && typeof node.componentSetId === "string")
simplified.componentSetId = node.componentSetId;
if ("componentProperties" in node)
simplified.componentProperties = node.componentProperties;
if ("componentPropertyDefinitions" in node)
simplified.componentPropertyDefinitions = node.componentPropertyDefinitions;
if ("componentPropertyReferences" in node)
simplified.componentPropertyReferences = node.componentPropertyReferences;
if ("exposedInstances" in node)
simplified.exposedInstances = node.exposedInstances;
if (
"isExposedInstance" in node &&
typeof node.isExposedInstance === "boolean"
)
simplified.isExposedInstance = node.isExposedInstance;
if ("overrides" in node) simplified.overrides = node.overrides;
if ("children" in node && Array.isArray(node.children)) {
simplified.children = node.children
.map((childNode) => parseNode({ node: childNode }))
.filter((child): child is SimplifiedNode => child !== null);
}
return simplified;
};
/**
* Processes the raw Figma API response into a simplified data structure.
* Extracts components, styles, and nodes for easier processing.
*/
export const getSimplifiedFigmaData = (
response: GetFileNodesResponse
): {
components: Record<
string,
{
id: string;
key: string;
name: string;
componentSetId: string | null | undefined;
}
>;
componentSets: Record<
string,
{ id: string; key: string; name: string; description: string | undefined }
>;
styles: Record<string, { [key: string]: Style }>;
nodes: SimplifiedNode[];
} => {
const aggregatedComponents: Record<string, Component> = {};
const aggregatedComponentSets: Record<string, ComponentSet> = {};
const aggregatedStyles: Record<string, { [key: string]: Style }> = {};
const nodeResponses = Object.values(response.nodes);
nodeResponses.forEach((nodeResponse) => {
if (nodeResponse.components) {
Object.assign(aggregatedComponents, nodeResponse.components);
}
if (nodeResponse.componentSets) {
Object.assign(aggregatedComponentSets, nodeResponse.componentSets);
}
if (nodeResponse.styles) {
Object.assign(aggregatedStyles, nodeResponse.styles);
}
});
const nodesToParse = nodeResponses.map((n) => n.document);
const sanitizedComponents = sanitizeComponents(aggregatedComponents);
const sanitizedComponentSets = sanitizeComponentSets(aggregatedComponentSets);
const simplifiedNodes = nodesToParse
.filter(isVisible)
.map((n) => parseNode({ node: n }))
.filter((child) => child !== null && child !== undefined);
return {
components: sanitizedComponents,
componentSets: sanitizedComponentSets,
styles: aggregatedStyles,
nodes: simplifiedNodes,
};
};
/**
* Maps Figma node types to simplified component types for code generation.
*/
export const simplifyType = (type: string): string => {
const typeMap: Record<string, string> = {
FRAME: "Container",
TEXT: "Text",
RECTANGLE: "Box",
INSTANCE: "Component",
"IMAGE-SVG": "Icon",
VECTOR: "Icon",
GROUP: "Group",
};
return typeMap[type] || type;
};