├── .DS_Store ├── .babelrc ├── .gitignore ├── .vscode ├── extension.json ├── launch.json └── tasks.json ├── .vscodeignore ├── LICENSE.md ├── README.md ├── icon.png ├── out ├── 0ab669b7a0d19b178f57.woff ├── 1431d1cef06ad04f5458.woff2 ├── 168d6383e73339293ac3.woff ├── 169619821ea93019d1bb.woff2 ├── 227c93190fe7f82de3f8.woff2 ├── 3230f9b040f3c630e0c3.woff2 ├── 32fc45a3d1e8ea11fabc.woff2 ├── 3425a701027d0699e369.woff2 ├── 35b9d6be04b95f0f0530.woff2 ├── 4777461b144e55145268.woff2 ├── 50e795c1345353b0e996.woff2 ├── 62ced72e5832f02c2796.woff2 ├── 6fb9cffb1d3e72bf9293.woff2 ├── 71a33b6b50457b2c903a.woff2 ├── 804378952da8a10faae2.woff2 ├── 861b791f9de857a6e7bc.woff2 ├── 9165081d10e1ba601384.woff2 ├── 9ac81fefbe6c319ea40b.woff2 ├── a457fde362a540fcadff.woff ├── b009a76ad6afe4ebd301.woff2 ├── bd9854c751441ccc1a70.woff2 ├── be4d02458ce53887dc37.woff2 ├── c35e4c3958e209d17b31.woff2 ├── c48fb6765a9fcb00b330.woff2 ├── c5d001fa922fa66a147f.woff ├── cad7d3d9cb265e334e58.woff2 ├── d010f1f324e111a22e53.woff2 ├── d8642a3d1d4ef6179644.woff2 ├── db2632771401f61463fe.woff2 ├── dc7dcec8e3f654e0ed63.woff2 ├── ed67ad54b1a8f5d21150.woff2 ├── extension.js ├── f25d774ecfe0996f8eb5.woff2 ├── main.wv.js └── main.wv.js.LICENSE.txt ├── package.json ├── src ├── .DS_Store ├── extension.ts ├── getNonce.ts ├── media │ ├── Install_ReacTree.png │ ├── ReactTree_Background.png │ ├── ReactTree_Logo.png │ ├── ReactTree_Logo_noBackground.png │ ├── favicon.ico │ ├── icon.png │ ├── launch-props-open-files.gif │ ├── navbar-controls.gif │ ├── status_bar_icon.png │ ├── themes.gif │ └── vscode marketplace search.png ├── panel.ts ├── parser.ts ├── test │ ├── runTest.ts │ ├── suite │ │ ├── extension.test.ts │ │ ├── index.ts │ │ └── parser.test.ts │ └── test_apps │ │ ├── test_0 │ │ ├── components │ │ │ └── App.jsx │ │ └── index.js │ │ ├── test_1 │ │ ├── components │ │ │ ├── App.jsx │ │ │ └── Main.jsx │ │ └── index.js │ │ ├── test_10 │ │ ├── components │ │ │ ├── App.jsx │ │ │ ├── DrillCreator.jsx │ │ │ ├── ExerciseCreator.jsx │ │ │ ├── ExercisesDisplay.jsx │ │ │ ├── HistoryDisplay.jsx │ │ │ ├── Login.jsx │ │ │ ├── Logout.jsx │ │ │ ├── Nav.jsx │ │ │ └── Signup.jsx │ │ └── index.jsx │ │ ├── test_11 │ │ ├── components │ │ │ ├── App1.jsx │ │ │ └── App2.jsx │ │ └── index.js │ │ ├── test_12 │ │ ├── components │ │ │ └── Navbar.jsx │ │ └── pages │ │ │ └── index.js │ │ ├── test_13 │ │ ├── components │ │ │ ├── App.jsx │ │ │ ├── Page1.jsx │ │ │ ├── Page2.jsx │ │ │ └── Page3.jsx │ │ └── index.js │ │ ├── test_2 │ │ └── index.js │ │ ├── test_3 │ │ ├── App.jsx │ │ ├── actions │ │ │ └── actions.js │ │ ├── constants │ │ │ └── actionTypes.js │ │ ├── containers │ │ │ ├── ConnectedContainer.jsx │ │ │ └── UnconnectedContainer.jsx │ │ ├── index.html │ │ ├── index.js │ │ ├── reducers │ │ │ ├── fakeReducer.js │ │ │ └── index.js │ │ └── store.js │ │ ├── test_4 │ │ └── index.js │ │ ├── test_5 │ │ ├── components │ │ │ ├── Container.js │ │ │ ├── JS.js │ │ │ ├── JSX.jsx │ │ │ ├── TS.ts │ │ │ └── TSX.tsx │ │ └── index.js │ │ ├── test_6 │ │ ├── App2.jsx │ │ ├── components │ │ │ └── App1.jsx │ │ └── index.js │ │ ├── test_7 │ │ ├── components │ │ │ ├── App.jsx │ │ │ └── Main.jsx │ │ └── index.js │ │ ├── test_8 │ │ ├── components │ │ │ ├── App.jsx │ │ │ └── Main.jsx │ │ └── index.js │ │ └── test_9 │ │ ├── components │ │ ├── App.jsx │ │ └── Main.jsx │ │ └── index.js ├── types │ ├── ImportObj.ts │ ├── Tree.ts │ └── index.d.ts └── webview │ ├── .DS_Store │ ├── components │ ├── Flow.tsx │ ├── Navbar.tsx │ └── Sidebar.tsx │ ├── dagre.css │ ├── index.tsx │ └── style.css ├── tsconfig.json └── webpack.config.ts /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /.vscode-test/ 3 | /*.vsix 4 | /.DS_Store 5 | /package-lock.json 6 | -------------------------------------------------------------------------------- /.vscode/extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["ms-vscode.vscode-typescript-tslint-plugin", "msjsdiag.debugger-for-chrome"] 3 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": [ 10 | "--extensionDevelopmentPath=${workspaceFolder}" 11 | ], 12 | "outFiles": [ 13 | "${workspaceFolder}/out/**/*.js" 14 | ], 15 | "preLaunchTask": "npm: compile" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": [ 10 | "--extensionDevelopmentPath=${workspaceFolder}" 11 | ], 12 | "outFiles": [ 13 | "${workspaceFolder}/out/*/.js" 14 | ], 15 | "preLaunchTask": "npm: compile" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | **/* 2 | 3 | !out/**/*.js 4 | !CHANGELOG.md 5 | !packages.json 6 | !images 7 | !README.md 8 | !icon.png 9 | !notes-usage 10 | !upgrade -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 reacTree 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | 6 | Logo 7 | 8 |

ReacTree

9 |

10 | 11 |

12 | A VS Code extension with a dynamic and interactive hierarchy visualizer for React applications. 13 |
14 |
15 |

16 | 17 | 18 |
19 | 20 | 21 | Install ReacTree 22 | 23 |

24 |
25 | 26 |
27 | 28 | 29 |
30 | Table of Contents 31 |
    32 |
  1. Overview
  2. 33 |
  3. Installation
  4. 34 |
  5. Getting Started
  6. 35 |
  7. Functionality
  8. 36 |
  9. Tech Stack
  10. 37 |
  11. Articles
  12. 38 |
  13. Contributing
  14. 39 |
  15. Meet our Team
  16. 40 |
  17. License
  18. 41 |
42 |
43 | 44 |
45 |
46 | 47 | ## Overview 48 | 49 |

50 | ReacTree is a VS Code extension which visualizes the component hierarchy within a React application, enabling developers to quickly identify the relationships between components. The extension generates a hierarchy tree of React components, displaying the parent-child relationships and how data is passed between components. 51 |

52 |
53 |

54 | 55 |

56 | 57 | ## Installation 58 | 59 | The ReacTree extension can be easily installed via the VS Code Marketplace. Bring up the Extensions view by clicking on the Extensions icon in the Activity Bar on the side of VS Code or by using the View: Extensions command (Ctrl+Shift+X). Type ‘reactree’ in the search box and select the Install button. VS Code will download and install the extension from the Marketplace. 60 |
61 | 62 |

63 |
64 | 65 |
66 |

67 |
68 | 69 | ## Getting Started 70 | 71 | After installing the ReacTree extension in your VSCode, a ‘Start Tree’ item will be added to the Status Bar of your VS Code (bottom right). The extension can be launched by clicking on the Start Tree item on the Status Bar or by using the Command Palette (Ctrl+Shift+P) and selecting ReactTree: Show Panel. 72 |
73 | 74 |

75 |
76 | 77 |
78 |

