mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2024-12-22 05:33:02 -05:00
Reworked remote components (#197)
* reworked remote components * changeset
This commit is contained in:
parent
6a5ec1b89e
commit
c5dd5d011e
10 changed files with 31 additions and 133 deletions
5
.changeset/seven-roses-sniff.md
Normal file
5
.changeset/seven-roses-sniff.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Rework remote components
|
|
@ -1,39 +0,0 @@
|
||||||
export class RemoteComponentsLibrary {
|
|
||||||
private components: Map<string, ComponentNode | ComponentSetNode> = new Map();
|
|
||||||
private queue: string[] = [];
|
|
||||||
|
|
||||||
public register(id: string, component: ComponentNode | ComponentSetNode) {
|
|
||||||
if (!this.components.has(id)) {
|
|
||||||
this.queue.push(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.components.set(id, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(id: string): ComponentNode | ComponentSetNode | undefined {
|
|
||||||
return this.components.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public has(id: string): boolean {
|
|
||||||
return this.components.has(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public next(): ComponentNode | ComponentSetNode {
|
|
||||||
const lastKey = this.queue.pop();
|
|
||||||
|
|
||||||
if (!lastKey) throw new Error('No components to pop');
|
|
||||||
|
|
||||||
const component = this.components.get(lastKey);
|
|
||||||
if (!component) throw new Error('Component not found');
|
|
||||||
|
|
||||||
return component;
|
|
||||||
}
|
|
||||||
|
|
||||||
public remaining(): number {
|
|
||||||
return this.queue.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public total(): number {
|
|
||||||
return this.components.size;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
|
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
|
||||||
|
|
||||||
import { RemoteComponentsLibrary } from './RemoteComponents';
|
|
||||||
|
|
||||||
export const textStyles: Map<string, TextStyle | undefined> = new Map();
|
export const textStyles: Map<string, TextStyle | undefined> = new Map();
|
||||||
export const paintStyles: Map<string, PaintStyle | undefined> = new Map();
|
export const paintStyles: Map<string, PaintStyle | undefined> = new Map();
|
||||||
export const overrides: Map<string, NodeChangeProperty[]> = new Map();
|
export const overrides: Map<string, NodeChangeProperty[]> = new Map();
|
||||||
export const images: Map<string, Image | null> = new Map();
|
export const images: Map<string, Image | null> = new Map();
|
||||||
export const components: Map<string, ComponentShape> = new Map();
|
export const components: Map<string, ComponentShape> = new Map();
|
||||||
export const remoteComponents = new RemoteComponentsLibrary();
|
|
||||||
|
|
|
@ -1,31 +1,10 @@
|
||||||
import { sleep } from '@common/sleep';
|
import { sleep } from '@common/sleep';
|
||||||
|
|
||||||
import { remoteComponents } from '@plugin/libraries';
|
|
||||||
import { transformPageNode } from '@plugin/transformers';
|
import { transformPageNode } from '@plugin/transformers';
|
||||||
import { translateRemoteChildren } from '@plugin/translators';
|
|
||||||
|
|
||||||
import { PenpotPage } from '@ui/lib/types/penpotPage';
|
import { PenpotPage } from '@ui/lib/types/penpotPage';
|
||||||
|
|
||||||
export const processPages = async (node: DocumentNode): Promise<PenpotPage[]> => {
|
export const processPages = async (node: DocumentNode): Promise<PenpotPage[]> => {
|
||||||
const children = await processLocalDocument(node);
|
|
||||||
const remoteComponents = await processRemoteComponents();
|
|
||||||
if (remoteComponents) {
|
|
||||||
children.push(remoteComponents);
|
|
||||||
}
|
|
||||||
|
|
||||||
return children;
|
|
||||||
};
|
|
||||||
|
|
||||||
const processRemoteComponents = async (): Promise<PenpotPage | undefined> => {
|
|
||||||
if (remoteComponents.remaining() > 0) {
|
|
||||||
return {
|
|
||||||
name: 'External Components',
|
|
||||||
children: await translateRemoteChildren()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const processLocalDocument = async (node: DocumentNode): Promise<PenpotPage[]> => {
|
|
||||||
const children = [];
|
const children = [];
|
||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { overrides, remoteComponents } from '@plugin/libraries';
|
import { overrides } from '@plugin/libraries';
|
||||||
import {
|
import {
|
||||||
transformAutoLayout,
|
transformAutoLayout,
|
||||||
transformBlend,
|
transformBlend,
|
||||||
|
@ -28,19 +28,15 @@ export const transformInstanceNode = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryComponent = getPrimaryComponent(mainComponent);
|
const primaryComponent = getPrimaryComponent(mainComponent);
|
||||||
if (isUnprocessableComponent(primaryComponent)) {
|
const isOrphan = isOrphanInstance(primaryComponent);
|
||||||
return;
|
let nodeOverrides = {};
|
||||||
}
|
if (!isOrphan) {
|
||||||
|
|
||||||
if (primaryComponent.remote) {
|
|
||||||
registerExternalComponents(primaryComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerTextVariableOverrides(node, primaryComponent);
|
registerTextVariableOverrides(node, primaryComponent);
|
||||||
|
|
||||||
if (node.overrides.length > 0) {
|
if (node.overrides.length > 0) {
|
||||||
node.overrides.forEach(override => overrides.set(override.id, override.overriddenFields));
|
node.overrides.forEach(override => overrides.set(override.id, override.overriddenFields));
|
||||||
}
|
}
|
||||||
|
nodeOverrides = transformOverrides(node);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'instance',
|
type: 'instance',
|
||||||
|
@ -48,6 +44,7 @@ export const transformInstanceNode = async (
|
||||||
mainComponentFigmaId: mainComponent.id,
|
mainComponentFigmaId: mainComponent.id,
|
||||||
isComponentRoot: isComponentRoot(node),
|
isComponentRoot: isComponentRoot(node),
|
||||||
showContent: !node.clipsContent,
|
showContent: !node.clipsContent,
|
||||||
|
isOrphan,
|
||||||
...transformFigmaIds(node),
|
...transformFigmaIds(node),
|
||||||
...transformFills(node),
|
...transformFills(node),
|
||||||
...transformEffects(node),
|
...transformEffects(node),
|
||||||
|
@ -62,7 +59,7 @@ export const transformInstanceNode = async (
|
||||||
...transformConstraints(node),
|
...transformConstraints(node),
|
||||||
...transformAutoLayout(node),
|
...transformAutoLayout(node),
|
||||||
...(await transformChildren(node)),
|
...(await transformChildren(node)),
|
||||||
...transformOverrides(node)
|
...nodeOverrides
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,14 +71,6 @@ const getPrimaryComponent = (mainComponent: ComponentNode): ComponentNode | Comp
|
||||||
return mainComponent;
|
return mainComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerExternalComponents = (primaryComponent: ComponentNode | ComponentSetNode): void => {
|
|
||||||
if (remoteComponents.has(primaryComponent.id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteComponents.register(primaryComponent.id, primaryComponent);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getComponentTextPropertyOverrides = (
|
const getComponentTextPropertyOverrides = (
|
||||||
node: InstanceNode,
|
node: InstanceNode,
|
||||||
primaryComponent: ComponentNode | ComponentSetNode
|
primaryComponent: ComponentNode | ComponentSetNode
|
||||||
|
@ -133,14 +122,8 @@ const registerTextVariableOverrides = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const isOrphanInstance = (primaryComponent: ComponentNode | ComponentSetNode): boolean => {
|
||||||
* We do not want to process component instances in the following scenarios:
|
return primaryComponent.parent === null || primaryComponent.remote;
|
||||||
*
|
|
||||||
* 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 = (primaryComponent: ComponentNode | ComponentSetNode): boolean => {
|
|
||||||
return primaryComponent.parent === null && !primaryComponent.remote;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isComponentRoot = (node: InstanceNode): boolean => {
|
const isComponentRoot = (node: InstanceNode): boolean => {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { sleep } from '@common/sleep';
|
import { sleep } from '@common/sleep';
|
||||||
|
|
||||||
import { remoteComponents } from '@plugin/libraries';
|
|
||||||
import { transformGroupNodeLike, transformSceneNode } from '@plugin/transformers';
|
import { transformGroupNodeLike, transformSceneNode } from '@plugin/transformers';
|
||||||
import { transformMaskFigmaIds } from '@plugin/transformers/partials';
|
import { transformMaskFigmaIds } from '@plugin/transformers/partials';
|
||||||
|
|
||||||
|
@ -62,35 +61,3 @@ export const translateChildren = async (children: readonly SceneNode[]): Promise
|
||||||
|
|
||||||
return transformedChildren;
|
return transformedChildren;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const translateRemoteChildren = async (): Promise<PenpotNode[]> => {
|
|
||||||
const transformedChildren: PenpotNode[] = [];
|
|
||||||
let currentRemote = 1;
|
|
||||||
|
|
||||||
figma.ui.postMessage({
|
|
||||||
type: 'PROGRESS_STEP',
|
|
||||||
data: 'remote'
|
|
||||||
});
|
|
||||||
|
|
||||||
while (remoteComponents.remaining() > 0) {
|
|
||||||
figma.ui.postMessage({
|
|
||||||
type: 'PROGRESS_TOTAL_ITEMS',
|
|
||||||
data: remoteComponents.total()
|
|
||||||
});
|
|
||||||
|
|
||||||
const child = remoteComponents.next();
|
|
||||||
|
|
||||||
const penpotNode = await transformSceneNode(child);
|
|
||||||
|
|
||||||
if (penpotNode) transformedChildren.push(penpotNode);
|
|
||||||
|
|
||||||
figma.ui.postMessage({
|
|
||||||
type: 'PROGRESS_PROCESSED_ITEMS',
|
|
||||||
data: currentRemote++
|
|
||||||
});
|
|
||||||
|
|
||||||
await sleep(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return transformedChildren;
|
|
||||||
};
|
|
||||||
|
|
|
@ -15,10 +15,6 @@ const stepMessages: Record<Steps, Messages> = {
|
||||||
total: 'pages processed 💪',
|
total: 'pages processed 💪',
|
||||||
current: 'Currently processing layer'
|
current: 'Currently processing layer'
|
||||||
},
|
},
|
||||||
remote: {
|
|
||||||
total: 'remote components processed 📦',
|
|
||||||
current: 'Currently processing layer'
|
|
||||||
},
|
|
||||||
images: {
|
images: {
|
||||||
total: 'images downloaded 📸'
|
total: 'images downloaded 📸'
|
||||||
},
|
},
|
||||||
|
@ -74,7 +70,6 @@ const StepProgress = (): JSX.Element | null => {
|
||||||
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 'processing':
|
case 'processing':
|
||||||
case 'remote':
|
|
||||||
case 'images':
|
case 'images':
|
||||||
case 'optimization':
|
case 'optimization':
|
||||||
case 'building':
|
case 'building':
|
||||||
|
|
|
@ -21,7 +21,6 @@ export type UseFigmaHook = {
|
||||||
|
|
||||||
export type Steps =
|
export type Steps =
|
||||||
| 'processing'
|
| 'processing'
|
||||||
| 'remote'
|
|
||||||
| 'images'
|
| 'images'
|
||||||
| 'optimization'
|
| 'optimization'
|
||||||
| 'building'
|
| 'building'
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
|
import { Uuid } from '@ui/lib/types/utils/uuid';
|
||||||
import { components, parseFigmaId } from '@ui/parser';
|
import { components, parseFigmaId } from '@ui/parser';
|
||||||
import { ComponentInstance } from '@ui/types';
|
import { ComponentInstance } from '@ui/types';
|
||||||
|
|
||||||
import { createArtboard } from '.';
|
import { createArtboard } from '.';
|
||||||
|
|
||||||
|
let remoteFileId: Uuid | undefined = undefined;
|
||||||
|
|
||||||
export const createComponentInstance = (
|
export const createComponentInstance = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
{
|
{
|
||||||
|
@ -23,7 +26,7 @@ export const createComponentInstance = (
|
||||||
}
|
}
|
||||||
|
|
||||||
shape.shapeRef = uiComponent.mainInstanceId;
|
shape.shapeRef = uiComponent.mainInstanceId;
|
||||||
shape.componentFile = file.getId();
|
shape.componentFile = shape.isOrphan ? getRemoteFileId(file) : file.getId();
|
||||||
shape.componentRoot = isComponentRoot;
|
shape.componentRoot = isComponentRoot;
|
||||||
shape.componentId = uiComponent.componentId;
|
shape.componentId = uiComponent.componentId;
|
||||||
|
|
||||||
|
@ -46,3 +49,11 @@ const createUiComponent = (file: PenpotFile, mainComponentFigmaId: string) => {
|
||||||
|
|
||||||
return uiComponent;
|
return uiComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRemoteFileId = (file: PenpotFile): Uuid => {
|
||||||
|
if (!remoteFileId) {
|
||||||
|
remoteFileId = file.newId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return remoteFileId;
|
||||||
|
};
|
||||||
|
|
|
@ -26,6 +26,7 @@ export type ComponentInstance = ShapeGeomAttributes &
|
||||||
figmaRelatedId?: string;
|
figmaRelatedId?: string;
|
||||||
isComponentRoot: boolean;
|
isComponentRoot: boolean;
|
||||||
showContent?: boolean;
|
showContent?: boolean;
|
||||||
|
isOrphan: boolean;
|
||||||
type: 'instance';
|
type: 'instance';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue