0
Fork 0
mirror of https://github.com/penpot/penpot-exporter-figma-plugin.git synced 2025-01-03 05:10:13 -05:00

Merge pull request #13 from Runroom/feature/code-refactor

Code Refactor
This commit is contained in:
Alex Sánchez 2024-04-09 11:31:03 +02:00 committed by GitHub
commit ffcf040bae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 138 additions and 103 deletions

View file

@ -49,3 +49,7 @@ export type ExportFile = {
penpotFile: PenpotFile; penpotFile: PenpotFile;
fontNames: Set<FontName>; fontNames: Set<FontName>;
}; };
export interface Signatures {
[key: string]: string;
}

View file

@ -1,114 +1,15 @@
import { NodeData, TextData } from '../common/interfaces'; import { handleCancelMessage, handleExportMessage, handleResizeMessage } from './messageHandlers';
interface Signatures {
[key: string]: string;
}
const signatures: Signatures = {
'R0lGODdh': 'image/gif',
'R0lGODlh': 'image/gif',
'iVBORw0KGgo': 'image/png',
'/9j/': 'image/jpg'
};
function detectMimeType(b64: string) {
for (const s in signatures) {
if (b64.indexOf(s) === 0) {
return signatures[s];
}
}
}
async function traverse(node: BaseNode): Promise<NodeData | TextData> {
const children: (NodeData | TextData)[] = [];
if (node.type === 'PAGE') {
await node.loadAsync();
}
if ('children' in node) {
if (node.type !== 'INSTANCE') {
for (const child of node.children) {
children.push(await traverse(child));
}
}
}
const result = {
id: node.id,
type: node.type,
name: node.name,
children: children,
x: 'x' in node ? node.x : 0,
y: 'y' in node ? node.y : 0,
width: 'width' in node ? node.width : 0,
height: 'height' in node ? node.height : 0,
fills: 'fills' in node ? (node.fills === figma.mixed ? [] : node.fills) : [], // TODO: Support mixed fills
imageFill: ''
};
if (result.fills) {
// Find any fill of type image
const imageFill = result.fills.find(fill => fill.type === 'IMAGE');
if (imageFill && 'exportAsync' in node) {
// An "image" in Figma is a shape with one or more image fills, potentially blended with other fill
// types. Given the complexity of mirroring this exactly in Penpot, which treats images as first-class
// objects, we're going to simplify this by exporting this shape as a PNG image.
const value = await node.exportAsync({ format: 'PNG' });
const b64 = figma.base64Encode(value);
result.imageFill = 'data:' + detectMimeType(b64) + ';base64,' + b64;
}
}
if (node.type == 'TEXT') {
const styledTextSegments = node.getStyledTextSegments([
'fontName',
'fontSize',
'fontWeight',
'lineHeight',
'letterSpacing',
'textCase',
'textDecoration',
'fills'
]);
if (styledTextSegments[0]) {
const font = {
...result,
fontName: styledTextSegments[0].fontName,
fontSize: styledTextSegments[0].fontSize.toString(),
fontWeight: styledTextSegments[0].fontWeight.toString(),
characters: node.characters,
lineHeight: styledTextSegments[0].lineHeight,
letterSpacing: styledTextSegments[0].letterSpacing,
fills: styledTextSegments[0].fills,
textCase: styledTextSegments[0].textCase,
textDecoration: styledTextSegments[0].textDecoration,
textAlignHorizontal: node.textAlignHorizontal,
textAlignVertical: node.textAlignVertical,
children: styledTextSegments
};
return font as TextData;
}
}
return result as NodeData;
}
figma.showUI(__html__, { themeColors: true, height: 200, width: 300 }); figma.showUI(__html__, { themeColors: true, height: 200, width: 300 });
figma.ui.onmessage = async msg => { figma.ui.onmessage = async msg => {
if (msg.type === 'export') { if (msg.type === 'export') {
const root: NodeData | TextData = await traverse(figma.root); // start the traversal at the root await handleExportMessage();
figma.ui.postMessage({ type: 'FIGMAFILE', data: root });
} }
if (msg.type === 'cancel') { if (msg.type === 'cancel') {
figma.closePlugin(); handleCancelMessage();
} }
if (msg.type === 'resize') { if (msg.type === 'resize') {
figma.ui.resize(msg.width, msg.height); handleResizeMessage(msg.width, msg.height);
} }
}; };

View file

@ -0,0 +1 @@
export * from './traverseNode';

View file

