Skip to content

Commit

Permalink
Merge pull request #13 from bitovi/feat/PD-378-appearance
Browse files Browse the repository at this point in the history
Feat/pd 378 appearance
  • Loading branch information
Mattchewone authored Feb 6, 2025
2 parents df8cbc9 + be9a732 commit 441b64b
Show file tree
Hide file tree
Showing 14 changed files with 4,670 additions and 176 deletions.
134 changes: 82 additions & 52 deletions src/processors/background.processor.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,99 @@
import { StyleProcessor, VariableToken, ProcessedValue } from '../types';
import { rgbaToString } from '../utils/color.utils';
import { processGradient } from '../utils/gradient.utils';
import { normalizeValue } from '../utils/value.utils';

export const backgroundProcessor: StyleProcessor = {
property: "background",
bindingKey: "fills",
process: async (variables: VariableToken[], node?: SceneNode): Promise<ProcessedValue | null> => {
if (node && 'fills' in node && Array.isArray(node.fills)) {
const visibleFills = node.fills.filter(fill => fill.visible);
if (!visibleFills.length) return null;
export const backgroundProcessors: StyleProcessor[] = [
{
property: "background",
bindingKey: "fills",
process: async (variables: VariableToken[], node?: SceneNode): Promise<ProcessedValue | null> => {
if (node && 'fills' in node && Array.isArray(node.fills)) {
const visibleFills = node.fills.filter(fill => fill.visible);
if (!visibleFills.length) return null;

const warningsSet = new Set<string>();
const errorsSet = new Set<string>();
const warningsSet = new Set<string>();
const errorsSet = new Set<string>();

const backgrounds = await Promise.all(visibleFills.map(async (fill: Paint) => {
if (fill.type === "SOLID") {
const fillVariable = variables.find(v => v.property === 'fills');
if (fillVariable) {
return {
value: fillVariable.value,
rawValue: fillVariable.rawValue
};
const backgrounds = await Promise.all(visibleFills.map(async (fill: Paint) => {
if (fill.type === "SOLID") {
const fillVariable = variables.find(v => v.property === 'fills');
if (fillVariable) {
return {
value: fillVariable.value,
rawValue: fillVariable.rawValue
};
}

const { r, g, b } = fill.color;
const a = fill.opacity ?? 1;
const value = rgbaToString(r, g, b, a);
return { value, rawValue: value };
}

const { r, g, b } = fill.color;
const a = fill.opacity ?? 1;
const value = rgbaToString(r, g, b, a);
return { value, rawValue: value };
}
if (fill.type.startsWith('GRADIENT_')) {
const result = processGradient(fill as GradientPaint, node.id);
if (result.warnings) {
result.warnings.forEach(warning => warningsSet.add(warning));
}
if (result.errors) {
result.errors.forEach(error => errorsSet.add(error));
}

if (fill.type.startsWith('GRADIENT_')) {
const result = processGradient(fill as GradientPaint, node.id);
if (result.warnings) {
result.warnings.forEach(warning => warningsSet.add(warning));
}
if (result.errors) {
result.errors.forEach(error => errorsSet.add(error));
if (result.value) {
return {
value: result.value,
rawValue: result.value
};
}
}

if (result.value) {
return {
value: result.value,
rawValue: result.value
};
}
}

return null;
}));
return null;
}));

const result: ProcessedValue = {
warnings: warningsSet.size > 0 ? Array.from(warningsSet) : undefined,
errors: errorsSet.size > 0 ? Array.from(errorsSet) : undefined,
value: null,
rawValue: null
};
const result: ProcessedValue = {
warnings: warningsSet.size > 0 ? Array.from(warningsSet) : undefined,
errors: errorsSet.size > 0 ? Array.from(errorsSet) : undefined,
value: null,
rawValue: null
};

const validBackgrounds = backgrounds.filter((b): b is NonNullable<typeof b> => b !== null);
if (validBackgrounds.length > 0) {
result.value = validBackgrounds.map(b => b.value).join(', ');
result.rawValue = validBackgrounds.map(b => b.rawValue).join(', ');
const validBackgrounds = backgrounds.filter((b): b is NonNullable<typeof b> => b !== null);
if (validBackgrounds.length > 0) {
result.value = validBackgrounds.map(b => b.value).join(', ');
result.rawValue = validBackgrounds.map(b => b.rawValue).join(', ');
}

return result;
}
return null;
}
},
{
property: "opacity",
bindingKey: "opacity",
process: async (variables: VariableToken[], node?: SceneNode): Promise<ProcessedValue | null> => {
if (node && 'opacity' in node && node.opacity !== 1) {
const opacityVariable = variables.find(v => v.property === 'opacity');
if (opacityVariable) {
return {
value: opacityVariable.value,
rawValue: opacityVariable.rawValue
};
}

return result;
if (typeof node.opacity === 'number') {
const value = normalizeValue({
propertyName: 'opacity',
value: node.opacity
});
return {
value,
rawValue: value
};
}
}
return null;
}
return null;
}
};
];
107 changes: 90 additions & 17 deletions src/processors/border.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,8 @@ export const borderProcessors: StyleProcessor[] = [
},
{
property: "border-radius",
bindingKey: "cornerRadius",
bindingKey: undefined,
process: async (variables, node?: SceneNode): Promise<ProcessedValue | null> => {
const radiusVariable = variables.find(v => v.property === 'border-radius');
if (radiusVariable) {
return {
value: radiusVariable.value,
rawValue: radiusVariable.rawValue
};
}

// Handle ELLIPSE nodes
if (node?.type === 'ELLIPSE') {
const EPSILON = 0.00001;
Expand All @@ -211,18 +203,21 @@ export const borderProcessors: StyleProcessor[] = [
node.arcData.innerRadius === 0
)
) {
return { value: '50%', rawValue: '50%' };
return { value: '50%', rawValue: '50%', valueType: '%' };
}
// For partial circles or donuts, don't apply border-radius
return null;
}

// Handle other nodes with cornerRadius
if (node && 'cornerRadius' in node && node.cornerRadius) {
const value = `${String(node.cornerRadius)}px`;
return { value, rawValue: value };
}
return null;
// Handle nodes with cornerRadius
if (!node) return null;
const radii = getCornerRadii(node, variables);
if (!radii) return null;

return {
value: radii.values.join(' '),
rawValue: radii.rawValues.join(' '),
valueType: radii.valueType
};
}
},
];
Expand Down Expand Up @@ -348,4 +343,82 @@ const processBorderSide = async (
rawValue,
valueType: "px",
};
};

// Add this utility function near the other utility functions
const getCornerRadii = (node: SceneNode, variables?: any[]) => {
if (!('topRightRadius' in node) && !('bottomRightRadius' in node) &&
!('bottomLeftRadius' in node) && !('topLeftRadius' in node)) {
return null;
}

// Handle variables first
const cornerVars = [
variables?.find(v => v.property === 'topLeftRadius'),
variables?.find(v => v.property === 'topRightRadius'),
variables?.find(v => v.property === 'bottomRightRadius'),
variables?.find(v => v.property === 'bottomLeftRadius')
];

if (cornerVars.some(v => v)) {
const cssValues = cornerVars.map(v => v?.value || '0');
const rawValues = cornerVars.map(v => v?.rawValue || '0');
if (cssValues.every(v => v === '0')) return null;

const valueType = rawValues.find(v => v !== '0')?.includes('%') ? '%' : 'px';
return optimizeRadiusValues(cssValues, rawValues, valueType);
}

// Handle node values
const corners = [
'topLeftRadius' in node ? node.topLeftRadius as number : 0,
'topRightRadius' in node ? node.topRightRadius as number : 0,
'bottomRightRadius' in node ? node.bottomRightRadius as number : 0,
'bottomLeftRadius' in node ? node.bottomLeftRadius as number : 0
];

if (corners.every(r => !r)) return null;

const cssValues = corners.map(radius =>
radius ? `${Math.round(radius)}px` : '0'
);

const valueType = cssValues.find(v => v !== '0')?.includes('%') ? '%' : 'px';
return optimizeRadiusValues(cssValues, cssValues, valueType);
};

const optimizeRadiusValues = (values: string[], rawValues: string[], valueType: 'px' | '%') => {
// If all values are the same, return single value
if (rawValues.every(v => v === rawValues[0])) {
return {
values: [values[0]],
rawValues: [rawValues[0]],
valueType
};
}

// Check for top-left-and-bottom-right | top-right-and-bottom-left pattern
if (rawValues[0] === rawValues[2] && rawValues[1] === rawValues[3]) {
return {
values: [values[0], values[1]],
rawValues: [rawValues[0], rawValues[1]],
valueType
};
}

// Check for top-left | top-right-and-bottom-left | bottom-right pattern
if (rawValues[1] === rawValues[3]) {
return {
values: [values[0], values[1], values[2]],
rawValues: [rawValues[0], rawValues[1], rawValues[2]],
valueType
};
}

// Return all four values if no pattern matches
return {
values,
rawValues,
valueType
};
};
4 changes: 2 additions & 2 deletions src/processors/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { StyleProcessor } from '../types/processors';
import { backgroundProcessor } from './background.processor';
import { backgroundProcessors } from './background.processor';
import { fontProcessors } from './font.processor';
import { layoutProcessors } from './layout.processor';
import { borderProcessors } from './border.processor';
Expand All @@ -16,7 +16,7 @@ export function getProcessorsForNode(node: SceneNode): StyleProcessor[] {
case "INSTANCE":
case "ELLIPSE":
return [
backgroundProcessor,
...backgroundProcessors,
...layoutProcessors,
...borderProcessors,
...spacingProcessors,
Expand Down
25 changes: 8 additions & 17 deletions src/services/variable.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { VariableToken } from '../types';
import { rgbaToString } from '../utils/color.utils';
import { sanitizeName } from '../utils/string.utils';
import { normalizeValue } from '../utils/value.utils';

async function getVariableFallback(variable: Variable | null, propertyName: string = ''): Promise<string> {
if (!variable) return '';
Expand All @@ -18,7 +20,10 @@ async function getVariableFallback(variable: Variable | null, propertyName: stri
switch (variable.resolvedType) {
case "FLOAT": {
const numValue = value as number;
return shouldHaveUnits(propertyName, numValue) ? `${numValue}px` : String(numValue);
return normalizeValue({
propertyName,
value: numValue
});
}
case "COLOR": {
if (typeof value === 'object' && 'r' in value) {
Expand All @@ -45,8 +50,8 @@ export async function collectBoundVariable(varId: string, property: string, path
type: 'variable',
path,
property,
name: variable.name,
value: `$${variable.name}`,
name: sanitizeName(variable.name),
value: `$${sanitizeName(variable.name)}`,
rawValue: rawValue.toLowerCase(),
valueType: valueType,
metadata: {
Expand All @@ -56,17 +61,3 @@ export async function collectBoundVariable(varId: string, property: string, path
}
};
}

function shouldHaveUnits(propertyName: string, value: number): boolean {
const unitlessProperties = ['font-weight', 'opacity'];
const propertyLower = propertyName.toLowerCase();

if (unitlessProperties.some(prop => propertyLower.includes(prop))) {
return false;
}
if (propertyLower.includes('line-height')) {
return value > 4;
}

return true;
}
Loading

0 comments on commit 441b64b

Please sign in to comment.