0
Fork 0
mirror of https://github.com/penpot/penpot-exporter-figma-plugin.git synced 2024-12-22 05:33:02 -05:00
This commit is contained in:
Alex Sánchez 2024-06-14 08:15:49 +02:00
parent 18d1a5dab7
commit 9f5f05d2a9
No known key found for this signature in database
GPG key ID: 68A95170EEB87E16
17 changed files with 152 additions and 86 deletions

View file

@ -6,16 +6,29 @@ const nodeActsAsMask = (node: SceneNode): boolean => {
return 'isMask' in node && node.isMask;
};
const nodeIsLayoutWithItemReverseZIndex = (node: ChildrenMixin): boolean => {
return !!('itemReverseZIndex' in node && node.itemReverseZIndex);
};
export const transformChildren = async (
node: ChildrenMixin,
baseX: number = 0,
baseY: number = 0
baseY: number = 0,
zIndex: number = 0
): Promise<Children> => {
const maskIndex = node.children.findIndex(nodeActsAsMask);
const containsMask = maskIndex !== -1;
const itemReverseZIndex = nodeIsLayoutWithItemReverseZIndex(node);
return {
children: containsMask
? await translateMaskChildren(node.children, maskIndex, baseX, baseY)
: await translateChildren(node.children, baseX, baseY)
? await translateMaskChildren(
node.children,
maskIndex,
baseX,
baseY,
zIndex,
itemReverseZIndex
)
: await translateChildren(node.children, baseX, baseY, zIndex, itemReverseZIndex)
};
};

View file

