├── .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 |
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 |
25 |
26 |
27 |
28 |
29 |
30 | Table of Contents
31 |
32 | Overview
33 | Installation
34 | Getting Started
35 | Functionality
36 | Tech Stack
37 | Articles
38 | Contributing
39 | Meet our Team
40 | License
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 | , 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 |
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 | , 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 |
21 |
22 | {/* React Router Switches */}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {/* If not logged in force redirect to login page */}
34 | {!userInfo.name ? : null}
35 |
36 |
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 |
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 |
204 |
205 | );
206 | };
207 |
208 | export default ExerciseCreator;
209 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_10/components/ExercisesDisplay.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const ExercisesDisplay = () => {
5 | const [exerciseData, setExerciseData] = useState([]);
6 |
7 | useEffect(() => {
8 | console.log('Getting data from server');
9 | // getExercises();
10 | fetch('/api/')
11 | .then((res) => res.json())
12 | .then((exercises) => {
13 | console.log('exercises are', exercises);
14 | setExerciseData(exercises);
15 | })
16 | .catch((error) => {
17 | console.log('error on ExercisesDisplay', error);
18 | });
19 | }, []);
20 |
21 | return (
22 |
23 |
Pick an Exercise:
24 | {exerciseData.map((exercise, i) => {
25 | console.log('makes all data exercises');
26 | return (
27 |
28 |
{exercise.name}
29 | Type: {exercise.typesname}
30 | Description: {exercise.description}
31 |
32 | Start Drill
33 |
34 |
35 |
36 | );
37 | })}
38 |
39 | );
40 | };
41 |
42 | export default ExercisesDisplay;
43 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_10/components/HistoryDisplay.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const HistoryDisplay = () => {
4 | const [history, setHistory] = useState([]);
5 |
6 | const getHistory = () => {
7 | fetch('/api/history')
8 | .then((res) => {
9 | if (res.status === 200) {
10 | return res.json();
11 | }
12 | throw new Error ('Error getting history from server.');
13 | })
14 | .then((data) => {
15 | console.log('Our getHistory data is:', data);
16 | setHistory(data);
17 | })
18 | .catch((error) => console.error(error));
19 | };
20 |
21 | useEffect(() => {
22 | console.log('GETTING HISTROY FROM SERVER');
23 | getHistory();
24 | }, []);
25 |
26 | const drills = history.map((drill, i) => {
27 | return Date: {drill.date}, id: {drill.exercise_id}, Weight: {drill.weight}, Sets: {drill.sets}, Reps: {drill.reps}, Rest: {drill.rest_interval}
28 | });
29 |
30 | return(
31 |
32 |
Drill History:
33 |
36 |
37 | );
38 | };
39 |
40 | export default HistoryDisplay;
41 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_10/components/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Redirect, Link } from 'react-router-dom';
3 |
4 | // React element to render login form and submit login to server
5 | const Login = ({ setUserInfo }) => {
6 | const [formVals, setFormVals] = useState({ email: '', password: '' });
7 | const [loggedIn, setLoggedIn] = useState(false);
8 | const [errorMessage, setErrorMessage] = useState('');
9 |
10 | // Helper function to update state formVals on form change
11 | const updateFormVal = (key, val) => {
12 | setFormVals({ ...formVals, [key]: val });
13 | };
14 |
15 | // Function to submit signup form data to server, create new account
16 | const login = () => {
17 | console.log('logging in!', formVals);
18 |
19 | fetch('/api/login', {
20 | method: 'POST',
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | },
24 | body: JSON.stringify(formVals),
25 | })
26 | .then((response) => {
27 | // If login successful, set state for redirect
28 | console.log('LOGIN RESPONSE: ', response.status);
29 | if (response.status === 200 || response.status === 400) {
30 | return response.json();
31 | }
32 | throw new Error('Error when trying to login a user!');
33 | }).then((data) => {
34 | // If Error on Login display error message
35 | if (data.message) {
36 | setErrorMessage(data.message);
37 | return;
38 | }
39 | // Successful login, redirect to main page:
40 | setUserInfo(data);
41 | setLoggedIn(true);
42 | })
43 | .catch((err) => console.error(err));
44 | };
45 |
46 | const { email, password } = formVals;
47 |
48 | // If signed up correctly, redirect to main page
49 | if (loggedIn) {
50 | return ;
51 | }
52 |
53 | // If not logged in render login form
54 | if (!loggedIn) {
55 | return (
56 |
111 | );
112 | }
113 | };
114 |
115 | export default Login;
116 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_10/components/Logout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect } from 'react-router-dom';
3 |
4 | // React component that fakes logging out and returns user to login page
5 | const Logout = ({ setUserInfo }) => {
6 | setUserInfo({ name: '', email: '' });
7 |
8 | return ;
9 | };
10 |
11 | export default Logout;
12 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_10/components/Nav.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const Nav = ({ userInfo }) => {
5 | console.log('this is the navbar speaking', userInfo);
6 |
7 | // Navbar when not signed in:
8 | if (!userInfo.name) {
9 | return (
10 |
11 | Login
12 | Signup
13 |
14 | );
15 | }
16 |
17 | // Signed in Navbar:
18 | return (
19 |
20 | Home
21 | Create Exercise
22 | History
23 | Logout
24 |
25 | {userInfo.name
26 | ? (
27 |
28 | Logged in as:
29 | {userInfo.name}
30 | -
31 | {userInfo.email}
32 |
33 | )
34 | : null}
35 |
36 |
37 | );
38 | };
39 |
40 | export default Nav;
41 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_10/components/Signup.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Redirect, Link } from 'react-router-dom';
3 |
4 | // React component to render signup form and send form data to server
5 | const Signup = ({ setUserInfo }) => {
6 | const [formVals, setFormVals] = useState({ email: '', name: '', password: '' });
7 | const [loggedIn, setLoggedIn] = useState(false);
8 | const [errorMessage, setErrorMessage] = useState('');
9 |
10 | // Helper function to update state formVals on form change
11 | const updateFormVal = (key, val) => {
12 | setFormVals({ ...formVals, [key]: val });
13 | };
14 |
15 | // Function to submit signup form data to server, create new account
16 | const signup = () => {
17 | console.log('signing up!', formVals);
18 | fetch('/api/signup', {
19 | method: 'POST',
20 | headers: {
21 | 'Content-Type': 'application/json',
22 | },
23 | body: JSON.stringify(formVals),
24 | })
25 | .then((response) => {
26 | // If signup successful, login
27 | console.log('SIGNUP RESPONSE: ', response.status);
28 | if (response.status === 201 || 400) {
29 | return response.json();
30 | }
31 | throw new Error('Error when trying to create new user!');
32 | })
33 | .then((data) => {
34 | if (data.message) {
35 | setErrorMessage(data.message);
36 | return;
37 | }
38 | setUserInfo(data);
39 | setLoggedIn(true);
40 | })
41 | .catch((err) => console.error(err));
42 | };
43 |
44 | const { email, name, password } = formVals;
45 |
46 | // If signed up correctly, redirect to main page
47 | if (loggedIn) {
48 | return ;
49 | }
50 |
51 | // If not logged in render signup form
52 | if (!loggedIn) {
53 | return (
54 |
125 | );
126 | }
127 | };
128 |
129 | export default Signup;
130 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_10/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { BrowserRouter as Router } from 'react-router-dom';
4 |
5 | // Import styles from SASS
6 | import styles from './scss/application.scss';
7 |
8 | // Import React Components
9 | import App from './components/App.jsx';
10 |
11 | // TEST 3 - Multi component application including react-router components
12 |
13 | render(
14 | ,
15 | document.getElementById('root'),
16 | );
17 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_11/components/App1.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import App2 from './App2.jsx'
3 |
4 | class App1 extends Component {
5 | render () {
6 | return (
7 |
11 | )
12 | }
13 | }
14 |
15 | export default App1;
16 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_11/components/App2.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import App1 from './App1.jsx'
3 |
4 | class App2 extends Component {
5 | render () {
6 | return (
7 |
11 | )
12 | }
13 | }
14 |
15 | export default App2;
16 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_11/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 |
4 | import App1 from './components/App1.jsx'
5 |
6 | // TEST 11 - Recursive import of App1 and App2
7 |
8 | render(
9 | , document.getElementById('root'));
12 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_12/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Link from 'next/link';
3 | import logo from '../public/sapling-logo.png';
4 |
5 | const Navbar = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | };
16 |
17 | export default Navbar;
--------------------------------------------------------------------------------
/src/test/test_apps/test_12/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import Image from 'next/image'
3 | import Navbar from '../components/Navbar';
4 |
5 | export default function Home() {
6 | return (
7 |
8 |
9 |
Sapling
10 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
25 | )
26 | }
--------------------------------------------------------------------------------
/src/test/test_apps/test_13/components/App.jsx:
--------------------------------------------------------------------------------
1 | const Page1 = lazy(() => import('./Page1'));
2 | import Page2 from './Page2';
3 | const Page3 = import('./Page3');
4 |
5 | export default function Routes() {
6 | return (
7 |
12 | );
13 | }
--------------------------------------------------------------------------------
/src/test/test_apps/test_13/components/Page1.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class Page1 extends Component {
4 | render () {
5 | return (
6 |
9 | )
10 | }
11 | }
12 |
13 | export default Page1;
14 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_13/components/Page2.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class Page2 extends Component {
4 | render () {
5 | return (
6 |
9 | )
10 | }
11 | }
12 |
13 | export default Page2;
14 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_13/components/Page3.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class Page3 extends Component {
4 | render () {
5 | return (
6 |
9 | )
10 | }
11 | }
12 |
13 | export default Page3;
14 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_13/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 |
4 | import App from './components/App.jsx'
5 |
6 | // TEST 11 - Recursive import of App1 and App2
7 |
8 | render(
9 | , document.getElementById('root'));
12 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_2/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Switch, Route } from 'react-router-dom';
4 | import Tippy from 'tippy';
5 |
6 | // TEST 2 - Third Party Components, Destructuring Import
7 |
8 | render(
9 |
10 |
11 |
12 |
13 |
14 |
15 |
, document.getElementById('root'));
16 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ConnectedContainer from './containers/ConnectedContainer'
3 | import UnconnectedContainer from './containers/UnconnectedContainer'
4 |
5 |
6 | class App extends Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | render() {
12 | return (
13 |
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/actions/actions.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/actionTypes';
2 |
3 | export const fakeAction1Creator = () => ({
4 | type: types.FAKE_ACTION_1,
5 | });
6 |
7 | export const fakeAction2Creator = () => ({
8 | type: types.FAKE_ACTION_2,
9 | });
10 |
11 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/constants/actionTypes.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | export const FAKE_ACTION_1 = 'FAKE_ACTION_1';
3 | export const FAKE_ACTION_2 = 'FAKE_ACTION_2';
4 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/containers/ConnectedContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import * as actions from '../actions/actions';
4 |
5 | const mapStateToProps = state => {
6 | return {
7 | count: state.fake.count,
8 | }
9 | }
10 |
11 | const mapDispatchToProps = dispatch => ({
12 | fakeAction1: () => dispatch(actions.FAKE_ACTION_1()),
13 | fakeAction2: () => dispatch(actions.FAKE_ACTION_2()),
14 | })
15 |
16 | class ConnectedContainer extends Component {
17 | constructor(props) {
18 | super(props)
19 | }
20 |
21 | render() {
22 | return (
23 | This is a container connected to the redux Store
24 | )
25 | }
26 | }
27 |
28 | export default connect(mapStateToProps, mapDispatchToProps)(ConnectedContainer);
29 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/containers/UnconnectedContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class UnconnectedContainer extends Component {
4 | constructor(props) {
5 | super(props)
6 | }
7 |
8 | render() {
9 | This is a container not connected to the Redux store.
10 | }
11 | }
12 |
13 | export default UnconnectedContainer;
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NOSEBLEEDS
6 |
7 |
8 |
9 |
10 |
11 |
12 | NOSEBLEEDS
13 |
17 |
18 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import App from './App.jsx';
4 |
5 | import { Provider } from 'react-redux';
6 | import store from './store';
7 |
8 | render (
9 |
10 |
11 | ,
12 | document.getElementById('root')
13 | );
14 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/reducers/fakeReducer.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/actionTypes';
2 |
3 | const initialState = {
4 | count: 0,
5 | };
6 |
7 | const fakeReducer = (state = initialState, action) => {
8 |
9 | switch (action.type) {
10 | case types.FAKE_ACTION_1: {
11 | return {
12 | count: count + 1,
13 | }
14 | }
15 |
16 | case types.FAKE_ACTION_2: {
17 | return {
18 | count: count + 2,
19 | }
20 | }
21 |
22 | default:
23 | return state;
24 | }
25 | };
26 |
27 | export default fakeReducer;
28 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import geekReducer from './geekReducer';
3 |
4 | export default combineReducers({
5 | fake: fakeReducer,
6 | });
7 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_3/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import { composeWithDevTools } from 'redux-devtools-extension';
3 | import thunk from 'redux-thunk'; //this was important to use in order to implement async requests to our database in actions.js
4 | import reducers from './reducers/index';
5 | //import { loadMarkets } from './actions/actions'; //this might be used in the future to load data for the user ref. unit 12 test
6 |
7 | const store = createStore(
8 | reducers,
9 | composeWithDevTools(applyMiddleware(thunk)),
10 | );
11 |
12 | export default store;
13 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_4/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Switch as S, Route as R } from 'react-router-dom';
4 |
5 | // TEST 4 - Third Party Components, Destructuring Import and Aliasing
6 |
7 | render(
8 |
9 |
10 |
11 |
12 |
, document.getElementById('root'));
13 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_5/components/Container.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Container = () => {
4 | return (
5 | This is a container!
6 | )
7 | }
8 |
9 | export default Container
--------------------------------------------------------------------------------
/src/test/test_apps/test_5/components/JS.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Container from './Container.js'
4 |
5 | class JS extends Component {
6 | render () {
7 | return (
8 |
12 | )
13 | }
14 | }
15 |
16 | export default JS;
17 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_5/components/JSX.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Container from './Container.js'
4 |
5 | class JSX extends Component {
6 | render () {
7 | return (
8 |
12 | )
13 | }
14 | }
15 |
16 | export default JSX;
17 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_5/components/TS.ts:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Container from './Container.js'
4 |
5 | class TS extends Component {
6 | render () {
7 | return (
8 |
12 | )
13 | }
14 | }
15 |
16 | export default TS;
--------------------------------------------------------------------------------
/src/test/test_apps/test_5/components/TSX.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Container from './Container.js'
4 |
5 | class TSX extends Component {
6 | render () {
7 | return (
8 |
12 | )
13 | }
14 | }
15 |
16 | export default TSX;
--------------------------------------------------------------------------------
/src/test/test_apps/test_5/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Switch as S, Route as R } from 'react-router-dom';
4 |
5 | import JS from './components/JS';
6 | import JSX from './components/JSX';
7 | import TS from './components/TS';
8 | import TSX from './components/TSX';
9 |
10 | // TEST 5 - No file extensions for js, jsx, ts, tsx files. Switch, Route are never used
11 |
12 | render(
13 |
14 |
15 |
16 |
17 |
18 |
, document.getElementById('root'));
19 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_6/App2.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import App1 from './App1.jsx'
3 |
4 | class App2 extends Component {
5 | render () {
6 | return (
7 |
11 | )
12 | }
13 | }
14 |
15 | export default App2;
16 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_6/components/App1.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import App2 from './App2.jsx'
3 |
4 | class App1 extends Component {
5 | render () {
6 | return (
7 |
11 | )
12 | }
13 | }
14 |
15 | export default App1;
16 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_6/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 |
4 | import App1 from './components/App1.jsx'
5 |
6 | // TEST 6 - Bad import of App2 from App1 Component
7 |
8 | render(
9 | , document.getElementById('root'));
12 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_7/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Main from './Main.jsx'
4 |
5 | class App extends Component {
6 | this is bad javascript
7 | render () {
8 | return (
9 |
13 | )
14 | }
15 | }
16 |
17 | export default App;
18 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_7/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_7/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 |
4 | import App from './components/App.jsx'
5 |
6 | // TEST 7 - Invalid Javascript could crash babel parser
7 |
8 | render(
9 | , document.getElementById('root'));
12 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_8/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Main from './Main';
3 |
4 |
5 |
6 | class App extends Component {
7 | render () {
8 | return (
9 |
10 |
11 |
12 | )
13 | }
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_8/components/Main.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | const Main = (props) => {
4 |
5 | return (
6 | {props.prop1}
7 | )
8 | }
9 |
10 |
11 | export default Main;
12 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_8/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 8 - Simple React App with two App Components and one prop
9 |
10 | render(
11 | , document.getElementById('root')
14 | );
--------------------------------------------------------------------------------
/src/test/test_apps/test_9/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Main from './Main';
3 |
4 |
5 |
6 | class App extends Component {
7 | render () {
8 | return (
9 |
10 |
11 |
12 |
13 | )
14 | }
15 | }
16 |
17 | export default App;
18 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_9/components/Main.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | const Main = (props) => {
4 |
5 | return (
6 | {props.prop1}
7 | )
8 | }
9 |
10 |
11 | export default Main;
12 |
--------------------------------------------------------------------------------
/src/test/test_apps/test_9/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 9 - Prop Detection, one App Component renders two Main Components, each with different props
9 |
10 | render(
11 | , document.getElementById('root')
14 | );
--------------------------------------------------------------------------------
/src/types/ImportObj.ts:
--------------------------------------------------------------------------------
1 | export type ImportObj = {
2 | [key: string]: { importPath: string; importName: string; };
3 | };
4 |
--------------------------------------------------------------------------------
/src/types/Tree.ts:
--------------------------------------------------------------------------------
1 | // React component tree is a nested data structure, children are Trees
2 |
3 | export type Tree = {
4 | id: string;
5 | name: string;
6 | fileName: string;
7 | filePath: string;
8 | importPath: string;
9 | expanded: boolean;
10 | depth: number;
11 | count: number;
12 | thirdParty: boolean;
13 | reactRouter: boolean;
14 | reduxConnect: boolean;
15 | children: Tree[];
16 | parentList: string[];
17 | props: { [key: string]: boolean; };
18 | error: string;
19 | };
20 |
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.jpg';
2 | declare module '*.jpeg';
3 |
--------------------------------------------------------------------------------
/src/webview/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/ReacTree/8cdc86004598e3538cb8189316025bd02b2fd6cb/src/webview/.DS_Store
--------------------------------------------------------------------------------
/src/webview/components/Flow.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import SwapVertRoundedIcon from '@mui/icons-material/SwapVertRounded';
3 | import SwapHorizRoundedIcon from '@mui/icons-material/SwapHorizRounded';
4 | import PIcon from '@mui/icons-material/LocalParking';
5 | import { useCallback, useEffect, useState } from 'react';
6 | import ReactFlow, {
7 | useNodesState,
8 | useEdgesState,
9 | addEdge,
10 | ConnectionLineType,
11 | Controls,
12 | } from 'reactflow';
13 | import * as dagre from 'dagre';
14 |
15 | import 'reactflow/dist/style.css';
16 | import '../dagre.css';
17 |
18 | const Flow = ({ initialNodes, initialEdges, handleAllProps}: any) => {
19 | const addNewTools = () => {
20 | const extraButton1 = document.createElement('button');
21 | const extraButton2 = document.createElement('button');
22 |
23 | extraButton1.setAttribute('type', 'button');
24 | extraButton2.setAttribute('type', 'button');
25 |
26 | extraButton1.setAttribute('class', 'react-flow__controls-button react-flow__controls-interactive');
27 | extraButton2.setAttribute('class', 'react-flow__controls-button react-flow__controls-interactive');
28 |
29 | const toolbar = document.getElementsByClassName('react-flow__panel react-flow__controls bottom left');
30 | toolbar[0].appendChild(extraButton1);
31 | toolbar[0].appendChild(extraButton2);
32 | };
33 | const [showAllProps, setShowAllProps]: [boolean, Function] = useState(false);
34 | const [vertical, setVertical] = useState(true);
35 |
36 | useEffect(() => {
37 | setTimeout(addNewTools, 5);
38 | }, []);
39 |
40 | const dagreGraph = new dagre.graphlib.Graph();
41 | dagreGraph.setDefaultEdgeLabel(() => ({}));
42 |
43 | const nodeWidth = 250;
44 | const nodeHeight = 120;
45 | const [disabled, setDisabled]: any = useState(false);
46 |
47 | const getLayoutedElements = (
48 | nodes: any[],
49 | edges: any[],
50 | direction = 'TB'
51 | ) => {
52 | const isHorizontal = direction === 'LR';
53 | dagreGraph.setGraph({ rankdir: direction });
54 |
55 | nodes.forEach((node) => {
56 | dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
57 | });
58 |
59 | edges.forEach((edge) => {
60 | dagreGraph.setEdge(edge.source, edge.target);
61 | });
62 |
63 | dagre.layout(dagreGraph);
64 |
65 | nodes.forEach((node) => {
66 | const nodeWithPosition = dagreGraph.node(node.id);
67 | node.targetPosition = isHorizontal ? 'left' : 'top';
68 | node.sourcePosition = isHorizontal ? 'right' : 'bottom';
69 |
70 | // We are shifting the dagre node position (anchor=center center) to the top left
71 | // so it matches the React Flow node anchor point (top left).
72 | node.position = {
73 | x: nodeWithPosition.x - nodeWidth / 2,
74 | y: nodeWithPosition.y - nodeHeight / 2,
75 | };
76 | return node;
77 | });
78 | return { nodes, edges };
79 | };
80 |
81 | const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
82 | initialNodes,
83 | initialEdges
84 | );
85 |
86 | useEffect(() => {
87 | if (initialNodes) {
88 | setNodes(initialNodes);
89 | }
90 | if (initialEdges) {
91 | setEdges(initialEdges);
92 | }
93 | }, [initialNodes]);
94 |
95 | const [nodes, setNodes, onNodesChange] = useNodesState(layoutedNodes);
96 | const [edges, setEdges, onEdgesChange] = useEdgesState(layoutedEdges);
97 |
98 | const onConnect = useCallback(
99 | (params) =>
100 | setEdges((eds) =>
101 | addEdge(
102 | { ...params, type: ConnectionLineType.SmoothStep, animated: true },
103 | eds
104 | )
105 | ),
106 | []
107 | );
108 |
109 | const onLayout = useCallback(
110 | (direction) => {
111 | setDisabled(!disabled);
112 | const { nodes: layoutedNodes, edges: layoutedEdges } =
113 | getLayoutedElements(nodes, edges, direction);
114 | setNodes([...layoutedNodes]);
115 | setEdges([...layoutedEdges]);
116 | },
117 | [nodes, edges]
118 | );
119 |
120 | return (
121 |
122 |
123 |
132 |
133 |
134 | {
135 | vertical ?
136 |
{
137 | onLayout('LR')
138 | setVertical(!vertical)
139 | }}>
140 |
141 |
142 | :
143 |
{
144 | onLayout('TB')
145 | setVertical(!vertical)
146 | }}>
147 |
148 |
149 | }
150 | {
151 | showAllProps ?
152 |
{
153 | handleAllProps('none');
154 | setShowAllProps(!showAllProps);
155 | }}>
156 |
157 |
158 | :
159 |
{
160 | handleAllProps('block');
161 | setShowAllProps(!showAllProps);
162 | }}>
163 |
164 |
165 | }
166 |
167 |
168 | );
169 | };
170 |
171 | export default Flow;
--------------------------------------------------------------------------------
/src/webview/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { useState, useEffect } from 'react';
3 | import * as ReactDOM from 'react-dom';
4 | import RefreshIcon from '@mui/icons-material/Refresh';
5 | import FileUploadOutlinedIcon from '@mui/icons-material/FileUploadOutlined';
6 | import FileUploadRoundedIcon from '@mui/icons-material/FileUploadRounded';
7 | import ClearIcon from '@mui/icons-material/Clear';
8 |
9 | // imports for the icons
10 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11 | import { faDownload } from '@fortawesome/free-solid-svg-icons';
12 |
13 | interface vscode {
14 | postMessage(message: any): void;
15 | }
16 | // declare function acquireVsCodeApi(): vscode;
17 | declare const vscode: vscode;
18 |
19 | const Navbar = ({ rootFile }: any) => {
20 | // onChange function that will send a message to the extension when the user selects a file
21 | const fileMessage = (e: any) => {
22 | const filePath = e.target.files[0].path;
23 | // Reset event target value to null so the same file selection causes onChange event to trigger
24 | e.target.value = null;
25 | if (filePath) {
26 | vscode.postMessage({
27 | type: "onFile",
28 | value: filePath
29 | });
30 | }
31 | };
32 |
33 | console.log('ROOT', rootFile)
34 |
35 | // Render section
36 | return (
37 |
38 |
{fileMessage(e);}}/>
39 |
40 |
41 | {/* */}
42 | {rootFile ? ` ${rootFile}` : ' Select File'}
43 |
44 |
45 | {/* */}
46 |
47 |
48 | );
49 | };
50 |
51 | export default Navbar;
--------------------------------------------------------------------------------
/src/webview/components/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useEffect, useState } from "react";
3 | import { Node, Edge } from "reactflow";
4 | import Badge from '@mui/material/Badge';
5 | import TextSnippetIcon from '@mui/icons-material/TextSnippet';
6 | import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
7 | import InfoIcon from '@mui/icons-material/Info';
8 |
9 | import Flow from './Flow';
10 | import Navbar from './Navbar';
11 |
12 | import CIcon from "@coreui/icons-react";
13 | import { cibRedux } from "@coreui/icons";
14 |
15 |
16 | interface vscode {
17 | postMessage(message: any): void;
18 | }
19 | // declare function acquireVsCodeApi(): vscode;
20 | declare const vscode: vscode;
21 |
22 | interface SidebarProps {
23 | initialNodes: any;
24 | initialEdges: Edge[];
25 | viewData: any;
26 | }
27 |
28 | const Sidebar = () => {
29 | // state variables for the incomimg treeData, parsed viewData, user's settings, and the root file name
30 | const [treeData, setTreeData]: any = useState();
31 | const [settings, setSettings]: [{ [key: string]: boolean }, Function] = useState();
32 | const [rootFile, setRootFile]: [string | undefined, Function] = useState();
33 |
34 | let viewData: any;
35 | useEffect(() => {
36 | // Event Listener for 'message' from the extension
37 | window.addEventListener('message', (event) => {
38 | const message = event.data;
39 | switch (message.type) {
40 | // Listener to receive the tree data, update navbar and tree view
41 | case 'parsed-data': {
42 | let data = [];
43 | data.push(message.value);
44 | setRootFile(message.value.fileName);
45 | setSettings(message.settings);
46 | setTreeData(data);
47 | break;
48 | }
49 | // Listener to receive the user's settings
50 | case 'settings-data': {
51 | setSettings(message.value);
52 | break;
53 | }
54 | }
55 | });
56 |
57 | // Post message to the extension whenever sapling is opened
58 | vscode.postMessage({
59 | type: 'onReacTreeVisible',
60 | value: null,
61 | });
62 |
63 | // Post message to the extension for the user's settings whenever sapling is opened
64 | vscode.postMessage({
65 | type: 'onSettingsAcquire',
66 | value: null,
67 | });
68 | }, []);
69 |
70 | const viewFile = (file: any) => {
71 | // Edge case to verify that there is in fact a file path for the current node
72 | if (file) {
73 | vscode.postMessage({
74 | type: 'onViewFile',
75 | value: file,
76 | });
77 | }
78 | };
79 |
80 | // Separate useEffect that gets triggered when the treeData and settings state variables get updated
81 | useEffect(() => {
82 | if (treeData && settings) {
83 | // Invoke parser to parse based on user's settings
84 | parseViewTree();
85 | }
86 | }, [treeData, settings]);
87 |
88 | // initialize iniialialNodes for ReactFlow setup
89 | const initialNodes: Node[] = [];
90 | let id = 0;
91 | const nodeIDs =[]
92 | const propsObj: any = {};
93 |
94 | const handleProps = (id) => {
95 | const propsDiv = document.getElementById(id);
96 | const styles = window.getComputedStyle(propsDiv);
97 | const display = styles.getPropertyValue('display');
98 | if (display === 'none') propsDiv.style.display = 'block';
99 | else propsDiv.style.display = 'none'
100 | }
101 | const handleAllProps = (displayValue) => {
102 | for (let i = 0; i < nodeIDs.length; i++) {
103 | const id = nodeIDs[i];
104 | const propsDiv = document.getElementById(id);
105 | propsDiv.style.display = displayValue
106 | }
107 | }
108 |
109 | const sendFilePath = (item: any) => {
110 | vscode.postMessage({
111 | type: 'onViewFileContent',
112 | value: item,
113 | });
114 | };
115 |
116 |
117 | const getNodes = (tree: any) => {
118 | if (!tree) {
119 | return;
120 | }
121 |
122 | tree.forEach((item: any) => {
123 | if (Object.keys(item.props).length > 0) nodeIDs.push(item.id);
124 | const node = {
125 | id: (++id).toString(),
126 | data: {
127 | // if the item has props, show them on each div
128 | label: (
129 | //
130 |
131 | {/* for rendering modal to show live render of component */}
132 | {item.count > 1 && (
133 |
139 |
140 | )}
141 |
158 | {item.fileName}
159 |
160 | {Object.keys(item.props).length > 0 &&
161 | (
162 | <>
163 |
174 | {Object.keys(item.props).map((prop: any, idx: number) => (
175 |
176 | •{prop}
177 |
178 | ))}
179 |
180 | >
181 | )}
182 |
183 |
184 | {Object.keys(item.props).length > 0 && (
185 | handleProps(item.id)}/>
186 | )}
187 | viewFile(item.filePath)}/>
188 |
189 |
190 | {item.reduxConnect && (
191 |
192 | )}
193 |
194 |
195 |
196 | ),
197 | },
198 | onClick : () => handleProps(item.fileName),
199 | position: { x: 0, y: 0 },
200 | type: item.depth === 0 ? 'input' : '',
201 | style: {
202 | backgroundColor: "var(--vscode-dropdown-background)",
203 | borderRadius: "15px",
204 | width: '265px',
205 | boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)',
206 | border: 'none',
207 | padding: '10px 10px 3px 10px'
208 | },
209 | };
210 | initialNodes.push(node);
211 | if (item.children) {
212 | getNodes(item.children);
213 | }
214 | });
215 | };
216 |
217 | //initialEdges test
218 | const initialEdges: Edge[] = [];
219 | let ide = 0;
220 |
221 | const makeEdges = (tree: any, parentId?: any) => {
222 | if (!tree) {
223 | return;
224 | }
225 | tree.forEach((item: any) => {
226 | const nodeId = ++ide;
227 | if (parentId) {
228 | const edge = {
229 | id: `e${parentId}-${nodeId}`,
230 | source: parentId.toString(),
231 | target: nodeId.toString(),
232 | type: 'smoothstep',
233 | animated: false,
234 | };
235 | initialEdges.push(edge);
236 | }
237 | if (item.children) {
238 | makeEdges(item.children, nodeId);
239 | }
240 | });
241 | };
242 |
243 | // Edits and returns component tree based on users settings
244 | const parseViewTree = (): void => {
245 | // Deep copy of the treeData passed in
246 | const treeParsed = JSON.parse(JSON.stringify(treeData[0]));
247 |
248 | // Helper function for the recursive parsing
249 | const traverse = (node: any): void => {
250 | let validChildren = [];
251 |
252 | // Logic to parse the nodes based on the users settings
253 | for (let i = 0; i < node.children.length; i++) {
254 | if (
255 | node.children[i].thirdParty &&
256 | settings.thirdParty &&
257 | !node.children[i].reactRouter
258 | ) {
259 | validChildren.push(node.children[i]);
260 | } else if (node.children[i].reactRouter && settings.reactRouter) {
261 | validChildren.push(node.children[i]);
262 | } else if (
263 | !node.children[i].thirdParty &&
264 | !node.children[i].reactRouter
265 | ) {
266 | validChildren.push(node.children[i]);
267 | }
268 | }
269 |
270 | // Update children with only valid nodes, and recurse through each node
271 | node.children = validChildren;
272 | node.children.forEach((child: any) => {
273 | traverse(child);
274 | });
275 | };
276 |
277 | // Invoking the helper function
278 | traverse(treeParsed);
279 | // Update the vewData state
280 | viewData = ([treeParsed])
281 | getNodes(viewData);
282 | makeEdges(viewData);
283 | };
284 | // Render section
285 | return (
286 |
287 |
288 |
289 |
290 | );
291 | };
292 |
293 | export default Sidebar;
294 |
--------------------------------------------------------------------------------
/src/webview/dagre.css:
--------------------------------------------------------------------------------
1 | .layoutflow {
2 | flex-grow: 1;
3 | position: relative;
4 | height: 100%;
5 | width: 100%
6 | }
7 |
8 | .layoutflow .controls {
9 | position: absolute;
10 | right: 10px;
11 | top: 10px;
12 | z-index: 10;
13 | font-size: 12px;
14 | }
15 |
16 | .layoutflow .controls button:first-child {
17 | margin-right: 10px;
18 | }
--------------------------------------------------------------------------------
/src/webview/index.tsx:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | const ReactDOM = require('react-dom');
3 |
4 | import '@fontsource/roboto/300.css';
5 | import '@fontsource/roboto/400.css';
6 | import '@fontsource/roboto/500.css';
7 | import '@fontsource/roboto/700.css';
8 | import './style.css';
9 | import './dagre.css';
10 |
11 | // Component import
12 | import Sidebar from './components/Sidebar';
13 |
14 | ReactDOM.render( , document.getElementById('root') as HTMLElement);
15 |
--------------------------------------------------------------------------------
/src/webview/style.css:
--------------------------------------------------------------------------------
1 | /* this gets exported as style.css and can be used for the default theming */
2 | /* these are the necessary styles for React Flow, they get used by base.css and style.css */
3 | body {
4 | padding: 0px;
5 | background-color: var(--vscode-editor-background);
6 | color: var(--vscode-editor-foreground);
7 | }
8 | .panel {
9 | height: 100vh;
10 | width: 100vw;
11 | }
12 | .MuiBadge-badge {
13 | top: -14px !important;
14 | right: -128px !important;
15 | }
16 | .css-cgmxn1 {
17 | display: inline !important;
18 | }
19 | .nodeToolbar {
20 | display: flex;
21 | justify-content: center;
22 | padding: 3px 4px;
23 | }
24 |
25 | .css-vubbuv {
26 | font-size: 1.3rem;
27 | }
28 | .react-flow__container {
29 | position: absolute;
30 | width: 100%;
31 | height: 100%;
32 | top: 0;
33 | left: 0;
34 | }
35 | .react-flow__handle.connectable {
36 | background-color: rgb(0 0 0 / 0%) !important;
37 | border: none !important;
38 | }
39 | .react-flow__pane {
40 | z-index: 1;
41 | cursor: auto !important;
42 | }
43 | /* .react-flow__pane.selection {
44 | cursor: pointer;
45 | }
46 | .react-flow__pane.dragging {
47 | cursor: -webkit-grabbing;
48 | cursor: grabbing;
49 | } */
50 | .react-flow__viewport {
51 | transform-origin: 0 0;
52 | z-index: 2;
53 | pointer-events: none;
54 | }
55 | .react-flow__renderer {
56 | z-index: 4;
57 | }
58 | .react-flow__selection {
59 | z-index: 6;
60 | }
61 | .react-flow__nodesselection-rect:focus,
62 | .react-flow__nodesselection-rect:focus-visible {
63 | outline: none;
64 | }
65 | .react-flow .react-flow__edges {
66 | pointer-events: none;
67 | overflow: visible;
68 | }
69 | .react-flow__edge-path,
70 | .react-flow__connection-path {
71 | stroke: #b1b1b7;
72 | stroke-width: 1;
73 | fill: none;
74 | }
75 | .react-flow__edge {
76 | pointer-events: visibleStroke;
77 | cursor: pointer;
78 | }
79 | .react-flow__edge.animated path {
80 | stroke-dasharray: 5;
81 | -webkit-animation: dashdraw 0.5s linear infinite;
82 | animation: dashdraw 0.5s linear infinite;
83 | }
84 | .react-flow__edge.animated path.react-flow__edge-interaction {
85 | stroke-dasharray: none;
86 | -webkit-animation: none;
87 | animation: none;
88 | }
89 | .react-flow__edge.inactive {
90 | pointer-events: none;
91 | }
92 | .react-flow__edge.selected,
93 | .react-flow__edge:focus,
94 | .react-flow__edge:focus-visible {
95 | outline: none;
96 | }
97 | .react-flow__edge.selected .react-flow__edge-path,
98 | .react-flow__edge:focus .react-flow__edge-path,
99 | .react-flow__edge:focus-visible .react-flow__edge-path {
100 | stroke: #555;
101 | }
102 | .react-flow__edge-textwrapper {
103 | pointer-events: all;
104 | }
105 | .react-flow__edge-textbg {
106 | fill: white;
107 | }
108 | .react-flow__edge .react-flow__edge-text {
109 | pointer-events: none;
110 | -webkit-user-select: none;
111 | -moz-user-select: none;
112 | user-select: none;
113 | }
114 | .react-flow__connection {
115 | pointer-events: none;
116 | }
117 | .react-flow__connection .animated {
118 | stroke-dasharray: 5;
119 | -webkit-animation: dashdraw 0.5s linear infinite;
120 | animation: dashdraw 0.5s linear infinite;
121 | }
122 | .react-flow__connectionline {
123 | z-index: 1001;
124 | }
125 | .react-flow__nodes {
126 | pointer-events: none;
127 | transform-origin: 0 0;
128 | }
129 | .react-flow__node {
130 | position: absolute;
131 | -webkit-user-select: none;
132 | -moz-user-select: none;
133 | user-select: none;
134 | pointer-events: all;
135 | transform-origin: 0 0;
136 | box-sizing: border-box;
137 | cursor: -webkit-grab;
138 | cursor: grab;
139 | }
140 | .react-flow__node.dragging {
141 | cursor: -webkit-grabbing;
142 | cursor: grabbing;
143 | }
144 | .react-flow__nodesselection {
145 | z-index: 3;
146 | transform-origin: left top;
147 | pointer-events: none;
148 | }
149 | .react-flow__nodesselection-rect {
150 | position: absolute;
151 | pointer-events: all;
152 | cursor: -webkit-grab;
153 | cursor: grab;
154 | }
155 | .react-flow__handle {
156 | position: absolute;
157 | pointer-events: none;
158 | min-width: 5px;
159 | min-height: 5px;
160 | width: 6px;
161 | height: 6px;
162 | background: #1a192b;
163 | border: 1px solid white;
164 | border-radius: 100%;
165 | }
166 | .react-flow__handle.connectable {
167 | pointer-events: all;
168 | cursor: crosshair;
169 | }
170 | .react-flow__handle-bottom {
171 | top: auto;
172 | left: 50%;
173 | bottom: -4px;
174 | transform: translate(-50%, 0);
175 | }
176 | .react-flow__handle-top {
177 | left: 50%;
178 | top: -4px;
179 | transform: translate(-50%, 0);
180 | }
181 | .react-flow__handle-left {
182 | top: 50%;
183 | left: -4px;
184 | transform: translate(0, -50%);
185 | }
186 | .react-flow__handle-right {
187 | right: -4px;
188 | top: 50%;
189 | transform: translate(0, -50%);
190 | }
191 | .react-flow__edgeupdater {
192 | cursor: move;
193 | pointer-events: all;
194 | }
195 | .react-flow__panel {
196 | position: absolute;
197 | z-index: 5;
198 | margin: 15px;
199 | }
200 | .react-flow__panel.top {
201 | top: 0;
202 | }
203 | .react-flow__panel.bottom {
204 | bottom: 0;
205 | }
206 | .react-flow__panel.left {
207 | left: 0;
208 | }
209 | .react-flow__panel.right {
210 | right: 0;
211 | }
212 | .react-flow__panel.center {
213 | left: 50%;
214 | transform: translateX(-50%);
215 | }
216 | .react-flow__attribution {
217 | font-size: 10px;
218 | background: rgba(255, 255, 255, 0.5);
219 | padding: 2px 3px;
220 | margin: 0;
221 | }
222 | .react-flow__attribution a {
223 | text-decoration: none;
224 | color: #999;
225 | }
226 | @-webkit-keyframes dashdraw {
227 | from {
228 | stroke-dashoffset: 10;
229 | }
230 | }
231 | @keyframes dashdraw {
232 | from {
233 | stroke-dashoffset: 10;
234 | }
235 | }
236 | .react-flow__edgelabel-renderer {
237 | position: absolute;
238 | width: 100%;
239 | height: 100%;
240 | pointer-events: none;
241 | }
242 | .react-flow__edge.updating .react-flow__edge-path {
243 | stroke: #777;
244 | }
245 | .react-flow__edge-text {
246 | font-size: 10px;
247 | }
248 | .react-flow__node.selectable:focus,
249 | .react-flow__node.selectable:focus-visible {
250 | outline: none;
251 | }
252 | .react-flow__node-default,
253 | .react-flow__node-input,
254 | .react-flow__node-output,
255 | .react-flow__node-group {
256 | padding: 10px;
257 | border-radius: 3px;
258 | width: 150px;
259 | font-size: 12px;
260 | color: #222;
261 | text-align: center;
262 | border-width: 1px;
263 | border-style: solid;
264 | border-color: #1a192b;
265 | background-color: white;
266 | }
267 | .react-flow__node-default.selectable:hover,
268 | .react-flow__node-input.selectable:hover,
269 | .react-flow__node-output.selectable:hover,
270 | .react-flow__node-group.selectable:hover {
271 | box-shadow: 0 1px 4px 1px rgba(0, 0, 0, 0.08);
272 | }
273 | .react-flow__node-default.selectable.selected,
274 | .react-flow__node-default.selectable:focus,
275 | .react-flow__node-default.selectable:focus-visible,
276 | .react-flow__node-input.selectable.selected,
277 | .react-flow__node-input.selectable:focus,
278 | .react-flow__node-input.selectable:focus-visible,
279 | .react-flow__node-output.selectable.selected,
280 | .react-flow__node-output.selectable:focus,
281 | .react-flow__node-output.selectable:focus-visible,
282 | .react-flow__node-group.selectable.selected,
283 | .react-flow__node-group.selectable:focus,
284 | .react-flow__node-group.selectable:focus-visible {
285 | box-shadow: 0 0 0 0.5px #1a192b;
286 | }
287 | .react-flow__node-group {
288 | background-color: rgba(240, 240, 240, 0.25);
289 | }
290 | .react-flow__nodesselection-rect,
291 | .react-flow__selection {
292 | background: rgba(0, 89, 220, 0.08);
293 | border: 1px dotted rgba(0, 89, 220, 0.8);
294 | }
295 | .react-flow__nodesselection-rect:focus,
296 | .react-flow__nodesselection-rect:focus-visible,
297 | .react-flow__selection:focus,
298 | .react-flow__selection:focus-visible {
299 | outline: none;
300 | }
301 | .react-flow__controls-button {
302 | border: none;
303 | background: #fefefe;
304 | border-bottom: 1px solid #eee;
305 | box-sizing: content-box;
306 | display: flex;
307 | justify-content: center;
308 | align-items: center;
309 | width: 16px;
310 | height: 16px;
311 | cursor: pointer;
312 | -webkit-user-select: none;
313 | -moz-user-select: none;
314 | user-select: none;
315 | padding: 5px;
316 | }
317 | .react-flow__controls-button:hover {
318 | background: #f4f4f4;
319 | }
320 | .react-flow__controls-button svg {
321 | width: 100%;
322 | max-width: 12px;
323 | max-height: 12px;
324 | }
325 | .react-flow__minimap {
326 | background-color: #fff;
327 | }
328 |
329 | .tree_view {
330 | height: 100vh;
331 | }
332 |
333 | .navbar {
334 | display: flex;
335 | position: absolute;
336 | top: 80px;
337 | /* padding: 10px; */
338 | border-radius: 0px 35px 35px 0px;
339 | z-index: 2;
340 | box-shadow: 0px 4px 4px rgb(0 0 0 / 25%);
341 | background-color: var(--vscode-input-background);
342 | border: solid var(--vscode-settings-focusedRowBorder);
343 | border-left: none;
344 | }
345 |
346 | .inputfile {
347 | width: 0.1px;
348 | height: 0.1px;
349 | opacity: 0;
350 | overflow: hidden;
351 | /* position: absolute; */
352 | z-index: -1;
353 | }
354 | .navbarContents{
355 | display: flex;
356 | padding: 2px 5px;
357 | justify-content: space-around;
358 | display: flex;
359 | align-items: center;
360 | justify-content: center;
361 | height: 3.5em;
362 | }
363 | #inputLabel {
364 | /* padding: 0.4em; */
365 | font-size: 1.5em;
366 | font-weight: 400;
367 | /* background: #003f8e; */
368 | display: flex;
369 | margin-left: 10px;
370 | margin-right: 5px;
371 | border-radius: 5px;
372 | transition: 0.2s;
373 | }
374 | #inputLabel strong{
375 | color: var(--vscode-foreground);
376 | }
377 |
378 | .nodeTitle:hover {
379 | white-space: normal !important; /* allow line breaks within the text */
380 | overflow: visible !important;
381 | text-overflow: initial !important; /* show the entire text */
382 | }
383 | #inputLabel:hover {
384 | background: opacity(0.8);
385 | }
386 |
387 | #inputLabel {
388 | cursor: pointer;
389 | }
390 |
391 | .nodeData {
392 | display: block;
393 | padding: 0;
394 | font-size: 1.5em;
395 |
396 | }
397 |
398 | .controls {
399 | display:flex;
400 | flex-direction: row;
401 | }
402 | .reduxIcon {
403 | display: inline-block;
404 | fill: var(--vscode-foreground);
405 | }
406 | .icon {
407 | position: relative;
408 | right: 7px;
409 | top: 3px;
410 | width: 15px;
411 | height: 15px;
412 | fill: var(--vscode-foreground);
413 | }
414 |
415 | div.react-flow__panel.react-flow__attribution {
416 | visibility: hidden;
417 | }
418 |
419 | div.react-flow__panel.react-flow__controls.bottom.left{
420 | left:auto;
421 | right: 0px;
422 | bottom: 25px;
423 | height: 27px;
424 | margin-right: 0px;
425 | display: flex;
426 | flex-direction: row;
427 | }
428 | .react-flow__controls-button.react-flow__controls-zoomin{
429 | border-radius: 30px 0px 0px 30px;
430 | }
431 | .react-flow__controls {
432 | border: solid var(--vscode-settings-focusedRowBorder) !important;
433 | border-right: none !important;
434 | box-shadow: 0px 4px 4px rgb(0 0 0 / 25%) !important;
435 | border-radius: 50px 0px 0px 50px !important;
436 | height: 3.5em !important;
437 | }
438 | .react-flow__controls-button{
439 | background-color: var(--vscode-input-background) !important;
440 | border: none !important;
441 | height: 33px !important;
442 | width: 2.5rem !important;
443 | fill: var(--vscode-foreground) !important;
444 | }
445 | .customToolbarButton {
446 | position: absolute;
447 | display: flex;
448 | bottom: 42px;
449 | right: 50px;
450 | }
451 | .customToolbarButton svg{
452 | max-width: 22px !important;
453 | max-height: 22px !important;
454 | }
455 | .customToolbarButton2 {
456 | right: 0px !important;
457 | }
458 | .customToolbarButton2 svg {
459 | max-width: 18px !important;
460 | max-height: 18px !important;
461 | }
462 |
463 | .react-flow__controls-button:hover {
464 | background-image: linear-gradient(rgb(0 0 0/40%) 0 0) !important;
465 | }
466 | .react-flow__controls-button:active {
467 | background: #e5e5e5;
468 | -webkit-box-shadow: inset 0px 0px 5px #c1c1c1;
469 | -moz-box-shadow: inset 0px 0px 5px #c1c1c1;
470 | box-shadow: inset 0px 0px 5px #c1c1c1;
471 | outline: none;
472 | }
473 | .show {
474 | width: 26px;
475 | height: 26px;
476 | right: 26px;
477 | bottom: 41px;
478 | background-color: var(--vscode-input-background) !important;
479 | }
480 |
481 | .hide {
482 | visibility: hidden;
483 | }
484 |
485 | .allProps {
486 | left:auto;
487 | width: 26px;
488 | height: 26px;
489 | right: 0%;
490 | bottom: 41px;
491 | background-color: var(--vscode-input-background) !important;
492 | }
493 |
494 | .MuiButtonBase-root.MuiIconButton-root.MuiIconButton-sizeMedium{
495 | padding-right: 0px;
496 | padding-left: 0px;
497 | height: 26px;
498 | padding-top: 0px;
499 | padding-bottom: 0px;
500 | background-color: var(--vscode-input-background) !important;
501 | }
502 |
503 | .css-1yxmbwk {
504 | display: inline-block !important;
505 | height: 100% !important;
506 | width: 2.5rem !important;
507 | border-radius: 0 !important;
508 | }
509 |
510 | #extraButtonsContainer {
511 | height: auto;
512 | }
513 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "lib": ["es6", "dom"],
7 | "jsx": "react",
8 | "sourceMap": true,
9 | "rootDir": "src",
10 | "strict": false, /* enable all strict type-checking options /
11 | / Additional Checks /
12 | // "noImplicitReturns": true, / Report error when not all code paths in function return a value. /
13 | // "noFallthroughCasesInSwitch": true, / Report errors for fallthrough cases in switch statement. /
14 | // "noUnusedParameters": true, / Report errors on unused parameters. */
15 | // "esModuleInterop": true
16 | },
17 | "include": ["src", "src/types/index.d.ts"],
18 | "exclude": [
19 | "node_modules",
20 | ".vscode-test",
21 | "src/webviews",
22 | "src/test/test_apps"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/webpack.config.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as webpack from 'webpack';
3 |
4 | const extConfig: webpack.Configuration = {
5 | target: 'node',
6 | entry: './src/extension.ts',
7 | output: {
8 | filename: 'extension.js',
9 | libraryTarget: 'commonjs2',
10 | path: path.resolve(__dirname, 'out'),
11 | },
12 | resolve: { extensions: ['.ts', '.js'] },
13 | module: { rules: [{ test: /\.ts$/, loader: 'ts-loader' }] },
14 | externals: { vscode: 'vscode' },
15 | };
16 |
17 | const webviewConfig: webpack.Configuration = {
18 | target: 'web',
19 | entry: './src/webview/index.tsx',
20 | output: {
21 | filename: '[name].wv.js',
22 | path: path.resolve(__dirname, 'out'),
23 | },
24 | resolve: {
25 | extensions: ['.js', '.ts', '.tsx', 'scss'],
26 | },
27 | module: {
28 | rules: [
29 | { test: /\.tsx?$/, use: ['babel-loader', 'ts-loader'] },
30 | {
31 | test: /\.css$/,
32 | use: ['style-loader', 'css-loader'],
33 | },
34 | ],
35 | },
36 | };
37 |
38 | export default [webviewConfig, extConfig];
39 |
--------------------------------------------------------------------------------