0
Fork 0
mirror of https://github.com/penpot/penpot-exporter-figma-plugin.git synced 2024-12-22 13:43:03 -05:00

Remote components (External design systems) (#140)

* remote components processing

* changeset

* fixes

* fixes

* fixes

* fixes

* fix everything

* revert for now

* fixes

* change delete nodes flag

---------

Co-authored-by: Jordi Sala Morales <jordism91@gmail.com>
This commit is contained in:
Alex Sánchez 2024-06-06 09:37:35 +02:00 committed by GitHub
parent 3094f05e98
commit be5ff3be8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 96 additions and 8 deletions

View file

@ -0,0 +1,5 @@
---
"penpot-exporter": minor
---
Remote components processing

View file

@ -0,0 +1,30 @@
class RemoteComponentsLibrary {
private components: Record<string, ComponentNode | ComponentSetNode> = {};
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();

View file

@ -23,6 +23,7 @@ export const transformComponentNode = async (
type: 'component', type: 'component',
name: node.name, name: node.name,
path: node.parent?.type === 'COMPONENT_SET' ? node.parent.name : '', path: node.parent?.type === 'COMPONENT_SET' ? node.parent.name : '',
showContent: !node.clipsContent,
...transformFigmaIds(node), ...transformFigmaIds(node),
...transformFills(node), ...transformFills(node),
...transformEffects(node), ...transformEffects(node),

View file

@ -1,5 +1,7 @@
import { componentsLibrary } from '@plugin/ComponentLibrary'; import { componentsLibrary } from '@plugin/ComponentLibrary';
import { imagesLibrary } from '@plugin/ImageLibrary'; import { imagesLibrary } from '@plugin/ImageLibrary';
import { remoteComponentLibrary } from '@plugin/RemoteComponentLibrary';
import { translateRemoteChildren } from '@plugin/translators';
import { sleep } from '@plugin/utils'; import { sleep } from '@plugin/utils';
import { PenpotDocument } from '@ui/types'; import { PenpotDocument } from '@ui/types';
@ -28,6 +30,13 @@ export const transformDocumentNode = async (node: DocumentNode): Promise<PenpotD
await sleep(0); await sleep(0);
} }
if (remoteComponentLibrary.remaining() > 0) {
children.push({
name: 'External Components',
children: await translateRemoteChildren()
});
}
const images: Record<string, Uint8Array> = {}; const images: Record<string, Uint8Array> = {};
for (const [key, image] of Object.entries(imagesLibrary.all())) { for (const [key, image] of Object.entries(imagesLibrary.all())) {

View file

@ -1,3 +1,4 @@
import { remoteComponentLibrary } from '@plugin/RemoteComponentLibrary';
import { import {
transformBlend, transformBlend,
transformChildren, transformChildren,
@ -24,11 +25,16 @@ export const transformInstanceNode = async (
return; return;
} }
if (isExternalComponent(mainComponent)) {
await registerExternalComponents(mainComponent);
}
return { return {
type: 'instance', type: 'instance',
name: node.name, name: node.name,
mainComponentFigmaId: mainComponent.id, mainComponentFigmaId: mainComponent.id,
isComponentRoot: isComponentRoot(node), isComponentRoot: isComponentRoot(node),
showContent: !node.clipsContent,
...transformFigmaIds(node), ...transformFigmaIds(node),
...transformFills(node), ...transformFills(node),
...transformEffects(node), ...transformEffects(node),
@ -42,19 +48,39 @@ export const transformInstanceNode = async (
}; };
}; };
const registerExternalComponents = async (mainComponent: ComponentNode): Promise<void> => {
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: * We do not want to process component instances in the following scenarios:
* *
* 1. If the component comes from an external design system. * 1. If the component does not have a parent. (it's been removed)
* 2. 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).
* 3. Main component can be in a ComponentSet (the same logic applies to the parent).
*/ */
const isUnprocessableComponent = (mainComponent: ComponentNode): boolean => { const isUnprocessableComponent = (mainComponent: ComponentNode): boolean => {
return ( return (
mainComponent.remote || (mainComponent.parent === null && !mainComponent.remote) ||
mainComponent.parent === null ||
(mainComponent.parent?.type === 'COMPONENT_SET' && (mainComponent.parent?.type === 'COMPONENT_SET' &&
(mainComponent.parent.parent === null || mainComponent.parent.remote)) mainComponent.parent.parent === null &&
!mainComponent.parent.remote)
); );
}; };

View file

@ -1,3 +1,4 @@
import { remoteComponentLibrary } from '@plugin/RemoteComponentLibrary';
import { transformGroupNodeLike, transformSceneNode } from '@plugin/transformers'; import { transformGroupNodeLike, transformSceneNode } from '@plugin/transformers';
import { transformMaskFigmaIds } from '@plugin/transformers/partials'; import { transformMaskFigmaIds } from '@plugin/transformers/partials';
import { sleep } from '@plugin/utils'; import { sleep } from '@plugin/utils';
@ -50,3 +51,19 @@ export const translateChildren = async (
return transformedChildren; return transformedChildren;
}; };
export const translateRemoteChildren = async (): Promise<PenpotNode[]> => {
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;
};

View file

@ -19,6 +19,7 @@ export type ComponentAttributes = {
type?: 'component'; type?: 'component';
name: string; name: string;
path: string; path: string;
showContent?: boolean;
mainInstanceId?: Uuid; mainInstanceId?: Uuid;
mainInstancePage?: Uuid; mainInstancePage?: Uuid;
}; };

View file

@ -17,7 +17,6 @@ export const createComponent = (file: PenpotFile, { figmaId }: ComponentRoot) =>
const frameId = createArtboard(file, { const frameId = createArtboard(file, {
...component, ...component,
showContent: true,
componentFile: file.getId(), componentFile: file.getId(),
componentId, componentId,
componentRoot: true, componentRoot: true,

View file

@ -34,7 +34,6 @@ export const createComponentInstance = (
createArtboard(file, { createArtboard(file, {
...rest, ...rest,
showContent: true,
shapeRef: uiComponent.mainInstanceId, shapeRef: uiComponent.mainInstanceId,
componentFile: file.getId(), componentFile: file.getId(),
componentRoot: isComponentRoot, componentRoot: isComponentRoot,

View file

@ -16,5 +16,6 @@ export type ComponentInstance = ShapeGeomAttributes &
figmaId?: string; figmaId?: string;
figmaRelatedId?: string; figmaRelatedId?: string;
isComponentRoot: boolean; isComponentRoot: boolean;
showContent?: boolean;
type: 'instance'; type: 'instance';
}; };