Skip to content

Commit

Permalink
Fix component overrides (#200)
Browse files Browse the repository at this point in the history
* wip

* wip

* wip

* wip

* wip

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes

* fixes
  • Loading branch information
Cenadros authored Jun 28, 2024
1 parent c5dd5d0 commit 303cc83
Show file tree
Hide file tree
Showing 26 changed files with 223 additions and 100 deletions.
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

0 comments on commit 303cc83

Please sign in to comment.