mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2024-12-22 05:33:02 -05:00
Fix component overrides (#200)
* wip * wip * wip * wip * wip * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes * fixes
This commit is contained in:
parent
c5dd5d011e
commit
303cc833a0
26 changed files with 223 additions and 100 deletions
5
.changeset/shy-plants-argue.md
Normal file
5
.changeset/shy-plants-argue.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Improvements in overrides management
|
|
@ -1,7 +1,9 @@
|
||||||
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
|
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
|
||||||
|
import { ComponentProperty } from '@ui/types';
|
||||||
|
|
||||||
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 componentProperties: Map<string, ComponentProperty> = new Map();
|
||||||
|
|
|
@ -1,23 +1,13 @@
|
||||||
import { overrides as overridesLibrary } from '@plugin/libraries';
|
import { overrides } from '@plugin/libraries';
|
||||||
import { syncAttributes } from '@plugin/utils/syncAttributes';
|
import { translateTouched } from '@plugin/translators';
|
||||||
|
|
||||||
import { SyncGroups } from '@ui/lib/types/utils/syncGroups';
|
import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
|
|
||||||
export const transformOverrides = (node: SceneNode) => {
|
|
||||||
const overrides = overridesLibrary.get(node.id);
|
|
||||||
if (!overrides) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const touched: SyncGroups[] = [];
|
|
||||||
|
|
||||||
overrides.forEach(override => {
|
|
||||||
if (syncAttributes[override]) {
|
|
||||||
touched.push(...syncAttributes[override]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
export const transformOverrides = (
|
||||||
|
node: SceneNode
|
||||||
|
): Pick<ShapeAttributes, 'touched' | 'componentPropertyReferences'> => {
|
||||||
return {
|
return {
|
||||||
touched
|
touched: translateTouched(overrides.get(node.id)),
|
||||||
|
componentPropertyReferences: node.componentPropertyReferences
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,6 +21,7 @@ export const transformText = (node: TextNode): TextAttributes & Pick<TextShape,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
characters: node.characters,
|
||||||
content: {
|
content: {
|
||||||
type: 'root',
|
type: 'root',
|
||||||
verticalAlign: translateVerticalAlign(node.textAlignVertical),
|
verticalAlign: translateVerticalAlign(node.textAlignVertical),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { components } from '@plugin/libraries';
|
import { componentProperties, components } from '@plugin/libraries';
|
||||||
import {
|
import {
|
||||||
transformAutoLayout,
|
transformAutoLayout,
|
||||||
transformBlend,
|
transformBlend,
|
||||||
|
@ -18,6 +18,10 @@ import {
|
||||||
|
|
||||||
import { ComponentRoot } from '@ui/types';
|
import { ComponentRoot } from '@ui/types';
|
||||||
|
|
||||||
|
const isNonVariantComponentNode = (node: ComponentNode): boolean => {
|
||||||
|
return node.parent?.type !== 'COMPONENT_SET';
|
||||||
|
};
|
||||||
|
|
||||||
export const transformComponentNode = async (node: ComponentNode): Promise<ComponentRoot> => {
|
export const transformComponentNode = async (node: ComponentNode): Promise<ComponentRoot> => {
|
||||||
components.set(node.id, {
|
components.set(node.id, {
|
||||||
type: 'component',
|
type: 'component',
|
||||||
|
@ -40,6 +44,18 @@ export const transformComponentNode = async (node: ComponentNode): Promise<Compo
|
||||||
...transformAutoLayout(node)
|
...transformAutoLayout(node)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isNonVariantComponentNode(node)) {
|
||||||
|
try {
|
||||||
|
Object.entries(node.componentPropertyDefinitions).forEach(([key, value]) => {
|
||||||
|
if (value.type === 'TEXT' || value.type === 'BOOLEAN') {
|
||||||
|
componentProperties.set(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error registering component properties', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
figmaId: node.id,
|
figmaId: node.id,
|
||||||
type: 'component',
|
type: 'component',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { toObject } from '@common/map';
|
import { toObject } from '@common/map';
|
||||||
|
|
||||||
import { components } from '@plugin/libraries';
|
import { componentProperties, components } from '@plugin/libraries';
|
||||||
import {
|
import {
|
||||||
processImages,
|
processImages,
|
||||||
processPages,
|
processPages,
|
||||||
|
@ -27,6 +27,7 @@ export const transformDocumentNode = async (node: DocumentNode): Promise<PenpotD
|
||||||
components: toObject(components),
|
components: toObject(components),
|
||||||
images,
|
images,
|
||||||
paintStyles,
|
paintStyles,
|
||||||
textStyles
|
textStyles,
|
||||||
|
componentProperties: toObject(componentProperties)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { componentProperties } from '@plugin/libraries';
|
||||||
import {
|
import {
|
||||||
transformAutoLayout,
|
transformAutoLayout,
|
||||||
transformBlend,
|
transformBlend,
|
||||||
|
@ -23,12 +24,30 @@ const isSectionNode = (node: FrameNode | SectionNode | ComponentSetNode): node i
|
||||||
return node.type === 'SECTION';
|
return node.type === 'SECTION';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isComponentSetNode = (
|
||||||
|
node: FrameNode | SectionNode | ComponentSetNode
|
||||||
|
): node is ComponentSetNode => {
|
||||||
|
return node.type === 'COMPONENT_SET';
|
||||||
|
};
|
||||||
|
|
||||||
export const transformFrameNode = async (
|
export const transformFrameNode = async (
|
||||||
node: FrameNode | SectionNode | ComponentSetNode
|
node: FrameNode | SectionNode | ComponentSetNode
|
||||||
): Promise<FrameShape> => {
|
): Promise<FrameShape> => {
|
||||||
let frameSpecificAttributes: Partial<FrameShape> = {};
|
let frameSpecificAttributes: Partial<FrameShape> = {};
|
||||||
let referencePoint: Point = { x: node.absoluteTransform[0][2], y: node.absoluteTransform[1][2] };
|
let referencePoint: Point = { x: node.absoluteTransform[0][2], y: node.absoluteTransform[1][2] };
|
||||||
|
|
||||||
|
if (isComponentSetNode(node)) {
|
||||||
|
try {
|
||||||
|
Object.entries(node.componentPropertyDefinitions).forEach(([key, value]) => {
|
||||||
|
if (value.type === 'TEXT' || value.type === 'BOOLEAN') {
|
||||||
|
componentProperties.set(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error registering component properties', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isSectionNode(node)) {
|
if (!isSectionNode(node)) {
|
||||||
const { x, y, ...transformAndRotation } = transformRotationAndPosition(node);
|
const { x, y, ...transformAndRotation } = transformRotationAndPosition(node);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
transformStrokes
|
transformStrokes
|
||||||
} from '@plugin/transformers/partials';
|
} from '@plugin/transformers/partials';
|
||||||
|
|
||||||
import { ComponentInstance, ComponentTextPropertyOverride } from '@ui/types';
|
import { ComponentInstance } from '@ui/types';
|
||||||
|
|
||||||
export const transformInstanceNode = async (
|
export const transformInstanceNode = async (
|
||||||
node: InstanceNode
|
node: InstanceNode
|
||||||
|
@ -30,14 +30,20 @@ export const transformInstanceNode = async (
|
||||||
const primaryComponent = getPrimaryComponent(mainComponent);
|
const primaryComponent = getPrimaryComponent(mainComponent);
|
||||||
const isOrphan = isOrphanInstance(primaryComponent);
|
const isOrphan = isOrphanInstance(primaryComponent);
|
||||||
let nodeOverrides = {};
|
let nodeOverrides = {};
|
||||||
if (!isOrphan) {
|
if (!isOrphan && node.overrides.length > 0) {
|
||||||
registerTextVariableOverrides(node, primaryComponent);
|
node.overrides.forEach(override => overrides.set(override.id, override.overriddenFields));
|
||||||
if (node.overrides.length > 0) {
|
|
||||||
node.overrides.forEach(override => overrides.set(override.id, override.overriddenFields));
|
|
||||||
}
|
|
||||||
nodeOverrides = transformOverrides(node);
|
nodeOverrides = transformOverrides(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchedOverrides = overrides.get(node.id) ?? [];
|
||||||
|
if (node.visible !== mainComponent.visible) {
|
||||||
|
fetchedOverrides.push('visible');
|
||||||
|
}
|
||||||
|
if (node.locked !== mainComponent.locked) {
|
||||||
|
fetchedOverrides.push('locked');
|
||||||
|
}
|
||||||
|
overrides.set(node.id, fetchedOverrides);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'instance',
|
type: 'instance',
|
||||||
name: node.name,
|
name: node.name,
|
||||||
|
@ -71,57 +77,6 @@ const getPrimaryComponent = (mainComponent: ComponentNode): ComponentNode | Comp
|
||||||
return mainComponent;
|
return mainComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getComponentTextPropertyOverrides = (
|
|
||||||
node: InstanceNode,
|
|
||||||
primaryComponent: ComponentNode | ComponentSetNode
|
|
||||||
): ComponentTextPropertyOverride[] => {
|
|
||||||
try {
|
|
||||||
const componentPropertyDefinitions = Object.entries(
|
|
||||||
primaryComponent.componentPropertyDefinitions
|
|
||||||
).filter(([, value]) => value.type === 'TEXT');
|
|
||||||
|
|
||||||
const instanceComponentProperties = new Map(
|
|
||||||
Object.entries(node.componentProperties).filter(([, value]) => value.type === 'TEXT')
|
|
||||||
);
|
|
||||||
|
|
||||||
return componentPropertyDefinitions
|
|
||||||
.map(([key, value]) => {
|
|
||||||
const nodeValue = instanceComponentProperties.get(key);
|
|
||||||
return {
|
|
||||||
id: key,
|
|
||||||
...value,
|
|
||||||
value: nodeValue ? nodeValue.value : value.defaultValue
|
|
||||||
} as ComponentTextPropertyOverride;
|
|
||||||
})
|
|
||||||
.filter(({ value, defaultValue }) => value !== defaultValue);
|
|
||||||
} catch (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const registerTextVariableOverrides = (
|
|
||||||
node: InstanceNode,
|
|
||||||
primaryComponent: ComponentNode | ComponentSetNode
|
|
||||||
) => {
|
|
||||||
const mergedOverridden = getComponentTextPropertyOverrides(node, primaryComponent);
|
|
||||||
|
|
||||||
if (mergedOverridden.length > 0) {
|
|
||||||
const textNodes = node
|
|
||||||
.findChildren(child => child.type === 'TEXT')
|
|
||||||
.filter(textNode => {
|
|
||||||
const componentPropertyReference = textNode.componentPropertyReferences?.characters;
|
|
||||||
return (
|
|
||||||
componentPropertyReference &&
|
|
||||||
mergedOverridden.some(property => property.id === componentPropertyReference)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
textNodes.forEach(textNode => {
|
|
||||||
overrides.set(textNode.id, ['text']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isOrphanInstance = (primaryComponent: ComponentNode | ComponentSetNode): boolean => {
|
const isOrphanInstance = (primaryComponent: ComponentNode | ComponentSetNode): boolean => {
|
||||||
return primaryComponent.parent === null || primaryComponent.remote;
|
return primaryComponent.parent === null || primaryComponent.remote;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,3 +7,4 @@ export * from './translateLayout';
|
||||||
export * from './translateRotation';
|
export * from './translateRotation';
|
||||||
export * from './translateShadowEffects';
|
export * from './translateShadowEffects';
|
||||||
export * from './translateStrokes';
|
export * from './translateStrokes';
|
||||||
|
export * from './translateTouched';
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { SyncGroups } from '@ui/lib/types/utils/syncGroups';
|
import { SyncGroups } from '@ui/lib/types/utils/syncGroups';
|
||||||
|
|
||||||
export type SyncAttributes = {
|
type SyncAttributes = {
|
||||||
[key in NodeChangeProperty]: SyncGroups[];
|
[key in NodeChangeProperty]: SyncGroups[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const syncAttributes: SyncAttributes = {
|
const syncAttributes: SyncAttributes = {
|
||||||
name: [':name-group'],
|
name: [':name-group'],
|
||||||
fills: [':fill-group'],
|
fills: [':fill-group'],
|
||||||
backgrounds: [':fill-group'],
|
backgrounds: [':fill-group'],
|
||||||
|
@ -118,8 +118,8 @@ export const syncAttributes: SyncAttributes = {
|
||||||
minWidth: [],
|
minWidth: [],
|
||||||
minHeight: [],
|
minHeight: [],
|
||||||
maxWidth: [],
|
maxWidth: [],
|
||||||
maxLines: [],
|
|
||||||
maxHeight: [],
|
maxHeight: [],
|
||||||
|
maxLines: [],
|
||||||
counterAxisSpacing: [],
|
counterAxisSpacing: [],
|
||||||
counterAxisAlignContent: [],
|
counterAxisAlignContent: [],
|
||||||
openTypeFeatures: [],
|
openTypeFeatures: [],
|
||||||
|
@ -148,3 +148,21 @@ export const syncAttributes: SyncAttributes = {
|
||||||
authorName: [],
|
authorName: [],
|
||||||
code: []
|
code: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const translateTouched = (
|
||||||
|
changedProperties: NodeChangeProperty[] | undefined
|
||||||
|
): SyncGroups[] => {
|
||||||
|
const syncGroups: Set<SyncGroups> = new Set();
|
||||||
|
|
||||||
|
if (!changedProperties) return [];
|
||||||
|
|
||||||
|
changedProperties.forEach(changedProperty => {
|
||||||
|
const syncGroup = syncAttributes[changedProperty];
|
||||||
|
|
||||||
|
if (syncGroup && syncGroup.length > 0) {
|
||||||
|
syncGroup.forEach(group => syncGroups.add(group));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(syncGroups);
|
||||||
|
};
|
|
@ -11,6 +11,7 @@ import { Shadow } from '@ui/lib/types/utils/shadow';
|
||||||
import { Stroke } from '@ui/lib/types/utils/stroke';
|
import { Stroke } from '@ui/lib/types/utils/stroke';
|
||||||
import { SyncGroups } from '@ui/lib/types/utils/syncGroups';
|
import { SyncGroups } from '@ui/lib/types/utils/syncGroups';
|
||||||
import { Uuid } from '@ui/lib/types/utils/uuid';
|
import { Uuid } from '@ui/lib/types/utils/uuid';
|
||||||
|
import { ComponentPropertyReference } from '@ui/types';
|
||||||
|
|
||||||
export type ShapeBaseAttributes = {
|
export type ShapeBaseAttributes = {
|
||||||
id?: Uuid;
|
id?: Uuid;
|
||||||
|
@ -74,6 +75,7 @@ export type ShapeAttributes = {
|
||||||
blur?: Blur;
|
blur?: Blur;
|
||||||
growType?: GrowType;
|
growType?: GrowType;
|
||||||
touched?: SyncGroups[];
|
touched?: SyncGroups[];
|
||||||
|
componentPropertyReferences?: ComponentPropertyReference; // @TODO: move to any other place
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShapeGeomAttributes = {
|
export type ShapeGeomAttributes = {
|
||||||
|
|
|
@ -16,6 +16,7 @@ export type TextShape = ShapeBaseAttributes &
|
||||||
export type TextAttributes = {
|
export type TextAttributes = {
|
||||||
type?: 'text';
|
type?: 'text';
|
||||||
content?: TextContent;
|
content?: TextContent;
|
||||||
|
characters?: string; // @ TODO: move to any other place
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TextContent = {
|
export type TextContent = {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { FrameShape } from '@ui/lib/types/shapes/frameShape';
|
import { FrameShape } from '@ui/lib/types/shapes/frameShape';
|
||||||
import { Uuid } from '@ui/lib/types/utils/uuid';
|
import { Uuid } from '@ui/lib/types/utils/uuid';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolFills, symbolStrokes } from '@ui/parser/creators/symbols';
|
import { symbolFills, symbolStrokes, symbolTouched } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
import { createItems } from '.';
|
import { createItems } from '.';
|
||||||
|
|
||||||
|
@ -16,6 +16,12 @@ export const createArtboard = (
|
||||||
shape.shapeRef ??= parseFigmaId(file, figmaRelatedId, true);
|
shape.shapeRef ??= parseFigmaId(file, figmaRelatedId, true);
|
||||||
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
||||||
shape.strokes = symbolStrokes(shape.strokes);
|
shape.strokes = symbolStrokes(shape.strokes);
|
||||||
|
shape.touched = symbolTouched(
|
||||||
|
!shape.hidden,
|
||||||
|
undefined,
|
||||||
|
shape.touched,
|
||||||
|
shape.componentPropertyReferences
|
||||||
|
);
|
||||||
|
|
||||||
file.addArtboard(shape);
|
file.addArtboard(shape);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { BoolShape } from '@ui/lib/types/shapes/boolShape';
|
import { BoolShape } from '@ui/lib/types/shapes/boolShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolBoolType, symbolFills, symbolStrokes } from '@ui/parser/creators/symbols';
|
import {
|
||||||
|
symbolBoolType,
|
||||||
|
symbolFills,
|
||||||
|
symbolStrokes,
|
||||||
|
symbolTouched
|
||||||
|
} from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
import { createItems } from '.';
|
import { createItems } from '.';
|
||||||
|
|
||||||
|
@ -14,6 +19,12 @@ export const createBool = (
|
||||||
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
||||||
shape.strokes = symbolStrokes(shape.strokes);
|
shape.strokes = symbolStrokes(shape.strokes);
|
||||||
shape.boolType = symbolBoolType(shape.boolType);
|
shape.boolType = symbolBoolType(shape.boolType);
|
||||||
|
shape.touched = symbolTouched(
|
||||||
|
!shape.hidden,
|
||||||
|
undefined,
|
||||||
|
shape.touched,
|
||||||
|
shape.componentPropertyReferences
|
||||||
|
);
|
||||||
|
|
||||||
file.addBool(shape);
|
file.addBool(shape);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { CircleShape } from '@ui/lib/types/shapes/circleShape';
|
import { CircleShape } from '@ui/lib/types/shapes/circleShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolFills, symbolStrokes } from '@ui/parser/creators/symbols';
|
import { symbolFills, symbolStrokes, symbolTouched } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
export const createCircle = (
|
export const createCircle = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
|
@ -11,6 +11,12 @@ export const createCircle = (
|
||||||
shape.shapeRef = parseFigmaId(file, figmaRelatedId, true);
|
shape.shapeRef = parseFigmaId(file, figmaRelatedId, true);
|
||||||
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
||||||
shape.strokes = symbolStrokes(shape.strokes);
|
shape.strokes = symbolStrokes(shape.strokes);
|
||||||
|
shape.touched = symbolTouched(
|
||||||
|
!shape.hidden,
|
||||||
|
undefined,
|
||||||
|
shape.touched,
|
||||||
|
shape.componentPropertyReferences
|
||||||
|
);
|
||||||
|
|
||||||
file.createCircle(shape);
|
file.createCircle(shape);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,14 +9,7 @@ let remoteFileId: Uuid | undefined = undefined;
|
||||||
|
|
||||||
export const createComponentInstance = (
|
export const createComponentInstance = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
{
|
{ type, mainComponentFigmaId, isComponentRoot, ...shape }: ComponentInstance
|
||||||
type,
|
|
||||||
mainComponentFigmaId,
|
|
||||||
figmaId,
|
|
||||||
figmaRelatedId,
|
|
||||||
isComponentRoot,
|
|
||||||
...shape
|
|
||||||
}: ComponentInstance
|
|
||||||
) => {
|
) => {
|
||||||
const uiComponent =
|
const uiComponent =
|
||||||
components.get(mainComponentFigmaId) ?? createUiComponent(file, mainComponentFigmaId);
|
components.get(mainComponentFigmaId) ?? createUiComponent(file, mainComponentFigmaId);
|
||||||
|
@ -25,7 +18,9 @@ export const createComponentInstance = (
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
shape.shapeRef = uiComponent.mainInstanceId;
|
if (!shape.figmaRelatedId) {
|
||||||
|
shape.shapeRef = uiComponent.mainInstanceId;
|
||||||
|
}
|
||||||
shape.componentFile = shape.isOrphan ? getRemoteFileId(file) : file.getId();
|
shape.componentFile = shape.isOrphan ? getRemoteFileId(file) : file.getId();
|
||||||
shape.componentRoot = isComponentRoot;
|
shape.componentRoot = isComponentRoot;
|
||||||
shape.componentId = uiComponent.componentId;
|
shape.componentId = uiComponent.componentId;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { GroupShape } from '@ui/lib/types/shapes/groupShape';
|
import { GroupShape } from '@ui/lib/types/shapes/groupShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
|
import { symbolTouched } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
import { createItems } from '.';
|
import { createItems } from '.';
|
||||||
|
|
||||||
|
@ -10,6 +11,12 @@ export const createGroup = (
|
||||||
) => {
|
) => {
|
||||||
shape.id = parseFigmaId(file, figmaId);
|
shape.id = parseFigmaId(file, figmaId);
|
||||||
shape.shapeRef = parseFigmaId(file, figmaRelatedId, true);
|
shape.shapeRef = parseFigmaId(file, figmaRelatedId, true);
|
||||||
|
shape.touched = symbolTouched(
|
||||||
|
!shape.hidden,
|
||||||
|
undefined,
|
||||||
|
shape.touched,
|
||||||
|
shape.componentPropertyReferences
|
||||||
|
);
|
||||||
|
|
||||||
file.addGroup(shape);
|
file.addGroup(shape);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolFills, symbolPathContent, symbolStrokes } from '@ui/parser/creators/symbols';
|
import {
|
||||||
|
symbolFills,
|
||||||
|
symbolPathContent,
|
||||||
|
symbolStrokes,
|
||||||
|
symbolTouched
|
||||||
|
} from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
export const createPath = (
|
export const createPath = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
|
@ -12,6 +17,12 @@ export const createPath = (
|
||||||
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
||||||
shape.strokes = symbolStrokes(shape.strokes);
|
shape.strokes = symbolStrokes(shape.strokes);
|
||||||
shape.content = symbolPathContent(shape.content);
|
shape.content = symbolPathContent(shape.content);
|
||||||
|
shape.touched = symbolTouched(
|
||||||
|
!shape.hidden,
|
||||||
|
undefined,
|
||||||
|
shape.touched,
|
||||||
|
shape.componentPropertyReferences
|
||||||
|
);
|
||||||
|
|
||||||
file.createPath(shape);
|
file.createPath(shape);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { RectShape } from '@ui/lib/types/shapes/rectShape';
|
import { RectShape } from '@ui/lib/types/shapes/rectShape';
|
||||||
import { parseFigmaId } from '@ui/parser';
|
import { parseFigmaId } from '@ui/parser';
|
||||||
import { symbolFills, symbolStrokes } from '@ui/parser/creators/symbols';
|
import { symbolFills, symbolStrokes, symbolTouched } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
export const createRectangle = (
|
export const createRectangle = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
|
@ -11,6 +11,12 @@ export const createRectangle = (
|
||||||
shape.shapeRef = parseFigmaId(file, figmaRelatedId, true);
|
shape.shapeRef = parseFigmaId(file, figmaRelatedId, true);
|
||||||
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
|
||||||
shape.strokes = symbolStrokes(shape.strokes);
|
shape.strokes = symbolStrokes(shape.strokes);
|
||||||
|
shape.touched = symbolTouched(
|
||||||
|
!shape.hidden,
|
||||||
|
undefined,
|
||||||
|
shape.touched,
|
||||||
|
shape.componentPropertyReferences
|
||||||
|
);
|
||||||
|
|
||||||
file.createRect(shape);
|
file.createRect(shape);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { Paragraph, TextContent, TextNode, TextShape } from '@ui/lib/types/shapes/textShape';
|
import { Paragraph, TextContent, TextNode, TextShape } from '@ui/lib/types/shapes/textShape';
|
||||||
import { parseFigmaId, typographies } from '@ui/parser';
|
import { parseFigmaId, typographies } from '@ui/parser';
|
||||||
import { symbolFills, symbolStrokes } from '@ui/parser/creators/symbols';
|
import { symbolFills, symbolStrokes, symbolTouched } from '@ui/parser/creators/symbols';
|
||||||
|
|
||||||
export const createText = (
|
export const createText = (
|
||||||
file: PenpotFile,
|
file: PenpotFile,
|
||||||
{ type, figmaId, figmaRelatedId, ...shape }: TextShape
|
{ type, figmaId, figmaRelatedId, characters, ...shape }: TextShape
|
||||||
) => {
|
) => {
|
||||||
shape.id = parseFigmaId(file, figmaId);
|
shape.id = parseFigmaId(file, figmaId);
|
||||||
shape.shapeRef = parseFigmaId(file, figmaRelatedId, true);
|
shape.shapeRef = parseFigmaId(file, figmaRelatedId, true);
|
||||||
shape.content = parseContent(shape.content);
|
shape.content = parseContent(shape.content);
|
||||||
shape.strokes = symbolStrokes(shape.strokes);
|
shape.strokes = symbolStrokes(shape.strokes);
|
||||||
|
shape.touched = symbolTouched(
|
||||||
|
!shape.hidden,
|
||||||
|
characters,
|
||||||
|
shape.touched,
|
||||||
|
shape.componentPropertyReferences
|
||||||
|
);
|
||||||
|
|
||||||
file.createText(shape);
|
file.createText(shape);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,3 +2,4 @@ export * from './symbolBoolType';
|
||||||
export * from './symbolFills';
|
export * from './symbolFills';
|
||||||
export * from './symbolPathContent';
|
export * from './symbolPathContent';
|
||||||
export * from './symbolStrokes';
|
export * from './symbolStrokes';
|
||||||
|
export * from './symbolTouched';
|
||||||
|
|
35
ui-src/parser/creators/symbols/symbolTouched.ts
Normal file
35
ui-src/parser/creators/symbols/symbolTouched.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { SyncGroups } from '@ui/lib/types/utils/syncGroups';
|
||||||
|
import { componentProperties } from '@ui/parser';
|
||||||
|
import { ComponentPropertyReference } from '@ui/types';
|
||||||
|
|
||||||
|
export const symbolTouched = (
|
||||||
|
visible: boolean | undefined,
|
||||||
|
characters: string | undefined,
|
||||||
|
touched: SyncGroups[] | undefined,
|
||||||
|
componentPropertyReferences: ComponentPropertyReference | undefined
|
||||||
|
): SyncGroups[] | undefined => {
|
||||||
|
if (!componentPropertyReferences) {
|
||||||
|
return touched;
|
||||||
|
}
|
||||||
|
|
||||||
|
const propertyReferenceVisible = componentPropertyReferences.visible;
|
||||||
|
const propertyReferenceCharacters = componentPropertyReferences.characters;
|
||||||
|
|
||||||
|
if (
|
||||||
|
propertyReferenceVisible &&
|
||||||
|
visible !== componentProperties.get(propertyReferenceVisible)?.defaultValue &&
|
||||||
|
!touched?.includes(':visibility-group')
|
||||||
|
) {
|
||||||
|
touched?.push(':visibility-group');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
propertyReferenceCharacters &&
|
||||||
|
characters !== componentProperties.get(propertyReferenceCharacters)?.defaultValue &&
|
||||||
|
!touched?.includes(':content-group')
|
||||||
|
) {
|
||||||
|
touched?.push(':content-group');
|
||||||
|
}
|
||||||
|
|
||||||
|
return touched;
|
||||||
|
};
|
|
@ -3,7 +3,7 @@ import { TypographyStyle } from '@ui/lib/types/shapes/textShape';
|
||||||
import { FillStyle } from '@ui/lib/types/utils/fill';
|
import { FillStyle } from '@ui/lib/types/utils/fill';
|
||||||
import { ImageColor } from '@ui/lib/types/utils/imageColor';
|
import { ImageColor } from '@ui/lib/types/utils/imageColor';
|
||||||
import { Uuid } from '@ui/lib/types/utils/uuid';
|
import { Uuid } from '@ui/lib/types/utils/uuid';
|
||||||
import { UiComponent } from '@ui/types';
|
import { ComponentProperty, UiComponent } from '@ui/types';
|
||||||
|
|
||||||
export const typographies: Map<string, TypographyStyle> = new Map();
|
export const typographies: Map<string, TypographyStyle> = new Map();
|
||||||
export const images: Map<string, ImageColor> = new Map();
|
export const images: Map<string, ImageColor> = new Map();
|
||||||
|
@ -11,3 +11,4 @@ export const identifiers: Map<string, Uuid> = new Map();
|
||||||
export const components: Map<string, UiComponent> = new Map();
|
export const components: Map<string, UiComponent> = new Map();
|
||||||
export const componentShapes: Map<string, ComponentShape> = new Map();
|
export const componentShapes: Map<string, ComponentShape> = new Map();
|
||||||
export const colors: Map<string, FillStyle> = new Map();
|
export const colors: Map<string, FillStyle> = new Map();
|
||||||
|
export const componentProperties: Map<string, ComponentProperty> = new Map();
|
||||||
|
|
|
@ -6,7 +6,13 @@ import { createFile } from '@ui/lib/penpot';
|
||||||
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
import { PenpotFile } from '@ui/lib/types/penpotFile';
|
||||||
import { TypographyStyle } from '@ui/lib/types/shapes/textShape';
|
import { TypographyStyle } from '@ui/lib/types/shapes/textShape';
|
||||||
import { FillStyle } from '@ui/lib/types/utils/fill';
|
import { FillStyle } from '@ui/lib/types/utils/fill';
|
||||||
import { colors, componentShapes, images, typographies } from '@ui/parser';
|
import {
|
||||||
|
colors,
|
||||||
|
componentShapes,
|
||||||
|
images,
|
||||||
|
typographies,
|
||||||
|
componentProperties as uiComponentProperties
|
||||||
|
} from '@ui/parser';
|
||||||
import { buildFile } from '@ui/parser/creators';
|
import { buildFile } from '@ui/parser/creators';
|
||||||
import { PenpotDocument } from '@ui/types';
|
import { PenpotDocument } from '@ui/types';
|
||||||
|
|
||||||
|
@ -123,9 +129,11 @@ export const parse = async ({
|
||||||
components,
|
components,
|
||||||
images,
|
images,
|
||||||
paintStyles,
|
paintStyles,
|
||||||
textStyles
|
textStyles,
|
||||||
|
componentProperties
|
||||||
}: PenpotDocument) => {
|
}: PenpotDocument) => {
|
||||||
init(componentShapes, components);
|
init(componentShapes, components);
|
||||||
|
init(uiComponentProperties, componentProperties);
|
||||||
|
|
||||||
const file = createFile(name);
|
const file = createFile(name);
|
||||||
|
|
||||||
|
|
|
@ -36,3 +36,20 @@ export type UiComponent = {
|
||||||
mainInstanceId: Uuid;
|
mainInstanceId: Uuid;
|
||||||
componentFigmaId: string;
|
componentFigmaId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ComponentProperty = {
|
||||||
|
type: 'BOOLEAN' | 'TEXT' | 'INSTANCE_SWAP' | 'VARIANT';
|
||||||
|
defaultValue: string | boolean;
|
||||||
|
preferredValues?: {
|
||||||
|
type: 'COMPONENT' | 'COMPONENT_SET';
|
||||||
|
key: string;
|
||||||
|
}[];
|
||||||
|
variantOptions?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// This type comes directly from Figma. We have it here because we need to reference it from the UI
|
||||||
|
export type ComponentPropertyReference =
|
||||||
|
| {
|
||||||
|
[nodeProperty in 'visible' | 'characters' | 'mainComponent']?: string;
|
||||||
|
}
|
||||||
|
| null;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { PenpotPage } from '@ui/lib/types/penpotPage';
|
||||||
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
|
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
|
||||||
import { TypographyStyle } from '@ui/lib/types/shapes/textShape';
|
import { TypographyStyle } from '@ui/lib/types/shapes/textShape';
|
||||||
import { FillStyle } from '@ui/lib/types/utils/fill';
|
import { FillStyle } from '@ui/lib/types/utils/fill';
|
||||||
|
import { ComponentProperty } from '@ui/types/component';
|
||||||
|
|
||||||
export type PenpotDocument = {
|
export type PenpotDocument = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -10,4 +11,5 @@ export type PenpotDocument = {
|
||||||
images: Record<string, Uint8Array>;
|
images: Record<string, Uint8Array>;
|
||||||
paintStyles: Record<string, FillStyle>;
|
paintStyles: Record<string, FillStyle>;
|
||||||
textStyles: Record<string, TypographyStyle>;
|
textStyles: Record<string, TypographyStyle>;
|
||||||
|
componentProperties: Record<string, ComponentProperty>;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue