├── .eslintrc.json
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── assets
├── favicon.ico
└── next_nav_logo.png
├── out
├── directory.js
├── directory.js.map
├── extension.js
├── extension.js.map
├── functions.js
├── functions.js.map
├── main.js
├── makeTree.js
├── makeTree.js.map
└── test
│ ├── runTest.js
│ ├── runTest.js.map
│ └── suite
│ ├── extension.test.js
│ ├── extension.test.js.map
│ ├── index.js
│ ├── index.js.map
│ ├── makeTree.js
│ └── makeTree.js.map
├── package-lock.json
├── package.json
├── src
├── extension.ts
├── functions.ts
├── makeTree.ts
└── types.d.ts
├── test
├── test.ts
└── testfiles
│ ├── basic-client.ts
│ ├── client-in-comments.ts
│ ├── comments-before-client.ts
│ ├── comments-before-server.js
│ └── server-in-comments.js
├── tsconfig.json
└── webview-react-app
├── .eslintrc
├── dist
├── bundle.js
├── bundle.js.LICENSE.txt
└── index.html
├── index.html
├── package-lock.json
├── package.json
├── src
├── App.tsx
├── VsCodeApiContext.tsx
├── components
│ ├── LayoutFlow.tsx
│ ├── Node.tsx
│ ├── TreeContainer.tsx
│ └── modals
│ │ ├── DetailsView.tsx
│ │ ├── FolderAdd.tsx
│ │ ├── FolderDelete.tsx
│ │ └── ListItem.tsx
├── functions.ts
└── index.tsx
├── style.css
├── tsconfig.json
├── utils
└── types.ts
└── webpack.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": [
9 | "@typescript-eslint"
10 | ],
11 | "rules": {
12 | "@typescript-eslint/naming-convention": "warn",
13 | "@typescript-eslint/semi": "warn",
14 | "curly": "warn",
15 | "eqeqeq": "warn",
16 | "no-throw-literal": "warn",
17 | "semi": "off"
18 | },
19 | "ignorePatterns": [
20 | "out",
21 | "dist",
22 | "**/*.d.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | webview-react-app/node_modules
3 | NextNav-*.vsix
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "outFiles": [
16 | "${workspaceFolder}/out/**/*.js"
17 | ],
18 | "preLaunchTask": "${defaultBuildTask}"
19 | },
20 | {
21 | "name": "Extension Tests",
22 | "type": "extensionHost",
23 | "request": "launch",
24 | "args": [
25 | "--extensionDevelopmentPath=${workspaceFolder}",
26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
27 | ],
28 | "outFiles": [
29 | "${workspaceFolder}/out/test/**/*.js"
30 | ],
31 | "preLaunchTask": "${defaultBuildTask}"
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off"
11 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$tsc-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | src/**
4 | .gitignore
5 | .yarnrc
6 | **/node_modules
7 | vsc-extension-quickstart.md
8 | **/tsconfig.json
9 | **/.eslintrc.json
10 | **/*.map
11 | **/*.ts
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [1.0.0] - 2023-09-06
9 |
10 | - Initial release
11 |
12 | ## [1.0.2] - 2023-09-06
13 |
14 | ### Fixed
15 |
16 | - Fix to remove string on new file creation
17 | - Update README.md to reflect new known issues
18 |
19 | ## [1.0.3] - 2023-09-17
20 |
21 | ### Fixed
22 |
23 | - Fix to stop long folder names from clipping node edge
24 |
25 | ### Updated
26 |
27 | - Update to show import popover on load
28 |
29 | ## [1.0.4] - 2023-09-28
30 |
31 | ### Updated
32 |
33 | - Update import to grab 'src/app' automatically if present
34 |
35 | ### Deprecated
36 |
37 | - Change to show import popover on load
38 |
39 | ## [1.0.5] - 2023-11-16
40 |
41 | ### Updated
42 |
43 | - Add status-bar launch item. Redirects or opens extension panel from any other panel in VS Code
44 | - Prevent icon overflow in nodes with 'view more' icon
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 
8 | 
9 |
10 |
11 |
12 |
13 |
14 | 
15 | 
16 | 
17 | 
18 | 
19 |
20 |
21 |
22 | #
23 |
24 | ## NEXT.NAV
25 |
26 | Welcome to Next.Nav , a Visual Studio Code extension for viewing and manipulating projects using the Next.js App Router.
27 |
28 | ## Features
29 |
30 | Next.nav allows Next.js developers to:
31 |
32 | - Load a projects src/app route.
33 | - View routes in a legible node based tree view.
34 | - Easily access files in nested routes.
35 | - Add / Remove routes to a clearly defined parent route.
36 | - Add / Remove files to routes.
37 | - Display whether routes render Client side or Server side.
38 |
39 | ## Getting Started
40 |
41 | ### Launching the Extension and Opening a Tree
42 |
43 | 1. Install VSCode
44 | 2. Install the extension by searching "Next Nav" in the extension marketplace or Launch VS Code Quick Open (Ctrl+P in Windows/Linux) or (Command+P in MacOS), paste the following command `ext install NextNav.NextNav` and press enter.
45 | 3. Open a Next.js project that is using the App Router in VSCode
46 | 
47 | 4. Launch Next.Nav from status bar or by opening the command palette using (Ctrl+Shift+P in Windows/Linux) or (Command+Shift+P in MacOS) and typing `Next.Nav` highlight and press Enter
48 | 
49 | 5. Select the Import Path icon and input the relative or absolute path of your root App route (Note: Next.Nav will automatically grab your file structure if it is under the 'src/app' route)
50 |
51 | ### Route Traversal
52 |
53 | 
54 |
55 | - You can traverse into sub trees/folders by pressing the sub-directory icon (to the right of the client/server) of the node you want to become the root node
56 | - From the sub-directory, you can exit to the original tree by press the exit button on the root node. If this button is not there, you are already in the original tree.
57 | - The original tree is determined by the initial folder path the extension opens on or the path you enter into the path field.
58 |
59 | ### Opening Files
60 |
61 | 
62 |
63 | 1. When you hover over a file type in the tree it will tell you the name
64 | 2. You can click on the icon of the file in the folder to open it.
65 | Alternatively, you can click on the folder to open a modal with all of the files
66 | 3. Click on a file to open it
67 |
68 | ### Adding Files
69 |
70 | 
71 |
72 | 1. Click on any blank space on a folder node to open a modal to view its contents.
73 | 2. Add a file name and extension in the input field.
74 | 3. Add file with the green add file icon.
75 |
76 | ### Deleting Files
77 |
78 | **warning:** this will **permanently** delete the file.
79 |
80 | 
81 |
82 | 1. Click on any blank space on a folder node to open a modal to view a folders contents.
83 | 2. Click the red trash icon next to the file you want to delete.
84 | 3. Click confirm in the pop-over to **permanently** delete the file. (**warning:** this can not be undone)
85 |
86 | ### Adding Folders
87 |
88 | 
89 |
90 | 1. Click on the plus icon on the right edge of the folder node you want your new folder to be nested in.
91 | 2. Give your new folder a name and submit.
92 |
93 | ### Deleting Folders
94 |
95 | **warning:** this will **permanently** delete all contained files and sub folders
96 |
97 | 
98 |
99 | 1. Click on the minus icon on the left edge of the folder node you want to delete
100 | 2. Type the name of the folder to confirm deletion of the directory and all sub directories and files contained. (**warning:** this can not be undone)
101 |
102 | ## Want to Contribute?
103 |
104 | Next.Nav is an Open Source product and we encourage developers to contribute. Please create a fork of the dev branch and create a feature branch on your own repo. Please make all pull request from your feature branch into Next.Nav's dev branch.
105 |
106 | ### Known Issues
107 |
108 | - Demo Tree initially loads in upper let corner on first load
109 | - File deletion popover does not disappear after confirm click
110 |
111 | ### Features to Add
112 |
113 | - Renaming folders and files: add an edit button for file and folder names
114 | - Moving folders and files: adjust routes through drag and drop
115 | - Filter for file types: add a checklist panel to highlight selected file types
116 | - Add support for more file types
117 | - Add toggle option for client/server-side render text on nodes
118 | - Recently used route (time stamp)
119 |
120 | ### Possible Iterations
121 |
122 | - Implement debugging and optimization insights for routes
123 | - Provide a pages router to app router converter
124 | - Expand to prototyping tool for Next.js projects
125 |
126 | ## Release Notes
127 |
128 | ### 1.1.0 - Feature Update
129 |
130 | 1.1.0
131 |
132 | Adds sub-tree traversal and focus feature
133 | Adds fixes for Windows file path directory
134 |
135 |
136 |
137 | 1.1.1
138 |
139 | fixes tutorial tree errors
140 | adds auto focus to input fields
141 | updates "getting started" instructions in README
142 |
143 |
144 |
145 | ### 1.0.0 - Initial release of Next.Nav
146 |
147 | 1.0.2
148 |
149 | Fix to disallow submit on enter keypress for an empty input field within import popover
150 | Fix to remove string after new file creation
151 | Improve various UI elements
152 | Update README.md to reflect new known issues
153 |
154 |
155 |
156 | 1.0.3
157 |
158 | Fix to stop long folder names from clipping node edge (c/o miso-devel !)
159 | Update to show import popover on load
160 |
161 |
162 |
163 | 1.0.4
164 |
165 | Update import to grab 'src/app' automatically if present
166 | Revert change to show import popover on load
167 |
168 |
169 | 1.0.5
170 |
171 | Adds status-bar launch item. This makes it seamless when navigating back to the extension.
172 | Adds limit to icons displayed
173 |
174 |
175 |
176 | ## Contributors
177 |
178 |
179 |
180 |
181 |
182 |
183 | Anatoliy Sokolov
184 |
185 | Linkedin |
186 | GitHub
187 |
188 |
189 |
190 |
191 | Brian Henkel
192 |
193 | Linkedin |
194 | GitHub
195 |
196 |
197 |
198 |
199 | Jordan Querubin
200 |
201 | Linkedin |
202 | GitHub
203 |
204 |
205 |
206 | Nathan Peel
207 |
208 | Linkedin |
209 | GitHub
210 |
211 |
212 |
213 |
214 | Darren Pavel
215 |
216 | Linkedin |
217 | GitHub
218 |
219 |
220 |
221 |
--------------------------------------------------------------------------------
/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Next-Nav/f48dedc5eb4d8eb3f73aa9c79fe31231a1d8562a/assets/favicon.ico
--------------------------------------------------------------------------------
/assets/next_nav_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Next-Nav/f48dedc5eb4d8eb3f73aa9c79fe31231a1d8562a/assets/next_nav_logo.png
--------------------------------------------------------------------------------
/out/directory.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | //# sourceMappingURL=directory.js.map
--------------------------------------------------------------------------------
/out/directory.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"directory.js","sourceRoot":"","sources":["../src/directory.ts"],"names":[],"mappings":""}
--------------------------------------------------------------------------------
/out/extension.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.deactivate = exports.activate = void 0;
4 | const vscode = require("vscode");
5 | const path = require("path");
6 | const makeTree_1 = require("./makeTree");
7 | const fs_1 = require("fs");
8 | const functions_1 = require("./functions");
9 | let lastSubmittedDir = null; // directory user gave
10 | let webview = null;
11 | //get the directory to send to the React
12 | async function sendUpdatedDirectory(webview, dirName) {
13 | try {
14 | // Call treeMaker with only one folder name
15 | const result = await (0, makeTree_1.default)(dirName);
16 | const sendString = JSON.stringify(result);
17 | //console.log(sendString);
18 | webview.webview.postMessage({ command: 'sendString', data: sendString });
19 | }
20 | catch (error) {
21 | vscode.window.showErrorMessage('Error sending updated directory: ' + error.message);
22 | }
23 | }
24 | function activate(context) {
25 | const iconName = 'next-nav-icon';
26 | context.globalState.update(iconName, true);
27 | const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
28 | statusBarItem.text = 'Next.Nav';
29 | statusBarItem.command = 'next-extension.next-nav';
30 | statusBarItem.tooltip = 'Launch Next Nav';
31 | statusBarItem.show();
32 | context.subscriptions.push(statusBarItem);
33 | //runs when extension is called every time
34 | let disposable = vscode.commands.registerCommand('next-extension.next-nav', async () => {
35 | //search for an existing panel and redirect to that if it exists
36 | if (webview) {
37 | webview.reveal(vscode.ViewColumn.One);
38 | }
39 | else {
40 | //create a webview to put React on
41 | webview = vscode.window.createWebviewPanel('Next.Nav', 'Next.Nav', vscode.ViewColumn.One, {
42 | enableScripts: true,
43 | //make the extension persist on tab
44 | retainContextWhenHidden: true,
45 | });
46 | webview.onDidDispose(() => {
47 | webview = null;
48 | }, null, context.subscriptions);
49 | //When we get requests from React
50 | if (webview === null) {
51 | throw new Error('Webview is null');
52 | }
53 | const cloneView = webview;
54 | cloneView.webview.onDidReceiveMessage(async (message) => {
55 | console.log('Received message:', message);
56 | switch (message.command) {
57 | //save directory for future use
58 | case 'submitDir':
59 | let formCheck = false;
60 | if (message['form']) {
61 | formCheck = true;
62 | }
63 | const folderLocation = await (0, functions_1.getValidDirectoryPath)(path.normalize(message.folderName));
64 | if (folderLocation) {
65 | lastSubmittedDir = folderLocation;
66 | // vscode.window.showInformationMessage(
67 | // 'Directory is now ' + lastSubmittedDir
68 | // );
69 | cloneView.webview.postMessage({
70 | command: 'submitDirResponse',
71 | result: true,
72 | form: formCheck,
73 | });
74 | }
75 | else {
76 | if (message.showError) {
77 | vscode.window.showErrorMessage('Invalid directory: ' + message.folderName);
78 | }
79 | cloneView.webview.postMessage({
80 | command: 'submitDirResponse',
81 | result: false,
82 | form: formCheck,
83 | });
84 | }
85 | break;
86 | //send directory to React
87 | case 'getRequest':
88 | if (lastSubmittedDir) {
89 | await sendUpdatedDirectory(cloneView, lastSubmittedDir);
90 | }
91 | else {
92 | console.error('No directory has been submitted yet.');
93 | vscode.window.showErrorMessage('No directory has been submitted yet.');
94 | }
95 | break;
96 | // open a file in the extension
97 | case 'open_file':
98 | const filePath = message.filePath;
99 | try {
100 | const document = await vscode.workspace.openTextDocument(filePath);
101 | await vscode.window.showTextDocument(document);
102 | console.log(`Switched to tab with file: ${filePath}`);
103 | }
104 | catch (err) {
105 | vscode.window.showErrorMessage(`Error opening file: ${err.message}`);
106 | console.error(`Error opening file: ${err}`);
107 | }
108 | break;
109 | //add a new file in at specified path
110 | case 'addFile':
111 | try {
112 | const filePath = message.filePath;
113 | await fs_1.promises.writeFile(path.normalize(filePath), '');
114 | //let the React know we added a file
115 | cloneView.webview.postMessage({ command: 'added_addFile' });
116 | }
117 | catch (error) {
118 | console.error('Error creating file:', error.message);
119 | vscode.window.showErrorMessage('Error creating file: ' + error.message);
120 | }
121 | break;
122 | //add a new folder at a specified path
123 | case 'addFolder':
124 | try {
125 | const folderPath = message.filePath;
126 | await fs_1.promises.mkdir(path.normalize(folderPath));
127 | cloneView.webview.postMessage({ command: 'added_addFolder' });
128 | }
129 | catch (error) {
130 | console.error('Error creating folder:', error.message);
131 | vscode.window.showErrorMessage('Error creating folder: ' + error.message);
132 | }
133 | break;
134 | //delete a file at specified path
135 | case 'deleteFile':
136 | try {
137 | const filePath = message.filePath;
138 | const uri = vscode.Uri.file(path.normalize(filePath));
139 | if (await fs_1.promises.stat(filePath)) {
140 | await vscode.workspace.fs.delete(uri, { useTrash: true });
141 | }
142 | else {
143 | throw new Error('File does not exist');
144 | }
145 | //let the React know we deleted a file
146 | cloneView.webview.postMessage({
147 | command: 'added_deleteFile',
148 | });
149 | }
150 | catch (error) {
151 | console.error('Error deleting file:', error.message);
152 | vscode.window.showErrorMessage('Error deleting file: ' + error.message);
153 | }
154 | break;
155 | //delete a folder at specified path
156 | case 'deleteFolder':
157 | try {
158 | const folderPath = message.filePath;
159 | const uri = vscode.Uri.file(path.normalize(folderPath));
160 | //delete folder and subfolders
161 | if (await fs_1.promises.stat(folderPath)) {
162 | await vscode.workspace.fs.delete(uri, {
163 | recursive: true,
164 | useTrash: true,
165 | });
166 | }
167 | else {
168 | throw new Error('Folder does not exist');
169 | }
170 | // Let the React app know that we've successfully deleted a folder
171 | cloneView.webview.postMessage({
172 | command: 'added_deleteFolder',
173 | });
174 | }
175 | catch (error) {
176 | vscode.window.showErrorMessage('Error deleting folder: ' + error.message);
177 | }
178 | break;
179 | }
180 | }, undefined, context.subscriptions);
181 | try {
182 | //bundle for react code
183 | const bundlePath = path.join(context.extensionPath, 'webview-react-app', 'dist', 'bundle.js');
184 | const bundleContent = await fs_1.promises.readFile(bundlePath, 'utf-8');
185 | //html in the webview to put our react code into
186 | webview.webview.html = `
187 |
188 |
189 |
190 |
191 | Next.Nav
192 |
193 |
194 |
195 |
196 |
199 |
200 | `;
201 | }
202 | catch (err) {
203 | console.log(err);
204 | }
205 | // vscode.window.showInformationMessage('Welcome to Next.Nav!');
206 | }
207 | });
208 | context.subscriptions.push(disposable);
209 | }
210 | exports.activate = activate;
211 | function deactivate() { }
212 | exports.deactivate = deactivate;
213 | //# sourceMappingURL=extension.js.map
--------------------------------------------------------------------------------
/out/extension.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/out/functions.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.getValidDirectoryPath = void 0;
4 | const fs = require("fs/promises");
5 | const path = require("path");
6 | const vscode = require("vscode");
7 | //Checks that child directory is within parent directory
8 | function isSubdirectory(parent, child) {
9 | const parentPath = path.resolve(parent).toLowerCase();
10 | const childPath = path.resolve(child).toLowerCase();
11 | console.log('p: ' + parentPath);
12 | console.log('c: ' + childPath);
13 | return parentPath.startsWith(childPath);
14 | }
15 | async function getValidDirectoryPath(dirPath) {
16 | try {
17 | if (!vscode.workspace.workspaceFolders) {
18 | return '';
19 | }
20 | const workspaceDir = vscode.workspace.workspaceFolders[0].uri.fsPath;
21 | // Convert to absolute path if it is a relative path
22 | const absoluteDirPath = path.isAbsolute(dirPath)
23 | ? dirPath
24 | : path.join(workspaceDir, dirPath);
25 | console.log(absoluteDirPath);
26 | // Validate if this path is within the workspace directory
27 | if (!isSubdirectory(absoluteDirPath, workspaceDir)) {
28 | console.log('workspace: ', workspaceDir);
29 | console.log('absolute: ', absoluteDirPath);
30 | console.log('not within working dir');
31 | return '';
32 | }
33 | // Check if the directory actually exists
34 | const stat = await fs.stat(absoluteDirPath);
35 | if (!stat.isDirectory()) {
36 | console.log('doesnt exist');
37 | return '';
38 | }
39 | //logging path to test in windows
40 | return absoluteDirPath; // Return the validated absolute directory path
41 | }
42 | catch (err) {
43 | return '';
44 | }
45 | }
46 | exports.getValidDirectoryPath = getValidDirectoryPath;
47 | //# sourceMappingURL=functions.js.map
--------------------------------------------------------------------------------
/out/functions.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"functions.js","sourceRoot":"","sources":["../src/functions.ts"],"names":[],"mappings":";;;AAAA,kCAAkC;AAClC,6BAA6B;AAC7B,iCAAiC;AAEjC,wDAAwD;AACxD,SAAS,cAAc,CAAC,MAAc,EAAE,KAAa;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAC/B,OAAO,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;AAC1C,CAAC;AAEM,KAAK,UAAU,qBAAqB,CAAC,OAAe;IACzD,IAAI;QACF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,gBAAgB,EAAE;YACtC,OAAO,EAAE,CAAC;SACX;QACD,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;QACrE,oDAAoD;QACpD,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAC9C,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAE7B,0DAA0D;QAC1D,IAAI,CAAC,cAAc,CAAC,eAAe,EAAE,YAAY,CAAC,EAAE;YAClD,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACtC,OAAO,EAAE,CAAC;SACX;QACD,yCAAyC;QACzC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE;YACvB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YAC5B,OAAO,EAAE,CAAC;SACX;QACD,iCAAiC;QAEjC,OAAO,eAAe,CAAC,CAAC,+CAA+C;KACxE;IAAC,OAAO,GAAQ,EAAE;QACjB,OAAO,EAAE,CAAC;KACX;AACH,CAAC;AA/BD,sDA+BC"}
--------------------------------------------------------------------------------
/out/main.js:
--------------------------------------------------------------------------------
1 | "use strict";var M=Object.create;var v=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var T=Object.getPrototypeOf,R=Object.prototype.hasOwnProperty;var W=(e,s)=>{for(var t in s)v(e,t,{get:s[t],enumerable:!0})},P=(e,s,t,n)=>{if(s&&typeof s=="object"||typeof s=="function")for(let a of S(s))!R.call(e,a)&&a!==t&&v(e,a,{get:()=>s[a],enumerable:!(n=C(s,a))||n.enumerable});return e};var f=(e,s,t)=>(t=e!=null?M(T(e)):{},P(s||!e||!e.__esModule?v(t,"default",{value:e,enumerable:!0}):t,e)),j=e=>P(v({},"__esModule",{value:!0}),e);var _={};W(_,{activate:()=>z,deactivate:()=>V});module.exports=j(_);var i=f(require("vscode")),m=f(require("path"));var k=f(require("fs/promises")),E=require("readline"),D=require("fs"),g=f(require("path"));async function I(e){let s=(0,E.createInterface)({input:(0,D.createReadStream)(e,{end:9999})}),t="",n=!1;for await(let r of s){if(n){r.includes("*/")&&(n=!1);continue}let c=r.indexOf("/*");if(c!==-1){n=!0;let l=r.indexOf("*/");if(l!==-1&&l>c){n=!1;let o=r.slice(0,c)+r.slice(l+2);if(o.trim()){t=o.trim();break}continue}continue}let w=r.split("//")[0].trim();if(w){t=w;break}}return s.close(),['"use client"',"'use client'","`use client`"].some(r=>t.includes(r))}async function b(e){let s=1,t=/\.(js|jsx|css|ts|tsx|sass|scss|html)$/,n=[{id:0,folderName:g.basename(e),parentNode:null,path:e,contents:[],render:"server"}];async function a(r,c){let w=await k.readdir(r,{withFileTypes:!0});for(let l of w){let o=g.join(r,l.name);if(l.isDirectory()){let h={id:s++,folderName:l.name,parentNode:c,path:o,contents:[],render:"server"};n.push(h),await a(o,h.id)}else t.test(l.name)&&(n[c].contents.push(l.name),await I(o)&&(n[c].render="client"))}}try{return await a(e,0),n}catch{return{}}}var p=require("fs");var N=f(require("fs/promises")),u=f(require("path")),y=f(require("vscode"));function L(e,s){let t=u.resolve(e).toLowerCase(),n=u.resolve(s).toLowerCase();return console.log("p: "+t),console.log("c: "+n),t.startsWith(n)}async function F(e){try{if(!y.workspace.workspaceFolders)return"";let s=y.workspace.workspaceFolders[0].uri.fsPath,t=u.isAbsolute(e)?e:u.join(s,e);return console.log(t),L(t,s)?(await N.stat(t)).isDirectory()?t:(console.log("doesnt exist"),""):(console.log("workspace: ",s),console.log("absolute: ",t),console.log("not within working dir"),"")}catch{return""}}var x=null,d=null;async function O(e,s){try{let t=await b(s),n=JSON.stringify(t);e.webview.postMessage({command:"sendString",data:n})}catch(t){i.window.showErrorMessage("Error sending updated directory: "+t.message)}}function z(e){let s="next-nav-icon";e.globalState.update(s,!0);let t=i.window.createStatusBarItem(i.StatusBarAlignment.Right);t.text="Next.Nav",t.command="next-extension.next-nav",t.tooltip="Launch Next Nav",t.show(),e.subscriptions.push(t);let n=i.commands.registerCommand("next-extension.next-nav",async()=>{if(d)d.reveal(i.ViewColumn.One);else{if(d=i.window.createWebviewPanel("Next.Nav","Next.Nav",i.ViewColumn.One,{enableScripts:!0,retainContextWhenHidden:!0}),d.onDidDispose(()=>{d=null},null,e.subscriptions),d===null)throw new Error("Webview is null");let a=d;a.webview.onDidReceiveMessage(async r=>{switch(console.log("Received message:",r),r.command){case"submitDir":let c=!1;r.form&&(c=!0);let w=await F(m.normalize(r.folderName));w?(x=w,a.webview.postMessage({command:"submitDirResponse",result:!0,form:c})):(r.showError&&i.window.showErrorMessage("Invalid directory: "+r.folderName),a.webview.postMessage({command:"submitDirResponse",result:!1,form:c}));break;case"getRequest":x?await O(a,x):(console.error("No directory has been submitted yet."),i.window.showErrorMessage("No directory has been submitted yet."));break;case"open_file":let l=r.filePath;try{let o=await i.workspace.openTextDocument(l);await i.window.showTextDocument(o),console.log(`Switched to tab with file: ${l}`)}catch(o){i.window.showErrorMessage(`Error opening file: ${o.message}`),console.error(`Error opening file: ${o}`)}break;case"addFile":try{let o=r.filePath;await p.promises.writeFile(m.normalize(o),""),a.webview.postMessage({command:"added_addFile"})}catch(o){console.error("Error creating file:",o.message),i.window.showErrorMessage("Error creating file: "+o.message)}break;case"addFolder":try{let o=r.filePath;await p.promises.mkdir(m.normalize(o)),a.webview.postMessage({command:"added_addFolder"})}catch(o){console.error("Error creating folder:",o.message),i.window.showErrorMessage("Error creating folder: "+o.message)}break;case"deleteFile":try{let o=r.filePath,h=i.Uri.file(m.normalize(o));if(await p.promises.stat(o))await i.workspace.fs.delete(h,{useTrash:!0});else throw new Error("File does not exist");a.webview.postMessage({command:"added_deleteFile"})}catch(o){console.error("Error deleting file:",o.message),i.window.showErrorMessage("Error deleting file: "+o.message)}break;case"deleteFolder":try{let o=r.filePath,h=i.Uri.file(m.normalize(o));if(await p.promises.stat(o))await i.workspace.fs.delete(h,{recursive:!0,useTrash:!0});else throw new Error("Folder does not exist");a.webview.postMessage({command:"added_deleteFolder"})}catch(o){i.window.showErrorMessage("Error deleting folder: "+o.message)}break}},void 0,e.subscriptions);try{let r=m.join(e.extensionPath,"webview-react-app","dist","bundle.js"),c=await p.promises.readFile(r,"utf-8");d.webview.html=`
2 |
3 |
4 |
5 |
6 | Next.Nav
7 |
8 |
9 |
10 |
11 |
14 |
15 | `}catch(r){console.log(r)}}});e.subscriptions.push(n)}function V(){}0&&(module.exports={activate,deactivate});
16 |
--------------------------------------------------------------------------------
/out/makeTree.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.checkForClientDirective = void 0;
4 | const fsPromises = require("fs/promises");
5 | const readline_1 = require("readline");
6 | const fs_1 = require("fs");
7 | const path = require("path");
8 | // Function used to parse through file and check if its client side rendered
9 | async function checkForClientDirective(filePath) {
10 | // Create a Readable Stream to read the file
11 | const rl = (0, readline_1.createInterface)({
12 | input: (0, fs_1.createReadStream)(filePath, { end: 9999 }), // Read up to the first 1000 bytes assuming the client is in there dont want to read whole file
13 | });
14 | let firstNonCommentText = ''; // Store the first non-comment line of code
15 | let inCommentBlock = false; // Flag for inside a block comment
16 | // Loop through each line of the file
17 | for await (const line of rl) {
18 | // Check if inside a block comment
19 | if (inCommentBlock) {
20 | // If end of block comment is found, exit block comment mode
21 | if (line.includes('*/')) {
22 | inCommentBlock = false;
23 | }
24 | continue;
25 | }
26 | // Check for start of a new block comment
27 | let startCommentIndex = line.indexOf('/*');
28 | if (startCommentIndex !== -1) {
29 | inCommentBlock = true; // Enter block comment mode
30 | // Check if it is a single-line block comment
31 | let endCommentIndex = line.indexOf('*/');
32 | if (endCommentIndex !== -1 && endCommentIndex > startCommentIndex) {
33 | // Exit block comment mode
34 | inCommentBlock = false;
35 | // Remove the block comment and check the remaining text if there is a comment and code on the same line
36 | const modifiedLine = line.slice(0, startCommentIndex) + line.slice(endCommentIndex + 2);
37 | if (modifiedLine.trim()) {
38 | firstNonCommentText = modifiedLine.trim();
39 | break;
40 | }
41 | continue;
42 | }
43 | continue;
44 | }
45 | // Remove single-line comments (//) and check the remaining text in a case where we have code then //comment
46 | const noSingleLineComment = line.split('//')[0].trim();
47 | if (noSingleLineComment) {
48 | firstNonCommentText = noSingleLineComment;
49 | break;
50 | }
51 | }
52 | // Close the Readable Stream
53 | rl.close();
54 | // Check if the first non-comment text contains any form of "use client"
55 | const targetStrings = ['"use client"', "'use client'", '`use client`'];
56 | return targetStrings.some((target) => firstNonCommentText.includes(target));
57 | }
58 | exports.checkForClientDirective = checkForClientDirective;
59 | //function to make the tree
60 | async function treeMaker(validDir) {
61 | let idCounter = 1;
62 | const extensions = /\.(js|jsx|css|ts|tsx|sass|scss|html)$/;
63 | //directory to be put into the output structure, id of the directory will match its index in the structure
64 | const structure = [
65 | {
66 | id: 0,
67 | folderName: path.basename(validDir),
68 | parentNode: null,
69 | path: validDir,
70 | contents: [],
71 | render: 'server',
72 | },
73 | ];
74 | // Recursive function to list files and populate structure
75 | async function listFiles(dir, parent) {
76 | const entities = await fsPromises.readdir(dir, { withFileTypes: true });
77 | for (const entity of entities) {
78 | const fullPath = path.join(dir, entity.name);
79 | if (entity.isDirectory()) {
80 | const directoryData = {
81 | id: idCounter++,
82 | folderName: entity.name,
83 | parentNode: parent,
84 | path: fullPath,
85 | contents: [],
86 | render: 'server',
87 | };
88 | structure.push(directoryData);
89 | await listFiles(fullPath, directoryData.id);
90 | }
91 | else if (extensions.test(entity.name)) {
92 | structure[parent].contents.push(entity.name);
93 | // Check if this file has the 'use client' directive
94 | if (await checkForClientDirective(fullPath)) {
95 | structure[parent].render = 'client';
96 | }
97 | }
98 | }
99 | }
100 | try {
101 | await listFiles(validDir, 0);
102 | return structure;
103 | }
104 | catch (err) {
105 | return {};
106 | }
107 | }
108 | exports.default = treeMaker;
109 | //# sourceMappingURL=makeTree.js.map
--------------------------------------------------------------------------------
/out/makeTree.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"makeTree.js","sourceRoot":"","sources":["../src/makeTree.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/out/test/runTest.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const path = require("path");
4 | const test_electron_1 = require("@vscode/test-electron");
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 | // The path to test runner
11 | // Passed to --extensionTestsPath
12 | const extensionTestsPath = path.resolve(__dirname, './suite/index');
13 | // Download VS Code, unzip it and run the integration test
14 | await (0, test_electron_1.runTests)({ extensionDevelopmentPath, extensionTestsPath });
15 | }
16 | catch (err) {
17 | console.error('Failed to run tests', err);
18 | process.exit(1);
19 | }
20 | }
21 | main();
22 | //# sourceMappingURL=runTest.js.map
--------------------------------------------------------------------------------
/out/test/runTest.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"runTest.js","sourceRoot":"","sources":["../../src/test/runTest.ts"],"names":[],"mappings":";;AAAA,6BAA6B;AAE7B,yDAAiD;AAEjD,KAAK,UAAU,IAAI;IAClB,IAAI;QACH,4DAA4D;QAC5D,yCAAyC;QACzC,MAAM,wBAAwB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEnE,0BAA0B;QAC1B,iCAAiC;QACjC,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QAEpE,0DAA0D;QAC1D,MAAM,IAAA,wBAAQ,EAAC,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,CAAC,CAAC;KACjE;IAAC,OAAO,GAAG,EAAE;QACb,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KAChB;AACF,CAAC;AAED,IAAI,EAAE,CAAC"}
--------------------------------------------------------------------------------
/out/test/suite/extension.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const assert = require("assert");
4 | // You can import and use all API from the 'vscode' module
5 | // as well as import your extension to test it
6 | const vscode = require("vscode");
7 | // import * as myExtension from '../../extension';
8 | suite('Extension Test Suite', () => {
9 | vscode.window.showInformationMessage('Start all tests.');
10 | test('Sample test', () => {
11 | assert.strictEqual(-1, [1, 2, 3].indexOf(5));
12 | assert.strictEqual(-1, [1, 2, 3].indexOf(0));
13 | });
14 | });
15 | //# sourceMappingURL=extension.test.js.map
--------------------------------------------------------------------------------
/out/test/suite/extension.test.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"extension.test.js","sourceRoot":"","sources":["../../../src/test/suite/extension.test.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AAEjC,0DAA0D;AAC1D,8CAA8C;AAC9C,iCAAiC;AACjC,kDAAkD;AAElD,KAAK,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAClC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,kBAAkB,CAAC,CAAC;IAEzD,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
--------------------------------------------------------------------------------
/out/test/suite/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.run = void 0;
4 | const path = require("path");
5 | const Mocha = require("mocha");
6 | const glob = require("glob");
7 | function run() {
8 | // Create the mocha test
9 | const mocha = new Mocha({
10 | ui: 'tdd',
11 | color: true
12 | });
13 | const testsRoot = path.resolve(__dirname, '..');
14 | return new Promise((c, e) => {
15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
16 | if (err) {
17 | return e(err);
18 | }
19 | // Add files to the test suite
20 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
21 | try {
22 | // Run the mocha test
23 | mocha.run(failures => {
24 | if (failures > 0) {
25 | e(new Error(`${failures} tests failed.`));
26 | }
27 | else {
28 | c();
29 | }
30 | });
31 | }
32 | catch (err) {
33 | console.error(err);
34 | e(err);
35 | }
36 | });
37 | });
38 | }
39 | exports.run = run;
40 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/out/test/suite/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/test/suite/index.ts"],"names":[],"mappings":";;;AAAA,6BAA6B;AAC7B,+BAA+B;AAC/B,6BAA6B;AAE7B,SAAgB,GAAG;IAClB,wBAAwB;IACxB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;QACvB,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,IAAI;KACX,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAEhD,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC3B,IAAI,CAAC,eAAe,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YACxD,IAAI,GAAG,EAAE;gBACR,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;aACd;YAED,8BAA8B;YAC9B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAE9D,IAAI;gBACH,qBAAqB;gBACrB,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;oBACpB,IAAI,QAAQ,GAAG,CAAC,EAAE;wBACjB,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,QAAQ,gBAAgB,CAAC,CAAC,CAAC;qBAC1C;yBAAM;wBACN,CAAC,EAAE,CAAC;qBACJ;gBACF,CAAC,CAAC,CAAC;aACH;YAAC,OAAO,GAAG,EAAE;gBACb,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBACnB,CAAC,CAAC,GAAG,CAAC,CAAC;aACP;QACF,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC;AAjCD,kBAiCC"}
--------------------------------------------------------------------------------
/out/test/suite/makeTree.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const fs = require("fs/promises");
4 | const path = require("path");
5 | const functions_1 = require("./functions");
6 | async function treeMaker() {
7 | let idCounter = 1; // global id counter
8 | const structure = [
9 | { id: 0,
10 | folderName: 'app',
11 | parentNode: null,
12 | contents: []
13 | }
14 | ];
15 | const directory = await (0, functions_1.findDir)();
16 | const extensions = /\.(js|jsx|css|ts|tsx)$/;
17 | async function listFiles(dir, parent) {
18 | const withFileTypes = true;
19 | const entities = await fs.readdir(dir, { withFileTypes });
20 | for (const entity of entities) {
21 | const fullPath = path.join(dir, entity.name);
22 | if (entity.isDirectory()) {
23 | const directoryData = {
24 | id: idCounter++,
25 | folderName: entity.name,
26 | parentNode: parent,
27 | contents: []
28 | };
29 | structure.push(directoryData);
30 | await listFiles(fullPath, directoryData.id);
31 | }
32 | else if (extensions.test(entity.name)) {
33 | structure[parent].contents.push(entity.name);
34 | }
35 | }
36 | }
37 | await listFiles(directory, 0);
38 | console.log('tree ', JSON.stringify(structure, null, 2));
39 | return structure;
40 | }
41 | exports.default = treeMaker;
42 | //# sourceMappingURL=makeTree.js.map
--------------------------------------------------------------------------------
/out/test/suite/makeTree.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"makeTree.js","sourceRoot":"","sources":["../../../src/test/suite/makeTree.ts"],"names":[],"mappings":";;AAAA,kCAAkC;AAElC,6BAA6B;AAC7B,2CAAsC;AAGvB,KAAK,UAAU,SAAS;IACrC,IAAI,SAAS,GAAG,CAAC,CAAC,CAAE,oBAAoB;IACxC,MAAM,SAAS,GAAgB;QAC7B,EAAE,EAAE,EAAE,CAAC;YACP,UAAU,EAAE,KAAK;YACjB,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,EAAE;SACX;KACA,CAAC;IAEF,MAAM,SAAS,GAAI,MAAM,IAAA,mBAAO,GAAE,CAAC;IACnC,MAAM,UAAU,GAAG,wBAAwB,CAAC;IAE9C,KAAK,UAAU,SAAS,CAAC,GAAW,EAAE,MAAc;QAElD,MAAM,aAAa,GAAG,IAAI,CAAC;QAC3B,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;QAE1D,KAAK,MAAM,MAAM,IAAI,QAAoB,EAAE;YACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAE7C,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE;gBACxB,MAAM,aAAa,GAAc;oBAC/B,EAAE,EAAE,SAAS,EAAE;oBACf,UAAU,EAAE,MAAM,CAAC,IAAI;oBACvB,UAAU,EAAE,MAAM;oBAClB,QAAQ,EAAE,EAAE;iBACb,CAAC;gBAEF,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;gBAE9B,MAAM,SAAS,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC;aAC7C;iBAAM,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;gBACvC,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;aAC9C;SACF;IACH,CAAC;IAED,MAAM,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACzD,OAAO,SAAS,CAAC;AACnB,CAAC;AAzCD,4BAyCC"}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "NextNav",
3 | "displayName": "Next.Nav",
4 | "publisher": "NextNav",
5 | "description": "Navigate and create routes easily in your Next.js application",
6 | "icon": "assets/next_nav_logo.png",
7 | "version": "1.1.1",
8 | "engines": {
9 | "vscode": "^1.81.0"
10 | },
11 | "categories": [
12 | "Other"
13 | ],
14 | "activationEvents": [
15 | "onStartupFinished"
16 | ],
17 | "main": "./out/extension.js",
18 | "contributes": {
19 | "commands": [
20 | {
21 | "command": "next-extension.next-nav",
22 | "title": "Next.Nav"
23 | }
24 | ],
25 | "menus": {
26 | "statusBar": [
27 | {
28 | "command": "next-extension.next-nav",
29 | "when": "true"
30 | }
31 | ]
32 | }
33 | },
34 | "scripts": {
35 | "vscode:prepublish": "npm run esbuild-base -- --minify",
36 | "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node",
37 | "esbuild": "npm run esbuild-base -- --sourcemap",
38 | "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch",
39 | "test-compile": "tsc -p ./",
40 | "compile": "tsc -p ./",
41 | "watch": "tsc -watch -p ./",
42 | "pretest": "npm run compile && npm run lint",
43 | "lint": "eslint src --ext ts",
44 | "test": "mocha -r ts-node/register 'test/**/*.ts'"
45 | },
46 | "devDependencies": {
47 | "@types/expect": "^24.3.0",
48 | "@types/glob": "^8.1.0",
49 | "@types/mocha": "^10.0.1",
50 | "@types/node": "20.2.5",
51 | "@types/vscode": "^1.81.0",
52 | "@typescript-eslint/eslint-plugin": "^5.59.8",
53 | "@typescript-eslint/parser": "^5.59.8",
54 | "@vscode/test-electron": "^2.3.2",
55 | "esbuild": "^0.19.2",
56 | "eslint": "^8.41.0",
57 | "glob": "^8.1.0",
58 | "mocha": "^10.2.0",
59 | "ts-node": "^10.9.1",
60 | "typescript": "^5.1.3"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as path from 'path';
3 | import treeMaker from './makeTree';
4 | import { promises as fs } from 'fs';
5 | import { getValidDirectoryPath } from './functions';
6 |
7 | let lastSubmittedDir: string | null = null; // directory user gave
8 | let webview: vscode.WebviewPanel | null = null;
9 | //get the directory to send to the React
10 | async function sendUpdatedDirectory(
11 | webview: vscode.WebviewPanel,
12 | dirName: string
13 | ): Promise {
14 | try {
15 | // Call treeMaker with only one folder name
16 | const result = await treeMaker(dirName);
17 | const sendString = JSON.stringify(result);
18 | //console.log(sendString);
19 | webview.webview.postMessage({ command: 'sendString', data: sendString });
20 | } catch (error: any) {
21 | vscode.window.showErrorMessage(
22 | 'Error sending updated directory: ' + error.message
23 | );
24 | }
25 | }
26 |
27 | export function activate(context: vscode.ExtensionContext) {
28 | const iconName = 'next-nav-icon';
29 | context.globalState.update(iconName, true);
30 | const statusBarItem = vscode.window.createStatusBarItem(
31 | vscode.StatusBarAlignment.Right
32 | );
33 | statusBarItem.text = 'Next.Nav';
34 | statusBarItem.command = 'next-extension.next-nav';
35 | statusBarItem.tooltip = 'Launch Next Nav';
36 | statusBarItem.show();
37 | context.subscriptions.push(statusBarItem);
38 |
39 | //runs when extension is called every time
40 | let disposable = vscode.commands.registerCommand(
41 | 'next-extension.next-nav',
42 | async () => {
43 | //search for an existing panel and redirect to that if it exists
44 | if (webview) {
45 | webview.reveal(vscode.ViewColumn.One);
46 | } else {
47 | //create a webview to put React on
48 | webview = vscode.window.createWebviewPanel(
49 | 'Next.Nav',
50 | 'Next.Nav',
51 | vscode.ViewColumn.One,
52 | {
53 | enableScripts: true,
54 | //make the extension persist on tab
55 | retainContextWhenHidden: true,
56 | }
57 | );
58 |
59 | webview.onDidDispose(
60 | () => {
61 | webview = null;
62 | },
63 | null,
64 | context.subscriptions
65 | );
66 |
67 | //When we get requests from React
68 | if (webview === null) {
69 | throw new Error('Webview is null');
70 | }
71 | const cloneView = webview as vscode.WebviewPanel;
72 | cloneView.webview.onDidReceiveMessage(
73 | async (message) => {
74 | console.log('Received message:', message);
75 | switch (message.command) {
76 | //save directory for future use
77 | case 'submitDir':
78 | let formCheck = false;
79 | if (message['form']) {
80 | formCheck = true;
81 | }
82 | const folderLocation = await getValidDirectoryPath(
83 | path.normalize(message.folderName)
84 | );
85 | if (folderLocation) {
86 | lastSubmittedDir = folderLocation;
87 | // vscode.window.showInformationMessage(
88 | // 'Directory is now ' + lastSubmittedDir
89 | // );
90 | cloneView.webview.postMessage({
91 | command: 'submitDirResponse',
92 | result: true,
93 | form: formCheck,
94 | });
95 | } else {
96 | if (message.showError) {
97 | vscode.window.showErrorMessage(
98 | 'Invalid directory: ' + message.folderName
99 | );
100 | }
101 | cloneView.webview.postMessage({
102 | command: 'submitDirResponse',
103 | result: false,
104 | form: formCheck,
105 | });
106 | }
107 | break;
108 | //send directory to React
109 | case 'getRequest':
110 | if (lastSubmittedDir) {
111 | await sendUpdatedDirectory(cloneView, lastSubmittedDir);
112 | } else {
113 | console.error('No directory has been submitted yet.');
114 | vscode.window.showErrorMessage(
115 | 'No directory has been submitted yet.'
116 | );
117 | }
118 | break;
119 | // open a file in the extension
120 | case 'open_file':
121 | const filePath = message.filePath;
122 | try {
123 | const document = await vscode.workspace.openTextDocument(
124 | filePath
125 | );
126 | await vscode.window.showTextDocument(document);
127 | console.log(`Switched to tab with file: ${filePath}`);
128 | } catch (err: any) {
129 | vscode.window.showErrorMessage(
130 | `Error opening file: ${err.message}`
131 | );
132 | console.error(`Error opening file: ${err}`);
133 | }
134 | break;
135 | //add a new file in at specified path
136 | case 'addFile':
137 | try {
138 | const filePath = message.filePath;
139 | await fs.writeFile(path.normalize(filePath), '');
140 | //let the React know we added a file
141 | cloneView.webview.postMessage({ command: 'added_addFile' });
142 | } catch (error: any) {
143 | console.error('Error creating file:', error.message);
144 | vscode.window.showErrorMessage(
145 | 'Error creating file: ' + error.message
146 | );
147 | }
148 | break;
149 | //add a new folder at a specified path
150 | case 'addFolder':
151 | try {
152 | const folderPath = message.filePath;
153 | await fs.mkdir(path.normalize(folderPath));
154 | cloneView.webview.postMessage({ command: 'added_addFolder' });
155 | } catch (error: any) {
156 | console.error('Error creating folder:', error.message);
157 | vscode.window.showErrorMessage(
158 | 'Error creating folder: ' + error.message
159 | );
160 | }
161 | break;
162 |
163 | //delete a file at specified path
164 | case 'deleteFile':
165 | try {
166 | const filePath = message.filePath;
167 | const uri = vscode.Uri.file(path.normalize(filePath));
168 | if (await fs.stat(filePath)) {
169 | await vscode.workspace.fs.delete(uri, { useTrash: true });
170 | } else {
171 | throw new Error('File does not exist');
172 | }
173 | //let the React know we deleted a file
174 | cloneView.webview.postMessage({
175 | command: 'added_deleteFile',
176 | });
177 | } catch (error: any) {
178 | console.error('Error deleting file:', error.message);
179 | vscode.window.showErrorMessage(
180 | 'Error deleting file: ' + error.message
181 | );
182 | }
183 | break;
184 | //delete a folder at specified path
185 | case 'deleteFolder':
186 | try {
187 | const folderPath = message.filePath;
188 | const uri = vscode.Uri.file(path.normalize(folderPath));
189 |
190 | //delete folder and subfolders
191 | if (await fs.stat(folderPath)) {
192 | await vscode.workspace.fs.delete(uri, {
193 | recursive: true,
194 | useTrash: true,
195 | });
196 | } else {
197 | throw new Error('Folder does not exist');
198 | }
199 | // Let the React app know that we've successfully deleted a folder
200 | cloneView.webview.postMessage({
201 | command: 'added_deleteFolder',
202 | });
203 | } catch (error: any) {
204 | vscode.window.showErrorMessage(
205 | 'Error deleting folder: ' + error.message
206 | );
207 | }
208 | break;
209 | }
210 | },
211 | undefined,
212 | context.subscriptions
213 | );
214 |
215 | try {
216 | //bundle for react code
217 | const bundlePath = path.join(
218 | context.extensionPath,
219 | 'webview-react-app',
220 | 'dist',
221 | 'bundle.js'
222 | );
223 | const bundleContent = await fs.readFile(bundlePath, 'utf-8');
224 | //html in the webview to put our react code into
225 | webview.webview.html = `
226 |
227 |
228 |
229 |
230 | Next.Nav
231 |
232 |
233 |
234 |
235 |
238 |
239 | `;
240 | } catch (err) {
241 | console.log(err);
242 | }
243 | // vscode.window.showInformationMessage('Welcome to Next.Nav!');
244 | }
245 | }
246 | );
247 | context.subscriptions.push(disposable);
248 | }
249 | export function deactivate() {}
250 |
--------------------------------------------------------------------------------
/src/functions.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs/promises';
2 | import * as path from 'path';
3 | import * as vscode from 'vscode';
4 |
5 | //Checks that child directory is within parent directory
6 | function isSubdirectory(parent: string, child: string) {
7 | const parentPath = path.resolve(parent).toLowerCase();
8 | const childPath = path.resolve(child).toLowerCase();
9 | console.log('p: ' + parentPath);
10 | console.log('c: ' + childPath);
11 | return parentPath.startsWith(childPath);
12 | }
13 |
14 | export async function getValidDirectoryPath(dirPath: string): Promise {
15 | try {
16 | if (!vscode.workspace.workspaceFolders) {
17 | return '';
18 | }
19 | const workspaceDir = vscode.workspace.workspaceFolders[0].uri.fsPath;
20 | // Convert to absolute path if it is a relative path
21 | const absoluteDirPath = path.isAbsolute(dirPath)
22 | ? dirPath
23 | : path.join(workspaceDir, dirPath);
24 | console.log(absoluteDirPath);
25 |
26 | // Validate if this path is within the workspace directory
27 | if (!isSubdirectory(absoluteDirPath, workspaceDir)) {
28 | console.log('workspace: ', workspaceDir);
29 | console.log('absolute: ', absoluteDirPath);
30 | console.log('not within working dir');
31 | return '';
32 | }
33 | // Check if the directory actually exists
34 | const stat = await fs.stat(absoluteDirPath);
35 | if (!stat.isDirectory()) {
36 | console.log('doesnt exist');
37 | return '';
38 | }
39 | //logging path to test in windows
40 |
41 | return absoluteDirPath; // Return the validated absolute directory path
42 | } catch (err: any) {
43 | return '';
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/makeTree.ts:
--------------------------------------------------------------------------------
1 | import * as fsPromises from 'fs/promises';
2 | import { Dirent } from 'fs';
3 | import { createInterface } from 'readline';
4 | import { createReadStream } from 'fs';
5 | import * as path from 'path';
6 | import { Directory } from './types';
7 |
8 |
9 | // Function used to parse through file and check if its client side rendered
10 | export async function checkForClientDirective(filePath: string): Promise {
11 | // Create a Readable Stream to read the file
12 | const rl = createInterface({
13 | input: createReadStream(filePath, { end: 9999 }), // Read up to the first 1000 bytes assuming the client is in there dont want to read whole file
14 | });
15 |
16 | let firstNonCommentText = ''; // Store the first non-comment line of code
17 | let inCommentBlock = false; // Flag for inside a block comment
18 |
19 | // Loop through each line of the file
20 | for await (const line of rl) {
21 | // Check if inside a block comment
22 | if (inCommentBlock) {
23 | // If end of block comment is found, exit block comment mode
24 | if (line.includes('*/')) {
25 | inCommentBlock = false;
26 | }
27 | continue;
28 | }
29 |
30 | // Check for start of a new block comment
31 | let startCommentIndex = line.indexOf('/*');
32 | if (startCommentIndex !== -1) {
33 | inCommentBlock = true; // Enter block comment mode
34 |
35 | // Check if it is a single-line block comment
36 | let endCommentIndex = line.indexOf('*/');
37 | if (endCommentIndex !== -1 && endCommentIndex > startCommentIndex) {
38 | // Exit block comment mode
39 | inCommentBlock = false;
40 |
41 | // Remove the block comment and check the remaining text if there is a comment and code on the same line
42 | const modifiedLine =
43 | line.slice(0, startCommentIndex) + line.slice(endCommentIndex + 2);
44 | if (modifiedLine.trim()) {
45 | firstNonCommentText = modifiedLine.trim();
46 | break;
47 | }
48 | continue;
49 | }
50 | continue;
51 | }
52 |
53 | // Remove single-line comments (//) and check the remaining text in a case where we have code then //comment
54 | const noSingleLineComment = line.split('//')[0].trim();
55 | if (noSingleLineComment) {
56 | firstNonCommentText = noSingleLineComment;
57 | break;
58 | }
59 | }
60 |
61 | // Close the Readable Stream
62 | rl.close();
63 |
64 | // Check if the first non-comment text contains any form of "use client"
65 | const targetStrings = ['"use client"', "'use client'", '`use client`'];
66 | return targetStrings.some((target) => firstNonCommentText.includes(target));
67 | }
68 | //function to make the tree
69 | export default async function treeMaker(
70 | validDir: string
71 | ): Promise {
72 | let idCounter = 1;
73 | const extensions = /\.(js|jsx|css|ts|tsx|sass|scss|html)$/;
74 | //directory to be put into the output structure, id of the directory will match its index in the structure
75 | const structure: Directory[] = [
76 | {
77 | id: 0,
78 | folderName: path.basename(validDir),
79 | parentNode: null,
80 | path: validDir,
81 | contents: [],
82 | render: 'server',
83 | },
84 | ];
85 |
86 |
87 |
88 | // Recursive function to list files and populate structure
89 | async function listFiles(dir: string, parent: number): Promise {
90 | const entities = await fsPromises.readdir(dir, { withFileTypes: true });
91 |
92 | for (const entity of entities) {
93 | const fullPath = path.join(dir, entity.name);
94 |
95 | if (entity.isDirectory()) {
96 | const directoryData: Directory = {
97 | id: idCounter++,
98 | folderName: entity.name,
99 | parentNode: parent,
100 | path: fullPath,
101 | contents: [],
102 | render: 'server',
103 | };
104 |
105 | structure.push(directoryData);
106 | await listFiles(fullPath, directoryData.id);
107 | } else if (extensions.test(entity.name)) {
108 | structure[parent].contents.push(entity.name);
109 |
110 | // Check if this file has the 'use client' directive
111 | if (await checkForClientDirective(fullPath)) {
112 | structure[parent].render = 'client';
113 | }
114 | }
115 | }
116 | }
117 |
118 | try {
119 | await listFiles(validDir, 0);
120 | return structure;
121 | } catch (err) {
122 | return {};
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | //directory
2 | export interface Directory {
3 | id: number;
4 | folderName: string;
5 | parentNode: number | null;
6 | path: string;
7 | render: 'server' | 'client';
8 | contents: (string | Directory)[];
9 | }
10 |
--------------------------------------------------------------------------------
/test/test.ts:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import * as path from 'path';
3 | import { checkForClientDirective } from '.././src/makeTree';
4 |
5 | describe('testing checkFor ClientDirective', ()=> {
6 |
7 |
8 | it('it should be able to find a use clients with no comments', async () => {
9 | const dir = path.join(__dirname, './testfiles/basic-client.js');
10 | console.log(dir);
11 | expect(await checkForClientDirective(dir)).toEqual(true);
12 | });
13 |
14 | it('it should be able to find a use client in comments', async () => {
15 | const dir = path.join(__dirname, './testfiles/client-in-comments.js');
16 | console.log(dir);
17 | expect(await checkForClientDirective(dir)).toEqual(true);
18 | });
19 |
20 | it('it should be able to find a use client after comments', async () => {
21 | const dir = path.join(__dirname, './testfiles/comments-before-client.js');
22 | expect(await checkForClientDirective(dir)).toEqual(true);
23 | });
24 |
25 | it('it should be able to find something that doesnt match use client in comments', async () => {
26 | const dir = path.join(__dirname, './testfiles/server-in-comments.js');
27 | expect(await checkForClientDirective(dir)).toEqual(false);
28 | });
29 |
30 | it('it should be able to find something that doesnt match use client after comments', async () => {
31 | const dir = path.join(__dirname, './testfiles/comments-before-server.js');
32 | expect(await checkForClientDirective(dir)).toEqual(false);
33 | });
34 |
35 |
36 | });
37 |
38 |
--------------------------------------------------------------------------------
/test/testfiles/basic-client.ts:
--------------------------------------------------------------------------------
1 | 'use client';
--------------------------------------------------------------------------------
/test/testfiles/client-in-comments.ts:
--------------------------------------------------------------------------------
1 | /*testing for use client
2 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Commodo elit at imperdiet dui accumsan sit amet nulla. Tincidunt augue interdum velit euismod in pellentesque massa placerat. Mauris nunc congue nisi vitae suscipit tellus mauris. Consectetur adipiscing elit ut aliquam purus sit amet luctus. Rhoncus urna neque viverra justo nec ultrices dui sapien. Iaculis nunc sed augue lacus viverra vitae congue. Vitae aliquet nec ullamcorper sit amet. Lacus suspendisse faucibus interdum posuere. Eget dolor morbi non arcu risus quis. Vitae ultricies leo integer malesuada nunc vel. Dictumst quisque sagittis purus sit amet volutpat. Volutpat odio facilisis mauris sit amet massa vitae tortor. Pellentesque pulvinar pellentesque habitant morbi tristique senectus et netus. Pharetra convallis posuere morbi leo. Lacus laoreet non curabitur gravida arcu ac tortor. Rhoncus dolor purus non enim praesent elementum facilisis leo. Id diam maecenas ultricies mi eget mauris. Lacus vestibulum sed arcu non odio euismod. Cursus sit amet dictum sit amet justo donec enim diam.
3 |
4 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Commodo elit at imperdiet dui accumsan sit amet nulla. Tincidunt augue interdum velit euismod in pellentesque massa placerat. Mauris nunc congue nisi vitae suscipit tellus mauris. Consectetur adipiscing elit ut aliquam purus sit amet luctus. Rhoncus urna neque viverra justo nec ultrices dui sapien. Iaculis nunc sed augue lacus viverra vitae congue. Vitae aliquet nec ullamcorper sit amet. Lacus suspendisse faucibus interdum posuere. Eget dolor morbi non arcu risus quis. Vitae ultricies leo integer malesuada nunc vel. Dictumst quisque sagittis purus sit amet volutpat. Volutpat odio facilisis mauris sit amet massa vitae tortor. Pellentesque pulvinar pellentesque habitant morbi tristique senectus et netus. Pharetra convallis posuere morbi leo. Lacus laoreet non curabitur gravida arcu ac tortor. Rhoncus dolor purus non enim praesent elementum facilisis leo. Id diam maecenas ultricies mi eget mauris. Lacus vestibulum sed arcu non odio euismod. Cursus sit amet dictum sit amet justo donec enim diam.
5 | */
6 |
7 | /*Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Commodo elit at imperdiet dui accumsan sit amet nulla. Tincidunt augue interdum velit euismod in pellentesque massa placerat. Mauris nunc congue nisi vitae suscipit tellus mauris. Consectetur adipiscing elit ut aliquam purus sit amet luctus. Rhoncus urna neque viverra justo nec ultrices dui sapien. Iaculis nunc sed augue lacus viverra vitae congue. Vitae aliquet nec ullamcorper sit amet. Lacus suspendisse faucibus interdum posuere. Eget dolor morbi non arcu risus quis. Vitae ultricies leo integer malesuada nunc vel. Dictumst quisque sagittis purus sit amet volutpat. Volutpat odio facilisis mauris sit amet massa vitae tortor. Pellentesque pulvinar pellentesque habitant morbi tristique senectus et netus. Pharetra convallis posuere morbi leo. Lacus laoreet non curabitur gravida arcu ac tortor. Rhoncus dolor purus non enim praesent elementum facilisis leo. Id diam maecenas ultricies mi eget mauris. Lacus vestibulum sed arcu non odio euismod. Cursus sit amet dictum sit a.*/'use client'; /*done*/
8 |
9 |
--------------------------------------------------------------------------------
/test/testfiles/comments-before-client.ts:
--------------------------------------------------------------------------------
1 | /*testing for use client
2 | *
3 | *Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Commodo elit at imperdiet dui accumsan sit amet nulla. Tincidunt augue interdum velit euismod in pellentesque massa placerat. Mauris nunc congue nisi vitae suscipit tellus mauris. Consectetur adipiscing elit ut aliquam purus sit amet luctus. Rhoncus urna neque viverra justo nec ultrices dui sapien. Iaculis nunc sed augue lacus viverra vitae congue. Vitae aliquet nec ullamcorper sit amet. Lacus suspendisse faucibus interdum posuere. Eget dolor morbi non arcu risus quis. Vitae ultricies leo integer malesuada nunc vel. Dictumst quisque sagittis purus sit amet volutpat. Volutpat odio facilisis mauris sit amet massa vitae tortor. Pellentesque pulvinar pellentesque habitant morbi tristique senectus et netus. Pharetra convallis posuere morbi leo. Lacus laoreet non curabitur gravida arcu ac tortor. Rhoncus dolor purus non enim praesent elementum facilisis leo. Id diam maecenas ultricies mi eget mauris. Lacus vestibulum sed arcu non odio euismod. Cursus sit amet dictum sit amet justo donec enim diam.
4 |
5 | Tristique magna sit amet purus gravida quis blandit. Magna ac placerat vestibulum lectus mauris. Donec massa sapien faucibus et molestie ac feugiat sed. Iaculis nunc sed augue lacus viverra vitae congue eu. Aliquam id diam maecenas ultricies mi. Vulputate dignissim suspendisse in est. Nibh tortor id aliquet lectus proin nibh nisl condimentum id. Leo in vitae turpis massa sed elementum tempus. Fames ac turpis egestas sed tempus. Magna etiam tempor orci eu lobortis elementum. A lacus vestibulum sed arcu non odio euismod lacinia at. Metus vulputate eu scelerisque felis imperdiet. Aliquet risus feugiat in ante metus.
6 |
7 | Pellentesque eu tincidunt tortor aliquam nulla facilisi. Et pharetra pharetra massa massa. Enim neque volutpat ac tincidunt vitae semper. Neque volutpat ac tincidunt vitae semper quis lectus. Dui vivamus arcu felis bibendum ut. Et netus et malesuada fames ac. Dui ut ornare lectus sit amet est placerat in. Elit ullamcorper dignissim cras tincidunt lobortis feugiat. Vitae et leo duis ut diam. Elementum nibh tellus molestie nunc non blandit massa.
8 |
9 | */
10 |
11 | //End Lorem Ipsum
12 | 'use client';
--------------------------------------------------------------------------------
/test/testfiles/comments-before-server.js:
--------------------------------------------------------------------------------
1 | /*testing for use client
2 | *
3 | *Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Commodo elit at imperdiet dui accumsan sit amet nulla. Tincidunt augue interdum velit euismod in pellentesque massa placerat. Mauris nunc congue nisi vitae suscipit tellus mauris. Consectetur adipiscing elit ut aliquam purus sit amet luctus. Rhoncus urna neque viverra justo nec ultrices dui sapien. Iaculis nunc sed augue lacus viverra vitae congue. Vitae aliquet nec ullamcorper sit amet. Lacus suspendisse faucibus interdum posuere. Eget dolor morbi non arcu risus quis. Vitae ultricies leo integer malesuada nunc vel. Dictumst quisque sagittis purus sit amet volutpat. Volutpat odio facilisis mauris sit amet massa vitae tortor. Pellentesque pulvinar pellentesque habitant morbi tristique senectus et netus. Pharetra convallis posuere morbi leo. Lacus laoreet non curabitur gravida arcu ac tortor. Rhoncus dolor purus non enim praesent elementum facilisis leo. Id diam maecenas ultricies mi eget mauris. Lacus vestibulum sed arcu non odio euismod. Cursus sit amet dictum sit amet justo donec enim diam.
4 |
5 | Tristique magna sit amet purus gravida quis blandit. Magna ac placerat vestibulum lectus mauris. Donec massa sapien faucibus et molestie ac feugiat sed. Iaculis nunc sed augue lacus viverra vitae congue eu. Aliquam id diam maecenas ultricies mi. Vulputate dignissim suspendisse in est. Nibh tortor id aliquet lectus proin nibh nisl condimentum id. Leo in vitae turpis massa sed elementum tempus. Fames ac turpis egestas sed tempus. Magna etiam tempor orci eu lobortis elementum. A lacus vestibulum sed arcu non odio euismod lacinia at. Metus vulputate eu scelerisque felis imperdiet. Aliquet risus feugiat in ante metus.
6 |
7 | Pellentesque eu tincidunt tortor aliquam nulla facilisi. Et pharetra pharetra massa massa. Enim neque volutpat ac tincidunt vitae semper. Neque volutpat ac tincidunt vitae semper quis lectus. Dui vivamus arcu felis bibendum ut. Et netus et malesuada fames ac. Dui ut ornare lectus sit amet est placerat in. Elit ullamcorper dignissim cras tincidunt lobortis feugiat. Vitae et leo duis ut diam. Elementum nibh tellus molestie nunc non blandit massa.
8 |
9 | Vestibulum morbi blandit cursus risus at ultrices mi. Aenean et tortor at risus viverra. Bibendum est ultricies integer quis auctor elit sed vulputate mi. In hac habitasse platea dictumst. Vel pretium lectus quam id leo in. Tincidunt tortor aliquam nulla facilisi cras fermentum odio. Turpis egestas sed tempus urna et pharetra. At varius vel pharetra vel turpis. Ornare suspendisse sed nisi lacus sed viverra. Ultricies integer quis auctor elit sed vulputate mi sit. In vitae turpis massa sed elementum tempus egestas sed. Sem fringilla ut morbi tincidunt augue interdum velit euismod in. Auctor urna nunc id cursus metus aliquam eleifend.
10 |
11 | */
12 |
13 | //End Lorem Ipsum
14 | const testFunc = (nums) => {
15 | return nums + 1;
16 | };
--------------------------------------------------------------------------------
/test/testfiles/server-in-comments.js:
--------------------------------------------------------------------------------
1 | /*testing for use client
2 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Commodo elit at imperdiet dui accumsan sit amet nulla. Tincidunt augue interdum velit euismod in pellentesque massa placerat. Mauris nunc congue nisi vitae suscipit tellus mauris. Consectetur adipiscing elit ut aliquam purus sit amet luctus. Rhoncus urna neque viverra justo nec ultrices dui sapien. Iaculis nunc sed augue lacus viverra vitae congue. Vitae aliquet nec ullamcorper sit amet. Lacus suspendisse faucibus interdum posuere. Eget dolor morbi non arcu risus quis. Vitae ultricies leo integer malesuada nunc vel. Dictumst quisque sagittis purus sit amet volutpat. Volutpat odio facilisis mauris sit amet massa vitae tortor. Pellentesque pulvinar pellentesque habitant morbi tristique senectus et netus. Pharetra convallis posuere morbi leo. Lacus laoreet non curabitur gravida arcu ac tortor. Rhoncus dolor purus non enim praesent elementum facilisis leo. Id diam maecenas ultricies mi eget mauris. Lacus vestibulum sed arcu non odio euismod. Cursus sit amet dictum sit amet justo donec enim diam.
3 |
4 | Tristique magna sit amet purus gravida quis blandit. Magna ac placerat vestibulum lectus mauris. Donec massa sapien faucibus et molestie ac feugiat sed. Iaculis nunc sed augue lacus viverra vitae congue eu. Aliquam id diam maecenas ultricies mi. Vulputate dignissim suspendisse in est. Nibh tortor id aliquet lectus proin nibh nisl condimentum id. Leo in vitae turpis massa sed elementum tempus. Fames ac turpis egestas sed tempus. Magna etiam tempor orci eu lobortis elementum. A lacus vestibulum sed arcu non odio euismod lacinia at. Metus vulputate eu scelerisque felis imperdiet. Aliquet risus feugiat in ante metus.
5 |
6 | */const testFunc = (nums) => {
7 | return nums + 1;
8 | }; /*test */
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2020",
5 | "outDir": "out",
6 | "lib": [
7 | "ES2020"
8 | ],
9 | "sourceMap": true,
10 | "rootDir": "src",
11 | "strict": true /* enable all strict type-checking options */
12 | /* Additional Checks */
13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
16 | },
17 | "include": [
18 | "src/**/*.ts",
19 | "src/**/*.tsx"
20 | ],
21 | "exclude": [
22 | "webview-react-app/**/*"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/webview-react-app/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "@typescript-eslint/naming-convention": [
4 | "error",
5 | {
6 | "selector": "variable",
7 | "format": ["camelCase", "UPPER_CASE", "PascalCase"],
8 | "leadingUnderscore": "allow",
9 | "trailingUnderscore": "allow"
10 | }
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/webview-react-app/dist/bundle.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /**
2 | * @license React
3 | * react-dom.production.min.js
4 | *
5 | * Copyright (c) Facebook, Inc. and its affiliates.
6 | *
7 | * This source code is licensed under the MIT license found in the
8 | * LICENSE file in the root directory of this source tree.
9 | */
10 |
11 | /**
12 | * @license React
13 | * react-jsx-runtime.production.min.js
14 | *
15 | * Copyright (c) Facebook, Inc. and its affiliates.
16 | *
17 | * This source code is licensed under the MIT license found in the
18 | * LICENSE file in the root directory of this source tree.
19 | */
20 |
21 | /**
22 | * @license React
23 | * react.production.min.js
24 | *
25 | * Copyright (c) Facebook, Inc. and its affiliates.
26 | *
27 | * This source code is licensed under the MIT license found in the
28 | * LICENSE file in the root directory of this source tree.
29 | */
30 |
31 | /**
32 | * @license React
33 | * scheduler.production.min.js
34 | *
35 | * Copyright (c) Facebook, Inc. and its affiliates.
36 | *
37 | * This source code is licensed under the MIT license found in the
38 | * LICENSE file in the root directory of this source tree.
39 | */
40 |
41 | /**
42 | * @license React
43 | * use-sync-external-store-shim.production.min.js
44 | *
45 | * Copyright (c) Facebook, Inc. and its affiliates.
46 | *
47 | * This source code is licensed under the MIT license found in the
48 | * LICENSE file in the root directory of this source tree.
49 | */
50 |
51 | /**
52 | * @license React
53 | * use-sync-external-store-shim/with-selector.production.min.js
54 | *
55 | * Copyright (c) Facebook, Inc. and its affiliates.
56 | *
57 | * This source code is licensed under the MIT license found in the
58 | * LICENSE file in the root directory of this source tree.
59 | */
60 |
61 | /** @license React v16.13.1
62 | * react-is.production.min.js
63 | *
64 | * Copyright (c) Facebook, Inc. and its affiliates.
65 | *
66 | * This source code is licensed under the MIT license found in the
67 | * LICENSE file in the root directory of this source tree.
68 | */
69 |
--------------------------------------------------------------------------------
/webview-react-app/dist/index.html:
--------------------------------------------------------------------------------
1 | next.nav
--------------------------------------------------------------------------------
/webview-react-app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | next.nav
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/webview-react-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webview-react-app",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "build": "webpack --mode production",
9 | "test": "echo \"Error: no test specified\" && exit 1",
10 | "start": "webpack serve --mode development --open"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@chakra-ui/react": "^2.8.2",
17 | "@emotion/react": "^11.11.3",
18 | "@emotion/styled": "^11.11.0",
19 | "elkjs": "^0.8.2",
20 | "framer-motion": "^10.16.16",
21 | "react": "^18.2.0",
22 | "react-dom": "^18.2.0",
23 | "react-icons": "^4.10.1",
24 | "reactflow": "^11.8.2"
25 | },
26 | "devDependencies": {
27 | "@types/react": "^18.2.20",
28 | "@types/react-dom": "^18.2.7",
29 | "css-loader": "^6.8.1",
30 | "html-webpack-plugin": "^5.5.3",
31 | "style-loader": "^3.3.3",
32 | "ts-loader": "^9.4.4",
33 | "typescript": "^5.1.6",
34 | "webpack": "^5.88.2",
35 | "webpack-cli": "^5.1.4",
36 | "webpack-dev-server": "^4.15.1"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/webview-react-app/src/App.tsx:
--------------------------------------------------------------------------------
1 | import TreeContainer from './components/TreeContainer';
2 | import { ChakraProvider } from '@chakra-ui/react';
3 | import React, { useState, useEffect, useRef } from 'react';
4 | import { useVsCodeApi } from './VsCodeApiContext';
5 |
6 | const App: React.FC = () => {
7 | const vscode = useVsCodeApi();
8 | console.log('App Components has been reached');
9 | return (
10 | //Provides Charka library to the elements inside
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default App;
18 |
--------------------------------------------------------------------------------
/webview-react-app/src/VsCodeApiContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, ReactNode } from "react";
2 |
3 | declare global {
4 | interface Window {
5 | acquireVsCodeApi: () => any;
6 | }
7 | }
8 |
9 | const VsCodeApiContext = createContext(null);
10 |
11 | interface VsCodeApiProviderProps {
12 | children: ReactNode;
13 | }
14 |
15 | export const VsCodeApiProvider: React.FC = ({
16 | children,
17 | }) => {
18 | const vscode = window.acquireVsCodeApi();
19 | return (
20 |
21 | {children}
22 |
23 | );
24 | };
25 |
26 | export const useVsCodeApi = () => {
27 | return useContext(VsCodeApiContext);
28 | };
29 |
--------------------------------------------------------------------------------
/webview-react-app/src/components/LayoutFlow.tsx:
--------------------------------------------------------------------------------
1 | // import { initialNodes, initialEdges } from './nodes-edges.tsx';
2 | import ELK, { ElkNode } from "elkjs/lib/elk.bundled.js";
3 | import React, {
4 | useState,
5 | useCallback,
6 | useLayoutEffect,
7 | useEffect,
8 | } from "react";
9 | import ReactFlow, {
10 | addEdge,
11 | Panel,
12 | useNodesState,
13 | useEdgesState,
14 | useReactFlow,
15 | Edge,
16 | Connection,
17 | Controls, MiniMap} from "reactflow";
18 | import {
19 | Button,
20 | Heading,
21 | FormControl,
22 | Icon,
23 | IconButton,
24 | Input,
25 | FormErrorMessage,
26 | Popover,
27 | PopoverTrigger,
28 | PopoverContent,
29 | PopoverHeader,
30 | PopoverBody,
31 | } from "@chakra-ui/react";
32 | import { useVsCodeApi } from "../VsCodeApiContext";
33 |
34 | import { BiImport, BiRefresh, BiSitemap } from "react-icons/bi";
35 |
36 | export type FileNode = {
37 | id: number;
38 | folderName: string;
39 | parentNode: number | null;
40 | contents?: string[];
41 | path?: string;
42 | };
43 |
44 | export type Tree = FileNode[];
45 |
46 | //import the empty styles from reactflow to allow for other styles
47 | import "reactflow/dist/base.css";
48 | //import styles from NextNav style.css file.
49 | //This import is required to remove ReactFlow borders
50 | import "../../style.css";
51 |
52 | const elk = new ELK();
53 |
54 | // Elk has a *huge* amount of options to configure. To see everything you can
55 | // tweak check out:
56 | //
57 | // - https://www.eclipse.org/elk/reference/algorithms.html
58 | // - https://www.eclipse.org/elk/reference/options.html
59 | const elkOptions = {
60 | "elk.algorithm": "layered",
61 | "elk.layered.spacing.nodeNodeBetweenLayers": "200",
62 | "elk.spacing.nodeNode": "100",
63 | };
64 |
65 | //---For Updating the types later--
66 | // interface customElkNode extends ElkNode {
67 | // data?: {
68 | // label: string;
69 | // };
70 | // type?: string;
71 | // position?: {
72 | // x: number;
73 | // y: number;
74 | // };
75 | // }
76 |
77 | // interface customElkEdge extends ElkExtendedEdge {
78 | // type?: string;
79 | // }
80 | //------------//
81 |
82 | //Retrieves the nodes with updated positions from elkJS
83 | const getLayoutedElements = async (
84 | nodes: any[],
85 | edges: any[],
86 | options = { ["elk.direction"]: "RIGHT" }
87 | ): Promise => {
88 | //Changes the Direction of the graph based on the input
89 | const isHorizontal: boolean =
90 | options["elk.direction"] === "DOWN" ? false : true;
91 |
92 | //Forms data to pass to ELK function
93 | const graph: ElkNode = {
94 | id: "root",
95 | layoutOptions: options,
96 | children: nodes.map((node) => ({
97 | ...node,
98 | // Adjust the target and source handle positions based on the layout
99 | // direction.
100 | targetPosition: isHorizontal ? "left" : "top",
101 | sourcePosition: isHorizontal ? "right" : "bottom",
102 |
103 | // Hardcode a width and height for elk to use when layouting.
104 | //Adjust this to change the spacing between nodes
105 | width: 200,
106 | height: 200,
107 | })),
108 | edges: edges,
109 | };
110 |
111 | try {
112 | const elkGraph = await elk.layout(graph); //retrieves the new nodes with new positions
113 | if (!elkGraph.children) {
114 | elkGraph.children = []; //prevents elkGraph.children from being undefined
115 | }
116 |
117 | console.log("elkGraph", elkGraph);
118 | return {
119 | nodes: elkGraph.children.map((node) => ({
120 | ...node,
121 | //applies the positions to the child nodes to be readble for ReactFlow
122 | position: { x: node.x, y: node.y },
123 | })),
124 |
125 | edges: elkGraph.edges,
126 | };
127 | } catch (error) {
128 | //Displayed when the wrong data is passed to elk.layout
129 | console.log("catch block failed: ", error);
130 | }
131 | };
132 |
133 | type props = {
134 | initialNodes: any[];
135 | initialEdges: any[];
136 | srcDir: string;
137 | appDir: string;
138 | parseData: () => void;
139 | handleRequestDir: () => void;
140 | validDir: boolean;
141 | dirFormValue: string;
142 | setDirFormValue: (string: string) => void;
143 |
144 | };
145 |
146 | export default function LayoutFlow({
147 | initialNodes,
148 | initialEdges,
149 | parseData,
150 | handleRequestDir,
151 | validDir,
152 | dirFormValue,
153 | setDirFormValue,
154 | }: props) {
155 | // console.log('component rendered');
156 | const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
157 | const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
158 |
159 | const [view, setView] = useState("RIGHT");
160 |
161 | const [isOpen, setIsOpen] = useState(false);
162 | const open = () => setIsOpen(!isOpen);
163 | const close = () => setIsOpen(false);
164 |
165 | const { fitView } = useReactFlow(); //needed to position the tree within the window
166 | const vscode = useVsCodeApi();
167 |
168 | //For when a use connects a node. Not needed currently
169 | const onConnect = useCallback(
170 | (params: Edge | Connection) => setEdges((eds) => addEdge(params, eds)),
171 | []
172 | );
173 |
174 | //Caches a function that retrieves the positions from the nodes from getLayoutedElements
175 | //This funciton persists between loads, unless one of the dependencies change.
176 | //All variables associated with this funciton are cached as well.
177 | const onLayout = useCallback(
178 | async ({
179 | direction,
180 | useInitialNodes = false,
181 | }: {
182 | direction: string;
183 | useInitialNodes?: boolean;
184 | }): Promise => {
185 | const opts = { "elk.direction": direction, ...elkOptions };
186 | const ns = useInitialNodes ? initialNodes : nodes;
187 | console.log("OnLayout-nodes", ns);
188 |
189 | const es = useInitialNodes ? initialEdges : edges;
190 | console.log("OnLayout-edges", es);
191 |
192 | //gets the new nodes from the result of getLayoutedElements
193 | const layoutedElms = await getLayoutedElements(ns, es, opts);
194 | console.log("layouted", layoutedElms);
195 |
196 | setNodes(layoutedElms.nodes);
197 | setEdges(layoutedElms.edges);
198 |
199 | //adjusts the view based on the new tree
200 | window.requestAnimationFrame(() => fitView());
201 | },
202 | //initialNodes is a required dependency to make the useCalbback cache a new function with new data
203 | [nodes, edges, initialNodes]
204 | );
205 |
206 | // Calculate the initial layout on mount.
207 | useLayoutEffect(() => {
208 | console.log("initNodes", initialNodes);
209 |
210 | //sets the initial direction of the graph:
211 | onLayout({ direction: view, useInitialNodes: true });
212 | }, [initialNodes]);
213 |
214 | //BACKGROUND OF THE APP
215 | const reactFlowStyle = {
216 | background: "linear-gradient(#212121, #000000)",
217 | };
218 |
219 | const handleSubmitDir = (showError = true) => {
220 | console.log(vscode);
221 | console.log("Sending directory", dirFormValue);
222 | vscode.postMessage({
223 | command: "submitDir",
224 | folderName: dirFormValue,
225 | showError: showError,
226 | form: true,
227 | });
228 | };
229 |
230 | //on-load send message
231 | useEffect(() => {
232 | handleSubmitDir(false);
233 | setDirFormValue("");
234 | }, []);
235 |
236 | return (
237 |
247 |
248 | NEXT.NAV
249 |
250 |
251 |
257 |
258 | }
264 | _hover={{ bg: "white", textColor: "black" }}
265 | onClick={open}
266 | />
267 |
268 |
274 | Import Path
275 |
276 |
277 | {
285 | setDirFormValue(e.target.value);
286 | }}
287 | onKeyDown={(e) => {
288 | if (e.key === "Enter" && dirFormValue) {
289 | handleSubmitDir();
290 | close();
291 | setDirFormValue("");
292 | }
293 | }}
294 | />
295 |
296 | To find path, right click on the app folder and click copy
297 | relative or absolute path.
298 |
299 |
300 | {
305 | handleSubmitDir();
306 | close();
307 | setDirFormValue("");
308 | }}>
309 | Submit
310 |
311 |
312 |
313 | {" "}
314 |
322 | ) : (
323 |
324 | )
325 | }
326 | _hover={{ bg: "white", textColor: "black" }}
327 | onClick={() => {
328 | if (view === "DOWN") {
329 | onLayout({ direction: "RIGHT" });
330 | setView("RIGHT");
331 | } else {
332 | onLayout({ direction: "DOWN" });
333 | setView("DOWN");
334 | }
335 | }}
336 | />{" "}
337 | }
343 | _hover={{ bg: "white", textColor: "black" }}
344 | onClick={() => {
345 | if (validDir) {
346 | handleRequestDir();
347 | parseData();
348 | }
349 | }}
350 | />
351 |
352 |
353 | );
354 | }
355 |
--------------------------------------------------------------------------------
/webview-react-app/src/components/Node.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FileNode } from './TreeContainer';
3 | import { useVsCodeApi } from '../VsCodeApiContext';
4 |
5 | import {
6 | Card,
7 | CardHeader,
8 | CardBody,
9 | CardFooter,
10 | Heading,
11 | HStack,
12 | useDisclosure,
13 | Icon,
14 | IconButton,
15 | Tooltip,
16 | } from '@chakra-ui/react';
17 |
18 | import { IconType } from 'react-icons';
19 | import { PiFileCodeFill, PiDotsThreeOutlineFill } from 'react-icons/pi';
20 | import {
21 | SiCss3,
22 | SiReact,
23 | SiJavascript,
24 | SiTypescript,
25 | SiSass,
26 | SiHtml5,
27 | } from 'react-icons/si';
28 | import { BiImport, BiArrowBack } from 'react-icons/bi';
29 |
30 | import DetailsView from './modals/DetailsView';
31 | import FolderAdd from './modals/FolderAdd';
32 | import FolderDelete from './modals/FolderDelete';
33 |
34 | type Props = {
35 | data: FileNode;
36 | handlePostMessage: (
37 | filePath: string,
38 | command: string,
39 | setterFunc?: (string: string) => any
40 | ) => void;
41 | onPathChange: (string: string) => void;
42 | pathStack: Array;
43 | onRemovePath: () => void;
44 | rootPath: string;
45 | isDefault: boolean;
46 | };
47 |
48 | const Node = ({
49 | data,
50 | handlePostMessage,
51 | onPathChange,
52 | pathStack,
53 | onRemovePath,
54 | rootPath,
55 | isDefault,
56 | }: Props): JSX.Element => {
57 | //deconstruct props here. Used let to account for undefined checking.
58 | let { contents, parentNode, folderName, path, render }: FileNode = data;
59 | const vscode = useVsCodeApi();
60 |
61 | const {
62 | isOpen: nodeIsOpen,
63 | onOpen: nodeOnOpen,
64 | onClose: nodeOnClose,
65 | } = useDisclosure();
66 |
67 | //ensures obj.contents is never undefined
68 | if (!contents) {
69 | contents = [];
70 | }
71 | if (!path) {
72 | path = '';
73 | }
74 |
75 | //function that creates a new view using this node as the root node when we go into a subtree
76 | const handleSubmitDir = () => {
77 | onPathChange(rootPath);
78 | console.log(vscode);
79 | console.log('Creating new root with', path);
80 | console.log('path', pathStack);
81 | vscode.postMessage({
82 | command: 'submitDir',
83 | folderName: path,
84 | showError: false,
85 | });
86 | };
87 |
88 | const handlePrevDir = () => {
89 | const newDir = pathStack[pathStack.length - 1];
90 | console.log('newDir', newDir);
91 | onRemovePath();
92 | console.log('path', pathStack);
93 | vscode.postMessage({
94 | command: 'submitDir',
95 | folderName: newDir,
96 | showError: false,
97 | });
98 | };
99 |
100 | // selects an icon to use based on a file name
101 | const getIcon = (fileString: string): [IconType, string] => {
102 | // store of file extensions and their respective icons and icon background color
103 | const iconStore: { [index: string]: [IconType, string] } = {
104 | default: [PiFileCodeFill, 'white'],
105 | html: [SiHtml5,'#e34c26'],
106 | css: [SiCss3, '#264de4'],
107 | sass: [SiSass, '#cf6d99'],
108 | scss: [SiSass, '#cf6d99'],
109 | jsx: [SiReact, '#61DBFB'],
110 | js: [SiJavascript, '#f7df1e'],
111 | ts: [SiTypescript, '#007acc'],
112 | tsx: [SiReact, '#007acc'],
113 | };
114 | // finds files extension type with regEx matching
115 | const ext: RegExpMatchArray | null = fileString.match(/[^.]*$/); //['ts']
116 | // returns a default icon for non-matching files
117 | if (ext === null) {
118 | return iconStore.default;
119 | }
120 | // converts extension to lowercase
121 | const extStr: string = ext[0].toLowerCase();
122 | //console.log('extension string', extStr);
123 | if (iconStore.hasOwnProperty(extStr)) {
124 | return iconStore[extStr];
125 | } else {
126 | return iconStore.default;
127 | }
128 | };
129 | //generate the amount of file icons based on the number of contents
130 | const files: JSX.Element[] = [];
131 | const length = Math.min(contents.length, 8);
132 | for (let i = 0; i < length; i++) {
133 | const icon = getIcon(contents[i]);
134 | files.push(
135 |
136 | }
143 | onClick={(e) => {
144 | e.stopPropagation();
145 | clickWrapper(handlePostMessage,isDefault,path.concat('/', contents[i]), 'open_file');
146 | }}
147 | />
148 |
149 | );
150 | }
151 |
152 | const clickWrapper = (onClickFunction: Function, isDefault: boolean, ...args: any[]): void => {
153 | if (!isDefault) {
154 | return onClickFunction(...args);
155 | }
156 | console.log('This is a default node, no action will be taken');
157 | return;
158 | };
159 |
160 | const boxShadowColor = render === 'client' ? '#ffcf9e' : '#9FFFCB';
161 |
162 | return (
163 |
170 | {clickWrapper(nodeOnOpen, isDefault);}}
172 | bgColor='#050505'
173 | align='center'
174 | minW='15rem'
175 | w='15rem'
176 | minH='12rem'
177 | padding='10px 20px'
178 | borderRadius='15px'
179 | position='relative'
180 | boxShadow={`0px 0px 7px 1px ${
181 | parentNode === null ? '#FF9ED2' : boxShadowColor
182 | }`}
183 | >
184 |
185 |
186 | {folderName}
187 |
188 |
189 |
190 |
191 | {files}
192 | {contents.length > 8 && (
193 |
194 | }
201 | />
202 |
203 | )}
204 |
205 |
206 |
215 | {render === 'client' ? 'Client' : 'Server'}
216 | {parentNode !== null ? (
217 | //forward button
218 |
219 | }
225 | _hover={{ bg: 'white', textColor: 'black' }}
226 | onClick={(e) => {
227 | e.stopPropagation();
228 | clickWrapper(handleSubmitDir, isDefault);
229 | }}
230 | />
231 |
232 | ) : //back button
233 | pathStack.length === 0 ? null : (
234 |
235 | }
241 | _hover={{ bg: 'white', textColor: 'black' }}
242 | onClick={(e) => {
243 | e.stopPropagation();
244 | clickWrapper(handlePrevDir, isDefault);
245 | }}
246 | />
247 |
248 | )}
249 |
250 |
251 |
252 |
260 |
261 |
270 |
271 | {/* node modal */}
272 |
281 |
282 | );
283 | };
284 |
285 | export default Node;
286 |
--------------------------------------------------------------------------------
/webview-react-app/src/components/TreeContainer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import LayoutFlow from "./LayoutFlow";
3 | import { ReactFlowProvider, Controls, MiniMap } from "reactflow";
4 | import Node from "./Node";
5 | // import { handleReceivedMessage, handleRequestDirectory } from "../functions";
6 | import { useVsCodeApi } from "../VsCodeApiContext";
7 |
8 |
9 | export type FileNode = {
10 | id: number;
11 | folderName: string;
12 | parentNode: number | null;
13 | contents: string[];
14 | path: string;
15 | render: string;
16 | };
17 |
18 | export type Tree = FileNode[];
19 |
20 | //---Dumby data to show on initial load---//
21 | const initNodes: Tree = [
22 | {
23 | id: 0,
24 | folderName: "Root",
25 | parentNode: null,
26 | contents: ["globals.css", "layout.js", "page.jsx", "page.module.css"],
27 | path: "",
28 | render: "server",
29 | },
30 | {
31 | id: 1,
32 | folderName: "Child",
33 | parentNode: 0,
34 | contents: ["page.jsx", "page.module.css"],
35 | path: "",
36 | render: "server",
37 | },
38 | {
39 | id: 2,
40 | folderName: "Child",
41 | parentNode: 0,
42 | contents: ["page.jsx", "page.module.css"],
43 | path: "",
44 | render: "server",
45 | },
46 | {
47 | id: 3,
48 | folderName: "Sub-Child",
49 | parentNode: 2,
50 | contents: ["page.jsx", "page.module.css"],
51 | path: "",
52 | render: "server",
53 | },
54 | {
55 | id: 4,
56 | folderName: "Sub-Child",
57 | parentNode: 2,
58 | contents: ["page.jsx", "page.module.css"],
59 | path: "",
60 | render: "client",
61 | },
62 | {
63 | id: 5,
64 | folderName: "Child",
65 | parentNode: 0,
66 | contents: ["loading.jsx", "page.jsx", "page.module.css"],
67 | path: "",
68 | render: "server",
69 | },
70 | ];
71 |
72 | const tutorialTree = JSON.stringify(initNodes);
73 |
74 | //---COMPONENTS---//
75 | export default function TreeContainer() {
76 | const [initialNodes, setInitialNodes] = useState([]);
77 | const [initialEdges, setInitialEdges] = useState([]);
78 | const [isParsed, setIsParsed] = useState(false); //tracks if the parseData function was called
79 | const [directory, setDirectory] = useState(tutorialTree);
80 | const [pathStack, setPathStack] = useState([]);
81 | const [isDefault, setIsDefault] = useState(true);
82 | const vscode = useVsCodeApi();
83 | // TODO create path history array useState for back button, add path on entry subpath. Pop off on back click
84 |
85 | //state for communicating with "backend"
86 | const [srcDir, setSrcDir] = useState("src");
87 | const [appDir, setAppDir] = useState("app");
88 | const srcDirRef = useRef("src");
89 | const appDirRef = useRef("app");
90 |
91 | //state for managing path input
92 | const [validDir, setValidDir] = useState(false);
93 | const [dirFormValue, setDirFormValue] = useState("src/app");
94 |
95 | //update state for making icons clickable
96 | useEffect(() => {
97 | setIsDefault(false);
98 | }, [directory]);
99 |
100 | // Update the refs whenever srcDir or appDir changes
101 | useEffect(() => {
102 | srcDirRef.current = srcDir;
103 | appDirRef.current = appDir;
104 | window.addEventListener("message", (e) =>
105 | handleReceivedMessage(
106 | e,
107 | setDirectory,
108 | srcDirRef.current,
109 | appDirRef.current
110 | )
111 | );
112 | return () => {
113 | window.removeEventListener("message", (e) =>
114 | handleReceivedMessage(
115 | e,
116 | setDirectory,
117 | srcDirRef.current,
118 | appDirRef.current
119 | )
120 | );
121 | };
122 | }, [srcDir, appDir]);
123 |
124 | const handleAddPath = (path: string):void => {
125 | setPathStack([...pathStack, path]);
126 | };
127 |
128 | const handleRemovePath = ():void => {
129 | if(pathStack.length === 0){
130 | console.log("Removing from empty Path");
131 | return;
132 | }
133 | const newPath = [...pathStack];
134 | newPath.pop();
135 | console.log('newPath: ', newPath);
136 | setPathStack(newPath);
137 |
138 | };
139 |
140 | const handleRequestDirectory = (srcDirRef: string, appDirRef: string) => {
141 | //console.log("srcDir: ", srcDirRef, " appDir: ", appDirRef);
142 | console.log("asking for directory");
143 | vscode.postMessage({
144 | command: "getRequest",
145 | src: srcDirRef || "src",
146 | app: appDirRef || "app",
147 | });
148 | };
149 |
150 | const handleReceivedMessage = (
151 | event: MessageEvent,
152 | setDirectory: (state: string) => void,
153 | srcDirRef: string,
154 | appDirRef: string
155 | ) => {
156 | const message = event.data;
157 | switch (message.command) {
158 | //get directory
159 | case "sendString":
160 | console.log("getting directory");
161 | const formattedDirectory = JSON.stringify(
162 | JSON.parse(message.data),
163 | null,
164 | 2
165 | );
166 | if (formattedDirectory.length) {
167 | setDirectory(formattedDirectory);
168 | }
169 | break;
170 | //file was just added we need to get directory again
171 | case "added_addFile":
172 | console.log("file added");
173 | handleRequestDirectory(srcDirRef, appDirRef);
174 | break;
175 | //file was just deleted we need to get directory again
176 | case "added_deleteFile":
177 | console.log("file deleted");
178 | handleRequestDirectory(srcDirRef, appDirRef);
179 | break;
180 | //folder was just added we need to get directory again
181 | case "added_addFolder":
182 | console.log("folder added");
183 | handleRequestDirectory(srcDirRef, appDirRef);
184 | break;
185 | //folder was just deleted we need to get directory again
186 | case "added_deleteFolder":
187 | console.log("folder deleted");
188 | handleRequestDirectory(srcDirRef, appDirRef);
189 | break;
190 | case "submitDirResponse":
191 | console.log("received", message);
192 | setValidDir(message.result);
193 | if (message.result) {
194 | if(message.form) {
195 | setPathStack([]);
196 | }
197 | handleRequestDirectory(srcDirRef, appDirRef);
198 | }
199 | }
200 | };
201 |
202 | const parseData = (serverResponse: Tree) => {
203 | const position = { x: 0, y: 0 };
204 |
205 | const newNodes: any[] = [];
206 | const newEdges: any[] = [];
207 |
208 | //function that sends messages to the extension/backend
209 | //This function is drilled down to the modal components and invoked there
210 | const handlePostMessage: (
211 | filePath: string,
212 | command: string,
213 | setterFunc?: (string: string) => any
214 | ) => void = (filePath, command, setterFunc) => {
215 | vscode.postMessage({
216 | command: command,
217 | filePath: filePath,
218 | });
219 | if (setterFunc) {
220 | setterFunc("");
221 | }
222 | };
223 |
224 |
225 | serverResponse.forEach((obj) => {
226 | //populate the newNodes with the data from the "server"
227 | newNodes.push({
228 | id: `${obj.id}`,
229 | data: {
230 | label: (
231 |
240 | ),
241 | },
242 | position,
243 | });
244 | //create newEdges from the "server" if the current node has a parent
245 | if (obj.parentNode !== null) {
246 | newEdges.push({
247 | id: `${obj.parentNode}_${obj.id}`,
248 | source: `${obj.parentNode}`, //this is the parents id
249 | target: `${obj.id}`,
250 | type: "smoothstep", //determines the line style
251 | animated: true, //displays marching ants
252 | });
253 | }
254 | });
255 |
256 | //update state with new nodes and edges
257 | setInitialNodes(newNodes);
258 | setInitialEdges(newEdges);
259 | setIsParsed(true);
260 | };
261 |
262 | //invoked parseData to show tutorial tree
263 | useEffect(() => {
264 | parseData(JSON.parse(directory));
265 | }, [directory]);
266 |
267 | return (
268 | //if isParsed has not been called, don't display the ReactFlow content:
269 |
270 | {isParsed ? (
271 | //this outer div is required to give the size of the ReactFlow window
272 |
273 | {/* Must Wrap the Layout flow in the ReactFlow component imported from ReactFLOW */}
274 |
275 | {
281 | parseData(JSON.parse(directory));
282 | }}
283 | handleRequestDir={() => {
284 | handleRequestDirectory(srcDir, appDir);
285 | }}
286 | validDir={validDir}
287 | dirFormValue={dirFormValue}
288 | setDirFormValue={setDirFormValue}
289 | />
290 |
291 |
292 | ) : (
293 |
Nothing to Display
294 | )}
295 |
296 | );
297 | }
298 |
--------------------------------------------------------------------------------
/webview-react-app/src/components/modals/DetailsView.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useRef } from 'react';
2 | import { FileNode } from '../TreeContainer';
3 | import { IconType } from 'react-icons';
4 | import {
5 | Modal,
6 | ModalOverlay,
7 | ModalContent,
8 | ModalHeader,
9 | ModalFooter,
10 | ModalBody,
11 | ModalCloseButton,
12 | Popover,
13 | PopoverTrigger,
14 | PopoverContent,
15 | PopoverCloseButton,
16 | PopoverBody,
17 | PopoverArrow,
18 | Portal,
19 | Stack,
20 | Box,
21 | Button,
22 | Icon,
23 | Flex,
24 | IconButton,
25 | FormControl,
26 | Input,
27 | } from '@chakra-ui/react';
28 | import { PiTrashFill, PiFilePlusFill } from 'react-icons/pi';
29 |
30 | import { useVsCodeApi } from '../../VsCodeApiContext';
31 | import ListItem from './ListItem';
32 |
33 | type Props = {
34 | props: FileNode;
35 | handlePostMessage: (filePath: string,
36 | command: string,
37 | setterFunc?: (string: string) => any) => void;
38 | getIcon: (id: string) => [IconType, string];
39 | isOpen: boolean;
40 | onClose: () => void;
41 | clickWrapper: Function,
42 | isDefault: boolean
43 | };
44 | const DetailsView = ({ props, handlePostMessage, getIcon, isOpen, onClose, clickWrapper, isDefault }: Props): JSX.Element => {
45 | let { contents, folderName, path }: FileNode = props;
46 | const vscode = useVsCodeApi();
47 | const ref = useRef(null);
48 | const [addFileValue, setAddFileValue] = useState('');
49 |
50 | if (!contents) {
51 | contents = [];
52 | }
53 | if (!path) {
54 | path = '';
55 | }
56 | const modalFiles: JSX.Element[] = [];
57 | for (let i = 0; i < contents.length; i++) {
58 | const icon = getIcon(contents[i]);
59 | modalFiles.push(
60 |
61 | );
62 | }
63 |
64 | return (
65 |
66 |
70 |
77 | {folderName}
78 |
79 |
80 | {modalFiles}
81 |
82 |
83 | {/* input form */}
84 |
85 | {
94 | if (e.key === 'Enter') {
95 | clickWrapper(handlePostMessage, isDefault, path.concat('/', addFileValue), 'addFile', setAddFileValue);
96 | }
97 | }}
98 | onChange={(e) => {
99 | setAddFileValue(e.currentTarget.value);
100 | }}
101 | value={addFileValue}
102 | />
103 |
104 |
105 | }
115 | onClick={(e) => {
116 | console.log(addFileValue);
117 | clickWrapper(handlePostMessage, isDefault, path.concat('/', addFileValue), 'addFile', setAddFileValue);
118 | }}
119 | />
120 |
121 |
122 |
123 | );
124 | };
125 |
126 | export default DetailsView;
127 |
--------------------------------------------------------------------------------
/webview-react-app/src/components/modals/FolderAdd.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, SetStateAction } from 'react';
2 | import {
3 | Button,
4 | FormControl,
5 | Icon,
6 | IconButton,
7 | Input,
8 | Modal,
9 | ModalOverlay,
10 | ModalContent,
11 | ModalHeader,
12 | ModalFooter,
13 | ModalBody,
14 | ModalCloseButton,
15 | useDisclosure,
16 | } from '@chakra-ui/react';
17 | import { PiFolderNotchPlusFill, PiPlusCircleBold } from 'react-icons/pi';
18 |
19 | type Props = {
20 | path: String;
21 | parentNode: Number | null;
22 | render: string;
23 | clickWrapper: Function;
24 | handlePostMessage: (
25 | filePath: string,
26 | // event:
27 | // | React.MouseEvent
28 | // | React.KeyboardEvent,
29 | command: string,
30 | setterFunc?: (string: string) => any
31 | ) => void;
32 | isDefault: boolean;
33 | };
34 |
35 | const FolderAdd = ({ path, parentNode, render, handlePostMessage, clickWrapper, isDefault }: Props) => {
36 | const OverlayOne = () => (
37 |
41 | );
42 |
43 | const [overlay, setOverlay] = React.useState( );
44 | const [addFolderValue, setAddFolderValue] = useState('');
45 |
46 | const {
47 | isOpen: addIsOpen,
48 | onOpen: addOnOpen,
49 | onClose: addOnClose,
50 | } = useDisclosure();
51 |
52 | const boxShadowColor = render === 'client' ? '#ffcf9e' : '#9FFFCB';
53 |
54 | return (
55 | <>
56 | {
75 | setOverlay( );
76 | addOnOpen();
77 | }}
78 | >
79 |
80 |
81 |
82 |
88 | {overlay}
89 |
96 | Add Folder
97 |
98 |
99 |
100 | {
109 | if (e.key === 'Enter') {
110 | clickWrapper(handlePostMessage, isDefault, path.concat('/', addFolderValue), 'addFolder');
111 | addOnClose();
112 | }
113 | }}
114 | onChange={(e) => {
115 | setAddFolderValue(e.currentTarget.value);
116 | }}
117 | value={addFolderValue}
118 | />
119 | }
129 | onClick={(e) => {
130 | clickWrapper(handlePostMessage, isDefault, path.concat('/', addFolderValue), 'addFolder');
131 | addOnClose();
132 | }}
133 | />
134 |
135 |
136 |
137 |
138 |
139 | >
140 | );
141 | };
142 |
143 | export default FolderAdd;
144 |
--------------------------------------------------------------------------------
/webview-react-app/src/components/modals/FolderDelete.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React, { useState, SetStateAction } from 'react';
3 | import {
4 | Button,
5 | FormControl,
6 | FormErrorMessage,
7 | Icon,
8 | IconButton,
9 | Input,
10 | Modal,
11 | ModalOverlay,
12 | ModalContent,
13 | ModalHeader,
14 | ModalFooter,
15 | ModalBody,
16 | ModalCloseButton,
17 | useDisclosure,
18 | } from '@chakra-ui/react';
19 | import { PiMinusCircleBold } from 'react-icons/pi';
20 |
21 | type Props = {
22 | path: string;
23 | parentNode: Number | null;
24 | folderName: string;
25 | render: string;
26 | clickWrapper: Function;
27 | isDefault: boolean;
28 | handlePostMessage: (
29 | filePath: string,
30 | // event:
31 | // | React.MouseEvent
32 | // | React.KeyboardEvent,
33 | command: string,
34 | setterFunc?: (string: string) => any,
35 |
36 | ) => void;
37 | };
38 |
39 | const FolderDelete = ({
40 | path,
41 | parentNode,
42 | folderName,
43 | render,
44 | handlePostMessage,
45 | clickWrapper,
46 | isDefault
47 | }: Props) => {
48 | const OverlayOne = () => (
49 |
53 | );
54 |
55 | const [overlay, setOverlay] = React.useState( );
56 | const [deleteFolderValue, setDeleteFolderValue] = useState('');
57 |
58 | const {
59 | isOpen: deleteIsOpen,
60 | onOpen: deleteOnOpen,
61 | onClose: deleteOnClose,
62 | } = useDisclosure();
63 | const boxShadowColor = render === 'client' ? '#ffcf9e' : '#9FFFCB';
64 |
65 | return (
66 | <>
67 | {parentNode !== null && (
68 | {
87 | setOverlay( );
88 | deleteOnOpen();
89 | }}
90 | >
91 |
92 |
93 | )}
94 |
95 |
101 | {overlay}
102 |
109 | Delete Folder
110 |
111 |
112 |
116 | {
125 | if (e.key === 'Enter' && deleteFolderValue === folderName) {
126 | clickWrapper(handlePostMessage, isDefault, path, 'deleteFolder', setDeleteFolderValue);
127 | deleteOnClose();
128 | }
129 | }}
130 | onChange={(e) => {
131 | setDeleteFolderValue(e.currentTarget.value);
132 | }}
133 | value={deleteFolderValue}
134 | />
135 |
136 | Input must match folder name
137 |
138 |
139 | {
143 | clickWrapper(handlePostMessage, isDefault, path, 'deleteFolder', setDeleteFolderValue);
144 | deleteOnClose();
145 | }}
146 | >
147 | Confirm
148 |
149 |
150 |
151 |
152 |
153 | >
154 | );
155 | };
156 |
157 | export default FolderDelete;
158 |
--------------------------------------------------------------------------------
/webview-react-app/src/components/modals/ListItem.tsx:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | import { Popover,
4 | PopoverTrigger,
5 | PopoverContent,
6 | PopoverCloseButton,
7 | PopoverBody,
8 | PopoverArrow,
9 | Portal,
10 | Box,
11 | Button,
12 | Flex,
13 | Icon,
14 | IconButton
15 | } from '@chakra-ui/react';
16 |
17 | import { PiTrashFill } from 'react-icons/pi';
18 |
19 | import { FileNode, ListItemProps } from '../../../utils/types';
20 |
21 |
22 | const ListItem = ({props, handlePostMessage, isOpen, onClose, icon, fileName, containerRef}: ListItemProps): JSX.Element => {
23 | let path = props.path ?? '';
24 | let contents = props.contents ?? [];
25 |
26 | const ref = useRef(null);
27 |
28 | return (
29 |
30 | {' '}
31 |
32 | {' '}
33 | }
39 | onClick={() => {
40 | handlePostMessage(path.concat("/", fileName), "open_file");
41 | }}
42 | >
43 | {' '}
44 | {fileName}
45 |
46 | {/* */}
47 |
48 | {({ onClose }) => (
49 | <>
50 |
51 | }
58 | />
59 |
60 |
61 |
69 |
70 |
71 | {
75 | handlePostMessage(path.concat('/', fileName), 'deleteFile');
76 | onClose();
77 | }}
78 | ref={ref}
79 | >
80 |
81 | Confirm
82 |
83 |
84 |
85 |
86 | >
87 | )}
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | export default ListItem;
--------------------------------------------------------------------------------
/webview-react-app/src/functions.ts:
--------------------------------------------------------------------------------
1 | import { useVsCodeApi } from './VsCodeApiContext';
2 |
3 | // //all of the different messages
4 | const vscode = useVsCodeApi();
5 | //get directory
6 | export const handleRequestDirectory = (
7 | srcDirRef: string,
8 | appDirRef: string
9 | ) => {
10 | console.log('srcDir: ', srcDirRef, ' appDir: ', appDirRef);
11 | vscode.postMessage({
12 | command: 'getRequest',
13 | src: srcDirRef || 'src',
14 | app: appDirRef || 'app',
15 | });
16 | };
17 |
18 | export const handleReceivedMessage = (
19 | event: MessageEvent,
20 | setDirectory: (state: string) => void,
21 | srcDirRef: string,
22 | appDirRef: string
23 | ) => {
24 | const message = event.data;
25 | switch (message.command) {
26 | //get directory
27 | case 'sendString':
28 | const formattedDirectory = JSON.stringify(
29 | JSON.parse(message.data),
30 | null,
31 | 2
32 | );
33 | setDirectory(formattedDirectory);
34 | break;
35 | //file was just added we need to get directory again
36 | case 'added_addFile':
37 | console.log('file added');
38 | handleRequestDirectory(srcDirRef, appDirRef);
39 | break;
40 | //file was just deleted we need to get directory again
41 | case 'added_deleteFile':
42 | console.log('file deleted');
43 | handleRequestDirectory(srcDirRef, appDirRef);
44 | break;
45 | }
46 | };
47 |
48 | //open a tab
49 | export const handleOpenFile = (filePath: string) => {
50 | vscode.postMessage({
51 | command: 'open_file',
52 | filePath: filePath,
53 | });
54 | };
55 |
56 | //add a file need path and filename.extension
57 | export const handleAddFile = (filePath: string, addFileName: string) => {
58 | console.log(filePath);
59 | vscode.postMessage({
60 | command: 'addFile',
61 | path: filePath,
62 | fileName: addFileName,
63 | });
64 | };
65 |
66 | //delete a file need path and filename.extension
67 | export const handleDeleteFile = (filePath: string, deleteFileName: string) => {
68 | vscode.postMessage({
69 | command: 'deleteFile',
70 | path: filePath,
71 | fileName: deleteFileName,
72 | });
73 | };
74 |
--------------------------------------------------------------------------------
/webview-react-app/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import { VsCodeApiProvider } from "./VsCodeApiContext"; // Make sure you import it from the correct file
5 |
6 | //wrap app in VsCodeApiProvider component
7 | ReactDOM.render(
8 |
9 |
10 |
11 |
12 | ,
13 | document.getElementById("root")
14 | );
15 |
--------------------------------------------------------------------------------
/webview-react-app/style.css:
--------------------------------------------------------------------------------
1 | /*REQUIRED TO AVOID WHITE BAR. DO NOT REMOVE*/
2 | body {
3 | /*There is a padding setting applied to the body from vscode. This setting prevents that*/
4 | padding: unset
5 | }
6 |
7 | div button {
8 | /*ensures that all or most of the next is white by default*/
9 | color: white
10 | }
11 | .react-flow__node {
12 | border: none;
13 | }
14 |
15 | .react-flow__node:focus {
16 | border: none;
17 | }
18 |
19 | .react-flow__edge-path {
20 | stroke-width: 5;
21 | stroke: rgb(255, 255, 255);
22 | }
23 |
24 |
25 | /*Styling for the ReactFlow attribution*/
26 | .react-flow__attribution{
27 | border-top-left-radius: 7px ;
28 | background-color: rgb(40, 33, 33);
29 | }
30 |
31 | .react-flow__handle {
32 | background-color: #050505;
33 | z-index: -1000000000000;
34 | }
35 |
36 | .react-flow__node-default.selected {
37 | border: unset
38 | }
--------------------------------------------------------------------------------
/webview-react-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "ESNext",
5 | "moduleResolution": "node",
6 | "esModuleInterop": true,
7 | "jsx": "react-jsx",
8 | "rootDir": "./",
9 | "outDir": "./dist",
10 | "strict": true,
11 | "lib": ["ES2020", "dom"]
12 | },
13 | "include": ["./src/**/*.tsx", "./src/**/*.ts"],
14 | "exclude": ["node_modules"]
15 | }
16 |
--------------------------------------------------------------------------------
/webview-react-app/utils/types.ts:
--------------------------------------------------------------------------------
1 | import { IconType } from 'react-icons';
2 |
3 | export type FileNode = {
4 | id: number;
5 | folderName: string;
6 | parentNode: number | null;
7 | contents?: string[];
8 | path?: string;
9 | render?: string;
10 | };
11 |
12 | export type Tree = FileNode[];
13 |
14 | export type NodeProps = {
15 | props: FileNode;
16 | pathStack?: Array;
17 | rootPath?: string;
18 | isOpen: boolean;
19 | icon?: [IconType, string];
20 | handlePostMessage: (
21 | filePath: string,
22 | command: string,
23 | setterFunc?: (string: string) => any
24 | ) => void;
25 | onPathChange?: (string: string) => void;
26 | getIcon?: (id: string) => [IconType, string];
27 | onRemovePath?: () => void;
28 | onClose: () => void;
29 | };
30 |
31 | export type ListItemProps = {
32 | props: FileNode;
33 | isOpen: boolean;
34 | icon: IconType;
35 | fileName: string;
36 | containerRef: React.RefObject;
37 | handlePostMessage: (
38 | filePath: string,
39 | command: string,
40 | setterFunc?: (string: string) => any
41 | ) => void;
42 | onClose: () => void;
43 | };
--------------------------------------------------------------------------------
/webview-react-app/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/naming-convention */
2 | import path from 'path';
3 | import { fileURLToPath } from 'url';
4 | import HtmlWebpackPlugin from 'html-webpack-plugin';
5 |
6 | const __filename = fileURLToPath(import.meta.url);
7 | const __dirname = path.dirname(__filename);
8 |
9 | export default {
10 | entry: './src/index.tsx',
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.resolve(__dirname, 'dist'),
14 | },
15 | plugins: [
16 | new HtmlWebpackPlugin({
17 | template: './index.html',
18 | }),
19 | ],
20 | //devServer for development mode in the browser
21 | devServer: {
22 | host: 'localhost',
23 | port: 8080,
24 | hot: true,
25 | },
26 | resolve: {
27 | extensions: ['.ts', '.tsx', '.js', '.jsx'],
28 | },
29 | module: {
30 | rules: [
31 | { test: /\.tsx?$/, loader: 'ts-loader' },
32 | { test: /\.css$/, use: ['style-loader', 'css-loader'] },
33 | ],
34 | },
35 | };
36 |
--------------------------------------------------------------------------------