Skip to content

Commit 9a0aea0

Browse files
committed
Improve group and multi-selection
1 parent 14fa979 commit 9a0aea0

5 files changed

Lines changed: 84 additions & 50 deletions

File tree

apps/plugin/plugin-src/code.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ export const defaultPluginSettings: PluginSettings = {
2323
responsiveRoot: false,
2424
flutterGenerationMode: "snippet",
2525
swiftUIGenerationMode: "snippet",
26-
roundTailwindValues: false,
27-
roundTailwindColors: false,
28-
useColorVariables: false,
26+
roundTailwindValues: true,
27+
roundTailwindColors: true,
28+
useColorVariables: true,
2929
customTailwindPrefix: "",
3030
embedImages: false,
3131
embedVectors: false,

packages/backend/src/altNodes/jsonNodeConversion.ts

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { addWarning } from "../common/commonConversionWarnings";
22
import { PluginSettings } from "types";
33
import { variableToColorName } from "../tailwind/conversionTables";
44
import { HasGeometryTrait, Node, Paint } from "../api_types";
5+
import { calculateRectangleFromBoundingBox } from "../common/commonPosition";
56

67
// Performance tracking counters
78
export let getNodeByIdAsyncTime = 0;
@@ -196,7 +197,11 @@ const processNodePair = async (
196197
const nodeType = jsonNode.type;
197198

198199
// Store the cumulative rotation (parent's cumulative + node's own)
199-
jsonNode.cumulativeRotation = parentCumulativeRotation;
200+
if (parentNode) {
201+
// Only add cumulative when there is a parent. This is useful for the GROUP -> FRAME transformation, where
202+
// we want to move the rotation of the GROUP to children, but want to se FRAME to 0.
203+
jsonNode.cumulativeRotation = parentCumulativeRotation;
204+
}
200205

201206
// Handle empty frames and convert to rectangles
202207
if (
@@ -358,11 +363,33 @@ const processNodePair = async (
358363
}
359364

360365
// Always copy size and position
361-
if ("width" in figmaNode) {
362-
jsonNode.width = figmaNode.width;
363-
jsonNode.height = figmaNode.height;
364-
jsonNode.x = figmaNode.x;
365-
jsonNode.y = figmaNode.y;
366+
if ("absoluteBoundingBox" in jsonNode && jsonNode.absoluteBoundingBox) {
367+
if (jsonNode.parent) {
368+
// Extract width and height from bounding box and rotation. This is necessary because Figma JSON API doesn't have width and height.
369+
const rect = calculateRectangleFromBoundingBox(
370+
{
371+
width: jsonNode.absoluteBoundingBox.width,
372+
height: jsonNode.absoluteBoundingBox.height,
373+
x:
374+
jsonNode.absoluteBoundingBox.x -
375+
(jsonNode.parent?.absoluteBoundingBox.x || 0),
376+
y:
377+
jsonNode.absoluteBoundingBox.y -
378+
(jsonNode.parent?.absoluteBoundingBox.y || 0),
379+
},
380+
-((jsonNode.rotation || 0) + (jsonNode.cumulativeRotation || 0)),
381+
);
382+
383+
jsonNode.width = rect.width;
384+
jsonNode.height = rect.height;
385+
jsonNode.x = rect.left;
386+
jsonNode.y = rect.top;
387+
} else {
388+
jsonNode.width = jsonNode.absoluteBoundingBox.width;
389+
jsonNode.height = jsonNode.absoluteBoundingBox.height;
390+
jsonNode.x = 0;
391+
jsonNode.y = 0;
392+
}
366393
}
367394

368395
if ("individualStrokeWeights" in jsonNode) {
@@ -425,8 +452,6 @@ const processNodePair = async (
425452
"children" in figmaNode &&
426453
figmaNode.children.length === jsonNode.children.length
427454
) {
428-
console.log("cumulative", parentCumulativeRotation);
429-
430455
const cumulative =
431456
parentCumulativeRotation +
432457
(jsonNode.type === "GROUP" ? jsonNode.rotation || 0 : 0);
@@ -489,21 +514,38 @@ export const nodesToJSON = async (
489514
): Promise<Node[]> => {
490515
// Reset name counters for each conversion
491516
nodeNameCounters.clear();
492-
493517
const exportJsonStart = Date.now();
494-
// First get the JSON representation of nodes
495-
const nodeJson = (await Promise.all(
496-
nodes.map(
497-
async (node) =>
498-
(
499-
(await node.exportAsync({
500-
format: "JSON_REST_V1",
501-
})) as any
502-
).document,
503-
),
504-
)) as Node[];
505-
506-
console.log("[debug] initial nodeJson", { ...nodeJson[0] });
518+
// First get the JSON representation of nodes with rotation handling
519+
const nodeResults = await Promise.all(
520+
nodes.map(async (node) => {
521+
// Export node to JSON
522+
const nodeDoc = (
523+
(await node.exportAsync({
524+
format: "JSON_REST_V1",
525+
})) as any
526+
).document;
527+
528+
let nodeCumulativeRotation = 0;
529+
530+
// Wire GROUPs into FRAME.
531+
if (node.type === "GROUP") {
532+
nodeDoc.type = "FRAME";
533+
534+
// Fix rotation for children.
535+
if ("rotation" in nodeDoc && nodeDoc.rotation) {
536+
nodeCumulativeRotation = -nodeDoc.rotation * (180 / Math.PI);
537+
nodeDoc.rotation = 0;
538+
}
539+
}
540+
541+
return {
542+
nodeDoc,
543+
nodeCumulativeRotation,
544+
};
545+
}),
546+
);
547+
548+
console.log("[debug] initial nodeJson", { ...nodes[0] });
507549

508550
console.log(
509551
`[benchmark][inside nodesToJSON] JSON_REST_V1 export: ${Date.now() - exportJsonStart}ms`,
@@ -515,9 +557,11 @@ export const nodesToJSON = async (
515557

516558
for (let i = 0; i < nodes.length; i++) {
517559
const processedNode = await processNodePair(
518-
nodeJson[i],
560+
nodeResults[i].nodeDoc,
519561
nodes[i],
520562
settings,
563+
undefined,
564+
nodeResults[i].nodeCumulativeRotation,
521565
);
522566
if (processedNode !== null) {
523567
if (Array.isArray(processedNode)) {

packages/backend/src/code.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ export const run = async (settings: PluginSettings) => {
3333
if (selection.length === 0) {
3434
postEmptyMessage();
3535
return;
36-
} else if (selection.length > 1) {
37-
addWarning(
38-
"Ungrouped elements may have incorrect positioning. If this happens, try wrapping the selection in a Frame or Group.",
39-
);
4036
}
4137

4238
// Timing with Date.now() instead of console.time

packages/backend/src/common/commonPosition.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,15 @@ export const getCommonPositionValue = (
55
settings?: HTMLSettings | TailwindSettings,
66
): { x: number; y: number } => {
77
if (node.parent && node.parent.absoluteBoundingBox) {
8-
const x = node.absoluteBoundingBox.x - node.parent.absoluteBoundingBox.x;
9-
const y = node.absoluteBoundingBox.y - node.parent.absoluteBoundingBox.y;
10-
118
if (settings?.embedVectors && node.svg) {
129
// When embedding vectors, we need to use the absolute position, since it already includes the rotation.
13-
return { x: x, y: y };
10+
return {
11+
x: node.absoluteBoundingBox.x - node.parent.absoluteBoundingBox.x,
12+
y: node.absoluteBoundingBox.y - node.parent.absoluteBoundingBox.y,
13+
};
1414
}
1515

16-
const rect = calculateRectangleFromBoundingBox(
17-
{
18-
width: node.absoluteBoundingBox.width,
19-
height: node.absoluteBoundingBox.height,
20-
x: x,
21-
y: y,
22-
},
23-
-((node.rotation || 0) + (node.cumulativeRotation || 0)),
24-
);
25-
26-
return { x: rect.left, y: rect.top };
16+
return { x: node.x, y: node.y };
2717
}
2818

2919
if (node.parent && node.parent.type === "GROUP") {
@@ -54,7 +44,7 @@ interface RectangleStyle {
5444
rotation: number; // Rotation in degrees
5545
}
5646

57-
function calculateRectangleFromBoundingBox(
47+
export function calculateRectangleFromBoundingBox(
5848
boundingBox: BoundingBox,
5949
figmaRotationDegrees: number,
6050
): RectangleStyle {

packages/backend/src/html/htmlMain.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -359,19 +359,23 @@ export const generateHTMLPreview = async (
359359
nodes: SceneNode[],
360360
settings: PluginSettings,
361361
): Promise<HTMLPreview> => {
362-
const result = await htmlMain(
362+
let result = await htmlMain(
363363
nodes,
364364
{
365365
...settings,
366366
htmlGenerationMode: "html",
367367
},
368-
true,
368+
nodes.length > 1 ? false : true,
369369
);
370370

371+
if (nodes.length > 1) {
372+
result.html = `<div style="width: 100%; height: 100%">${result.html}</div>`;
373+
}
374+
371375
return {
372376
size: {
373-
width: nodes[0].width,
374-
height: nodes[0].height,
377+
width: Math.max(...nodes.map((node) => node.width)),
378+
height: nodes.reduce((sum, node) => sum + node.height, 0),
375379
},
376380
content: result.html,
377381
};

0 commit comments

Comments
 (0)