diff --git a/README.md b/README.md index 2a943e9d..7bd98db6 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ When finding the unknown (a `Group` or `Frame` with more than one child and no v ### Flutter limits and ideas -- **Unformatted code:** output code is not formatted, but even [dartpad](https://dartpad.dev/) offers a format button. - **Stack:** in some simpler cases, a `Stack` could be replaced with a `Container` and a `BoxDecoration`. Discover those cases and optimize them. - **Material Styles**: text could be matched to existing Material styles (like outputting `Headline6` when text size is 20). - **Identify Buttons**: the plugin could identify specific buttons and output them instead of always using `Container` or `Material`. diff --git a/__tests__/altNodes/altConversions.test.ts b/__tests__/altNodes/altConversions.test.ts index 8c22fcf3..d87b27c2 100644 --- a/__tests__/altNodes/altConversions.test.ts +++ b/__tests__/altNodes/altConversions.test.ts @@ -111,6 +111,26 @@ describe("AltConversions", () => { ).toEqual(`
`); }); + it("Line", () => { + // this test requires mocking the full EllipseNode. Figma-api-stub doesn't support VectorNode. + class LineNode { + readonly type = "LINE"; + } + + interface LineNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin { + readonly type: "LINE"; + clone(): LineNode; + } + + const node = new LineNode(); + // set read-only variables + Object.defineProperty(node, "width", { value: 20 }); + + expect( + tailwindMain(convertIntoAltNodes([node], new AltFrameNode())) + ).toEqual(`
`); + }); + it("Vector", () => { // this test requires mocking the full VectorNode. Figma-api-stub doesn't support VectorNode. class VectorNode { @@ -137,6 +157,4 @@ describe("AltConversions", () => { tailwindMain(convertIntoAltNodes([node], new AltFrameNode())) ).toEqual(`
`); }); - - // todo add a test for EllipseNode, but there is no EllipseNode in figma-api-stubs! }); diff --git a/__tests__/flutter/builderImpl/flutterBorder.test.ts b/__tests__/flutter/builderImpl/flutterBorder.test.ts index cee18887..c088fc81 100644 --- a/__tests__/flutter/builderImpl/flutterBorder.test.ts +++ b/__tests__/flutter/builderImpl/flutterBorder.test.ts @@ -21,7 +21,7 @@ describe("Flutter Border", () => { node.cornerRadius = 2; expect(flutterBorderRadius(node)).toEqual( - "borderRadius: BorderRadius.circular(2), " + "\nborderRadius: BorderRadius.circular(2)," ); node.cornerRadius = figma.mixed; @@ -30,7 +30,7 @@ describe("Flutter Border", () => { node.bottomLeftRadius = 0; node.bottomRightRadius = 0; expect(flutterBorderRadius(node)).toEqual( - "borderRadius: BorderRadius.only(topLeft: Radius.circular(2), topRight: Radius.circular(0), bottomLeft: Radius.circular(0), bottomRight: Radius.circular(0), ), " + "\nborderRadius: BorderRadius.only(topLeft: Radius.circular(2), topRight: Radius.circular(0), bottomLeft: Radius.circular(0), bottomRight: Radius.circular(0), )," ); const ellipseNode = new AltEllipseNode(); @@ -47,7 +47,7 @@ describe("Flutter Border", () => { }, ]; expect(flutterBorder(node)).toEqual( - "border: Border.all(color: Colors.black, width: 2,), " + "\nborder: Border.all(color: Colors.black, width: 2, )," ); node.strokeWeight = 0; @@ -65,7 +65,9 @@ describe("Flutter Border", () => { node.bottomLeftRadius = 0; node.bottomRightRadius = 0; expect(flutterShape(node)).toEqual( - "shape: RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(4), topRight: Radius.circular(0), bottomLeft: Radius.circular(0), bottomRight: Radius.circular(0), ), )," + `\nshape: RoundedRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(4), topRight: Radius.circular(0), bottomLeft: Radius.circular(0), bottomRight: Radius.circular(0), ), +),` ); const ellipseNode = new AltEllipseNode(); @@ -77,7 +79,9 @@ describe("Flutter Border", () => { }, ]; expect(flutterShape(ellipseNode)).toEqual( - "shape: CircleBorder(side: BorderSide(width: 4, color: Color(0xff3f3f3f), ), ), " + `\nshape: CircleBorder( + side: BorderSide(width: 4, color: Color(0xff3f3f3f), ), +),` ); }); }); diff --git a/__tests__/flutter/builderImpl/flutterColor.test.ts b/__tests__/flutter/builderImpl/flutterColor.test.ts index 332291ac..017e867e 100644 --- a/__tests__/flutter/builderImpl/flutterColor.test.ts +++ b/__tests__/flutter/builderImpl/flutterColor.test.ts @@ -24,8 +24,8 @@ describe("Flutter Color", () => { }, ]; - expect(flutterColorFromFills(node.fills)).toEqual( - "color: Color(0xffef5138), " + expect(flutterBoxDecorationColor(node.fills)).toEqual( + "\ncolor: Color(0xffef5138)," ); }); @@ -44,7 +44,7 @@ describe("Flutter Color", () => { }, ]; - expect(flutterColorFromFills(node.fills)).toEqual("color: Colors.black, "); + expect(flutterColorFromFills(node.fills)).toEqual("color: Colors.black,"); node.fills = [ { @@ -58,7 +58,7 @@ describe("Flutter Color", () => { }, ]; - expect(flutterColorFromFills(node.fills)).toEqual("color: Colors.white, "); + expect(flutterColorFromFills(node.fills)).toEqual("color: Colors.white,"); }); it("opacity and visibility changes", () => { @@ -92,7 +92,7 @@ describe("Flutter Color", () => { ]; // this scenario should never happen in real life; figma allows undefined to be set, but not to be get. - expect(flutterColorFromFills(node.fills)).toEqual("color: Colors.black, "); + expect(flutterColorFromFills(node.fills)).toEqual("color: Colors.black,"); node.fills = [ { @@ -106,8 +106,8 @@ describe("Flutter Color", () => { visible: true, }, ]; - expect(flutterColorFromFills(node.fills)).toEqual( - "color: Color(0x00000000), " + expect(flutterBoxDecorationColor(node.fills)).toEqual( + "\ncolor: Color(0x00000000)," ); }); @@ -135,7 +135,7 @@ describe("Flutter Color", () => { node.fills = [gradientFill]; expect(flutterBoxDecorationColor(node.fills)).toEqual( - "gradient: LinearGradient(begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [Colors.black], ), " + "\ngradient: LinearGradient(begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [Colors.black], )," ); // topLeft to bottomRight (135) @@ -144,7 +144,7 @@ describe("Flutter Color", () => { [1.3402682542800903, -1.4652644395828247, 0.5407097935676575], ]); expect(flutterBoxDecorationColor(node.fills)).toEqual( - "gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.black], ), " + "\ngradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.black], )," ); // bottom to top (-90) @@ -153,7 +153,7 @@ describe("Flutter Color", () => { [-2.3507132530212402, -1.0997783306265774e-7, 1.6796307563781738], ]); expect(flutterBoxDecorationColor(node.fills)).toEqual( - "gradient: LinearGradient(begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [Colors.black], ), " + "\ngradient: LinearGradient(begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [Colors.black], )," ); // top to bottom (90) @@ -162,7 +162,7 @@ describe("Flutter Color", () => { [3.9725232124328613, -1.4210854715202004e-14, -0.8289895057678223], ]); expect(flutterBoxDecorationColor(node.fills)).toEqual( - "gradient: LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.black], ), " + "\ngradient: LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.black], )," ); // left to right (0) @@ -171,7 +171,7 @@ describe("Flutter Color", () => { [6.030897026221282e-8, -3.364259719848633, 2.188383102416992], ]); expect(flutterBoxDecorationColor(node.fills)).toEqual( - "gradient: LinearGradient(begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [Colors.black], ), " + "\ngradient: LinearGradient(begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [Colors.black], )," ); // right to left (180) @@ -180,7 +180,7 @@ describe("Flutter Color", () => { [0.07747448235750198, 4.357592582702637, -1.0299113988876343], ]); expect(flutterBoxDecorationColor(node.fills)).toEqual( - "gradient: LinearGradient(begin: Alignment.centerRight, end: Alignment.centerLeft, colors: [Colors.black], ), " + "\ngradient: LinearGradient(begin: Alignment.centerRight, end: Alignment.centerLeft, colors: [Colors.black], )," ); // bottom left to top right (-135) @@ -189,7 +189,7 @@ describe("Flutter Color", () => { [-3.7344324588775635, 2.3110527992248535, 0.4661891460418701], ]); expect(flutterBoxDecorationColor(node.fills)).toEqual( - "gradient: LinearGradient(begin: Alignment.bottomRight, end: Alignment.topLeft, colors: [Colors.black], ), " + "\ngradient: LinearGradient(begin: Alignment.bottomRight, end: Alignment.topLeft, colors: [Colors.black], )," ); // bottom left to top right (-45) @@ -198,7 +198,7 @@ describe("Flutter Color", () => { [-1.3051068782806396, -1.3525396585464478, 1.8345310688018799], ]); expect(flutterBoxDecorationColor(node.fills)).toEqual( - "gradient: LinearGradient(begin: Alignment.bottomLeft, end: Alignment.topRight, colors: [Colors.black], ), " + "\ngradient: LinearGradient(begin: Alignment.bottomLeft, end: Alignment.topRight, colors: [Colors.black], )," ); // top right to bottom left (-45) @@ -207,7 +207,7 @@ describe("Flutter Color", () => { [1.5028705596923828, 1.2872726917266846, -1.0877336263656616], ]); expect(flutterBoxDecorationColor(node.fills)).toEqual( - "gradient: LinearGradient(begin: Alignment.topRight, end: Alignment.bottomLeft, colors: [Colors.black], ), " + "\ngradient: LinearGradient(begin: Alignment.topRight, end: Alignment.bottomLeft, colors: [Colors.black], )," ); }); @@ -256,7 +256,15 @@ describe("Flutter Color", () => { node.cornerRadius = 16; expect(flutterMain([node])).toEqual( - `Container(width: 18, height: 18, decoration: BoxDecoration(borderRadius: BorderRadius.circular(16), border: Border.all(color: Color(0xff3f3f3f), width: 4,), gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.black, Color(0xffff0000)], ), ), )` + `Container( + width: 18, + height: 18, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Color(0xff3f3f3f), width: 4, ), + gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.black, Color(0xffff0000)], ), + ), +)` ); }); diff --git a/__tests__/flutter/builderImpl/flutterPadding.test.ts b/__tests__/flutter/builderImpl/flutterPadding.test.ts index 15a1ae65..4918c01f 100644 --- a/__tests__/flutter/builderImpl/flutterPadding.test.ts +++ b/__tests__/flutter/builderImpl/flutterPadding.test.ts @@ -17,7 +17,7 @@ describe("Flutter Padding", () => { frameNode.paddingTop = 2; frameNode.paddingBottom = 2; expect(flutterPadding(frameNode)).toEqual( - "padding: const EdgeInsets.all(2), " + "\npadding: const EdgeInsets.all(2)," ); frameNode.paddingLeft = 1; @@ -25,7 +25,7 @@ describe("Flutter Padding", () => { frameNode.paddingTop = 3; frameNode.paddingBottom = 4; expect(flutterPadding(frameNode)).toEqual( - "padding: const EdgeInsets.only(left: 1, right: 2, top: 3, bottom: 4, ), " + "\npadding: const EdgeInsets.only(left: 1, right: 2, top: 3, bottom: 4, )," ); frameNode.paddingLeft = 2; @@ -33,7 +33,7 @@ describe("Flutter Padding", () => { frameNode.paddingTop = 4; frameNode.paddingBottom = 4; expect(flutterPadding(frameNode)).toEqual( - "padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 4, ), " + "\npadding: const EdgeInsets.symmetric(horizontal: 2, vertical: 4, )," ); frameNode.paddingLeft = 2; @@ -41,7 +41,7 @@ describe("Flutter Padding", () => { frameNode.paddingTop = 0; frameNode.paddingBottom = 0; expect(flutterPadding(frameNode)).toEqual( - "padding: const EdgeInsets.symmetric(horizontal: 2, ), " + "\npadding: const EdgeInsets.symmetric(horizontal: 2, )," ); frameNode.paddingLeft = 0; @@ -49,7 +49,7 @@ describe("Flutter Padding", () => { frameNode.paddingTop = 2; frameNode.paddingBottom = 2; expect(flutterPadding(frameNode)).toEqual( - "padding: const EdgeInsets.symmetric(vertical: 2, ), " + "\npadding: const EdgeInsets.symmetric(vertical: 2, )," ); frameNode.paddingLeft = 0; diff --git a/__tests__/flutter/builderImpl/flutterPosition.test.ts b/__tests__/flutter/builderImpl/flutterPosition.test.ts index 438cdd56..e52ad6c7 100644 --- a/__tests__/flutter/builderImpl/flutterPosition.test.ts +++ b/__tests__/flutter/builderImpl/flutterPosition.test.ts @@ -70,84 +70,141 @@ describe("Flutter Position", () => { node.x = 37; node.y = 37; expect(flutterPosition(node, "child")).toEqual( - "Positioned.fill(child: Align(alignment: Alignment.center, child: child),)," + `Positioned.fill( + child: Align( + alignment: Alignment.center, + child: child + ), +),` ); // top-left node.x = 0; node.y = 0; expect(flutterPosition(node, "child")).toEqual( - "Positioned.fill(child: Align(alignment: Alignment.topLeft, child: child),)," + `Positioned.fill( + child: Align( + alignment: Alignment.topLeft, + child: child + ), +),` ); // top-right node.x = 75; node.y = 0; expect(flutterPosition(node, "child")).toEqual( - "Positioned.fill(child: Align(alignment: Alignment.topRight, child: child),)," + `Positioned.fill( + child: Align( + alignment: Alignment.topRight, + child: child + ), +),` ); // bottom-left node.x = 0; node.y = 75; expect(flutterPosition(node, "child")).toEqual( - "Positioned.fill(child: Align(alignment: Alignment.bottomLeft, child: child),)," + `Positioned.fill( + child: Align( + alignment: Alignment.bottomLeft, + child: child + ), +),` ); // bottom-right node.x = 75; node.y = 75; expect(flutterPosition(node, "child")).toEqual( - "Positioned.fill(child: Align(alignment: Alignment.bottomRight, child: child),)," + `Positioned.fill( + child: Align( + alignment: Alignment.bottomRight, + child: child + ), +),` ); // top-center node.x = 37; node.y = 0; expect(flutterPosition(node, "child")).toEqual( - "Positioned.fill(child: Align(alignment: Alignment.topCenter, child: child),)," + `Positioned.fill( + child: Align( + alignment: Alignment.topCenter, + child: child + ), +),` ); // left-center node.x = 0; node.y = 37; expect(flutterPosition(node, "child")).toEqual( - "Positioned.fill(child: Align(alignment: Alignment.centerLeft, child: child),)," + `Positioned.fill( + child: Align( + alignment: Alignment.centerLeft, + child: child + ), +),` ); // bottom-center node.x = 37; node.y = 75; expect(flutterPosition(node, "child")).toEqual( - "Positioned.fill(child: Align(alignment: Alignment.bottomCenter, child: child),)," + `Positioned.fill( + child: Align( + alignment: Alignment.bottomCenter, + child: child + ), +),` ); // right-center node.x = 75; node.y = 37; expect(flutterPosition(node, "child")).toEqual( - "Positioned.fill(child: Align(alignment: Alignment.centerRight, child: child),)," + `Positioned.fill( + child: Align( + alignment: Alignment.centerRight, + child: child + ), +),` ); // center Y, random X node.x = 22; node.y = 37; expect(flutterPosition(node, "child")).toEqual( - "Positioned(left: 22, top: 37, child: child)," + `Positioned( + left: 22, + top: 37, + child: child +),` ); // center X, random Y node.x = 37; node.y = 22; expect(flutterPosition(node, "child")).toEqual( - "Positioned(left: 37, top: 22, child: child)," + `Positioned( + left: 37, + top: 22, + child: child +),` ); // without position node.x = 45; node.y = 88; expect(flutterPosition(node, "child")).toEqual( - "Positioned(left: 45, top: 88, child: child)," + `Positioned( + left: 45, + top: 88, + child: child +),` ); }); diff --git a/__tests__/flutter/builderImpl/flutterShadow.test.ts b/__tests__/flutter/builderImpl/flutterShadow.test.ts index 166dfcb5..64ae5602 100644 --- a/__tests__/flutter/builderImpl/flutterShadow.test.ts +++ b/__tests__/flutter/builderImpl/flutterShadow.test.ts @@ -20,15 +20,34 @@ describe("Flutter Shadow", () => { radius: 4, visible: true, }, + { + type: "DROP_SHADOW", + blendMode: "NORMAL", + color: { r: 1, g: 1, b: 0, a: 0.25 }, + offset: { x: 4, y: 4 }, + radius: 8, + visible: true, + }, ]; expect(flutterBoxShadow(node)).toEqual( - "boxShadow: [ BoxShadow(color: Color(0x3f000000), blurRadius: 4, offset: Offset(0, 4), ), ], " + `\nboxShadow: [ + BoxShadow( + color: Color(0x3f000000), + blurRadius: 4, + offset: Offset(0, 4), + ), + BoxShadow( + color: Color(0x3fffff00), + blurRadius: 8, + offset: Offset(4, 4), + ), +],` ); const [elev, color] = flutterElevationAndShadowColor(node); - expect(elev).toEqual("elevation: 4, "); - expect(color).toEqual("color: Color(0x3f000000), "); + expect(elev).toEqual("\nelevation: 4, "); + expect(color).toEqual("\ncolor: Color(0x3f000000), "); }); it("inner shadow", () => { diff --git a/__tests__/flutter/builderImpl/flutterSize.test.ts b/__tests__/flutter/builderImpl/flutterSize.test.ts index 4dd3f05a..6f9b4a2d 100644 --- a/__tests__/flutter/builderImpl/flutterSize.test.ts +++ b/__tests__/flutter/builderImpl/flutterSize.test.ts @@ -15,15 +15,15 @@ describe("Flutter Size", () => { node.width = 16; node.height = 16; - expect(flutterSize(node).size).toEqual("width: 16, height: 16, "); + expect(flutterSize(node).size).toEqual("\nwidth: 16,\nheight: 16,"); node.width = 100; node.height = 200; - expect(flutterSize(node).size).toEqual("width: 100, height: 200, "); + expect(flutterSize(node).size).toEqual("\nwidth: 100,\nheight: 200,"); node.width = 300; node.height = 300; - expect(flutterSize(node).size).toEqual("width: 300, height: 300, "); + expect(flutterSize(node).size).toEqual("\nwidth: 300,\nheight: 300,"); }); it("STRETCH inside AutoLayout", () => { @@ -45,13 +45,13 @@ describe("Flutter Size", () => { node.children = [child]; const fSize1 = flutterSize(child); - expect(fSize1.size).toEqual("height: double.infinity, "); + expect(fSize1.size).toEqual("\nheight: double.infinity,"); expect(fSize1.isExpanded).toEqual(true); node.layoutMode = "VERTICAL"; const fSize2 = flutterSize(child); - expect(fSize2.size).toEqual("width: double.infinity, "); + expect(fSize2.size).toEqual("\nwidth: double.infinity,"); expect(fSize2.isExpanded).toEqual(true); }); @@ -62,7 +62,7 @@ describe("Flutter Size", () => { node.height = 48; node.children = [new AltRectangleNode(), new AltRectangleNode()]; - expect(flutterSize(node).size).toEqual("width: 48, height: 48, "); + expect(flutterSize(node).size).toEqual("\nwidth: 48,\nheight: 48,"); }); it("counterAxisSizingMode is FIXED", () => { @@ -73,13 +73,13 @@ describe("Flutter Size", () => { node.children = [new AltRectangleNode(), new AltRectangleNode()]; node.layoutMode = "HORIZONTAL"; - expect(flutterSize(node).size).toEqual("height: 48, "); + expect(flutterSize(node).size).toEqual("\nheight: 48,"); node.layoutMode = "VERTICAL"; - expect(flutterSize(node).size).toEqual("width: 48, "); + expect(flutterSize(node).size).toEqual("\nwidth: 48,"); node.layoutMode = "NONE"; - expect(flutterSize(node).size).toEqual("width: 48, height: 48, "); + expect(flutterSize(node).size).toEqual("\nwidth: 48,\nheight: 48,"); }); it("counterAxisSizingMode is AUTO", () => { @@ -106,7 +106,7 @@ describe("Flutter Size", () => { parentNode.children = [node]; node.parent = parentNode; expect(flutterSize(node).size).toEqual(""); - expect(flutterSize(parentNode).size).toEqual("width: 48, height: 48, "); + expect(flutterSize(parentNode).size).toEqual("\nwidth: 48,\nheight: 48,"); }); it("width changes when there are strokes", () => { @@ -116,7 +116,7 @@ describe("Flutter Size", () => { node.width = 8; node.height = 8; - expect(flutterSize(node).size).toEqual("width: 8, height: 8, "); + expect(flutterSize(node).size).toEqual("\nwidth: 8,\nheight: 8,"); node.strokeWeight = 4; node.strokes = [ @@ -127,10 +127,10 @@ describe("Flutter Size", () => { ]; node.strokeAlign = "CENTER"; - expect(flutterSize(node).size).toEqual("width: 12, height: 12, "); + expect(flutterSize(node).size).toEqual("\nwidth: 12,\nheight: 12,"); node.strokeAlign = "OUTSIDE"; - expect(flutterSize(node).size).toEqual("width: 16, height: 16, "); + expect(flutterSize(node).size).toEqual("\nwidth: 16,\nheight: 16,"); }); it("adjust parent if children's size + stroke > parent size", () => { @@ -155,12 +155,12 @@ describe("Flutter Size", () => { const fSize1 = flutterSize(parentNode); - expect(fSize1.size).toEqual("width: 16, height: 16, "); + expect(fSize1.size).toEqual("\nwidth: 16,\nheight: 16,"); expect(fSize1.isExpanded).toEqual(false); node.strokeAlign = "CENTER"; const fSize2 = flutterSize(parentNode); - expect(fSize2.size).toEqual("width: 12, height: 12, "); + expect(fSize2.size).toEqual("\nwidth: 12,\nheight: 12,"); expect(fSize2.isExpanded).toEqual(false); }); @@ -179,7 +179,7 @@ describe("Flutter Size", () => { parentNode.children = [node]; - expect(flutterSize(parentNode).size).toEqual("width: 12, height: 12, "); - expect(flutterSize(node).size).toEqual("width: 12, height: 12, "); + expect(flutterSize(parentNode).size).toEqual("\nwidth: 12,\nheight: 12,"); + expect(flutterSize(node).size).toEqual("\nwidth: 12,\nheight: 12,"); }); }); diff --git a/__tests__/flutter/flutterContainer.test.ts b/__tests__/flutter/flutterContainer.test.ts index e7758f1f..4f7c8a61 100644 --- a/__tests__/flutter/flutterContainer.test.ts +++ b/__tests__/flutter/flutterContainer.test.ts @@ -37,8 +37,21 @@ describe("Flutter Container", () => { parent.children = [node]; node.parent = parent; - expect(flutterContainer(parent, "")).toEqual(` -Padding(padding: const EdgeInsets.all(10), ),`); + expect(flutterContainer(parent, "")).toEqual(`Padding( + padding: const EdgeInsets.all(10), +),`); + + node.layoutGrow = 1; + node.layoutAlign = "STRETCH"; + + parent.primaryAxisSizingMode = "FIXED"; + parent.counterAxisSizingMode = "FIXED"; + + expect(flutterContainer(node, "")).toEqual(`Expanded( + child: Container( + height: double.infinity, + ), +),`); }); it("standard scenario", () => { @@ -46,11 +59,16 @@ Padding(padding: const EdgeInsets.all(10), ),`); node.width = 10; node.height = 10; - expect(flutterContainer(node, "")).toEqual(` -Container(width: 10, height: 10, ),`); + expect(flutterContainer(node, "")).toEqual(`Container( + width: 10, + height: 10, +),`); - expect(flutterContainer(node, "child")).toEqual(` -Container(width: 10, height: 10, child: child),`); + expect(flutterContainer(node, "child")).toEqual(`Container( + width: 10, + height: 10, + child: child +),`); }); it("ellipse", () => { @@ -58,7 +76,12 @@ Container(width: 10, height: 10, child: child),`); node.width = 10; node.height = 10; - expect(flutterContainer(node, "")).toEqual(` -Container(width: 10, height: 10, decoration: BoxDecoration(shape: BoxShape.circle, ), ),`); + expect(flutterContainer(node, "")).toEqual(`Container( + width: 10, + height: 10, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), +),`); }); }); diff --git a/__tests__/flutter/flutterMain.test.ts b/__tests__/flutter/flutterMain.test.ts index ca63636e..8efc1180 100644 --- a/__tests__/flutter/flutterMain.test.ts +++ b/__tests__/flutter/flutterMain.test.ts @@ -51,9 +51,31 @@ describe("Flutter Main", () => { child2.parent = node; expect(flutterMain([convertToAutoLayout(node)], "", false)) - .toEqual(`Container(width: 32, height: 32, child: Stack(children:[Positioned(left: 9, top: 9, child: -Container(width: 4, height: 4, color: Colors.white, ),),Positioned(left: 9, top: 9, child: -Container(width: 4, height: 4, ),),],),)`); + .toEqual(`Container( + width: 32, + height: 32, + child: Stack( + children:[ + Positioned( + left: 9, + top: 9, + child: Container( + width: 4, + height: 4, + color: Colors.white, + ), + ), + Positioned( + left: 9, + top: 9, + child: Container( + width: 4, + height: 4, + ), + ), + ], + ), +)`); }); it("children is larger than 384", () => { @@ -91,10 +113,31 @@ Container(width: 4, height: 4, ),),],),)`); child1.parent = node; child2.parent = node; - expect(flutterMain([convertToAutoLayout(node)])) - .toEqual(`Container(width: 420, height: 420, child: Stack(children:[Positioned(left: 9, top: 9, child: -Container(width: 385, height: 8, color: Colors.white, ),),Positioned(left: 9, top: 9, child: -Container(width: 8, height: 385, ),),],),)`); + expect(flutterMain([convertToAutoLayout(node)])).toEqual(`Container( + width: 420, + height: 420, + child: Stack( + children:[ + Positioned( + left: 9, + top: 9, + child: Container( + width: 385, + height: 8, + color: Colors.white, + ), + ), + Positioned( + left: 9, + top: 9, + child: Container( + width: 8, + height: 385, + ), + ), + ], + ), +)`); }); it("Group with relative position", () => { @@ -127,9 +170,21 @@ Container(width: 8, height: 385, ),),],),)`); node.children = [child]; child.parent = node; - expect(flutterMain([node])) - .toEqual(`Container(width: 32, height: 32, child: Stack(children:[Positioned(left: 9, top: 9, child: -Container(width: 4, height: 4, color: Colors.white, ),),],),)`); + expect(flutterMain([node])).toEqual(`Container( + width: 32, + height: 32, + child: Stack( + children:[Positioned( + left: 9, + top: 9, + child: Container( + width: 4, + height: 4, + color: Colors.white, + ), + ),], + ), +)`); }); it("Row and Column with 2 children", () => { @@ -184,40 +239,93 @@ Container(width: 4, height: 4, color: Colors.white, ),),],),)`); child1.parent = node; child2.parent = node; - expect(flutterMain([node])) - .toEqual(`Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children:[ -Container(width: 8, height: 8, color: Colors.white, ), -SizedBox(width: 8), -Container(width: 8, height: 8, color: Colors.black, ),], ),`); + expect(flutterMain([node])).toEqual(`Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children:[ + Container( + width: 8, + height: 8, + color: Colors.white, + ), + SizedBox(width: 8), + Container( + width: 8, + height: 8, + color: Colors.black, + ), + ], +)`); // variations for test coverage node.layoutMode = "VERTICAL"; + node.layoutGrow = 1; node.primaryAxisAlignItems = "CENTER"; node.counterAxisAlignItems = "CENTER"; - expect(flutterMain([node])) - .toEqual(`Column(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children:[ -Container(width: 8, height: 8, color: Colors.white, ), -SizedBox(height: 8), -Container(width: 8, height: 8, color: Colors.black, ),], ),`); + expect(flutterMain([node])).toEqual(`Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children:[ + Container( + width: 8, + height: 8, + color: Colors.white, + ), + SizedBox(height: 8), + Container( + width: 8, + height: 8, + color: Colors.black, + ), + ], +)`); node.primaryAxisAlignItems = "MAX"; node.counterAxisAlignItems = "MAX"; - expect(flutterMain([node])) - .toEqual(`Column(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children:[ -Container(width: 8, height: 8, color: Colors.white, ), -SizedBox(height: 8), -Container(width: 8, height: 8, color: Colors.black, ),], ),`); + expect(flutterMain([node])).toEqual(`Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children:[ + Container( + width: 8, + height: 8, + color: Colors.white, + ), + SizedBox(height: 8), + Container( + width: 8, + height: 8, + color: Colors.black, + ), + ], +)`); node.primaryAxisAlignItems = "SPACE_BETWEEN"; node.counterAxisAlignItems = "CENTER"; - expect(flutterMain([node])) - .toEqual(`Column(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children:[ -Container(width: 8, height: 8, color: Colors.white, ), -SizedBox(height: 8), -Container(width: 8, height: 8, color: Colors.black, ),], ),`); + expect(flutterMain([node])).toEqual(`Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children:[ + Container( + width: 8, + height: 8, + color: Colors.white, + ), + SizedBox(height: 8), + Container( + width: 8, + height: 8, + color: Colors.black, + ), + ], +)`); }); it("Row with 1 children", () => { @@ -285,10 +393,32 @@ Container(width: 8, height: 8, color: Colors.black, ),], ),`); node.children = [child1, child2]; expect(flutterMain([node], "", true)).toEqual( - `SizedBox(width: 32, child: Material(color: Colors.white, child: Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children:[ -Expanded(child: SizedBox(height: double.infinity, child: Material(color: Colors.white, ), ), ), -SizedBox(width: 8), -Container(width: 8, height: 8, ),], ), ), ),` + `SizedBox( + width: 32, + child: Material( + color: Colors.white, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children:[ + Expanded( + child: SizedBox( + height: double.infinity, + child: Material( + color: Colors.white, + ), + ), + ), + SizedBox(width: 8), + Container( + width: 8, + height: 8, + ), + ], + ), + ), +)` ); }); }); diff --git a/__tests__/flutter/flutterMaterial.test.ts b/__tests__/flutter/flutterMaterial.test.ts index f774d479..86ef3949 100644 --- a/__tests__/flutter/flutterMaterial.test.ts +++ b/__tests__/flutter/flutterMaterial.test.ts @@ -12,8 +12,9 @@ describe("Flutter Material", () => { const node = new AltRectangleNode(); // undefined (unitialized, only happen on tests) - expect(flutterMaterial(node, "")).toEqual(` -Material(color: Colors.transparent, ), `); + expect(flutterMaterial(node, "")).toEqual(`Material( + color: Colors.transparent, +),`); node.width = 0; node.height = 10; @@ -43,8 +44,13 @@ Material(color: Colors.transparent, ), `); parent.children = [node]; node.parent = parent; - expect(flutterMaterial(parent, "child")).toEqual(` -Material(color: Colors.transparent, child: Padding(padding: const EdgeInsets.all(10), ), child: child), ), `); + expect(flutterMaterial(parent, "child")).toEqual(`Material( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(10), + child: child + ), +),`); }); it("standard scenario", () => { @@ -63,11 +69,24 @@ Material(color: Colors.transparent, child: Padding(padding: const EdgeInsets.all ]; expect(flutterMaterial(node, "")).toEqual( - `\nSizedBox(width: 10, height: 10, child: Material(color: Colors.white, ), ), ` + `SizedBox( + width: 10, + height: 10, + child: Material( + color: Colors.white, + ), +),` ); expect(flutterMaterial(node, "child")).toEqual( - `\nSizedBox(width: 10, height: 10, child: Material(color: Colors.white, child: child), ), ` + `SizedBox( + width: 10, + height: 10, + child: Material( + color: Colors.white, + child: child + ), +),` ); }); @@ -77,7 +96,14 @@ Material(color: Colors.transparent, child: Padding(padding: const EdgeInsets.all node.height = 10; expect(flutterMaterial(node, "")).toEqual( - `\nSizedBox(width: 10, height: 10, child: Material(color: Colors.transparent, shape: CircleBorder(), ), ), ` + `SizedBox( + width: 10, + height: 10, + child: Material( + color: Colors.transparent, + shape: CircleBorder(), + ), +),` ); }); @@ -98,7 +124,16 @@ Material(color: Colors.transparent, child: Padding(padding: const EdgeInsets.all ]; expect(flutterMaterial(node, "")).toEqual( - `\nSizedBox(width: 10, height: 10, child: Material(color: Colors.transparent, shape: RoundedRectangleBorder(side: BorderSide(width: 4, color: Colors.white, ), ),), ), ` + `SizedBox( + width: 10, + height: 10, + child: Material( + color: Colors.transparent, + shape: RoundedRectangleBorder( + side: BorderSide(width: 4, color: Colors.white, ), + ), + ), +),` ); }); @@ -119,7 +154,15 @@ Material(color: Colors.transparent, child: Padding(padding: const EdgeInsets.all node.children = [child]; expect(flutterMaterial(node, "")).toEqual( - `\nSizedBox(width: 10, height: 10, child: Material(color: Colors.transparent, borderRadius: BorderRadius.circular(10), clipBehavior: Clip.antiAlias, ), ), ` + `SizedBox( + width: 10, + height: 10, + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(10), + clipBehavior: Clip.antiAlias, + ), +),` ); }); }); diff --git a/__tests__/flutter/flutterText.test.ts b/__tests__/flutter/flutterText.test.ts index c653523d..7494d8f8 100644 --- a/__tests__/flutter/flutterText.test.ts +++ b/__tests__/flutter/flutterText.test.ts @@ -15,16 +15,29 @@ describe("Flutter Text", () => { node.textAutoResize = "NONE"; expect(flutterMain([node])).toEqual( - 'SizedBox(width: 16, height: 16, child: Text("", ), )' + `SizedBox( + width: 16, + height: 16, + child: Text( + "", + ), +)` ); node.textAutoResize = "HEIGHT"; expect(flutterMain([node])).toEqual( - 'SizedBox(width: 16, child: Text("", ), )' + `SizedBox( + width: 16, + child: Text( + "", + ), +)` ); node.textAutoResize = "WIDTH_AND_HEIGHT"; - expect(flutterMain([node])).toEqual('Text("", ),'); + expect(flutterMain([node])).toEqual(`Text( + "", +)`); }); it("textAlignHorizontal", () => { @@ -35,17 +48,33 @@ describe("Flutter Text", () => { node.textAutoResize = "WIDTH_AND_HEIGHT"; node.textAlignHorizontal = "LEFT"; - expect(flutterMain([node])).toEqual('Text("", ),'); + expect(flutterMain([node])).toEqual(`Text( + "", +)`); node.textAutoResize = "NONE"; node.textAlignHorizontal = "CENTER"; expect(flutterMain([node])).toEqual( - 'SizedBox(width: 16, height: 16, child: Text("", textAlign: TextAlign.center, ), )' + `SizedBox( + width: 16, + height: 16, + child: Text( + "", + textAlign: TextAlign.center, + ), +)` ); node.textAlignHorizontal = "JUSTIFIED"; expect(flutterMain([node])).toEqual( - 'SizedBox(width: 16, height: 16, child: Text("", textAlign: TextAlign.justify, ), )' + `SizedBox( + width: 16, + height: 16, + child: Text( + "", + textAlign: TextAlign.justify, + ), +)` ); }); it("fontSize", () => { @@ -57,7 +86,12 @@ describe("Flutter Text", () => { node.textAutoResize = "WIDTH_AND_HEIGHT"; expect(flutterMain([node])).toEqual( - 'Text("", style: TextStyle(fontSize: 16, ), ),' + `Text( + "", + style: TextStyle( + fontSize: 16, + ), +)` ); }); @@ -72,29 +106,42 @@ describe("Flutter Text", () => { family: "inter", style: "bold", }; - expect(flutterMain([node])).toEqual( - 'Text("", style: TextStyle(fontFamily: "inter", fontWeight: FontWeight.w700, ), ),' - ); + expect(flutterMain([node])).toEqual(`Text( + "", + style: TextStyle( + fontFamily: "inter", + fontWeight: FontWeight.w700, + ), +)`); node.fontName = { family: "inter", style: "medium italic", }; - expect(flutterMain([node])).toEqual( - 'Text("", style: TextStyle(fontStyle: FontStyle.italic, fontFamily: "inter", fontWeight: FontWeight.w500, ), ),' - ); + expect(flutterMain([node])).toEqual(`Text( + "", + style: TextStyle( + fontStyle: FontStyle.italic, + fontFamily: "inter", + fontWeight: FontWeight.w500, + ), +)`); node.fontName = { family: "inter", style: "regular", }; - expect(flutterMain([node])).toEqual('Text("", ),'); + expect(flutterMain([node])).toEqual(`Text( + "", +)`); node.fontName = { family: "inter", style: "doesn't exist", }; - expect(flutterMain([node])).toEqual('Text("", ),'); + expect(flutterMain([node])).toEqual(`Text( + "", +)`); }); it("letterSpacing", () => { @@ -110,7 +157,13 @@ describe("Flutter Text", () => { unit: "PERCENT", }; expect(flutterMain([node])).toEqual( - 'Text("", style: TextStyle(fontSize: 24, letterSpacing: 26.40, ), ),' + `Text( + "", + style: TextStyle( + fontSize: 24, + letterSpacing: 26.40, + ), +)` ); node.letterSpacing = { @@ -118,7 +171,13 @@ describe("Flutter Text", () => { unit: "PIXELS", }; expect(flutterMain([node])).toEqual( - 'Text("", style: TextStyle(fontSize: 24, letterSpacing: 10, ), ),' + `Text( + "", + style: TextStyle( + fontSize: 24, + letterSpacing: 10, + ), +)` ); }); @@ -133,13 +192,18 @@ describe("Flutter Text", () => { value: 110, unit: "PERCENT", }; - expect(flutterMain([node])).toEqual('Text("", ),'); + expect(flutterMain([node])).toEqual(`Text( + "", +)`); node.lineHeight = { value: 10, unit: "PIXELS", }; - expect(flutterMain([node])).toEqual('Text("", ),'); + + expect(flutterMain([node])).toEqual(`Text( + "", +)`); }); it("textCase", () => { @@ -147,29 +211,37 @@ describe("Flutter Text", () => { node.characters = "aA"; node.textCase = "LOWER"; - expect(flutterMain([node])).toEqual('Text("aa", ),'); + expect(flutterMain([node])).toEqual(`Text( + "aa", +)`); // todo implement it // node.textCase = "TITLE"; // expect(flutterMain([node])).toEqual('Text("Aa", ),'); node.textCase = "UPPER"; - expect(flutterMain([node])).toEqual('Text("AA", ),'); + expect(flutterMain([node])).toEqual(`Text( + "AA", +)`); node.textCase = "ORIGINAL"; - expect(flutterMain([node])).toEqual('Text("aA", ),'); + expect(flutterMain([node])).toEqual(`Text( + "aA", +)`); node.textAlignHorizontal = "CENTER"; node.layoutAlign = "INHERIT"; - expect(flutterMain([node])).toEqual( - 'Text("aA", textAlign: TextAlign.center, ),' - ); + expect(flutterMain([node])).toEqual(`Text( + "aA", + textAlign: TextAlign.center, +)`); node.textAlignHorizontal = "JUSTIFIED"; node.layoutAlign = "INHERIT"; - expect(flutterMain([node])).toEqual( - 'Text("aA", textAlign: TextAlign.justify, ),' - ); + expect(flutterMain([node])).toEqual(`Text( + "aA", + textAlign: TextAlign.justify, +)`); }); it("textDecoration", () => { @@ -177,14 +249,23 @@ describe("Flutter Text", () => { node.characters = ""; node.textDecoration = "NONE"; - expect(flutterMain([node])).toEqual('Text("", ),'); + expect(flutterMain([node])).toEqual(`Text( + "", +)`); node.textDecoration = "STRIKETHROUGH"; - expect(flutterMain([node])).toEqual('Text("", ),'); + expect(flutterMain([node])).toEqual(`Text( + "", +)`); node.textDecoration = "UNDERLINE"; expect(flutterMain([node])).toEqual( - 'Text("", style: TextStyle(decoration: TextDecoration.underline, ), ),' + `Text( + "", + style: TextStyle( + decoration: TextDecoration.underline, + ), +)` ); }); diff --git a/__tests__/html/builderImpl/htmlSize.test.ts b/__tests__/html/builderImpl/htmlSize.test.ts index 9ea67308..7a4f3320 100644 --- a/__tests__/html/builderImpl/htmlSize.test.ts +++ b/__tests__/html/builderImpl/htmlSize.test.ts @@ -32,19 +32,20 @@ describe("HTML Size", () => { const child = new AltRectangleNode(); child.layoutAlign = "STRETCH"; + child.layoutGrow = 1; child.width = 100; child.height = 100; child.parent = node; node.children = [child]; - expect(htmlSize(child, false)).toEqual("width: 100px; height: 100%; "); + expect(htmlSize(child, false)).toEqual("flex: 1 1 0%; height: 100%; "); // fail node.layoutMode = "VERTICAL"; child.width = 16; child.height = 16; - expect(htmlSize(child, false)).toEqual("width: 100%; height: 16px; "); + expect(htmlSize(child, false)).toEqual("width: 100%; flex: 1 1 0%; "); }); it("counterAxisSizingMode is AUTO", () => { diff --git a/__tests__/tailwind/tailwindText.test.ts b/__tests__/tailwind/tailwindText.test.ts index 7d95f8e3..42d8bc74 100644 --- a/__tests__/tailwind/tailwindText.test.ts +++ b/__tests__/tailwind/tailwindText.test.ts @@ -38,6 +38,9 @@ describe("Tailwind Text", () => { node.textAlignHorizontal = "CENTER"; expect(tailwindMain([node])).toEqual('

