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:
parent
3094f05e98
commit
be5ff3be8e
10 changed files with 96 additions and 8 deletions
5
.changeset/early-lamps-report.md
Normal file
5
.changeset/early-lamps-report.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Remote components processing
|
30
plugin-src/RemoteComponentLibrary.ts
Normal file
30
plugin-src/RemoteComponentLibrary.ts
Normal 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();
|
|
@ -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),
|
||||||
|
|
|
@ -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())) {
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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';
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue