diff --git a/src/code.ts b/src/code.ts index 4f1ebd8..6f1a915 100644 --- a/src/code.ts +++ b/src/code.ts @@ -43,27 +43,21 @@ async function traverse(node: BaseNode): Promise { 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 + 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) { + 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. - 'exportAsync' in node && - node.exportAsync({ format: 'PNG' }).then(value => { - const b64 = figma.base64Encode(value); - figma.ui.postMessage({ - type: 'IMAGE', - data: { - id: node.id, - value: 'data:' + detectMimeType(b64) + ';base64,' + b64 - } - }); - }); + const value = await node.exportAsync({ format: 'PNG' }); + const b64 = figma.base64Encode(value); + + result.imageFill = 'data:' + detectMimeType(b64) + ';base64,' + b64; } } @@ -105,10 +99,11 @@ async function traverse(node: BaseNode): Promise { figma.showUI(__html__, { themeColors: true, height: 200, width: 300 }); -const root: NodeData | TextData = await traverse(figma.root); // start the traversal at the root -figma.ui.postMessage({ type: 'FIGMAFILE', data: root }); - -figma.ui.onmessage = msg => { +figma.ui.onmessage = async msg => { + if (msg.type === 'export') { + const root: NodeData | TextData = await traverse(figma.root); // start the traversal at the root + figma.ui.postMessage({ type: 'FIGMAFILE', data: root }); + } if (msg.type === 'cancel') { figma.closePlugin(); } diff --git a/src/interfaces.ts b/src/interfaces.ts index 1d144ff..a872585 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -8,6 +8,7 @@ export type NodeData = { width: number; height: number; fills: readonly Paint[]; + imageFill?: string; }; export type TextDataChildren = Pick< diff --git a/src/ui.css b/src/ui.css index 3cce829..2e11866 100644 --- a/src/ui.css +++ b/src/ui.css @@ -57,6 +57,12 @@ button:focus-visible { outline-color: var(--color-border-focus); } +button:disabled { + background-color: black; + color: var(--color-text); + cursor: not-allowed; +} + button.brand { --color-bg: var(--color-bg-brand); --color-text: var(--color-text-brand); diff --git a/src/ui.tsx b/src/ui.tsx index d6e93cc..b615dc2 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -2,40 +2,22 @@ import * as React from 'react'; import { createRoot } from 'react-dom/client'; import slugify from 'slugify'; +import fonts from './gfonts.json'; import { NodeData, TextData, TextDataChildren } from './interfaces'; import { PenpotFile, createFile } from './penpot'; import './ui.css'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -declare function require(path: string): any; - -// Open resources/gfonts.json and create a set of matched font names -const gfonts = new Set(); -require('./gfonts.json').forEach((font: string) => gfonts.add(font)); - -type FigmaImageData = { - value: string; - width: number; - height: number; -}; +const gfonts = new Set(fonts); type PenpotExporterState = { - isDebug: boolean; - penpotFileData: string; missingFonts: Set; - figmaFileData: string; - figmaRootNode: NodeData | null; - images: { [id: string]: FigmaImageData }; + exporting: boolean; }; export default class PenpotExporter extends React.Component { state: PenpotExporterState = { - isDebug: false, - penpotFileData: '', - figmaFileData: '', missingFonts: new Set(), - figmaRootNode: null, - images: {} + exporting: false }; componentDidMount = () => { @@ -292,24 +274,18 @@ export default class PenpotExporter extends React.Component { - const file = this.createPenpotFile(); - const penpotFileMap = file.asMap(); - this.setState(() => ({ - penpotFileData: JSON.stringify( - penpotFileMap, - (key, value) => (value instanceof Map ? [...value] : value), - 4 - ) - })); - file.export(); + this.setState(() => ({ exporting: true })); + parent.postMessage({ pluginMessage: { type: 'export' } }, '*'); }; onCancel = () => { @@ -398,30 +354,9 @@ export default class PenpotExporter extends React.Component { if (event.data.pluginMessage.type == 'FIGMAFILE') { - this.setState(() => ({ - figmaFileData: JSON.stringify( - event.data.pluginMessage.data, - (key, value) => (value instanceof Map ? [...value] : value), - 4 - ), - figmaRootNode: event.data.pluginMessage.data - })); - } else if (event.data.pluginMessage.type == 'IMAGE') { - const data = event.data.pluginMessage.data; - const image = document.createElement('img'); - - image.addEventListener('load', () => { - // Get byte array from response - this.setState(state => { - state.images[data.id] = { - value: data.value, - width: image.naturalWidth, - height: image.naturalHeight - }; - return state; - }); - }); - image.src = data.value; + const file = this.createPenpotFile(event.data.pluginMessage.data); + file.export(); + this.setState(() => ({ exporting: false })); } }; @@ -434,19 +369,8 @@ export default class PenpotExporter extends React.Component) => { - const isDebug = event.currentTarget.checked; - this.setState(() => ({ isDebug: isDebug })); }; renderFontWarnings = () => { @@ -476,34 +400,10 @@ export default class PenpotExporter extends React.ComponentEnsure fonts are installed in Penpot before importing.
{this.renderFontWarnings()}
-
- - -
-
-
-