├── .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 | next.nav logo 3 |

4 | 5 |
6 | 7 | ![Version: 1.1.1](https://img.shields.io/badge/version-1.1.1-black) 8 | ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) 9 | 10 |
11 | 12 |
13 | 14 | ![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) 15 | ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) 16 | ![React](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) 17 | ![Webpack](https://img.shields.io/badge/webpack-%238DD6F9.svg?style=for-the-badge&logo=webpack&logoColor=black) 18 | ![Git](https://img.shields.io/badge/git-%23F05033.svg?style=for-the-badge&logo=git&logoColor=white) 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 | ![Launching the Extension](https://i.imgur.com/JB0a26c.gif "Launching the Extension") 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 | ![Opening a Tree](https://i.imgur.com/EMVb6w9.gif "Opening a Tree") 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 | ![Route Traversal](https://i.imgur.com/tfJXtFD.gif "Route Traversal") 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 | ![Opening Files](https://i.imgur.com/LLZQi6V.gif "Opening Files") 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 | ![Adding Files](https://i.imgur.com/MTga1G1.gif "Adding Files") 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 | ![Deleting Files](https://i.imgur.com/rZi5r9q.gif "Deleting Files") 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 | ![Adding Folders](https://i.imgur.com/DSLN7aL.gif "Adding Folders") 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 | ![Deleting Folders](https://i.imgur.com/WZtWYx4.gif "Deleting Folders") 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 | 135 |
136 | 137 |
1.1.1 138 | 143 |
144 | 145 | ### 1.0.0 - Initial release of Next.Nav 146 | 147 |
1.0.2 148 | 154 |
155 | 156 |
1.0.3 157 | 161 |
162 | 163 |
1.0.4 164 | 168 |
169 |
1.0.5 170 | 174 |
175 | 176 | ## Contributors 177 | 178 | 179 | 180 | 188 | 196 | 211 | 219 | 220 |
181 | a photo of Anatoliy Sokolov 182 |
183 | Anatoliy Sokolov 184 |
185 | Linkedin | 186 | GitHub 187 |
189 | a photo of Brian Henkel 190 |
191 | Brian Henkel 192 |
193 | Linkedin | 194 | GitHub 195 |
197 | a photo of Jordan Querubin 198 |
199 | Jordan Querubin 200 |
201 | Linkedin | 202 | GitHub 203 |
204 | a photo of Nathan Peel 205 |
206 | Nathan Peel 207 |
208 | Linkedin | 209 | GitHub 210 |
212 | a photo of Darren Pavel 213 |
214 | Darren Pavel 215 |
216 | Linkedin | 217 | GitHub 218 |
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 | 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 | 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 | 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 | 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 | 46 | {/* */} 47 | 48 | {({ onClose }) => ( 49 | <> 50 | 51 | } 58 | /> 59 | 60 | 61 | 69 | 70 | 71 | 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 | --------------------------------------------------------------------------------