From 14c9d02cc928aef64b9068ff7d0e2b87b231edb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Mon, 17 Jun 2024 12:19:53 +0200 Subject: [PATCH] Text Variables Overrides (#167) * wip * register text variables overrides * changeset * fixes * fixes --- .changeset/violet-otters-impress.md | 5 ++ .../transformers/transformInstanceNode.ts | 87 ++++++++++++++----- ui-src/types/component.ts | 7 ++ 3 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 .changeset/violet-otters-impress.md diff --git a/.changeset/violet-otters-impress.md b/.changeset/violet-otters-impress.md new file mode 100644 index 00000000..3b5abd34 --- /dev/null +++ b/.changeset/violet-otters-impress.md @@ -0,0 +1,5 @@ +--- +"penpot-exporter": minor +--- + +Register Text Variables as Instance override diff --git a/plugin-src/transformers/transformInstanceNode.ts b/plugin-src/transformers/transformInstanceNode.ts index 2d1639cf..d9336be1 100644 --- a/plugin-src/transformers/transformInstanceNode.ts +++ b/plugin-src/transformers/transformInstanceNode.ts @@ -18,22 +18,28 @@ import { transformStrokes } from '@plugin/transformers/partials'; -import { ComponentInstance } from '@ui/types'; +import { ComponentInstance, ComponentTextPropertyOverride } from '@ui/types'; export const transformInstanceNode = async ( node: InstanceNode, baseRotation: number ): Promise => { const mainComponent = await node.getMainComponentAsync(); + if (mainComponent === null) { + return; + } - if (mainComponent === null || isUnprocessableComponent(mainComponent)) { + const primaryComponent = getPrimaryComponent(mainComponent); + if (isUnprocessableComponent(primaryComponent)) { return; } - if (isExternalComponent(mainComponent)) { - registerExternalComponents(mainComponent); + if (primaryComponent.remote) { + registerExternalComponents(primaryComponent); } + registerTextVariableOverrides(node, primaryComponent); + if (node.overrides.length > 0) { node.overrides.forEach(override => overridesLibrary.register(override.id, override.overriddenFields) @@ -64,25 +70,67 @@ export const transformInstanceNode = async ( }; }; -const registerExternalComponents = (mainComponent: ComponentNode): void => { - let component: ComponentSetNode | ComponentNode = mainComponent; - - if (component.parent?.type === 'COMPONENT_SET') { - component = component.parent; +const getPrimaryComponent = (mainComponent: ComponentNode): ComponentNode | ComponentSetNode => { + if (mainComponent.parent?.type === 'COMPONENT_SET') { + return mainComponent.parent; } - if (remoteComponentLibrary.get(component.id) !== undefined) { + return mainComponent; +}; + +const registerExternalComponents = (primaryComponent: ComponentNode | ComponentSetNode): void => { + if (remoteComponentLibrary.get(primaryComponent.id) !== undefined) { return; } - remoteComponentLibrary.register(component.id, component); + remoteComponentLibrary.register(primaryComponent.id, primaryComponent); }; -const isExternalComponent = (mainComponent: ComponentNode): boolean => { - return ( - mainComponent.remote || - (mainComponent.parent?.type === 'COMPONENT_SET' && mainComponent.parent.remote) +const getComponentTextPropertyOverrides = ( + node: InstanceNode, + primaryComponent: ComponentNode | ComponentSetNode +): ComponentTextPropertyOverride[] => { + 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); +}; + +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 => { + overridesLibrary.register(textNode.id, ['text']); + }); + } }; /** @@ -91,13 +139,8 @@ const isExternalComponent = (mainComponent: ComponentNode): boolean => { * 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 = (mainComponent: ComponentNode): boolean => { - return ( - (mainComponent.parent === null && !mainComponent.remote) || - (mainComponent.parent?.type === 'COMPONENT_SET' && - mainComponent.parent.parent === null && - !mainComponent.parent.remote) - ); +const isUnprocessableComponent = (primaryComponent: ComponentNode | ComponentSetNode): boolean => { + return primaryComponent.parent === null && !primaryComponent.remote; }; const isComponentRoot = (node: InstanceNode): boolean => { diff --git a/ui-src/types/component.ts b/ui-src/types/component.ts index e28b5f69..189ace0e 100644 --- a/ui-src/types/component.ts +++ b/ui-src/types/component.ts @@ -7,6 +7,13 @@ export type ComponentRoot = { type: 'component'; }; +export type ComponentTextPropertyOverride = { + id: string; + type: 'TEXT'; + value: string; + defaultValue: string; +}; + export type ComponentInstance = ShapeGeomAttributes & ShapeAttributes & LayoutAttributes &