├── .gitignore ├── assets ├── Demo.gif ├── logo.png ├── InstallVisiVue.gif └── CommandPalleteDemo.gif ├── src ├── webview │ ├── main.js │ └── components │ │ ├── ColorKey.vue │ │ ├── initial-elements.js │ │ ├── NodeTemplate.vue │ │ ├── FileImport.vue │ │ ├── elements.js │ │ └── App.vue ├── types │ └── Tree.ts ├── extension.ts ├── panel.ts └── parser.ts ├── .vscode ├── extensions.json ├── settings.json ├── tasks.json └── launch.json ├── .vscodeignore ├── CHANGELOG.md ├── .eslintrc.json ├── vueCompilerTypes.txt ├── tsconfig.json ├── LICENSE ├── webpack.config.ts ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out -------------------------------------------------------------------------------- /assets/Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/VisiVue/HEAD/assets/Demo.gif -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/VisiVue/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/InstallVisiVue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/VisiVue/HEAD/assets/InstallVisiVue.gif -------------------------------------------------------------------------------- /assets/CommandPalleteDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/VisiVue/HEAD/assets/CommandPalleteDemo.gif -------------------------------------------------------------------------------- /src/webview/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | 3 | import App from "./components/App"; 4 | createApp(App).mount("#app"); 5 | 6 | // import FileImport from "./components/FileImport"; 7 | // createApp(FileImport).mount("#app"); -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | webpack.config.js 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/.eslintrc.json 12 | **/*.map 13 | **/*.ts 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "extension" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /src/types/Tree.ts: -------------------------------------------------------------------------------- 1 | export type Tree = { 2 | id: string; 3 | name: string; 4 | fileName: string; 5 | filePath: string; 6 | fileDirname: string; 7 | importPath: string; 8 | children: {}[]; 9 | parentList: string[]; 10 | props: { 11 | oneWay: string[]; 12 | twoWay: string[]; 13 | }; 14 | error: string; 15 | }; -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off" 13 | } -------------------------------------------------------------------------------- /vueCompilerTypes.txt: -------------------------------------------------------------------------------- 1 | type === 1: Represents an element node. These nodes correspond to HTML elements in the template. 2 | type === 2: Represents a text node. These nodes contain static text or interpolated expressions within double curly braces ({{ }}). 3 | type === 3: Represents a text node. These nodes contain static text content. 4 | type === 4: Represents a comment node. These nodes represent HTML comments in the template. 5 | type === 5: Represents an interpolation node. These nodes contain dynamic expressions within double curly braces ({{ }}). 6 | type === 6: Represents an attribute node. These nodes contain static attributes like class, id, or other HTML attributes. 7 | type === 7: Represents a directive node. These nodes correspond to directives like v-bind, v-on, or v-model. -------------------------------------------------------------------------------- /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 | "allowJs": true, 12 | "strict": false, /* enable all strict type-checking options */ 13 | /* Additional Checks */ 14 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 15 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 16 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 17 | }, 18 | "include": ["src"], 19 | "exclude" : [ 20 | "node_modules", 21 | ".vscode-test", 22 | "src/webviews", 23 | "test" 24 | 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import Panel from "./panel"; 3 | 4 | export function activate(context: vscode.ExtensionContext) { 5 | let disposable = vscode.commands.registerCommand( 6 | "extension.showPanels", 7 | () => { 8 | // @ts-ignore 9 | Panel.createOrShow(context); 10 | } 11 | ); 12 | 13 | context.subscriptions.push(disposable); 14 | 15 | // Create VisiVue status bar button in the bottom right of vscode 16 | const item = vscode.window.createStatusBarItem( 17 | vscode.StatusBarAlignment.Right 18 | ); 19 | 20 | item.command = "extension.showPanels"; 21 | item.tooltip = "Activate VisiVue Extension"; 22 | item.text = "$(type-hierarchy) Launch VisiVue"; 23 | item.show(); 24 | } 25 | export function deactivate() {} 26 | -------------------------------------------------------------------------------- /.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": "$ts-webpack-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never", 13 | "group": "watchers" 14 | }, 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | } 19 | }, 20 | { 21 | "type": "npm", 22 | "script": "watch-tests", 23 | "problemMatcher": "$tsc-watch", 24 | "isBackground": true, 25 | "presentation": { 26 | "reveal": "never", 27 | "group": "watchers" 28 | }, 29 | "group": "build" 30 | }, 31 | { 32 | "label": "tasks: watch-tests", 33 | "dependsOn": [ 34 | "npm: watch", 35 | "npm: watch-tests" 36 | ], 37 | "problemMatcher": [] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.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}/dist/**/*.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/**/*.js", 30 | "${workspaceFolder}/dist/**/*.js" 31 | ], 32 | "preLaunchTask": "tasks: watch-tests" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as path from "path"; 3 | import * as webpack from "webpack"; 4 | import { VueLoaderPlugin } from "vue-loader"; 5 | 6 | const extConfig: webpack.Configuration = { 7 | mode: "development", 8 | target: "node", 9 | entry: "./src/extension.ts", 10 | output: { 11 | filename: "extension.js", 12 | libraryTarget: "commonjs2", 13 | path: path.resolve(__dirname, "out"), 14 | }, 15 | resolve: { extensions: [".ts", ".js", ".vue"] }, 16 | module: { 17 | rules: [ 18 | { test: /\.ts$/, loader: "ts-loader" }, 19 | // { test: /\.vue$/, loader: "vue-loader" }, 20 | ], 21 | }, 22 | externals: { vscode: "vscode" }, 23 | // plugins: [new VueLoaderPlugin()], 24 | }; 25 | 26 | const webviewConfig: webpack.Configuration = { 27 | mode: "development", 28 | target: "web", 29 | entry: "./src/webview/main.js", // ENTRY FILE: CHANGE FOR VUE 30 | output: { 31 | filename: "[name].wv.js", 32 | path: path.resolve(__dirname, "out"), 33 | }, 34 | resolve: { 35 | extensions: [".js", ".ts", ".tsx", ".scss", ".vue"], 36 | }, 37 | module: { 38 | rules: [ 39 | { test: /\.tsx?$/, use: ["babel-loader", "ts-loader"] }, 40 | { test: /\.vue$/, loader: "vue-loader" }, 41 | { 42 | test: /\.css$/, 43 | use: ["style-loader", "css-loader"], 44 | }, 45 | ], 46 | }, 47 | plugins: [new VueLoaderPlugin()], 48 | }; 49 | 50 | export default [webviewConfig, extConfig]; -------------------------------------------------------------------------------- /src/webview/components/ColorKey.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/webview/components/initial-elements.js: -------------------------------------------------------------------------------- 1 | // import { MarkerType } from "@vue-flow/core"; 2 | 3 | 4 | /** 5 | * You can pass elements together as a v-model value 6 | * or split them up into nodes and edges and pass them to the `nodes` and `edges` props of Vue Flow (or useVueFlow composable) 7 | */ 8 | 9 | // need to figure out how to dynamically create the 'x' and 'y' positions based on the node's position in the tree 10 | 11 | export const initialElements = [ 12 | { 13 | id: "1", 14 | type: "input", 15 | label: "monday.js", 16 | position: { x: 250, y: 0 }, 17 | class: "light", 18 | parentId: 0, 19 | }, 20 | { 21 | id: "2", 22 | // type: "output", // specifies handles. 'default' or none, is two handles (top and bottom). 'input' has a bottom handle. 'output' has a top handle 23 | label: "App.vue", 24 | position: { x: 250, y: 100 }, 25 | class: "light", 26 | parentId: "1", 27 | }, 28 | { 29 | id: "3", 30 | label: "MainContainer.vue", 31 | position: { x: 250, y: 200 }, 32 | class: "light", 33 | parentId: "2", 34 | }, 35 | { 36 | id: "4", 37 | label: "MainDisplay.vue", 38 | position: { x: 150, y: 300 }, 39 | class: "light", 40 | parentId: "3", 41 | }, 42 | { 43 | id: "5", 44 | type: "output", 45 | label: "Content.vue", 46 | position: { x: 350, y: 300 }, 47 | class: "light", 48 | parentId: "3", 49 | }, 50 | { 51 | id: "e1-2", 52 | source: "1", 53 | target: "2", 54 | // animated: false 55 | }, 56 | { 57 | id: "e2-3", 58 | type: "smoothstep", 59 | label: "one-way", 60 | source: "2", 61 | target: "3", 62 | animated: false, 63 | // markerEnd: MarkerType.ArrowClosed, 64 | }, 65 | { 66 | id: "e3-4", 67 | type: "smoothstep", 68 | label: "smoothstep-edge", 69 | source: "3", 70 | target: "4", 71 | }, 72 | { 73 | id: "e3-5", 74 | type: "step", 75 | label: "step-edge", 76 | source: "3", 77 | target: "5", 78 | style: { stroke: "orange" }, 79 | labelBgStyle: { fill: "orange" }, 80 | }, 81 | ]; 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "extension", 3 | "displayName": "extension", 4 | "description": "", 5 | "version": "0.0.1", 6 | "engines": { 7 | "vscode": "^1.79.0" 8 | }, 9 | "categories": [ 10 | "Other" 11 | ], 12 | "activationEvents": [], 13 | "main": "./out/extension.js", 14 | "contributes": { 15 | "commands": [ 16 | { 17 | "command": "extension.showPanels", 18 | "title": "Show VisiVue Panel" 19 | }, 20 | { 21 | "command": "extension.testPanel", 22 | "title": "Test Panel" 23 | } 24 | ] 25 | }, 26 | "scripts": { 27 | "vscode:prepublish": "npm run compile", 28 | "compile": "webpack --mode production --config webpack.config.ts", 29 | "watch": "webpack --watch", 30 | "package": "webpack --mode production --devtool hidden-source-map", 31 | "compile-tests": "tsc -p . --outDir out", 32 | "watch-tests": "tsc -p . -w --outDir out", 33 | "pretest": "npm run compile-tests && npm run compile && npm run lint", 34 | "lint": "eslint src --ext ts", 35 | "test": "node ./out/test/runTest.js" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.22.6", 39 | "@types/glob": "^8.1.0", 40 | "@types/mocha": "^10.0.1", 41 | "@types/node": "20.2.5", 42 | "@types/vscode": "^1.79.0", 43 | "@typescript-eslint/eslint-plugin": "^5.59.8", 44 | "@typescript-eslint/parser": "^5.59.8", 45 | "@vscode/test-electron": "^2.3.2", 46 | "eslint": "^8.41.0", 47 | "glob": "^8.1.0", 48 | "mocha": "^10.2.0", 49 | "ts-loader": "^9.4.3", 50 | "typescript": "^5.1.5", 51 | "vue-template-babel-compiler": "^2.0.0", 52 | "webpack": "^5.85.0", 53 | "webpack-cli": "^5.1.1" 54 | }, 55 | "dependencies": { 56 | "@babel/preset-env": "^7.22.5", 57 | "@vue-flow/background": "^1.2.0", 58 | "@vue-flow/controls": "^1.1.0", 59 | "@vue-flow/core": "^1.20.2", 60 | "@vue-flow/minimap": "^1.1.1", 61 | "@vue/compiler-sfc": "^3.3.4", 62 | "babel": "^6.23.0", 63 | "babel-loader": "^9.1.2", 64 | "css-loader": "^6.8.1", 65 | "os-dns-native": "^1.2.0", 66 | "sass-loader": "^13.3.2", 67 | "style-loader": "^3.3.3", 68 | "ts-node": "^10.9.1", 69 | "vscode": "^1.1.37", 70 | "vue": "3.3.4", 71 | "vue-flow": "^0.3.0", 72 | "vue-loader": "^17.2.2", 73 | "vue-template-compiler": "2.7.14" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/webview/components/NodeTemplate.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 50 | 51 | -------------------------------------------------------------------------------- /src/webview/components/FileImport.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | -------------------------------------------------------------------------------- /src/panel.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Parser } from './parser'; 3 | 4 | export default class Panel { 5 | public static currentPanel: Panel | undefined; 6 | 7 | private static readonly viewType = 'VisiVue'; 8 | 9 | private readonly _panel: vscode.WebviewPanel; 10 | private readonly _extensionUri: vscode.Uri; 11 | private readonly _context: vscode.ExtensionContext; 12 | private _disposables: vscode.Disposable[] = []; 13 | private parser: Parser | undefined; 14 | 15 | // Method to see if a panel already exists. If not, create a new panel with the 'context' passed into it in the extension.ts file 16 | public static createOrShow(context: vscode.ExtensionContext) { 17 | const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; 18 | if (Panel.currentPanel){ 19 | Panel.currentPanel._panel.reveal(column); 20 | } else { 21 | Panel.currentPanel = new Panel(context, vscode.ViewColumn.Two); 22 | } 23 | } 24 | 25 | private constructor(_context: vscode.ExtensionContext, column: vscode.ViewColumn) { 26 | this._context = _context; 27 | this._extensionUri = _context.extensionUri; 28 | 29 | // Create and show a new webview panel 30 | this._panel = vscode.window.createWebviewPanel(Panel.viewType, "VisiVueTree", column, { 31 | enableScripts: true, 32 | retainContextWhenHidden: true, 33 | localResourceRoots: [this._extensionUri], 34 | }); 35 | 36 | // Set the webview's initial html content 37 | this._panel.webview.html = this._getHtmlForWebview(this._panel.webview); 38 | 39 | //Listen for when the panel gets disposed 40 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 41 | this._panel.webview.onDidReceiveMessage( 42 | async (msg: any) => { 43 | switch (msg.type) { 44 | case 'onFile': 45 | if (msg.value){ 46 | // connects us to parser file 47 | this.parser = new Parser(msg.value); 48 | this.parser.entryFileParse(); 49 | this.updateView(); 50 | } 51 | break; 52 | // For the implementation of a button on each individual node that will redirect the user to the source code 53 | // of the given component that is clicked on. For this to work, must post a message with the type being 'onViewFile' 54 | // so that this case gets hit. 55 | case 'onViewFile': 56 | if (!msg.value) { 57 | return; 58 | }; 59 | const doc = await vscode.workspace.openTextDocument(msg.value); 60 | const editor = await vscode.window.showTextDocument(doc, { 61 | preserveFocus: false, 62 | preview: false 63 | }); 64 | break; 65 | } 66 | }, 67 | null, 68 | this._disposables 69 | ); 70 | } 71 | 72 | // Whenever a new file is uploaded, it is parsed. We retrieve the tree and post it to the front-end 73 | private async updateView() { 74 | const tree = this.parser!.getTree(); 75 | this._context.workspaceState.update('VisiVueTree', tree); 76 | this._panel.webview.postMessage({ 77 | type: 'parsed-data', 78 | value: tree, 79 | settings: await vscode.workspace.getConfiguration('VisiVueTree') 80 | }); 81 | } 82 | 83 | // Method to clear disposables array and 'clean' the data 84 | public dispose() { 85 | Panel.currentPanel = undefined; 86 | this._panel.dispose(); 87 | while (this._disposables.length) { 88 | const node = this._disposables.pop(); 89 | if (node) { 90 | node.dispose(); 91 | } 92 | } 93 | } 94 | private _getHtmlForWebview(webview: vscode.Webview) { 95 | // @ts-ignore 96 | const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'out', 'main.wv.js')); 97 | return ` 98 | 99 | 100 | 101 | 102 | 103 | Panel Title 104 | 105 | 106 |
107 | 114 | 115 | 116 | 117 | `; 118 | } 119 | } -------------------------------------------------------------------------------- /src/webview/components/elements.js: -------------------------------------------------------------------------------- 1 | 2 | export const tree = { 3 | children: [{ 4 | children: [ 5 | { 6 | children: [ 7 | { 8 | children: [], 9 | count: 1, 10 | depth: 0, 11 | error: "", 12 | expanded: false, 13 | fileName: "GrandBaby One-A", 14 | filePath: 15 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/components/Sidebar.tsx", 16 | id: "FA6sF8JbqMloijRT6QOxHh3i5p1v29bJ", 17 | importPath: "/", 18 | name: "GrandBaby One-A", 19 | parentList: [ 20 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 21 | ], 22 | props: { 23 | oneWay: [], 24 | twoWay: [], 25 | }, 26 | reactRouter: false, 27 | reduxConnect: false, 28 | thirdParty: false, 29 | // [[Prototype]]: Object, 30 | }, 31 | { 32 | children: [ 33 | { 34 | children: [], 35 | count: 1, 36 | depth: 0, 37 | error: "", 38 | expanded: false, 39 | fileName: "G-G-Baby 1", 40 | filePath: 41 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/components/Sidebar.tsx", 42 | id: "FA6sF8JbqMloijRT6QOxHh3i5p1v29bJ", 43 | importPath: "/", 44 | name: "G-G-Baby 1", 45 | parentList: [ 46 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 47 | ], 48 | props: { 49 | oneWay: ['p-3'], 50 | twoWay: [], 51 | }, 52 | reactRouter: false, 53 | reduxConnect: false, 54 | thirdParty: false, 55 | // [[Prototype]]: Object, 56 | }, 57 | ], 58 | count: 1, 59 | depth: 0, 60 | error: "", 61 | expanded: false, 62 | fileName: "GrandBaby One-B", 63 | filePath: 64 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/components/Sidebar.tsx", 65 | id: "FA6sF8JbqMloijRT6QOxHh3i5p1v29bJ", 66 | importPath: "/", 67 | name: "GrandBaby One-B", 68 | parentList: [ 69 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 70 | ], 71 | props: { 72 | oneWay: ['p-3'], 73 | twoWay: ['p-2'], 74 | }, 75 | reactRouter: false, 76 | reduxConnect: false, 77 | thirdParty: false, 78 | // [[Prototype]]: Object, 79 | }, 80 | ], 81 | count: 1, 82 | depth: 0, 83 | error: "", 84 | expanded: false, 85 | fileName: "Child One", 86 | filePath: 87 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/components/Sidebar.tsx", 88 | id: "FA6sF8JbqMloijRT6QOxHh3i5p1v29bJ", 89 | importPath: "/", 90 | name: "Child One", 91 | parentList: [ 92 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 93 | ], 94 | props: { 95 | oneWay: ['p-1'], 96 | twoWay: ['p-2'], 97 | }, 98 | reactRouter: false, 99 | reduxConnect: false, 100 | thirdParty: false, 101 | // [[Prototype]]: Object, 102 | }, 103 | { 104 | children: [{ 105 | children: [], 106 | count: 1, 107 | depth: 0, 108 | error: "", 109 | expanded: false, 110 | fileName: "GrandBaby One-C", 111 | filePath: 112 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/components/Sidebar.tsx", 113 | id: "FA6sF8JbqMloijRT6QOxHh3i5p1v29bJ", 114 | importPath: "/", 115 | name: "GrandBaby One-C", 116 | parentList: [ 117 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 118 | ], 119 | props: { 120 | oneWay: [], 121 | twoWay: [], 122 | }, 123 | reactRouter: false, 124 | reduxConnect: false, 125 | thirdParty: false, 126 | // [[Prototype]]: Object, 127 | }, 128 | { 129 | children: [ 130 | { 131 | children: [], 132 | count: 1, 133 | depth: 0, 134 | error: "", 135 | expanded: false, 136 | fileName: "G-G-Baby 2", 137 | filePath: 138 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/components/Sidebar.tsx", 139 | id: "FA6sF8JbqMloijRT6QOxHh3i5p1v29bJ", 140 | importPath: "/", 141 | name: "G-G-Baby 2", 142 | parentList: [ 143 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 144 | ], 145 | props: { 146 | oneWay: ['p-3'], 147 | twoWay: [], 148 | }, 149 | reactRouter: false, 150 | reduxConnect: false, 151 | thirdParty: false, 152 | // [[Prototype]]: Object, 153 | }, 154 | ], 155 | count: 1, 156 | depth: 0, 157 | error: "", 158 | expanded: false, 159 | fileName: "GrandBaby One-D", 160 | filePath: 161 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/components/Sidebar.tsx", 162 | id: "FA6sF8JbqMloijRT6QOxHh3i5p1v29bJ", 163 | importPath: "/", 164 | name: "GrandBaby One-D", 165 | parentList: [ 166 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 167 | ], 168 | props: { 169 | oneWay: ['p-3'], 170 | twoWay: ['p-2'], 171 | }, 172 | reactRouter: false, 173 | reduxConnect: false, 174 | thirdParty: false, 175 | // [[Prototype]]: Object, 176 | }, 177 | ], 178 | count: 1, 179 | depth: 0, 180 | error: "", 181 | expanded: false, 182 | fileName: "Child Two", 183 | filePath: 184 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/components/Sidebar.tsx", 185 | id: "FA6sF8JbqMloijRT6QOxHh3i5p1v29bJ", 186 | importPath: "/", 187 | name: "Child Two", 188 | parentList: [ 189 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 190 | ], 191 | props: { 192 | oneWay: [], 193 | twoWay: ['p-4'], 194 | }, 195 | reactRouter: false, 196 | reduxConnect: false, 197 | thirdParty: false, 198 | // [[Prototype]]: Object, 199 | }, 200 | ], 201 | count: 1, 202 | depth: 0, 203 | error: "", 204 | expanded: false, 205 | fileName: "Parent", 206 | filePath: 207 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 208 | id: "twJWgoTMrb7UkXQI8tC1iGPX9VpfK50I", 209 | importPath: "/", 210 | name: "Parent", 211 | parentList: [], 212 | props: { 213 | oneWay: [], 214 | twoWay: [], 215 | }, 216 | reactRouter: false, 217 | reduxConnect: false, 218 | thirdParty: false, 219 | // [[Prototype]]: Object, 220 | }], 221 | count: 1, 222 | depth: 0, 223 | error: "", 224 | expanded: false, 225 | fileName: "Index.tsx", 226 | filePath: 227 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/components/Sidebar.tsx", 228 | id: "FA6sF8JbqMloijRT6QOxHh3i5p1v29bJ", 229 | importPath: "/", 230 | name: "Index.tsx", 231 | parentList: [ 232 | "/Users/abehenderson/Documents/Codesmith/ReacTree/src/webview/index.tsx", 233 | ], 234 | props: { 235 | oneWay: [], 236 | twoWay: [], 237 | }, 238 | reactRouter: false, 239 | reduxConnect: false, 240 | thirdParty: false, 241 | // [[Prototype]]: Object, 242 | }; 243 | 244 | 245 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { Tree } from './types/Tree'; 3 | import { readFileSync } from 'fs'; 4 | import * as vueCompiler from '@vue/compiler-sfc'; 5 | import { parse, transform } from '@vue/compiler-dom'; 6 | 7 | export class Parser { 8 | entryFile: string; 9 | tree: Tree | undefined; 10 | constructor(filePath: string) { 11 | // default for mac users 12 | this.entryFile = filePath; 13 | // conditionals checking if OS is windows 14 | // check to see if it is a windows platform 15 | if (process.platform === 'linux' && this.entryFile.includes('wsl$')){ 16 | // string manipulation to make sure the entryFile matches what we need for when we reference it in the 17 | // root definition in the parse method 18 | this.entryFile = path.resolve(filePath.split(path.win32.sep).join(path.posix.sep)); 19 | this.entryFile = '/' + this.entryFile.split('/').slice(3).join('/'); 20 | } else if (process.platform === 'linux' && /[a-zA-Z]/.test(this.entryFile[0])) { 21 | const root = `/mnt/${this.entryFile[0].toLowerCase()}`; 22 | this.entryFile = path.join(root, filePath.split(path.win32.sep).slice(1).join(path.posix.sep)); 23 | } 24 | // cleaning this.tree and re-setting it to undefined 25 | this.tree = undefined; 26 | } 27 | // This is called in panel.ts to initiate the building of the AST. parser() is then called to initiate iteration over 28 | // the user's component hierarchy to build out the AST that we will eventually send to App.vue where they will modify 29 | // such that the tree is compatible with Vue Flow. 30 | public entryFileParse() { 31 | const root = { 32 | id: '1', 33 | name: path.basename(this.entryFile).replace(/\.vue?$/, ''), 34 | fileName: path.basename(this.entryFile), 35 | filePath: this.entryFile, 36 | fileDirname: path.dirname(this.entryFile), 37 | importPath: '/', 38 | parentList: [], 39 | children: [], 40 | props: { 41 | oneWay: [], 42 | twoWay: [] 43 | }, 44 | error: '' 45 | }; 46 | this.tree = root; 47 | // store AST that parser function creates one large AST (One object with nested children) to send to Vue Flow 48 | this.parser(this.tree); 49 | return this.tree; 50 | } 51 | 52 | // DON'T FORGET TO CHANGE TYPES LATER AFTER TESTING IS DONE 53 | private parser(root: Tree): void { 54 | let queue: (Tree | string)[] = [root]; 55 | let id = root.id; 56 | // iterate through tree 57 | while(queue.length !== 0) { 58 | let curr: any = queue.shift(); 59 | // console.log(curr.name) 60 | // A check to see if the current node shifted out of the queue is one we want to iterate over or not. 61 | if (curr === 'dead') {continue;} 62 | let sourceCode: string = readFileSync(path.resolve(curr.filePath)).toString(); 63 | // getChildren() is a helper function. Check below in the code for more details. 64 | const arrOfChildren = this.getChildren(sourceCode, curr.fileName, id); 65 | // getImports() is a helper function. Check below in the code for more details. 66 | const imports = this.getImports(sourceCode, curr.fileName, id); 67 | // Iterate through array of child components and instantiate a new ChildNode object. 68 | // Because App.vue is expecting to receive a nested object, 69 | // we are building a single object with children components nested 70 | for (let i = 0; i < arrOfChildren.length; i++) { 71 | let goodToCreateNode = false; 72 | // extractVariabes is a helper function. Check below in the code for more details. 73 | const objOfVariables = this.extractVariables(sourceCode, arrOfChildren[i]); 74 | id = `${+id + 1}`; 75 | let filePath = curr.fileDirname; 76 | for (let j = 0; j < imports.length; j++) { 77 | if (imports[j].local === arrOfChildren[i]) { 78 | // Check to see if the source of the import for the given file is from a user created file or from Vue. 79 | if (imports[j].source[0] !== '@') { 80 | if (imports[j].source.includes('.vue')) { 81 | filePath += imports[j].source.slice(1); 82 | } else { 83 | filePath += imports[j].source.slice(1) + '.vue'; 84 | } 85 | goodToCreateNode = true; 86 | break; 87 | } 88 | } 89 | } 90 | 91 | if (goodToCreateNode) { 92 | const newFileDirname = path.dirname(filePath); 93 | const childSourceCode = readFileSync(path.resolve(filePath)).toString(); 94 | // Handles edge cases for components that have no script tag, but have template tags (icons, etc.) 95 | if (!childSourceCode.includes('script' || 'script setup')) {break;}; 96 | const childNode = { 97 | id: id, 98 | name: arrOfChildren[i], 99 | fileName: `${arrOfChildren[i]}.vue`, 100 | filePath: filePath, 101 | fileDirname: newFileDirname, 102 | importPath: '/', 103 | parentList: [], 104 | children: [], 105 | props: { 106 | oneWay: [], 107 | twoWay: [] 108 | }, 109 | error: '' 110 | }; 111 | // Access object that stores oneway and twoway data bound variables and push to proper arrays of the newly created node. 112 | objOfVariables.twoway.forEach(el => { 113 | childNode.props.twoWay.push(el); 114 | }); 115 | objOfVariables.oneway.forEach(el => { 116 | childNode.props.oneWay.push(el); 117 | }); 118 | // Here is where the tree is being built out by populating the 'children' array. 119 | curr.children.push(childNode); 120 | queue.push(childNode); 121 | } else { 122 | queue.push('dead'); 123 | } 124 | } 125 | } 126 | }; 127 | 128 | public getTree(): Tree{ 129 | return this.tree!; 130 | } 131 | 132 | // Helper function to grab child elements. * Consider console logging vueCompiler.compileTemplate({source: sourceCode, filename, id}) to see what properties it has. * 133 | public getChildren(sourceCode: string, filename: string, id: string): any { 134 | const arrOfChildren = vueCompiler.compileTemplate({ source: sourceCode, filename, id }).ast.components; 135 | return arrOfChildren; 136 | } 137 | // Helper function to extract variables when iterating through the components. 138 | // 'parse' is imported from @vue/compiler-dom on line 5. 139 | public extractVariables(template: string, component: string): any { 140 | const variables = { 141 | oneway: [], 142 | twoway: [] 143 | }; 144 | const ast = parse(template); 145 | transform(ast, { 146 | nodeTransforms: [ 147 | (node) => { 148 | // console.log('NODE: ', node) 149 | 150 | // Refer to vueCompilerTypes.txt at the root level to understand what each type refers to. 151 | // Dont need to iterate through props because there can only be one v-model tag per component 152 | if (node.hasOwnProperty('tag') && node['tag'] === component) { 153 | if (node.type === 1 && node.props.some((prop) => prop.type === 7 && prop.name === 'model')) { 154 | const twoWayDirective = node.props.find((prop) => prop.type === 7 && prop.name === 'model'); 155 | try { 156 | if (twoWayDirective['arg'] !== undefined) { 157 | variables.twoway.push(twoWayDirective['arg'].content); 158 | } else { 159 | variables.twoway.push(twoWayDirective['exp'].content) 160 | } 161 | } catch(error){ 162 | } 163 | } 164 | // iterate through props because there can be multiple one way props passed down into each component 165 | if (node.type === 1 && node.props.some((prop) => (prop.type === 7 || prop.type === 6 ) && prop.name !== 'model')){ 166 | node.props.forEach(currnode => { 167 | if (currnode.type === 7 && currnode.name !== 'model') { 168 | if (currnode['arg'] !== undefined) { 169 | variables.oneway.push(currnode.arg['content']); 170 | } else { 171 | variables.oneway.push(currnode.exp['content']); 172 | } 173 | } 174 | }); 175 | } 176 | } 177 | } 178 | ] 179 | }); 180 | return variables; 181 | } 182 | // Call parse method from vueCompiler on current component. It will return an object of type SFCParseResult 183 | // Deconstruct result in 'descriptor'. 'descriptor' is of type SFCDescriptor, which is important because that is the only type that 184 | // vueCompiler.compileScript takes as its first argument. 185 | // Second argument is an options object with the only required property being 'id'. 186 | // This will return an object of type SFCScriptBlock that has an imports property. 187 | // Store the values array of the object and return it. 188 | // Here, the values will be objects that have a 'source' property that we will be able to grab the import path for later use. 189 | // (Will be used for dynamically creating the correct file path) 190 | public getImports(template: string, filename: string, id: string): any { 191 | if (!template.includes('script' || 'script setup')) {return [];} 192 | const { descriptor } = vueCompiler.parse(template, {filename}); // return object type SFCParseResult with descriptor property 193 | const { imports } = vueCompiler.compileScript(descriptor, {id}); // return object type SFCScriptBlock with imports property 194 | const result = Object.values(imports); 195 | return result; 196 | } 197 | } 198 | 199 | -------------------------------------------------------------------------------- /src/webview/components/App.vue: -------------------------------------------------------------------------------- 1 | 229 | 230 | 265 | 266 | 379 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | VisiVue Logo 3 |

4 |

VisiVue (Beta)

5 | As applications built in Vue JS scale in size and complexity, developers tasked with their maintenance and management often face challenges, particularly when dealing with unanticipated side-effects caused by two-way data binding. This is where VisiVue comes in. VisiVue is a VS Code extension that aims to fulfill our mission of providing Vue developers with deeper insight into their application's structure, data flow, and state management as it scales. 6 | 7 |
8 |
9 | 10 |

With VisiVue, developers are able to:

11 |
  • Visualize the component hierarchy of an application
  • 12 |
  • Keep track of stateful variables as they flow from parent to children
  • 13 |
  • Understand what variables are one-way data bound and two-way data bound
  • 14 |
  • Keep track of which components these stateful variables are drilled through
  • 15 | 16 |
    17 |
    18 | 19 |

    Get Started

    20 | 21 | 1. Head to the VS Code store and download VisiVue 22 | 23 | 24 |
    25 | 26 | 2. Once installed, you may need to reload your VS Code, but next open up the command pallette by pressing "cmd + shift + p" on MacOS or "ctrl + shift + p" and click the command "Show VisiVue Panel". 27 | 28 | 29 |
    30 | 31 | 3. Once you click the command in the command pallette, it will open up VisiVue! All you have to do is import the Vue file you want to visualize and you are set! 32 | 33 | 34 |
    35 | 36 |

    Open Source

    37 | 38 |

    How to Contribute

    39 | 40 | --------------------- 41 | We are looking for developers who believe in VisiVue and want to take it to the next level! Here are some features that we are currently working on implementing. Feel free to fork this repo and take a look at the source code! 42 | 43 |
    44 | 45 | Before submitting a pull request, please open an issue on our 'Issues' section. 46 | 47 |
    48 | 49 | | Further Features to Implement | Status | 50 | | ------- | ------ | 51 | | Vue Router Compatiblity | ⏳ | 52 | | Pinia Compatibility | ⏳ | 53 | | Testing Suites | ⏳ | 54 | | Make the tree centered and zoomed properly on mount | ⏳ | 55 | | Icon on each node that pulls up source code for that specific node | ⏳ | 56 | | Compatibility with Vue applications that utilize provide/inject functions | ⏳ | 57 | 58 | 59 |
    60 |

    How to Run in Development Mode

    61 | 62 | --------------------- 63 | 64 | 1. Clone the dev repo. 65 | ``` 66 | git clone https://github.com/oslabs-beta/VisiVue.git 67 | ``` 68 | 2. Go into the VisiVue directory on your local machine and install dependencies 69 | ``` 70 | npm install 71 | ``` 72 | 3. Once you have successfully installed dependencies, to open the developer environment for the VS Code extension, press 'ctrl + f5' if you are on Windows, or 'fn + f5' if you are on MacOS. This will open a new VS Code text editor and allow you to see a development version of the extension. 73 | 74 | - Note that may be difficult to get console logs when working on the Vue files specifically, so use the browser version by first checking out to the dev branch on your local machine: 75 | ``` 76 | git checkout dev 77 | ``` 78 | -Then navigate into the 'visivue-browser' directory: 79 | ``` 80 | cd visivue-browser 81 | ``` 82 | -And then run: 83 | ``` 84 | npm run dev 85 | ``` 86 | -there we have a browser version of our extension set up with nodemon 87 | 88 | 4. Once the VS Code development version is open, open the command pallete by pressing 'cmd + shift + p' and look for the command 'Show VisiVue Panel' and now you can see the extension! 89 | 90 | - If you are working in the VS Code developer environment, make sure to press 'cmd + r' to refresh the extension so that it compiles and runs your most recent changes. 91 | 92 |
    93 | Here are some links to documentation that you may find useful: 94 | 95 | * Vue.js 96 | * Vue Flow 97 | * VS Code Extension API 98 | * Webview API 99 | * Developer's Guide to Building VSCode Webview Panel with React and Messages by Michael Mike Benliyan 100 | * TypeScript 101 | 102 |
    103 |

    Known Bugs

    104 |
  • 105 | If you select a file and a tree is rendered: subsequently selecting another file should clear the current tree and render a new tree for that new file. However, what happens is that the newly rendered tree will not have nodes, but will still persist the edges. What we suspect is happening is that Vue Flow's internal processing of our data is causing this issue. We are currently working on a fix. 106 |
  • 107 | 108 |
    109 |
    110 | 111 |

    Change Log

    112 |
    113 |

    Tech Stack

    114 | 115 | ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) 116 | ![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) 117 | ![Vue.js](https://img.shields.io/badge/vuejs-%2335495e.svg?style=for-the-badge&logo=vuedotjs&logoColor=%234FC08D) 118 | ![Webpack](https://img.shields.io/badge/webpack-%238DD6F9.svg?style=for-the-badge&logo=webpack&logoColor=black) 119 | ![Visual Studio Code](https://img.shields.io/badge/Visual%20Studio%20Code-0078d7.svg?style=for-the-badge&logo=visual-studio-code&logoColor=white) 120 | ![VueFlow](https://img.shields.io/badge/Vue%20Flow-%2335495e.svg?style=for-the-badge&logo=vuedotjs&logoColor=%234FC08D) 121 | ![Vite](https://img.shields.io/badge/vite-%23646CFF.svg?style=for-the-badge&logo=vite&logoColor=white) 122 | ![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) 123 | 124 |
    125 |
    126 | 127 |

    VisiVue Contributors

    128 |

    Accelerated by OS Labs and inspired by ReacTree

    129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 |
    NameGitHubLinkedIn
    Abe HendersonGitHubLinkedIn
    Christopher ParkGitHubLinkedIn
    Kasey NguyenGitHubLinkedIn
    Ulf WongGitHubLinkedIn
    Yosuke TomitaGitHubLinkedIn
    165 | 166 | --- 167 | 168 | 169 | 170 | --------------------------------------------------------------------------------