From 8697902e086174170eeeb24a075a783eeb308ccc Mon Sep 17 00:00:00 2001 From: Jordi Sala Morales Date: Tue, 18 Jun 2024 09:50:38 +0200 Subject: [PATCH] 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 --- .changeset/tidy-pandas-enjoy.md | 5 ++ .../transformers/transformComponentNode.ts | 3 +- .../transformers/transformDocumentNode.ts | 13 ++-- ui-src/components/ExporterProgress.tsx | 14 ++++- ui-src/context/useFigma.ts | 17 ++++- ui-src/lib/types/shapes/shape.ts | 5 +- .../parser/creators/createComponentLibrary.ts | 36 ----------- .../creators/createComponentsLibrary.ts | 63 +++++++++++++++++++ ui-src/parser/creators/createFile.ts | 41 ++++++++++++ ui-src/parser/creators/createItems.ts | 6 ++ ui-src/parser/creators/index.ts | 3 +- ui-src/parser/libraries/UiComponents.ts | 2 +- ui-src/parser/parse.ts | 40 ++++-------- ui-src/types/component.ts | 1 + 14 files changed, 169 insertions(+), 80 deletions(-) create mode 100644 .changeset/tidy-pandas-enjoy.md delete mode 100644 ui-src/parser/creators/createComponentLibrary.ts create mode 100644 ui-src/parser/creators/createComponentsLibrary.ts create mode 100644 ui-src/parser/creators/createFile.ts diff --git a/.changeset/tidy-pandas-enjoy.md b/.changeset/tidy-pandas-enjoy.md new file mode 100644 index 0000000..2079aaa --- /dev/null +++ b/.changeset/tidy-pandas-enjoy.md @@ -0,0 +1,5 @@ +--- +"penpot-exporter": minor +--- + +Track better the progress on file creation diff --git a/plugin-src/transformers/transformComponentNode.ts b/plugin-src/transformers/transformComponentNode.ts index c19ed12..eba890a 100644 --- a/plugin-src/transformers/transformComponentNode.ts +++ b/plugin-src/transformers/transformComponentNode.ts @@ -45,6 +45,7 @@ export const transformComponentNode = async ( return { figmaId: node.id, - type: 'component' + type: 'component', + name: node.name }; }; diff --git a/plugin-src/transformers/transformDocumentNode.ts b/plugin-src/transformers/transformDocumentNode.ts index 236d356..9b4811c 100644 --- a/plugin-src/transformers/transformDocumentNode.ts +++ b/plugin-src/transformers/transformDocumentNode.ts @@ -12,18 +12,21 @@ import { transformPageNode } from '.'; const downloadImages = async (): Promise> => { const imageToDownload = Object.entries(imagesLibrary.all()); const images: Record = {}; - let currentImage = 1; - figma.ui.postMessage({ - type: 'PROGRESS_STEP', - data: 'images' - }); + if (imageToDownload.length === 0) return images; + + let currentImage = 1; figma.ui.postMessage({ type: 'PROGRESS_TOTAL_ITEMS', data: imageToDownload.length }); + figma.ui.postMessage({ + type: 'PROGRESS_STEP', + data: 'images' + }); + for (const [key, image] of imageToDownload) { const bytes = await image?.getBytesAsync(); diff --git a/ui-src/components/ExporterProgress.tsx b/ui-src/components/ExporterProgress.tsx index 5781237..a142b91 100644 --- a/ui-src/components/ExporterProgress.tsx +++ b/ui-src/components/ExporterProgress.tsx @@ -25,7 +25,15 @@ const stepMessages: Record = { optimization: { 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 🚀', current: 'Please wait, this process might take a while...' } @@ -51,6 +59,8 @@ const StepProgress = (): JSX.Element | null => { case 'remote': case 'images': case 'optimization': + case 'building': + case 'components': return ( <> {processedItems} of {totalItems} {stepMessages[step].total} @@ -64,7 +74,7 @@ const StepProgress = (): JSX.Element | null => { ) : undefined} ); - case 'downloading': + case 'exporting': return ( <> {stepMessages[step].total} diff --git a/ui-src/context/useFigma.ts b/ui-src/context/useFigma.ts index c71b0a3..f9c6664 100644 --- a/ui-src/context/useFigma.ts +++ b/ui-src/context/useFigma.ts @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'; import { FormValues } from '@ui/components/ExportForm'; import { parse } from '@ui/parser'; -import { MessageData } from '.'; +import { MessageData, sendMessage } from '.'; export type UseFigmaHook = { missingFonts: string[] | undefined; @@ -19,7 +19,14 @@ export type UseFigmaHook = { 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 => { const [missingFonts, setMissingFonts] = useState(); @@ -44,6 +51,12 @@ export const useFigma = (): UseFigmaHook => { switch (pluginMessage.type) { case 'PENPOT_DOCUMENT': { const file = await parse(pluginMessage.data); + + sendMessage({ + type: 'PROGRESS_STEP', + data: 'exporting' + }); + const blob = await file.export(); download(blob, `${pluginMessage.data.name}.zip`); diff --git a/ui-src/lib/types/shapes/shape.ts b/ui-src/lib/types/shapes/shape.ts index 2f3d7fe..30d84ca 100644 --- a/ui-src/lib/types/shapes/shape.ts +++ b/ui-src/lib/types/shapes/shape.ts @@ -16,7 +16,6 @@ export type ShapeBaseAttributes = { id?: Uuid; figmaId?: string; // @TODO: move to any other place figmaRelatedId?: string; // @TODO: move to any other place - name?: string; type?: | 'frame' | 'group' @@ -29,8 +28,6 @@ export type ShapeBaseAttributes = { | 'image' | 'component' | 'instance'; - selrect?: Selrect; - points?: Point[]; transform?: Matrix; transformInverse?: Matrix; parentId?: Uuid; @@ -39,7 +36,7 @@ export type ShapeBaseAttributes = { }; export type ShapeAttributes = { - name?: string; + name: string; componentId?: string; componentFile?: string; componentRoot?: boolean; diff --git a/ui-src/parser/creators/createComponentLibrary.ts b/ui-src/parser/creators/createComponentLibrary.ts deleted file mode 100644 index 4bdf69c..0000000 --- a/ui-src/parser/creators/createComponentLibrary.ts +++ /dev/null @@ -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(); - }); -}; diff --git a/ui-src/parser/creators/createComponentsLibrary.ts b/ui-src/parser/creators/createComponentsLibrary.ts new file mode 100644 index 0000000..fae3fe8 --- /dev/null +++ b/ui-src/parser/creators/createComponentsLibrary.ts @@ -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(); +}; diff --git a/ui-src/parser/creators/createFile.ts b/ui-src/parser/creators/createFile.ts new file mode 100644 index 0000000..f391f87 --- /dev/null +++ b/ui-src/parser/creators/createFile.ts @@ -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; +}; diff --git a/ui-src/parser/creators/createItems.ts b/ui-src/parser/creators/createItems.ts index 874eccc..d861892 100644 --- a/ui-src/parser/creators/createItems.ts +++ b/ui-src/parser/creators/createItems.ts @@ -1,3 +1,4 @@ +import { sendMessage } from '@ui/context'; import { PenpotFile } from '@ui/lib/types/penpotFile'; import { PenpotNode } from '@ui/types'; @@ -20,6 +21,11 @@ export const createItems = (file: PenpotFile, nodes: PenpotNode[]) => { }; const createItem = (file: PenpotFile, node: PenpotNode) => { + sendMessage({ + type: 'PROGRESS_CURRENT_ITEM', + data: node.name + }); + switch (node.type) { case 'rect': return createRectangle(file, node); diff --git a/ui-src/parser/creators/index.ts b/ui-src/parser/creators/index.ts index bdcd5a3..87a4c49 100644 --- a/ui-src/parser/creators/index.ts +++ b/ui-src/parser/creators/index.ts @@ -3,7 +3,8 @@ export * from './createBool'; export * from './createCircle'; export * from './createComponent'; export * from './createComponentInstance'; -export * from './createComponentLibrary'; +export * from './createComponentsLibrary'; +export * from './createFile'; export * from './createGroup'; export * from './createItems'; export * from './createPage'; diff --git a/ui-src/parser/libraries/UiComponents.ts b/ui-src/parser/libraries/UiComponents.ts index 5e0bbe1..cb95a46 100644 --- a/ui-src/parser/libraries/UiComponents.ts +++ b/ui-src/parser/libraries/UiComponents.ts @@ -1,6 +1,6 @@ import { Uuid } from '@ui/lib/types/utils/uuid'; -type UiComponent = { +export type UiComponent = { componentId: Uuid; mainInstancePage?: Uuid; mainInstanceId: Uuid; diff --git a/ui-src/parser/parse.ts b/ui-src/parser/parse.ts index e0335ad..0376ffc 100644 --- a/ui-src/parser/parse.ts +++ b/ui-src/parser/parse.ts @@ -3,27 +3,29 @@ import { componentsLibrary } from '@plugin/ComponentLibrary'; import { sleep } from '@plugin/utils/sleep'; import { sendMessage } from '@ui/context'; -import { createFile } from '@ui/lib/penpot'; -import { createComponentLibrary, createPage } from '@ui/parser/creators'; -import { uiComponents, uiImages } from '@ui/parser/libraries'; +import { createFile } from '@ui/parser/creators'; +import { uiImages } from '@ui/parser/libraries'; import { PenpotDocument } from '@ui/types'; -import { idLibrary, parseImage } from '.'; +import { parseImage } from '.'; const optimizeImages = async (images: Record) => { const imagesToOptimize = Object.entries(images); - let imagesOptimized = 1; - sendMessage({ - type: 'PROGRESS_STEP', - data: 'optimization' - }); + if (imagesToOptimize.length === 0) return; + + let imagesOptimized = 1; sendMessage({ type: 'PROGRESS_TOTAL_ITEMS', data: imagesToOptimize.length }); + sendMessage({ + type: 'PROGRESS_STEP', + data: 'optimization' + }); + for (const [key, bytes] of imagesToOptimize) { if (bytes) { uiImages.register(key, await parseImage(bytes)); @@ -43,23 +45,5 @@ export const parse = async ({ name, children = [], components, images }: PenpotD await optimizeImages(images); - sendMessage({ - 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; + return createFile(name, children); }; diff --git a/ui-src/types/component.ts b/ui-src/types/component.ts index 189ace0..ed1f50a 100644 --- a/ui-src/types/component.ts +++ b/ui-src/types/component.ts @@ -5,6 +5,7 @@ import { Children } from '@ui/lib/types/utils/children'; export type ComponentRoot = { figmaId: string; type: 'component'; + name: string; }; export type ComponentTextPropertyOverride = {