79 |
80 | 81 | ## Functionality 82 | 83 | - After launching the extension, click on the Select File button and select the file you want to serve as the root. The extension generates a hierarchy tree of React components, displaying the parent-child relationships and the data passed between components. 84 | - Toggle the tree's nodes to view the component's props. Easily access the component files by clicking the file button, which will direct you to the corresponding file. 85 |

86 | 87 |

88 | 89 |
90 |
91 | 92 | - Customize the tree by dragging and dropping components to your preferred layout. Easily switch your view to vertical or horizontal with a click of a button. You can also lock your tree in place so you don't accidentally move your tree. 93 | 94 |

95 | 96 |

97 | 98 |
99 |
100 | 101 | - ⇧⌘P(Mac) Ctrl+Shift+P (Windows) then type "Preferences:Color Theme" (or use ⌘K⌘T on Mac or Ctlr+K Ctrl+T on Windows) to change the theme of VSCode and ReacTree that best suits your preference. 102 |

103 | 104 |

105 | 106 |
107 |
108 |
109 | 110 | ## Tech Stack 111 | 112 | - [React](https://reactjs.org/) 113 | - [Typescript](https://www.typescriptlang.org/) 114 | - [Reactflow](https://reactflow.dev/) 115 | - [VSCode Extension API](https://code.visualstudio.com/api) 116 | - [Babel Parser](https://babeljs.io/docs/en/babel-parser) 117 | - [Webpack](https://webpack.js.org/) 118 |
119 |
120 | 121 | ## Articles 122 | 123 | Checkout out our medium article for more information about ReacTree! 124 | 125 | Additionally, we realized documentation on building a VSCode Webview Panel with React and Messaging is scarce. Don't worry, we wrote this article which goes in depth to easily understand how to build a Webview Panel! 126 | 127 |
128 |
129 | 130 | ## Contributing 131 | 132 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 133 | 134 | You can check out more information and get started with ReacTree on its official webpage and on its LinkedIn page. These pages provide useful information about the project, including how it works, its key features, and how to get started with using it. 135 | 136 | Additionally, you can find the project’s source code, documentation, and issue tracker in Github. You can also fork the project, make changes, and submit pull requests to help improve the project. 137 | 138 | If you like the project and find it useful, please consider giving it a star on GitHub. This can help increase visibility for the project and attract more contributors and users. 139 | 140 |

141 |
142 | Report Bug / Request Feature 143 |

