diff --git a/.changeset/early-lamps-report.md b/.changeset/early-lamps-report.md new file mode 100644 index 00000000..3648c5bd --- /dev/null +++ b/.changeset/early-lamps-report.md @@ -0,0 +1,5 @@ +--- +"penpot-exporter": minor +--- + +Remote components processing diff --git a/plugin-src/RemoteComponentLibrary.ts b/plugin-src/RemoteComponentLibrary.ts new file mode 100644 index 00000000..69a0ca78 --- /dev/null +++ b/plugin-src/RemoteComponentLibrary.ts @@ -0,0 +1,30 @@ +class RemoteComponentsLibrary { + private components: Record = {}; + private queue: string[] = []; + + public register(id: string, component: ComponentNode | ComponentSetNode) { + if (!Object.prototype.hasOwnProperty.call(this.components, id)) { + this.queue.push(id); + } + + this.components[id] = component; + } + + public get(id: string): ComponentNode | ComponentSetNode | undefined { + return this.components[id]; + } + + public next(): ComponentNode | ComponentSetNode { + const lastKey = this.queue.pop(); + + if (!lastKey) throw new Error('No components to pop'); + + return this.components[lastKey]; + } + + public remaining(): number { + return this.queue.length; + } +} + +export const remoteComponentLibrary = new RemoteComponentsLibrary(); diff --git a/plugin-src/transformers/transformComponentNode.ts b/plugin-src/transformers/transformComponentNode.ts index fd3ac2ba..8063cc54 100644 --- a/plugin-src/transformers/transformComponentNode.ts +++ b/plugin-src/transformers/transformComponentNode.ts @@ -23,6 +23,7 @@ export const transformComponentNode = async ( type: 'component', name: node.name, path: node.parent?.type === 'COMPONENT_SET' ? node.parent.name : '', + showContent: !node.clipsContent, ...transformFigmaIds(node), ...transformFills(node), ...transformEffects(node), diff --git a/plugin-src/transformers/transformDocumentNode.ts b/plugin-src/transformers/transformDocumentNode.ts index 19c1ad89..8371f2d6 100644 --- a/plugin-src/transformers/transformDocumentNode.ts +++ b/plugin-src/transformers/transformDocumentNode.ts @@ -1,5 +1,7 @@ import { componentsLibrary } from '@plugin/ComponentLibrary'; import { imagesLibrary } from '@plugin/ImageLibrary'; +import { remoteComponentLibrary } from '@plugin/RemoteComponentLibrary'; +import { translateRemoteChildren } from '@plugin/translators'; import { sleep } from '@plugin/utils'; import { PenpotDocument } from '@ui/types'; @@ -28,6 +30,13 @@ export const transformDocumentNode = async (node: DocumentNode): Promise 0) { + children.push({ + name: 'External Components', + children: await translateRemoteChildren() + }); + } + const images: Record = {}; for (const [key, image] of Object.entries(imagesLibrary.all())) { diff --git a/plugin-src/transformers/transformInstanceNode.ts b/plugin-src/transformers/transformInstanceNode.ts index e761db84..6006eb9b 100644 --- a/plugin-src/transformers/transformInstanceNode.ts +++ b/plugin-src/transformers/transformInstanceNode.ts @@ -1,3 +1,4 @@ +import { remoteComponentLibrary } from '@plugin/RemoteComponentLibrary'; import { transformBlend, transformChildren, @@ -24,11 +25,16 @@ export const transformInstanceNode = async ( return; } + if (isExternalComponent(mainComponent)) { + await registerExternalComponents(mainComponent); + } + return { type: 'instance', name: node.name, mainComponentFigmaId: mainComponent.id, isComponentRoot: isComponentRoot(node), + showContent: !node.clipsContent, ...transformFigmaIds(node), ...transformFills(node), ...transformEffects(node), @@ -42,19 +48,39 @@ export const transformInstanceNode = async ( }; }; +const registerExternalComponents = async (mainComponent: ComponentNode): Promise => { + let component: ComponentSetNode | ComponentNode = mainComponent; + + if (component.parent?.type === 'COMPONENT_SET') { + component = component.parent; + } + + if (remoteComponentLibrary.get(component.id) !== undefined) { + return; + } + + remoteComponentLibrary.register(component.id, component); +}; + +const isExternalComponent = (mainComponent: ComponentNode): boolean => { + return ( + mainComponent.remote || + (mainComponent.parent?.type === 'COMPONENT_SET' && mainComponent.parent.remote) + ); +}; + /** * We do not want to process component instances in the following scenarios: * - * 1. If the component comes from an external design system. - * 2. If the component does not have a parent. (it's been removed) - * 3. Main component can be in a ComponentSet (the same logic applies to the parent). + * 1. If the component does not have a parent. (it's been removed) + * 2. Main component can be in a ComponentSet (the same logic applies to the parent). */ const isUnprocessableComponent = (mainComponent: ComponentNode): boolean => { return ( - mainComponent.remote || - mainComponent.parent === null || + (mainComponent.parent === null && !mainComponent.remote) || (mainComponent.parent?.type === 'COMPONENT_SET' && - (mainComponent.parent.parent === null || mainComponent.parent.remote)) + mainComponent.parent.parent === null && + !mainComponent.parent.remote) ); }; diff --git a/plugin-src/translators/translateChildren.ts b/plugin-src/translators/translateChildren.ts index fdf32a04..b4c522cf 100644 --- a/plugin-src/translators/translateChildren.ts +++ b/plugin-src/translators/translateChildren.ts @@ -1,3 +1,4 @@ +import { remoteComponentLibrary } from '@plugin/RemoteComponentLibrary'; import { transformGroupNodeLike, transformSceneNode } from '@plugin/transformers'; import { transformMaskFigmaIds } from '@plugin/transformers/partials'; import { sleep } from '@plugin/utils'; @@ -50,3 +51,19 @@ export const translateChildren = async ( return transformedChildren; }; + +export const translateRemoteChildren = async (): Promise => { + const transformedChildren: PenpotNode[] = []; + + while (remoteComponentLibrary.remaining() > 0) { + const child = remoteComponentLibrary.next(); + + const penpotNode = await transformSceneNode(child); + + if (penpotNode) transformedChildren.push(penpotNode); + + await sleep(0); + } + + return transformedChildren; +}; diff --git a/ui-src/lib/types/shapes/componentShape.ts b/ui-src/lib/types/shapes/componentShape.ts index 6f7bef11..b0d3b7cc 100644 --- a/ui-src/lib/types/shapes/componentShape.ts +++ b/ui-src/lib/types/shapes/componentShape.ts @@ -19,6 +19,7 @@ export type ComponentAttributes = { type?: 'component'; name: string; path: string; + showContent?: boolean; mainInstanceId?: Uuid; mainInstancePage?: Uuid; }; diff --git a/ui-src/parser/creators/createComponent.ts b/ui-src/parser/creators/createComponent.ts index 4799e5fc..9ca6eadd 100644 --- a/ui-src/parser/creators/createComponent.ts +++ b/ui-src/parser/creators/createComponent.ts @@ -17,7 +17,6 @@ export const createComponent = (file: PenpotFile, { figmaId }: ComponentRoot) => const frameId = createArtboard(file, { ...component, - showContent: true, componentFile: file.getId(), componentId, componentRoot: true, diff --git a/ui-src/parser/creators/createComponentInstance.ts b/ui-src/parser/creators/createComponentInstance.ts index c93d081b..f580241a 100644 --- a/ui-src/parser/creators/createComponentInstance.ts +++ b/ui-src/parser/creators/createComponentInstance.ts @@ -34,7 +34,6 @@ export const createComponentInstance = ( createArtboard(file, { ...rest, - showContent: true, shapeRef: uiComponent.mainInstanceId, componentFile: file.getId(), componentRoot: isComponentRoot, diff --git a/ui-src/types/component.ts b/ui-src/types/component.ts index 3bce2a14..e28b5f69 100644 --- a/ui-src/types/component.ts +++ b/ui-src/types/component.ts @@ -16,5 +16,6 @@ export type ComponentInstance = ShapeGeomAttributes & figmaId?: string; figmaRelatedId?: string; isComponentRoot: boolean; + showContent?: boolean; type: 'instance'; };