@ -30,19 +30,23 @@ export const transformAutoLayout = (node: BaseFrameMixin): LayoutAttributes => {
};
};
export const transformLayoutSizing = (
export const transformLayoutAttributes = (
node: LayoutMixin
): Pick<LayoutChildAttributes, 'layoutItemH-Sizing' | 'layoutItemV-Sizing'> => {
): Pick<
LayoutChildAttributes,
'layoutItemH-Sizing' | 'layoutItemV-Sizing' | 'layoutItemAbsolute'
> => {
return {
'layoutItemH-Sizing': translateLayoutSizing(node.layoutSizingHorizontal),
'layoutItemV-Sizing': translateLayoutSizing(node.layoutSizingVertical)
'layoutItemV-Sizing': translateLayoutSizing(node.layoutSizingVertical),
'layoutItemAbsolute': node.layoutPositioning === 'ABSOLUTE'
};
};
export const transformAutoLayoutPosition = (
node: AutoLayoutChildrenMixin
): Pick<LayoutChildAttributes, 'layoutItemAbsolute'> => {
export const transformLayoutItemZIndex = (
zIndex: number
): Pick<LayoutChildAttributes, 'layoutItemZ-Index'> => {
return {
layoutItemAbsolute: node.layoutPositioning === 'ABSOLUTE'
'layoutItemZ-Index': zIndex
};
};

View file

@ -1,11 +1,11 @@
import { parseSVG } from 'svg-path-parser';
import {
transformAutoLayoutPosition,
transformBlend,
transformDimensionAndPositionFromVectorPath,
transformEffects,
transformLayoutSizing,
transformLayoutAttributes,
transformLayoutItemZIndex,
transformProportion,
transformSceneNode,
transformStrokesFromVector
@ -35,7 +35,8 @@ export const transformVectorPathsAsContent = (
export const transformVectorPaths = (
node: VectorNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): PathShape[] => {
const pathShapes = node.vectorPaths
.filter((vectorPath, index) => {
@ -45,7 +46,14 @@ export const transformVectorPaths = (
);
})
.map((vectorPath, index) =>
transformVectorPath(node, vectorPath, (node.vectorNetwork.regions ?? [])[index], baseX, baseY)
transformVectorPath(
node,
vectorPath,
(node.vectorNetwork.regions ?? [])[index],
baseX,
baseY,
zIndex
)
);
const geometryShapes = node.fillGeometry
@ -55,7 +63,7 @@ export const transformVectorPaths = (
vectorPath => normalizePath(vectorPath.data) === normalizePath(geometry.data)
)
)
.map(geometry => transformVectorPath(node, geometry, undefined, baseX, baseY));
.map(geometry => transformVectorPath(node, geometry, undefined, baseX, baseY, zIndex));
return [...geometryShapes, ...pathShapes];
};
@ -92,7 +100,8 @@ const transformVectorPath = (
vectorPath: VectorPath,
vectorRegion: VectorRegion | undefined,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): PathShape => {
const normalizedPaths = parseSVG(vectorPath.data);
@ -107,13 +116,13 @@ const transformVectorPath = (
},
constraintsH: 'scale',
constraintsV: 'scale',
...transformLayoutItemZIndex(zIndex),
...transformStrokesFromVector(node, normalizedPaths, vectorRegion),
...transformEffects(node),
...transformDimensionAndPositionFromVectorPath(vectorPath, baseX, baseY),
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),
...transformLayoutSizing(node),
...transformAutoLayoutPosition(node)
...transformLayoutAttributes(node)
};
};

View file

@ -1,12 +1,12 @@
import {
transformAutoLayoutPosition,
transformBlend,
transformChildren,
transformDimensionAndPosition,
transformEffects,
transformFigmaIds,
transformFills,
transformLayoutSizing,
transformLayoutAttributes,
transformLayoutItemZIndex,
transformProportion,
transformSceneNode,
transformStrokes
@ -18,12 +18,14 @@ import { BoolShape } from '@ui/lib/types/shapes/boolShape';
export const transformBooleanNode = async (
node: BooleanOperationNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): Promise<BoolShape> => {
return {
type: 'bool',
name: node.name,
boolType: translateBoolType(node.booleanOperation),
...transformLayoutItemZIndex(zIndex),
...transformFigmaIds(node),
...(await transformChildren(node, baseX, baseY)),
...transformFills(node),
@ -33,7 +35,6 @@ export const transformBooleanNode = async (
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),
...transformLayoutSizing(node),
...transformAutoLayoutPosition(node)
...transformLayoutAttributes(node)
};
};

View file

@ -1,7 +1,6 @@
import { componentsLibrary } from '@plugin/ComponentLibrary';
import {
transformAutoLayout,
transformAutoLayoutPosition,
transformBlend,
transformChildren,
transformConstraints,
@ -10,7 +9,8 @@ import {
transformEffects,
transformFigmaIds,
transformFills,
transformLayoutSizing,
transformLayoutAttributes,
transformLayoutItemZIndex,
transformProportion,
transformSceneNode,
transformStrokes
@ -21,13 +21,15 @@ import { ComponentRoot } from '@ui/types';
export const transformComponentNode = async (
node: ComponentNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): Promise<ComponentRoot> => {
componentsLibrary.register(node.id, {
type: 'component',
name: node.name,
path: node.parent?.type === 'COMPONENT_SET' ? node.parent.name : '',
showContent: !node.clipsContent,
...transformLayoutItemZIndex(zIndex),
...transformFigmaIds(node),
...transformFills(node),
...transformEffects(node),
@ -35,10 +37,9 @@ export const transformComponentNode = async (
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),
...transformLayoutSizing(node),
...transformAutoLayoutPosition(node),
...transformLayoutAttributes(node),
...transformCornerRadius(node),
...(await transformChildren(node, baseX + node.x, baseY + node.y)),
...(await transformChildren(node, baseX + node.x, baseY + node.y, zIndex)),
...transformDimensionAndPosition(node, baseX, baseY),
...transformConstraints(node),
...transformAutoLayout(node)

View file

@ -1,12 +1,12 @@
import {
transformAutoLayoutPosition,
transformBlend,
transformConstraints,
transformDimension,
transformEffects,
transformFigmaIds,
transformFills,
transformLayoutSizing,
transformLayoutAttributes,
transformLayoutItemZIndex,
transformProportion,
transformRotationAndPosition,
transformSceneNode,
@ -18,11 +18,13 @@ import { CircleShape } from '@ui/lib/types/shapes/circleShape';
export const transformEllipseNode = (
node: EllipseNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): CircleShape => {
return {
type: 'circle',
name: node.name,
...transformLayoutItemZIndex(zIndex),
...transformFigmaIds(node),
...transformFills(node),
...transformEffects(node),
@ -32,8 +34,7 @@ export const transformEllipseNode = (
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),
...transformLayoutSizing(node),
...transformAutoLayoutPosition(node),
...transformLayoutAttributes(node),
...transformConstraints(node)
};
};

View file

@ -1,6 +1,5 @@
import {
transformAutoLayout,
transformAutoLayoutPosition,
transformBlend,
transformChildren,
transformConstraints,
@ -9,7 +8,8 @@ import {
transformEffects,
transformFigmaIds,
transformFills,
transformLayoutSizing,
transformLayoutAttributes,
transformLayoutItemZIndex,
transformProportion,
transformSceneNode,
transformStrokes
@ -24,7 +24,8 @@ const isSectionNode = (node: FrameNode | SectionNode | ComponentSetNode): node i
export const transformFrameNode = async (
node: FrameNode | SectionNode | ComponentSetNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): Promise<FrameShape> => {
let frameSpecificAttributes: Partial<FrameShape> = {};
@ -37,8 +38,7 @@ export const transformFrameNode = async (
// @see: https://forum.figma.com/t/add-a-blendmode-property-for-sectionnode/58560
...transformBlend(node),
...transformProportion(node),
...transformLayoutSizing(node),
...transformAutoLayoutPosition(node),
...transformLayoutAttributes(node),
...transformCornerRadius(node),
...transformEffects(node),
...transformConstraints(node),
@ -50,10 +50,11 @@ export const transformFrameNode = async (
type: 'frame',
name: node.name,
showContent: isSectionNode(node) ? true : !node.clipsContent,
...transformLayoutItemZIndex(zIndex),
...transformFigmaIds(node),
...transformFills(node),
...frameSpecificAttributes,
...(await transformChildren(node, baseX + node.x, baseY + node.y)),
...(await transformChildren(node, baseX + node.x, baseY + node.y, zIndex)),
...transformDimensionAndPosition(node, baseX, baseY),
...transformSceneNode(node)
};

View file

@ -3,6 +3,7 @@ import {
transformDimensionAndPosition,
transformEffects,
transformFigmaIds,
transformLayoutItemZIndex,
transformSceneNode
} from '@plugin/transformers/partials';
import { transformChildren } from '@plugin/transformers/partials';
@ -12,11 +13,12 @@ import { GroupShape } from '@ui/lib/types/shapes/groupShape';
export const transformGroupNode = async (
node: GroupNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): Promise<GroupShape> => {
return {
...transformFigmaIds(node),
...transformGroupNodeLike(node, baseX, baseY),
...transformGroupNodeLike(node, baseX, baseY, zIndex),
...transformEffects(node),
...transformBlend(node),
...(await transformChildren(node, baseX, baseY))
@ -26,11 +28,13 @@ export const transformGroupNode = async (
export const transformGroupNodeLike = (
node: BaseNodeMixin & DimensionAndPositionMixin & SceneNodeMixin,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): GroupShape => {
return {
type: 'group',
name: node.name,
...transformLayoutItemZIndex(zIndex),
...transformDimensionAndPosition(node, baseX, baseY),
...transformSceneNode(node)
};

View file

@ -1,7 +1,6 @@
import { remoteComponentLibrary } from '@plugin/RemoteComponentLibrary';
import {
transformAutoLayout,
transformAutoLayoutPosition,
transformBlend,
transformChildren,
transformConstraints,
@ -10,7 +9,8 @@ import {
transformEffects,
transformFigmaIds,
transformFills,
transformLayoutSizing,
transformLayoutAttributes,
transformLayoutItemZIndex,
transformProportion,
transformSceneNode,
transformStrokes
@ -21,7 +21,8 @@ import { ComponentInstance } from '@ui/types';
export const transformInstanceNode = async (
node: InstanceNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): Promise<ComponentInstance | undefined> => {
const mainComponent = await node.getMainComponentAsync();
@ -39,6 +40,7 @@ export const transformInstanceNode = async (
mainComponentFigmaId: mainComponent.id,
isComponentRoot: isComponentRoot(node),
showContent: !node.clipsContent,
...transformLayoutItemZIndex(zIndex),
...transformFigmaIds(node),
...transformFills(node),
...transformEffects(node),
@ -46,13 +48,12 @@ export const transformInstanceNode = async (
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),
...transformLayoutSizing(node),
...transformAutoLayoutPosition(node),
...transformLayoutAttributes(node),
...transformCornerRadius(node),
...transformDimensionAndPosition(node, baseX, baseY),
...transformConstraints(node),
...transformAutoLayout(node),
...(await transformChildren(node, baseX + node.x, baseY + node.y))
...(await transformChildren(node, baseX + node.x, baseY + node.y, zIndex))
};
};

View file

@ -1,12 +1,12 @@
import {
transformAutoLayoutPosition,
transformBlend,
transformConstraints,
transformDimensionAndPosition,
transformEffects,
transformFigmaIds,
transformFills,
transformLayoutSizing,
transformLayoutAttributes,
transformLayoutItemZIndex,
transformProportion,
transformSceneNode,
transformStrokes,
@ -22,11 +22,13 @@ const hasFillGeometry = (node: StarNode | LineNode | PolygonNode): boolean => {
export const transformPathNode = (
node: StarNode | LineNode | PolygonNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): PathShape => {
return {
type: 'path',
name: node.name,
...transformLayoutItemZIndex(zIndex),
...transformFigmaIds(node),
...(hasFillGeometry(node) ? transformFills(node) : []),
...transformStrokes(node),
@ -36,8 +38,7 @@ export const transformPathNode = (
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),
...transformLayoutSizing(node),
...transformAutoLayoutPosition(node),
...transformLayoutAttributes(node),
...transformConstraints(node)
};
};

View file

@ -1,5 +1,4 @@
import {
transformAutoLayoutPosition,
transformBlend,
transformConstraints,
transformCornerRadius,
@ -7,7 +6,8 @@ import {
transformEffects,
transformFigmaIds,
transformFills,
transformLayoutSizing,
transformLayoutAttributes,
transformLayoutItemZIndex,
transformProportion,
transformRotationAndPosition,
transformSceneNode,
@ -19,11 +19,13 @@ import { RectShape } from '@ui/lib/types/shapes/rectShape';
export const transformRectangleNode = (
node: RectangleNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): RectShape => {
return {
type: 'rect',
name: node.name,
...transformLayoutItemZIndex(zIndex),
...transformFigmaIds(node),
...transformFills(node),
...transformEffects(node),
@ -33,8 +35,7 @@ export const transformRectangleNode = (
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),
...transformLayoutSizing(node),
...transformAutoLayoutPosition(node),
...transformLayoutAttributes(node),
...transformCornerRadius(node),
...transformConstraints(node)
};

View file

@ -16,7 +16,8 @@ import {
export const transformSceneNode = async (
node: SceneNode,
baseX: number = 0,
baseY: number = 0
baseY: number = 0,
zIndex: number = 0
): Promise<PenpotNode | undefined> => {
let penpotNode: PenpotNode | undefined;
@ -27,38 +28,38 @@ export const transformSceneNode = async (
switch (node.type) {
case 'RECTANGLE':
penpotNode = transformRectangleNode(node, baseX, baseY);
penpotNode = transformRectangleNode(node, baseX, baseY, zIndex);
break;
case 'ELLIPSE':
penpotNode = transformEllipseNode(node, baseX, baseY);
penpotNode = transformEllipseNode(node, baseX, baseY, zIndex);
break;
case 'SECTION':
case 'FRAME':
case 'COMPONENT_SET':
penpotNode = await transformFrameNode(node, baseX, baseY);
penpotNode = await transformFrameNode(node, baseX, baseY, zIndex);
break;
case 'GROUP':
penpotNode = await transformGroupNode(node, baseX, baseY);
penpotNode = await transformGroupNode(node, baseX, baseY, zIndex);
break;
case 'TEXT':
penpotNode = transformTextNode(node, baseX, baseY);
penpotNode = transformTextNode(node, baseX, baseY, zIndex);
break;
case 'VECTOR':
penpotNode = transformVectorNode(node, baseX, baseY);
penpotNode = transformVectorNode(node, baseX, baseY, zIndex);
break;
case 'STAR':
case 'POLYGON':
case 'LINE':
penpotNode = transformPathNode(node, baseX, baseY);
penpotNode = transformPathNode(node, baseX, baseY, zIndex);
break;
case 'BOOLEAN_OPERATION':
penpotNode = await transformBooleanNode(node, baseX, baseY);
penpotNode = await transformBooleanNode(node, baseX, baseY, zIndex);
break;
case 'COMPONENT':
penpotNode = await transformComponentNode(node, baseX, baseY);
penpotNode = await transformComponentNode(node, baseX, baseY, zIndex);
break;
case 'INSTANCE':
penpotNode = await transformInstanceNode(node, baseX, baseY);
penpotNode = await transformInstanceNode(node, baseX, baseY, zIndex);
break;
}

View file

@ -1,11 +1,11 @@
import {
transformAutoLayoutPosition,
transformBlend,
transformConstraints,
transformDimensionAndPosition,
transformEffects,
transformFigmaIds,
transformLayoutSizing,
transformLayoutAttributes,
transformLayoutItemZIndex,
transformProportion,
transformSceneNode,
transformStrokes,
@ -14,10 +14,16 @@ import {
import { TextShape } from '@ui/lib/types/shapes/textShape';
export const transformTextNode = (node: TextNode, baseX: number, baseY: number): TextShape => {
export const transformTextNode = (
node: TextNode,
baseX: number,
baseY: number,
zIndex: number
): TextShape => {
return {
type: 'text',
name: node.name,
...transformLayoutItemZIndex(zIndex),
...transformFigmaIds(node),
...transformText(node),
...transformDimensionAndPosition(node, baseX, baseY),
@ -25,8 +31,7 @@ export const transformTextNode = (node: TextNode, baseX: number, baseY: number):
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),
...transformLayoutSizing(node),
...transformAutoLayoutPosition(node),
...transformLayoutAttributes(node),
...transformStrokes(node),
...transformConstraints(node)
};

View file

@ -1,6 +1,7 @@
import {
transformConstraints,
transformFigmaIds,
transformLayoutItemZIndex,
transformVectorPaths
} from '@plugin/transformers/partials';
@ -18,21 +19,23 @@ import { transformGroupNodeLike } from '.';
export const transformVectorNode = (
node: VectorNode,
baseX: number,
baseY: number
baseY: number,
zIndex: number
): GroupShape | PathShape => {
const children = transformVectorPaths(node, baseX, baseY);
const children = transformVectorPaths(node, baseX, baseY, zIndex);
if (children.length === 1) {
return {
...children[0],
name: node.name,
...transformLayoutItemZIndex(zIndex),
...transformFigmaIds(node),
...transformConstraints(node)
};
}
return {
...transformGroupNodeLike(node, baseX, baseY),
...transformGroupNodeLike(node, baseX, baseY, zIndex),
...transformFigmaIds(node),
...transformConstraints(node),
children

View file

@ -1,6 +1,6 @@
import { remoteComponentLibrary } from '@plugin/RemoteComponentLibrary';
import { transformGroupNodeLike, transformSceneNode } from '@plugin/transformers';
import { transformMaskFigmaIds } from '@plugin/transformers/partials';
import { transformLayoutItemZIndex, transformMaskFigmaIds } from '@plugin/transformers/partials';
import { sleep } from '@plugin/utils';
import { PenpotNode } from '@ui/types';
@ -18,15 +18,26 @@ export const translateMaskChildren = async (
children: readonly SceneNode[],
maskIndex: number,
baseX: number,
baseY: number
baseY: number,
zIndex: number,
itemReverseZIndex: boolean = false
): Promise<PenpotNode[]> => {
const maskChild = children[maskIndex];
const unmaskedChildren = await translateChildren(children.slice(0, maskIndex), baseX, baseY);
const unmaskedChildren = await translateChildren(
children.slice(0, maskIndex),
baseX,
baseY,
zIndex,
itemReverseZIndex
);
const maskedChildren = await translateChildren(children.slice(maskIndex), baseX, baseY);
const lastZIndex = itemReverseZIndex ? zIndex - unmaskedChildren.length - 1 : zIndex;
const maskGroup = {
...transformMaskFigmaIds(maskChild),
...transformGroupNodeLike(maskChild, baseX, baseY),
...transformGroupNodeLike(maskChild, baseX, baseY, zIndex),
...transformLayoutItemZIndex(lastZIndex),
children: maskedChildren,
maskedGroup: true
};
@ -37,12 +48,19 @@ export const translateMaskChildren = async (
export const translateChildren = async (
children: readonly SceneNode[],
baseX: number = 0,
baseY: number = 0
baseY: number = 0,
zIndex: number = 0,
itemReverseZIndex: boolean = false
): Promise<PenpotNode[]> => {
const transformedChildren: PenpotNode[] = [];
for (const child of children) {
const penpotNode = await transformSceneNode(child, baseX, baseY);
const penpotNode = await transformSceneNode(
child,
baseX,
baseY,
itemReverseZIndex ? zIndex-- : zIndex
);
if (penpotNode) transformedChildren.push(penpotNode);

View file

@ -1,3 +1,4 @@
import { LayoutChildAttributes } from '@ui/lib/types/shapes/layout';
import {
ShapeAttributes,
ShapeBaseAttributes,
@ -10,7 +11,8 @@ export type GroupShape = ShapeBaseAttributes &
ShapeGeomAttributes &
ShapeAttributes &
GroupAttributes &
Children;
Children &
LayoutChildAttributes;
type GroupAttributes = {
type?: 'group';

View file

@ -46,7 +46,7 @@ export type LayoutChildAttributes = {
| typeof ITEM_ALIGN_SELF_CENTER
| typeof ITEM_ALIGN_SELF_STRETCH;
'layoutItemAbsolute'?: boolean;
'layoutItemZIndex'?: number;
'layoutItemZ-Index'?: number;
};
export type JustifyAlignContent =