├── .gitignore
├── plugin_readme_demo.gif
├── src
├── preprocessors
│ ├── toFixed.js
│ └── pixelUnitPreprocessor.js
├── generators
│ ├── generateLinkedGraphicCode.js
│ ├── generatePolygonCode.js
│ ├── generateSceneNodeStyles.js
│ ├── generateBooleanGroupCode.js
│ ├── generateGroupCode.js
│ ├── generatePathCode.js
│ ├── generateRepeatGridCode.js
│ ├── generateSymbolInstanceCode.js
│ ├── generatePlaceholderCode.js
│ ├── generateArtboardCode.js
│ ├── generateContainerCode.js
│ ├── generateLineCode.js
│ ├── generateTextCode
│ │ ├── index.js
│ │ ├── generateTextRangeStyles.js
│ │ ├── generateTextStyles.js
│ │ └── converters.js
│ ├── generateEllipseCode.js
│ ├── generateRectangleCode.js
│ ├── generateGraphicNodeCode.js
│ ├── generateNodeCode.js
│ └── generateChildrenMatrixCode.js
├── main.js
├── helpers
│ ├── representColor.js
│ ├── output
│ │ ├── prettifyCode.js
│ │ ├── createComponentSkeleton.js
│ │ └── save.js
│ ├── childNearestParent
│ │ └── index.js
│ └── ChildrenMatrix
│ │ └── index.js
└── commands
│ ├── generateCodeForEntireDocument.js
│ └── generateCodeForSelectedComponent.js
├── __tests__
└── ChildrenMatrix
│ ├── setChild.test.js
│ ├── calculateGlobalBounds.test.js
│ ├── getChild.test.js
│ ├── doesChildrenExistInOneRow.test.js
│ ├── doesChildrenExistInOneColumn.test.js
│ ├── getColumnNodes.test.js
│ ├── doNodesExistWithinSameRow.test.js
│ ├── getRowActualChildrenCount.test.js
│ ├── getToBeMergedRowsCount.test.js
│ ├── doNodesExistWithinSameColumn.test.js
│ ├── checkDuplicatedNodesExist.test.js
│ ├── rearrangeMatrix.test.js
│ ├── getLeftChild.test.js
│ ├── flatten.test.js
│ ├── getPossibleSlots.test.js
│ ├── sortChildren.test.js
│ ├── initiateMatrix.test.js
│ ├── getTopChild.test.js
│ ├── getNodesToBeDuplicated.test.js
│ ├── getMostSuitableSlot.test.js
│ ├── getSlotRowNeighbors.test.js
│ ├── getSlotColumnNeighbors.test.js
│ ├── calculateSlotChildMetric.test.js
│ └── layChildrenInsideMatrix.test.js
├── .eslintrc.js
├── webpack.config.js
├── LICENSE
├── package.json
├── jestSetup.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
--------------------------------------------------------------------------------
/plugin_readme_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hossamnasser938/React-Native-Generator-Plugin/HEAD/plugin_readme_demo.gif
--------------------------------------------------------------------------------
/src/preprocessors/toFixed.js:
--------------------------------------------------------------------------------
1 | function toFixed(n) {
2 | return +n.toFixed(2);
3 | }
4 |
5 | module.exports = {
6 | toFixed
7 | };
8 |
--------------------------------------------------------------------------------
/src/generators/generateLinkedGraphicCode.js:
--------------------------------------------------------------------------------
1 | function generateLinkedGraphicCode(linkedGraphic) {
2 | return "\n{/* {this is a node from an external resource like a Creative Cloud Library} */}\n";
3 | }
4 |
5 | module.exports = {
6 | generateLinkedGraphicCode
7 | };
8 |
--------------------------------------------------------------------------------
/src/generators/generatePolygonCode.js:
--------------------------------------------------------------------------------
1 | const { generatePlaceholderCode } = require("./generatePlaceholderCode");
2 |
3 | function generatePolygonCode(polygon) {
4 | return `\n{/* {Polygon is not supported. It can be exported as Svg} */}\n${generatePlaceholderCode(
5 | polygon
6 | )}`;
7 | }
8 |
9 | module.exports = {
10 | generatePolygonCode
11 | };
12 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | const {
2 | generateCodeForEntireDocument
3 | } = require("./commands/generateCodeForEntireDocument");
4 | const {
5 | generateCodeForSelectedComponent
6 | } = require("./commands/generateCodeForSelectedComponent");
7 |
8 | module.exports = {
9 | commands: {
10 | generateCodeForEntireDocument,
11 | generateCodeForSelectedComponent
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/src/generators/generateSceneNodeStyles.js:
--------------------------------------------------------------------------------
1 | const { toFixed } = require("../preprocessors/toFixed");
2 |
3 | function generateSceneNodeStyles(node) {
4 | const styles = {};
5 |
6 | if (node.opacity && node.opacity < 1) {
7 | styles.opacity = toFixed(node.opacity);
8 | }
9 |
10 | return styles;
11 | }
12 |
13 | module.exports = {
14 | generateSceneNodeStyles
15 | };
16 |
--------------------------------------------------------------------------------
/src/helpers/representColor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * represents color object in a way acceptable by RN
3 | * @param {*} color an instance of Color
4 | * returns string representation of color
5 | */
6 | function representColor(color) {
7 | const { r, g, b, a } = color.toRgba();
8 |
9 | return `rgba(${r}, ${g}, ${b}, ${a})`;
10 | }
11 |
12 | module.exports = {
13 | representColor
14 | };
15 |
--------------------------------------------------------------------------------
/src/generators/generateBooleanGroupCode.js:
--------------------------------------------------------------------------------
1 | const { generatePlaceholderCode } = require("./generatePlaceholderCode");
2 |
3 | function generateBooleanGroupCode(booleanGroup) {
4 | return `\n{/* {BooleanGroup is not supported. It can be exported as Svg} */}\n${generatePlaceholderCode(
5 | booleanGroup
6 | )}`;
7 | }
8 |
9 | module.exports = {
10 | generateBooleanGroupCode
11 | };
12 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/setChild.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | test("test ChildrenMatrix.setChild function", () => {
4 | const m = new ChildrenMatrix([{ globalBounds: {} }, { globalBounds: {} }]);
5 |
6 | expect(m.matrix[0][0]).toBeFalsy();
7 |
8 | m.setChild({ i: 0, j: 0 }, {});
9 |
10 | expect(m.matrix[0][0]).not.toBeFalsy();
11 | });
12 |
--------------------------------------------------------------------------------
/src/generators/generateGroupCode.js:
--------------------------------------------------------------------------------
1 | const { generateContainerCode } = require("./generateContainerCode");
2 | const { getParentChildren } = require("../helpers/childNearestParent/index");
3 |
4 | function generateGroupCode(group, additionalStyles) {
5 | const groupChildren = getParentChildren(group);
6 |
7 | return generateContainerCode(groupChildren, group, additionalStyles);
8 | }
9 |
10 | module.exports = {
11 | generateGroupCode
12 | };
13 |
--------------------------------------------------------------------------------
/src/helpers/output/prettifyCode.js:
--------------------------------------------------------------------------------
1 | const beautify = require("js-beautify");
2 |
3 | function prettifyCode(code) {
4 | // prettify code using js-beatify
5 | const beautifiedComponent = beautify(code, {
6 | indent_size: 2,
7 | operator_position: "preserve-newline"
8 | });
9 |
10 | // remove white space added after opening tag
11 | return beautifiedComponent.replace(new RegExp(/<\s/, "g"), "<");
12 | }
13 |
14 | module.exports = {
15 | prettifyCode
16 | };
17 |
--------------------------------------------------------------------------------
/src/generators/generatePathCode.js:
--------------------------------------------------------------------------------
1 | const { generatePlaceholderCode } = require("./generatePlaceholderCode");
2 |
3 | /**
4 | * generates code for path element
5 | * @param {*} path an instance of Path
6 | * @returns string ui code
7 | */
8 | function generatePathCode(path) {
9 | return `\n{/* {Path is not supported. It can be exported as Svg} */}\n${generatePlaceholderCode(
10 | path
11 | )}`;
12 | }
13 |
14 | module.exports = {
15 | generatePathCode
16 | };
17 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | commonjs: true,
5 | es6: true,
6 | node: true
7 | },
8 | extends: "eslint:recommended",
9 | parserOptions: {
10 | ecmaVersion: 2017
11 | },
12 | rules: {
13 | indent: ["error", 2],
14 | "linebreak-style": 0,
15 | quotes: ["error", "double"],
16 | semi: ["error", "always"],
17 | "no-unused-vars": 0,
18 | "no-console": "off",
19 | "no-undef": "off"
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/src/generators/generateRepeatGridCode.js:
--------------------------------------------------------------------------------
1 | const { generateContainerCode } = require("./generateContainerCode");
2 | const { getParentChildren } = require("../helpers/childNearestParent/index");
3 |
4 | function generateRepeatGridCode(repeatGrid, additionalStyles) {
5 | const repeatGridChildren = getParentChildren(repeatGrid);
6 |
7 | return generateContainerCode(
8 | repeatGridChildren,
9 | repeatGrid,
10 | additionalStyles
11 | );
12 | }
13 |
14 | module.exports = {
15 | generateRepeatGridCode
16 | };
17 |
--------------------------------------------------------------------------------
/src/generators/generateSymbolInstanceCode.js:
--------------------------------------------------------------------------------
1 | const { generateContainerCode } = require("./generateContainerCode");
2 | const { getParentChildren } = require("../helpers/childNearestParent/index");
3 |
4 | function generateSymbolInstanceCode(symbolInstance, additionalStyles) {
5 | const symbolInstanceChildren = getParentChildren(symbolInstance);
6 |
7 | return generateContainerCode(
8 | symbolInstanceChildren,
9 | symbolInstance,
10 | additionalStyles
11 | );
12 | }
13 |
14 | module.exports = {
15 | generateSymbolInstanceCode
16 | };
17 |
--------------------------------------------------------------------------------
/src/preprocessors/pixelUnitPreprocessor.js:
--------------------------------------------------------------------------------
1 | const { toFixed } = require("./toFixed");
2 |
3 | /**
4 | * preprocesses pixel units to be compatibe with css prepreocessors like react-native-extended-stylesheet
5 | * @param {*} unit a number representing pixels
6 | * @returns another form of unit such as '10rem', '10h', etc
7 | */
8 | function pixelUnitPreprocessor(unit) {
9 | // for now no options. In the future may be something like this `${unit}rem`
10 | return toFixed(unit);
11 | }
12 |
13 | module.exports = {
14 | pixelUnitPreprocessor
15 | };
16 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './src/main.js',
3 | output: {
4 | path: __dirname,
5 | filename: 'dist/main.js',
6 | libraryTarget: 'commonjs2'
7 | },
8 | devtool: 'none',
9 | externals: {
10 | assets: 'assets',
11 | scenegraph: 'scenegraph',
12 | application: 'application',
13 | commands: 'commands',
14 | clipboard: 'clipboard',
15 | cloud: 'cloud',
16 | uxp: 'uxp',
17 | viewport: 'viewport',
18 | interactions: 'interactions'
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/generators/generatePlaceholderCode.js:
--------------------------------------------------------------------------------
1 | const {
2 | pixelUnitPreprocessor
3 | } = require("../preprocessors/pixelUnitPreprocessor");
4 |
5 | /**
6 | * generates a placeholder code for unsupported node types
7 | * @param {*} node
8 | */
9 | function generatePlaceholderCode(node) {
10 | const styles = {
11 | width: pixelUnitPreprocessor(node.globalBounds.width),
12 | height: pixelUnitPreprocessor(node.globalBounds.height),
13 | backgroundColor: "#000000"
14 | };
15 |
16 | return `\n`;
17 | }
18 |
19 | module.exports = { generatePlaceholderCode };
20 |
--------------------------------------------------------------------------------
/src/helpers/output/createComponentSkeleton.js:
--------------------------------------------------------------------------------
1 | const { prettifyCode } = require("./prettifyCode");
2 | const elements = ["View", "Text", "Image"];
3 |
4 | function createComponentSkeleton(uiCode) {
5 | const existingElements = [];
6 |
7 | elements.forEach(element => {
8 | if (uiCode.includes("<" + element)) {
9 | existingElements.push(element);
10 | }
11 | });
12 |
13 | const component = `import React from 'react';
14 | import {${existingElements.join(", ")}} from 'react-native';
15 |
16 | export default () => {
17 | return (
18 | ${uiCode}
19 | );
20 | };
21 | `;
22 |
23 | return prettifyCode(component);
24 | }
25 |
26 | module.exports = {
27 | createComponentSkeleton
28 | };
29 |
--------------------------------------------------------------------------------
/src/generators/generateArtboardCode.js:
--------------------------------------------------------------------------------
1 | const { generateContainerCode } = require("./generateContainerCode");
2 | const {
3 | flattenNodeChildren,
4 | specifyChildrenNearestParents,
5 | getParentChildren
6 | } = require("../helpers/childNearestParent/index");
7 |
8 | function generateArtboardCode(artboard) {
9 | const artboardFlattenedChildren = [artboard];
10 | flattenNodeChildren(artboard, artboardFlattenedChildren);
11 |
12 | specifyChildrenNearestParents(artboardFlattenedChildren);
13 |
14 | const style = { flex: 1 };
15 |
16 | let code = generateContainerCode(
17 | getParentChildren(artboard),
18 | artboard,
19 | style
20 | );
21 |
22 | return code;
23 | }
24 |
25 | module.exports = {
26 | generateArtboardCode
27 | };
28 |
--------------------------------------------------------------------------------
/src/generators/generateContainerCode.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../helpers/ChildrenMatrix/index");
2 | const { generateChildrenMatrixCode } = require("./generateChildrenMatrixCode");
3 |
4 | /**
5 | * generates code for container element
6 | * @param {*} children an array of children nodes
7 | * @param {*} additionalStyle a style object coming from Rectangle or Ellipse
8 | * @returns string ui code
9 | */
10 | function generateContainerCode(children, parent, additionalStyle) {
11 | const childrenMatrix = new ChildrenMatrix(children);
12 | childrenMatrix.layChildrenInsideMatrix();
13 |
14 | return generateChildrenMatrixCode(childrenMatrix, parent, additionalStyle);
15 | }
16 |
17 | module.exports = {
18 | generateContainerCode
19 | };
20 |
--------------------------------------------------------------------------------
/src/helpers/output/save.js:
--------------------------------------------------------------------------------
1 | const fs = require("uxp").storage.localFileSystem;
2 | const { error, alert } = require("@adobe/xd-plugin-toolkit/lib/dialogs");
3 | const { createComponentSkeleton } = require("./createComponentSkeleton");
4 |
5 | /**
6 | * save ui components into files
7 | * @param {*} components an array of objects {name, code}
8 | */
9 | async function save(components) {
10 | try {
11 | const folder = await fs.getFolder();
12 |
13 | if (folder) {
14 | components.forEach(async ({ name, code }, index) => {
15 | const file = await folder.createFile(`Component${index}.js`);
16 | await file.write(createComponentSkeleton(code));
17 | });
18 |
19 | alert("Files Saved");
20 | }
21 | } catch (e) {
22 | error("Unexpected error occurred");
23 | }
24 | }
25 |
26 | module.exports = {
27 | save
28 | };
29 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/calculateGlobalBounds.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | test("test ChidrenMatrix.calculateGlobalBounds function", () => {
4 | const child1 = { globalBounds: { x: 100, y: 70, width: 50, height: 50 } };
5 | const child2 = { globalBounds: { x: 200, y: 70, width: 20, height: 300 } };
6 | const child3 = { globalBounds: { x: 400, y: 70, width: 70, height: 50 } };
7 | const child4 = {
8 | globalBounds: { x: 100, y: 200, width: 50, height: 150 }
9 | };
10 | const child5 = {
11 | globalBounds: { x: 400, y: 200, width: 70, height: 150 }
12 | };
13 |
14 | const cm = new ChildrenMatrix([child1, child2, child3, child4, child5]);
15 | cm.calculateGlobalBounds();
16 |
17 | expect(cm.globalBounds).toEqual({
18 | x: 100,
19 | y: 70,
20 | width: 370,
21 | height: 300
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/commands/generateCodeForEntireDocument.js:
--------------------------------------------------------------------------------
1 | const { error } = require("@adobe/xd-plugin-toolkit/lib/dialogs");
2 | const { generateArtboardCode } = require("../generators/generateArtboardCode");
3 | const {
4 | clearChildNearestParent
5 | } = require("../helpers/childNearestParent/index");
6 | const { save } = require("../helpers/output/save");
7 |
8 | async function generateCodeForEntireDocument(selection, documentRoot) {
9 | const components = [];
10 |
11 | try {
12 | documentRoot.children.forEach(documentItem => {
13 | if (documentItem.constructor.name === "Artboard") {
14 | components.push({
15 | name: documentItem.name,
16 | code: generateArtboardCode(documentItem)
17 | });
18 | }
19 | });
20 |
21 | await save(components);
22 | clearChildNearestParent();
23 | } catch (err) {
24 | console.log("err", err);
25 | error("Unexpected error occurred");
26 | clearChildNearestParent();
27 | }
28 | }
29 |
30 | module.exports = {
31 | generateCodeForEntireDocument
32 | };
33 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getChild.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { guid: "child1", globalBounds: {} };
4 | const child2 = { guid: "child2", globalBounds: {} };
5 | const child3 = { guid: "child3", globalBounds: {} };
6 |
7 | test("test ChildrenMatrix.getChild function", () => {
8 | const m = new ChildrenMatrix([child1, child2, child3]);
9 |
10 | m.setChild({ i: 0, j: 0 }, child1);
11 | m.setChild({ i: 1, j: 0 }, child2);
12 | m.setChild({ i: 1, j: 1 }, child3);
13 |
14 | expect(m.getChild({ i: 0, j: 0 })).toEqual(child1);
15 | expect(m.getChild({ i: 0, j: 1 })).toBeFalsy();
16 | expect(m.getChild({ i: 0, j: 2 })).toBeFalsy();
17 | expect(m.getChild({ i: 1, j: 0 })).toEqual(child2);
18 | expect(m.getChild({ i: 1, j: 1 })).toEqual(child3);
19 | expect(m.getChild({ i: 1, j: 2 })).toBeFalsy();
20 | expect(m.getChild({ i: 2, j: 0 })).toBeFalsy();
21 | expect(m.getChild({ i: 2, j: 1 })).toBeFalsy();
22 | expect(m.getChild({ i: 2, j: 2 })).toBeFalsy();
23 | });
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Hossam Abdelnaser Mahmoud
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/generators/generateLineCode.js:
--------------------------------------------------------------------------------
1 | const {
2 | pixelUnitPreprocessor
3 | } = require("../preprocessors/pixelUnitPreprocessor");
4 | const { representColor } = require("../helpers/representColor");
5 |
6 | /**
7 | * generates code for line element
8 | * @param {*} line an instance of Line
9 | * @returns string ui code
10 | */
11 | function generateLineCode(line, additionalStyles) {
12 | // TODO: handle diagonal lines
13 |
14 | const style = { ...additionalStyles };
15 |
16 | const { start, end, strokeEnabled, stroke, strokeWidth, globalBounds } = line;
17 |
18 | if (strokeEnabled) {
19 | style.backgroundColor = representColor(stroke);
20 |
21 | if (start.x !== end.x) {
22 | // horizontal
23 | style.width = pixelUnitPreprocessor(globalBounds.width);
24 | style.height = pixelUnitPreprocessor(strokeWidth);
25 | } else {
26 | // vertical
27 | style.height = pixelUnitPreprocessor(globalBounds.height);
28 | style.width = pixelUnitPreprocessor(strokeWidth);
29 | }
30 | }
31 |
32 | return `\n`;
33 | }
34 |
35 | module.exports = {
36 | generateLineCode
37 | };
38 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/doesChildrenExistInOneRow.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { guid: "child1", globalBounds: {} };
4 | const child2 = { guid: "child2", globalBounds: {} };
5 | const child3 = { guid: "child3", globalBounds: {} };
6 | const child4 = { guid: "child4", globalBounds: {} };
7 |
8 | describe("test ChildrenMatrix.doesChildrenExistInOneRow function", () => {
9 | test("should return true", () => {
10 | const m = new ChildrenMatrix([child1, child2, child3, child4]);
11 |
12 | m.setChild({ i: 0, j: 0 }, child1);
13 | m.setChild({ i: 0, j: 1 }, child2);
14 | m.setChild({ i: 0, j: 2 }, child3);
15 | m.setChild({ i: 0, j: 3 }, child4);
16 |
17 | expect(m.doesChildrenExistInOneRow()).toBeTruthy();
18 | });
19 |
20 | test("should return false", () => {
21 | const m = new ChildrenMatrix([child1, child2, child3, child4]);
22 |
23 | m.setChild({ i: 0, j: 0 }, child1);
24 | m.setChild({ i: 0, j: 1 }, child2);
25 | m.setChild({ i: 1, j: 0 }, child3);
26 | m.setChild({ i: 1, j: 1 }, child4);
27 |
28 | expect(m.doesChildrenExistInOneRow()).toBeFalsy();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/doesChildrenExistInOneColumn.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { guid: "child1", globalBounds: {} };
4 | const child2 = { guid: "child2", globalBounds: {} };
5 | const child3 = { guid: "child3", globalBounds: {} };
6 | const child4 = { guid: "child4", globalBounds: {} };
7 |
8 | describe("test ChildrenMatrix.doesChildrenExistInOneColumn function", () => {
9 | test("should return true", () => {
10 | const m = new ChildrenMatrix([child1, child2, child3, child4]);
11 |
12 | m.setChild({ i: 0, j: 0 }, child1);
13 | m.setChild({ i: 1, j: 0 }, child2);
14 | m.setChild({ i: 2, j: 0 }, child3);
15 | m.setChild({ i: 3, j: 0 }, child4);
16 |
17 | expect(m.doesChildrenExistInOneColumn()).toBeTruthy();
18 | });
19 |
20 | test("should return false", () => {
21 | const m = new ChildrenMatrix([child1, child2, child3, child4]);
22 |
23 | m.setChild({ i: 0, j: 0 }, child1);
24 | m.setChild({ i: 0, j: 1 }, child2);
25 | m.setChild({ i: 1, j: 0 }, child3);
26 | m.setChild({ i: 1, j: 1 }, child4);
27 |
28 | expect(m.doesChildrenExistInOneColumn()).toBeFalsy();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getColumnNodes.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { guid: "child1", globalBounds: {} };
4 | const child2 = { guid: "child2", globalBounds: {} };
5 | const child3 = { guid: "child3", globalBounds: {} };
6 | const child4 = { guid: "child4", globalBounds: {} };
7 | const child5 = { guid: "child5", globalBounds: {} };
8 | const child6 = { guid: "child6", globalBounds: {} };
9 |
10 | test("test ChildrenMatrix.getColumnNodes function", () => {
11 | const m = new ChildrenMatrix([
12 | child1,
13 | child2,
14 | child3,
15 | child4,
16 | child5,
17 | child6
18 | ]);
19 |
20 | m.setChild({ i: 0, j: 0 }, child1);
21 | m.setChild({ i: 1, j: 0 }, child2);
22 | m.setChild({ i: 1, j: 1 }, child3);
23 | m.setChild({ i: 2, j: 0 }, child4);
24 | m.setChild({ i: 2, j: 1 }, child5);
25 | m.setChild({ i: 2, j: 2 }, child6);
26 |
27 | expect(m.getColumnNodes(0)).toEqual([child1, child2, child4]);
28 | expect(m.getColumnNodes(1)).toEqual([child3, child5]);
29 | expect(m.getColumnNodes(2)).toEqual([child6]);
30 | expect(m.getColumnNodes(3)).toEqual([]);
31 | expect(m.getColumnNodes(4)).toEqual([]);
32 | expect(m.getColumnNodes(5)).toEqual([]);
33 | });
34 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/doNodesExistWithinSameRow.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { globalBounds: { x: 200, y: 200, width: 50, height: 50 } };
4 | const child2 = { globalBounds: { x: 0, y: 500, width: 50, height: 50 } };
5 | const child3 = { globalBounds: { x: 100, y: 50, width: 50, height: 50 } };
6 | const child4 = { globalBounds: { x: 300, y: 200, width: 50, height: 50 } };
7 | const child5 = { globalBounds: { x: 400, y: 180, width: 50, height: 50 } };
8 | const child6 = { globalBounds: { x: 500, y: 220, width: 50, height: 50 } };
9 |
10 | const m = new ChildrenMatrix([child1, child2, child3, child4, child5, child6]);
11 |
12 | describe("test ChildrenMatrix.doNodesExistWithinSameRow function", () => {
13 | test("should exist within the same row", () => {
14 | expect(m.doNodesExistWithinSameRow(child1, child4)).toBeTruthy();
15 | expect(m.doNodesExistWithinSameRow(child1, child5)).toBeTruthy();
16 | expect(m.doNodesExistWithinSameRow(child1, child6)).toBeTruthy();
17 | });
18 |
19 | test("should not exist within the same row", () => {
20 | expect(m.doNodesExistWithinSameRow(child1, child3)).toBeFalsy();
21 | expect(m.doNodesExistWithinSameRow(child1, child2)).toBeFalsy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getRowActualChildrenCount.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { guid: "child1", globalBounds: {} };
4 | const child2 = { guid: "child2", globalBounds: {} };
5 | const child3 = { guid: "child3", globalBounds: {} };
6 | const child4 = { guid: "child4", globalBounds: {} };
7 | const child5 = { guid: "child5", globalBounds: {} };
8 | const child6 = { guid: "child6", globalBounds: {} };
9 |
10 | test("test ChildrenMatrix.getRowActualChildrenCount function", () => {
11 | const m = new ChildrenMatrix([
12 | child1,
13 | child2,
14 | child3,
15 | child4,
16 | child5,
17 | child6
18 | ]);
19 |
20 | m.setChild({ i: 0, j: 0 }, child1);
21 | m.setChild({ i: 1, j: 0 }, child2);
22 | m.setChild({ i: 1, j: 1 }, child3);
23 | m.setChild({ i: 2, j: 0 }, child4);
24 | m.setChild({ i: 2, j: 1 }, child5);
25 | m.setChild({ i: 2, j: 2 }, child6);
26 |
27 | expect(m.getRowActualChildrenCount(0)).toBe(1);
28 | expect(m.getRowActualChildrenCount(1)).toBe(2);
29 | expect(m.getRowActualChildrenCount(2)).toBe(3);
30 | expect(m.getRowActualChildrenCount(3)).toBe(0);
31 | expect(m.getRowActualChildrenCount(4)).toBe(0);
32 | expect(m.getRowActualChildrenCount(5)).toBe(0);
33 | });
34 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getToBeMergedRowsCount.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { guid: "child1", globalBounds: {} };
4 | const child2 = { guid: "child2", globalBounds: {} };
5 | const child3 = { guid: "child3", globalBounds: {} };
6 | const child4 = { guid: "child4", globalBounds: {} };
7 | const child5 = { guid: "child5", globalBounds: {} };
8 | const child6 = { guid: "child6", globalBounds: {} };
9 | const child7 = { guid: "child7", globalBounds: {} };
10 |
11 | test("test ChildrenMatrix.getToBeMergedRowsCount function", () => {
12 | const m = new ChildrenMatrix([
13 | child1,
14 | child2,
15 | child3,
16 | child4,
17 | child5,
18 | child6,
19 | child7
20 | ]);
21 |
22 | m.setChild({ i: 0, j: 0 }, child1);
23 | m.setChild({ i: 0, j: 2 }, child2);
24 | m.setChild({ i: 1, j: 0 }, child3);
25 | m.setChild({ i: 1, j: 2 }, child4);
26 | m.setChild({ i: 2, j: 0 }, child5);
27 | m.setChild({ i: 2, j: 2 }, child6);
28 | m.setChild({ i: 0, j: 1 }, child7);
29 | m.setChild({ i: 1, j: 1 }, child7);
30 |
31 | expect(m.getToBeMergedRowsCount({ i: 0, j: 1 })).toBe(2);
32 |
33 | m.setChild({ i: 2, j: 1 }, child7);
34 |
35 | expect(m.getToBeMergedRowsCount({ i: 0, j: 1 })).toBe(3);
36 | });
37 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/doNodesExistWithinSameColumn.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { globalBounds: { x: 200, y: 200, width: 50, height: 50 } };
4 | const child2 = { globalBounds: { x: 500, y: 50, width: 50, height: 50 } };
5 | const child3 = { globalBounds: { x: 100, y: 100, width: 50, height: 50 } };
6 | const child4 = { globalBounds: { x: 200, y: 300, width: 50, height: 50 } };
7 | const child5 = { globalBounds: { x: 180, y: 400, width: 50, height: 50 } };
8 | const child6 = { globalBounds: { x: 230, y: 500, width: 50, height: 50 } };
9 |
10 | const m = new ChildrenMatrix([child1, child2, child3, child4, child5, child6]);
11 |
12 | describe("test ChildrenMatrix.doNodesExistWithinSameColumn function", () => {
13 | test("should exist within the same column", () => {
14 | expect(m.doNodesExistWithinSameColumn(child1, child4)).toBeTruthy();
15 | expect(m.doNodesExistWithinSameColumn(child1, child5)).toBeTruthy();
16 | expect(m.doNodesExistWithinSameColumn(child1, child6)).toBeTruthy();
17 | });
18 |
19 | test("should not exist within the same column", () => {
20 | expect(m.doNodesExistWithinSameColumn(child1, child2)).toBeFalsy();
21 | expect(m.doNodesExistWithinSameColumn(child1, child3)).toBeFalsy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-generate-plugin",
3 | "version": "1.0.0",
4 | "description": "generates react native components directly from design",
5 | "main": "main.js",
6 | "author": "Hossam Abdelnaser",
7 | "license": "MIT",
8 | "scripts": {
9 | "start": "npm run debug:watch",
10 | "test": "jest",
11 | "build": "npm run compile && xdpm package dist",
12 | "compile": "webpack --config webpack.config.js --mode production",
13 | "debug:install": "webpack --config webpack.config.js --mode development && xdpm install dist -o",
14 | "debug:watch": "xdpm watch dist | webpack --config webpack.config.js --mode development --watch",
15 | "debug:watch-prerelease": "xdpm watch dist -w p | webpack --config webpack.config.js --mode development --watch",
16 | "lint": "eslint src/**/*.js --ignore-pattern node_modules/"
17 | },
18 | "devDependencies": {
19 | "@adobe/xdpm": "^4.0.0",
20 | "@types/adobe-xd": "AdobeXD/typings",
21 | "eslint": "^6.8.0",
22 | "jest": "^25.1.0",
23 | "webpack": "^4.42.1",
24 | "webpack-cli": "^3.3.11"
25 | },
26 | "jest": {
27 | "setupFilesAfterEnv": [
28 | "./jestSetup.js"
29 | ]
30 | },
31 | "dependencies": {
32 | "@adobe/xd-plugin-toolkit": "0.0.1",
33 | "js-beautify": "^1.10.3"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/generators/generateTextCode/index.js:
--------------------------------------------------------------------------------
1 | const { generateTextStyles } = require("./generateTextStyles");
2 | const { generateTextRangeStyles } = require("./generateTextRangeStyles");
3 |
4 | /**
5 | * generates code for text element
6 | * @param {*} text an instance of Text
7 | * @returns string ui code
8 | */
9 | function generateTextCode(textElement, additionalStyles) {
10 | // TODO: extract common styles from ranges
11 |
12 | const { text, styleRanges } = textElement;
13 |
14 | if (styleRanges.length === 1) {
15 | // one style text
16 | const style = { ...generateTextStyles(textElement), ...additionalStyles };
17 |
18 | return `${text}\n`;
19 | }
20 |
21 | // multistyle text
22 |
23 | let code = `\n`;
24 | let startCharIndex = 0;
25 |
26 | styleRanges.forEach(styleRange => {
27 | const { length } = styleRange;
28 |
29 | const currentStyle = generateTextRangeStyles(styleRange);
30 | const currentText = text.substring(startCharIndex, startCharIndex + length);
31 |
32 | code += `${currentText}`;
35 |
36 | startCharIndex += length;
37 | });
38 |
39 | code += `\n`;
40 |
41 | return code;
42 | }
43 |
44 | module.exports = {
45 | generateTextCode
46 | };
47 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/checkDuplicatedNodesExist.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { guid: "child1", globalBounds: {} };
4 | const child2 = { guid: "child2", globalBounds: {} };
5 | const child3 = { guid: "child3", globalBounds: {} };
6 | const child4 = { guid: "child4", globalBounds: {} };
7 | const child5 = { guid: "child5", globalBounds: {} };
8 |
9 | describe("test ChildrenMatrix.checkDuplicatedNodesExist function", () => {
10 | test("should return duplicated node", () => {
11 | const m = new ChildrenMatrix([child1, child2, child3, child4, child5]);
12 | m.setChild({ i: 0, j: 0 }, child1);
13 | m.setChild({ i: 0, j: 2 }, child2);
14 | m.setChild({ i: 1, j: 0 }, child3);
15 | m.setChild({ i: 1, j: 2 }, child4);
16 | m.setChild({ i: 0, j: 1 }, child5);
17 | m.setChild({ i: 1, j: 1 }, child5);
18 |
19 | expect(m.checkDuplicatedNodesExist()).toEqual({ i: 0, j: 1 });
20 | });
21 |
22 | test("should return nothing", () => {
23 | const m = new ChildrenMatrix([child1, child2, child3, child4, child5]);
24 | m.setChild({ i: 0, j: 0 }, child1);
25 | m.setChild({ i: 0, j: 2 }, child2);
26 | m.setChild({ i: 1, j: 0 }, child3);
27 | m.setChild({ i: 1, j: 2 }, child4);
28 | m.setChild({ i: 0, j: 1 }, child5);
29 |
30 | expect(m.checkDuplicatedNodesExist()).toBeFalsy();
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/rearrangeMatrix.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { guid: "child1", globalBounds: {} };
4 | const child2 = { guid: "child2", globalBounds: {} };
5 | const child3 = { guid: "child3", globalBounds: {} };
6 | const child4 = { guid: "child4", globalBounds: {} };
7 | const child5 = { guid: "child5", globalBounds: {} };
8 |
9 | test("test ChildrenMatrix.rearrangeMatrix function", () => {
10 | const m = new ChildrenMatrix([child1, child2, child3, child4, child5]);
11 |
12 | m.setChild({ i: 0, j: 0 }, child1);
13 | m.setChild({ i: 0, j: 2 }, child2);
14 | m.setChild({ i: 1, j: 0 }, child3);
15 | m.setChild({ i: 1, j: 2 }, child4);
16 | m.setChild({ i: 0, j: 1 }, child5);
17 | m.setChild({ i: 1, j: 1 }, child5);
18 |
19 | m.rearrangeMatrix({ i: 0, j: 1 });
20 |
21 | expect(m.matrix).toEqual([
22 | [expect.any(ChildrenMatrix), child5, expect.any(ChildrenMatrix)],
23 | [expect.anyFalsyValue(), expect.anyFalsyValue(), expect.anyFalsyValue()],
24 | [expect.anyFalsyValue(), expect.anyFalsyValue(), expect.anyFalsyValue()]
25 | ]);
26 |
27 | expect(m.getChild({ i: 0, j: 0 }).matrix).toEqual([
28 | [child1, expect.anyFalsyValue()],
29 | [child3, expect.anyFalsyValue()]
30 | ]);
31 |
32 | expect(m.getChild({ i: 0, j: 2 }).matrix).toEqual([
33 | [child2, expect.anyFalsyValue()],
34 | [child4, expect.anyFalsyValue()]
35 | ]);
36 | });
37 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getLeftChild.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = {
4 | globalBounds: {
5 | x: 100,
6 | y: 100,
7 | width: 50,
8 | height: 50
9 | }
10 | };
11 |
12 | const child2 = {
13 | globalBounds: {
14 | x: 200,
15 | y: 100,
16 | width: 50,
17 | height: 50
18 | }
19 | };
20 |
21 | const child3 = {
22 | globalBounds: {
23 | x: 400,
24 | y: 100,
25 | width: 50,
26 | height: 50
27 | }
28 | };
29 |
30 | const child4 = {
31 | globalBounds: {
32 | x: 100,
33 | y: 300,
34 | width: 50,
35 | height: 50
36 | }
37 | };
38 |
39 | const child5 = {
40 | globalBounds: {
41 | x: 400,
42 | y: 300,
43 | width: 50,
44 | height: 50
45 | }
46 | };
47 |
48 | test("test ChildrenMatrix.getLeftChild function", () => {
49 | const cm1 = new ChildrenMatrix([child1, child2, child3, child4, child5]);
50 | cm1.setChild({ i: 0, j: 0 }, child1);
51 | cm1.setChild({ i: 0, j: 1 }, child2);
52 | cm1.setChild({ i: 0, j: 2 }, child3);
53 | cm1.setChild({ i: 1, j: 0 }, child4);
54 | cm1.setChild({ i: 1, j: 2 }, child5);
55 |
56 | expect(cm1.getLeftChild({ i: 0, j: 0 })).toBe(null);
57 | expect(cm1.getLeftChild({ i: 0, j: 1 })).toBe(child1);
58 | expect(cm1.getLeftChild({ i: 0, j: 2 })).toBe(child2);
59 | expect(cm1.getLeftChild({ i: 1, j: 0 })).toBe(null);
60 | expect(cm1.getLeftChild({ i: 1, j: 2 })).toBe(child4);
61 | });
62 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/flatten.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = { guid: "child1", globalBounds: {} };
4 | const child2 = { guid: "child2", globalBounds: {} };
5 | const child3 = { guid: "child3", globalBounds: {} };
6 | const child4 = { guid: "child4", globalBounds: {} };
7 |
8 | describe("test ChildrenMatrix.flatten method", () => {
9 | test("children exist in the same row", () => {
10 | const m = new ChildrenMatrix([child1, child2, child3, child4]);
11 |
12 | m.setChild({ i: 0, j: 0 }, child1);
13 | m.setChild({ i: 0, j: 1 }, child2);
14 | m.setChild({ i: 0, j: 2 }, child3);
15 | m.setChild({ i: 0, j: 3 }, child4);
16 |
17 | expect(m.flatten()).toEqual([
18 | { node: child1, slot: { i: 0, j: 0 } },
19 | { node: child2, slot: { i: 0, j: 1 } },
20 | { node: child3, slot: { i: 0, j: 2 } },
21 | { node: child4, slot: { i: 0, j: 3 } }
22 | ]);
23 | });
24 |
25 | test("children exist in the same column", () => {
26 | const m = new ChildrenMatrix([child1, child2, child3, child4]);
27 |
28 | m.setChild({ i: 0, j: 0 }, child1);
29 | m.setChild({ i: 1, j: 0 }, child2);
30 | m.setChild({ i: 2, j: 0 }, child3);
31 | m.setChild({ i: 3, j: 0 }, child4);
32 |
33 | expect(m.flatten()).toEqual([
34 | { node: child1, slot: { i: 0, j: 0 } },
35 | { node: child2, slot: { i: 1, j: 0 } },
36 | { node: child3, slot: { i: 2, j: 0 } },
37 | { node: child4, slot: { i: 3, j: 0 } }
38 | ]);
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getPossibleSlots.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | describe("test ChildrenMatrix.getPossibleSlots function", () => {
4 | test("test 1x1 matrix", () => {
5 | const m = new ChildrenMatrix([{ globalBounds: {} }]);
6 | expect(m.getPossibleSlots()).toEqual([{ i: 0, j: 0 }]);
7 |
8 | m.setChild({ i: 0, j: 0 }, {});
9 | expect(m.getPossibleSlots()).toEqual([]);
10 | });
11 |
12 | test("test 2x2 matrix", () => {
13 | const m = new ChildrenMatrix([{ globalBounds: {} }, { globalBounds: {} }]);
14 | expect(m.getPossibleSlots()).toEqual([{ i: 0, j: 0 }]);
15 |
16 | m.setChild({ i: 0, j: 0 }, {});
17 | expect(m.getPossibleSlots()).toMatchArryIgnoringOrder([
18 | { i: 0, j: 1 },
19 | { i: 1, j: 0 },
20 | { i: 1, j: 1 }
21 | ]);
22 | });
23 |
24 | test("test 3x3 matrix", () => {
25 | const m = new ChildrenMatrix([
26 | { globalBounds: {} },
27 | { globalBounds: {} },
28 | { globalBounds: {} }
29 | ]);
30 | expect(m.getPossibleSlots()).toEqual([{ i: 0, j: 0 }]);
31 |
32 | m.setChild({ i: 0, j: 0 }, {});
33 | expect(m.getPossibleSlots()).toMatchArryIgnoringOrder([
34 | { i: 0, j: 1 },
35 | { i: 1, j: 0 },
36 | { i: 1, j: 1 }
37 | ]);
38 |
39 | m.setChild({ i: 0, j: 1 }, {});
40 | expect(m.getPossibleSlots()).toMatchArryIgnoringOrder([
41 | { i: 0, j: 2 },
42 | { i: 1, j: 0 },
43 | { i: 1, j: 1 },
44 | { i: 1, j: 2 }
45 | ]);
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/src/generators/generateEllipseCode.js:
--------------------------------------------------------------------------------
1 | const { generateGraphicNodeCode } = require("./generateGraphicNodeCode");
2 | const { generateContainerCode } = require("./generateContainerCode");
3 | const { getParentChildren } = require("../helpers/childNearestParent/index");
4 | const {
5 | pixelUnitPreprocessor
6 | } = require("../preprocessors/pixelUnitPreprocessor");
7 | const { toFixed } = require("../preprocessors/toFixed");
8 |
9 | /**
10 | * generates code for ellipse element
11 | * @param {*} ellipse an instance of Ellipse
12 | * @returns string ui code
13 | */
14 | function generateEllipseCode(ellipse, additionalStyles) {
15 | const styles = {
16 | ...additionalStyles
17 | };
18 |
19 | const { radiusX, radiusY, isCircle } = ellipse;
20 |
21 | if (isCircle) {
22 | styles.width = pixelUnitPreprocessor(radiusX * 2);
23 | styles.height = pixelUnitPreprocessor(radiusX * 2);
24 | styles.borderRadius = radiusX;
25 | } else {
26 | if (radiusX > radiusY) {
27 | styles.width = pixelUnitPreprocessor(radiusY * 2);
28 | styles.height = pixelUnitPreprocessor(radiusY * 2);
29 | styles.borderRadius = radiusY;
30 | styles.transform = [{ scaleX: toFixed(radiusX / radiusY) }];
31 | } else {
32 | styles.width = pixelUnitPreprocessor(radiusX * 2);
33 | styles.height = pixelUnitPreprocessor(radiusX * 2);
34 | styles.borderRadius = radiusX;
35 | styles.transform = [{ scaleY: toFixed(radiusY / radiusX) }];
36 | }
37 | }
38 |
39 | return generateGraphicNodeCode(ellipse, styles);
40 | }
41 |
42 | module.exports = {
43 | generateEllipseCode
44 | };
45 |
--------------------------------------------------------------------------------
/src/generators/generateTextCode/generateTextRangeStyles.js:
--------------------------------------------------------------------------------
1 | const {
2 | convertFontFamilyAttribute,
3 | convertFontStyleAttribute,
4 | convertFontSizeAttribute,
5 | convertCharSpacingAttribute,
6 | convertUnderlineAttribute,
7 | convertStrikethroughAttribute,
8 | convertTextTransformAttribute,
9 | convertTextScriptAttribute,
10 | convertTextAlignAttribute
11 | } = require("./converters");
12 | const { representColor } = require("../../helpers/representColor");
13 |
14 | /**
15 | * generates style for text range item
16 | * @param {*} textRange an instance of TextRange
17 | * @returns style object
18 | */
19 | function generateTextRangeStyles(textRange) {
20 | // TODO: handle color with stroke
21 |
22 | const {
23 | fill,
24 | fontFamily,
25 | fontStyle,
26 | fontSize,
27 | charSpacing,
28 | underline,
29 | strikethrough,
30 | textTransform,
31 | textScript,
32 | textAlign
33 | } = textRange;
34 |
35 | const style = {
36 | ...convertFontFamilyAttribute(fontFamily),
37 | ...convertFontStyleAttribute(fontStyle),
38 | ...convertFontSizeAttribute(fontSize),
39 | ...convertCharSpacingAttribute(charSpacing),
40 | ...convertUnderlineAttribute(underline),
41 | ...convertStrikethroughAttribute(strikethrough),
42 | ...convertTextTransformAttribute(textTransform),
43 | ...convertTextScriptAttribute(textScript),
44 | ...convertTextAlignAttribute(textAlign)
45 | };
46 |
47 | if (fill) {
48 | style.color = representColor(fill);
49 | }
50 |
51 | return style;
52 | }
53 |
54 | module.exports = {
55 | generateTextRangeStyles
56 | };
57 |
--------------------------------------------------------------------------------
/src/generators/generateRectangleCode.js:
--------------------------------------------------------------------------------
1 | const { generateGraphicNodeCode } = require("./generateGraphicNodeCode");
2 | const { generateContainerCode } = require("./generateContainerCode");
3 | const { getParentChildren } = require("../helpers/childNearestParent/index");
4 | const {
5 | pixelUnitPreprocessor
6 | } = require("../preprocessors/pixelUnitPreprocessor");
7 |
8 | /**
9 | * generates code for rectangle element
10 | * @param {*} rectangle an instance of Rectangle
11 | * @returns string ui code
12 | */
13 | function generateRectangleCode(rectangle, additionalStyles) {
14 | const styles = {
15 | ...additionalStyles
16 | };
17 |
18 | const { width, height, hasRoundedCorners, effectiveCornerRadii } = rectangle;
19 |
20 | styles.width = pixelUnitPreprocessor(width);
21 | styles.height = pixelUnitPreprocessor(height);
22 |
23 | if (hasRoundedCorners) {
24 | const { topLeft, topRight, bottomRight, bottomLeft } = effectiveCornerRadii;
25 |
26 | if (
27 | [topLeft, topRight, bottomRight, bottomLeft].every(
28 | (value, index, array) => value === array[0]
29 | )
30 | ) {
31 | // all values are equal
32 | styles.borderRadius = pixelUnitPreprocessor(topLeft);
33 | } else {
34 | styles.borderTopStartRadius = pixelUnitPreprocessor(topLeft);
35 | styles.borderTopEndRadius = pixelUnitPreprocessor(topRight);
36 | styles.borderBottomEndRadius = pixelUnitPreprocessor(bottomRight);
37 | styles.borderBottomStartRadius = pixelUnitPreprocessor(bottomLeft);
38 | }
39 | }
40 |
41 | return generateGraphicNodeCode(rectangle, styles);
42 | }
43 |
44 | module.exports = {
45 | generateRectangleCode
46 | };
47 |
--------------------------------------------------------------------------------
/src/generators/generateTextCode/generateTextStyles.js:
--------------------------------------------------------------------------------
1 | const {
2 | convertFontFamilyAttribute,
3 | convertFontStyleAttribute,
4 | convertFontSizeAttribute,
5 | convertCharSpacingAttribute,
6 | convertUnderlineAttribute,
7 | convertStrikethroughAttribute,
8 | convertTextTransformAttribute,
9 | convertTextScriptAttribute,
10 | convertTextAlignAttribute
11 | } = require("./converters");
12 | const { representColor } = require("../../helpers/representColor");
13 |
14 | /**
15 | * generates style for text element
16 | * @param {*} textElement an instance of Text
17 | * @returns style object
18 | */
19 | function generateTextStyles(textElement) {
20 | // TODO: handle color with stroke
21 |
22 | const {
23 | strokeEnabled,
24 | stroke,
25 | fillEnabled,
26 | fill,
27 | fontFamily,
28 | fontStyle,
29 | fontSize,
30 | charSpacing,
31 | underline,
32 | strikethrough,
33 | textTransform,
34 | textScript,
35 | textAlign
36 | } = textElement;
37 |
38 | const style = {
39 | ...convertFontFamilyAttribute(fontFamily),
40 | ...convertFontStyleAttribute(fontStyle),
41 | ...convertFontSizeAttribute(fontSize),
42 | ...convertCharSpacingAttribute(charSpacing),
43 | ...convertUnderlineAttribute(underline),
44 | ...convertStrikethroughAttribute(strikethrough),
45 | ...convertTextTransformAttribute(textTransform),
46 | ...convertTextScriptAttribute(textScript),
47 | ...convertTextAlignAttribute(textAlign)
48 | };
49 |
50 | if (fillEnabled || strokeEnabled) {
51 | style.color = fillEnabled ? representColor(fill) : representColor(stroke);
52 | }
53 |
54 | return style;
55 | }
56 |
57 | module.exports = {
58 | generateTextStyles
59 | };
60 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/sortChildren.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | test("test ChildrenMatrix.sortChildren function", () => {
4 | const child1 = { globalBounds: { x: 100, y: 100, width: 50, height: 50 } };
5 | const child2 = { globalBounds: { x: 100, y: 200, width: 50, height: 50 } };
6 | const child3 = { globalBounds: { x: 200, y: 100, width: 50, height: 50 } };
7 |
8 | const children12Matrix = new ChildrenMatrix([child1, child2]);
9 | expect(children12Matrix.children).toEqual([child1, child2]);
10 |
11 | children12Matrix.sortChildren();
12 | expect(children12Matrix.children).toEqual([child1, child2]);
13 |
14 | const children21Matrix = new ChildrenMatrix([child2, child1]);
15 | expect(children21Matrix.children).toEqual([child2, child1]);
16 |
17 | children21Matrix.sortChildren();
18 | expect(children21Matrix.children).toEqual([child1, child2]);
19 |
20 | const children123Matrix = new ChildrenMatrix([child1, child2, child3]);
21 | expect(children123Matrix.children).toEqual([child1, child2, child3]);
22 |
23 | const children231Matrix = new ChildrenMatrix([child2, child3, child1]);
24 | expect(children231Matrix.children).toEqual([child2, child3, child1]);
25 |
26 | const children213Matrix = new ChildrenMatrix([child2, child1, child3]);
27 | expect(children213Matrix.children).toEqual([child2, child1, child3]);
28 |
29 | children123Matrix.sortChildren();
30 | expect(children123Matrix.children).toEqual([child1, child3, child2]);
31 |
32 | children231Matrix.sortChildren();
33 | expect(children231Matrix.children).toEqual([child1, child3, child2]);
34 |
35 | children213Matrix.sortChildren();
36 | expect(children213Matrix.children).toEqual([child1, child3, child2]);
37 | });
38 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/initiateMatrix.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | describe("test initiating new childdren matrix object", () => {
4 | describe("should initiate children matrix correctly", () => {
5 | const child1 = { globalBounds: { x: 100, y: 70, width: 100, height: 50 } };
6 | const child2 = { globalBounds: { x: 200, y: 70, width: 50, height: 300 } };
7 | const child3 = { globalBounds: { x: 400, y: 70, width: 100, height: 50 } };
8 | const child4 = {
9 | globalBounds: { x: 100, y: 200, width: 100, height: 150 }
10 | };
11 | const child5 = {
12 | globalBounds: { x: 400, y: 200, width: 100, height: 150 }
13 | };
14 |
15 | const cm = new ChildrenMatrix([child1, child2, child3, child4, child5]);
16 |
17 | test("should set n(matrix length)", () => {
18 | expect(cm.n).toEqual(5);
19 | });
20 |
21 | test("should initiate falsy matrix", () => {
22 | expect(cm.matrix).isFalsyMatrix(5);
23 | });
24 |
25 | test("should calculate matrix global bounds", () => {
26 | expect(cm.globalBounds).toEqual({
27 | x: 100,
28 | y: 70,
29 | width: 400,
30 | height: 300
31 | });
32 | });
33 | });
34 |
35 | test("should throw an error", () => {
36 | expect(() => {
37 | new ChildrenMatrix();
38 | }).toThrow();
39 |
40 | expect(() => {
41 | new ChildrenMatrix(0);
42 | }).toThrow();
43 |
44 | expect(() => {
45 | new ChildrenMatrix(-3);
46 | }).toThrow();
47 |
48 | expect(() => {
49 | new ChildrenMatrix("g");
50 | }).toThrow();
51 |
52 | expect(() => {
53 | new ChildrenMatrix({});
54 | }).toThrow();
55 |
56 | expect(() => {
57 | new ChildrenMatrix([]);
58 | }).toThrow();
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getTopChild.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = {
4 | globalBounds: {
5 | x: 100,
6 | y: 100,
7 | width: 50,
8 | height: 120
9 | }
10 | };
11 |
12 | const child2 = {
13 | globalBounds: {
14 | x: 200,
15 | y: 100,
16 | width: 50,
17 | height: 150
18 | }
19 | };
20 |
21 | const child3 = {
22 | globalBounds: {
23 | x: 100,
24 | y: 300,
25 | width: 50,
26 | height: 50
27 | }
28 | };
29 |
30 | const child4 = {
31 | globalBounds: {
32 | x: 200,
33 | y: 300,
34 | width: 50,
35 | height: 50
36 | }
37 | };
38 |
39 | const child5 = {
40 | globalBounds: {
41 | x: 300,
42 | y: 300,
43 | width: 50,
44 | height: 50
45 | }
46 | };
47 |
48 | test("test ChildrenMatrix.getTopChild function", () => {
49 | const cm1 = new ChildrenMatrix([child1, child2, child3]);
50 | cm1.setChild({ i: 0, j: 0 }, child1);
51 | cm1.setChild({ i: 0, j: 1 }, child2);
52 | cm1.setChild({ i: 1, j: 0 }, child3);
53 |
54 | const cm2 = new ChildrenMatrix([child1, child2, child4]);
55 | cm2.setChild({ i: 0, j: 0 }, child1);
56 | cm2.setChild({ i: 0, j: 1 }, child2);
57 | cm2.setChild({ i: 1, j: 1 }, child4);
58 |
59 | const cm3 = new ChildrenMatrix([child1, child2, child5]);
60 | cm3.setChild({ i: 0, j: 0 }, child1);
61 | cm3.setChild({ i: 0, j: 1 }, child2);
62 | cm3.setChild({ i: 1, j: 2 }, child5);
63 |
64 | expect(cm1.getTopChild({ i: 0, j: 0 })).toBe(null);
65 | expect(cm1.getTopChild({ i: 0, j: 1 })).toBe(null);
66 | expect(cm1.getTopChild({ i: 1, j: 0 })).toBe(child2);
67 |
68 | expect(cm2.getTopChild({ i: 0, j: 0 })).toBe(null);
69 | expect(cm2.getTopChild({ i: 0, j: 1 })).toBe(null);
70 | expect(cm2.getTopChild({ i: 1, j: 1 })).toBe(child2);
71 |
72 | expect(cm3.getTopChild({ i: 0, j: 0 })).toBe(null);
73 | expect(cm3.getTopChild({ i: 0, j: 1 })).toBe(null);
74 | expect(cm3.getTopChild({ i: 1, j: 2 })).toBe(child2);
75 | });
76 |
--------------------------------------------------------------------------------
/jestSetup.js:
--------------------------------------------------------------------------------
1 | // TODO: This file is executed before each test. find a better place to extend expect
2 | expect.extend({
3 | toMatchArryIgnoringOrder(received, expected) {
4 | try {
5 | expect(received.length).toEqual(expected.length);
6 | expect(received).toEqual(expect.arrayContaining(expected));
7 |
8 | return {
9 | message: () =>
10 | `expected ${JSON.stringify(received)} not to match ${JSON.stringify(
11 | expected
12 | )}`,
13 | pass: true
14 | };
15 | } catch (e) {
16 | return {
17 | message: () =>
18 | `expected ${JSON.stringify(received)} to match array ${JSON.stringify(
19 | expected
20 | )} ignoring the order`,
21 | pass: false
22 | };
23 | }
24 | }
25 | });
26 |
27 | expect.extend({
28 | isFalsyMatrix(received, n) {
29 | const exactN = received.length === n && received[0].length === n;
30 |
31 | let allFalsy = true;
32 |
33 | received.forEach(tuple => {
34 | tuple.forEach(item => {
35 | if (item) {
36 | allFalsy = false;
37 | }
38 | });
39 | });
40 |
41 | if (exactN && allFalsy) {
42 | return {
43 | message: () =>
44 | `expected ${JSON.stringify(
45 | received
46 | )} not to be an array of all falsy values of n = ${n}`,
47 | pass: true
48 | };
49 | } else {
50 | return {
51 | message: () =>
52 | `expected ${JSON.stringify(
53 | received
54 | )} to be an array of all falsy values of n = ${n}`,
55 | pass: false
56 | };
57 | }
58 | }
59 | });
60 |
61 | expect.extend({
62 | anyFalsyValue(received) {
63 | if (!received) {
64 | return {
65 | message: () => `expected a truthy value but got a falsy one`,
66 | pass: true
67 | };
68 | }
69 |
70 | return {
71 | message: () => `expected a falsy value but got a truthy one`,
72 | pass: false
73 | };
74 | }
75 | });
76 |
--------------------------------------------------------------------------------
/src/generators/generateGraphicNodeCode.js:
--------------------------------------------------------------------------------
1 | const { generateContainerCode } = require("./generateContainerCode");
2 | const { getParentChildren } = require("../helpers/childNearestParent/index");
3 | const {
4 | pixelUnitPreprocessor
5 | } = require("../preprocessors/pixelUnitPreprocessor");
6 | const { representColor } = require("../helpers/representColor");
7 |
8 | /**
9 | * generates graphicNode(either a Rectangle or Ellipse) code
10 | * @param {*} graphicNode an instance of GraphicNode
11 | * @returns code
12 | */
13 | function generateGraphicNodeCode(graphicNode, additionalStyles) {
14 | const styles = { ...additionalStyles };
15 |
16 | const { fillEnabled, fill, strokeEnabled, stroke, strokeWidth } = graphicNode;
17 |
18 | if (strokeEnabled) {
19 | styles.borderWidth = pixelUnitPreprocessor(strokeWidth);
20 | const strokeAsHexColor = representColor(stroke);
21 | styles.borderColor = strokeAsHexColor;
22 | }
23 |
24 | let element = "View";
25 |
26 | if (fillEnabled) {
27 | switch (fill.constructor.name) {
28 | case "Color":
29 | styles.backgroundColor = representColor(fill);
30 | break;
31 |
32 | case "ImageFill":
33 | element = "Image";
34 | break;
35 |
36 | default:
37 | styles.backgroundColor = "#ffffff";
38 | }
39 | }
40 |
41 | const children = getParentChildren(graphicNode);
42 |
43 | if (children.length) {
44 | return element === "View"
45 | ? generateContainerCode(children, graphicNode, styles)
46 | : `\n${generateContainerCode(
49 | children,
50 | graphicNode,
51 | { flex: 1 }
52 | )}`;
53 | }
54 |
55 | return `<${element} style={${JSON.stringify(
56 | element === "View" ? { alignItems: "flex-start", ...styles } : styles
57 | )}}${
58 | element === "Image" ? " source={{/* add your source here */}}" : ""
59 | } />\n`;
60 | }
61 |
62 | module.exports = {
63 | generateGraphicNodeCode
64 | };
65 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getNodesToBeDuplicated.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | test("test getNodesToBeDuplicated function", () => {
4 | const child1 = { globalBounds: { x: 0, y: 0, width: 50, height: 50 } };
5 | const child2 = { globalBounds: { x: 100, y: 0, width: 50, height: 200 } };
6 | const child3 = { globalBounds: { x: 200, y: 0, width: 50, height: 50 } };
7 | const child4 = { globalBounds: { x: 0, y: 100, width: 50, height: 50 } };
8 | const child5 = { globalBounds: { x: 200, y: 100, width: 50, height: 50 } };
9 | const child6 = { globalBounds: { x: 0, y: 150, width: 50, height: 50 } };
10 | const child7 = { globalBounds: { x: 200, y: 150, width: 50, height: 50 } };
11 | const child8 = { globalBounds: { x: 0, y: 200, width: 50, height: 50 } };
12 | const child9 = { globalBounds: { x: 200, y: 200, width: 50, height: 50 } };
13 | const child10 = { globalBounds: { x: 0, y: 250, width: 50, height: 50 } };
14 | const child11 = { globalBounds: { x: 200, y: 250, width: 50, height: 50 } };
15 |
16 | const m = new ChildrenMatrix([
17 | child1,
18 | child2,
19 | child3,
20 | child4,
21 | child5,
22 | child6,
23 | child7,
24 | child8,
25 | child9,
26 | child10,
27 | child11
28 | ]);
29 |
30 | m.setChild({ i: 0, j: 0 }, child1);
31 | m.setChild({ i: 0, j: 1 }, child2);
32 | m.setChild({ i: 0, j: 2 }, child3);
33 | m.setChild({ i: 1, j: 0 }, child4);
34 | m.setChild({ i: 1, j: 2 }, child5);
35 | m.setChild({ i: 2, j: 0 }, child6);
36 | m.setChild({ i: 2, j: 2 }, child7);
37 | m.setChild({ i: 3, j: 0 }, child8);
38 | m.setChild({ i: 3, j: 2 }, child9);
39 |
40 | expect(m.getNodesToBeDuplicated()).toEqual([
41 | { node: child2, slot: { i: 0, j: 1 } }
42 | ]);
43 |
44 | m.setChild({ i: 1, j: 1 }, child2);
45 |
46 | expect(m.getNodesToBeDuplicated()).toEqual([
47 | { node: child2, slot: { i: 1, j: 1 } }
48 | ]);
49 |
50 | m.setChild({ i: 2, j: 1 }, child2);
51 |
52 | expect(m.getNodesToBeDuplicated()).toEqual([
53 | { node: child2, slot: { i: 2, j: 1 } }
54 | ]);
55 |
56 | m.setChild({ i: 3, j: 1 }, child2);
57 |
58 | expect(m.getNodesToBeDuplicated()).toEqual([]);
59 | });
60 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getMostSuitableSlot.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | describe("test ChildrenMatrix.getMostSuitableSlot function", () => {
4 | test("test 2x2 matrix", () => {
5 | const m = new ChildrenMatrix([{ globalBounds: {} }, { globalBounds: {} }]);
6 |
7 | const existingChild = {
8 | globalBounds: { x: 100, y: 100, width: 50, height: 50 }
9 | };
10 |
11 | m.setChild({ i: 0, j: 0 }, existingChild);
12 |
13 | const newChild1 = {
14 | globalBounds: { x: 100, y: 200, width: 50, height: 50 }
15 | };
16 |
17 | expect(m.getMostSuitableSlot(newChild1)).toEqual({ i: 1, j: 0 });
18 |
19 | const newChild2 = {
20 | globalBounds: { x: 200, y: 100, width: 100, height: 100 }
21 | };
22 |
23 | expect(m.getMostSuitableSlot(newChild2)).toEqual({ i: 0, j: 1 });
24 | });
25 |
26 | test("test 3x3 matrix", () => {
27 | const m = new ChildrenMatrix([
28 | { globalBounds: {} },
29 | { globalBounds: {} },
30 | { globalBounds: {} }
31 | ]);
32 |
33 | const existingChild1 = {
34 | globalBounds: { x: 100, y: 100, width: 50, height: 50 }
35 | };
36 |
37 | m.setChild({ i: 0, j: 0 }, existingChild1);
38 |
39 | const newChild1 = {
40 | globalBounds: { x: 100, y: 200, width: 50, height: 50 }
41 | };
42 |
43 | expect(m.getMostSuitableSlot(newChild1)).toEqual({ i: 1, j: 0 });
44 |
45 | const newChild2 = {
46 | globalBounds: { x: 200, y: 100, width: 50, height: 50 }
47 | };
48 |
49 | expect(m.getMostSuitableSlot(newChild2)).toEqual({ i: 0, j: 1 });
50 |
51 | const existingChild2 = {
52 | globalBounds: { x: 100, y: 300, width: 50, height: 50 }
53 | };
54 |
55 | m.setChild({ i: 1, j: 0 }, existingChild2);
56 |
57 | const newChild3 = {
58 | globalBounds: { x: 300, y: 100, width: 50, height: 50 }
59 | };
60 |
61 | expect(m.getMostSuitableSlot(newChild3)).toEqual({ i: 0, j: 1 });
62 |
63 | const existingChild3 = {
64 | globalBounds: { x: 200, y: 300, width: 50, height: 50 }
65 | };
66 |
67 | m.setChild({ i: 1, j: 1 }, existingChild3);
68 |
69 | const newChild4 = {
70 | globalBounds: { x: 200, y: 400, width: 50, height: 50 }
71 | };
72 |
73 | expect(m.getMostSuitableSlot(newChild4)).toEqual({ i: 2, j: 1 });
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/src/generators/generateNodeCode.js:
--------------------------------------------------------------------------------
1 | const { generateArtboardCode } = require("./generateArtboardCode");
2 | const { generateRectangleCode } = require("./generateRectangleCode");
3 | const { generateEllipseCode } = require("./generateEllipseCode");
4 | const { generatePolygonCode } = require("./generatePolygonCode");
5 | const { generateLineCode } = require("./generateLineCode");
6 | const { generatePathCode } = require("./generatePathCode");
7 | const { generateBooleanGroupCode } = require("./generateBooleanGroupCode");
8 | const { generateTextCode } = require("./generateTextCode/index");
9 | const { generateGroupCode } = require("./generateGroupCode");
10 | const { generateSymbolInstanceCode } = require("./generateSymbolInstanceCode");
11 | const { generateRepeatGridCode } = require("./generateRepeatGridCode");
12 | const { generateLinkedGraphicCode } = require("./generateLinkedGraphicCode");
13 | const { generateChildrenMatrixCode } = require("./generateChildrenMatrixCode");
14 | const { generateSceneNodeStyles } = require("./generateSceneNodeStyles");
15 |
16 | function generateNodeCode(node, additionalStyles) {
17 | const styles = { ...additionalStyles, ...generateSceneNodeStyles(node) };
18 |
19 | switch (node.constructor.name) {
20 | case "Artboard":
21 | return generateArtboardCode(node);
22 |
23 | case "Rectangle":
24 | return generateRectangleCode(node, styles);
25 |
26 | case "Ellipse":
27 | return generateEllipseCode(node, styles);
28 |
29 | case "Polygon":
30 | return generatePolygonCode(node, styles);
31 |
32 | case "Line":
33 | return generateLineCode(node, styles);
34 |
35 | case "Path":
36 | return generatePathCode(node, styles);
37 |
38 | case "BooleanGroup":
39 | return generateBooleanGroupCode(node, styles);
40 |
41 | case "Text":
42 | return generateTextCode(node, styles);
43 |
44 | case "Group":
45 | return generateGroupCode(node, styles);
46 |
47 | case "SymbolInstance":
48 | return generateSymbolInstanceCode(node, styles);
49 |
50 | case "RepeatGrid":
51 | return generateRepeatGridCode(node, styles);
52 |
53 | case "LinkedGraphic":
54 | return generateLinkedGraphicCode(node, styles);
55 |
56 | case "ChildrenMatrix":
57 | return generateChildrenMatrixCode(node, null, styles);
58 |
59 | default:
60 | return "\n";
61 | }
62 | }
63 |
64 | module.exports = {
65 | generateNodeCode
66 | };
67 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getSlotRowNeighbors.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | describe("test ChildrenMatrix.getSlotRowNeighbors function", () => {
4 | test("should return an empty array for 2x2 matrix", () => {
5 | const m = new ChildrenMatrix([{ globalBounds: {} }, { globalBounds: {} }]);
6 |
7 | m.setChild({ i: 0, j: 0 }, { id: "id1" });
8 |
9 | expect(m.getSlotRowNeighbors({ i: 1, j: 0 })).toEqual([]);
10 | expect(m.getSlotRowNeighbors({ i: 1, j: 1 })).toEqual([]);
11 |
12 | m.setChild({ i: 0, j: 1 }, { id: "id2" });
13 | expect(m.getSlotRowNeighbors({ i: 1, j: 0 })).toEqual([]);
14 | expect(m.getSlotRowNeighbors({ i: 1, j: 1 })).toEqual([]);
15 | });
16 |
17 | test("should return an empty array for 3x3 matrix", () => {
18 | const m = new ChildrenMatrix([
19 | { globalBounds: {} },
20 | { globalBounds: {} },
21 | { globalBounds: {} }
22 | ]);
23 |
24 | m.setChild({ i: 0, j: 0 }, { id: "id1" });
25 |
26 | expect(m.getSlotRowNeighbors({ i: 1, j: 0 })).toEqual([]);
27 | expect(m.getSlotRowNeighbors({ i: 1, j: 1 })).toEqual([]);
28 | expect(m.getSlotRowNeighbors({ i: 1, j: 2 })).toEqual([]);
29 |
30 | expect(m.getSlotRowNeighbors({ i: 2, j: 0 })).toEqual([]);
31 | expect(m.getSlotRowNeighbors({ i: 2, j: 1 })).toEqual([]);
32 | expect(m.getSlotRowNeighbors({ i: 2, j: 2 })).toEqual([]);
33 |
34 | m.setChild({ i: 1, j: 1 }, { id: "id2" });
35 |
36 | expect(m.getSlotRowNeighbors({ i: 2, j: 0 })).toEqual([]);
37 | expect(m.getSlotRowNeighbors({ i: 2, j: 1 })).toEqual([]);
38 | expect(m.getSlotRowNeighbors({ i: 2, j: 2 })).toEqual([]);
39 | });
40 |
41 | test("should return an array of children for 2x2 matrix", () => {
42 | const m = new ChildrenMatrix([{ globalBounds: {} }, { globalBounds: {} }]);
43 |
44 | m.setChild({ i: 0, j: 0 }, { id: "id1" });
45 | m.setChild({ i: 1, j: 1 }, { id: "id2" });
46 |
47 | expect(m.getSlotRowNeighbors({ i: 0, j: 1 })).toEqual([{ id: "id1" }]);
48 | expect(m.getSlotRowNeighbors({ i: 1, j: 0 })).toEqual([{ id: "id2" }]);
49 | });
50 |
51 | test("should return an array of children for 3x3 matrix", () => {
52 | const m = new ChildrenMatrix([
53 | { globalBounds: {} },
54 | { globalBounds: {} },
55 | { globalBounds: {} }
56 | ]);
57 |
58 | m.setChild({ i: 0, j: 0 }, { id: "id1" });
59 |
60 | expect(m.getSlotRowNeighbors({ i: 0, j: 1 })).toEqual([{ id: "id1" }]);
61 | expect(m.getSlotRowNeighbors({ i: 0, j: 2 })).toEqual([{ id: "id1" }]);
62 |
63 | m.setChild({ i: 0, j: 1 }, { id: "id2" });
64 |
65 | expect(m.getSlotRowNeighbors({ i: 0, j: 2 })).toMatchArryIgnoringOrder([
66 | { id: "id1" },
67 | { id: "id2" }
68 | ]);
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/getSlotColumnNeighbors.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | describe("test ChildrenMatrix.getSlotColumnNeighbors function", () => {
4 | test("should return an empty array for 2x2 matrix", () => {
5 | const m = new ChildrenMatrix([{ globalBounds: {} }, { globalBounds: {} }]);
6 |
7 | m.setChild({ i: 0, j: 0 }, { id: "id1" });
8 |
9 | expect(m.getSlotColumnNeighbors({ i: 0, j: 1 })).toEqual([]);
10 | expect(m.getSlotColumnNeighbors({ i: 1, j: 1 })).toEqual([]);
11 |
12 | m.setChild({ i: 1, j: 0 }, { id: "id2" });
13 | expect(m.getSlotColumnNeighbors({ i: 0, j: 1 })).toEqual([]);
14 | expect(m.getSlotColumnNeighbors({ i: 1, j: 1 })).toEqual([]);
15 | });
16 |
17 | test("should return an empty array for 3x3 matrix", () => {
18 | const m = new ChildrenMatrix([
19 | { globalBounds: {} },
20 | { globalBounds: {} },
21 | { globalBounds: {} }
22 | ]);
23 |
24 | m.setChild({ i: 0, j: 0 }, { id: "id1" });
25 |
26 | expect(m.getSlotColumnNeighbors({ i: 0, j: 1 })).toEqual([]);
27 | expect(m.getSlotColumnNeighbors({ i: 1, j: 1 })).toEqual([]);
28 | expect(m.getSlotColumnNeighbors({ i: 2, j: 1 })).toEqual([]);
29 |
30 | expect(m.getSlotColumnNeighbors({ i: 0, j: 2 })).toEqual([]);
31 | expect(m.getSlotColumnNeighbors({ i: 1, j: 2 })).toEqual([]);
32 | expect(m.getSlotColumnNeighbors({ i: 2, j: 2 })).toEqual([]);
33 |
34 | m.setChild({ i: 1, j: 1 }, { id: "id2" });
35 |
36 | expect(m.getSlotColumnNeighbors({ i: 0, j: 2 })).toEqual([]);
37 | expect(m.getSlotColumnNeighbors({ i: 1, j: 2 })).toEqual([]);
38 | expect(m.getSlotColumnNeighbors({ i: 2, j: 2 })).toEqual([]);
39 | });
40 |
41 | test("should return an array of children for 2x2 matrix", () => {
42 | const m = new ChildrenMatrix([{ globalBounds: {} }, { globalBounds: {} }]);
43 |
44 | m.setChild({ i: 0, j: 0 }, { id: "id1" });
45 | m.setChild({ i: 1, j: 1 }, { id: "id2" });
46 |
47 | expect(m.getSlotColumnNeighbors({ i: 0, j: 1 })).toEqual([{ id: "id2" }]);
48 | expect(m.getSlotColumnNeighbors({ i: 1, j: 0 })).toEqual([{ id: "id1" }]);
49 | });
50 |
51 | test("should return an array of children for 3x3 matrix", () => {
52 | const m = new ChildrenMatrix([
53 | { globalBounds: {} },
54 | { globalBounds: {} },
55 | { globalBounds: {} }
56 | ]);
57 |
58 | m.setChild({ i: 0, j: 0 }, { id: "id1" });
59 |
60 | expect(m.getSlotColumnNeighbors({ i: 1, j: 0 })).toEqual([{ id: "id1" }]);
61 | expect(m.getSlotColumnNeighbors({ i: 2, j: 0 })).toEqual([{ id: "id1" }]);
62 |
63 | m.setChild({ i: 1, j: 0 }, { id: "id2" });
64 |
65 | expect(m.getSlotColumnNeighbors({ i: 2, j: 0 })).toMatchArryIgnoringOrder([
66 | { id: "id1" },
67 | { id: "id2" }
68 | ]);
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/src/commands/generateCodeForSelectedComponent.js:
--------------------------------------------------------------------------------
1 | const { error } = require("@adobe/xd-plugin-toolkit/lib/dialogs");
2 | const { generateNodeCode } = require("../generators/generateNodeCode");
3 | const {
4 | generateContainerCode
5 | } = require("../generators/generateContainerCode");
6 | const {
7 | flattenNodeChildren,
8 | specifyChildrenNearestParents,
9 | getNodeArtboard,
10 | filterChildrenWithNoParents,
11 | clearChildNearestParent
12 | } = require("../helpers/childNearestParent/index");
13 | const { save } = require("../helpers/output/save");
14 |
15 | async function generateCodeForSelectedComponent(selection) {
16 | try {
17 | if (selection.items.length === 0) {
18 | error("No selected items");
19 | return;
20 | } else {
21 | const selectedItemsArtboards = [];
22 |
23 | for (const selectedItem of selection.items) {
24 | const selectedItemArtboard = getNodeArtboard(selectedItem);
25 |
26 | if (!selectedItemArtboard) {
27 | error("Please, select component within an artboard");
28 | return;
29 | }
30 |
31 | // if this artboard does not exits then add it
32 | if (
33 | !selectedItemsArtboards.find(
34 | artboard => artboard.guid === selectedItemArtboard.guid
35 | )
36 | ) {
37 | selectedItemsArtboards.push(selectedItemArtboard);
38 | }
39 |
40 | if (selectedItemsArtboards.length > 1) {
41 | error("Please, select component within one artboard");
42 | return;
43 | }
44 | }
45 |
46 | // if current artboard is not one of the selected items then we need to attach each child to its nearset parent
47 | // if it is, then this step will be done inside generateArtbpardCode
48 |
49 | const currentArtboard = selectedItemsArtboards[0];
50 |
51 | if (!selection.items.find(item => item.guid === currentArtboard.guid)) {
52 | const children = [];
53 | flattenNodeChildren(currentArtboard, children);
54 | specifyChildrenNearestParents(children);
55 | }
56 |
57 | if (selection.items.length === 1) {
58 | await save([
59 | {
60 | name: selection.items[0].name,
61 | code: generateNodeCode(selection.items[0])
62 | }
63 | ]);
64 | } else {
65 | await save([
66 | {
67 | name: "SelectedComponent",
68 | code: generateContainerCode(
69 | filterChildrenWithNoParents(selection.items)
70 | )
71 | }
72 | ]);
73 | }
74 | }
75 |
76 | clearChildNearestParent();
77 | } catch (err) {
78 | console.log("err", err);
79 | error("Unexpected error occurred");
80 | clearChildNearestParent();
81 | }
82 | }
83 |
84 | module.exports = {
85 | generateCodeForSelectedComponent
86 | };
87 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/calculateSlotChildMetric.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | describe("test ChildrenMatrix.calculateSlotChildMetric function", () => {
4 | test("when having row neighbours", () => {
5 | const m = new ChildrenMatrix([{ globalBounds: {} }, { globalBounds: {} }]);
6 |
7 | m.setChild(
8 | { i: 0, j: 0 },
9 | {
10 | globalBounds: { x: 100, y: 100, width: 50, height: 50 }
11 | }
12 | );
13 |
14 | expect(
15 | m.calculateSlotChildMetric(
16 | { i: 0, j: 1 },
17 | { globalBounds: { x: 200, y: 100, width: 50, height: 50 } }
18 | )
19 | ).toBe(1);
20 |
21 | expect(
22 | m.calculateSlotChildMetric(
23 | { i: 1, j: 0 },
24 | { globalBounds: { x: 200, y: 100, width: 50, height: 50 } }
25 | )
26 | ).toBe(-1);
27 | });
28 |
29 | test("when having column neighbours", () => {
30 | const m = new ChildrenMatrix([{ globalBounds: {} }, { globalBounds: {} }]);
31 |
32 | m.setChild(
33 | { i: 0, j: 0 },
34 | {
35 | globalBounds: { x: 100, y: 100, width: 50, height: 50 }
36 | }
37 | );
38 |
39 | expect(
40 | m.calculateSlotChildMetric(
41 | { i: 1, j: 0 },
42 | { globalBounds: { x: 100, y: 200, width: 50, height: 50 } }
43 | )
44 | ).toBe(1);
45 |
46 | expect(
47 | m.calculateSlotChildMetric(
48 | { i: 0, j: 1 },
49 | { globalBounds: { x: 100, y: 200, width: 50, height: 50 } }
50 | )
51 | ).toBe(-1);
52 | });
53 |
54 | test("when having both row and column neighbours", () => {
55 | const m = new ChildrenMatrix([
56 | { globalBounds: {} },
57 | { globalBounds: {} },
58 | { globalBounds: {} },
59 | { globalBounds: {} }
60 | ]);
61 |
62 | m.setChild(
63 | { i: 0, j: 0 },
64 | {
65 | globalBounds: { x: 100, y: 100, width: 50, height: 50 }
66 | }
67 | );
68 |
69 | m.setChild(
70 | { i: 0, j: 1 },
71 | {
72 | globalBounds: { x: 200, y: 100, width: 50, height: 50 }
73 | }
74 | );
75 |
76 | m.setChild(
77 | { i: 1, j: 1 },
78 | {
79 | globalBounds: { x: 200, y: 200, width: 50, height: 50 }
80 | }
81 | );
82 |
83 | expect(
84 | m.calculateSlotChildMetric(
85 | { i: 1, j: 0 },
86 | { globalBounds: { x: 100, y: 200, width: 50, height: 50 } }
87 | )
88 | ).toBe(2);
89 |
90 | expect(
91 | m.calculateSlotChildMetric(
92 | { i: 2, j: 0 },
93 | { globalBounds: { x: 100, y: 200, width: 50, height: 50 } }
94 | )
95 | ).toBe(1);
96 |
97 | expect(
98 | m.calculateSlotChildMetric(
99 | { i: 0, j: 2 },
100 | { globalBounds: { x: 100, y: 200, width: 50, height: 50 } }
101 | )
102 | ).toBe(-2);
103 |
104 | expect(
105 | m.calculateSlotChildMetric(
106 | { i: 2, j: 2 },
107 | { globalBounds: { x: 100, y: 200, width: 50, height: 50 } }
108 | )
109 | ).toBe(0);
110 | });
111 | });
112 |
--------------------------------------------------------------------------------
/src/generators/generateTextCode/converters.js:
--------------------------------------------------------------------------------
1 | const { Text } = require("scenegraph");
2 | const {
3 | pixelUnitPreprocessor
4 | } = require("../../preprocessors/pixelUnitPreprocessor");
5 |
6 | function convertFontFamilyAttribute(fontFamily) {
7 | return { fontFamily };
8 | }
9 |
10 | function convertFontStyleAttribute(fontStyle) {
11 | const style = {};
12 |
13 | switch (fontStyle) {
14 | case "Regular":
15 | break;
16 |
17 | case "Italic":
18 | style.fontStyle = "italic";
19 | break;
20 |
21 | case "UltraLight":
22 | style.fontWeight = "100";
23 | break;
24 |
25 | case "UltraLight Italic":
26 | style.fontWeight = "100";
27 | style.fontStyle = "italic";
28 | break;
29 |
30 | case "Thin":
31 | style.fontWeight = "200";
32 | break;
33 |
34 | case "Thin Italic":
35 | style.fontWeight = "200";
36 | style.fontStyle = "italic";
37 | break;
38 |
39 | case "Light":
40 | style.fontWeight = "300";
41 | break;
42 |
43 | case "Light Italic":
44 | style.fontWeight = "300";
45 | style.fontStyle = "italic";
46 | break;
47 |
48 | case "Medium":
49 | style.fontWeight = "400";
50 | break;
51 |
52 | case "Medium Italic":
53 | style.fontWeight = "400";
54 | style.fontStyle = "italic";
55 | break;
56 |
57 | case "Bold":
58 | style.fontWeight = "bold";
59 | break;
60 |
61 | case "Bold Italic":
62 | style.fontWeight = "bold";
63 | style.fontStyle = "italic";
64 | break;
65 |
66 | case "Condensed Bold":
67 | style.fontWeight = "800";
68 | break;
69 |
70 | case "Condensed Black":
71 | style.fontWeight = "900";
72 | break;
73 | }
74 |
75 | return style;
76 | }
77 |
78 | function convertFontSizeAttribute(fontSize) {
79 | return { fontSize: pixelUnitPreprocessor(fontSize) };
80 | }
81 |
82 | function convertCharSpacingAttribute(charSpacing) {
83 | if (charSpacing) {
84 | return { letterSpacing: pixelUnitPreprocessor(charSpacing / 50) };
85 | }
86 |
87 | return {};
88 | }
89 |
90 | function convertUnderlineAttribute(underline) {
91 | if (underline) {
92 | return { textDecorationLine: "underline" };
93 | }
94 |
95 | return {};
96 | }
97 |
98 | function convertStrikethroughAttribute(strikethrough) {
99 | if (strikethrough) {
100 | return { textDecorationLine: "line-through" };
101 | }
102 |
103 | return {};
104 | }
105 |
106 | function convertTextTransformAttribute(textTransform) {
107 | if (textTransform !== "none") {
108 | const textTransformConverter = {
109 | uppercase: "uppercase",
110 | lowercase: "lowercase",
111 | titlecase: "capitalize"
112 | };
113 |
114 | return { textTransform: textTransformConverter[textTransform] };
115 | }
116 |
117 | return {};
118 | }
119 |
120 | function convertTextScriptAttribute(textScript) {
121 | // TODO
122 |
123 | return {};
124 | }
125 |
126 | function convertTextAlignAttribute(textAlign) {
127 | if (textAlign !== Text.ALIGN_LEFT) {
128 | const textAlignConverter = {
129 | [Text.ALIGN_CENTER]: "center",
130 | [Text.ALIGN_RIGHT]: "right"
131 | };
132 |
133 | return { textAlign: textAlignConverter[textAlign] };
134 | }
135 | }
136 |
137 | module.exports = {
138 | convertFontStyleAttribute,
139 | convertFontFamilyAttribute,
140 | convertFontSizeAttribute,
141 | convertCharSpacingAttribute,
142 | convertUnderlineAttribute,
143 | convertStrikethroughAttribute,
144 | convertTextTransformAttribute,
145 | convertTextScriptAttribute,
146 | convertTextAlignAttribute
147 | };
148 |
--------------------------------------------------------------------------------
/src/helpers/childNearestParent/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | During specifying children nearest parents, looping is done reversely
3 | to let children attached to nearest parents based on coordinates and also based on document structure
4 | For example having a Text within Rectangle withing a Group
5 | while Group and Rectangle having the same coordinates
6 | Text's parent should be Rectangle and Rectangle's parent should be Group
7 | */
8 |
9 | let childrenNearestParents = [];
10 |
11 | function setChildNearestParent(child, parent) {
12 | childrenNearestParents.push({ child, parent });
13 | }
14 |
15 | function getParentChildren(parent) {
16 | return childrenNearestParents
17 | .filter(item => item.parent && item.parent.guid === parent.guid)
18 | .map(item => item.child);
19 | }
20 |
21 | function getChildParent(child) {
22 | const targetItem = childrenNearestParents.find(
23 | item => item.child.guid === child.guid
24 | );
25 |
26 | return targetItem && targetItem.parent;
27 | }
28 |
29 | function filterChildrenWithNoParents(children) {
30 | return children.filter(child => !getChildParent(child));
31 | }
32 |
33 | function clearChildNearestParent() {
34 | childrenNearestParents = [];
35 | }
36 |
37 | function flattenNodeChildren(node, children) {
38 | node.children.forEach(nodeChild => {
39 | children.push(nodeChild);
40 |
41 | if (nodeChild.children.length) {
42 | flattenNodeChildren(nodeChild, children);
43 | }
44 | });
45 | }
46 |
47 | function specifyChildrenNearestParents(children) {
48 | // loop over children in a reverse order
49 | for (let i = children.length - 1; i >= 0; i--) {
50 | const cChild = children[i];
51 | const nearestParent = specifyChildNearestParent(cChild, children);
52 | setChildNearestParent(cChild, nearestParent);
53 | }
54 | }
55 |
56 | function specifyChildNearestParent(child, nodes) {
57 | let nearestParent;
58 |
59 | // loop over nodes in a reverse order
60 | for (let i = nodes.length - 1; i >= 0; i--) {
61 | const node = nodes[i];
62 |
63 | if (canBeParentForChild(child, node)) {
64 | if (nearestParent) {
65 | nearestParent = whichIsNearestParent(nearestParent, node);
66 | } else {
67 | nearestParent = node;
68 | }
69 | }
70 | }
71 |
72 | return nearestParent;
73 | }
74 |
75 | function canBeParentForChild(child, node) {
76 | if (
77 | // they are not the same node
78 | child.guid !== node.guid &&
79 | // if @param node is a child of @param child then @param node cannot be a parent for @param child
80 | !childrenNearestParents.find(
81 | item =>
82 | item.child.guid === node.guid &&
83 | item.parent &&
84 | item.parent.guid === child.guid
85 | ) &&
86 | // the child exists within the bounds of the node
87 | node.globalBounds.x <= child.globalBounds.x &&
88 | node.globalBounds.y <= child.globalBounds.y &&
89 | node.globalBounds.x + node.globalBounds.width >=
90 | child.globalBounds.x + child.globalBounds.width &&
91 | node.globalBounds.y + node.globalBounds.height >=
92 | child.globalBounds.y + child.globalBounds.height
93 | ) {
94 | return true;
95 | }
96 |
97 | return false;
98 | }
99 |
100 | function whichIsNearestParent(parent1, parent2) {
101 | // updating this order of comparison may break reverse looping
102 | if (parent1.globalBounds.y < parent2.globalBounds.y) {
103 | return parent2;
104 | } else if (parent1.globalBounds.y === parent2.globalBounds.y) {
105 | if (parent1.globalBounds.x < parent2.globalBounds.x) {
106 | return parent2;
107 | } else {
108 | return parent1;
109 | }
110 | } else {
111 | return parent1;
112 | }
113 | }
114 |
115 | /**
116 | * gets the artboard that contains the given node
117 | * @param {*} node an instance of SceneNode
118 | * @returns an instance of Artboard or null
119 | */
120 | function getNodeArtboard(node) {
121 | if (node.constructor.name === "Artboard") {
122 | return node;
123 | }
124 |
125 | let parent = node.parent;
126 |
127 | while (parent && parent.constructor.name !== "Artboard") {
128 | parent = parent.parent;
129 | }
130 |
131 | return parent;
132 | }
133 |
134 | module.exports = {
135 | getParentChildren,
136 | filterChildrenWithNoParents,
137 | clearChildNearestParent,
138 | flattenNodeChildren,
139 | specifyChildrenNearestParents,
140 | getNodeArtboard
141 | };
142 |
--------------------------------------------------------------------------------
/src/generators/generateChildrenMatrixCode.js:
--------------------------------------------------------------------------------
1 | const {
2 | pixelUnitPreprocessor
3 | } = require("../preprocessors/pixelUnitPreprocessor");
4 |
5 | /**
6 | * generates code for a set of nodes aligned in a ChildrenMatrix
7 | * @param {*} childrenMatrix
8 | * @param {*} additionalStyle a style object coming from Rectangle or Ellipse
9 | * @returns string ui code
10 | */
11 | function generateChildrenMatrixCode(childrenMatrix, parent, additionalStyle) {
12 | const { generateNodeCode } = require("./generateNodeCode"); // Late require for fixing circular dependency
13 |
14 | // check if it is only one node
15 | if (childrenMatrix.n === 1 && !additionalStyle) {
16 | return generateNodeCode(childrenMatrix.getChild({ i: 0, j: 0 }));
17 | }
18 |
19 | // check whether children exist in one column
20 | const childrenExistInOneColumn = childrenMatrix.doesChildrenExistInOneColumn();
21 |
22 | let code = "";
23 |
24 | if (childrenExistInOneColumn) {
25 | code += `\n`;
30 |
31 | childrenMatrix.flatten().forEach(({ node, slot }) => {
32 | code += generateNodeCode(
33 | node,
34 | generateMarginStyles(slot, childrenMatrix)
35 | );
36 | });
37 |
38 | code += `\n`;
39 |
40 | return code;
41 | }
42 |
43 | // check whether children exist in one row
44 | const childrenExistInOneRow = childrenMatrix.doesChildrenExistInOneRow();
45 |
46 | if (childrenExistInOneRow) {
47 | const style = {
48 | flexDirection: "row",
49 | alignItems: "flex-start",
50 | ...generatePaddingStyles(childrenMatrix, parent),
51 | ...additionalStyle
52 | };
53 |
54 | code += `\n`;
55 |
56 | childrenMatrix.flatten().forEach(({ node, slot }) => {
57 | code += generateNodeCode(
58 | node,
59 | generateMarginStyles(slot, childrenMatrix)
60 | );
61 | });
62 |
63 | code += `\n`;
64 |
65 | return code;
66 | }
67 |
68 | // children are dispersed
69 | code += `\n`;
74 |
75 | childrenMatrix.matrix.map((row, rowIndex) => {
76 | const childrenCount = childrenMatrix.getRowActualChildrenCount(rowIndex);
77 |
78 | if (childrenCount) {
79 | if (childrenCount > 1) {
80 | code += ``;
81 |
82 | row.map((child, columnIndex) => {
83 | if (child) {
84 | const styles = generateMarginStyles(
85 | { i: rowIndex, j: columnIndex },
86 | childrenMatrix
87 | );
88 |
89 | code += generateNodeCode(child, styles);
90 | }
91 | });
92 |
93 | code += `\n`;
94 | } else {
95 | const child = row.find(child => !!child);
96 | const columnIndex = row.findIndex(child => !!child);
97 |
98 | const styles = generateMarginStyles(
99 | { i: rowIndex, j: columnIndex },
100 | childrenMatrix
101 | );
102 |
103 | code += generateNodeCode(child, styles);
104 | }
105 | }
106 | });
107 |
108 | code += `\n`;
109 |
110 | return code;
111 | }
112 |
113 | function generateMarginStyles(slot, childrenMatrix) {
114 | const node = childrenMatrix.getChild(slot);
115 |
116 | const left = childrenMatrix.getLeftChild(slot);
117 | const top = childrenMatrix.getTopChild(slot);
118 |
119 | let marginStart;
120 | if (left) {
121 | marginStart =
122 | node.globalBounds.x - (left.globalBounds.x + left.globalBounds.width);
123 | } else {
124 | marginStart = node.globalBounds.x - childrenMatrix.globalBounds.x;
125 | }
126 |
127 | let marginTop;
128 | if (top) {
129 | marginTop =
130 | node.globalBounds.y - (top.globalBounds.y + top.globalBounds.height);
131 | } else {
132 | marginTop = node.globalBounds.y - childrenMatrix.globalBounds.y;
133 | }
134 |
135 | const styles = {};
136 |
137 | if (marginStart) {
138 | styles.marginStart = pixelUnitPreprocessor(marginStart);
139 | }
140 |
141 | if (marginTop) {
142 | styles.marginTop = pixelUnitPreprocessor(marginTop);
143 | }
144 |
145 | return styles;
146 | }
147 |
148 | function generatePaddingStyles(childrenMatrix, parent) {
149 | if (!parent) {
150 | return {};
151 | }
152 |
153 | const styles = {};
154 |
155 | const paddingStart = childrenMatrix.globalBounds.x - parent.globalBounds.x;
156 | const paddingTop = childrenMatrix.globalBounds.y - parent.globalBounds.y;
157 |
158 | if (paddingStart) {
159 | styles.paddingStart = pixelUnitPreprocessor(paddingStart);
160 | }
161 |
162 | if (paddingTop) {
163 | styles.paddingTop = pixelUnitPreprocessor(paddingTop);
164 | }
165 |
166 | return styles;
167 | }
168 |
169 | module.exports = {
170 | generateChildrenMatrixCode
171 | };
172 |
--------------------------------------------------------------------------------
/__tests__/ChildrenMatrix/layChildrenInsideMatrix.test.js:
--------------------------------------------------------------------------------
1 | const { ChildrenMatrix } = require("../../src/helpers/ChildrenMatrix/index");
2 |
3 | const child1 = {
4 | guid: "child1",
5 | globalBounds: { x: 100, y: 100, width: 50, height: 50 }
6 | };
7 | const child2 = {
8 | guid: "child2",
9 | globalBounds: { x: 200, y: 100, width: 50, height: 50 }
10 | };
11 | const child3 = {
12 | guid: "child3",
13 | globalBounds: { x: 300, y: 100, width: 50, height: 50 }
14 | };
15 | const child4 = {
16 | guid: "child4",
17 | globalBounds: { x: 100, y: 200, width: 50, height: 50 }
18 | };
19 | const child5 = {
20 | guid: "child5",
21 | globalBounds: { x: 200, y: 200, width: 50, height: 50 }
22 | };
23 | const child6 = {
24 | guid: "child6",
25 | globalBounds: { x: 100, y: 300, width: 50, height: 50 }
26 | };
27 |
28 | describe("test ChildrenMatrix.layChildrenInsideMatrix function", () => {
29 | test("test 2 children", () => {
30 | // the same row
31 | const children12Matrix = new ChildrenMatrix([child1, child2]);
32 | const children21Matrix = new ChildrenMatrix([child2, child1]);
33 |
34 | const expected1 = [
35 | [child1, child2],
36 | [expect.anyFalsyValue(), expect.anyFalsyValue()]
37 | ];
38 |
39 | expect(children12Matrix.layChildrenInsideMatrix()).toEqual(expected1);
40 | expect(children21Matrix.layChildrenInsideMatrix()).toEqual(expected1);
41 |
42 | // the same column
43 | const children14Matrix = new ChildrenMatrix([child1, child4]);
44 | const children41Matrix = new ChildrenMatrix([child4, child1]);
45 |
46 | const expected2 = [
47 | [child1, expect.anyFalsyValue()],
48 | [child4, expect.anyFalsyValue()]
49 | ];
50 |
51 | expect(children14Matrix.layChildrenInsideMatrix()).toEqual(expected2);
52 | expect(children41Matrix.layChildrenInsideMatrix()).toEqual(expected2);
53 | });
54 |
55 | test("test 3 children", () => {
56 | // the same row
57 | const children123Matrix = new ChildrenMatrix([child1, child2, child3]);
58 | const children213Matrix = new ChildrenMatrix([child2, child1, child3]);
59 | const children312Matrix = new ChildrenMatrix([child3, child1, child2]);
60 |
61 | const expected1 = [
62 | [child1, child2, child3],
63 | [expect.anyFalsyValue(), expect.anyFalsyValue(), expect.anyFalsyValue()],
64 | [expect.anyFalsyValue(), expect.anyFalsyValue(), expect.anyFalsyValue()]
65 | ];
66 |
67 | expect(children123Matrix.layChildrenInsideMatrix()).toEqual(expected1);
68 | expect(children213Matrix.layChildrenInsideMatrix()).toEqual(expected1);
69 | expect(children312Matrix.layChildrenInsideMatrix()).toEqual(expected1);
70 |
71 | // the same column
72 | const children146Matrix = new ChildrenMatrix([child1, child4, child6]);
73 | const children416Matrix = new ChildrenMatrix([child4, child1, child6]);
74 | const children614Matrix = new ChildrenMatrix([child6, child1, child4]);
75 |
76 | const expected2 = [
77 | [child1, expect.anyFalsyValue(), expect.anyFalsyValue()],
78 | [child4, expect.anyFalsyValue(), expect.anyFalsyValue()],
79 | [child6, expect.anyFalsyValue(), expect.anyFalsyValue()]
80 | ];
81 |
82 | expect(children146Matrix.layChildrenInsideMatrix()).toEqual(expected2);
83 | expect(children416Matrix.layChildrenInsideMatrix()).toEqual(expected2);
84 | expect(children614Matrix.layChildrenInsideMatrix()).toEqual(expected2);
85 |
86 | // dispersed
87 | const children124Matrix = new ChildrenMatrix([child1, child2, child4]);
88 | const children214Matrix = new ChildrenMatrix([child2, child1, child4]);
89 | const children412Matrix = new ChildrenMatrix([child4, child1, child2]);
90 |
91 | const expected3 = [
92 | [child1, child2, expect.anyFalsyValue()],
93 | [child4, expect.anyFalsyValue(), expect.anyFalsyValue()],
94 | [expect.anyFalsyValue(), expect.anyFalsyValue(), expect.anyFalsyValue()]
95 | ];
96 |
97 | expect(children124Matrix.layChildrenInsideMatrix()).toEqual(expected3);
98 | expect(children214Matrix.layChildrenInsideMatrix()).toEqual(expected3);
99 | expect(children412Matrix.layChildrenInsideMatrix()).toEqual(expected3);
100 | });
101 |
102 | test("test 4 children", () => {
103 | const children1245Matrix = new ChildrenMatrix([
104 | child1,
105 | child2,
106 | child4,
107 | child5
108 | ]);
109 | const children2451Matrix = new ChildrenMatrix([
110 | child2,
111 | child4,
112 | child5,
113 | child1
114 | ]);
115 | const children4215Matrix = new ChildrenMatrix([
116 | child4,
117 | child2,
118 | child1,
119 | child5
120 | ]);
121 | const children5214Matrix = new ChildrenMatrix([
122 | child5,
123 | child2,
124 | child1,
125 | child4
126 | ]);
127 |
128 | const expected = [
129 | [child1, child2, expect.anyFalsyValue(), expect.anyFalsyValue()],
130 | [child4, child5, expect.anyFalsyValue(), expect.anyFalsyValue()],
131 | [
132 | expect.anyFalsyValue(),
133 | expect.anyFalsyValue(),
134 | expect.anyFalsyValue(),
135 | expect.anyFalsyValue()
136 | ],
137 | [
138 | expect.anyFalsyValue(),
139 | expect.anyFalsyValue(),
140 | expect.anyFalsyValue(),
141 | expect.anyFalsyValue()
142 | ]
143 | ];
144 |
145 | expect(children1245Matrix.layChildrenInsideMatrix()).toEqual(expected);
146 | expect(children2451Matrix.layChildrenInsideMatrix()).toEqual(expected);
147 | expect(children4215Matrix.layChildrenInsideMatrix()).toEqual(expected);
148 | expect(children5214Matrix.layChildrenInsideMatrix()).toEqual(expected);
149 | });
150 |
151 | test("should duplicate children", () => {
152 | const childToBeDuplicated = {
153 | guid: "childToBeDuplicated",
154 | globalBounds: { x: 180, y: 100, width: 10, height: 200 }
155 | };
156 |
157 | const m = new ChildrenMatrix([
158 | child1,
159 | child2,
160 | childToBeDuplicated,
161 | child4,
162 | child5
163 | ]);
164 |
165 | const expected = [
166 | [
167 | expect.any(ChildrenMatrix),
168 | childToBeDuplicated,
169 | expect.any(ChildrenMatrix)
170 | ],
171 | [expect.anyFalsyValue(), expect.anyFalsyValue(), expect.anyFalsyValue()],
172 | [expect.anyFalsyValue(), expect.anyFalsyValue(), expect.anyFalsyValue()]
173 | ];
174 |
175 | expect(m.layChildrenInsideMatrix()).toEqual(expected);
176 | });
177 | });
178 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-Native-Generator-Plugin
2 | Adobe XD Plugin that generates React Native components directly from design.
3 |
4 | Press [Here](https://xd.adobelanding.com/en/xd-plugin-download/?pluginId=ecd83aaa) to download the plugin if you have Adobe XD installed.
5 |
6 | 
7 |
8 | Component code generated in the video/GIF:
9 |
10 | ```js
11 | import React from 'react';
12 | import {
13 | View,
14 | Text
15 | } from 'react-native';
16 |
17 | export default () => {
18 | return (
19 |
27 |
35 | Log In
44 |
50 | Your Email
58 |
68 |
69 |
75 | Password
83 |
93 |
94 | Forget Password ?
104 |
110 |
123 | Log In
131 |
132 |
133 | Or
143 | continue with social account
144 |
150 |
164 |
165 | {
166 | /* {Path is not supported. It can be exported as Svg} */ }
167 |
175 | Continue with Facebook
185 |
186 |
187 |
193 |
207 |
208 | {
209 | /* {Path is not supported. It can be exported as Svg} */ }
210 |
218 | Continue with Twitter
228 |
229 |
230 |
238 | Do you have an account ?
246 | SIGN UP
254 |
255 |
256 |
257 | );
258 | };
259 | ```
260 |
261 | - You can use it to generate components for
262 |
263 | - a selected item(s),
264 | - an artboard(screen).
265 | - the entire document(all artboards in the document). For each artboard, a file will be generated that holds component code for that artboard.
266 |
267 | - Currently unsupported node types:
268 |
269 | - Polygon.
270 | - Path.
271 | - BooleanGroup.
272 | - LinkedGraphic.
273 |
274 | - Built using JavaScript through [Adobe XD Plugin APIs](https://adobexdplatform.com/plugin-docs/).
275 |
276 | - External tools used:
277 |
278 | - **webpack** to bundle the plugin code.
279 | - **jest** to test complex parts of the Plugin.
280 | - **js-beautify** to beatify generated code.
281 |
282 | - Pull Requests and Issues are welcome.
283 |
284 | - Licensed under **MIT**.
285 |
--------------------------------------------------------------------------------
/src/helpers/ChildrenMatrix/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * constructor function that gets children array of a container node
3 | * and builds a matrix in which children are layed based on their coordinates
4 | * this matrix helps aligning children using the flexbox algorithm
5 | *
6 | * This technique is based on 3 simple assumptions:
7 | * - nodes with close x values are more likely to exist in the same column
8 | * - nodes with close y values are more likely to exist in the same row
9 | * - nodes close to the very top left are aligned first
10 | *
11 | * Terms
12 | * - slot => an object {i, j} such that i is the first index and j is the second index of an item within the matrix
13 | * - child => an instance of Scenenode
14 | * - matrix => a 2D array. For n items we create a 2D array of n rows each row with n items
15 | *
16 | * @param {*} children an array of nodes that exists within the bounds of a container node
17 | * @returns an instance of ChildrenMatrix
18 | */
19 | function ChildrenMatrix(children) {
20 | if (!Array.isArray(children) || children.length === 0) {
21 | throw new Error(
22 | "invalid children passed to ChildrenMatrix constructor function. Should be an array of at least one child"
23 | );
24 | }
25 |
26 | this.children = children;
27 | this.n = children.length;
28 |
29 | // initiate a 2D array with falsy values until being populated with children
30 | this.matrix = new Array(this.n)
31 | .fill(null)
32 | .map(_ => new Array(this.n).fill(null));
33 |
34 | this.calculateGlobalBounds();
35 | }
36 |
37 | ChildrenMatrix.prototype.calculateGlobalBounds = function() {
38 | const firstChildBounds = this.children[0].globalBounds;
39 |
40 | const minX = this.children.reduce((acc, child) => {
41 | if (child.globalBounds.x < acc) {
42 | return child.globalBounds.x;
43 | }
44 |
45 | return acc;
46 | }, firstChildBounds.x);
47 |
48 | const minY = this.children.reduce((acc, child) => {
49 | if (child.globalBounds.y < acc) {
50 | return child.globalBounds.y;
51 | }
52 |
53 | return acc;
54 | }, firstChildBounds.y);
55 |
56 | const maxXPlusWidth = this.children.reduce((acc, child) => {
57 | if (child.globalBounds.x + child.globalBounds.width > acc) {
58 | return child.globalBounds.x + child.globalBounds.width;
59 | }
60 |
61 | return acc;
62 | }, firstChildBounds.x + firstChildBounds.width);
63 |
64 | const maxYPlusHeight = this.children.reduce((acc, child) => {
65 | if (child.globalBounds.y + child.globalBounds.height > acc) {
66 | return child.globalBounds.y + child.globalBounds.height;
67 | }
68 |
69 | return acc;
70 | }, firstChildBounds.y + firstChildBounds.height);
71 |
72 | const globalBounds = {
73 | x: minX,
74 | y: minY,
75 | width: maxXPlusWidth - minX,
76 | height: maxYPlusHeight - minY
77 | };
78 |
79 | this.globalBounds = globalBounds;
80 | };
81 |
82 | /**
83 | * sets a child node in a given empty slot
84 | * @param slot
85 | * @param child
86 | * @returns nothing
87 | */
88 | ChildrenMatrix.prototype.setChild = function({ i, j }, child) {
89 | this.matrix[i][j] = child;
90 | };
91 |
92 | /**
93 | * gets a child node from a given empty slot
94 | * @param slot
95 | * @returns node
96 | */
97 | ChildrenMatrix.prototype.getChild = function({ i, j }) {
98 | return this.matrix[i][j];
99 | };
100 |
101 | /**
102 | * gets the nearest left child
103 | * @param slot
104 | * @returns node
105 | */
106 | ChildrenMatrix.prototype.getLeftChild = function({ i, j }) {
107 | let iteratingJ = j;
108 |
109 | while (iteratingJ > 0) {
110 | const left = this.getChild({ i, j: iteratingJ - 1 });
111 | if (left) {
112 | return left;
113 | }
114 | iteratingJ--;
115 | }
116 |
117 | return null;
118 | };
119 |
120 | /**
121 | * gets the nearest top child(the child with max y + height withing the nearest row that have children)
122 | * @param slot
123 | * @returns node
124 | */
125 | ChildrenMatrix.prototype.getTopChild = function({ i, j }) {
126 | let topChild = null;
127 |
128 | if (i > 0) {
129 | this.matrix[i - 1].forEach(node => {
130 | if (node) {
131 | if (!topChild) {
132 | topChild = node;
133 | } else {
134 | if (
135 | topChild.globalBounds.y + topChild.globalBounds.height <
136 | node.globalBounds.y + node.globalBounds.height
137 | ) {
138 | topChild = node;
139 | }
140 | }
141 | }
142 | });
143 | }
144 |
145 | return topChild;
146 | };
147 |
148 | /**
149 | * gets the slots that contain children nodes within the same row of the given empty slot
150 | * @param slot
151 | * @returns an array of nodes
152 | */
153 | ChildrenMatrix.prototype.getSlotRowNeighbors = function({ i, j }) {
154 | return this.matrix[i].filter((item, index) => index !== j && item);
155 | };
156 |
157 | /**
158 | * gets the slots that contain children nodes within the same column of the given empty slot
159 | * @param slot
160 | * @returns an array of nodes
161 | */
162 | ChildrenMatrix.prototype.getSlotColumnNeighbors = function({ i, j }) {
163 | return this.matrix.reduce((acc, row, index) => {
164 | return index !== i && row[j] ? acc.concat(row[j]) : acc;
165 | }, []);
166 | };
167 |
168 | /**
169 | * gets all children in a 1-D array
170 | * @returns an array of nodes
171 | */
172 | ChildrenMatrix.prototype.flatten = function() {
173 | const flattenedArray = [];
174 |
175 | this.matrix.forEach((row, rowIndex) => {
176 | row.forEach((node, columnIndex) => {
177 | if (node) {
178 | flattenedArray.push({ node, slot: { i: rowIndex, j: columnIndex } });
179 | }
180 | });
181 | });
182 |
183 | return flattenedArray;
184 | };
185 |
186 | /**
187 | * checks if all children are aligned in one column
188 | * @returns a boolean value
189 | */
190 | ChildrenMatrix.prototype.doesChildrenExistInOneColumn = function() {
191 | return this.matrix.reduce((acc, row) => !!row[0] && acc, true);
192 | };
193 |
194 | /**
195 | * checks if all children are aligned in one row
196 | * @returns a boolean value
197 | */
198 | ChildrenMatrix.prototype.doesChildrenExistInOneRow = function() {
199 | return this.matrix[0].reduce((acc, node) => !!node && acc, true);
200 | };
201 |
202 | /**
203 | * gets the number of nodes in a give row
204 | * @param rowIndex
205 | * @returns an array of nodes
206 | */
207 | ChildrenMatrix.prototype.getRowActualChildrenCount = function(rowIndex) {
208 | return this.matrix[rowIndex].reduce(
209 | (acc, node) => (!!node ? acc + 1 : acc),
210 | 0
211 | );
212 | };
213 |
214 | /**
215 | * gets the nodes within a column
216 | * @param columnIndex
217 | * @returns an array of nodes
218 | */
219 | ChildrenMatrix.prototype.getColumnNodes = function(columnIndex) {
220 | return this.matrix.reduce((acc, row) => {
221 | return row[columnIndex] ? acc.concat(row[columnIndex]) : acc;
222 | }, []);
223 | };
224 |
225 | /**
226 | * checks if 2 nodes exists in the same column(another meaning one of them exists withing the same vertical range of the other)
227 | */
228 | ChildrenMatrix.prototype.doNodesExistWithinSameColumn = function(
229 | firstNode,
230 | secondNode
231 | ) {
232 | return (
233 | (firstNode.globalBounds.x >= secondNode.globalBounds.x &&
234 | firstNode.globalBounds.x <=
235 | secondNode.globalBounds.x + secondNode.globalBounds.width) ||
236 | (secondNode.globalBounds.x >= firstNode.globalBounds.x &&
237 | secondNode.globalBounds.x <=
238 | firstNode.globalBounds.x + firstNode.globalBounds.width)
239 | );
240 | };
241 |
242 | /**
243 | * checks if 2 nodes exists in the same row(another meaning one of them exists withing the same horizontal range of the other)
244 | */
245 | ChildrenMatrix.prototype.doNodesExistWithinSameRow = function(
246 | firstNode,
247 | secondNode
248 | ) {
249 | return (
250 | (firstNode.globalBounds.y >= secondNode.globalBounds.y &&
251 | firstNode.globalBounds.y <=
252 | secondNode.globalBounds.y + secondNode.globalBounds.height) ||
253 | (secondNode.globalBounds.y >= firstNode.globalBounds.y &&
254 | secondNode.globalBounds.y <=
255 | firstNode.globalBounds.y + firstNode.globalBounds.height)
256 | );
257 | };
258 |
259 | /**
260 | * sorts the children array such that nodes at the very top left comes first
261 | * @returns nothing
262 | */
263 | ChildrenMatrix.prototype.sortChildren = function() {
264 | this.children.sort((a, b) => {
265 | return this.doNodesExistWithinSameRow(a, b)
266 | ? a.globalBounds.x - b.globalBounds.x
267 | : a.globalBounds.y - b.globalBounds.y;
268 | });
269 | };
270 |
271 | /**
272 | * calculates the likelihood that a new child node should be layed in a given slot relative to a set of possible slots
273 | * @param slot
274 | * @param newChild
275 | * @returns the likelihood value
276 | */
277 | ChildrenMatrix.prototype.calculateSlotChildMetric = function(slot, newChild) {
278 | let metric = 0;
279 |
280 | const rowNeighbors = this.getSlotRowNeighbors(slot);
281 |
282 | const columnNeighbors = this.getSlotColumnNeighbors(slot);
283 |
284 | rowNeighbors.forEach(rowNeighbor => {
285 | if (this.doNodesExistWithinSameRow(newChild, rowNeighbor)) {
286 | metric += 1;
287 | } else {
288 | metric -= 1;
289 | }
290 | });
291 |
292 | columnNeighbors.forEach(columnNeighbor => {
293 | if (this.doNodesExistWithinSameColumn(newChild, columnNeighbor)) {
294 | metric += 1;
295 | } else {
296 | metric -= 1;
297 | }
298 | });
299 |
300 | return metric;
301 | };
302 |
303 | /**
304 | * gets the empty slots that a new child node can be layed in
305 | * based on the number and positions of the children that are currently being in the matrix
306 | * @returns an array of ampty slots
307 | */
308 | ChildrenMatrix.prototype.getPossibleSlots = function() {
309 | let containsAtLeastOneChild = false;
310 | const possibleSlots = [];
311 |
312 | this.matrix.forEach((row, rowIndex) => {
313 | row.forEach((slot, columnIndex) => {
314 | if (
315 | !slot &&
316 | (rowIndex === 0 || this.getRowActualChildrenCount(rowIndex - 1)) &&
317 | (columnIndex === 0 || this.getColumnNodes(columnIndex - 1).length)
318 | ) {
319 | possibleSlots.push({ i: rowIndex, j: columnIndex });
320 | }
321 | });
322 | });
323 |
324 | return possibleSlots;
325 | };
326 |
327 | /**
328 | * gets the most suitable empty slot in which a new child should be layed in
329 | * @param newChild
330 | * @returns an empty slot
331 | */
332 | ChildrenMatrix.prototype.getMostSuitableSlot = function(newChild) {
333 | const possibleSlots = this.getPossibleSlots();
334 |
335 | const slotsMetrics = [];
336 |
337 | // evaluate slots
338 | possibleSlots.forEach(slot => {
339 | const metric = this.calculateSlotChildMetric(slot, newChild);
340 |
341 | slotsMetrics.push({ slot, metric });
342 | });
343 |
344 | const leastMetricSlot = slotsMetrics.reduce((acc, v) => {
345 | if (v.metric > acc.metric) {
346 | return v;
347 | }
348 |
349 | return acc;
350 | }, slotsMetrics[0]);
351 |
352 | return leastMetricSlot.slot;
353 | };
354 |
355 | /**
356 | * determines the nodes that should be duplicated in multiple slots when the row of node structure is not enough
357 | * @returns an array of nodes
358 | */
359 | ChildrenMatrix.prototype.getNodesToBeDuplicated = function() {
360 | const toBeDuplicatedNodes = [];
361 |
362 | this.matrix.forEach((row, i) => {
363 | row.forEach((node, j) => {
364 | if (
365 | node && // not empty slot
366 | this.matrix[i + 1] && // not last row in the matrix
367 | this.getRowActualChildrenCount(i + 1) && // next row has nodes
368 | !this.getChild({ i: i + 1, j }) && // the bottom neighbor is an empty slot
369 | // check if any node in the next row lies within the height of this node
370 | this.getSlotRowNeighbors({ i: i + 1, j }).find(
371 | item =>
372 | item.globalBounds.y >= node.globalBounds.y &&
373 | item.globalBounds.y <=
374 | node.globalBounds.y + node.globalBounds.height
375 | )
376 | ) {
377 | toBeDuplicatedNodes.push({ node, slot: { i, j } });
378 | }
379 | });
380 | });
381 |
382 | return toBeDuplicatedNodes;
383 | };
384 |
385 | /**
386 | * gets the first slot of a duplicated node if exist
387 | * @returns a slot object or null
388 | */
389 | ChildrenMatrix.prototype.checkDuplicatedNodesExist = function() {
390 | // iterate over columns
391 | for (let j = 0; j < this.n; j++) {
392 | const columnNodes = this.getColumnNodes(j);
393 |
394 | // get top duplicated node row index
395 | const nodeRowIndex = columnNodes.findIndex(
396 | (node, index) =>
397 | index < columnNodes.length - 1 && // it is not the last node in the array
398 | node.guid === columnNodes[index + 1].guid // it occupies the next node
399 | );
400 |
401 | if (nodeRowIndex !== -1) {
402 | return { i: nodeRowIndex, j };
403 | }
404 | }
405 |
406 | return null;
407 | };
408 |
409 | /**
410 | * given a slot of a duplicated node, it gets how many rows to be merged(or how many duplicated node exist)
411 | * @param {*} slot a slot that contains a duplicated node
412 | * @returns an integer representing how many duplicated nodes
413 | */
414 | ChildrenMatrix.prototype.getToBeMergedRowsCount = function(targetSlot) {
415 | const columnNodes = this.getColumnNodes(targetSlot.j);
416 |
417 | return columnNodes
418 | .slice(targetSlot.i)
419 | .reduce((acc, node, index, slicedArray) => {
420 | if (
421 | index < slicedArray.length - 1 && // node not exist in the last column
422 | node.guid === slicedArray[index + 1].guid // the next node in the column is duplicated
423 | ) {
424 | return acc + 1;
425 | }
426 |
427 | return acc;
428 | }, 1);
429 | };
430 |
431 | /**
432 | * rearrange the matrix to remove duplicated nodes and create nested ChildrenMatrix to fit complex structure
433 | * this function updates all instance variables: children, n, matrix
434 | * @param {*} slot a slot that contains a duplicated node
435 | * @returns nothing
436 | */
437 | ChildrenMatrix.prototype.rearrangeMatrix = function(targetSlot) {
438 | const toBeMergedRowsCount = this.getToBeMergedRowsCount(targetSlot);
439 | const toBeMergedRowsIndices = [targetSlot.i];
440 |
441 | for (let iterator = 1; iterator < toBeMergedRowsCount; iterator++) {
442 | toBeMergedRowsIndices.push(iterator + toBeMergedRowsIndices[0]);
443 | }
444 |
445 | let childrenCount = 1; // 1 for the duplicated nodes that will be eventually one node
446 |
447 | // iterate over rows not affected with the merge
448 | this.matrix.forEach((row, rowIndex) => {
449 | if (!toBeMergedRowsIndices.includes(rowIndex)) {
450 | childrenCount += this.getRowActualChildrenCount(rowIndex);
451 | }
452 | });
453 |
454 | // check if the duplicated node has items to be merged on its left & right
455 | let therIsLeftNodes = false;
456 | let thereIsRightNodes = false;
457 |
458 | this.matrix.forEach((row, i) => {
459 | row.forEach((node, j) => {
460 | if (node && toBeMergedRowsIndices.includes(i)) {
461 | if (j > targetSlot.j) {
462 | thereIsRightNodes = true;
463 | } else if (j < targetSlot.j) {
464 | therIsLeftNodes = true;
465 | }
466 | }
467 | });
468 | });
469 |
470 | if (therIsLeftNodes) {
471 | childrenCount += 1;
472 | }
473 |
474 | if (thereIsRightNodes) {
475 | childrenCount += 1;
476 | }
477 |
478 | const children = new Array(childrenCount);
479 | children.fill({ globalBounds: {} });
480 |
481 | const newChildrenMatrix = new ChildrenMatrix(children);
482 |
483 | // set not affected nodes
484 | this.matrix.forEach((row, i) => {
485 | if (!toBeMergedRowsIndices.includes(i)) {
486 | row.forEach((node, j) => {
487 | if (node) {
488 | if (i > targetSlot.i + toBeMergedRowsCount - 1) {
489 | newChildrenMatrix.setChild(
490 | { i: i - toBeMergedRowsCount + 1, j },
491 | node
492 | );
493 | } else {
494 | newChildrenMatrix.setChild({ i, j }, node);
495 | }
496 | }
497 | });
498 | }
499 | });
500 |
501 | // set duplicated node
502 | newChildrenMatrix.setChild(
503 | { i: targetSlot.i, j: therIsLeftNodes ? 1 : 0 },
504 | this.getChild(targetSlot)
505 | );
506 |
507 | // set nodes on left and right of the duplicated node
508 | if (therIsLeftNodes) {
509 | const leftItems = [];
510 |
511 | this.matrix.forEach((row, i) => {
512 | if (toBeMergedRowsIndices.includes(i)) {
513 | row.forEach((node, j) => {
514 | if (node && j < targetSlot.j) {
515 | leftItems.push({ node, slot: { i, j } });
516 | }
517 | });
518 | }
519 | });
520 |
521 | const targetSlotLeftCMatrixChildren = leftItems.map(item => item.node);
522 | const targetSlotLeftCMatrix = new ChildrenMatrix(
523 | targetSlotLeftCMatrixChildren
524 | );
525 |
526 | leftItems.forEach(({ node, slot }) => {
527 | targetSlotLeftCMatrix.setChild(
528 | { i: slot.i - targetSlot.i, j: slot.j },
529 | node
530 | );
531 | });
532 |
533 | newChildrenMatrix.setChild(
534 | { i: targetSlot.i, j: 0 },
535 | targetSlotLeftCMatrix
536 | );
537 | }
538 |
539 | // set its right in the slot {i: targetSlot.i, j: 2}
540 | if (thereIsRightNodes) {
541 | const rightItems = [];
542 |
543 | this.matrix.forEach((row, i) => {
544 | if (toBeMergedRowsIndices.includes(i)) {
545 | row.forEach((node, j) => {
546 | if (node && j > targetSlot.j) {
547 | rightItems.push({ node, slot: { i, j } });
548 | }
549 | });
550 | }
551 | });
552 |
553 | const targetSlotRightCMatrixChildren = rightItems.map(item => item.node);
554 | const targetSlotRightCMatrix = new ChildrenMatrix(
555 | targetSlotRightCMatrixChildren
556 | );
557 |
558 | rightItems.forEach(({ node, slot }) => {
559 | targetSlotRightCMatrix.setChild(
560 | { i: slot.i - targetSlot.i, j: slot.j - targetSlot.j - 1 },
561 | node
562 | );
563 | });
564 |
565 | newChildrenMatrix.setChild(
566 | { i: targetSlot.i, j: therIsLeftNodes ? 2 : 1 },
567 | targetSlotRightCMatrix
568 | );
569 | }
570 |
571 | this.n = newChildrenMatrix.n;
572 | this.children = newChildrenMatrix.children;
573 | this.matrix = newChildrenMatrix.matrix;
574 | };
575 |
576 | /**
577 | * lays the children nodes in the matrix
578 | * @returns the matrix after laying the children in
579 | */
580 | ChildrenMatrix.prototype.layChildrenInsideMatrix = function() {
581 | this.sortChildren();
582 |
583 | this.children.forEach(child => {
584 | const suitableSlot = this.getMostSuitableSlot(child);
585 |
586 | this.setChild(suitableSlot, child);
587 | });
588 |
589 | let toBeDuplicatedNodes = this.getNodesToBeDuplicated();
590 |
591 | while (toBeDuplicatedNodes.length) {
592 | toBeDuplicatedNodes.forEach(({ node, slot }) => {
593 | this.setChild({ i: slot.i + 1, j: slot.j }, node);
594 | });
595 |
596 | toBeDuplicatedNodes = this.getNodesToBeDuplicated();
597 | }
598 |
599 | let tSlot = this.checkDuplicatedNodesExist();
600 | while (tSlot) {
601 | this.rearrangeMatrix(tSlot);
602 |
603 | tSlot = this.checkDuplicatedNodesExist();
604 | }
605 |
606 | return this.matrix;
607 | };
608 |
609 | module.exports = {
610 | ChildrenMatrix
611 | };
612 |
--------------------------------------------------------------------------------