Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix component overrides #200

Merged
merged 17 commits into from
Jun 28, 2024
5 changes: 5 additions & 0 deletions .changeset/shy-plants-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"penpot-exporter": minor
---

Improvements in overrides management
2 changes: 2 additions & 0 deletions plugin-src/libraries.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
import { ComponentProperty } from '@ui/types';

export const textStyles: Map<string, TextStyle | undefined> = new Map();
export const paintStyles: Map<string, PaintStyle | undefined> = new Map();
export const overrides: Map<string, NodeChangeProperty[]> = new Map();
export const images: Map<string, Image | null> = new Map();
export const components: Map<string, ComponentShape> = new Map();
export const componentProperties: Map<string, ComponentProperty> = new Map();
26 changes: 8 additions & 18 deletions plugin-src/transformers/partials/transformOverrides.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import { overrides as overridesLibrary } from '@plugin/libraries';
import { syncAttributes } from '@plugin/utils/syncAttributes';
import { overrides } from '@plugin/libraries';
import { translateTouched } from '@plugin/translators';

import { SyncGroups } from '@ui/lib/types/utils/syncGroups';

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]);
}
});
import { ShapeAttributes } from '@ui/lib/types/shapes/shape';

export const transformOverrides = (
node: SceneNode
): Pick<ShapeAttributes, 'touched' | 'componentPropertyReferences'> => {
return {
touched
touched: translateTouched(overrides.get(node.id)),
componentPropertyReferences: node.componentPropertyReferences
};
};
1 change: 1 addition & 0 deletions plugin-src/transformers/partials/transformText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const transformText = (node: TextNode): TextAttributes & Pick<TextShape,
]);