144 |
145 | 146 | ## Meet Our Team 147 | 148 | - Justin Kim • [LinkedIn](https://www.linkedin.com/in/justin27kim/) • [Github](https://github.com/justin27kim) 149 | - Fabian Salazar • [LinkedIn](https://www.linkedin.com/in/fabian-salazar-260a7957/) • [Github](https://github.com/fsalazar88) 150 | - Brian Noh • [LinkedIn](https://www.linkedin.com/in/briannohski/) • [Github](https://github.com/dogenoh) 151 | - Mike Benliyan• [LinkedIn](https://www.linkedin.com/in/michaelbenliyan/) • [Github](https://github.com/MichaelBenliyan) 152 | - Kevin Liu• [LinkedIn](https://www.linkedin.com/in/kevindliu/) • [Github](https://github.com/K8Liu) 153 | 154 |
155 | 156 | ## License 157 | 158 | 159 | 160 | ReacTree is developed under the [MIT license](https://github.com/open-source-labs/ZusTime/LICENSE) 161 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/icon.png -------------------------------------------------------------------------------- /out/0ab669b7a0d19b178f57.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/0ab669b7a0d19b178f57.woff -------------------------------------------------------------------------------- /out/1431d1cef06ad04f5458.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/1431d1cef06ad04f5458.woff2 -------------------------------------------------------------------------------- /out/168d6383e73339293ac3.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/168d6383e73339293ac3.woff -------------------------------------------------------------------------------- /out/169619821ea93019d1bb.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/169619821ea93019d1bb.woff2 -------------------------------------------------------------------------------- /out/227c93190fe7f82de3f8.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/227c93190fe7f82de3f8.woff2 -------------------------------------------------------------------------------- /out/3230f9b040f3c630e0c3.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/3230f9b040f3c630e0c3.woff2 -------------------------------------------------------------------------------- /out/32fc45a3d1e8ea11fabc.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/32fc45a3d1e8ea11fabc.woff2 -------------------------------------------------------------------------------- /out/3425a701027d0699e369.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/3425a701027d0699e369.woff2 -------------------------------------------------------------------------------- /out/35b9d6be04b95f0f0530.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/35b9d6be04b95f0f0530.woff2 -------------------------------------------------------------------------------- /out/4777461b144e55145268.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/4777461b144e55145268.woff2 -------------------------------------------------------------------------------- /out/50e795c1345353b0e996.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/50e795c1345353b0e996.woff2 -------------------------------------------------------------------------------- /out/62ced72e5832f02c2796.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/62ced72e5832f02c2796.woff2 -------------------------------------------------------------------------------- /out/6fb9cffb1d3e72bf9293.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/6fb9cffb1d3e72bf9293.woff2 -------------------------------------------------------------------------------- /out/71a33b6b50457b2c903a.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/71a33b6b50457b2c903a.woff2 -------------------------------------------------------------------------------- /out/804378952da8a10faae2.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/804378952da8a10faae2.woff2 -------------------------------------------------------------------------------- /out/861b791f9de857a6e7bc.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/861b791f9de857a6e7bc.woff2 -------------------------------------------------------------------------------- /out/9165081d10e1ba601384.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/9165081d10e1ba601384.woff2 -------------------------------------------------------------------------------- /out/9ac81fefbe6c319ea40b.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/9ac81fefbe6c319ea40b.woff2 -------------------------------------------------------------------------------- /out/a457fde362a540fcadff.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/a457fde362a540fcadff.woff -------------------------------------------------------------------------------- /out/b009a76ad6afe4ebd301.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/b009a76ad6afe4ebd301.woff2 -------------------------------------------------------------------------------- /out/bd9854c751441ccc1a70.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/bd9854c751441ccc1a70.woff2 -------------------------------------------------------------------------------- /out/be4d02458ce53887dc37.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/be4d02458ce53887dc37.woff2 -------------------------------------------------------------------------------- /out/c35e4c3958e209d17b31.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/c35e4c3958e209d17b31.woff2 -------------------------------------------------------------------------------- /out/c48fb6765a9fcb00b330.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/c48fb6765a9fcb00b330.woff2 -------------------------------------------------------------------------------- /out/c5d001fa922fa66a147f.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/c5d001fa922fa66a147f.woff -------------------------------------------------------------------------------- /out/cad7d3d9cb265e334e58.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/cad7d3d9cb265e334e58.woff2 -------------------------------------------------------------------------------- /out/d010f1f324e111a22e53.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/d010f1f324e111a22e53.woff2 -------------------------------------------------------------------------------- /out/d8642a3d1d4ef6179644.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/d8642a3d1d4ef6179644.woff2 -------------------------------------------------------------------------------- /out/db2632771401f61463fe.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/db2632771401f61463fe.woff2 -------------------------------------------------------------------------------- /out/dc7dcec8e3f654e0ed63.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/dc7dcec8e3f654e0ed63.woff2 -------------------------------------------------------------------------------- /out/ed67ad54b1a8f5d21150.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/ed67ad54b1a8f5d21150.woff2 -------------------------------------------------------------------------------- /out/f25d774ecfe0996f8eb5.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/out/f25d774ecfe0996f8eb5.woff2 -------------------------------------------------------------------------------- /out/main.wv.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright (c) 2018 Jed Watson. 3 | Licensed under the MIT License (MIT), see 4 | http://jedwatson.github.io/classnames 5 | */ 6 | 7 | /** 8 | * @license React 9 | * react-dom.production.min.js 10 | * 11 | * Copyright (c) Facebook, Inc. and its affiliates. 12 | * 13 | * This source code is licensed under the MIT license found in the 14 | * LICENSE file in the root directory of this source tree. 15 | */ 16 | 17 | /** 18 | * @license React 19 | * react-jsx-runtime.production.min.js 20 | * 21 | * Copyright (c) Facebook, Inc. and its affiliates. 22 | * 23 | * This source code is licensed under the MIT license found in the 24 | * LICENSE file in the root directory of this source tree. 25 | */ 26 | 27 | /** 28 | * @license React 29 | * react.production.min.js 30 | * 31 | * Copyright (c) Facebook, Inc. and its affiliates. 32 | * 33 | * This source code is licensed under the MIT license found in the 34 | * LICENSE file in the root directory of this source tree. 35 | */ 36 | 37 | /** 38 | * @license React 39 | * scheduler.production.min.js 40 | * 41 | * Copyright (c) Facebook, Inc. and its affiliates. 42 | * 43 | * This source code is licensed under the MIT license found in the 44 | * LICENSE file in the root directory of this source tree. 45 | */ 46 | 47 | /** 48 | * @license React 49 | * use-sync-external-store-shim.production.min.js 50 | * 51 | * Copyright (c) Facebook, Inc. and its affiliates. 52 | * 53 | * This source code is licensed under the MIT license found in the 54 | * LICENSE file in the root directory of this source tree. 55 | */ 56 | 57 | /** 58 | * @license React 59 | * use-sync-external-store-shim/with-selector.production.min.js 60 | * 61 | * Copyright (c) Facebook, Inc. and its affiliates. 62 | * 63 | * This source code is licensed under the MIT license found in the 64 | * LICENSE file in the root directory of this source tree. 65 | */ 66 | 67 | /** 68 | * @mui/styled-engine v5.11.9 69 | * 70 | * @license MIT 71 | * This source code is licensed under the MIT license found in the 72 | * LICENSE file in the root directory of this source tree. 73 | */ 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactree", 3 | "displayName": "ReacTree", 4 | "description": "React hierarchy tree visualizer and navigation tool", 5 | "version": "1.0.7", 6 | "icon": "icon.png", 7 | "publisher": "ReacTreeDev", 8 | "preview": false, 9 | "engines": { 10 | "vscode": "^1.74.3" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/oslabs-beta/ReacTree/tree/main" 15 | }, 16 | "categories": [ 17 | "Other" 18 | ], 19 | "keywords": [ 20 | "react", 21 | "hierarchy tree", 22 | "react components", 23 | "parent-child", 24 | "navigation", 25 | "visualizer" 26 | ], 27 | "license": "MIT", 28 | "activationEvents": [ 29 | "onStartupFinished" 30 | ], 31 | "main": "./out/extension.js", 32 | "contributes": { 33 | "commands": [ 34 | { 35 | "command": "reacTree.start", 36 | "title": "ReacTree: Show Panel" 37 | }, 38 | { 39 | "command": "reacTree.startStatusBar", 40 | "title": "ReacTree: Show StatusBar" 41 | } 42 | ] 43 | }, 44 | "extensionPack": [ 45 | "docsmsft.docs-markdown", 46 | "docsmsft.docs-preview" 47 | ], 48 | "scripts": { 49 | "vscode:prepublish": "npm run compile", 50 | "compile": "webpack --mode production --config webpack.config.ts", 51 | "watch": "webpack --mode development --config webpack.config.ts --watch", 52 | "test": "node ./out/test/runTest.js" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.7.7", 56 | "@babel/preset-react": "^7.18.6", 57 | "@types/babel__core": "^7.20.0", 58 | "@types/chai": "^4.3.4", 59 | "@types/dagre": "^0.7.48", 60 | "@types/fs-extra": "^8.0.1", 61 | "@types/jest": "^24.0.24", 62 | "@types/mocha": "^10.0.1", 63 | "@types/node": "^12.20.55", 64 | "@types/object-path": "^0.11.0", 65 | "@types/rimraf": "^2.0.3", 66 | "@types/webpack": "^4.41.0", 67 | "@vscode/test-electron": "^2.2.3", 68 | "babel-loader": "^8.0.6", 69 | "chai": "^4.3.7", 70 | "copy-webpack-plugin": "^5.1.1", 71 | "glob": "^8.1.0", 72 | "mocha": "^10.2.0", 73 | "ts-loader": "^9.4.2", 74 | "ts-node": "^8.5.4", 75 | "typescript": "^4.9.5", 76 | "webpack": "^5.75.0", 77 | "webpack-cli": "^5.0.1" 78 | }, 79 | "dependencies": { 80 | "@babel/preset-env": "^7.20.2", 81 | "@coreui/icons": "^3.0.0", 82 | "@coreui/icons-react": "^2.1.0", 83 | "@emotion/react": "^11.10.5", 84 | "@emotion/styled": "^11.10.5", 85 | "@fontsource/roboto": "^4.5.8", 86 | "@fortawesome/free-solid-svg-icons": "^6.3.0", 87 | "@fortawesome/react-fontawesome": "^0.2.0", 88 | "@mui/icons-material": "^5.11.0", 89 | "@mui/material": "^5.11.8", 90 | "@types/react": "^18.0.27", 91 | "@types/react-dom": "^18.0.10", 92 | "@types/vscode": "^1.75.0", 93 | "babel-loader": "^9.1.2", 94 | "css-loader": "^6.7.3", 95 | "dagre": "^0.8.5", 96 | "fs-extra": "^8.1.0", 97 | "object-path": "^0.11.4", 98 | "path": "^0.12.7", 99 | "react": "^18.2.0", 100 | "react-dom": "^18.2.0", 101 | "reactflow": "^11.5.2", 102 | "sass-loader": "^13.2.0", 103 | "style-loader": "^3.3.1" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/.DS_Store -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import ReacTreePanel from './panel'; 3 | 4 | export function activate(extContext: vscode.ExtensionContext) { 5 | extContext.subscriptions.push( 6 | vscode.commands.registerCommand('reacTree.start', () => { 7 | ReacTreePanel.createOrShow(extContext); 8 | }) 9 | ); 10 | 11 | // Create reacTree status bar button 12 | const item = vscode.window.createStatusBarItem( 13 | vscode.StatusBarAlignment.Right 14 | ); 15 | 16 | item.command = 'reacTree.start'; 17 | item.tooltip = 'Activate ReacTree'; 18 | item.text = '$(type-hierarchy) Start Tree'; 19 | item.show(); 20 | } 21 | 22 | export function deactivate() {} 23 | -------------------------------------------------------------------------------- /src/getNonce.ts: -------------------------------------------------------------------------------- 1 | export function getNonce() : string { 2 | let text : string = ""; 3 | const possible : string = 4 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 5 | for (let i = 0; i < 32; i++) { 6 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 7 | } 8 | return text; 9 | } 10 | -------------------------------------------------------------------------------- /src/media/Install_ReacTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/Install_ReacTree.png -------------------------------------------------------------------------------- /src/media/ReactTree_Background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/ReactTree_Background.png -------------------------------------------------------------------------------- /src/media/ReactTree_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/ReactTree_Logo.png -------------------------------------------------------------------------------- /src/media/ReactTree_Logo_noBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/ReactTree_Logo_noBackground.png -------------------------------------------------------------------------------- /src/media/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/favicon.ico -------------------------------------------------------------------------------- /src/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/icon.png -------------------------------------------------------------------------------- /src/media/launch-props-open-files.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/launch-props-open-files.gif -------------------------------------------------------------------------------- /src/media/navbar-controls.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/navbar-controls.gif -------------------------------------------------------------------------------- /src/media/status_bar_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/status_bar_icon.png -------------------------------------------------------------------------------- /src/media/themes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/themes.gif -------------------------------------------------------------------------------- /src/media/vscode marketplace search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/media/vscode marketplace search.png -------------------------------------------------------------------------------- /src/panel.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { getNonce } from './getNonce'; 3 | import { Parser } from './parser'; 4 | 5 | export default class ReacTreePanel { 6 | public static currentPanel: ReacTreePanel | undefined; 7 | 8 | private static readonly viewType = 'reacTree'; 9 | 10 | private readonly _panel: vscode.WebviewPanel; 11 | private readonly _extensionUri: vscode.Uri; 12 | private readonly _extContext: vscode.ExtensionContext; 13 | private parser: Parser | undefined; 14 | private _disposables: vscode.Disposable[] = []; 15 | 16 | public static createOrShow(extContext: vscode.ExtensionContext) { 17 | const column = vscode.window.activeTextEditor 18 | ? vscode.window.activeTextEditor.viewColumn 19 | : undefined; 20 | 21 | // If we already have a panel, show it. 22 | // Otherwise, create a new panel. 23 | if (ReacTreePanel.currentPanel) { 24 | ReacTreePanel.currentPanel._panel.reveal(column); 25 | } else { 26 | // ReactPanel.currentPanel = new ReactPanel(extensionPath, column || vscode.ViewColumn.One); 27 | ReacTreePanel.currentPanel = new ReacTreePanel( 28 | extContext, 29 | vscode.ViewColumn.Two 30 | ); 31 | } 32 | } 33 | 34 | private constructor( 35 | extContext: vscode.ExtensionContext, 36 | column: vscode.ViewColumn 37 | ) { 38 | this._extContext = extContext; 39 | this._extensionUri = extContext.extensionUri; 40 | // Not added - state preserver** 41 | 42 | // Create and show a new webview panel 43 | this._panel = vscode.window.createWebviewPanel( 44 | ReacTreePanel.viewType, 45 | 'ReacTree', 46 | column, 47 | { 48 | // Enable javascript in the webview 49 | enableScripts: true, 50 | retainContextWhenHidden: true, 51 | // And restric the webview to only loading content from our extension's `media` directory. 52 | localResourceRoots: [this._extensionUri], 53 | } 54 | ); 55 | 56 | // Set webview favicon 57 | this._panel.iconPath = vscode.Uri.joinPath(this._extensionUri, "src/media", "favicon.ico"); 58 | 59 | // Set the webview's initial html content 60 | this._panel.webview.html = this._getHtmlForWebview(this._panel.webview); 61 | 62 | // Listen for when the panel is disposed 63 | // This happens when the user closes the panel or when the panel is closed programatically 64 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 65 | 66 | // Handle messages from the webview 67 | this._panel.webview.onDidReceiveMessage( 68 | async (msg: any) => { 69 | switch (msg.type) { 70 | case 'onFile': 71 | if (!msg.value) break; //if doesnt work change to return 72 | this.parser = new Parser(msg.value); 73 | this.parser.parse(); 74 | this.updateView(); 75 | break; 76 | case 'onViewFile': 77 | if (!msg.value) return; 78 | const doc = await vscode.workspace.openTextDocument(msg.value); 79 | const editor = await vscode.window.showTextDocument(doc, { 80 | preserveFocus: false, 81 | preview: false, 82 | }); 83 | break; 84 | } 85 | }, 86 | null, 87 | this._disposables 88 | ); 89 | } 90 | 91 | private async updateView() { 92 | // Save current state of tree to workspace state: 93 | const tree = this.parser!.getTree(); 94 | this._extContext.workspaceState.update('reacTree', tree); 95 | // Send updated tree to webview 96 | this._panel.webview.postMessage({ 97 | type: 'parsed-data', 98 | value: tree, 99 | settings: await vscode.workspace.getConfiguration('reacTree'), 100 | }); 101 | } 102 | 103 | public dispose() { 104 | ReacTreePanel.currentPanel = undefined; 105 | // Clean up our resources 106 | this._panel.dispose(); 107 | while (this._disposables.length) { 108 | const x = this._disposables.pop(); 109 | if (x) { 110 | x.dispose(); 111 | } 112 | } 113 | } 114 | 115 | private _getHtmlForWebview(webview: vscode.Webview) { 116 | const scriptUri = webview.asWebviewUri( 117 | vscode.Uri.joinPath(this._extensionUri, 'out', 'main.wv.js') 118 | ); 119 | 120 | const styleUri = webview.asWebviewUri( 121 | vscode.Uri.joinPath(this._extensionUri, 'media', 'styles.css') 122 | ); 123 | 124 | // Use a nonce to whitelist which scripts can be run 125 | const nonce = getNonce(); 126 | 127 | return ` 128 | 129 | 130 | 131 | 132 | reacttree 133 | 134 | 135 | 136 |
137 | 143 | 144 | 145 | 146 | `; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import * as babelParser from '@babel/parser'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | import { getNonce } from './getNonce'; 5 | import { Tree } from './types/Tree'; 6 | import { ImportObj } from './types/ImportObj'; 7 | import { File } from '@babel/types'; 8 | 9 | export class Parser { 10 | entryFile: string; 11 | tree: Tree | undefined; 12 | 13 | constructor(filePath: string) { 14 | // Fix when selecting files in wsl file system 15 | this.entryFile = filePath; 16 | if (process.platform === 'linux' && this.entryFile.includes('wsl$')) { 17 | this.entryFile = path.resolve( 18 | filePath.split(path.win32.sep).join(path.posix.sep) 19 | ); 20 | this.entryFile = '/' + this.entryFile.split('/').slice(3).join('/'); 21 | // Fix for when running wsl but selecting files held on windows file system 22 | } else if ( 23 | process.platform === 'linux' && 24 | /[a-zA-Z]/.test(this.entryFile[0]) 25 | ) { 26 | const root = `/mnt/${this.entryFile[0].toLowerCase()}`; 27 | this.entryFile = path.join( 28 | root, 29 | filePath.split(path.win32.sep).slice(1).join(path.posix.sep) 30 | ); 31 | } 32 | 33 | this.tree = undefined; 34 | // Break down and reasemble given filePath safely for any OS using path? 35 | } 36 | 37 | // Public method to generate component tree based on current entryFile 38 | public parse(): Tree { 39 | // Create root Tree node 40 | const root = { 41 | id: getNonce(), 42 | name: path.basename(this.entryFile).replace(/\.(t|j)sx?$/, ''), 43 | fileName: path.basename(this.entryFile), 44 | filePath: this.entryFile, 45 | importPath: '/', // this.entryFile here breaks windows file path on root e.g. C:\\ is detected as third party 46 | expanded: false, 47 | depth: 0, 48 | count: 1, 49 | thirdParty: false, 50 | reactRouter: false, 51 | reduxConnect: false, 52 | children: [], 53 | parentList: [], 54 | props: {}, 55 | error: '', 56 | }; 57 | 58 | this.tree = root; 59 | this.parser(root); 60 | return this.tree; 61 | } 62 | 63 | public getTree(): Tree { 64 | return this.tree!; 65 | } 66 | 67 | // Set Sapling Parser with a specific Data Tree (from workspace state) 68 | public setTree(tree: Tree): void { 69 | this.entryFile = tree.filePath; 70 | this.tree = tree; 71 | } 72 | 73 | public updateTree(filePath: string): Tree { 74 | let children: any[] = []; 75 | 76 | const getChildNodes = (node: Tree): void => { 77 | const { depth, filePath, expanded } = node; 78 | children.push({ depth, filePath, expanded }); 79 | }; 80 | 81 | const matchExpand = (node: Tree): void => { 82 | for (let i = 0; i < children.length; i += 1) { 83 | const oldNode = children[i]; 84 | if ( 85 | oldNode.depth === node.depth && 86 | oldNode.filePath === node.filePath && 87 | oldNode.expanded 88 | ) { 89 | node.expanded = true; 90 | } 91 | } 92 | }; 93 | 94 | const callback = (node: Tree): void => { 95 | if (node.filePath === filePath) { 96 | node.children.forEach((child) => { 97 | this.traverseTree(getChildNodes, child); 98 | }); 99 | 100 | const newNode = this.parser(node); 101 | 102 | this.traverseTree(matchExpand, newNode); 103 | 104 | children = []; 105 | } 106 | }; 107 | 108 | this.traverseTree(callback, this.tree); 109 | 110 | return this.tree!; 111 | } 112 | 113 | // Traverses the tree and changes expanded property of node whose id matches provided id 114 | public toggleNode(id: string, expanded: boolean): Tree { 115 | const callback = (node: { id: string; expanded: boolean }) => { 116 | if (node.id === id) { 117 | node.expanded = expanded; 118 | } 119 | }; 120 | 121 | this.traverseTree(callback, this.tree); 122 | 123 | return this.tree!; 124 | } 125 | 126 | // Traverses all nodes of current component tree and applies callback to each node 127 | private traverseTree( 128 | callback: Function, 129 | node: Tree | undefined = this.tree 130 | ): void { 131 | if (!node) { 132 | return; 133 | } 134 | 135 | callback(node); 136 | 137 | node.children.forEach((childNode) => { 138 | this.traverseTree(callback, childNode); 139 | }); 140 | } 141 | 142 | // Recursively builds the React component tree structure starting from root node 143 | private parser(componentTree: Tree): Tree | undefined { 144 | // If import is a node module, do not parse any deeper 145 | if (!['\\', '/', '.'].includes(componentTree.importPath[0])) { 146 | componentTree.thirdParty = true; 147 | if ( 148 | componentTree.fileName === 'react-router-dom' || 149 | componentTree.fileName === 'react-router' 150 | ) { 151 | componentTree.reactRouter = true; 152 | } 153 | return; 154 | } 155 | 156 | // Check that file has valid fileName/Path, if not found, add error to node and halt 157 | const fileName = this.getFileName(componentTree); 158 | if (!fileName) { 159 | componentTree.error = 'File not found.'; 160 | return; 161 | } 162 | 163 | // If current node recursively calls itself, do not parse any deeper: 164 | if (componentTree.parentList.includes(componentTree.filePath)) { 165 | return; 166 | } 167 | 168 | // Create abstract syntax tree of current component tree file 169 | let ast: babelParser.ParseResult; 170 | try { 171 | ast = babelParser.parse( 172 | fs.readFileSync(path.resolve(componentTree.filePath), 'utf-8'), 173 | { 174 | sourceType: 'module', 175 | tokens: true, 176 | plugins: ['jsx', 'typescript'], 177 | } 178 | ); 179 | } catch (err) { 180 | componentTree.error = 'Error while processing this file/node'; 181 | return componentTree; 182 | } 183 | 184 | // Find imports in the current file, then find child components in the current file 185 | const imports = this.getImports(ast.program.body); 186 | 187 | // Get any JSX Children of current file: 188 | if (ast.tokens) { 189 | componentTree.children = this.getJSXChildren( 190 | ast.tokens, 191 | imports, 192 | componentTree 193 | ); 194 | } 195 | 196 | // Check if current node is connected to the Redux store 197 | if (ast.tokens) { 198 | componentTree.reduxConnect = this.checkForRedux(ast.tokens, imports); 199 | } 200 | 201 | // Recursively parse all child components 202 | componentTree.children.forEach((child) => this.parser(child)); 203 | 204 | return componentTree; 205 | } 206 | 207 | // Finds files where import string does not include a file extension 208 | private getFileName(componentTree: Tree): string | undefined { 209 | const ext = path.extname(componentTree.filePath); 210 | let fileName: string | undefined = componentTree.fileName; 211 | 212 | if (!ext) { 213 | // Try and find file extension that exists in directory: 214 | const fileArray = fs.readdirSync(path.dirname(componentTree.filePath)); 215 | const regEx = new RegExp(`${componentTree.fileName}.(j|t)sx?$`); 216 | fileName = fileArray.find((fileStr) => fileStr.match(regEx)); 217 | fileName ? (componentTree.filePath += path.extname(fileName)) : null; 218 | } 219 | 220 | return fileName; 221 | } 222 | 223 | // Extracts Imports from current file 224 | // const Page1 = lazy(() => import('./page1')); -> is parsed as 'ImportDeclaration' 225 | // import Page2 from './page2'; -> is parsed as 'VariableDeclaration' 226 | private getImports(body: { [key: string]: any }[]): ImportObj { 227 | const bodyImports = body.filter( 228 | (item) => item.type === 'ImportDeclaration' || 'VariableDeclaration' 229 | ); 230 | // console.log('bodyImports are: ', bodyImports); 231 | return bodyImports.reduce((accum, curr) => { 232 | // Import Declarations: 233 | if (curr.type === 'ImportDeclaration') { 234 | curr.specifiers.forEach( 235 | (i: { 236 | local: { name: string | number }; 237 | imported: { name: any }; 238 | }) => { 239 | accum[i.local.name] = { 240 | importPath: curr.source.value, 241 | importName: i.imported ? i.imported.name : i.local.name, 242 | }; 243 | } 244 | ); 245 | } 246 | // Imports Inside Variable Declarations: // Not easy to deal with nested objects 247 | if (curr.type === 'VariableDeclaration') { 248 | const importPath = this.findVarDecImports(curr.declarations[0]); 249 | if (importPath) { 250 | const importName = curr.declarations[0].id.name; 251 | accum[curr.declarations[0].id.name] = { 252 | importPath, 253 | importName, 254 | }; 255 | } 256 | } 257 | return accum; 258 | }, {}); 259 | } 260 | 261 | // Recursive helper method to find import path in Variable Declaration 262 | private findVarDecImports(ast: { [key: string]: any }): string | boolean { 263 | // Base Case, find import path in variable declaration and return it, 264 | if (ast.hasOwnProperty('callee') && ast.callee.type === 'Import') { 265 | return ast.arguments[0].value; 266 | } 267 | 268 | // Otherwise look for imports in any other non null/undefined objects in the tree: 269 | for (let key in ast) { 270 | if (ast.hasOwnProperty(key) && typeof ast[key] === 'object' && ast[key]) { 271 | const importPath = this.findVarDecImports(ast[key]); 272 | if (importPath) { 273 | return importPath; 274 | } 275 | } 276 | } 277 | 278 | return false; 279 | } 280 | 281 | // Finds JSX React Components in current file 282 | private getJSXChildren( 283 | astTokens: any[], 284 | importsObj: ImportObj, 285 | parentNode: Tree 286 | ): Tree[] { 287 | let childNodes: { [key: string]: Tree } = {}; 288 | let props: { [key: string]: boolean } = {}; 289 | let token: { [key: string]: any }; 290 | 291 | for (let i = 0; i < astTokens.length; i++) { 292 | // Case for finding JSX tags eg 293 | if ( 294 | astTokens[i].type.label === 'jsxTagStart' && 295 | astTokens[i + 1].type.label === 'jsxName' && 296 | importsObj[astTokens[i + 1].value] 297 | ) { 298 | token = astTokens[i + 1]; 299 | props = this.getJSXProps(astTokens, i + 2); 300 | childNodes = this.getChildNodes( 301 | importsObj, 302 | token, 303 | props, 304 | parentNode, 305 | childNodes 306 | ); 307 | 308 | // Case for finding components passed in as props e.g. 309 | } else if ( 310 | astTokens[i].type.label === 'jsxName' && 311 | (astTokens[i].value === 'component' || 312 | astTokens[i].value === 'children') && 313 | importsObj[astTokens[i + 3].value] 314 | ) { 315 | token = astTokens[i + 3]; 316 | childNodes = this.getChildNodes( 317 | importsObj, 318 | token, 319 | props, 320 | parentNode, 321 | childNodes 322 | ); 323 | } 324 | } 325 | 326 | return Object.values(childNodes); 327 | } 328 | 329 | private getChildNodes( 330 | imports: ImportObj, 331 | astToken: { [key: string]: any }, 332 | props: { [key: string]: boolean }, 333 | parent: Tree, 334 | children: { [key: string]: Tree } 335 | ): { [key: string]: Tree } { 336 | if (children[astToken.value]) { 337 | children[astToken.value].count += 1; 338 | children[astToken.value].props = { 339 | ...children[astToken.value].props, 340 | ...props, 341 | }; 342 | } else { 343 | // Add tree node to childNodes if one does not exist 344 | children[astToken.value] = { 345 | id: getNonce(), 346 | name: imports[astToken.value]['importName'], 347 | fileName: path.basename(imports[astToken.value]['importPath']), 348 | filePath: path.resolve( 349 | path.dirname(parent.filePath), 350 | imports[astToken.value]['importPath'] 351 | ), 352 | importPath: imports[astToken.value]['importPath'], 353 | expanded: false, 354 | depth: parent.depth + 1, 355 | thirdParty: false, 356 | reactRouter: false, 357 | reduxConnect: false, 358 | count: 1, 359 | props: props, 360 | children: [], 361 | parentList: [parent.filePath].concat(parent.parentList), 362 | error: '', 363 | }; 364 | } 365 | 366 | return children; 367 | } 368 | 369 | // Extracts prop names from a JSX element 370 | private getJSXProps( 371 | astTokens: { [key: string]: any }[], 372 | j: number 373 | ): { [key: string]: boolean } { 374 | const props: any = {}; 375 | while (astTokens[j].type.label !== 'jsxTagEnd') { 376 | if ( 377 | astTokens[j].type.label === 'jsxName' && 378 | astTokens[j + 1].value === '=' 379 | ) { 380 | props[astTokens[j].value] = true; 381 | } 382 | j += 1; 383 | } 384 | return props; 385 | } 386 | 387 | // Checks if current Node is connected to React-Redux Store 388 | private checkForRedux(astTokens: any[], importsObj: ImportObj): boolean { 389 | // Check that react-redux is imported in this file (and we have a connect method or otherwise) 390 | let reduxImported = false; 391 | let connectAlias; 392 | Object.keys(importsObj).forEach((key) => { 393 | if ( 394 | importsObj[key].importPath === 'react-redux' && 395 | importsObj[key].importName === 'connect' 396 | ) { 397 | reduxImported = true; 398 | connectAlias = key; 399 | } 400 | }); 401 | 402 | if (!reduxImported) { 403 | return false; 404 | } 405 | 406 | // Check that connect method is invoked and exported in the file 407 | for (let i = 0; i < astTokens.length; i += 1) { 408 | if ( 409 | astTokens[i].type.label === 'export' && 410 | astTokens[i + 1].type.label === 'default' && 411 | astTokens[i + 2].value === connectAlias 412 | ) { 413 | return true; 414 | } 415 | } 416 | return false; 417 | } 418 | } -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to the extension test runner script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error(err); 19 | console.error('Failed to run tests'); 20 | process.exit(1); 21 | } 22 | } 23 | 24 | main(); -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, suite , test, before} from 'mocha'; 2 | import { expect } from 'chai'; 3 | 4 | // You can import and use all API from the 'vscode' module 5 | // as well as import your extension to test it 6 | import * as vscode from 'vscode'; 7 | // import * as myExtension from '../../extension'; 8 | 9 | suite('Extension Test Suite', () => { 10 | vscode.window.showInformationMessage('Start all tests.'); 11 | 12 | describe('reacTree loads correctly', () => { 13 | let reacTree; 14 | before (() => { 15 | reacTree = vscode.extensions.getExtension('ReacTreeDev.reactree'); 16 | }); 17 | 18 | test('reacTree is registered as an extension', () => { 19 | expect(reacTree).to.not.be.undefined; 20 | }); 21 | 22 | test('reacTree extension is activated after VSCode startup', () => { 23 | expect(reacTree.isActive).to.be.true; 24 | }); 25 | 26 | test('reacTree extension package.json exists', () => { 27 | expect(reacTree.packageJSON).to.not.be.undefined; 28 | }); 29 | }); 30 | 31 | // describe('It registers saplings commands successfully', () => { 32 | // let commandList; 33 | // before( (done) => { 34 | // vscode.commands.getCommands().then(commands => { 35 | // commandList = commands; 36 | // done(); 37 | // }); 38 | // }); 39 | 40 | // test('It registers the sapling.generateTree command', () => { 41 | // expect(commandList).to.be.an('array').that.does.include('sapling.generateTree'); 42 | // }); 43 | // }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | e(err); 34 | } 35 | }); 36 | }); 37 | } -------------------------------------------------------------------------------- /src/test/suite/parser.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { Parser } from '../../parser'; 3 | import { describe, suite , test, before} from 'mocha'; 4 | import { expect } from 'chai'; 5 | import * as path from 'path'; 6 | 7 | // You can import and use all API from the 'vscode' module 8 | // as well as import your extension to test it 9 | // import * as vscode from 'vscode'; 10 | // import * as myExtension from '../../extension'; 11 | 12 | suite('Parser Test Suite', () => { 13 | let parser, tree, file; 14 | 15 | // UNPARSED TREE TEST 16 | describe('It initializes correctly', () => { 17 | before( () => { 18 | file = path.join(__dirname, '../../../src/test/test_apps/test_0/index.js'); 19 | parser = new Parser(file); 20 | }); 21 | 22 | test('A new instance of the parser class is an object', () => { 23 | expect(parser).to.be.an('object'); 24 | }); 25 | 26 | test('It initializes with a proper entry file and an undefined tree', () => { 27 | expect(parser.entryFile).to.equal(file); 28 | expect(parser.tree).to.be.undefined; 29 | }); 30 | }); 31 | 32 | // TEST 0: ONE CHILD 33 | describe('It works for simple apps', () => { 34 | before( () => { 35 | file = path.join(__dirname, '../../../src/test/test_apps/test_0/index.js'); 36 | parser = new Parser(file); 37 | tree = parser.parse(); 38 | }); 39 | 40 | test('Parsing returns a object tree that is not undefined', () => { 41 | expect(tree).to.be.an('object'); 42 | }); 43 | 44 | test('Parsed tree has a property called name with value index and one child with name App', () => { 45 | expect(tree).to.have.own.property('name').that.is.equal('index'); 46 | expect(tree).to.have.own.property('children').that.is.an('array'); 47 | expect(tree.children[0]).to.have.own.property('name').that.is.equal('App'); 48 | }); 49 | }); 50 | 51 | // TEST 1: NESTED CHILDREN 52 | describe('It works for 2 components', () => { 53 | before(() => { 54 | file = path.join(__dirname, '../../../src/test/test_apps/test_1/index.js'); 55 | parser = new Parser(file); 56 | tree = parser.parse(); 57 | }); 58 | 59 | test('Parsed tree has a property called name with value index and one child with name App, which has its own child Main', () => { 60 | expect(tree).to.have.own.property('name').to.equal('index'); 61 | expect(tree.children[0].name).to.equal('App'); 62 | expect(tree.children[0]).to.have.own.property('children').that.is.an('array'); 63 | expect(tree.children[0].children[0]).to.have.own.property('name').to.equal('Main'); 64 | }); 65 | 66 | test('Parsed tree children should equal the child components', () => { 67 | expect(tree.children).to.have.lengthOf(1); 68 | expect(tree.children[0].children).to.have.lengthOf(1); 69 | }); 70 | 71 | test('Parsed tree depth is accurate', () => { 72 | expect(tree).to.have.own.property('depth').that.is.equal(0); 73 | expect(tree.children[0]).to.have.own.property('depth').that.is.equal(1); 74 | expect(tree.children[0].children[0]).to.have.own.property('depth').that.is.equal(2); 75 | }); 76 | }); 77 | 78 | // TEST 2: THIRD PARTY, REACT ROUTER, DESTRUCTURED IMPORTS 79 | describe('It works for third party / React Router components and destructured imports', () => { 80 | before(() => { 81 | file = path.join(__dirname, '../../../src/test/test_apps/test_2/index.js'); 82 | parser = new Parser(file); 83 | tree = parser.parse(); 84 | }); 85 | 86 | test('Should parse destructured and third party imports', () => { 87 | expect(tree.children).to.have.lengthOf(3); 88 | expect(tree.children[0]).to.have.own.property('name').that.is.oneOf(['Switch', 'Route']); 89 | expect(tree.children[1]).to.have.own.property('name').that.is.oneOf(['Switch', 'Route']); 90 | expect(tree.children[2]).to.have.own.property('name').that.is.equal('Tippy'); 91 | 92 | }); 93 | 94 | test('reactRouter should be designated as third party and reactRouter', () => { 95 | expect(tree.children[0]).to.have.own.property('thirdParty').to.be.true; 96 | expect(tree.children[1]).to.have.own.property('thirdParty').to.be.true; 97 | 98 | expect(tree.children[0]).to.have.own.property('reactRouter').to.be.true; 99 | expect(tree.children[1]).to.have.own.property('reactRouter').to.be.true; 100 | }); 101 | 102 | test('Tippy should be designated as third party and not reactRouter', () => { 103 | expect(tree.children[2]).to.have.own.property('thirdParty').to.be.true; 104 | expect(tree.children[2]).to.have.own.property('reactRouter').to.be.false; 105 | }); 106 | }); 107 | 108 | // TEST 3: IDENTIFIES REDUX STORE CONNECTION 109 | describe('It identifies a Redux store connection and designates the component as such', () => { 110 | before(() => { 111 | file = path.join(__dirname, '../../../src/test/test_apps/test_3/index.js'); 112 | parser = new Parser(file); 113 | tree = parser.parse(); 114 | }); 115 | 116 | test('The reduxConnect properties of the connected component and the unconnected component should be true and false, respectively', () => { 117 | expect(tree.children[1].children[0].name).to.equal('ConnectedContainer'); 118 | expect(tree.children[1].children[0]).to.have.own.property('reduxConnect').that.is.true; 119 | 120 | expect(tree.children[1].children[1].name).to.equal('UnconnectedContainer'); 121 | expect(tree.children[1].children[1]).to.have.own.property('reduxConnect').that.is.false; 122 | }); 123 | }); 124 | 125 | // TEST 4: ALIASED IMPORTS 126 | describe('It works for aliases', () => { 127 | before(() => { 128 | file = path.join(__dirname, '../../../src/test/test_apps/test_4/index.js'); 129 | parser = new Parser(file); 130 | tree = parser.parse(); 131 | }); 132 | 133 | test('alias should still give us components', () => { 134 | expect(tree.children).to.have.lengthOf(2); 135 | expect(tree.children[0]).to.have.own.property('name').that.is.equal('Switch'); 136 | expect(tree.children[1]).to.have.own.property('name').that.is.equal('Route'); 137 | 138 | expect(tree.children[0]).to.have.own.property('name').that.is.not.equal('S'); 139 | expect(tree.children[1]).to.have.own.property('name').that.is.not.equal('R'); 140 | }); 141 | }); 142 | 143 | // TEST 5: MISSING EXTENSIONS AND UNUSED IMPORTS 144 | describe('It works for extension-less imports', () => { 145 | let names, paths, expectedNames, expectedPaths; 146 | before(() => { 147 | file = path.join(__dirname, '../../../src/test/test_apps/test_5/index.js'); 148 | parser = new Parser(file); 149 | tree = parser.parse(); 150 | 151 | names = tree.children.map(child => child.name); 152 | paths = tree.children.map(child => child.filePath); 153 | 154 | expectedNames = ['JS', 'JSX', 'TS', 'TSX']; 155 | expectedPaths = [ 156 | '../../../src/test/test_apps/test_5/components/JS.js', 157 | '../../../src/test/test_apps/test_5/components/JSX.jsx', 158 | '../../../src/test/test_apps/test_5/components/TS.ts', 159 | '../../../src/test/test_apps/test_5/components/TSX.tsx' 160 | ].map( el => path.resolve(__dirname, el)); 161 | }); 162 | 163 | test('Check children match expected children', () => { 164 | expect(tree.children).to.have.lengthOf(4); 165 | 166 | for (let i = 0; i < tree.children.length; i++) { 167 | expect(tree.children[i].name).to.equal(expectedNames[i]); 168 | expect(tree.children[i].filePath).to.equal(expectedPaths[i]); 169 | expect(tree.children[i].error).to.equal(''); 170 | } 171 | }); 172 | 173 | test('Imports that are not invoked should not be children', () => { 174 | expect(names).to.not.contain('Switch'); 175 | expect(names).to.not.contain('Route'); 176 | }); 177 | }); 178 | 179 | // TEST 6: BAD IMPORT OF APP2 FROM APP1 COMPONENT 180 | describe('It works for badly imported children nodes', () => { 181 | before(() => { 182 | file = path.join(__dirname, '../../../src/test/test_apps/test_6/index.js'); 183 | parser = new Parser(file); 184 | tree = parser.parse(); 185 | }); 186 | 187 | test('improperly imported child component should exist but show an error', () => { 188 | expect(tree.children[0].children[0]).to.have.own.property('name').that.equals('App2'); 189 | expect(tree.children[0].children[0]).to.have.own.property('error').that.does.not.equal(''); 190 | }); 191 | }); 192 | 193 | // TEST 7: SYNTAX ERROR IN APP FILE CAUSES PARSER ERROR 194 | describe('It should log an error when the parser encounters a javascript syntax error', () => { 195 | before(() => { 196 | file = path.join(__dirname, '../../../src/test/test_apps/test_7/index.js'); 197 | parser = new Parser(file); 198 | tree = parser.parse(); 199 | }); 200 | 201 | test('Should have a nonempty error message on the invalid child and not parse further', () => { 202 | expect(tree.children[0]).to.have.own.property('name').that.equals('App'); 203 | expect(tree.children[0]).to.have.own.property('error').that.does.not.equal(''); 204 | expect(tree.children[0].children).to.have.lengthOf(0); 205 | }); 206 | }); 207 | 208 | // TEST 8: MULTIPLE PROPS ON ONE COMPONENT 209 | describe('It should properly count repeat components and consolidate and grab their props', () => { 210 | before(() => { 211 | file = path.join(__dirname, '../../../src/test/test_apps/test_8/index.js'); 212 | parser = new Parser(file); 213 | tree = parser.parse(); 214 | }); 215 | 216 | test('Grandchild should have a count of 1', () => { 217 | expect(tree.children[0].children[0]).to.have.own.property('count').that.equals(1); 218 | }); 219 | 220 | test('Grandchild should have the correct three props', () => { 221 | expect(tree.children[0].children[0].props).has.own.property('prop1').that.is.true; 222 | expect(tree.children[0].children[0].props).has.own.property('prop2').that.is.true; 223 | expect(tree.children[0].children[0].props).has.own.property('prop3').that.is.true; 224 | }); 225 | }); 226 | 227 | // TEST 9: FINDING DIFFERENT PROPS ACROSS TWO OR MORE IDENTICAL COMPONENTS 228 | describe('It should properly count repeat components and consolidate and grab their props', () => { 229 | before(() => { 230 | file = path.join(__dirname, '../../../src/test/test_apps/test_9/index.js'); 231 | parser = new Parser(file); 232 | tree = parser.parse(); 233 | }); 234 | 235 | test('Grandchild should have a count of 2', () => { 236 | expect(tree.children[0].children[0]).to.have.own.property('count').that.equals(2); 237 | }); 238 | 239 | test('Grandchild should have the correct two props', () => { 240 | expect(tree.children[0].children[0].props).has.own.property('prop1').that.is.true; 241 | expect(tree.children[0].children[0].props).has.own.property('prop2').that.is.true; 242 | }); 243 | }); 244 | 245 | // TEST 10: CHECK CHILDREN WORKS AND COMPONENTS WORK 246 | describe('It should render children when children are rendered as values of prop called component', () => { 247 | before(() => { 248 | file = path.join(__dirname, '../../../src/test/test_apps/test_10/index.jsx'); 249 | parser = new Parser(file); 250 | tree = parser.parse(); 251 | }); 252 | 253 | test('Parent should have children that match the value stored in component prop', () => { 254 | expect(tree.children[0]).to.have.own.property('name').that.is.equal('BrowserRouter'); 255 | expect(tree.children[1]).to.have.own.property('name').that.is.equal('App'); 256 | 257 | expect(tree.children[1].children[3]).to.have.own.property('name').that.is.equal('DrillCreator'); 258 | expect(tree.children[1].children[4]).to.have.own.property('name').that.is.equal('HistoryDisplay'); 259 | }); 260 | }); 261 | 262 | // TEST 11: PARSER DOESN'T BREAK UPON RECURSIVE COMPONENTS 263 | describe('It should render the second call of mutually recursive components, but no further', () => { 264 | before(() => { 265 | file = path.join(__dirname, '../../../src/test/test_apps/test_11/index.js'); 266 | parser = new Parser(file); 267 | tree = parser.parse(); 268 | }); 269 | 270 | test('Tree should not be undefined', () => { 271 | expect(tree).to.not.be.undefined; 272 | }); 273 | 274 | test('Tree should have an index component while child App1, grandchild App2, great-grandchild App1', () => { 275 | expect(tree).to.have.own.property('name').that.is.equal('index'); 276 | expect(tree.children).to.have.lengthOf(1); 277 | expect(tree.children[0]).to.have.own.property('name').that.is.equal('App1'); 278 | expect(tree.children[0].children).to.have.lengthOf(1); 279 | expect(tree.children[0].children[0]).to.have.own.property('name').that.is.equal('App2'); 280 | expect(tree.children[0].children[0].children).to.have.lengthOf(1); 281 | expect(tree.children[0].children[0].children[0]).to.have.own.property('name').that.is.equal('App1'); 282 | expect(tree.children[0].children[0].children[0].children).to.have.lengthOf(0); 283 | }); 284 | }); 285 | 286 | // TEST 12: NEXT.JS APPS 287 | describe('It should parse Next.js applications', () => { 288 | before(() => { 289 | file = path.join(__dirname, '../../../src/test/test_apps/test_12/pages/index.js'); 290 | parser = new Parser(file); 291 | tree = parser.parse(); 292 | }); 293 | 294 | test('Root should be named index, children should be named Head, Navbar, and Image, children of Navbar should be named Link and Image', () => { 295 | expect(tree).to.have.own.property('name').that.is.equal('index'); 296 | expect(tree.children).to.have.lengthOf(3); 297 | expect(tree.children[0]).to.have.own.property('name').that.is.equal('Head'); 298 | expect(tree.children[1]).to.have.own.property('name').that.is.equal('Navbar'); 299 | expect(tree.children[2]).to.have.own.property('name').that.is.equal('Image'); 300 | 301 | expect(tree.children[1].children).to.have.lengthOf(2); 302 | expect(tree.children[1].children[0]).to.have.own.property('name').that.is.equal('Link'); 303 | expect(tree.children[1].children[1]).to.have.own.property('name').that.is.equal('Image'); 304 | }); 305 | }); 306 | 307 | // TEST 13: Variable Declaration Imports and React.lazy Imports 308 | describe('It should parse VariableDeclaration imports including React.lazy imports', () => { 309 | before(() => { 310 | file = path.join(__dirname, '../../../src/test/test_apps/test_13/index.js'); 311 | parser = new Parser(file); 312 | tree = parser.parse(); 313 | }); 314 | 315 | test('Root should be named index, it should have one child named App', () => { 316 | expect(tree).to.have.own.property('name').that.is.equal('index'); 317 | expect(tree.children).to.have.lengthOf(1); 318 | expect(tree.children[0]).to.have.own.property('name').that.is.equal('App'); 319 | }); 320 | 321 | test('App should have three children, Page1, Page2 and Page3, all found successfully', () => { 322 | expect(tree.children[0].children[0]).to.have.own.property('name').that.is.equal('Page1'); 323 | expect(tree.children[0].children[0]).to.have.own.property('thirdParty').that.is.false; 324 | expect(tree.children[0].children[1]).to.have.own.property('name').that.is.equal('Page2'); 325 | expect(tree.children[0].children[1]).to.have.own.property('thirdParty').that.is.false; 326 | expect(tree.children[0].children[2]).to.have.own.property('name').that.is.equal('Page3'); 327 | expect(tree.children[0].children[2]).to.have.own.property('thirdParty').that.is.false; 328 | }); 329 | }); 330 | }); -------------------------------------------------------------------------------- /src/test/test_apps/test_0/components/App.jsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | 3 | class App extends Component { 4 | render () { 5 | return ( 6 |
I am App.
7 | ) 8 | } 9 | } 10 | 11 | export default App; 12 | -------------------------------------------------------------------------------- /src/test/test_apps/test_0/index.js: -------------------------------------------------------------------------------- 1 | import { prependOnceListener } from 'process'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | 5 | // Import React Components 6 | import App from './components/App.jsx'; 7 | 8 | // TEST 0 - Simple React App with one App Component 9 | 10 | render( 11 |
12 | 13 |
, document.getElementById('root') 14 | ); -------------------------------------------------------------------------------- /src/test/test_apps/test_1/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import Main from './Main.jsx' 4 | 5 | class App extends Component { 6 | render () { 7 | return ( 8 |
9 |
I am App.
10 |
11 |
12 | ) 13 | } 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /src/test/test_apps/test_1/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Main extends Component { 4 | render () { 5 | return ( 6 |
I am App.
7 | ) 8 | } 9 | } 10 | 11 | export default Main; 12 | -------------------------------------------------------------------------------- /src/test/test_apps/test_1/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | 4 | // Import React Components 5 | import App from './components/App.jsx'; 6 | 7 | // TEST 1 - Simple App with two components, App and Main, App renders Main 8 | 9 | render( 10 |
11 | 12 |
, document.getElementById('root') 13 | ); 14 | -------------------------------------------------------------------------------- /src/test/test_apps/test_10/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Switch, Route, Redirect } from 'react-router-dom'; 3 | 4 | // Import React Components 5 | import Nav from './Nav.jsx'; 6 | import ExercisesDisplay from './ExercisesDisplay.jsx'; 7 | import ExerciseCreator from './ExerciseCreator.jsx'; 8 | import DrillCreator from './DrillCreator.jsx'; 9 | import HistoryDisplay from './HistoryDisplay.jsx'; 10 | import Signup from './Signup.jsx'; 11 | import Login from './Login.jsx'; 12 | import Logout from './Logout.jsx'; 13 | 14 | // App Component 15 | const App = () => { 16 | const [userInfo, setUserInfo] = useState({ name: '', email: '' }); 17 | 18 | return ( 19 |
20 |
37 | ); 38 | }; 39 | 40 | export default App; -------------------------------------------------------------------------------- /src/test/test_apps/test_10/components/DrillCreator.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useParams, Link, Redirect } from 'react-router-dom'; 3 | 4 | const DrillCreator = () => { 5 | const { id } = useParams(); 6 | const [drillData, setDrillData] = useState({}); 7 | const [redirect, setRedirect] = useState(false); 8 | const [formVals, setFormVals] = useState({ 9 | exercise_id: id, 10 | weight: '', 11 | sets: '', 12 | reps: '', 13 | rest_interval: '', 14 | }); 15 | 16 | // Helper function to update state formVals on form change 17 | const updateFormVal = (key, val) => { 18 | setFormVals({ ...formVals, [key]: val }); 19 | }; 20 | 21 | // TODO MAKE REAL API CALL OR LIFT STATE TO APP 22 | // Is there a route for creating a drill? I only see createExercise 23 | const getExercise = () => { 24 | fetch(`/api/exercise/${id}`) 25 | .then((response) => { 26 | if (response.status === 200) { 27 | return response.json(); 28 | } 29 | throw new Error('Error when trying to get exercise details'); 30 | }) 31 | .then((data) => { 32 | console.log('exercise drill data is', data); 33 | setDrillData(data); 34 | }) 35 | .catch((error) => console.error(error)); 36 | }; 37 | 38 | // Get exercise data for drill info (CURRENTLY FAKE DATA) 39 | useEffect(() => { 40 | console.log('Getting data from server for drill'); 41 | getExercise(); 42 | }, []); 43 | 44 | // Function to submit drill form data to server, create new drill 45 | const createDrill = () => { 46 | console.log('trying to create new drill', formVals); 47 | 48 | fetch('/api/drill', { 49 | method: 'POST', 50 | headers: { 51 | 'Content-Type': 'application/json', 52 | }, 53 | body: JSON.stringify(formVals), 54 | }) 55 | .then((response) => { 56 | console.log('drill create response', response.status); 57 | if (response.status === 201) { 58 | return response.json(); 59 | } 60 | throw new Error('error when trying to create a drill'); 61 | }) 62 | .then((data) => { 63 | console.log('response is 201, data is', data); 64 | setRedirect(true); 65 | }) 66 | .catch((error) => console.error(error)); 67 | }; 68 | 69 | const { weight, sets, reps, rest_interval } = formVals; 70 | 71 | // Redirect to home page if drill created successfully 72 | if (redirect === true) { 73 | return ; 74 | } 75 | 76 | return ( 77 |
78 |

Create a new drill:

79 |

80 | Exercise Name: {drillData.name}

81 |

82 | Exercise Description: {drillData.description}

83 |

84 | Exercise Type: {drillData.type}

85 |

86 | Last Weight (LBs): {drillData.last_weight}

87 |

88 | Last Reps: {drillData.last_reps}

89 |

90 | Last Sets: {drillData.last_sets}

91 |

92 | Last Rest (Mins): {drillData.last_rest} 93 |

94 | 95 | {/* DRILL INPUT FORM */} 96 |
{ 98 | e.preventDefault(); 99 | createDrill(); 100 | }} 101 | > 102 | 103 | {/* DRILL WEIGHT INPUT */} 104 | 121 |
122 | 123 | {/* DRILL SETS INPUT */} 124 | 141 |
142 | 143 | {/* DRILL REPS INPUT */} 144 | 161 |
162 | 163 | {/*DRILL REST INPUT */} 164 | 181 |
182 | 183 | {/* FORM SUBMIT BUTTON */} 184 | 189 | 190 | {/* FORM CANCEL BUTTON */} 191 | 192 | 197 | 198 | 199 |
200 |
201 | ); 202 | }; 203 | 204 | export default DrillCreator; 205 | -------------------------------------------------------------------------------- /src/test/test_apps/test_10/components/ExerciseCreator.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | 4 | // React element allowing users to create a new exercise via form 5 | const ExerciseCreator = () => { 6 | const [redirect, setRedirect] = useState(false); 7 | const [formVals, setFormVals] = useState({ 8 | name: '', 9 | description: '', 10 | type_id: '1', 11 | init_weight: '', 12 | init_reps: '', 13 | init_sets: '', 14 | init_rest: '', 15 | }); 16 | 17 | // Helper function to update state formVals on form change 18 | const updateFormVal = (key, val) => { 19 | setFormVals({ ...formVals, [key]: val }); 20 | }; 21 | 22 | // Function to submit new exercise form data to server for processing 23 | const createExercise = () => { 24 | console.log('Trying to create exercise: ', formVals); 25 | fetch('/api/exercise', { 26 | method: 'POST', 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | }, 30 | body: JSON.stringify(formVals), 31 | }) 32 | .then((response) => { 33 | // If creation successful, redirect to exercises 34 | console.log('CREATE RESPONSE: ', response.status); 35 | if (response.status === 200) { 36 | return response.json(); 37 | } 38 | throw new Error('Error when trying to login a user!'); 39 | }).then((data) => { 40 | console.log('Added new exercise: ', data); 41 | setRedirect(true); 42 | }) 43 | .catch((err) => console.error(err)); 44 | }; 45 | 46 | const { 47 | name, description, type, init_weight, init_reps, init_sets, init_rest, 48 | } = formVals; 49 | 50 | // If successfully created new exercise, redirect to '/' route: 51 | if (redirect) { 52 | return ; 53 | } 54 | 55 | return ( 56 |
57 |

Create a new Exercise:

58 | 59 | {/* NEW EXERCISE FORM */} 60 |
{ 62 | e.preventDefault(); 63 | createExercise(); 64 | }} 65 | > 66 | 67 | {/* EXERCISE NAME INPUT */} 68 | 69 | { 74 | console.log('Updated createEx formVals: ', e.target.value); 75 | updateFormVal('name', e.target.value); 76 | }} 77 | value={name} 78 | name="name" 79 | required 80 | /> 81 |
82 | 83 | {/* EXERCISE TYPE INPUT */} 84 | 85 | 110 |
111 | 112 | {/* EXERCISE DESCRIPTION INPUT */} 113 | 114 |