mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2024-12-31 12:03:58 -05:00
Add loaders when the file is being built so we can track the progress (#172)
* Add loaders when the file is being built so we can track the progress * add changelog * improve * refactor
This commit is contained in:
parent
199984d961
commit
8697902e08
14 changed files with 169 additions and 80 deletions
5
.changeset/tidy-pandas-enjoy.md
Normal file
5
.changeset/tidy-pandas-enjoy.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Track better the progress on file creation
|
|
@ -45,6 +45,7 @@ export const transformComponentNode = async (
|
||||||
|
|
||||||
return {
|
return {
|
||||||
figmaId: node.id,
|
figmaId: node.id,
|
||||||
type: 'component'
|
type: 'component',
|
||||||
|
name: node.name
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,18 +12,21 @@ import { transformPageNode } from '.';
|
||||||
const downloadImages = async (): Promise<Record<string, Uint8Array>> => {
|
const downloadImages = async (): Promise<Record<string, Uint8Array>> => {
|
||||||
const imageToDownload = Object.entries(imagesLibrary.all());
|
const imageToDownload = Object.entries(imagesLibrary.all());
|
||||||
const images: Record<string, Uint8Array> = {};
|
const images: Record<string, Uint8Array> = {};
|
||||||
let currentImage = 1;
|
|
||||||
|
|
||||||
figma.ui.postMessage({
|
if (imageToDownload.length === 0) return images;
|
||||||
type: 'PROGRESS_STEP',
|
|
||||||
data: 'images'
|
let currentImage = 1;
|
||||||
});
|
|
||||||
|
|
||||||
figma.ui.postMessage({
|
figma.ui.postMessage({
|
||||||
type: 'PROGRESS_TOTAL_ITEMS',
|
type: 'PROGRESS_TOTAL_ITEMS',
|
||||||
data: imageToDownload.length
|
data: imageToDownload.length
|
||||||
});
|
});
|
||||||
|
|
||||||
|
figma.ui.postMessage({
|
||||||
|
type: 'PROGRESS_STEP',
|
||||||
|
data: 'images'
|
||||||
|
});
|
||||||
|
|
||||||
for (const [key, image] of imageToDownload) {
|
for (const [key, image] of imageToDownload) {
|
||||||
const bytes = await image?.getBytesAsync();
|
const bytes = await image?.getBytesAsync();
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,15 @@ const stepMessages: Record<Steps, Messages> = {
|
||||||
optimization: {
|
optimization: {
|
||||||
total: 'images optimized 📸'
|
total: 'images optimized 📸'
|
||||||
},
|
},
|
||||||
downloading: {
|
building: {
|
||||||
|
total: 'pages built 🏗️',
|
||||||
|
current: 'Currently processing layer'
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
total: 'components built 🏗️',
|
||||||
|
current: 'Currently processing layer'
|
||||||
|
},
|
||||||
|
exporting: {
|
||||||
total: 'Generating Penpot file 🚀',
|
total: 'Generating Penpot file 🚀',
|
||||||
current: 'Please wait, this process might take a while...'
|
current: 'Please wait, this process might take a while...'
|
||||||
}
|
}
|
||||||
|
@ -51,6 +59,8 @@ const StepProgress = (): JSX.Element | null => {
|
||||||
case 'remote':
|
case 'remote':
|
||||||
case 'images':
|
case 'images':
|
||||||
case 'optimization':
|
case 'optimization':
|
||||||
|
case 'building':
|
||||||
|
case 'components':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{processedItems} of {totalItems} {stepMessages[step].total}
|
{processedItems} of {totalItems} {stepMessages[step].total}
|
||||||
|
@ -64,7 +74,7 @@ const StepProgress = (): JSX.Element | null => {
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
case 'downloading':
|
case 'exporting':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{stepMessages[step].total}
|
{stepMessages[step].total}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
|
||||||
import { FormValues } from '@ui/components/ExportForm';
|
import { FormValues } from '@ui/components/ExportForm';
|
||||||
import { parse } from '@ui/parser';
|
import { parse } from '@ui/parser';
|
||||||
|
|
||||||
import { MessageData } from '.';
|
import { MessageData, sendMessage } from '.';
|
||||||
|
|
||||||
export type UseFigmaHook = {
|
export type UseFigmaHook = {
|
||||||
missingFonts: string[] | undefined;
|
missingFonts: string[] | undefined;
|
||||||
|
@ -19,7 +19,14 @@ export type UseFigmaHook = {
|
||||||
exportPenpot: (data: FormValues) => void;
|
exportPenpot: (data: FormValues) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Steps = 'processing' | 'remote' | 'images' | 'optimization' | 'downloading';
|
export type Steps =
|
||||||
|
| 'processing'
|
||||||
|
| 'remote'
|
||||||
|
| 'images'
|
||||||
|
| 'optimization'
|
||||||
|
| 'building'
|
||||||
|
| 'components'
|
||||||
|
| 'exporting';
|
||||||
|
|
||||||
export const useFigma = (): UseFigmaHook => {
|
export const useFigma = (): UseFigmaHook => {
|
||||||
const [missingFonts, setMissingFonts] = useState<string[]>();
|
const [missingFonts, setMissingFonts] = useState<string[]>();
|
||||||
|
@ -44,6 +51,12 @@ export const useFigma = (): UseFigmaHook => {
|
||||||
switch (pluginMessage.type) {
|
switch (pluginMessage.type) {
|
||||||
case 'PENPOT_DOCUMENT': {
|
case 'PENPOT_DOCUMENT': {
|
||||||
const file = await parse(pluginMessage.data);
|
const file = await parse(pluginMessage.data);
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
type: 'PROGRESS_STEP',
|
||||||
|
data: 'exporting'
|
||||||
|
});
|
||||||
|
|
||||||
const blob = await file.export();
|
const blob = await file.export();
|
||||||
|
|
||||||
download(blob, `${pluginMessage.data.name}.zip`);
|
download(blob, `${pluginMessage.data.name}.zip`);
|
||||||
|
|
|
@ -16,7 +16,6 @@ export type ShapeBaseAttributes = {
|
||||||
id?: Uuid;
|
id?: Uuid;
|
||||||
figmaId?: string; // @TODO: move to any other place
|
figmaId?: string; // @TODO: move to any other place
|
||||||
figmaRelatedId?: string; // @TODO: move to any other place
|
figmaRelatedId?: string; // @TODO: move to any other place
|
||||||
name?: string;
|
|
||||||
type?:
|
type?:
|
||||||
| 'frame'
|
| 'frame'
|
||||||
| 'group'
|
| 'group'
|
||||||
|
@ -29,8 +28,6 @@ export type ShapeBaseAttributes = {
|
||||||
| 'image'
|
| 'image'
|
||||||
| 'component'
|
| 'component'
|
||||||
| 'instance';
|
| 'instance';
|
||||||
selrect?: Selrect;
|
|
||||||
points?: Point[];
|
|
||||||
transform?: Matrix;
|
transform?: Matrix;
|
||||||
transformInverse?: Matrix;
|
transformInverse?: Matrix;
|
||||||
parentId?: Uuid;
|
parentId?: Uuid;
|
||||||
|
@ -39,7 +36,7 @@ export type ShapeBaseAttributes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShapeAttributes = {
|
export type ShapeAttributes = {
|
||||||
name?: string;
|
name: string;
|
||||||
componentId?: string;
|
componentId?: string;
|
||||||
componentFile?: string;
|
componentFile?: string;
|
||||||
componentRoot?: boolean;
|
componentRoot?: boolean;
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { componentsLibrary } from '@plugin/ComponentLibrary';
|
|
||||||
|
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
|
||||||
import { symbolBlendMode, symbolFills, symbolStrokes } from '@ui/parser/creators/symbols';
|
|
||||||
import { uiComponents } from '@ui/parser/libraries';
|
|
||||||
|
|
||||||
import { createItems } from '.';
|
|
||||||
|
|
||||||
export const createComponentLibrary = (file: PenpotFile) => {
|
|
||||||
uiComponents.all().forEach(uiComponent => {
|
|
||||||
const component = componentsLibrary.get(uiComponent.componentFigmaId);
|
|
||||||
if (!component) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { children = [], fills, strokes, blendMode, ...rest } = component;
|
|
||||||
|
|
||||||
file.startComponent({
|
|
||||||
...rest,
|
|
||||||
fills: symbolFills(fills),
|
|
||||||
strokes: symbolStrokes(strokes),
|
|
||||||
blendMode: symbolBlendMode(blendMode),
|
|
||||||
id: uiComponent.componentId,
|
|
||||||
componentId: uiComponent.componentId,
|
|
||||||
mainInstancePage: uiComponent.mainInstancePage,
|
|
||||||
mainInstanceId: uiComponent.mainInstanceId,
|
|
||||||
componentRoot: true,
|
|
||||||
mainInstance: true,
|
|
||||||
componentFile: file.getId()
|
|
||||||
});
|
|
||||||
|
|
||||||
createItems(file, children);
|
|
||||||
|
|
||||||
file.finishComponent();
|
|
||||||
});
|
|
||||||
};
|
|
63
ui-src/parser/creators/createComponentsLibrary.ts
Normal file
63
ui-src/parser/creators/createComponentsLibrary.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { componentsLibrary } from '@plugin/ComponentLibrary';
|
||||||
|
import { sleep } from '@plugin/utils/sleep';
|
||||||
|
|
||||||
|
import { sendMessage } from '@ui/context';
|
||||||
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
|
import { symbolBlendMode, symbolFills, symbolStrokes } from '@ui/parser/creators/symbols';
|
||||||
|
import { UiComponent, uiComponents } from '@ui/parser/libraries';
|
||||||
|
|
||||||
|
import { createItems } from '.';
|
||||||
|
|
||||||
|
export const createComponentsLibrary = async (file: PenpotFile) => {
|
||||||
|
let componentsBuilt = 1;
|
||||||
|
const components = uiComponents.all();
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
type: 'PROGRESS_TOTAL_ITEMS',
|
||||||
|
data: components.length
|
||||||
|
});
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
type: 'PROGRESS_STEP',
|
||||||
|
data: 'components'
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const uiComponent of components) {
|
||||||
|
createComponentLibrary(file, uiComponent);
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
type: 'PROGRESS_PROCESSED_ITEMS',
|
||||||
|
data: componentsBuilt++
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createComponentLibrary = async (file: PenpotFile, uiComponent: UiComponent) => {
|
||||||
|
const component = componentsLibrary.get(uiComponent.componentFigmaId);
|
||||||
|
|
||||||
|
if (!component) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children = [], fills, strokes, blendMode, ...rest } = component;
|
||||||
|
|
||||||
|
file.startComponent({
|
||||||
|
...rest,
|
||||||
|
fills: symbolFills(fills),
|
||||||
|
strokes: symbolStrokes(strokes),
|
||||||
|
blendMode: symbolBlendMode(blendMode),
|
||||||
|
id: uiComponent.componentId,
|
||||||
|
componentId: uiComponent.componentId,
|
||||||
|
mainInstancePage: uiComponent.mainInstancePage,
|
||||||
|
mainInstanceId: uiComponent.mainInstanceId,
|
||||||
|
componentRoot: true,
|
||||||
|
mainInstance: true,
|
||||||
|
componentFile: file.getId()
|
||||||
|
});
|
||||||
|
|
||||||
|
createItems(file, children);
|
||||||
|
|
||||||
|
file.finishComponent();
|
||||||
|
};
|
41
ui-src/parser/creators/createFile.ts
Normal file
41
ui-src/parser/creators/createFile.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { sleep } from '@plugin/utils/sleep';
|
||||||
|
|
||||||
|
import { sendMessage } from '@ui/context';
|
||||||
|
import { createFile as createPenpotFile } from '@ui/lib/penpot';
|
||||||
|
import { PenpotPage } from '@ui/lib/types/penpotPage';
|
||||||
|
import { idLibrary } from '@ui/parser';
|
||||||
|
import { createComponentsLibrary, createPage } from '@ui/parser/creators';
|
||||||
|
import { uiComponents } from '@ui/parser/libraries';
|
||||||
|
|
||||||
|
export const createFile = async (name: string, children: PenpotPage[]) => {
|
||||||
|
const file = createPenpotFile(name);
|
||||||
|
let pagesBuilt = 1;
|
||||||
|
|
||||||
|
uiComponents.init();
|
||||||
|
idLibrary.init();
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
type: 'PROGRESS_TOTAL_ITEMS',
|
||||||
|
data: children.length
|
||||||
|
});
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
type: 'PROGRESS_STEP',
|
||||||
|
data: 'building'
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const page of children) {
|
||||||
|
await createPage(file, page);
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
type: 'PROGRESS_PROCESSED_ITEMS',
|
||||||
|
data: pagesBuilt++
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
await createComponentsLibrary(file);
|
||||||
|
|
||||||
|
return file;
|
||||||
|
};
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { sendMessage } from '@ui/context';
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { PenpotNode } from '@ui/types';
|
import { PenpotNode } from '@ui/types';
|
||||||
|
|
||||||
|
@ -20,6 +21,11 @@ export const createItems = (file: PenpotFile, nodes: PenpotNode[]) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const createItem = (file: PenpotFile, node: PenpotNode) => {
|
const createItem = (file: PenpotFile, node: PenpotNode) => {
|
||||||
|
sendMessage({
|
||||||
|
type: 'PROGRESS_CURRENT_ITEM',
|
||||||
|
data: node.name
|
||||||
|
});
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'rect':
|
case 'rect':
|
||||||
return createRectangle(file, node);
|
return createRectangle(file, node);
|
||||||
|
|
|
@ -3,7 +3,8 @@ export * from './createBool';
|
||||||
export * from './createCircle';
|
export * from './createCircle';
|
||||||
export * from './createComponent';
|
export * from './createComponent';
|
||||||
export * from './createComponentInstance';
|
export * from './createComponentInstance';
|
||||||
export * from './createComponentLibrary';
|
export * from './createComponentsLibrary';
|
||||||
|
export * from './createFile';
|
||||||
export * from './createGroup';
|
export * from './createGroup';
|
||||||
export * from './createItems';
|
export * from './createItems';
|
||||||
export * from './createPage';
|
export * from './createPage';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Uuid } from '@ui/lib/types/utils/uuid';
|
import { Uuid } from '@ui/lib/types/utils/uuid';
|
||||||
|
|
||||||
type UiComponent = {
|
export type UiComponent = {
|
||||||
componentId: Uuid;
|
componentId: Uuid;
|
||||||
mainInstancePage?: Uuid;
|
mainInstancePage?: Uuid;
|
||||||
mainInstanceId: Uuid;
|
mainInstanceId: Uuid;
|
||||||
|
|
|
@ -3,27 +3,29 @@ import { componentsLibrary } from '@plugin/ComponentLibrary';
|
||||||
import { sleep } from '@plugin/utils/sleep';
|
import { sleep } from '@plugin/utils/sleep';
|
||||||
|
|
||||||
import { sendMessage } from '@ui/context';
|
import { sendMessage } from '@ui/context';
|
||||||
import { createFile } from '@ui/lib/penpot';
|
import { createFile } from '@ui/parser/creators';
|
||||||
import { createComponentLibrary, createPage } from '@ui/parser/creators';
|
import { uiImages } from '@ui/parser/libraries';
|
||||||
import { uiComponents, uiImages } from '@ui/parser/libraries';
|
|
||||||
import { PenpotDocument } from '@ui/types';
|
import { PenpotDocument } from '@ui/types';
|
||||||
|
|
||||||
import { idLibrary, parseImage } from '.';
|
import { parseImage } from '.';
|
||||||
|
|
||||||
const optimizeImages = async (images: Record<string, Uint8Array>) => {
|
const optimizeImages = async (images: Record<string, Uint8Array>) => {
|
||||||
const imagesToOptimize = Object.entries(images);
|
const imagesToOptimize = Object.entries(images);
|
||||||
let imagesOptimized = 1;
|
|
||||||
|
|
||||||
sendMessage({
|
if (imagesToOptimize.length === 0) return;
|
||||||
type: 'PROGRESS_STEP',
|
|
||||||
data: 'optimization'
|
let imagesOptimized = 1;
|
||||||
});
|
|
||||||
|
|
||||||
sendMessage({
|
sendMessage({
|
||||||
type: 'PROGRESS_TOTAL_ITEMS',
|
type: 'PROGRESS_TOTAL_ITEMS',
|
||||||
data: imagesToOptimize.length
|
data: imagesToOptimize.length
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sendMessage({
|
||||||
|
type: 'PROGRESS_STEP',
|
||||||
|
data: 'optimization'
|
||||||
|
});
|
||||||
|
|
||||||
for (const [key, bytes] of imagesToOptimize) {
|
for (const [key, bytes] of imagesToOptimize) {
|
||||||
if (bytes) {
|
if (bytes) {
|
||||||
uiImages.register(key, await parseImage(bytes));
|
uiImages.register(key, await parseImage(bytes));
|
||||||
|
@ -43,23 +45,5 @@ export const parse = async ({ name, children = [], components, images }: PenpotD
|
||||||
|
|
||||||
await optimizeImages(images);
|
await optimizeImages(images);
|
||||||
|
|
||||||
sendMessage({
|
return createFile(name, children);
|
||||||
type: 'PROGRESS_STEP',
|
|
||||||
data: 'downloading'
|
|
||||||
});
|
|
||||||
|
|
||||||
await sleep(20);
|
|
||||||
|
|
||||||
uiComponents.init();
|
|
||||||
idLibrary.init();
|
|
||||||
|
|
||||||
const file = createFile(name);
|
|
||||||
|
|
||||||
for (const page of children) {
|
|
||||||
await createPage(file, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
createComponentLibrary(file);
|
|
||||||
|
|
||||||
return file;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Children } from '@ui/lib/types/utils/children';
|
||||||
export type ComponentRoot = {
|
export type ComponentRoot = {
|
||||||
figmaId: string;
|
figmaId: string;
|
||||||
type: 'component';
|
type: 'component';
|
||||||
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ComponentTextPropertyOverride = {
|
export type ComponentTextPropertyOverride = {
|
||||||
|
|
Loading…
Reference in a new issue