@ -0,0 +1,24 @@
import { NodeData, TextData } from '../../common/interfaces';
import { getImageFill, getNodeData, getTextData } from '../utils';
export async function traverse(baseNode: BaseNode): Promise<NodeData | TextData> {
const children: (NodeData | TextData)[] = [];
if ('children' in baseNode && baseNode.type !== 'INSTANCE') {
for (const child of baseNode.children) {
children.push(await traverse(child));
}
}
const nodeData = getNodeData(baseNode, children);
if (nodeData.fills) {
nodeData.imageFill = await getImageFill(baseNode, nodeData);
}
const textNode = getTextData(baseNode, nodeData);
if (textNode) {
return textNode;
}
return nodeData;
}

View file

@ -0,0 +1,3 @@
export function handleCancelMessage() {
figma.closePlugin();
}

View file

@ -0,0 +1,8 @@
import { NodeData, TextData } from '../../common/interfaces';
import { traverse } from '../figma';
export async function handleExportMessage() {
await figma.loadAllPagesAsync(); // ensures all PageNodes are loaded
const root: NodeData | TextData = await traverse(figma.root); // start the traversal at the root
figma.ui.postMessage({ type: 'FIGMAFILE', data: root });
}

View file

@ -0,0 +1,3 @@
export function handleResizeMessage(width: number, height: number) {
figma.ui.resize(width, height);
}

View file

@ -0,0 +1,3 @@
export * from './handleCancelMessage';
export * from './handleExportMessage';
export * from './handleResizeMessage';

View file

@ -0,0 +1,16 @@
import { Signatures } from '../../common/interfaces';
const signatures: Signatures = {
'R0lGODdh': 'image/gif',
'R0lGODlh': 'image/gif',
'iVBORw0KGgo': 'image/png',
'/9j/': 'image/jpg'
};
export const detectMimeType = (b64: string) => {
for (const s in signatures) {
if (b64.indexOf(s) === 0) {
return signatures[s];
}
}
};

View file

@ -0,0 +1,18 @@
import { NodeData } from '../../common/interfaces';
import { detectMimeType } from './detectMimeType';
export async function getImageFill(baseNode: BaseNode, node: NodeData): Promise<string> {
// Find any fill of type image
const imageFill = node.fills.find(fill => fill.type === 'IMAGE');
if (imageFill && 'exportAsync' in baseNode) {
// An "image" in Figma is a shape with one or more image fills, potentially blended with other fill
// types. Given the complexity of mirroring this exactly in Penpot, which treats images as first-class
// objects, we're going to simplify this by exporting this shape as a PNG image.
const value = await baseNode.exportAsync({ format: 'PNG' });
const b64 = figma.base64Encode(value);
return 'data:' + detectMimeType(b64) + ';base64,' + b64;
}
return '';
}

View file

@ -0,0 +1,16 @@
import { NodeData, TextData } from '../../common/interfaces';
export const getNodeData = (node: BaseNode, children: (NodeData | TextData)[]): NodeData => {
return {
id: node.id,
type: node.type,
name: node.name,
children: children,
x: 'x' in node ? node.x : 0,
y: 'y' in node ? node.y : 0,
width: 'width' in node ? node.width : 0,
height: 'height' in node ? node.height : 0,
fills: 'fills' in node ? (node.fills === figma.mixed ? [] : node.fills) : [], // TODO: Support mixed fills
imageFill: ''
} as NodeData;
};

View file

@ -0,0 +1,34 @@
import { NodeData, TextData } from '../../common/interfaces';
export const getTextData = (baseNode: BaseNode, nodeData: NodeData): TextData | undefined => {
if (baseNode.type === 'TEXT') {
const styledTextSegments = baseNode.getStyledTextSegments([
'fontName',
'fontSize',
'fontWeight',
'lineHeight',
'letterSpacing',
'textCase',
'textDecoration',
'fills'
]);
if (styledTextSegments[0]) {
return {
...nodeData,
fontName: styledTextSegments[0].fontName,
fontSize: styledTextSegments[0].fontSize.toString(),
fontWeight: styledTextSegments[0].fontWeight.toString(),
characters: baseNode.characters,
lineHeight: styledTextSegments[0].lineHeight,
letterSpacing: styledTextSegments[0].letterSpacing,
fills: styledTextSegments[0].fills,
textCase: styledTextSegments[0].textCase,
textDecoration: styledTextSegments[0].textDecoration,
textAlignHorizontal: baseNode.textAlignHorizontal,
textAlignVertical: baseNode.textAlignVertical,
children: styledTextSegments
} as TextData;
}
}
};

View file

@ -0,0 +1,4 @@
export * from './detectMimeType';
export * from './getImageFill';
export * from './getNodeData';
export * from './getTextData';