├── .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 | ![Demo GIF](https://github.com/hossamnasser938/React-Native-Generator-Plugin/blob/master/plugin_readme_demo.gif) 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 | --------------------------------------------------------------------------------