return {
characters: node.characters,
content: {
type: 'root',
verticalAlign: translateVerticalAlign(node.textAlignVertical),
Expand Down
18 changes: 17 additions & 1 deletion plugin-src/transformers/transformComponentNode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { components } from '@plugin/libraries';
import { componentProperties, components } from '@plugin/libraries';
import {
transformAutoLayout,
transformBlend,
Expand All @@ -18,6 +18,10 @@ import {

import { ComponentRoot } from '@ui/types';

const isNonVariantComponentNode = (node: ComponentNode): boolean => {
return node.parent?.type !== 'COMPONENT_SET';
};

export const transformComponentNode = async (node: ComponentNode): Promise<ComponentRoot> => {
components.set(node.id, {
type: 'component',
Expand All @@ -40,6 +44,18 @@ export const transformComponentNode = async (node: ComponentNode): Promise<Compo
...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 {
figmaId: node.id,
type: 'component',
Expand Down
5 changes: 3 additions & 2 deletions plugin-src/transformers/transformDocumentNode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { toObject } from '@common/map';

import { components } from '@plugin/libraries';
import { componentProperties, components } from '@plugin/libraries';
import {
processImages,
processPages,
Expand All @@ -27,6 +27,7 @@ export const transformDocumentNode = async (node: DocumentNode): Promise<PenpotD
components: toObject(components),
images,
paintStyles,
textStyles
textStyles,
componentProperties: toObject(componentProperties)
};
};
19 changes: 19 additions & 0 deletions plugin-src/transformers/transformFrameNode.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { componentProperties } from '@plugin/libraries';
import {
transformAutoLayout,
transformBlend,
Expand All @@ -23,12 +24,30 @@ const isSectionNode = (node: FrameNode | SectionNode | ComponentSetNode): node i
return node.type === 'SECTION';
};

const isComponentSetNode = (
node: FrameNode | SectionNode | ComponentSetNode
): node is ComponentSetNode => {
return node.type === 'COMPONENT_SET';
};

export const transformFrameNode = async (
node: FrameNode | SectionNode | ComponentSetNode
): Promise<FrameShape> => {
let frameSpecificAttributes: Partial<FrameShape> = {};
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)) {
const { x, y, ...transformAndRotation } = transformRotationAndPosition(node);

Expand Down
69 changes: 12 additions & 57 deletions plugin-src/transformers/transformInstanceNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
transformStrokes
} from '@plugin/transformers/partials';

import { ComponentInstance, ComponentTextPropertyOverride } from '@ui/types';
import { ComponentInstance } from '@ui/types';

export const transformInstanceNode = async (
node: InstanceNode
Expand All @@ -30,14 +30,20 @@ export const transformInstanceNode = async (
const primaryComponent = getPrimaryComponent(mainComponent);
const isOrphan = isOrphanInstance(primaryComponent);
let nodeOverrides = {};
if (!isOrphan) {
registerTextVariableOverrides(node, primaryComponent);
if (node.overrides.length > 0) {
node.overrides.forEach(override => overrides.set(override.id, override.overriddenFields));
}
if (!isOrphan && node.overrides.length > 0) {
node.overrides.forEach(override => overrides.set(override.id, override.overriddenFields));
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 {
type: 'instance',
name: node.name,
Expand Down Expand Up @@ -71,57 +77,6 @@ const getPrimaryComponent = (mainComponent: ComponentNode): ComponentNode | Comp
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 => {
return primaryComponent.parent === null || primaryComponent.remote;
};
Expand Down
1 change: 1 addition & 0 deletions plugin-src/translators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './translateLayout';
export * from './translateRotation';
export * from './translateShadowEffects';
export * from './translateStrokes';
export * from './translateTouched';
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { SyncGroups } from '@ui/lib/types/utils/syncGroups';

export type SyncAttributes = {
type SyncAttributes = {
[key in NodeChangeProperty]: SyncGroups[];
};

export const syncAttributes: SyncAttributes = {
const syncAttributes: SyncAttributes = {
name: [':name-group'],
fills: [':fill-group'],
backgrounds: [':fill-group'],
Expand Down Expand Up @@ -118,8 +118,8 @@ export const syncAttributes: SyncAttributes = {
minWidth: [],
minHeight: [],
maxWidth: [],
maxLines: [],
maxHeight: [],
maxLines: [],
counterAxisSpacing: [],
counterAxisAlignContent: [],
openTypeFeatures: [],
Expand Down Expand Up @@ -148,3 +148,21 @@ export const syncAttributes: SyncAttributes = {
authorName: [],
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);
};
2 changes: 2 additions & 0 deletions ui-src/lib/types/shapes/shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Shadow } from '@ui/lib/types/utils/shadow';
import { Stroke } from '@ui/lib/types/utils/stroke';
import { SyncGroups } from '@ui/lib/types/utils/syncGroups';
import { Uuid } from '@ui/lib/types/utils/uuid';
import { ComponentPropertyReference } from '@ui/types';

export type ShapeBaseAttributes = {
id?: Uuid;
Expand Down Expand Up @@ -74,6 +75,7 @@ export type ShapeAttributes = {
blur?: Blur;
growType?: GrowType;
touched?: SyncGroups[];
componentPropertyReferences?: ComponentPropertyReference; // @TODO: move to any other place
};

export type ShapeGeomAttributes = {
Expand Down
1 change: 1 addition & 0 deletions ui-src/lib/types/shapes/textShape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type TextShape = ShapeBaseAttributes &
export type TextAttributes = {
type?: 'text';
content?: TextContent;
characters?: string; // @ TODO: move to any other place
};

export type TextContent = {
Expand Down
8 changes: 7 additions & 1 deletion ui-src/parser/creators/createArtboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PenpotFile } from '@ui/lib/types/penpotFile';
import { FrameShape } from '@ui/lib/types/shapes/frameShape';
import { Uuid } from '@ui/lib/types/utils/uuid';
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 '.';

Expand All @@ -16,6 +16,12 @@ export const createArtboard = (
shape.shapeRef ??= parseFigmaId(file, figmaRelatedId, true);
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
shape.strokes = symbolStrokes(shape.strokes);
shape.touched = symbolTouched(
!shape.hidden,
undefined,
shape.touched,
shape.componentPropertyReferences
);

file.addArtboard(shape);

Expand Down
13 changes: 12 additions & 1 deletion ui-src/parser/creators/createBool.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { PenpotFile } from '@ui/lib/types/penpotFile';
import { BoolShape } from '@ui/lib/types/shapes/boolShape';
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 '.';

Expand All @@ -14,6 +19,12 @@ export const createBool = (
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
shape.strokes = symbolStrokes(shape.strokes);
shape.boolType = symbolBoolType(shape.boolType);
shape.touched = symbolTouched(
!shape.hidden,
undefined,
shape.touched,
shape.componentPropertyReferences
);

file.addBool(shape);

Expand Down
8 changes: 7 additions & 1 deletion ui-src/parser/creators/createCircle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PenpotFile } from '@ui/lib/types/penpotFile';
import { CircleShape } from '@ui/lib/types/shapes/circleShape';
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 = (
file: PenpotFile,
Expand All @@ -11,6 +11,12 @@ export const createCircle = (
shape.shapeRef = parseFigmaId(file, figmaRelatedId, true);
shape.fills = symbolFills(shape.fillStyleId, shape.fills);
shape.strokes = symbolStrokes(shape.strokes);
shape.touched = symbolTouched(
!shape.hidden,
undefined,
shape.touched,
shape.componentPropertyReferences
);

file.createCircle(shape);
};
Loading
Loading