├── .babelrc ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── dependabot.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── config ├── paths.js ├── webpack.common.js └── webpack.config.js ├── package-lock.json ├── package.json ├── public ├── app.css ├── icons │ ├── icon_128.png │ ├── icon_16.png │ ├── icon_48.png │ ├── sveltool-demo.gif │ ├── tools.png │ └── toolscrop1.png ├── manifest.json └── panel.html └── src ├── App.svelte ├── devTools.js ├── leftPanel ├── DisplayContainer.svelte ├── DisplayPanelTree.svelte ├── Navbar.svelte └── PerformanceProfiler.svelte ├── panel.js ├── rightPanel ├── SideBarContainer.svelte └── components │ ├── PropsContainer.svelte │ ├── PropsDisplayView.svelte │ ├── SideNavBar.svelte │ ├── StateContainer.svelte │ └── StateDisplayView.svelte ├── toolbar ├── Button.svelte ├── DisplayElement.svelte ├── NavBarTools.svelte ├── PickerButton.svelte ├── ProfileButton.svelte ├── Search.svelte ├── Toolbar.svelte └── VisibilityButton.svelte └── utils ├── changeState.js ├── createD3DataObject.js ├── d3ProfilerRender.js ├── d3TreeRender.js ├── parser.js └── store.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # See https://editorconfig.org for more about editor config. 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Match all files 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | max_line_length = 80 14 | trim_trailing_whitespace = true 15 | 16 | # Markdown files 17 | [*.md] 18 | max_line_length = 0 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Environment** 24 | - Browser with version [e.g. chrome 22, firefox 70.0.1] 25 | - Devtools version [e.g. 1.3.1, built from master] 26 | - Svelte version [e.g. 3.15.0] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # production 7 | /build 8 | 9 | # misc 10 | .DS_Store 11 | 12 | npm-debug.log* 13 | 14 | .env 15 | /app -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # See https://prettier.io/docs/en/ignore.html for more about ignoring files from Prettier. 2 | 3 | # Ignore artifacts: 4 | build 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "bracketSpacing": true, 5 | "bracketSameLine": false, 6 | "arrowParens": "always", 7 | "htmlWhitespaceSensitivity": "css", 8 | "insertPragma": false, 9 | "semi": true 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![banner](./public/icons/toolscrop1.png) 4 | 5 | ![MIT License](https://img.shields.io/badge/license-MIT-%23fb7182) 6 | ![GitHub Stars](https://img.shields.io/github/stars/oslabs-beta/Sveltool?color=%23fb7182) 7 | ![GitHub Forks](https://img.shields.io/github/forks/oslabs-beta/Sveltool?color=%23fb7182) 8 | 9 | # A dynamic visualization tool for all things Svelte. 10 | 11 | [⚡ Getting Started](http://sveltool.com/) | 12 | [📚 Documentation](http://sveltool.com/) | 13 | [⌨️ Blog](https://medium.com/@daurand303/introducing-sveltool-20d2cc1977a8) | 14 | [💬 Twitter](https://twitter.com/sveltool) | 15 | [💼 LinkedIn](https://www.linkedin.com/company/sveltool/) 16 | 17 |
18 | 19 | ## Sveltool Features 20 | 21 | Sveltool parses your webpage and extracts the components, as well as their associated state and props. 22 | 23 | The tool then displays your component structure in an easy-to-understand, tree-like visualization powered by the D3.js libray. 24 | 25 | This allows you to view the hierarchical structure of your webpage and easily find the state and props of any component. 26 | 27 | ![banner](./public/icons/sveltool-demo.gif) 28 | 29 | ## How to get started 30 | 31 | Sveltool is in the process of being added to the Chrome Web Store. At that point, downloading Sveltool will automatically integrate it into your Chrome DevTools. 32 | 33 | In the meantime, you can fork and clone this repository. From there open Sveltool in your IDE 34 | in the terminal run 35 | 36 | ```bash 37 | cd [Sveltool/name of directory] 38 | ``` 39 | 40 | then run: 41 | 42 | ```bash 43 | npm install 44 | npm run build 45 | ``` 46 | 47 | After you run build, navigate to the Google Chrome Extensions page in your browser, turn developer mode on, and click “Load Unpacked” (in the top left). Go to the Sveltool file and upload the “build” folder into Chrome Extensions. When your Svelte app is open right click and go to “inspect”, where you will see the option to open Sveltool! 48 | 49 | ## Troubleshooting 50 | 51 | If your components are still not displaying make sure your application is in “dev mode”. This can be configured in your root component or in your Webpack/Vite config files. 52 | 53 | If you're having trouble seeing Sveltool in the browser, double check that you have uploaded the “build” folder when clicking “Load Unpacked”. 54 | 55 | If your components aren't displaying within the tool, simply refresh the page. 56 | 57 | If none of these helped your problem, open an issue with us on our GitHub page! 58 | 59 | ## How to contribute 60 | 61 | Sveltool is currently in alpha, we would love to hear your feedback, encouragement, advice, suggestions, or problems! If you would like to contribute please contact us at info@sveltool.com 62 | 63 | ## What’s to Come in BETA 64 | 65 | - **Developer settings:** 66 | Customize settings to meet needs based on project 67 | 68 | - **Cache Snapshots of previous state:** 69 | As users interact with site, Sveltool will cache snapshots of previous states 70 | 71 | - **Rank components by render-time:** 72 | Optimize application by tracking component render-time 73 | 74 | - **Time Travel debugging:** 75 | Navigate backwards and forwards between previous and current state 76 | 77 | ## Learn More 78 | 79 | Check out our website 80 | [here](http://sveltool.com/) 81 | 82 | Read our latest blog 83 | [here](https://medium.com/@daurand303/introducing-sveltool-20d2cc1977a8) 84 | 85 | Follow us on Twitter 86 | [@Sveltool](https://twitter.com/sveltool) 87 | 88 | Contact us 89 | info@sveltool.com 90 | 91 | ## Contributors 92 | 93 | - Daniel Aurand • [LinkedIn](https://www.linkedin.com/in/daniel-aurand/) • [Github](https://github.com/daurand) 94 | - Jessica Davila• [LinkedIn](https://www.linkedin.com/in/jessica-davila-5a8380115/) • [Github](https://github.com/jessdvila) 95 | - Micheal Grant • [LinkedIn](https://www.linkedin.com/in/michaelcolliergrant/) • [Github](https://github.com/MichaelCGrant) 96 | - Adepeju Orefejo• [LinkedIn](https://www.linkedin.com/in/adepeju-orefejo/) • [Github](https://github.com/adepeju4) 97 | - Meow Puttamadilok• [LinkedIn](https://www.linkedin.com/in/thasanee-p-686125243/) • [Github](https://github.com/Meowmerry) 98 | 99 | ## License 100 | 101 | MIT 102 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const PATHS = { 6 | src: path.resolve(__dirname, '../src'), 7 | build: path.resolve(__dirname, '../build'), 8 | }; 9 | 10 | module.exports = PATHS; 11 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 5 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 7 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 8 | const Dotenv = require('dotenv-webpack'); 9 | 10 | const PATHS = require('./paths'); 11 | 12 | const infoColor = (_message) => { 13 | return `\u001b[1m\u001b[34m${_message}\u001b[39m\u001b[22m`; 14 | }; 15 | 16 | // used in the module rules and in the stats exlude list 17 | const IMAGE_TYPES = /\.(png|jpe?g|gif|svg)$/i; 18 | 19 | const mode = process.env.mode || 'development'; 20 | const prod = mode === 'production'; 21 | 22 | // To re-use webpack configuration across templates, 23 | // CLI maintains a common webpack configuration file - `webpack.common.js`. 24 | // Whenever user creates an extension, CLI adds `webpack.common.js` file 25 | // in template's `config` folder 26 | const common = { 27 | entry: { 28 | panel: path.resolve(__dirname, '../src/panel.js'), 29 | devTools: path.resolve(__dirname, '../src/devTools.js'), 30 | }, 31 | devServer: { 32 | host: 'localhost', 33 | port: 8080, 34 | headers: { 'Access-Control-Allow-Origin': '*' }, 35 | 36 | open: true, 37 | https: false, 38 | allowedHosts: 'all', 39 | hot: false, 40 | watchFiles: ['src/**', '/public/panel.html'], 41 | static: { 42 | watch: true, 43 | directory: path.join(__dirname, '../public'), 44 | }, 45 | client: { 46 | logging: 'none', 47 | overlay: true, 48 | progress: false, 49 | }, 50 | setupMiddlewares: function (middlewares, devServer) { 51 | console.log( 52 | '------------------------------------------------------------' 53 | ); 54 | console.log(devServer.options.host); 55 | const port = devServer.options.port; 56 | const https = devServer.options.https ? 's' : ''; 57 | const domain1 = `http${https}://${devServer.options.host}:${port}`; 58 | const domain2 = `http${https}://localhost:${port}`; 59 | 60 | console.log( 61 | `Project running at:\n - ${infoColor(domain1)}\n - ${infoColor( 62 | domain2 63 | )}` 64 | ); 65 | 66 | return middlewares; 67 | }, 68 | }, 69 | output: { 70 | // the build folder to output bundles and assets in. 71 | path: PATHS.build, 72 | // the filename template for entry chunks 73 | filename: '[name].js', 74 | }, 75 | stats: { 76 | all: false, 77 | errors: true, 78 | builtAt: true, 79 | assets: true, 80 | excludeAssets: [IMAGE_TYPES], 81 | }, 82 | resolve: { 83 | alias: { 84 | svelte: path.resolve('node_modules', 'svelte'), 85 | }, 86 | extensions: ['.mjs', '.js', '.svelte'], 87 | mainFields: ['svelte', 'browser', 'module', 'main'], 88 | }, 89 | module: { 90 | rules: [ 91 | // Help webpack in understanding CSS files imported in .js files 92 | { 93 | test: /\.css$/, 94 | use: [ 95 | MiniCssExtractPlugin.loader, 96 | { 97 | loader: 'css-loader', 98 | options: { 99 | sourceMap: true, 100 | }, 101 | }, 102 | ], 103 | }, 104 | // Check for images imported in .js files and 105 | { 106 | test: IMAGE_TYPES, 107 | use: [ 108 | { 109 | loader: 'file-loader', 110 | options: { 111 | outputPath: 'images', 112 | name: '[name].[ext]', 113 | }, 114 | }, 115 | ], 116 | }, 117 | //Allows use of svelte 118 | { 119 | test: /\.(svelte)$/, 120 | use: { 121 | loader: 'svelte-loader', 122 | options: { 123 | compilerOptions: { 124 | dev: !prod, 125 | }, 126 | emitCss: prod, 127 | }, 128 | }, 129 | }, 130 | { 131 | test: /node_modules\/svelte\/.*\.mjs$/, 132 | resolve: { 133 | fullySpecified: false, 134 | }, 135 | }, 136 | ], 137 | }, 138 | resolve: { 139 | extensions: ['.js', '.svelte'], 140 | }, 141 | plugins: [ 142 | // Clean build folder 143 | new CleanWebpackPlugin(), 144 | // Copy static assets from `public` folder to `build` folder 145 | new CopyWebpackPlugin({ 146 | patterns: [ 147 | { 148 | from: '**/*', 149 | context: 'public', 150 | filter: (resourcePath) => { 151 | if ( 152 | resourcePath.slice(resourcePath.lastIndexOf('.html')) === '.html' 153 | ) 154 | return false; 155 | return true; 156 | }, 157 | }, 158 | ], 159 | }), 160 | // Extract CSS into separate files 161 | new MiniCssExtractPlugin({ 162 | filename: '[name].css', 163 | }), 164 | new HtmlWebpackPlugin({ 165 | template: path.resolve(__dirname, '../public/panel.html'), 166 | chunks: ['panel'], 167 | filename: 'panel.html', 168 | inject: true, 169 | }), 170 | new HtmlWebpackPlugin({ 171 | chunks: ['devTools'], 172 | filename: 'devTools.html', 173 | inject: true, 174 | }), 175 | new Dotenv(), 176 | ], 177 | }; 178 | 179 | module.exports = common; 180 | -------------------------------------------------------------------------------- /config/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { merge } = require('webpack-merge'); 4 | 5 | const common = require('./webpack.common.js'); 6 | const PATHS = require('./paths'); 7 | 8 | // Merge webpack configuration files 9 | const config = (env, argv) => 10 | merge(common, { 11 | entry: { 12 | panel: PATHS.src + '/panel.js', 13 | }, 14 | }); 15 | 16 | module.exports = config; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltool", 3 | "version": "0.0.1", 4 | "description": "Dynamic visualization for all things Svelte", 5 | "private": true, 6 | "scripts": { 7 | "watch": "webpack --mode=development --watch --config config/webpack.config.js", 8 | "dev": "webpack-dev-server --mode=development --config config/webpack.config.js", 9 | "build": "webpack --mode=production --config config/webpack.config.js", 10 | "format": "prettier --write --ignore-unknown \"{config,public,src}/**/*.{html,css,js,ts,json}\"" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.18.13", 14 | "@babel/polyfill": "^7.12.1", 15 | "@babel/preset-env": "^7.19.4", 16 | "babel-loader": "^8.2.5", 17 | "clean-webpack-plugin": "^4.0.0", 18 | "copy-webpack-plugin": "^11.0.0", 19 | "css-loader": "^6.7.1", 20 | "dotenv-webpack": "^8.0.1", 21 | "file-loader": "^6.2.0", 22 | "html-webpack-plugin": "^5.5.0", 23 | "mini-css-extract-plugin": "^2.6.1", 24 | "prettier": "^2.7.1", 25 | "svelte-loader": "^3.1.3", 26 | "webpack": "^5.74.0", 27 | "webpack-cli": "^4.10.0", 28 | "webpack-dev-server": "^4.10.0", 29 | "webpack-merge": "^5.8.0" 30 | }, 31 | "dependencies": { 32 | "d3": "^7.6.1", 33 | "d3v4": "^4.2.2", 34 | "portfinder-sync": "^0.0.2", 35 | "svelte": "^3.49.0", 36 | "svelte-icons-pack": "^2.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /public/app.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap'); 2 | 3 | *, 4 | *::before, 5 | *::after { 6 | margin: 0; 7 | padding: 0; 8 | box-sizing: inherit; 9 | scroll-behavior: smooth; 10 | } 11 | 12 | html { 13 | box-sizing: border-box; 14 | font-size: 62.5%; 15 | height: 100%; 16 | width: 100%; 17 | } 18 | 19 | #app { 20 | height: 100%; 21 | width: 100%; 22 | } 23 | 24 | body { 25 | font-family: 'Poppins', sans-serif; 26 | -webkit-font-smoothing: antialiased; 27 | -moz-osx-font-smoothing: grayscale; 28 | -ms-overflow-style: none; 29 | scrollbar-width: none; 30 | height: 100%; 31 | width: 100%; 32 | } 33 | 34 | *::-webkit-scrollbar { 35 | display: none; 36 | } 37 | 38 | li { 39 | list-style: none; 40 | } 41 | 42 | a { 43 | text-decoration: none; 44 | } 45 | g { 46 | width: 20rem !important; 47 | } 48 | 49 | /* .node polygon { 50 | fill: grey; 51 | stroke: black; 52 | stroke-width: 1px; 53 | } */ 54 | 55 | .node text { 56 | font: 14px sans-serif; 57 | } 58 | 59 | .link { 60 | fill: none; 61 | stroke: #000000; 62 | stroke-width: 1px; 63 | } 64 | 65 | .hierarchy-container::-webkit-scrollbar { 66 | display: none; 67 | } 68 | 69 | .hierarchy-container { 70 | width: 100%; 71 | height: 100%; 72 | overflow: auto; 73 | } 74 | 75 | .dropdown { 76 | background: url('data:image/svg+xml;charset=US-ASCII,.arrow%7Bfill%3A%23424242%3B%7D<%2Fstyle><%2Fsvg>%0A'); 77 | } 78 | @media only screen and (max-width: 550px) { 79 | html { 80 | font-size: 50%; 81 | } 82 | } 83 | 84 | @media (prefers-color-scheme: dark) { 85 | body { 86 | background: #282c34; 87 | } 88 | 89 | svg { 90 | fill: #fff; 91 | } 92 | } 93 | 94 | @media (prefers-color-scheme: light) { 95 | body { 96 | background: #fff; 97 | } 98 | 99 | svg { 100 | fill: #000; 101 | } 102 | 103 | circle { 104 | fill: grey; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /public/icons/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Sveltool/2b2d3615cf81307ac3b1c67457bbec27c9bb5256/public/icons/icon_128.png -------------------------------------------------------------------------------- /public/icons/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Sveltool/2b2d3615cf81307ac3b1c67457bbec27c9bb5256/public/icons/icon_16.png -------------------------------------------------------------------------------- /public/icons/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Sveltool/2b2d3615cf81307ac3b1c67457bbec27c9bb5256/public/icons/icon_48.png -------------------------------------------------------------------------------- /public/icons/sveltool-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Sveltool/2b2d3615cf81307ac3b1c67457bbec27c9bb5256/public/icons/sveltool-demo.gif -------------------------------------------------------------------------------- /public/icons/tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Sveltool/2b2d3615cf81307ac3b1c67457bbec27c9bb5256/public/icons/tools.png -------------------------------------------------------------------------------- /public/icons/toolscrop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Sveltool/2b2d3615cf81307ac3b1c67457bbec27c9bb5256/public/icons/toolscrop1.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sveltool", 3 | "version": "0.0.1", 4 | "description": "Dynamic visualization for all things Svelte", 5 | "manifest_version": 3, 6 | "minimum_chrome_version": "10.0", 7 | "devtools_page": "./devTools.html", 8 | 9 | "icons": { 10 | "16": "./icons/icon_16.png", 11 | "48": "./icons/icon_48.png", 12 | "128": "./icons/icon_128.png" 13 | }, 14 | 15 | "permissions": ["tabs", "contextMenus"], 16 | 17 | "externally_connectable": { 18 | "matches": ["http://localhost/*"] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Resources 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 | 35 | 51 | -------------------------------------------------------------------------------- /src/devTools.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create('Sveltool', null, './panel.html'); 2 | -------------------------------------------------------------------------------- /src/leftPanel/DisplayContainer.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
23 | 24 | {#if $visibility.component} 25 | 26 | {/if} 27 |
28 | 29 | 37 | -------------------------------------------------------------------------------- /src/leftPanel/DisplayPanelTree.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
28 | 29 | -------------------------------------------------------------------------------- /src/leftPanel/Navbar.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 30 | -------------------------------------------------------------------------------- /src/leftPanel/PerformanceProfiler.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 | 39 | -------------------------------------------------------------------------------- /src/panel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import App from './App.svelte'; 4 | 5 | const app = new App({ 6 | target: document.getElementById('app'), 7 | }); 8 | 9 | export default app; 10 | -------------------------------------------------------------------------------- /src/rightPanel/SideBarContainer.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 17 | -------------------------------------------------------------------------------- /src/rightPanel/components/PropsContainer.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | 10 |
11 | 12 | 20 | -------------------------------------------------------------------------------- /src/rightPanel/components/PropsDisplayView.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
23 | 24 | 41 | -------------------------------------------------------------------------------- /src/rightPanel/components/SideNavBar.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
{type}
7 |
8 | 9 | 29 | -------------------------------------------------------------------------------- /src/rightPanel/components/StateContainer.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | 10 |
11 | 12 | 25 | -------------------------------------------------------------------------------- /src/rightPanel/components/StateDisplayView.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 42 | -------------------------------------------------------------------------------- /src/toolbar/Button.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 79 | -------------------------------------------------------------------------------- /src/toolbar/DisplayElement.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
{component}
11 | 12 | 21 | -------------------------------------------------------------------------------- /src/toolbar/NavBarTools.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/toolbar/PickerButton.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 27 | -------------------------------------------------------------------------------- /src/toolbar/ProfileButton.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 17 | -------------------------------------------------------------------------------- /src/toolbar/Search.svelte: -------------------------------------------------------------------------------- 1 | 36 | 37 |
38 |
39 | 40 | 44 | 49 | 50 | 51 | {#if resultsPosition > -1} 52 | {resultsPosition + 1} of {results.length}  53 | {/if} 54 | 57 | 60 | 61 | 62 | 116 | -------------------------------------------------------------------------------- /src/toolbar/Toolbar.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 17 | -------------------------------------------------------------------------------- /src/toolbar/VisibilityButton.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 46 | 47 | 134 | -------------------------------------------------------------------------------- /src/utils/changeState.js: -------------------------------------------------------------------------------- 1 | //iterate through data recursively 2 | //the data that we're pulling is from props display view 3 | 4 | const componentSearch = (d, sourceData) => { 5 | let resultData; 6 | 7 | if (sourceData.name === d) { 8 | resultData = { props: sourceData.props, state: sourceData.state }; 9 | return resultData; 10 | } 11 | 12 | //if it's not root component that was clicked --> if root component has children, then iterate through children 13 | if (sourceData && sourceData.children) { 14 | for (let i = 0; i < sourceData.children.length; i++) { 15 | //recursively call component search to see if sourceData.children[i] has children 16 | let recursive = componentSearch(d, sourceData.children[i]); 17 | if (recursive) return recursive; 18 | } 19 | } 20 | return null; 21 | }; 22 | 23 | function createContainerEl() { 24 | const containerDiv = document.createElement('div'); 25 | containerDiv.style.padding = '1rem 0'; 26 | 27 | return containerDiv; 28 | } 29 | 30 | function parseObjects(state, parent) { 31 | for (const key in state) { 32 | const child = createDivEl(key, state[key]); 33 | parent.appendChild(child); 34 | } 35 | } 36 | 37 | function parseList(list, heading) { 38 | const container = createContainerEl(); 39 | const header = document.createElement('div'); 40 | 41 | const parent = document.createElement('ul'); 42 | const footer = document.createElement('div'); 43 | footer.innerHTML = `]`; 44 | footer.style.fontSize = '1.5rem'; 45 | 46 | for (let i = 0; i < list.length; i++) { 47 | if (list[i] !== 0 && !list[i]) continue; 48 | 49 | const child = document.createElement('li'); 50 | if (Array.isArray(list[i])) { 51 | child.appendChild(parseList(list[i])); 52 | } else if (typeof list[i] === 'object') { 53 | parseObjects(list[i], child); 54 | } else { 55 | child.innerText = list[i]; 56 | } 57 | 58 | child.style.padding = '1rem 1rem 0 1rem'; 59 | parent.appendChild(child); 60 | } 61 | 62 | header.innerHTML = `${heading} [`; 63 | header.style.fontSize = '1.5rem'; 64 | 65 | container.appendChild(header); 66 | container.appendChild(parent); 67 | container.appendChild(footer); 68 | return container; 69 | } 70 | 71 | function createDivEl(key, value) { 72 | const divParent = document.createElement('div'); 73 | const spanChild = document.createElement('span'); 74 | const inputValue = document.createElement('input'); 75 | const inputKey = document.createElement('div'); 76 | 77 | inputKey.innerHTML = `${key}: `; 78 | 79 | spanChild.style.display = 'flex'; 80 | spanChild.style.gap = '2%'; 81 | spanChild.style.fontSize = '12px'; 82 | spanChild.style.fontWeight = 500; 83 | 84 | inputValue.value = value; 85 | inputValue.style.color = '#40b3ff'; 86 | inputValue.style.background = 'transparent'; 87 | inputValue.style.border = 'none'; 88 | inputValue.style.outline = 'none'; 89 | inputValue.style.width = '-webkit-fill-available'; 90 | inputValue.style.fontSize = '12px'; 91 | inputValue.style.fontWeight = 500; 92 | spanChild.appendChild(inputKey); 93 | spanChild.appendChild(inputValue); 94 | divParent.appendChild(spanChild); 95 | 96 | inputValue.addEventListener('keyup', function (event) { 97 | event.preventDefault(); 98 | if (event.keyCode === 13) { 99 | this.blur(); 100 | } 101 | }); 102 | 103 | inputValue.addEventListener('onChange', function (event) {}); 104 | 105 | return divParent; 106 | } 107 | 108 | const changeState = (state, nested = false) => { 109 | let result = []; 110 | 111 | const uniContainer = createContainerEl(); 112 | const header = document.createElement('div'); 113 | const footer = document.createElement('div'); 114 | header.style.fontSize = '1.5rem'; 115 | footer.style.fontSize = '1.5rem'; 116 | if (!nested) { 117 | uniContainer.appendChild(header); 118 | } 119 | 120 | if (state !== 0 && !state) return state; 121 | if (typeof state === 'object') { 122 | for (const key in state) { 123 | if (Array.isArray(state[key])) { 124 | result.push(parseList(state[key], key)); 125 | } else if (typeof state[key] !== 'object') { 126 | const div = createDivEl(key, state[key]); 127 | div.style.padding = '0 1rem 0 1rem'; 128 | uniContainer.appendChild(div); 129 | } else { 130 | const container = createContainerEl(); 131 | const header = document.createElement('div'); 132 | header.innerHTML = `${key} {`; 133 | header.style.fontSize = '1.5rem'; 134 | 135 | const footer = document.createElement('div'); 136 | footer.innerHTML = `}`; 137 | footer.style.fontSize = '1.5rem'; 138 | 139 | container.appendChild(header); 140 | container.appendChild(...changeState(state[key], true)); 141 | 142 | container.appendChild(footer); 143 | 144 | result.push(container); 145 | } 146 | } 147 | 148 | if (uniContainer.childNodes.length > 1) { 149 | if (!nested) { 150 | uniContainer.appendChild(footer); 151 | } 152 | result.push(uniContainer); 153 | } 154 | } 155 | 156 | return result; 157 | }; 158 | 159 | export default changeState; 160 | -------------------------------------------------------------------------------- /src/utils/createD3DataObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Takes in the root component and the dependency definitions object. 4 | * Builds the output json to send to the D3 renderer. 5 | * 6 | **/ 7 | 8 | function D3DataObject(root = '', dependencies = {}, state = {}) { 9 | this.getData = (component = root, props) => { 10 | const componentData = {}; 11 | componentData.name = component; 12 | if (props) componentData.props = props; 13 | if (state[component]) componentData.state = state[component]; 14 | if (dependencies[component]) 15 | componentData.children = dependencies[component].map((dependency) => 16 | this.getData(dependency.name, dependency.props) 17 | ); 18 | return componentData; 19 | }; 20 | this.data = this.getData(); 21 | } 22 | 23 | export default D3DataObject; 24 | -------------------------------------------------------------------------------- /src/utils/d3ProfilerRender.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Sveltool/2b2d3615cf81307ac3b1c67457bbec27c9bb5256/src/utils/d3ProfilerRender.js -------------------------------------------------------------------------------- /src/utils/d3TreeRender.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | import { componentProps, componentState, currentComponent } from './store'; 3 | 4 | /*jshint esversion: 6 */ 5 | (function () { 6 | 'use strict'; 7 | })(); 8 | 9 | let { tree, hierarchy, select } = d3; 10 | 11 | class MyTree { 12 | constructor(data) { 13 | this.margin = { left: null, right: null, top: null, bottom: null }; 14 | this.width = null; 15 | this.height = null; 16 | this.barHeight = null; 17 | this.barWidth = null; 18 | this.i = 0; 19 | this.duration = null; 20 | this.tree = null; 21 | this.root = null; 22 | this.svg = null; 23 | this.colorScheme = null; 24 | this.component = ''; 25 | this.data = data; 26 | } 27 | 28 | $onInit(d3El, width, height, colorScheme) { 29 | this.margin = { top: 20, right: 10, bottom: 20, left: 10 }; 30 | this.width = width - this.margin.right - this.margin.left; 31 | this.height = height - this.margin.top - this.margin.bottom; 32 | this.barHeight = 20; 33 | this.barWidth = this.width * 0.8; 34 | this.i = 0; 35 | this.duration = 600; 36 | this.tree = tree().size([this.width, this.height]); 37 | // this.tree = tree().nodeSize([0, 30]); 38 | this.component = ''; 39 | this.tree = tree().nodeSize([0, 30]); 40 | this.root = this.tree(hierarchy(this.data)); 41 | 42 | this.root.each((d) => { 43 | d.name = d.id; //transferring name to a name letiable 44 | d.id = this.i; //Assigning numerical Ids 45 | this.i++; 46 | }); 47 | this.root.x0 = this.root.x; 48 | this.root.y0 = this.root.y; 49 | 50 | this.svg = select(d3El) 51 | .append('svg') 52 | .attr('width', this.width + this.margin.right + this.margin.left) 53 | .attr('height', this.height + this.margin.top + this.margin.bottom) 54 | .append('g') 55 | .attr( 56 | 'transform', 57 | 'translate(' + this.margin.left + ',' + this.margin.top + ')' 58 | ); 59 | 60 | if (this.root.children) { 61 | this.root.children.forEach(this.collapse); 62 | } 63 | this.update(this.root, colorScheme); 64 | } 65 | 66 | connector = function (d) { 67 | return 'M' + d.parent.y + ',' + d.parent.x + 'V' + d.x + 'H' + d.y; 68 | }; 69 | 70 | collapse = (d) => { 71 | if (d.children) { 72 | d._children = d.children; 73 | d._children.forEach(this.collapse); 74 | d.children = null; 75 | } 76 | }; 77 | 78 | click = (d) => { 79 | d = d.target.__data__; 80 | let props; 81 | componentProps.update(() => { 82 | props = d.data.props; 83 | 84 | return props; 85 | }); 86 | componentState.update((state) => { 87 | state = d.data.state; 88 | return state; 89 | }); 90 | 91 | currentComponent.update(() => { 92 | return d.data.name; 93 | }); 94 | 95 | if (d.children) { 96 | d._children = d.children; 97 | d.children = null; 98 | } else { 99 | d.children = d._children; 100 | d._children = null; 101 | } 102 | 103 | this.update(d, this.colorScheme); 104 | }; 105 | 106 | update = (source, colorScheme) => { 107 | this.width = 800; 108 | this.colorScheme = colorScheme ? colorScheme : this.colorScheme; 109 | // Compute the new tree layout. 110 | let nodes = this.tree(this.root); 111 | let nodesSort = []; 112 | nodes.eachBefore(function (n) { 113 | nodesSort.push(n); 114 | }); 115 | this.height = Math.max( 116 | 500, 117 | nodesSort.length * this.barHeight + this.margin.top + this.margin.bottom 118 | ); 119 | let links = nodesSort.slice(1); 120 | // Compute the "layout". 121 | nodesSort.forEach((n, i) => { 122 | n.x = i * this.barHeight; 123 | }); 124 | 125 | d3.select('svg') 126 | .transition() 127 | .duration(this.duration) 128 | .attr('height', this.height); 129 | 130 | // Update the nodes 131 | let node = this.svg.selectAll('g.node').data(nodesSort, function (d) { 132 | return d.id || (d.id = ++this.i); 133 | }); 134 | 135 | // Enter any new nodes at the parent's previous position. 136 | let nodeEnter = node 137 | .enter() 138 | .append('g') 139 | .attr('class', 'node') 140 | .attr('transform', function () { 141 | return 'translate(' + source.y0 + ',' + source.x0 + ')'; 142 | }) 143 | .on('click', (e) => { 144 | this.click(e); 145 | }); 146 | 147 | nodeEnter 148 | .append('polygon') 149 | .attr('points', function (d) { 150 | return d._children ? '0 -5, 0 4, 7 0' : '0 -1, 5 5, 9 -1'; 151 | }) 152 | .style('cursor', function (d) { 153 | return 'pointer'; 154 | }) 155 | .attr('fill', '#ff3e00'); 156 | 157 | nodeEnter 158 | .append('text') 159 | .attr('x', function (d) { 160 | return d.children || d._children ? 10 : 10; 161 | }) 162 | .attr('dy', '.35em') 163 | .attr('text-anchor', function (d) { 164 | return d.children || d._children ? 'start' : 'start'; 165 | }) 166 | .text(function (d) { 167 | if (d.data.name && d.data.name.length > 20) { 168 | return d.data.name.substring(0, 20) + '...'; 169 | } else { 170 | return d.data.name; 171 | } 172 | }) 173 | .style('fill-opacity', 1e-6) 174 | .style('cursor', 'pointer'); 175 | nodeEnter.append('svg:title').text(function (d) { 176 | return d.data.name; 177 | }); 178 | 179 | // Transition nodes to their new position. 180 | let nodeUpdate = node.merge(nodeEnter).transition().duration(this.duration); 181 | nodeUpdate.attr('transform', function (d) { 182 | return 'translate(' + d.y + ',' + d.x + ')'; 183 | }); 184 | 185 | nodeUpdate 186 | .select('polygon') 187 | .attr('points', function (d) { 188 | return d._children ? '0.9 -5, 0.9 4, 7 0' : '0 -3, 5 2, 9 -3'; 189 | }) 190 | .attr('fill', '#ff3e00') 191 | .attr('height', '50px') 192 | .style('cursor', function (d) { 193 | return 'pointer'; 194 | }); 195 | 196 | nodeUpdate.select('text').style('fill-opacity', 1); 197 | 198 | // Transition exiting nodes to the parent's new position (and remove the nodes) 199 | let nodeExit = node.exit().transition().duration(this.duration); 200 | 201 | nodeExit 202 | .attr('transform', function (d) { 203 | return 'translate(' + source.y + ',' + source.x + ')'; 204 | }) 205 | .remove(); 206 | 207 | nodeExit.select('polygon').attr('points', function (d) { 208 | return '0 -5, 0 4, 7 0'; 209 | }); 210 | 211 | nodeExit.select('text').style('fill-opacity', 1e-6); 212 | 213 | // Update the links… 214 | let link = this.svg.selectAll('path.link').data(links, function (d) { 215 | // return d.target.id; 216 | let id = d.id + '->' + d.parent.id; 217 | return id; 218 | }); 219 | 220 | // Enter any new links at the parent's previous position. 221 | let linkEnter = link 222 | .enter() 223 | .insert('path', 'g') 224 | .attr('class', 'link') 225 | .attr('d', (d) => { 226 | let o = { 227 | x: source.x0, 228 | y: source.y0, 229 | parent: { x: source.x0, y: source.y0 }, 230 | }; 231 | return this.connector(o); 232 | }); 233 | 234 | // Transition links to their new position. 235 | link 236 | .merge(linkEnter) 237 | .transition() 238 | .duration(this.duration) 239 | .attr('d', this.connector); 240 | 241 | // // Transition exiting nodes to the parent's new position. 242 | link 243 | .exit() 244 | .transition() 245 | .duration(this.duration) 246 | .attr('d', (d) => { 247 | let o = { 248 | x: source.x, 249 | y: source.y, 250 | parent: { x: source.x, y: source.y }, 251 | }; 252 | return this.connector(o); 253 | }) 254 | .remove(); 255 | 256 | // Stash the old positions for transition. 257 | nodesSort.forEach(function (d) { 258 | d.x0 = d.x; 259 | d.y0 = d.y; 260 | }); 261 | }; 262 | } 263 | 264 | export default MyTree; 265 | -------------------------------------------------------------------------------- /src/utils/parser.js: -------------------------------------------------------------------------------- 1 | import { parse, walk } from 'svelte/compiler'; 2 | import D3DataObject from './createD3DataObject'; 3 | 4 | async function parser() { 5 | // Define temporary data structures 6 | const dependencies = {}; 7 | const state = {}; 8 | const checked = {}; 9 | const usedComponents = []; 10 | 11 | // Get all files from inspected window that end in ".svelte" 12 | const arrSvelteFiles = await new Promise((resolve, reject) => { 13 | chrome.devtools.inspectedWindow.getResources((resources) => { 14 | const filteredResources = resources.filter((file) => 15 | file.url.includes('.svelte') 16 | ); 17 | if (filteredResources) resolve(filteredResources); 18 | else reject('No Svelte Resources Found'); 19 | }); 20 | }); 21 | 22 | // Create an array of component names from ".svelte" files 23 | const componentNames = arrSvelteFiles.map( 24 | (svelteFile) => 25 | `<${svelteFile.url.slice( 26 | svelteFile.url.lastIndexOf('/') + 1, 27 | svelteFile.url.lastIndexOf('.') 28 | )} />` 29 | ); 30 | 31 | // Define function to pull file content from filtered file array 32 | async function getContent(arrFiles) { 33 | const content = await arrFiles.map(async (file, index) => { 34 | const currentComponent = componentNames[index]; 35 | 36 | // Only check each component once no matter how many copies of each .svelte file there are 37 | if (checked[currentComponent]) return; 38 | checked[currentComponent] = true; 39 | usedComponents.push(currentComponent); 40 | 41 | // get file content for each Svelte file and process it 42 | const output = await new Promise((resolve, reject) => { 43 | file.getContent((source) => { 44 | if (source) resolve(source); 45 | }); 46 | }); 47 | return output; 48 | }); 49 | return Promise.all(content); 50 | } 51 | 52 | // Process file array into content array 53 | let arrSvelteContent = await getContent(arrSvelteFiles); 54 | // Filter components with no content or duplicate components 55 | arrSvelteContent = arrSvelteContent.filter((content) => { 56 | if (content) return content; 57 | }); 58 | 59 | // Iterate over each file content object and process it 60 | arrSvelteContent.forEach((content, index) => { 61 | const currentComponent = usedComponents[index]; 62 | 63 | // Parse the file contents and build an AST 64 | const ast = parse(content); 65 | 66 | // Walk the AST and output dependencies, props, and state 67 | walk(ast, { 68 | enter(ASTnode, parent, prop, index) { 69 | // find component dependencies 70 | if (ASTnode.type === 'InlineComponent') { 71 | const dependencyValue = {}; 72 | dependencyValue.name = `<${ASTnode.name} />`; 73 | // find props 74 | if (ASTnode.attributes[0]) { 75 | const foundProps = {}; 76 | ASTnode.attributes.forEach((el) => { 77 | foundProps[el.name] = el.value[0].data || ''; 78 | }); 79 | dependencyValue.props = foundProps; 80 | } 81 | dependencies[currentComponent] 82 | ? dependencies[currentComponent].push(dependencyValue) 83 | : (dependencies[currentComponent] = [dependencyValue]); 84 | } 85 | }, 86 | }); 87 | }); 88 | 89 | // Build array of all components from dependency array with thier depedencies 90 | const allComponents = []; 91 | for (const key in dependencies) { 92 | allComponents.push([key, dependencies[key]]); 93 | } 94 | 95 | // find the root component 96 | let rootComponent; 97 | // const allComponents = Object.entries(dependencies); 98 | console.log('All Components ==> ', allComponents); 99 | while (!rootComponent) { 100 | const curr = allComponents.shift(); 101 | const currName = curr[0]; 102 | // console.log('Current element ==> ', curr); 103 | let foundRoot = true; 104 | allComponents.forEach((comp) => { 105 | comp[1].forEach((dep) => { 106 | const { name } = dep; 107 | if (name === currName) foundRoot = false; 108 | }); 109 | }); 110 | if (foundRoot) rootComponent = currName; 111 | allComponents.push(curr); 112 | } 113 | 114 | // Build output json to send to D3 renderer 115 | // state is not currently being found or passed to D3 116 | const output = new D3DataObject(rootComponent, dependencies, state); 117 | return output.data; 118 | } 119 | 120 | export default parser; 121 | -------------------------------------------------------------------------------- /src/utils/store.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const componentProps = writable({}); 4 | export const componentState = writable({}); 5 | export const viewType = writable('none'); 6 | 7 | export const visibility = writable({ 8 | component: true, 9 | element: true, 10 | block: true, 11 | iteration: true, 12 | slot: true, 13 | text: true, 14 | anchor: false, 15 | }); 16 | export const selectedNode = writable({}); 17 | export const hoveredNodeId = writable(null); 18 | export const rootNodes = writable([]); 19 | export const searchValue = writable(''); 20 | export const profilerEnabled = writable(false); 21 | export const profileFrame = writable({}); 22 | 23 | export const treeData = writable({ 24 | edited: false, 25 | initData: { name: '' }, 26 | editData: {}, 27 | }); 28 | 29 | export const currentComponent = writable(''); 30 | --------------------------------------------------------------------------------