'); + node.textAlignHorizontal = "RIGHT"; + expect(tailwindMain([node])).toEqual('

'); + node.textAlignHorizontal = "JUSTIFIED"; expect(tailwindMain([node])).toEqual( '

' diff --git a/src/common/indentString.ts b/src/common/indentString.ts index ed719316..3e323c8f 100644 --- a/src/common/indentString.ts +++ b/src/common/indentString.ts @@ -1,9 +1,10 @@ // From https://github.com/sindresorhus/indent-string -export const indentString = (str: string, indentLevel: number): string => { - const options = { - includeEmptyLines: false, - }; +export const indentString = (str: string, indentLevel: number = 1): string => { + // const options = { + // includeEmptyLines: false, + // }; - const regex = options.includeEmptyLines ? /^/gm : /^(?!\s*$)/gm; + // const regex = options.includeEmptyLines ? /^/gm : /^(?!\s*$)/gm; + const regex = /^(?!\s*$)/gm; return str.replace(regex, " ".repeat(indentLevel * 4)); }; diff --git a/src/flutter/builderImpl/flutterBorder.ts b/src/flutter/builderImpl/flutterBorder.ts index 1842dd2f..df83ef9e 100644 --- a/src/flutter/builderImpl/flutterBorder.ts +++ b/src/flutter/builderImpl/flutterBorder.ts @@ -1,3 +1,4 @@ +import { indentString } from "./../../common/indentString"; import { AltEllipseNode, AltFrameNode } from "../../altNodes/altMixins"; import { AltSceneNode, AltRectangleNode } from "../../altNodes/altMixins"; import { flutterColorFromFills } from "./flutterColor"; @@ -13,11 +14,11 @@ export const flutterBorder = (node: AltSceneNode): string => { const propStrokeColor = flutterColorFromFills(node.strokes); // only add strokeWidth when there is a strokeColor (returns "" otherwise) - const propStrokeWidth = `width: ${numToAutoFixed(node.strokeWeight)},`; + const propStrokeWidth = `width: ${numToAutoFixed(node.strokeWeight)}, `; // generate the border, when it should exist return propStrokeColor && node.strokeWeight - ? `border: Border.all(${propStrokeColor}${propStrokeWidth}), ` + ? `\nborder: Border.all(${propStrokeColor} ${propStrokeWidth}),` : ""; }; @@ -27,14 +28,15 @@ export const flutterShape = ( const strokeColor = flutterColorFromFills(node.strokes); const side = strokeColor && node.strokeWeight > 0 - ? `side: BorderSide(width: ${node.strokeWeight}, ${strokeColor}), ` + ? `\nside: BorderSide(width: ${node.strokeWeight}, ${strokeColor} ),` : ""; if (node.type === "ELLIPSE") { - return `shape: CircleBorder(${side}), `; + return `\nshape: CircleBorder(${indentString(side)}${side ? "\n" : ""}),`; } - return `shape: RoundedRectangleBorder(${side}${flutterBorderRadius(node)}),`; + const properties = side + flutterBorderRadius(node); + return `\nshape: RoundedRectangleBorder(${indentString(properties)}\n),`; }; // retrieve the borderRadius, when existent (returns "" for EllipseNode) @@ -51,10 +53,10 @@ export const flutterBorderRadius = ( } return node.cornerRadius !== figma.mixed - ? `borderRadius: BorderRadius.circular(${numToAutoFixed( + ? `\nborderRadius: BorderRadius.circular(${numToAutoFixed( node.cornerRadius - )}), ` - : `borderRadius: BorderRadius.only(topLeft: Radius.circular(${numToAutoFixed( + )}),` + : `\nborderRadius: BorderRadius.only(topLeft: Radius.circular(${numToAutoFixed( node.topLeftRadius )}), topRight: Radius.circular(${numToAutoFixed( node.topRightRadius @@ -62,5 +64,5 @@ export const flutterBorderRadius = ( node.bottomLeftRadius )}), bottomRight: Radius.circular(${numToAutoFixed( node.bottomRightRadius - )}), ), `; + )}), ),`; }; diff --git a/src/flutter/builderImpl/flutterColor.ts b/src/flutter/builderImpl/flutterColor.ts index e0d027b7..aa9c3809 100644 --- a/src/flutter/builderImpl/flutterColor.ts +++ b/src/flutter/builderImpl/flutterColor.ts @@ -13,7 +13,7 @@ export const flutterColorFromFills = ( if (fill?.type === "SOLID") { // todo maybe ignore text color when it is black? const opacity = fill.opacity ?? 1.0; - return `color: ${flutterColor(fill.color, opacity)}, `; + return `color: ${flutterColor(fill.color, opacity)},`; } return ""; @@ -26,9 +26,9 @@ export const flutterBoxDecorationColor = ( if (fill?.type === "SOLID") { const opacity = fill.opacity ?? 1.0; - return `color: ${flutterColor(fill.color, opacity)}, `; + return `\ncolor: ${flutterColor(fill.color, opacity)},`; } else if (fill?.type === "GRADIENT_LINEAR") { - return `gradient: ${flutterGradient(fill)}, `; + return `\ngradient: ${flutterGradient(fill)},`; } return ""; diff --git a/src/flutter/builderImpl/flutterPadding.ts b/src/flutter/builderImpl/flutterPadding.ts index 3266f5eb..409c899f 100644 --- a/src/flutter/builderImpl/flutterPadding.ts +++ b/src/flutter/builderImpl/flutterPadding.ts @@ -15,7 +15,7 @@ export const flutterPadding = (node: AltSceneNode): string => { } if ("all" in padding) { - return `padding: const EdgeInsets.all(${numToAutoFixed(padding.all)}), `; + return `\npadding: const EdgeInsets.all(${numToAutoFixed(padding.all)}),`; } // horizontal and vertical, as the default AutoLayout @@ -33,7 +33,7 @@ export const flutterPadding = (node: AltSceneNode): string => { ? `vertical: ${numToAutoFixed(padding.vertical)}, ` : ""; - return `padding: const EdgeInsets.symmetric(${propHorizontalPadding}${propVerticalPadding}), `; + return `\npadding: const EdgeInsets.symmetric(${propHorizontalPadding}${propVerticalPadding}),`; } let comp = ""; @@ -53,7 +53,7 @@ export const flutterPadding = (node: AltSceneNode): string => { } if (comp !== "") { - return `padding: const EdgeInsets.only(${comp}), `; + return `\npadding: const EdgeInsets.only(${comp}),`; } return ""; diff --git a/src/flutter/builderImpl/flutterPosition.ts b/src/flutter/builderImpl/flutterPosition.ts index 82844779..98bc4dfc 100644 --- a/src/flutter/builderImpl/flutterPosition.ts +++ b/src/flutter/builderImpl/flutterPosition.ts @@ -1,3 +1,4 @@ +import { indentString } from "./../../common/indentString"; import { AltSceneNode } from "../../altNodes/altMixins"; import { commonPosition } from "../../common/commonPosition"; import { numToAutoFixed } from "../../common/numToAutoFixed"; @@ -26,7 +27,8 @@ export const flutterPosition = ( const diffX = numToAutoFixed(node.x - parentX); const diffY = numToAutoFixed(node.y - parentY); - return `Positioned(left: ${diffX}, top: ${diffY}, child: ${child}),`; + const properties = `\nleft: ${diffX},\ntop: ${diffY},\nchild: ${child}`; + return `Positioned(${indentString(properties)}\n),`; } } @@ -34,8 +36,11 @@ export const flutterPosition = ( }; const retrieveAbsolutePos = (node: AltSceneNode, child: string): string => { - const positionedAlign = (align: string) => - `Positioned.fill(child: Align(alignment: Alignment.${align}, child: ${child}),),`; + const positionedAlign = (align: string) => { + const alignProp = `\nalignment: Alignment.${align},\nchild: ${child}`; + const positionedProp = `\nchild: Align(${indentString(alignProp)}\n),`; + return `Positioned.fill(${indentString(positionedProp)}\n),`; + }; switch (commonPosition(node)) { case "": diff --git a/src/flutter/builderImpl/flutterShadow.ts b/src/flutter/builderImpl/flutterShadow.ts index f746e960..c0e49807 100644 --- a/src/flutter/builderImpl/flutterShadow.ts +++ b/src/flutter/builderImpl/flutterShadow.ts @@ -1,3 +1,4 @@ +import { indentString } from "./../../common/indentString"; import { AltSceneNode } from "../../altNodes/altMixins"; import { rgbTo8hex } from "../../common/color"; import { numToAutoFixed } from "../../common/numToAutoFixed"; @@ -13,15 +14,18 @@ export const flutterBoxShadow = (node: AltSceneNode): string => { let boxShadow = ""; dropShadow.forEach((d: ShadowEffect) => { - const color = `color: Color(0x${rgbTo8hex(d.color, d.color.a)}), `; - const radius = `blurRadius: ${numToAutoFixed(d.radius)}, `; - const offset = `offset: Offset(${numToAutoFixed( + const color = `\ncolor: Color(0x${rgbTo8hex(d.color, d.color.a)}),`; + const radius = `\nblurRadius: ${numToAutoFixed(d.radius)},`; + const offset = `\noffset: Offset(${numToAutoFixed( d.offset.x - )}, ${numToAutoFixed(d.offset.y)}), `; - boxShadow += `BoxShadow(${color}${radius}${offset}),`; + )}, ${numToAutoFixed(d.offset.y)}),`; + + const property = color + radius + offset; + + boxShadow += `\nBoxShadow(${indentString(property)}\n),`; }); - propBoxShadow = `boxShadow: [ ${boxShadow} ], `; + propBoxShadow = `\nboxShadow: [${indentString(boxShadow)}\n],`; } // TODO inner shadow, layer blur } @@ -40,11 +44,11 @@ export const flutterElevationAndShadowColor = ( ); if (dropShadow.length > 0 && dropShadow[0].type === "DROP_SHADOW") { - shadowColor = `color: Color(0x${rgbTo8hex( + shadowColor = `\ncolor: Color(0x${rgbTo8hex( dropShadow[0].color, dropShadow[0].color.a )}), `; - elevation = `elevation: ${numToAutoFixed(dropShadow[0].radius)}, `; + elevation = `\nelevation: ${numToAutoFixed(dropShadow[0].radius)}, `; } } diff --git a/src/flutter/builderImpl/flutterSize.ts b/src/flutter/builderImpl/flutterSize.ts index 8d0bec33..5f63e49d 100644 --- a/src/flutter/builderImpl/flutterSize.ts +++ b/src/flutter/builderImpl/flutterSize.ts @@ -12,7 +12,7 @@ export const flutterSize = ( // this cast will always be true, since nodeWidthHeight was called with false to relative. let propWidth = ""; if (typeof size.width === "number") { - propWidth = `width: ${numToAutoFixed(size.width)}, `; + propWidth = `\nwidth: ${numToAutoFixed(size.width)},`; } else if (size.width === "full") { // When parent is a Row, child must be Expanded. if ( @@ -22,13 +22,13 @@ export const flutterSize = ( ) { isExpanded = true; } else { - propWidth = `width: double.infinity, `; + propWidth = `\nwidth: double.infinity,`; } } let propHeight = ""; if (typeof size.height === "number") { - propHeight = `height: ${numToAutoFixed(size.height)}, `; + propHeight = `\nheight: ${numToAutoFixed(size.height)},`; } else if (size.height === "full") { // When parent is a Column, child must be Expanded. if ( @@ -38,9 +38,9 @@ export const flutterSize = ( ) { isExpanded = true; } else { - propHeight = `height: double.infinity, `; + propHeight = `\nheight: double.infinity,`; } } - return { size: `${propWidth}${propHeight}`, isExpanded: isExpanded }; + return { size: propWidth + propHeight, isExpanded: isExpanded }; }; diff --git a/src/flutter/flutterContainer.ts b/src/flutter/flutterContainer.ts index 3471e686..f97d4f0d 100644 --- a/src/flutter/flutterContainer.ts +++ b/src/flutter/flutterContainer.ts @@ -1,3 +1,4 @@ +import { indentString } from "./../common/indentString"; import { AltGroupNode } from "./../altNodes/altMixins"; import { flutterBorderRadius, @@ -33,7 +34,7 @@ export const flutterContainer = ( // todo Image & multiple fills /// if child is empty, propChild is empty - const propChild = child ? `child: ${child}` : ""; + const propChild = child ? `\nchild: ${child}` : ""; // [propPadding] will be "padding: const EdgeInsets.symmetric(...)" or "" let propPadding = ""; @@ -44,17 +45,22 @@ export const flutterContainer = ( let result: string; if (fSize.size || propBoxDecoration) { // Container is a container if [propWidthHeight] and [propBoxDecoration] are set. - result = `\nContainer(${fSize.size}${propBoxDecoration}${propPadding}${propChild}),`; + const properties = `${fSize.size}${propBoxDecoration}${propPadding}${propChild}`; + + result = `Container(${indentString(properties)}\n),`; } else if (propPadding) { // if there is just a padding, add Padding - result = `\nPadding(${propPadding}${propChild}),`; + const properties = `${propPadding}${propChild}`; + + result = `Padding(${indentString(properties)}\n),`; } else { result = child; } // Add Expanded() when parent is a Row/Column and width is full. if (fSize.isExpanded) { - result = `\nExpanded(child: ${result}),`; + const properties = `\nchild: ${result}`; + result = `Expanded(${indentString(properties)}\n),`; } return result; @@ -69,14 +75,25 @@ const getBoxDecoration = ( const propBorderRadius = flutterBorderRadius(node); // modify the circle's shape when type is ellipse - const propShape = node.type === "ELLIPSE" ? "shape: BoxShape.circle, " : ""; + const propShape = node.type === "ELLIPSE" ? "\nshape: BoxShape.circle," : ""; // generate the decoration, or just the backgroundColor when color is SOLID. - return propBorder || + if ( + propBorder || propShape || propBorder || propBorderRadius || propBackgroundColor[0] === "g" - ? `decoration: BoxDecoration(${propBorderRadius}${propShape}${propBorder}${propBoxShadow}${propBackgroundColor}), ` - : `${propBackgroundColor}`; + ) { + const properties = + propBorderRadius + + propShape + + propBorder + + propBoxShadow + + propBackgroundColor; + + return `\ndecoration: BoxDecoration(${indentString(properties)}\n),`; + } else { + return propBackgroundColor; + } }; diff --git a/src/flutter/flutterMain.ts b/src/flutter/flutterMain.ts index a252cc99..af3a494a 100644 --- a/src/flutter/flutterMain.ts +++ b/src/flutter/flutterMain.ts @@ -1,3 +1,4 @@ +import { indentString } from "./../common/indentString"; import { AltEllipseNode, AltFrameNode, @@ -23,11 +24,7 @@ export const flutterMain = ( let result = flutterWidgetGenerator(sceneNode); - // remove the initial \n that is made in Container. - if (result.length > 0 && result.slice(0, 1) === "\n") { - result = result.slice(1, result.length); - } - + // remove the last ',' result = result.slice(0, -1); return result; @@ -58,19 +55,27 @@ const flutterWidgetGenerator = ( comp += flutterText(node); } - // if the parent is an AutoLayout, and itemSpacing is set, add a SizedBox between items. - // on else, comp += "" - comp += addSpacingIfNeeded(node, index, sceneLen); + if (index < sceneLen - 1) { + // if the parent is an AutoLayout, and itemSpacing is set, add a SizedBox between items. + // on else, comp += "" + const spacing = addSpacingIfNeeded(node); + if (spacing) { + // comp += "\n"; + comp += spacing; + } + + // don't add a newline at last element. + comp += "\n"; + } }); return comp; }; const flutterGroup = (node: AltGroupNode): string => { - return flutterContainer( - node, - `Stack(children:[${flutterWidgetGenerator(node.children)}],),` - ); + const properties = `\nchildren:[${flutterWidgetGenerator(node.children)}],`; + + return flutterContainer(node, `Stack(${indentString(properties)}\n),`); }; const flutterContainer = ( @@ -111,7 +116,10 @@ const flutterFrame = (node: AltFrameNode): string => { } else { // node.layoutMode === "NONE" && node.children.length > 1 // children needs to be absolute - return flutterContainer(node, `Stack(children:[${children}],),`); + + const properties = `\nchildren:[\n${indentString(children, 1)}\n],`; + + return flutterContainer(node, `Stack(${indentString(properties)}\n),`); } }; @@ -131,7 +139,7 @@ const makeRowColumn = (node: AltFrameNode, children: string): string => { crossAlignType = "end"; break; } - const crossAxisAlignment = `crossAxisAlignment: CrossAxisAlignment.${crossAlignType}, `; + const crossAxisAlignment = `\ncrossAxisAlignment: CrossAxisAlignment.${crossAlignType},`; let mainAlignType; switch (node.primaryAxisAlignItems) { @@ -148,29 +156,31 @@ const makeRowColumn = (node: AltFrameNode, children: string): string => { mainAlignType = "spaceBetween"; break; } - const mainAxisAlignment = `mainAxisAlignment: MainAxisAlignment.${mainAlignType}, `; + const mainAxisAlignment = `\nmainAxisAlignment: MainAxisAlignment.${mainAlignType},`; let mainAxisSize; if (node.layoutGrow === 1) { - mainAxisSize = "mainAxisSize: MainAxisSize.max, "; + mainAxisSize = "\nmainAxisSize: MainAxisSize.max,"; } else { - mainAxisSize = "mainAxisSize: MainAxisSize.min, "; + mainAxisSize = "\nmainAxisSize: MainAxisSize.min,"; } - return `${rowOrColumn}(${mainAxisSize}${mainAxisAlignment}${crossAxisAlignment}children:[${children}], ), `; + const properties = + mainAxisSize + + mainAxisAlignment + + crossAxisAlignment + + `\nchildren:[\n${indentString(children, 1)}\n],`; + + return `${rowOrColumn}(${indentString(properties, 1)}\n),`; }; // TODO Vector support in Flutter is complicated. Currently, AltConversion converts it in a Rectangle. -const addSpacingIfNeeded = ( - node: AltSceneNode, - index: number, - len: number -): string => { +const addSpacingIfNeeded = (node: AltSceneNode): string => { if (node.parent?.type === "FRAME" && node.parent.layoutMode !== "NONE") { // check if itemSpacing is set and if it isn't the last value. // Don't add the SizedBox at last value. In Figma, itemSpacing CAN be negative; here it can't. - if (node.parent.itemSpacing > 0 && index < len - 1) { + if (node.parent.itemSpacing > 0) { if (node.parent.layoutMode === "HORIZONTAL") { return `\nSizedBox(width: ${numToAutoFixed(node.parent.itemSpacing)}),`; } else { diff --git a/src/flutter/flutterMaterial.ts b/src/flutter/flutterMaterial.ts index 03c7c4c4..7230baa2 100644 --- a/src/flutter/flutterMaterial.ts +++ b/src/flutter/flutterMaterial.ts @@ -9,6 +9,7 @@ import { AltFrameNode, } from "../altNodes/altMixins"; import { flutterColorFromFills } from "./builderImpl/flutterColor"; +import { indentString } from "../common/indentString"; // https://api.flutter.dev/flutter/material/Material-class.html export const flutterMaterial = ( @@ -26,24 +27,26 @@ export const flutterMaterial = ( const shape = materialShape(node); const clip = getClipping(node); const [elevation, shadowColor] = flutterElevationAndShadowColor(node); - const padChild = child ? `child: ${getPadding(node, child)}` : ""; + const padChild = child ? `\nchild: ${getPadding(node, child)}` : ""; const materialAttr = color + elevation + shadowColor + shape + clip + padChild; - let materialResult = `Material(${materialAttr}), `; + let materialResult = `Material(${indentString(materialAttr)}\n),`; const fSize: { size: string; isExpanded: boolean } = flutterSize(node); if (fSize.size) { - materialResult = `SizedBox(${fSize.size}child: ${materialResult}), `; + const properties = `${fSize.size}\nchild: ${materialResult}`; + materialResult = `SizedBox(${indentString(properties)}\n),`; } if (fSize.isExpanded) { - materialResult = `Expanded(child: ${materialResult}),`; + const properties = `\nchild: ${materialResult}`; + materialResult = `Expanded(${indentString(properties)}\n),`; } - return `\n${materialResult}`; + return materialResult; }; const materialColor = ( @@ -51,9 +54,9 @@ const materialColor = ( ): string => { const color = flutterColorFromFills(node.fills); if (!color) { - return "color: Colors.transparent, "; + return "\ncolor: Colors.transparent,"; } - return color; + return "\n" + color; }; const materialShape = ( @@ -71,7 +74,7 @@ const getClipping = (node: AltSceneNode): string => { if (node.type === "FRAME" && node.cornerRadius && node.cornerRadius !== 0) { clip = node.clipsContent; } - return clip ? "clipBehavior: Clip.antiAlias, " : ""; + return clip ? "\nclipBehavior: Clip.antiAlias," : ""; }; const getPadding = ( @@ -80,7 +83,9 @@ const getPadding = ( ): string => { const padding = flutterPadding(node); if (padding) { - return `Padding(${padding}), child: ${child}), `; + const properties = `${padding}\nchild: ${child}`; + + return `Padding(${indentString(properties)}\n),`; } return child; diff --git a/src/flutter/flutterTextBuilder.ts b/src/flutter/flutterTextBuilder.ts index 07efd916..d39b8ac3 100644 --- a/src/flutter/flutterTextBuilder.ts +++ b/src/flutter/flutterTextBuilder.ts @@ -1,3 +1,4 @@ +import { indentString } from "./../common/indentString"; import { commonLetterSpacing } from "./../common/commonTextHeightSpacing"; import { FlutterDefaultBuilder } from "./flutterDefaultBuilder"; import { AltTextNode } from "../altNodes/altMixins"; @@ -36,7 +37,7 @@ export const makeTextComponent = (node: AltTextNode): string => { // if alignHorizontal is LEFT, don't do anything because that is native const textAlign = alignHorizontal !== "left" - ? `textAlign: TextAlign.${alignHorizontal}, ` + ? `\ntextAlign: TextAlign.${alignHorizontal},` : ""; let text = node.characters; @@ -51,13 +52,17 @@ export const makeTextComponent = (node: AltTextNode): string => { const textStyle = getTextStyle(node); - const style = textStyle ? `style: TextStyle(${textStyle}), ` : ""; + const style = textStyle + ? `\nstyle: TextStyle(${indentString(textStyle)}\n),` + : ""; const splittedChars = text.split("\n"); const charsWithLineBreak = splittedChars.length > 1 ? splittedChars.join("\\n") : text; - return `Text("${charsWithLineBreak}", ${textAlign}${style}), `; + const properties = `\n"${charsWithLineBreak}",${textAlign}${style}`; + + return `Text(${indentString(properties)}\n),`; }; export const getTextStyle = (node: AltTextNode): string => { @@ -67,18 +72,18 @@ export const getTextStyle = (node: AltTextNode): string => { styleBuilder += flutterColorFromFills(node.fills); if (node.fontSize !== figma.mixed) { - styleBuilder += `fontSize: ${numToAutoFixed(node.fontSize)}, `; + styleBuilder += `\nfontSize: ${numToAutoFixed(node.fontSize)},`; } if (node.textDecoration === "UNDERLINE") { - styleBuilder += "decoration: TextDecoration.underline, "; + styleBuilder += "\ndecoration: TextDecoration.underline,"; } if (node.fontName !== figma.mixed) { const lowercaseStyle = node.fontName.style.toLowerCase(); if (lowercaseStyle.match("italic")) { - styleBuilder += "fontStyle: FontStyle.italic, "; + styleBuilder += "\nfontStyle: FontStyle.italic,"; } // ignore the font-style when regular (default) @@ -91,8 +96,8 @@ export const getTextStyle = (node: AltTextNode): string => { const weight = convertFontWeight(value); if (weight) { - styleBuilder += `fontFamily: "${node.fontName.family}", `; - styleBuilder += `fontWeight: FontWeight.w${weight}, `; + styleBuilder += `\nfontFamily: "${node.fontName.family}",`; + styleBuilder += `\nfontWeight: FontWeight.w${weight},`; } } } @@ -100,7 +105,7 @@ export const getTextStyle = (node: AltTextNode): string => { // todo lineSpacing const letterSpacing = commonLetterSpacing(node); if (letterSpacing > 0) { - styleBuilder += `letterSpacing: ${numToAutoFixed(letterSpacing)}, `; + styleBuilder += `\nletterSpacing: ${numToAutoFixed(letterSpacing)},`; } return styleBuilder; @@ -112,13 +117,24 @@ export const wrapTextAutoResize = ( ): string => { if (node.textAutoResize === "NONE") { // = instead of += because we want to replace it - return `SizedBox(width: ${numToAutoFixed( - node.width - )}, height: ${numToAutoFixed(node.height)}, child: ${child}),`; + + const properties = + "\nwidth: " + + numToAutoFixed(node.width) + + ",\nheight: " + + numToAutoFixed(node.height) + + ",\nchild: " + + child; + + return `SizedBox(${indentString(properties)}\n),`; } else if (node.textAutoResize === "HEIGHT") { // if HEIGHT is set, it means HEIGHT will be calculated automatically, but width won't // = instead of += because we want to replace it - return `SizedBox(width: ${numToAutoFixed(node.width)}, child: ${child}),`; + + const properties = + "\nwidth: " + numToAutoFixed(node.width) + ",\nchild: " + child; + + return `SizedBox(${indentString(properties)}\n),`; } return child; diff --git a/src/swiftui/swiftuiMain.ts b/src/swiftui/swiftuiMain.ts index 0d8a3bdf..731ce248 100644 --- a/src/swiftui/swiftuiMain.ts +++ b/src/swiftui/swiftuiMain.ts @@ -30,10 +30,11 @@ const swiftuiWidgetGenerator = ( ): string => { let comp = ""; - // shortchut to avoid getting the lenght on every loop call. - const sceneNodeLen = sceneNode.length; + // filter non visible nodes. This is necessary at this step because conversion already happened. + const visibleSceneNode = sceneNode.filter((d) => d.visible !== false); + const sceneLen = visibleSceneNode.length; - sceneNode.forEach((node, index) => { + visibleSceneNode.forEach((node, index) => { if (node.type === "RECTANGLE" || node.type === "ELLIPSE") { comp += swiftuiContainer(node, indentLevel); } else if (node.type === "GROUP") { @@ -45,7 +46,7 @@ const swiftuiWidgetGenerator = ( } // don't add a newline at last element. - if (index < sceneNodeLen - 1) { + if (index < sceneLen - 1) { comp += "\n"; } }); @@ -137,9 +138,10 @@ const swiftuiText = (node: AltTextNode, indentLevel: number): string => { }; const swiftuiFrame = (node: AltFrameNode, indentLevel: number): string => { - const updatedIdentLevel = node.children.length === 1 ? 0 : indentLevel + 1; + // when there is a single children, indent should be zero; [swiftuiContainer] will already assign it. + const updatedIndentLevel = node.children.length === 1 ? 0 : indentLevel + 1; - const children = widgetGeneratorWithLimits(node, updatedIdentLevel); + const children = widgetGeneratorWithLimits(node, updatedIndentLevel); // if there is only one child, there is no need for a HStack of VStack. if (node.children.length === 1) {