├── .gitignore ├── LICENSE ├── README.md ├── demo-apps ├── calendar │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app 2.html │ │ ├── app.d.ts │ │ ├── app.html │ │ ├── lib │ │ │ ├── Appointment.svelte │ │ │ ├── Calendar.svelte │ │ │ ├── Scheduler.svelte │ │ │ ├── firebase │ │ │ │ └── firebase.js │ │ │ └── schedule-store.ts │ │ └── routes │ │ │ └── +page.svelte │ ├── static │ │ └── favicon.png │ ├── svelte.config.js │ ├── tsconfig.json │ └── vite.config.ts ├── package-lock.json └── todo-list │ ├── .gitignore │ ├── .npmrc │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── app.html │ ├── components │ │ ├── Authenticate.svelte │ │ └── TodoItem.svelte │ ├── lib │ │ ├── firebase │ │ │ └── firebase.js │ │ └── index.js │ ├── routes │ │ ├── +layout.svelte │ │ ├── +page.svelte │ │ └── dashboard │ │ │ └── +page.svelte │ └── store │ │ └── store.js │ ├── static │ └── favicon.png │ ├── svelte.config.js │ └── vite.config.js ├── extension ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── Store │ ├── slices │ │ ├── currentSnapshotSlice.tsx │ │ ├── highlightedComponentSlice.tsx │ │ ├── timedEventsSlice.tsx │ │ └── treeHistorySlice.tsx │ └── store.ts ├── __mocks__ │ ├── @mui │ │ └── material │ │ │ └── Slider.tsx │ ├── chrome.ts │ ├── fileMock.js │ ├── mockData.ts │ ├── react-d3-tree.tsx │ └── styleMock.js ├── babel.config.js ├── extension-diagram.png ├── fileTransformer.js ├── jest.config.ts ├── jest.setup.js ├── package-lock.json ├── package.json ├── src │ ├── assets │ │ └── img │ │ │ ├── delete.svg │ │ │ ├── google.png │ │ │ ├── grey-icon-128.png │ │ │ ├── grey-icon-16.png │ │ │ ├── grey-icon-34.png │ │ │ ├── grey-icon-48.png │ │ │ ├── icon-128.png │ │ │ ├── icon-16.png │ │ │ ├── icon-34.png │ │ │ ├── icon-48.png │ │ │ └── upload.png │ ├── containers │ │ └── Greetings │ │ │ ├── Greetings.jsx │ │ │ └── Greetings.tsx │ ├── index.d.ts │ ├── manifest.json │ ├── messenger.ts │ ├── pages │ │ ├── Background │ │ │ └── index.js │ │ ├── ContentScriptIsolated │ │ │ ├── contentScriptIsolated.styles.css │ │ │ └── index.js │ │ ├── ContentScriptMain │ │ │ ├── contentScriptMain.styles.css │ │ │ ├── index.js │ │ │ └── modules │ │ │ │ └── print.js │ │ ├── Devtools │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── Options │ │ │ ├── Options.css │ │ │ ├── Options.tsx │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ ├── index.jsx │ │ │ └── index.tsx │ │ ├── Panel │ │ │ ├── App.test.tsx │ │ │ ├── Panel.css │ │ │ ├── Panel.tsx │ │ │ ├── PanelComponents │ │ │ │ ├── ChartTree │ │ │ │ │ └── ChartTree.tsx │ │ │ │ ├── ComponentInfo │ │ │ │ │ ├── ComponentInfo.css │ │ │ │ │ └── ComponentInfo.tsx │ │ │ │ ├── Navbar │ │ │ │ │ ├── Navbar.css │ │ │ │ │ └── Navbar.tsx │ │ │ │ ├── Rewinder │ │ │ │ │ ├── Rewinder.css │ │ │ │ │ └── Rewinder.tsx │ │ │ │ ├── StateModifier │ │ │ │ │ ├── StateModifier.css │ │ │ │ │ └── StateModifier.tsx │ │ │ │ ├── StateValue │ │ │ │ │ ├── StateValue.css │ │ │ │ │ └── StateValue.tsx │ │ │ │ ├── TreeComponent │ │ │ │ │ ├── TreeComponent.css │ │ │ │ │ └── TreeComponent.tsx │ │ │ │ └── custom-tree.css │ │ │ ├── PanelPages │ │ │ │ ├── ListPage │ │ │ │ │ ├── ListPage.css │ │ │ │ │ └── ListPage.tsx │ │ │ │ └── TreePage │ │ │ │ │ ├── TreePage.css │ │ │ │ │ └── TreePage.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── App.test.tsx.snap │ │ │ ├── disclosure-open.png │ │ │ ├── disclosure.png │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ └── index.tsx │ │ ├── Popup │ │ │ ├── Popup.css │ │ │ ├── Popup.tsx │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ ├── index.jsx │ │ │ └── index.tsx │ │ ├── SveltePanel │ │ │ └── src │ │ │ │ └── panel │ │ │ │ ├── .gitignore │ │ │ │ ├── .vscode │ │ │ │ └── extensions.json │ │ │ │ ├── README.md │ │ │ │ ├── index.html │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ ├── public │ │ │ │ └── vite.svg │ │ │ │ ├── src │ │ │ │ ├── App.svelte │ │ │ │ ├── PanelComponents │ │ │ │ │ ├── ChartTree │ │ │ │ │ │ └── ChartTree.svelte │ │ │ │ │ ├── ComponentInfo │ │ │ │ │ │ └── ComponentInfo.svelte │ │ │ │ │ ├── Navbar │ │ │ │ │ │ └── Navbar.svelte │ │ │ │ │ ├── Panel │ │ │ │ │ │ └── Panel.svelte │ │ │ │ │ ├── Rewinder │ │ │ │ │ │ └── Rewinder.svelte │ │ │ │ │ ├── StateModifier │ │ │ │ │ │ └── StateModifier.svelte │ │ │ │ │ ├── StateValue │ │ │ │ │ │ └── StateValue.svelte │ │ │ │ │ ├── TreeComponent │ │ │ │ │ │ └── TreeComponent.svelte │ │ │ │ │ └── custom-tree.css │ │ │ │ ├── PanelPages │ │ │ │ │ ├── ListPage │ │ │ │ │ │ └── ListPage.svelte │ │ │ │ │ └── TreePage │ │ │ │ │ │ └── TreePage.svelte │ │ │ │ ├── Store │ │ │ │ │ ├── currentSnapshot.ts │ │ │ │ │ ├── highlightedComponent.ts │ │ │ │ │ ├── store.ts │ │ │ │ │ ├── timedEvents.ts │ │ │ │ │ └── treeHistory.ts │ │ │ │ ├── app.css │ │ │ │ ├── assets │ │ │ │ │ └── svelte.svg │ │ │ │ ├── declarations.ts │ │ │ │ ├── lib │ │ │ │ │ └── Counter.svelte │ │ │ │ ├── main.ts │ │ │ │ ├── types.ts │ │ │ │ └── vite-env.d.ts │ │ │ │ ├── svelte.config.js │ │ │ │ ├── tsconfig.app.json │ │ │ │ ├── tsconfig.json │ │ │ │ ├── tsconfig.node.json │ │ │ │ └── vite.config.ts │ │ └── types.tsx │ └── setupTests.ts ├── tsconfig.json ├── utils │ ├── build.js │ ├── env.js │ └── webserver.js └── webpack.config.js └── server ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── src ├── server │ ├── controllers │ │ ├── authController.ts │ │ ├── dataController.ts │ │ └── userController.ts │ ├── db.ts │ ├── passport-config.ts │ ├── routes │ │ ├── authRouter.ts │ │ ├── dataRoute.ts │ │ └── userRouter.ts │ ├── server.ts │ └── util.ts └── types.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | script.sh -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte DevTools+ 2 | 3 | ![expanded banner](https://github.com/oslabs-beta/Svelte-DevTools-Plus/assets/62129976/1d1c3294-69cd-47a3-9216-0d9a2f68fd12) 4 | 5 | ## Installation 6 | 7 | [](https://chromewebstore.google.com/detail/svelte-devtools+/bgelkjgjabbfoeiicgoddeadicoofcfk?hl=en-US&utm_source=ext_sidebar) 8 | 9 | ## Features 10 | 11 | ### Dynamic component visualization 12 | Inspect the component hierarchy of an application to easily understand its structure 13 | 14 | ![StepAndTree](https://github.com/oslabs-beta/Svelte-DevTools-Plus/assets/62129976/239b42ac-4c69-46fa-b85d-fd64ed5efd4b) 15 | 16 | ### Real-time state and props updates 17 | Monitor real-time updates of a component's state and props, and track your application's behavior 18 | 19 | ![Dynamic](https://github.com/oslabs-beta/Svelte-DevTools-Plus/assets/62129976/14411e26-84e9-4c56-bff0-d300cb806be1) 20 | 21 | ### Component inspection 22 | Select a component in the component tree to inspect its state and props, and gain insight into how data flows through the application 23 | 24 | ![Inspect](https://github.com/oslabs-beta/Svelte-DevTools-Plus/assets/62129976/8b810a8b-fdd4-407d-8a34-0302517d79e2) 25 | 26 | ### Component state modification 27 | Modify the state and props of a component for testing and debugging purposes, and experiment with different scenarios without modifying your code 28 | 29 | ![Edit](https://github.com/oslabs-beta/Svelte-DevTools-Plus/assets/62129976/bbfeba2a-5780-4dce-8cb1-642dc42490ad) 30 | 31 | ### State Rewind 32 | Explore your application's state at different points in time, and easily observe how it changes over time 33 | 34 | ![Rewind](https://github.com/oslabs-beta/Svelte-DevTools-Plus/assets/62129976/07ad1b05-ab69-47d7-adcb-fbbdeb06e24f) 35 | 36 | ## Development 37 | 38 | 1. Fork and clone the repo: 39 | 40 | ``` 41 | git clone https://github.com/oslabs-beta/Svelte-DevTools-Plus.git 42 | ``` 43 | 44 | 2. Navigate to the project folder 45 | 46 | ``` 47 | cd Svelte-DevTools-Plus/extension 48 | ``` 49 | 50 | 3. Install the necessary packages: 51 | 52 | ``` 53 | npm install 54 | ``` 55 | 56 | ## Testing 57 | 58 | ### To create a development build 59 | 60 | ``` 61 | npm run dev 62 | ``` 63 | 64 | ### To create a production build 65 | 66 | ``` 67 | npm run build 68 | ``` 69 | 70 | ### To run the test suite 71 | ``` 72 | npm test 73 | ``` 74 | 75 | ### To load the extension 76 | 1. In Chrome, navigate to chrome://extensions/ 77 | 2. Turn on developer mode by clicking the switch in the top right corner 78 | 3. Click "Load unpacked" in the top left corner 79 | 4. Navigate to your local repository and select the build folder 80 | 81 | ### Demo apps 82 | For testing, demo apps are included with the repo in the demo-apps folder 83 | 84 | ## Contributors 85 | 86 | [Alexander Vranas](https://github.com/avranas) 87 | 88 | [Maciej Małecki](https://github.com/maciekmalecki) 89 | 90 | [Janice Chu](https://github.com/JaniceKZ) 91 | 92 | [Francis Espinoza](https://github.com/francis8933) 93 | 94 | [Laura Glass-Johnston](https://github.com/ellgeejay) 95 | 96 | ## License 97 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/oslabs-beta/Svelte-DevTools-Plus/blob/main/LICENSE) file for details. 98 | 99 | 100 | -------------------------------------------------------------------------------- /demo-apps/calendar/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /demo-apps/calendar/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:svelte/recommended', 7 | 'prettier' 8 | ], 9 | parser: '@typescript-eslint/parser', 10 | plugins: ['@typescript-eslint'], 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020, 14 | extraFileExtensions: ['.svelte'] 15 | }, 16 | env: { 17 | browser: true, 18 | es2017: true, 19 | node: true 20 | }, 21 | overrides: [ 22 | { 23 | files: ['*.svelte'], 24 | parser: 'svelte-eslint-parser', 25 | parserOptions: { 26 | parser: '@typescript-eslint/parser' 27 | } 28 | } 29 | ] 30 | }; 31 | -------------------------------------------------------------------------------- /demo-apps/calendar/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /demo-apps/calendar/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | -------------------------------------------------------------------------------- /demo-apps/calendar/.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /demo-apps/calendar/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /demo-apps/calendar/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /demo-apps/calendar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev & nodemon server.js", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 12 | "format": "prettier --plugin-search-dir . --write ." 13 | }, 14 | "devDependencies": { 15 | "@sveltejs/adapter-auto": "^2.0.0", 16 | "@sveltejs/kit": "^1.20.4", 17 | "@types/body-parser": "^1.19.2", 18 | "@types/dotenv": "^8.2.0", 19 | "@types/express": "^4.17.17", 20 | "@typescript-eslint/eslint-plugin": "^5.45.0", 21 | "@typescript-eslint/parser": "^5.45.0", 22 | "eslint": "^8.28.0", 23 | "eslint-config-prettier": "^8.5.0", 24 | "eslint-plugin-svelte": "^2.30.0", 25 | "prettier": "^2.8.0", 26 | "prettier-plugin-svelte": "^2.10.1", 27 | "svelte": "^4.0.0", 28 | "svelte-check": "^3.4.3", 29 | "tslib": "^2.4.1", 30 | "typescript": "^5.0.0", 31 | "vite": "^4.3.6" 32 | }, 33 | "type": "module", 34 | "dependencies": { 35 | "body-parser": "^1.20.2", 36 | "express": "^4.18.2", 37 | "mongodb": "^5.7.0", 38 | "mongoose": "^7.3.3", 39 | "nodemon": "^3.0.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /demo-apps/calendar/src/app 2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /demo-apps/calendar/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /demo-apps/calendar/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /demo-apps/calendar/src/lib/Appointment.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 | updateAppt(e, "completed")}/> 35 | 36 | updateAppt(e, "eventname")}>{apptName} 37 | updateAppt(e, "time")}>{time} 38 | 39 | 40 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /demo-apps/calendar/src/lib/Calendar.svelte: -------------------------------------------------------------------------------- 1 | 62 | 63 | 64 |
65 | 72 |
73 | 74 | 83 | 84 | 102 | 103 | 204 | -------------------------------------------------------------------------------- /demo-apps/calendar/src/lib/Scheduler.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 | 42 | 43 |
44 |
45 |
46 | dispatch('modalClose')} class="close" title="Close Modal"> 47 | × 48 | 49 |
50 | 51 | 52 |
53 |

My Schedule for

54 |

{dateHeading}

55 | 62 | 63 | 64 |
65 | 66 |
67 | 77 | : 78 | 88 |
89 | 90 | 91 |
92 | 93 |
94 | 101 | 102 |
103 | 104 | 105 |
106 | 113 | 114 |
115 |
116 |
117 | 118 | 119 |
120 | 121 |
122 |
123 |
124 | 125 | 126 | {#if appointments.length === 0} 127 |

No appointments scheduled

128 | {:else} 129 | {#each appointments as appt} 130 | 131 | 138 | 139 | {/each} 140 | {/if} 141 |
142 |
143 | 144 | 291 | -------------------------------------------------------------------------------- /demo-apps/calendar/src/lib/firebase/firebase.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/demo-apps/calendar/src/lib/firebase/firebase.js -------------------------------------------------------------------------------- /demo-apps/calendar/src/lib/schedule-store.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const scheduleStore = writable({ 4 | July_11_2023: [ 5 | { 6 | id: 7382377, 7 | eventname: "Go for a walk", 8 | time: "11:00am", 9 | completed: false 10 | } 11 | ], 12 | July_13_2023: [ 13 | { 14 | id: 7382376, 15 | eventname: "Bike ride", 16 | time: "6:00am", 17 | completed: false 18 | } 19 | ], 20 | July_27_2023: [ 21 | { 22 | id: 2983017, 23 | eventname: "Prep for OSP Launch", 24 | time: "9:05am", 25 | completed: false 26 | }, 27 | { 28 | id: 7394017, 29 | eventname: "Launch", 30 | time: "10:00am", 31 | completed: false 32 | }, 33 | { 34 | id: 4039176, 35 | eventname: "Launch celebration", 36 | time: "12:15pm", 37 | completed: false 38 | }, 39 | { 40 | id: 8293041, 41 | eventname: "Lecture: System Design", 42 | time: "2:30pm", 43 | completed: false 44 | }, 45 | { 46 | id: 9384071, 47 | eventname: "OSP Launch Presentation", 48 | time: "6:30pm", 49 | completed: false 50 | } 51 | ], 52 | July_31_2023: [ 53 | { 54 | id: 1203928, 55 | eventname: "Reinforcement Project Brief", 56 | time: "1:30pm", 57 | completed: false 58 | } 59 | ], 60 | August_4_2023: [ 61 | { 62 | id: 2938049, 63 | eventname: "Solo Technical Interview Practice", 64 | time: "2:00pm", 65 | completed: false 66 | } 67 | ], 68 | August_8_2023: [ 69 | { 70 | id: 2931234, 71 | eventname: "Lecture: Online Profiles Kickoff", 72 | time: "3:00pm", 73 | completed: false 74 | } 75 | ], 76 | August_11_2023: [ 77 | { 78 | id: 3710293, 79 | eventname: "Codesmith Graduation", 80 | time: "5:30pm", 81 | completed: false 82 | } 83 | ] 84 | }); -------------------------------------------------------------------------------- /demo-apps/calendar/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 98 | 99 |
100 | 101 | 102 | {#if schedulerShowing} 103 | 110 | {/if} 111 |
112 | 113 | 119 | -------------------------------------------------------------------------------- /demo-apps/calendar/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/demo-apps/calendar/static/favicon.png -------------------------------------------------------------------------------- /demo-apps/calendar/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/kit/vite'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter(), 15 | alias: { 16 | $db: './src/db' 17 | } 18 | } 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /demo-apps/calendar/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "isolatedModules": false 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // 16 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 17 | // from the referenced tsconfig.json - TypeScript does not merge them in 18 | } 19 | -------------------------------------------------------------------------------- /demo-apps/calendar/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /demo-apps/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltekit", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /demo-apps/todo-list/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /demo-apps/todo-list/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | -------------------------------------------------------------------------------- /demo-apps/todo-list/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /demo-apps/todo-list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltekit-todo-firebase", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "devDependencies": { 11 | "@sveltejs/adapter-auto": "^2.0.0", 12 | "@sveltejs/kit": "^1.20.4", 13 | "svelte": "^4.0.5", 14 | "vite": "^4.4.2" 15 | }, 16 | "type": "module", 17 | "dependencies": { 18 | "firebase": "^10.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo-apps/todo-list/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 | 14 | 21 | 33 | 34 | 35 |
%sveltekit.body%
36 | 37 | 38 | -------------------------------------------------------------------------------- /demo-apps/todo-list/src/components/Authenticate.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 |
41 |

{register ? 'Register' : 'Login'}

42 | {#if error} 43 |

The information you have entered is incorrect

44 | {/if} 45 | 49 | 53 | {#if register} 54 | 62 | {/if} 63 | 64 | 71 | 72 |
73 |

Or

74 | {#if register} 75 |
76 |

Already have an account?

77 |

{}}>Login

78 |
79 | {:else} 80 |
81 |

Don't have an account?

82 |

{}}>Register

83 |
84 | {/if} 85 |
86 |
87 | 88 | 251 | -------------------------------------------------------------------------------- /demo-apps/todo-list/src/components/TodoItem.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

{index + 1}. {todo}

10 |
11 | editTodo(index)} 13 | on:keydown={() => {}} 14 | class="fa-regular fa-pen-to-square" 15 | /> 16 | removeTodo(index)} 18 | on:keydown={() => {}} 19 | class="fa-regular fa-trash-can" 20 | /> 21 |
22 |
23 | 24 | 48 | -------------------------------------------------------------------------------- /demo-apps/todo-list/src/lib/firebase/firebase.js: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { deleteApp, getApps, getApp, initializeApp } from 'firebase/app'; 3 | import { getFirestore } from 'firebase/firestore'; 4 | import { getAuth } from 'firebase/auth'; 5 | // TODO: Add SDKs for Firebase products that you want to use 6 | // https://firebase.google.com/docs/web/setup#available-libraries 7 | 8 | // Your web app's Firebase configuration 9 | const firebaseConfig = { 10 | apiKey: import.meta.env.VITE_APIKEY, 11 | authDomain: import.meta.env.VITE_AUTH_DOMAIN, 12 | projectId: import.meta.env.VITE_PROJECT_ID, 13 | storageBucket: import.meta.env.VITE_STORAGE_BUCKET, 14 | messagingSenderId: import.meta.env.VITE_MESSAGIN_SENDER_ID, 15 | appId: import.meta.env.VITE_APP_ID, 16 | }; 17 | 18 | // Initialize Firebase 19 | let firebaseApp; 20 | if (!getApps().length) { 21 | firebaseApp = initializeApp(firebaseConfig); 22 | } else { 23 | firebaseApp = getApp(); 24 | deleteApp(firebaseApp); 25 | firebaseApp = initializeApp(firebaseConfig); 26 | } 27 | 28 | export const db = getFirestore(firebaseApp); 29 | export const auth = getAuth(firebaseApp); 30 | -------------------------------------------------------------------------------- /demo-apps/todo-list/src/lib/index.js: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /demo-apps/todo-list/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 53 | 54 |
55 | 56 |
57 | 58 | 68 | -------------------------------------------------------------------------------- /demo-apps/todo-list/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /demo-apps/todo-list/src/routes/dashboard/+page.svelte: -------------------------------------------------------------------------------- 1 | 55 | 56 | {#if !$authStore.loading} 57 |
58 |
59 |

Todo List

60 |
61 | 65 | 69 |
70 |
71 |
72 | {#if todoList.length === 0} 73 |

You have nothing to do! Add a todo!

74 | {/if} 75 | {#each todoList as todo, index} 76 | 77 | {/each} 78 |
79 |
80 | 81 | 82 |
83 |
84 | {/if} 85 | 86 | 176 | -------------------------------------------------------------------------------- /demo-apps/todo-list/src/store/store.js: -------------------------------------------------------------------------------- 1 | import { 2 | createUserWithEmailAndPassword, 3 | signInWithEmailAndPassword, 4 | signOut, 5 | } from 'firebase/auth'; 6 | import { writable } from 'svelte/store'; 7 | import { auth } from '../lib/firebase/firebase'; 8 | 9 | export const authStore = writable({ 10 | user: null, 11 | loading: true, 12 | data: {}, 13 | }); 14 | 15 | export const authHandlers = { 16 | signup: async (email, pass) => { 17 | await createUserWithEmailAndPassword(auth, email, pass); 18 | }, 19 | login: async (email, pass) => { 20 | await signInWithEmailAndPassword(auth, email, pass); 21 | }, 22 | logout: async () => { 23 | await signOut(auth); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /demo-apps/todo-list/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/demo-apps/todo-list/static/favicon.png -------------------------------------------------------------------------------- /demo-apps/todo-list/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { 6 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 7 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 8 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 9 | adapter: adapter() 10 | } 11 | }; 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /demo-apps/todo-list/vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /extension/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "env": { 4 | "test": { 5 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /extension/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignorePatterns: ['build/'], 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | webextensions: true, 8 | jest: true, 9 | }, 10 | extends: ['eslint:recommended', 'plugin:react/recommended'], 11 | overrides: [ 12 | { 13 | env: { 14 | node: true, 15 | }, 16 | files: ['.eslintrc.{js,cjs,jsx,tsx}'], 17 | parserOptions: { 18 | sourceType: 'script', 19 | }, 20 | }, 21 | ], 22 | globals: { 23 | JSX: true, 24 | }, 25 | parser: '@typescript-eslint/parser', 26 | parserOptions: { 27 | ecmaVersion: 'latest', 28 | sourceType: 'module', 29 | }, 30 | plugins: ['@typescript-eslint', 'react'], 31 | rules: { 32 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 33 | semi: ['error', 'always'], 34 | 'no-unused-vars': [ 35 | 'error', 36 | { 37 | vars: 'all', 38 | args: 'after-used', 39 | caughtErrors: 'all', 40 | ignoreRestSiblings: false, 41 | }, 42 | ], 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /extension/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | zip/ 4 | coverage/ 5 | .DS_Store 6 | .env -------------------------------------------------------------------------------- /extension/.prettierignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /extension/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } -------------------------------------------------------------------------------- /extension/Store/slices/currentSnapshotSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import type { RootState } from '../store'; 3 | 4 | const initialState = { 5 | rootComponent: null, 6 | }; 7 | 8 | // Keeps track of which snapshot the user is on 9 | const currentSnapshotSlice = createSlice({ 10 | name: 'currentSnapshot', 11 | initialState, 12 | reducers: { 13 | setCurrentSnapshot(state, action) { 14 | const payload = action.payload; 15 | state.rootComponent = payload.rootComponent; 16 | }, 17 | }, 18 | }); 19 | 20 | export function selectCurrentSnapshot(state: RootState) { 21 | return state.currentSnapshot; 22 | } 23 | export const { setCurrentSnapshot } = currentSnapshotSlice.actions; 24 | export default currentSnapshotSlice.reducer; 25 | -------------------------------------------------------------------------------- /extension/Store/slices/highlightedComponentSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import type { RootState } from '../store'; 3 | 4 | export interface Component { 5 | tagName: string; 6 | // A components detail can have any kind of data inside of it 7 | detail: any; 8 | children: Array; 9 | id: number; 10 | } 11 | 12 | const initialState = { 13 | tagName: '', 14 | detail: [], 15 | children: [], 16 | id: -1, 17 | } as Component; 18 | 19 | // Keeps track of the information of the selected component 20 | const highlightedComponentSlice = createSlice({ 21 | name: 'highlightedComponent', 22 | initialState, 23 | reducers: { 24 | setHighlightedComponent(state, action) { 25 | const payload = action.payload; 26 | state.detail = payload.detail; 27 | state.tagName = payload.tagName; 28 | state.id = payload.id; 29 | }, 30 | updateHighlightedComponent(state, action) { 31 | const payload = action.payload; 32 | state.detail = payload.detail; 33 | state.tagName = payload.tagName; 34 | }, 35 | }, 36 | }); 37 | 38 | export function selectHighlightedComponent(state: RootState) { 39 | return state.highlightedComponent; 40 | } 41 | export const { setHighlightedComponent, updateHighlightedComponent } = 42 | highlightedComponentSlice.actions; 43 | export default highlightedComponentSlice.reducer; 44 | -------------------------------------------------------------------------------- /extension/Store/slices/timedEventsSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import type { RootState } from '../store'; 3 | 4 | export type TimedEventsType = 'sendMessage' | 'receiveMessage'; 5 | 6 | export type TimedEventsState = { 7 | newTimeStart: number; 8 | eventTimes: number[]; 9 | }; 10 | 11 | interface TimedEventPayload { 12 | type: TimedEventsType; 13 | data: number; 14 | } 15 | const initialState: TimedEventsState = { 16 | newTimeStart: -1, 17 | eventTimes: [], 18 | }; 19 | 20 | const timedEventSlice = createSlice({ 21 | name: 'timedEvents', 22 | initialState, 23 | reducers: { 24 | addNewEvent(state, action) { 25 | const payload: TimedEventPayload = action.payload; 26 | if (payload.type === 'sendMessage' && state.newTimeStart == -1) { 27 | state.newTimeStart = payload.data; 28 | } else if (payload.type === 'receiveMessage') { 29 | if (state.newTimeStart == -1) { 30 | return; 31 | } 32 | state.eventTimes.push(payload.data - state.newTimeStart); 33 | state.newTimeStart = -1; 34 | } 35 | }, 36 | }, 37 | }); 38 | 39 | export function selectEvents(state: RootState) { 40 | return state.timedEvents; 41 | } 42 | 43 | export const { addNewEvent } = timedEventSlice.actions; 44 | export default timedEventSlice.reducer; 45 | -------------------------------------------------------------------------------- /extension/Store/slices/treeHistorySlice.tsx: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import type { RootState } from '../store'; 3 | import { Component } from './highlightedComponentSlice'; 4 | 5 | export interface TreeHistory { 6 | treeHistory: Array; 7 | } 8 | 9 | const initialState = { treeHistory: [] } as TreeHistory; 10 | 11 | // Keeps track of all state snapshots 12 | const treeHistorySlice = createSlice({ 13 | name: 'treeHistory', 14 | initialState, 15 | reducers: { 16 | addNewSnapshot(state, action) { 17 | const newSnapshot: Component = action.payload.newSnapshot; 18 | state.treeHistory.push(newSnapshot); 19 | }, 20 | deleteAllSnapshots(state) { 21 | state.treeHistory = [...state.treeHistory].slice(-1); 22 | }, 23 | }, 24 | }); 25 | 26 | export function selectTreeHistory(state: RootState) { 27 | return state.treeHistory; 28 | } 29 | export const { addNewSnapshot } = treeHistorySlice.actions; 30 | export default treeHistorySlice.reducer; 31 | -------------------------------------------------------------------------------- /extension/Store/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import highlightedComponentReducer from './slices/highlightedComponentSlice'; 3 | import currentSnapshotReducer from './slices/currentSnapshotSlice'; 4 | import treeHistoryReducer from './slices/treeHistorySlice'; 5 | import timedEventReducer from './slices/timedEventsSlice'; 6 | 7 | const reducer = { 8 | highlightedComponent: highlightedComponentReducer, 9 | currentSnapshot: currentSnapshotReducer, 10 | treeHistory: treeHistoryReducer, 11 | timedEvents: timedEventReducer, 12 | }; 13 | 14 | export const createTestStore = () => { 15 | return configureStore({ 16 | reducer, 17 | }); 18 | }; 19 | 20 | export const store = configureStore({ reducer }); 21 | 22 | // Infer the `RootState` and `AppDispatch` types from the store itself 23 | export type RootState = ReturnType; 24 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 25 | export type AppDispatch = typeof store.dispatch; 26 | -------------------------------------------------------------------------------- /extension/__mocks__/@mui/material/Slider.tsx: -------------------------------------------------------------------------------- 1 | // Mock MUI slider. When clicked it updates to snapshot #2 2 | import React from 'react'; 3 | 4 | interface SliderProps { 5 | onChange: (event: null, value: number) => void; 6 | onChangeCommitted: (event: null, value: number) => void; 7 | } 8 | 9 | function Slider(props: SliderProps) { 10 | function handleClick() { 11 | props.onChange(null, 2); 12 | props.onChangeCommitted(null, 2); 13 | } 14 | return
; 15 | } 16 | export default Slider; 17 | -------------------------------------------------------------------------------- /extension/__mocks__/chrome.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '../Store/slices/highlightedComponentSlice'; 2 | import { ChromeMessage } from '../src/messenger'; 3 | import initialData from './mockData'; 4 | 5 | let data = JSON.parse(JSON.stringify(initialData)); 6 | 7 | type ChromeMessageListener = (message: any) => void; 8 | 9 | interface MockRuntime { 10 | onMessage: { 11 | addListener: (callback: ChromeMessageListener) => void; 12 | removeListener: (callback: ChromeMessageListener) => void; 13 | _triggerMessage: (message: any) => void; 14 | }; 15 | sendMessage: (message: ChromeMessage) => void; 16 | } 17 | 18 | interface QueryInfo { 19 | active: boolean; 20 | lastFocusedWindow: boolean; 21 | } 22 | 23 | interface MockTabs { 24 | query: (queryInfo: QueryInfo) => [{ id: number; url: string }]; 25 | sendMessage: (tabId: number, message: ChromeMessage) => void; 26 | } 27 | 28 | export interface MockChrome { 29 | runtime: MockRuntime; 30 | tabs: MockTabs; 31 | clearListeners: () => void; 32 | resetMockData: () => void; 33 | sendEmptyDataOnNextRequest: () => void; 34 | } 35 | 36 | let listeners: ChromeMessageListener[] = []; 37 | let _sendEmptyDataOnNextRequest = false; 38 | 39 | function updateState(id: number | undefined, newState: any): Boolean { 40 | function helper(component: Component): Boolean { 41 | if (component.id === id) { 42 | for (let i = 0; i < component.detail.ctx.length; i++) { 43 | const state = component.detail.ctx[i]; 44 | if (state.key === Object.keys(newState)[0]) { 45 | const value = Object.values(newState)[0]; 46 | if (state.value === value) return false; 47 | // Update state 48 | state.value = value; 49 | return true; 50 | } 51 | } 52 | return false; 53 | } 54 | for (let i = 0; i < component.children.length; i++) { 55 | const child = component.children[i]; 56 | return helper(child); 57 | } 58 | return false; 59 | } 60 | return helper(data); 61 | } 62 | 63 | const chrome: MockChrome = { 64 | runtime: { 65 | onMessage: { 66 | addListener: (callback) => { 67 | listeners.push(callback); 68 | }, 69 | _triggerMessage: (message) => { 70 | listeners.forEach((callback) => callback(message)); 71 | }, 72 | removeListener: (callback) => { 73 | listeners = listeners.filter((c) => c === callback); 74 | }, 75 | }, 76 | sendMessage: function () {}, 77 | }, 78 | tabs: { 79 | query: () => { 80 | return [{ id: 0, url: '' }]; 81 | }, 82 | sendMessage: (tabId, request) => { 83 | switch (request.message) { 84 | case 'getRootComponent': 85 | { 86 | let message: {}; 87 | if (_sendEmptyDataOnNextRequest) { 88 | message = { 89 | type: 'returnRootComponent', 90 | rootComponent: undefined, 91 | }; 92 | _sendEmptyDataOnNextRequest = false; 93 | } else { 94 | message = { 95 | type: 'returnRootComponent', 96 | rootComponent: JSON.parse(JSON.stringify(data)), 97 | }; 98 | } 99 | listeners.forEach((f) => f(message)); 100 | } 101 | break; 102 | case 'injectState': 103 | { 104 | const stateUpdated = updateState( 105 | request.componentId, 106 | request.newState 107 | ); // Don't send an update if nothing was changed 108 | // This causes problems 109 | if (!stateUpdated) return; 110 | const message = { 111 | type: 'updateRootComponent', 112 | rootComponent: JSON.parse(JSON.stringify(data)), 113 | }; 114 | listeners.forEach((f) => f(message)); 115 | } 116 | break; 117 | case 'injectSnapshot': 118 | { 119 | if (!request.snapshot) return; 120 | data = JSON.parse(JSON.stringify(request.snapshot)); 121 | const message = { 122 | type: 'returnTempRoot', 123 | rootComponent: JSON.parse(JSON.stringify(data)), 124 | }; 125 | listeners.forEach((f) => f(message)); 126 | } 127 | break; 128 | } 129 | }, 130 | }, 131 | clearListeners: function () { 132 | listeners = []; 133 | }, 134 | resetMockData: function () { 135 | data = JSON.parse(JSON.stringify(initialData)); 136 | }, 137 | sendEmptyDataOnNextRequest: function () { 138 | _sendEmptyDataOnNextRequest = true; 139 | }, 140 | }; 141 | 142 | export default chrome; 143 | -------------------------------------------------------------------------------- /extension/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /extension/__mocks__/react-d3-tree.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const Tree = () => { 3 | return
; 4 | }; 5 | 6 | export default Tree; 7 | -------------------------------------------------------------------------------- /extension/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /extension/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { targets: { node: 'current' } }], 4 | '@babel/preset-typescript', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /extension/extension-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/extension-diagram.png -------------------------------------------------------------------------------- /extension/fileTransformer.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | process(sourceText, sourcePath) { 5 | return { 6 | code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`, 7 | }; 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /extension/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'jest'; 2 | 3 | const config: Config = { 4 | preset: 'ts-jest', 5 | moduleNameMapper: { 6 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 7 | '/__mocks__/fileMock.js', 8 | '\\.(css|less)$': 'identity-obj-proxy', 9 | '^chrome$': '/__mocks__/chrome.ts', 10 | }, 11 | transform: { 12 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 13 | '/fileTransformer.js', 14 | '^.+\\.ts?$': 'ts-jest', 15 | '^.+\\.(js|jsx)$': 'babel-jest', 16 | }, 17 | transformIgnorePatterns: [ 18 | 'node_modules/(?!(react-syntax-highlighter)/)', // Ignore all node_modules except react-syntax-highlighter 19 | ], 20 | setupFilesAfterEnv: ['./jest.setup.js', '/src/setupTests.ts'], 21 | testEnvironment: 'node', 22 | extensionsToTreatAsEsm: ['.ts', '.tsx'], 23 | }; 24 | 25 | export default config; 26 | -------------------------------------------------------------------------------- /extension/jest.setup.js: -------------------------------------------------------------------------------- 1 | // import { chrome } from 'jest-chrome' 2 | 3 | // Object.assign(global, require('jest-chrome')) 4 | -------------------------------------------------------------------------------- /extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Svelte DevTools+", 3 | "version": "5.0.4", 4 | "description": "Finally, some good DevTools for Svelte", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/oslabs-beta/Svelte-DevTools-Plus" 9 | }, 10 | "scripts": { 11 | "build": "node utils/build.js", 12 | "start": "node utils/webserver.js", 13 | "dev": "node utils/webserver.js", 14 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx", 15 | "prettier": "prettier --write '**/*.{js,jsx,ts,tsx,json,css,scss,md}'", 16 | "test": "jest --coverage" 17 | }, 18 | "dependencies": { 19 | "@emotion/react": "^11.11.1", 20 | "@emotion/styled": "^11.11.0", 21 | "@mui/base": "^5.0.0-beta.8", 22 | "@mui/material": "^5.14.1", 23 | "@reduxjs/toolkit": "^1.9.5", 24 | "@types/react-syntax-highlighter": "^15.5.11", 25 | "dotenv": "^16.3.1", 26 | "jest-snapshot": "^29.7.0", 27 | "mui": "^0.0.1", 28 | "react": "^18.2.0", 29 | "react-collapsible": "^2.10.0", 30 | "react-d3-tree": "^3.6.1", 31 | "react-dom": "^18.2.0", 32 | "react-redux": "^8.1.1", 33 | "react-router-dom": "^6.14.1", 34 | "react-split": "^2.0.14", 35 | "react-syntax-highlighter": "^15.5.0", 36 | "redux": "^4.2.1", 37 | "split": "^1.0.1", 38 | "ts-node": "^10.9.1" 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "^7.20.12", 42 | "@babel/plugin-proposal-class-properties": "^7.18.6", 43 | "@babel/plugin-transform-modules-commonjs": "^7.23.0", 44 | "@babel/preset-env": "^7.20.2", 45 | "@babel/preset-react": "^7.18.6", 46 | "@babel/preset-typescript": "^7.23.2", 47 | "@jest/globals": "^29.7.0", 48 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", 49 | "@testing-library/jest-dom": "^6.1.4", 50 | "@testing-library/react": "^14.0.0", 51 | "@testing-library/user-event": "^14.5.1", 52 | "@types/jest": "^29.5.6", 53 | "@types/react": "^18.0.26", 54 | "@types/react-dom": "^18.0.10", 55 | "@types/react-redux": "^7.1.25", 56 | "@types/react-test-renderer": "^18.0.5", 57 | "@types/redux": "^3.6.0", 58 | "@types/redux-thunk": "^2.1.0", 59 | "@typescript-eslint/eslint-plugin": "^7.7.0", 60 | "@typescript-eslint/parser": "^7.7.0", 61 | "babel-eslint": "^10.1.0", 62 | "babel-jest": "^29.7.0", 63 | "babel-loader": "^9.1.2", 64 | "babel-preset-react-app": "^10.0.1", 65 | "clean-webpack-plugin": "^4.0.0", 66 | "copy-webpack-plugin": "^11.0.0", 67 | "css-loader": "^6.7.3", 68 | "eslint": "^8.31.0", 69 | "eslint-config-prettier": "^8.8.0", 70 | "eslint-plugin-flowtype": "^8.0.3", 71 | "eslint-plugin-import": "^2.27.4", 72 | "eslint-plugin-jsx-a11y": "^6.7.1", 73 | "eslint-plugin-react": "^7.32.0", 74 | "eslint-plugin-react-hooks": "^4.6.0", 75 | "fs-extra": "^11.1.0", 76 | "html-loader": "^4.2.0", 77 | "html-webpack-plugin": "^5.5.0", 78 | "identity-obj-proxy": "^3.0.0", 79 | "jest": "^29.7.0", 80 | "jest-dom": "^4.0.0", 81 | "jest-environment-jsdom": "^29.7.0", 82 | "jsdom": "^22.1.0", 83 | "prettier": "2.8.8", 84 | "react-refresh": "^0.14.0", 85 | "react-refresh-typescript": "^2.0.7", 86 | "sass": "^1.57.1", 87 | "sass-loader": "^13.2.0", 88 | "source-map-loader": "^3.0.1", 89 | "style-loader": "^3.3.1", 90 | "svelte": "^5.19.6", 91 | "svelte-listener": "git+https://github.com/RedHatter/svelte-listener.git", 92 | "svelte-loader": "^3.2.4", 93 | "terser-webpack-plugin": "^5.3.6", 94 | "ts-jest": "^29.1.1", 95 | "ts-loader": "^9.4.2", 96 | "type-fest": "^3.5.2", 97 | "typescript": "^4.9.5", 98 | "typescript-eslint": "^7.7.0", 99 | "webpack": "^5.75.0", 100 | "webpack-cli": "^4.10.0", 101 | "webpack-dev-server": "^4.11.1", 102 | "zip-webpack-plugin": "^4.0.1" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /extension/src/assets/img/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extension/src/assets/img/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/google.png -------------------------------------------------------------------------------- /extension/src/assets/img/grey-icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/grey-icon-128.png -------------------------------------------------------------------------------- /extension/src/assets/img/grey-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/grey-icon-16.png -------------------------------------------------------------------------------- /extension/src/assets/img/grey-icon-34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/grey-icon-34.png -------------------------------------------------------------------------------- /extension/src/assets/img/grey-icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/grey-icon-48.png -------------------------------------------------------------------------------- /extension/src/assets/img/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/icon-128.png -------------------------------------------------------------------------------- /extension/src/assets/img/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/icon-16.png -------------------------------------------------------------------------------- /extension/src/assets/img/icon-34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/icon-34.png -------------------------------------------------------------------------------- /extension/src/assets/img/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/icon-48.png -------------------------------------------------------------------------------- /extension/src/assets/img/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/assets/img/upload.png -------------------------------------------------------------------------------- /extension/src/containers/Greetings/Greetings.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import icon from '../../assets/img/icon-128.png'; 3 | 4 | class GreetingComponent extends Component { 5 | state = { 6 | name: 'dev', 7 | }; 8 | 9 | render() { 10 | return ( 11 |
12 |

Hello, {this.state.name}!

13 | extension icon 14 |
15 | ); 16 | } 17 | } 18 | 19 | export default GreetingComponent; 20 | -------------------------------------------------------------------------------- /extension/src/containers/Greetings/Greetings.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import icon from '../../assets/img/icon-128.png'; 3 | 4 | class GreetingComponent extends Component { 5 | state = { 6 | name: 'dev', 7 | }; 8 | 9 | render() { 10 | return ( 11 |
12 |

Hello, {this.state.name}!

13 | extension icon 14 |
15 | ); 16 | } 17 | } 18 | 19 | export default GreetingComponent; 20 | -------------------------------------------------------------------------------- /extension/src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.jpg'; 2 | declare module '*.png'; 3 | declare module '*.svg'; 4 | -------------------------------------------------------------------------------- /extension/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "version": "1.2.0", 4 | "name": "Svelte DevTools+", 5 | "description": "Adds Svelte debugging tools to the Chrome Developer Tools", 6 | "options_page": "options.html", 7 | "background": { "service_worker": "background.bundle.js" }, 8 | "action": { 9 | "default_popup": "popup.html", 10 | "default_icon": "icon-34.png" 11 | }, 12 | "icons": { 13 | "128": "icon-128.png" 14 | }, 15 | "content_scripts": [ 16 | { 17 | "world": "MAIN", 18 | "matches": ["http://*/*", "https://*/*", ""], 19 | "js": ["contentScriptMain.bundle.js"], 20 | "run_at": "document_start" 21 | }, 22 | { 23 | "matches": ["http://*/*", "https://*/*", ""], 24 | "js": ["contentScriptIsolated.bundle.js"], 25 | "run_at": "document_start" 26 | } 27 | ], 28 | "devtools_page": "devtools.html", 29 | "web_accessible_resources": [ 30 | { 31 | "resources": ["icon-128.png", "icon-34.png"], 32 | "matches": [] 33 | } 34 | ], 35 | "permissions": ["activeTab"] 36 | } 37 | -------------------------------------------------------------------------------- /extension/src/messenger.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '../Store/slices/highlightedComponentSlice'; 2 | 3 | export type MessageType = 4 | | 'getRootComponent' 5 | | 'getSvelteVersion' 6 | | 'handleClosedPanel' 7 | | 'injectState' 8 | | 'injectSnapshot' 9 | | 'getProfilingData' 10 | | 'returnProfilingData'; 11 | 12 | export interface ChromeMessage { 13 | message: MessageType; 14 | snapshot?: Component; 15 | componentId?: number; 16 | eventTimes?: number[]; 17 | newState?: { 18 | [stateKey: string]: number | string; 19 | }; 20 | } 21 | 22 | export default function sendMessageToChrome( 23 | message: MessageType, 24 | payload: any = null 25 | ) { 26 | chrome.tabs.sendMessage(payload.tab.id!, { 27 | message, 28 | componentId: payload.componentId, 29 | newState: payload.newState, 30 | snapshot: payload.snapshot, 31 | eventTimes: payload.eventTimes, 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /extension/src/pages/Background/index.js: -------------------------------------------------------------------------------- 1 | console.log('This is the background page Put the background scripts here.'); 2 | 3 | // Send a message to the content script to let it know that 4 | // the devtool is closed 5 | function greyOutIcon() { 6 | chrome.action.setIcon( 7 | { 8 | path: { 9 | 34: 'grey-icon-34.png', 10 | 128: 'grey-icon-128.png', 11 | }, 12 | }, 13 | function () { 14 | if (chrome.runtime.lastError) { 15 | console.log('Error setting icon: ' + chrome.runtime.lastError.message); 16 | } 17 | } 18 | ); 19 | } 20 | 21 | async function updateIcon() { 22 | greyOutIcon(); 23 | const [tab] = await chrome.tabs.query({ 24 | active: true, 25 | lastFocusedWindow: true, 26 | }); 27 | chrome.tabs.sendMessage(tab.id, { message: 'getSvelteVersion' }); 28 | } 29 | chrome.tabs.onUpdated.addListener(updateIcon); 30 | chrome.tabs.onActivated.addListener(updateIcon); 31 | 32 | chrome.runtime.onMessage.addListener(function (message) { 33 | if (message.type === 'returnSvelteVersion') { 34 | // If message.svelteVersion is null, the app is not using Svelte 35 | if (message.svelteVersion) { 36 | chrome.action.setIcon( 37 | { 38 | path: { 39 | 34: 'icon-34.png', 40 | 128: 'icon-128.png', 41 | }, 42 | }, 43 | function () { 44 | if (chrome.runtime.lastError) { 45 | console.log( 46 | 'Error setting icon: ' + chrome.runtime.lastError.message 47 | ); 48 | } 49 | } 50 | ); 51 | } 52 | } 53 | }); 54 | 55 | chrome.runtime.onMessage.addListener(function () { 56 | chrome.runtime.sendMessage({ message: 'handleClosedPanel' }); 57 | }); 58 | 59 | greyOutIcon(); 60 | -------------------------------------------------------------------------------- /extension/src/pages/ContentScriptIsolated/contentScriptIsolated.styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/pages/ContentScriptIsolated/contentScriptIsolated.styles.css -------------------------------------------------------------------------------- /extension/src/pages/ContentScriptIsolated/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is the ISOLATED content script! This can communicate with our MAIN content 3 | script. It can also communicate with the Popup and Panel. So it's kind of like 4 | the middle man of this Chrome extension 5 | */ 6 | 7 | // Listens to messages from ContentScriptMain 8 | // and forwards them to other parts of the extension 9 | window.addEventListener('message', async (msg) => { 10 | if ( 11 | typeof msg !== 'object' || 12 | msg === null || 13 | msg.data?.source !== 'ContentScriptMain/index.js' 14 | ) { 15 | return; 16 | } 17 | switch (msg.data.type) { 18 | case 'updateRootComponent': 19 | case 'returnRootComponent': 20 | case 'returnTempRoot': 21 | chrome.runtime.sendMessage({ 22 | type: msg.data.type, 23 | rootComponent: msg.data.rootComponent, 24 | }); 25 | break; 26 | case 'returnSvelteVersion': 27 | chrome.runtime.sendMessage({ 28 | type: msg.data.type, 29 | svelteVersion: msg.data.svelteVersion, 30 | }); 31 | break; 32 | case 'returnProfilingData': 33 | chrome.runtime.sendMessage({ 34 | type: msg.data.type, 35 | eventTimes: msg.data.eventTimes, 36 | }); 37 | break; 38 | default: 39 | break; 40 | } 41 | }); 42 | 43 | // Listens for a message from the Popup and Panel 44 | // Forwards them to ContentScriptMain/index.js 45 | chrome.runtime.onMessage.addListener(function (request) { 46 | switch (request.message) { 47 | case 'returnProfilingData': 48 | chrome.runtime.sendMessage({ 49 | type: request.message, 50 | eventTimes: request.eventTimes, 51 | }); 52 | break; 53 | case 'getProfilingData': 54 | chrome.runtime.sendMessage({ 55 | type: request.message, 56 | }); 57 | break; 58 | case 'getRootComponent': 59 | case 'getSvelteVersion': 60 | case 'handleClosedPanel': 61 | window.postMessage({ 62 | // target: node.parent ? node.parent.id : null, 63 | type: request.message, 64 | source: 'ContentScriptIsolated/index.js', 65 | }); 66 | break; 67 | case 'injectState': 68 | window.postMessage({ 69 | // target: node.parent ? node.parent.id : null, 70 | type: request.message, 71 | newState: request.newState, 72 | componentId: request.componentId, 73 | source: 'ContentScriptIsolated/index.js', 74 | }); 75 | break; 76 | case 'injectSnapshot': 77 | window.postMessage({ 78 | type: request.message, 79 | snapshot: request.snapshot, 80 | source: 'ContentScriptIsolated/index.js', 81 | }); 82 | break; 83 | } 84 | }); 85 | 86 | // NOTE: If you're trying to send a message to a listener in a different 87 | // part of the extension that hasn't been loaded yet, you'll get an error 88 | // like "Unchecked runtime.lastError: Could not establish connection. 89 | // Receiving end does not exist." This happened to Alex when he was 90 | // trying to send a message to a listener inside the Panel component. 91 | // The Panel component hadn't been rendered yet, so no listener had been added. 92 | // Don't be like Alex 93 | -------------------------------------------------------------------------------- /extension/src/pages/ContentScriptMain/contentScriptMain.styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/pages/ContentScriptMain/contentScriptMain.styles.css -------------------------------------------------------------------------------- /extension/src/pages/ContentScriptMain/modules/print.js: -------------------------------------------------------------------------------- 1 | export const printLine = (line) => { 2 | console.log('===> FROM THE PRINT MODULE:', line); 3 | }; 4 | -------------------------------------------------------------------------------- /extension/src/pages/Devtools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /extension/src/pages/Devtools/index.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create('Svelte DevTools+', 'icon-34.png', 'index.html'); 2 | -------------------------------------------------------------------------------- /extension/src/pages/Options/Options.css: -------------------------------------------------------------------------------- 1 | .OptionsContainer { 2 | width: 100%; 3 | height: 50vh; 4 | font-size: 2rem; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | -------------------------------------------------------------------------------- /extension/src/pages/Options/Options.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Options.css'; 3 | 4 | interface Props { 5 | title: string; 6 | } 7 | 8 | const Options: React.FC = ({ title }: Props) => { 9 | return
{title} Page
; 10 | }; 11 | 12 | export default Options; 13 | -------------------------------------------------------------------------------- /extension/src/pages/Options/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/pages/Options/index.css -------------------------------------------------------------------------------- /extension/src/pages/Options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Settings 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /extension/src/pages/Options/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import Options from './Options'; 5 | import './index.css'; 6 | 7 | const container = document.getElementById('app-container'); 8 | const root = createRoot(container); // createRoot(container!) if you use TypeScript 9 | root.render(); 10 | -------------------------------------------------------------------------------- /extension/src/pages/Options/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import Options from './Options'; 5 | import './index.css'; 6 | 7 | const container = document.getElementById('app-container'); 8 | const root = createRoot(container!); // createRoot(container!) if you use TypeScript 9 | root.render(); 10 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/App.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import React from 'react'; 5 | import { render, screen } from '@testing-library/react'; 6 | import { Provider } from 'react-redux'; 7 | import { BrowserRouter } from 'react-router-dom'; 8 | import { jest } from '@jest/globals'; 9 | import { act } from 'react-dom/test-utils'; 10 | import userEvent from '@testing-library/user-event'; 11 | import Panel from './Panel'; 12 | import { createTestStore } from '../../../Store/store'; 13 | 14 | jest.mock('chrome'); 15 | 16 | async function customRender(ui: any, store: any) { 17 | await act(async () => 18 | render( 19 | 20 | 21 | 22 | 23 | 24 | ) 25 | ); 26 | } 27 | 28 | describe('Panel tests', function () { 29 | let store: any; 30 | beforeEach(async () => { 31 | store = createTestStore(); 32 | await customRender(, store); 33 | }); 34 | 35 | afterEach(() => { 36 | chrome.clearListeners(); 37 | chrome.resetMockData(); 38 | jest.clearAllMocks(); 39 | }); 40 | 41 | it('Successfully loads component data', () => { 42 | const app = screen.getByText('App'); 43 | expect(app).toBeInTheDocument(); 44 | }); 45 | 46 | it('Matches the snapshot', () => { 47 | const element = screen.getByTestId('panel'); 48 | expect(element).toMatchSnapshot(); 49 | }); 50 | 51 | it('Expands and collapses the child components', async () => { 52 | // Expand everything 53 | const appButton = screen.getByTestId('expand-button-App'); 54 | await userEvent.click(appButton); 55 | // App's children are now expanded 56 | let boardButton: HTMLElement | null = screen.getByTestId( 57 | 'expand-button-Board' 58 | ); 59 | await userEvent.click(boardButton); 60 | // Board's children are now expanded 61 | let rowButtons = screen.getAllByTestId('expand-button-Row'); 62 | expect(rowButtons.length).toBe(3); 63 | for (let row = 0; row < rowButtons.length; row++) { 64 | const rowButton = rowButtons[row]; 65 | await userEvent.click(rowButton); 66 | const boxButtons = screen.getAllByTestId('component-leaf-Box'); 67 | expect(boxButtons.length).toBe(3 * (row + 1)); 68 | } 69 | // All Row children are now expanded 70 | // Collapse everything 71 | for (let row = 0; row < rowButtons.length; row++) { 72 | const rowButton = rowButtons[row]; 73 | await userEvent.click(rowButton); 74 | const boxButtons = screen.queryAllByTestId('component-leaf-Box'); 75 | expect(boxButtons.length).toBe(3 * (rowButtons.length - row - 1)); 76 | } 77 | // All Row children are now collapsed 78 | await userEvent.click(boardButton); 79 | // Board's children are now collapsed 80 | rowButtons = screen.queryAllByTestId('expand-button-Row'); 81 | expect(rowButtons.length).toBe(0); 82 | await userEvent.click(appButton); 83 | // App's children are now collapsed 84 | boardButton = screen.queryByTestId('expand-button-Board'); 85 | expect(boardButton).toBeNull(); 86 | }); 87 | 88 | it('Properly displays component info', async () => { 89 | // Open App component 90 | const appExpand = screen.getByTestId('expand-button-App'); 91 | await userEvent.click(appExpand); 92 | // Click on Board component 93 | const boardButton = screen.getByTestId('component-button-Board'); 94 | await userEvent.click(boardButton); 95 | // Check Board's turn state 96 | const turnStateButton = screen.getByTestId('state-value-turn'); 97 | expect(turnStateButton.querySelector('p')?.innerHTML).toBe('X'); 98 | }); 99 | 100 | async function changeBoardTurnState(value: string) { 101 | const turnModifier = screen.getByTestId('modifier-turn'); 102 | await userEvent.click(turnModifier); 103 | await userEvent.type(turnModifier, value); 104 | await userEvent.keyboard('{Enter}'); 105 | } 106 | 107 | it('Changes turn state to "Q"', async () => { 108 | // Click on Board component 109 | const appExpand = screen.getByTestId('expand-button-App'); 110 | await userEvent.click(appExpand); 111 | const boardButton = screen.getByTestId('component-button-Board'); 112 | await userEvent.click(boardButton); 113 | const value = 'Q'; 114 | await changeBoardTurnState(value); 115 | // Check if its state has been changed 116 | const turnStateButton = screen.getByTestId('state-value-turn'); 117 | expect(turnStateButton.querySelector('p')?.innerHTML).toBe(value); 118 | }); 119 | 120 | it('Rewinds and reverts state', async () => { 121 | // Click on Board component 122 | const appExpand = screen.getByTestId('expand-button-App'); 123 | await userEvent.click(appExpand); 124 | const boardButton = screen.getByTestId('component-button-Board'); 125 | await userEvent.click(boardButton); 126 | // Change its state to create new snapshots 127 | await changeBoardTurnState('1'); 128 | await changeBoardTurnState('2'); 129 | await changeBoardTurnState('3'); 130 | // Check if its state has changed 131 | let turnStateButton = screen.getByTestId('state-value-turn'); 132 | expect(turnStateButton.querySelector('p')?.innerHTML).toBe('3'); 133 | let rewindButton = screen.getByTestId('rewind-button'); 134 | await userEvent.click(rewindButton); 135 | // Check if rewind was successful 136 | turnStateButton = screen.getByTestId('state-value-turn'); 137 | expect(turnStateButton.querySelector('p')?.innerHTML).toBe('2'); 138 | const forwardButton = screen.getByTestId('revert-button'); 139 | await userEvent.click(forwardButton); 140 | // Check if revert was successful 141 | turnStateButton = screen.getByTestId('state-value-turn'); 142 | expect(turnStateButton.querySelector('p')?.innerHTML).toBe('3'); 143 | // Clicks the mock slider, which sets the snapshot to snapshot #2 144 | const rewinderSlider = screen.getByTestId('rewinder-slider'); 145 | await userEvent.click(rewinderSlider); 146 | turnStateButton = screen.getByTestId('state-value-turn'); 147 | expect(turnStateButton.querySelector('p')?.innerHTML).toBe('1'); 148 | // Delete all snapshots 149 | const clearButton = screen.getByTestId('clear-button'); 150 | await userEvent.click(clearButton); 151 | // Clicking the Slider shouldn't change anything 152 | await userEvent.click(rewinderSlider); 153 | expect(turnStateButton.querySelector('p')?.innerHTML).toBe('1'); 154 | // Going forward shouldn't do anything 155 | await userEvent.click(forwardButton); 156 | expect(turnStateButton.querySelector('p')?.innerHTML).toBe('1'); 157 | // Going back shouldn't do anything 158 | await userEvent.click(rewindButton); 159 | expect(turnStateButton.querySelector('p')?.innerHTML).toBe('1'); 160 | }); 161 | 162 | // Jest does not support svgdom so this is all the testing we can 163 | // do with TreePage 164 | it('Navigates to TreePage', async () => { 165 | const treeButton = screen.getByTestId('tree-link'); 166 | await userEvent.click(treeButton); 167 | const treePage = screen.getByTestId('tree-page'); 168 | expect(treePage).toBeInTheDocument(); 169 | 170 | // This tells our chrome mock to return empty data on the next test 171 | // 'Displays an error message if it can not get component data' must 172 | // come after this function call! 173 | chrome.sendEmptyDataOnNextRequest(); 174 | }); 175 | 176 | it('Displays an error message if it can not get component data', async () => { 177 | const noDataError = screen.getByTestId('no-data-error'); 178 | expect(noDataError.innerHTML).toBe('Unable to get component data'); 179 | }); 180 | }); 181 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/Panel.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | body { 9 | --main-bg-color: #333b4a; 10 | --rewinder-height: 2.5rem; 11 | background-color: var(--main-bg-color); 12 | } 13 | 14 | .container { 15 | color: #eeeeee; 16 | border-radius: 1rem; 17 | box-shadow: 0px 0px 20px white; 18 | } 19 | 20 | .pane { 21 | display: flex; 22 | flex-direction: column; 23 | height: 100%; 24 | background-color: var(--main-bg-color); 25 | font-family: 'Source Sans Pro', sans-serif; 26 | } 27 | 28 | .nav-option { 29 | margin-right: 1rem; 30 | margin-left: 1rem; 31 | border-radius: 0.25rem; 32 | } 33 | 34 | .nav-option:hover { 35 | background-color: rgba(255, 255, 255, 0.1); 36 | } 37 | 38 | .nav-option:active { 39 | background-color: rgba(255, 255, 255, 0.4); 40 | } 41 | 42 | .split { 43 | display: flex; 44 | flex-direction: row; 45 | } 46 | 47 | .gutter { 48 | background-color: #eee; 49 | background-repeat: no-repeat; 50 | background-position: 50%; 51 | } 52 | 53 | .gutter.gutter-horizontal { 54 | width: 6px !important; 55 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); 56 | cursor: col-resize; 57 | } 58 | 59 | .nav { 60 | display: flex; 61 | align-items: stretch; 62 | } 63 | 64 | .nav ul { 65 | display: flex; 66 | /* gap: 1rem; */ 67 | list-style: none; 68 | margin: 0; 69 | padding: 0; 70 | } 71 | 72 | .nav a { 73 | color: inherit; 74 | text-decoration: none; 75 | display: flex; 76 | align-items: stretch; 77 | justify-content: center; 78 | } 79 | 80 | .nav li { 81 | color: rgb(28, 195, 221); 82 | } 83 | 84 | .nav-link { 85 | padding: 0.5rem; 86 | cursor: pointer; 87 | border-radius: 0.25rem; 88 | } 89 | 90 | .nav-link:active { 91 | background-color: rgb(55, 68, 114); 92 | } 93 | .nav-link:hover { 94 | background-color: rgb(114, 137, 218); 95 | } 96 | 97 | .page-content { 98 | height: calc(100% - var(--rewinder-height)); 99 | } 100 | 101 | #treeWrapper, 102 | .container, 103 | .split { 104 | height: 100%; 105 | } 106 | 107 | .container { 108 | height: 100%; 109 | } 110 | 111 | .list-page-gap { 112 | height: 2rem; 113 | } 114 | 115 | header { 116 | height: 3.25rem; 117 | max-height: 3.25rem; 118 | border-bottom: #eeeeee solid 2px; 119 | display: flex; 120 | justify-content: center; 121 | align-items: center; 122 | } 123 | 124 | #no-data-error { 125 | text-align: center; 126 | } 127 | 128 | #left-pane { 129 | height: 100%; 130 | } 131 | 132 | .pane-content { 133 | overflow: overlay; 134 | height: calc(100% - var(--rewinder-height)); 135 | } 136 | 137 | summary { 138 | cursor: pointer; 139 | } 140 | 141 | #last-update-message { 142 | position: absolute; 143 | color: yellow; 144 | margin-left: 1rem; 145 | } 146 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/ChartTree/ChartTree.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import React from 'react'; 3 | import Tree from 'react-d3-tree'; 4 | import './custom-tree.css'; 5 | 6 | const orgChart = {}; 7 | 8 | // Here we're using `renderCustomNodeElement` to bind event handlers 9 | // to the DOM nodes of our choice. 10 | // In this case, we only want the node to toggle if the *label* is clicked. 11 | // Additionally we've replaced the circle's `onClick` with a custom event, 12 | // which differentiates between branch and leaf nodes. 13 | const renderNodeWithCustomEvents = ({ 14 | nodeDatum, 15 | toggleNode, 16 | handleNodeClick, 17 | }) => ( 18 | 19 | handleNodeClick(nodeDatum)} /> 20 | 21 | {nodeDatum.name} (click me to toggle 👋) 22 | 23 | {nodeDatum.attributes?.department && ( 24 | 25 | Department: {nodeDatum.attributes?.department} 26 | 27 | )} 28 | 29 | ); 30 | 31 | import { useCallback, useState } from 'react'; 32 | 33 | export const useCenteredTree = (defaultTranslate = { x: 0, y: 0 }) => { 34 | const [translate, setTranslate] = useState(defaultTranslate); 35 | const containerRef = useCallback((containerElem) => { 36 | if (containerElem !== null) { 37 | const { width, height } = containerElem.getBoundingClientRect(); 38 | setTranslate({ x: width / 2, y: height / 2 }); 39 | } 40 | }, []); 41 | return [translate, containerRef]; 42 | }; 43 | 44 | export default function ChartTree() { 45 | const [translate] = useCenteredTree(); 46 | return ( 47 |
48 | 55 | renderNodeWithCustomEvents({ ...rd3tProps }) 56 | } 57 | /> 58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/ComponentInfo/ComponentInfo.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* --color: #eeeeee; */ 3 | /* --color: gold; */ 4 | /* --color: rgb(1, 184, 212); */ 5 | --color: rgb(28, 195, 221); 6 | } 7 | 8 | .pane h2 { 9 | text-align: center; 10 | color: var(--color); 11 | margin: 0; 12 | } 13 | 14 | .pane h3 { 15 | color: var(--color); 16 | font-weight: 600; 17 | padding-left: 0.7rem; 18 | } 19 | 20 | .component-info-ul { 21 | list-style: none; 22 | padding-left: 1.5rem; 23 | width: 100%; 24 | } 25 | 26 | .pane li { 27 | margin: 0; 28 | padding: 0; 29 | margin-bottom: 1rem; 30 | } 31 | 32 | .pane li p { 33 | padding: 0; 34 | margin: 0; 35 | } 36 | 37 | .property-item { 38 | display: flex; 39 | } 40 | .property-name { 41 | /* color: #eeeeee; */ 42 | color: #c6c6c6; 43 | font-family: Menlo, monospace; 44 | } 45 | 46 | .constant-property { 47 | font-family: Menlo, monospace; 48 | /* margin-left: 0.5rem; */ 49 | } 50 | 51 | .state-value { 52 | display: flex; 53 | margin-bottom: 0.5rem; 54 | } 55 | 56 | #component-header { 57 | margin: 1rem; 58 | color: #eeeeee; 59 | border: none; 60 | } 61 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/ComponentInfo/ComponentInfo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { 4 | Component, 5 | selectHighlightedComponent, 6 | } from '../../../../../Store/slices/highlightedComponentSlice'; 7 | import './ComponentInfo.css'; 8 | import { StateValue } from '../StateValue/StateValue'; 9 | 10 | // ComponentInfo displays all information about the selected component 11 | // and allows you to modify its state and props 12 | const ComponentInfo = () => { 13 | const highlightedComponent: Component = useSelector( 14 | selectHighlightedComponent 15 | ); 16 | 17 | return ( 18 |
19 |
20 |

{highlightedComponent.tagName}

21 |
22 |
23 |

State

24 | {highlightedComponent.detail.ctx && ( 25 |
    26 | {/* state is any, because a component's state can be anything */} 27 | {highlightedComponent.detail.ctx.map( 28 | (state: any, index: number) => ( 29 |
  • 33 |

    {state.key}:

    34 | 40 |
  • 41 | ) 42 | )} 43 |
44 | )} 45 |

Props

46 | {highlightedComponent.detail.attributes && ( 47 |
    48 | {/* prop is any, because a component's props can be anything */} 49 | {highlightedComponent.detail.attributes.map( 50 | (prop: any, index: number) => ( 51 |
  • 55 |

    {prop.key}:

    56 | 62 |
  • 63 | ) 64 | )} 65 |
66 | )} 67 |
68 |
69 | ); 70 | }; 71 | 72 | export default ComponentInfo; 73 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/Navbar/Navbar.css: -------------------------------------------------------------------------------- 1 | .nav { 2 | /* background-color: #2a2f3a; */ 3 | max-height: 45px; 4 | font-size: 20px; 5 | color: var(--color); 6 | display: flex; 7 | justify-content: space-around; 8 | font-family: 'Source Sans Pro', sans-serif; 9 | font-weight: 200px; 10 | padding-top: 5px; 11 | } 12 | 13 | .nav a { 14 | font-family: 'Source Sans Pro', sans-serif; 15 | font-weight: bold; 16 | } 17 | 18 | /* .tree:hover, 19 | .step:hover { 20 | border-top: 2px solid rgb(0, 94, 108); 21 | color: #c2c2c2; 22 | } */ 23 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/Navbar/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../TreeComponent/TreeComponent.css'; 3 | import { NavLink } from 'react-router-dom'; 4 | import './Navbar.css'; 5 | 6 | // add an event listener that would listen for a click on each li 7 | 8 | export default function Navbar() { 9 | const activeColor = 'rgb(28, 195, 221)'; 10 | const inactiveColor = '#fff'; 11 | const activeBackground = 'rgba(255, 255, 255, 0.25)'; 12 | const inactiveBackground = 'rgba(0, 0, 0, 0)'; 13 | 14 | interface Active { 15 | isActive: boolean; 16 | } 17 | 18 | const buttonStyle = ({ isActive }: Active) => ({ 19 | color: isActive ? activeColor : inactiveColor, 20 | background: isActive ? activeBackground : inactiveBackground, 21 | '&:hover': { 22 | background: '#ff0000', 23 | }, 24 | }); 25 | 26 | return ( 27 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/Rewinder/Rewinder.css: -------------------------------------------------------------------------------- 1 | #rewinder { 2 | width: 100%; 3 | height: var(--rewinder-height); 4 | background-color: var(--main-bg-color); 5 | border: 0.5px solid #f7f7f7b5; 6 | box-shadow: 0px 0px 3px #c4c3c3; 7 | position: absolute; 8 | bottom: 0px; 9 | display: flex; 10 | border-radius: 8px; 11 | } 12 | 13 | #slider-container { 14 | width: 100%; 15 | height: 100%; 16 | margin-left: 3rem; 17 | margin-right: 3rem; 18 | display: flex; 19 | align-items: center; 20 | } 21 | 22 | #back-button, 23 | #forward-button, 24 | #clear-button { 25 | border: 0.5px solid #676767a7; 26 | width: 3rem; 27 | cursor: pointer; 28 | background-color: var(--main-bg-color); 29 | color: rgb(28, 195, 221); 30 | /* color: #eeeeee; */ 31 | } 32 | 33 | #clear-button { 34 | font-size: 0.6rem; 35 | width: 100px; 36 | } 37 | 38 | #back-button:hover, 39 | #forward-button:hover, 40 | #clear-button:hover { 41 | background-color: #525c71; /*#444e62*/ 42 | color: #eeeeee; 43 | opacity: 1; 44 | } 45 | 46 | .MuiSlider-track, 47 | .MuiSlider-thumb { 48 | color: rgb(28, 195, 221); 49 | } 50 | 51 | .MuiSlider-rail { 52 | color: rgb(189, 236, 255); 53 | } 54 | 55 | .Mui-disabled { 56 | opacity: 0; 57 | } 58 | 59 | .css-17lmo96 { 60 | background-color: rgb(255, 255, 255) !important; 61 | width: 5px !important; 62 | height: 15px !important; 63 | } 64 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/Rewinder/Rewinder.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler, useEffect, useState } from 'react'; 2 | import './Rewinder.css'; 3 | import Slider from '@mui/material/Slider'; 4 | 5 | interface RewinderProps { 6 | numberOfSnapshots: number; 7 | changeSnapshot: (snapshotIndex: number) => void; 8 | clearSnapshotHistory: MouseEventHandler; 9 | } 10 | 11 | // This is the tool that allows you to move back and forth between 12 | // snapshots of application state 13 | export default function Rewinder({ 14 | numberOfSnapshots, 15 | changeSnapshot, 16 | clearSnapshotHistory, 17 | }: RewinderProps) { 18 | const [sliderValue, setSliderValue] = useState(numberOfSnapshots); 19 | function handleChange(event: Event, value: number | Array) { 20 | if (Array.isArray(value)) return; 21 | setSliderValue(value); 22 | } 23 | 24 | function handleChangeCommitted( 25 | event: React.SyntheticEvent | Event, 26 | value: number | Array 27 | ) { 28 | if (Array.isArray(value)) return; 29 | changeSnapshot(value - 1); 30 | } 31 | 32 | function goBack() { 33 | if (sliderValue <= 1) return; 34 | changeSnapshot(sliderValue - 2); 35 | setSliderValue(sliderValue - 1); 36 | } 37 | 38 | function goForward() { 39 | if (sliderValue >= numberOfSnapshots) return; 40 | changeSnapshot(sliderValue); 41 | setSliderValue(sliderValue + 1); 42 | } 43 | 44 | useEffect(() => { 45 | setSliderValue(numberOfSnapshots); 46 | }, [numberOfSnapshots]); 47 | 48 | const disabled = numberOfSnapshots <= 1 ? true : false; 49 | return ( 50 |
51 | 58 |
59 | 71 |
72 | 75 | 82 |
83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/StateModifier/StateModifier.css: -------------------------------------------------------------------------------- 1 | .state-modifier { 2 | margin-left: 0.5rem; 3 | } 4 | 5 | .state-display { 6 | cursor: pointer; 7 | text-align: center; 8 | color: #ff6ce9; 9 | font-family: Menlo, monospace; 10 | } 11 | 12 | .state-mod-input { 13 | display: none; 14 | } 15 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/StateModifier/StateModifier.tsx: -------------------------------------------------------------------------------- 1 | import React, { ChangeEvent, useRef, useState } from 'react'; 2 | import './StateModifier.css'; 3 | import sendMessageToChrome from '../../../../messenger'; 4 | import { useDispatch } from 'react-redux'; 5 | 6 | interface StateModifierProps { 7 | componentId: number; 8 | stateKey: string; 9 | initValue: number | string; 10 | } 11 | /* 12 | The StateModifier component is a modifiable piece of state for the 13 | selected component. When the user clicks on it, the text transforms into 14 | a text input field. Then when the user and hits enter, or clicks 15 | somewhere else, the text in the input field gets injected into the 16 | Svelte component 17 | */ 18 | const StateModifier = ({ 19 | componentId, 20 | stateKey, 21 | initValue, 22 | }: StateModifierProps) => { 23 | const [inputValue, setInputValue] = useState(String(initValue)); 24 | const input = useRef(null); 25 | const display = useRef(null); 26 | 27 | const dispatch = useDispatch(); 28 | 29 | function handleClickToEdit() { 30 | if (display.current === null || input.current === null) return; 31 | display.current.style.display = 'none'; 32 | input.current.style.display = 'block'; 33 | input.current.focus(); 34 | input.current.select(); 35 | } 36 | 37 | function finishEdit() { 38 | if (!display.current || !input.current) return; 39 | display.current.style.display = 'block'; 40 | input.current.style.display = 'none'; 41 | } 42 | 43 | async function handleSubmit() { 44 | try { 45 | const newState = { [stateKey]: inputValue }; 46 | const [tab] = await chrome.tabs.query({ 47 | active: true, 48 | lastFocusedWindow: true, 49 | }); 50 | dispatch({ 51 | type: 'timedEvents/addNewEvent', 52 | payload: { 53 | type: 'sendMessage', 54 | data: performance.now(), 55 | }, 56 | }); 57 | sendMessageToChrome('injectState', { 58 | tab: tab, 59 | componentId, 60 | newState, 61 | }); 62 | finishEdit(); 63 | } catch (err) { 64 | console.log(err); 65 | } 66 | } 67 | 68 | function handleKeyPress(e: React.KeyboardEvent) { 69 | // Losing focus will call handleSubmit() 70 | if (e.code === 'Enter' && input.current !== null) input.current.blur(); 71 | } 72 | 73 | function handleChange(e: ChangeEvent) { 74 | setInputValue(e.target.value); 75 | } 76 | 77 | return ( 78 |
79 |
80 | 89 |
90 |
96 |

{inputValue !== '' ? inputValue : '""'}

97 |
98 |
99 | ); 100 | }; 101 | 102 | export default StateModifier; 103 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/StateValue/StateValue.css: -------------------------------------------------------------------------------- 1 | .property-item { 2 | margin-left: 0.25rem; 3 | } 4 | 5 | li .component-info-array-list-item { 6 | margin-bottom: 0; 7 | list-style: none; 8 | } 9 | 10 | .state-value-summary { 11 | /* color: rgb(117, 117, 255); */ 12 | color: rgb(207, 142, 253); 13 | } 14 | 15 | .function-definition { 16 | padding-right: 1rem; 17 | } 18 | 19 | /*react-syntax-highligher was enforcing overflow-x. This was creating 20 | an ugly effect when expanding the window */ 21 | .code-block { 22 | overflow: visible !important; 23 | } 24 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/StateValue/StateValue.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import StateModifier from '../StateModifier/StateModifier'; 3 | import './StateValue.css'; 4 | import SyntaxHighlighter from 'react-syntax-highlighter'; 5 | import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/hljs'; 6 | 7 | interface StateValueProps { 8 | value: any; 9 | stateKey: string; 10 | componentId: number; 11 | isArray: boolean; 12 | } 13 | 14 | function replaceTabsWithSpaces(string: string) { 15 | return string.replace(/\t/g, ' '); 16 | } 17 | 18 | /* 19 | Each item under State and Props on the right side of the app is a StateValue 20 | This component displays information about the item, and allows you to 21 | modify it if the data type is modifiable 22 | */ 23 | export const StateValue = ({ 24 | value, 25 | stateKey, 26 | componentId, 27 | isArray, 28 | }: StateValueProps) => { 29 | return ( 30 |
35 | {Array.isArray(value) ? ( 36 |
37 | {`Array [${value.length}]`} 38 |
    39 | {/* Render an un-modifiable StateValue component if this data type 40 | can not be modified */} 41 | {value.map((i: any, index: number) => { 42 | return ( 43 |
  • 47 | 53 |
  • 54 | ); 55 | })} 56 |
57 |
58 | ) : value === null ? ( 59 | 'null' 60 | ) : value === false ? ( 61 | 'false' 62 | ) : value === true ? ( 63 | 'true' 64 | ) : typeof value === 'number' || typeof value === 'string' ? ( 65 | // Render a StateModifier component if this data type can be modified 66 | !isArray ? ( 67 | 72 | ) : ( 73 |
{value}
74 | ) 75 | ) : typeof value === 'object' ? ( 76 | Object.prototype.hasOwnProperty.call(value, '__isFunction') && 77 | value.__isFunction === true ? ( 78 |
79 |
80 | 81 | function 82 | 83 |
84 | 91 | {replaceTabsWithSpaces(value.source)} 92 | 93 |
94 |
95 |
96 | ) : null 97 | ) : null} 98 |
99 | ); 100 | }; 101 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/TreeComponent/TreeComponent.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background: rgb(53, 43, 60, 00); 3 | --background-hover: rgb(144, 144, 203, 0.25); 4 | --background-focused: rgba(85, 59, 104, 0.5); 5 | --highlight: rgba(161, 255, 247, 0.25); 6 | --cyan: rgb(0, 136, 157); 7 | --darker-cyan: rgb(0, 94, 108); 8 | --darkest-cyan: rgb(0, 65, 75); 9 | 10 | --grey: rgb(51, 51, 51); 11 | --black: rgb(38, 38, 38); 12 | --lightGrey: rgb(235, 235, 235); 13 | } 14 | 15 | .Collapsible__trigger { 16 | cursor: pointer; 17 | width: 100rem; 18 | } 19 | 20 | .Collapsible__trigger:focus { 21 | background-color: var(--darkest-cyan); 22 | } 23 | 24 | .Collapsible__trigger:hover { 25 | background: var(--darker-cyan); 26 | } 27 | 28 | .Collapsible { 29 | overflow: visible; 30 | } 31 | 32 | .Collapsible__contentOuter { 33 | background-color: rgb(00, 00, 00, 00); 34 | overflow: visible; 35 | } 36 | .Collapsible__contentInner { 37 | padding-left: 1rem; 38 | border: 1px solid var(--lightGrey); 39 | border-top: 0; 40 | 41 | p { 42 | margin-bottom: 10px; 43 | font-size: 14px; 44 | line-height: 20px; 45 | 46 | &:last-child { 47 | margin-bottom: 0; 48 | } 49 | } 50 | } 51 | 52 | .Collapsible__trigger { 53 | display: block; 54 | font-weight: 400; 55 | text-decoration: none; 56 | position: relative; 57 | padding: 0rem; 58 | background: var(--cyan); 59 | color: white; 60 | 61 | &:after { 62 | font-family: 'FontAwesome'; 63 | content: '\f107'; 64 | position: absolute; 65 | right: 10px; 66 | top: 10px; 67 | display: block; 68 | transition: transform 300ms; 69 | } 70 | 71 | &.is-open { 72 | &:after { 73 | transform: rotateZ(180deg); 74 | } 75 | } 76 | 77 | &.is-disabled { 78 | opacity: 0.5; 79 | background-color: grey; 80 | } 81 | } 82 | 83 | .CustomTriggerCSS { 84 | background-color: lightcoral; 85 | transition: background-color 200ms ease; 86 | } 87 | 88 | .CustomTriggerCSS--open { 89 | background-color: darkslateblue; 90 | } 91 | 92 | .Collapsible__custom-sibling { 93 | padding: 5px; 94 | font-size: 12px; 95 | background-color: #cbb700; 96 | color: black; 97 | } 98 | 99 | .MuiCollapse-root { 100 | background-color: rgba(0, 0, 0, 0); 101 | } 102 | 103 | .tree-component-bar { 104 | width: 100%; 105 | height: 1rem; 106 | margin-bottom: 2px; 107 | font-family: Menlo, monospace; 108 | color: rgb(201, 201, 201); 109 | background-color: var(--background); 110 | user-select: none; 111 | border: none; 112 | text-align: left; 113 | } 114 | 115 | .tree-component-bar:hover { 116 | background-color: var(--background-hover); 117 | cursor: pointer; 118 | } 119 | 120 | .tree-component-bar:active { 121 | background-color: var(--background-focused); 122 | cursor: pointer; 123 | } 124 | 125 | .tree-component-bar:focus { 126 | background-color: var(--highlight); 127 | cursor: pointer; 128 | } 129 | 130 | .tree-component-bar:focus { 131 | cursor: pointer; 132 | } 133 | 134 | .tree-component { 135 | display: flex; 136 | } 137 | 138 | .component-name { 139 | color: rgb(28, 195, 221); 140 | } 141 | 142 | .expand-button { 143 | height: 1rem; 144 | width: 1rem; 145 | background-color: rgba(0, 0, 0, 0); 146 | cursor: pointer; 147 | } 148 | 149 | .expand-button-container { 150 | height: 1rem; 151 | background-color: rgba(0, 0, 0, 0); 152 | width: 1rem; 153 | border: none; 154 | } 155 | 156 | #tree-content { 157 | height: 100%; 158 | border-radius: 0.5rem; 159 | background-color: rgba(255, 255, 255, 0.1); 160 | } 161 | 162 | #tree { 163 | margin-bottom: 10rem; 164 | } 165 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/TreeComponent/TreeComponent.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react'; 2 | import './TreeComponent.css'; 3 | import { useDispatch } from 'react-redux'; 4 | import { 5 | Component, 6 | selectHighlightedComponent, 7 | } from '../../../../../Store/slices/highlightedComponentSlice'; 8 | import { useSelector } from 'react-redux'; 9 | import { Collapse } from '@mui/material'; 10 | import disclosure from '../../disclosure.png'; 11 | import disclosureOpen from '../../disclosure-open.png'; 12 | 13 | interface TreeComponentProps { 14 | componentData: Component; 15 | level: number; 16 | } 17 | 18 | /* 19 | A TreeComponent is each component in the list visualization page. 20 | It shows up as simple text, and has an array of TreeComponents as children. 21 | Clicking on the TreeComponent toggles visibility of its children. 22 | */ 23 | const TreeComponent: React.FC = ({ 24 | componentData, 25 | level, 26 | }: TreeComponentProps) => { 27 | const childrenState: Array = []; 28 | if (componentData.children) { 29 | componentData.children.forEach((child: Component) => { 30 | childrenState.push( 31 | 32 | ); 33 | }); 34 | } 35 | 36 | const highlightedComponent: Component = useSelector( 37 | selectHighlightedComponent 38 | ); 39 | 40 | useEffect(() => { 41 | if (highlightedComponent.id === componentData.id) { 42 | dispatch({ 43 | type: 'highlightedComponent/updateHighlightedComponent', 44 | payload: componentData, 45 | }); 46 | } 47 | }, [componentData]); 48 | 49 | const [open, setOpen] = useState(false); 50 | const dispatch = useDispatch(); 51 | 52 | const handleExpand = useCallback( 53 | function () { 54 | setOpen(open ? false : true); 55 | }, 56 | [open] 57 | ); 58 | 59 | const handleHighlight = useCallback( 60 | function () { 61 | dispatch({ 62 | type: 'highlightedComponent/setHighlightedComponent', 63 | payload: { 64 | tagName: componentData.tagName, 65 | detail: componentData.detail, 66 | id: componentData.id, 67 | }, 68 | }); 69 | }, 70 | [dispatch] 71 | ); 72 | 73 | const collapsePadding = `${level}rem`; 74 | return ( 75 |
76 | {childrenState.length > 0 ? ( 77 |
78 |
79 | {open ? ( 80 | 87 | ) : ( 88 | 95 | )} 96 | 105 |
106 | 112 |
113 | {childrenState.map((item) => item)} 114 |
115 |
116 |
117 | ) : ( 118 | 128 | )} 129 |
130 | ); 131 | }; 132 | 133 | export default TreeComponent; 134 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelComponents/custom-tree.css: -------------------------------------------------------------------------------- 1 | .node__root > circle { 2 | fill: white; 3 | color: white; 4 | } 5 | 6 | .node__branch > circle { 7 | fill: white; 8 | } 9 | 10 | .node__leaf > circle { 11 | fill: white; 12 | } 13 | 14 | .rd3t-label__title > circle { 15 | color: white; 16 | } 17 | 18 | .text { 19 | fill: white; 20 | } 21 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelPages/ListPage/ListPage.css: -------------------------------------------------------------------------------- 1 | #root-container { 2 | padding-top: 2rem; 3 | padding-bottom: 2rem; 4 | } 5 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelPages/ListPage/ListPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './ListPage.css'; 3 | import TreeComponent from '../../PanelComponents/TreeComponent/TreeComponent'; 4 | import { ComponentPageProps } from '../../Panel'; 5 | 6 | // The page for List visualization 7 | const ListPage: React.FC = ({ 8 | rootComponentData, 9 | }: ComponentPageProps) => { 10 | return ( 11 |
12 |
13 | {rootComponentData && ( 14 | 19 | )} 20 |
21 |
22 | ); 23 | }; 24 | 25 | export default ListPage; 26 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelPages/TreePage/TreePage.css: -------------------------------------------------------------------------------- 1 | #tree-error-message { 2 | color: red; 3 | text-align: center; 4 | margin-top: 0.5rem; 5 | } 6 | 7 | .leaf-node { 8 | cursor: default; 9 | } 10 | 11 | .node-text { 12 | font-size: 12; 13 | fill: white; 14 | stroke: none; 15 | stroke-width: 1; 16 | } 17 | 18 | .tree-node-circle:hover { 19 | filter: drop-shadow(0px 0px 6px rgba(0, 0, 0, 0.7)); 20 | } 21 | 22 | .tree-node-circle:active { 23 | filter: drop-shadow(0px 0px 6px rgba(238, 255, 0, 0.7)); 24 | } 25 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/PanelPages/TreePage/TreePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useCallback, useState, useEffect } from 'react'; 2 | import Tree, { CustomNodeElementProps, TreeNodeDatum } from 'react-d3-tree'; 3 | import './TreePage.css'; 4 | import { useDispatch } from 'react-redux'; 5 | import { Component } from '../../../../../Store/slices/highlightedComponentSlice'; 6 | 7 | interface TreePageProps { 8 | rootComponentData: Component; // Define the type based on your actual data structure 9 | } 10 | 11 | const emptyNode: TreeNodeDatum = { 12 | __rd3t: { 13 | id: '0', 14 | depth: 0, 15 | collapsed: true, 16 | }, 17 | name: 'Error', 18 | attributes: {}, 19 | children: [], 20 | }; 21 | 22 | /* 23 | Setting up custom tree 24 | Here we're using `renderCustomNodeElement` to bind event handlers 25 | to the DOM nodes of our choice. 26 | In this case, we only want the node to toggle if the *label* is clicked. 27 | Additionally we've replaced the circle's `onClick` with a custom event, 28 | which differentiates between branch and leaf nodes. 29 | */ 30 | 31 | function createNodeText(name: string, onClick?: () => void): JSX.Element { 32 | let className = 'node-text'; 33 | if (!onClick) { 34 | className += ' leaf-node'; 35 | } 36 | const nodeText = ( 37 | 44 | {name} 45 | 46 | ); 47 | return nodeText; 48 | } 49 | 50 | const renderNodeWithCustomEvents = ( 51 | nodeDatum: TreeNodeDatum, 52 | toggleNode: () => void, 53 | handleNodeClick: (rootComponentData: TreeNodeDatum) => void 54 | ) => { 55 | return ( 56 | 57 | handleNodeClick(nodeDatum)} 62 | /> 63 | {nodeDatum.children && nodeDatum.children.length !== 0 64 | ? createNodeText(nodeDatum.name, toggleNode) 65 | : createNodeText(nodeDatum.name)} 66 | {nodeDatum.attributes?.department && ( 67 | 68 | (collapse) 69 | 70 | )} 71 | 72 | ); 73 | }; 74 | 75 | // Function responsible from parsing data and putting it into right format 76 | // Returns an empty object if the input can not be converted 77 | function convertToObject(input: Component, depth = 0): TreeNodeDatum { 78 | if (input === undefined || input == null) { 79 | return emptyNode; 80 | } 81 | const { tagName, children, detail, id } = input; 82 | if ( 83 | tagName === undefined || 84 | children === undefined || 85 | detail === undefined || 86 | id === undefined 87 | ) { 88 | return emptyNode; 89 | } 90 | const newObj: TreeNodeDatum = { 91 | __rd3t: { 92 | id: '0', 93 | depth: depth, 94 | collapsed: false, 95 | }, 96 | name: tagName, 97 | attributes: detail, 98 | children: [], 99 | }; 100 | if (children && children.length > 0) { 101 | newObj.children = children.map( 102 | (child): TreeNodeDatum => convertToObject(child, depth + 1) 103 | ); 104 | } 105 | return newObj; 106 | } 107 | 108 | // The page for Tree visualization 109 | const TreePage: React.FC = ({ 110 | rootComponentData, 111 | }: TreePageProps) => { 112 | const dispatch = useDispatch(); 113 | const elementRef = useRef(null); 114 | const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); 115 | const [orgChart, setOrgChart] = useState(emptyNode); 116 | const [errorMessage, setErrorMessage] = useState(''); 117 | 118 | const handleNodeClick = useCallback( 119 | (rootComponentData: TreeNodeDatum) => { 120 | dispatch({ 121 | type: 'highlightedComponent/setHighlightedComponent', 122 | payload: { 123 | tagName: rootComponentData.name, 124 | detail: rootComponentData.attributes, 125 | id: rootComponentData.__rd3t.id, 126 | }, 127 | }); 128 | }, 129 | [dispatch] 130 | ); 131 | 132 | useEffect(() => { 133 | if (elementRef.current) { 134 | const { offsetWidth, offsetHeight } = elementRef.current; 135 | setDimensions({ width: offsetWidth, height: offsetHeight }); 136 | } 137 | const data = convertToObject(rootComponentData); 138 | if (data === emptyNode) { 139 | setErrorMessage('Unable to get node data'); 140 | } 141 | setOrgChart(data); 142 | }, [rootComponentData]); 143 | 144 | return ( 145 |
146 | {errorMessage ? ( 147 |
{errorMessage}
148 | ) : ( 149 |
150 | 155 | renderNodeWithCustomEvents( 156 | rd3tNodeProps.nodeDatum, 157 | rd3tNodeProps.toggleNode, 158 | handleNodeClick 159 | ) 160 | } 161 | zoomable={true} 162 | orientation="horizontal" 163 | /> 164 |
165 | )} 166 |
167 | ); 168 | }; 169 | 170 | export default TreePage; 171 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/__snapshots__/App.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Panel tests Matches the snapshot 1`] = ` 4 |
8 |
11 |
14 |
18 |
19 | 47 |
48 |
51 |
54 |
58 |
61 |
62 |
65 | 74 | 86 |
87 |
88 |
89 |
90 |
91 |
92 |

95 | Last update took 0.34ms 96 |

97 |
98 |
99 |
100 |
104 |
107 |
110 |
111 |

112 |

113 |
116 |

117 | State 118 |

119 |

120 | Props 121 |

122 |
123 |
124 |
125 |
126 |
127 |
130 | 136 |
139 |
142 |
143 | 149 | 155 |
156 |
157 | `; 158 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/disclosure-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/pages/Panel/disclosure-open.png -------------------------------------------------------------------------------- /extension/src/pages/Panel/disclosure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/pages/Panel/disclosure.png -------------------------------------------------------------------------------- /extension/src/pages/Panel/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/extension/src/pages/Panel/index.css -------------------------------------------------------------------------------- /extension/src/pages/Panel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dev Tools Panel 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /extension/src/pages/Panel/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { Provider } from 'react-redux'; 4 | import Panel from './Panel'; 5 | import './index.css'; 6 | import { store } from '../../../Store/store'; 7 | import { BrowserRouter } from 'react-router-dom'; 8 | 9 | const container = document.getElementById('app-container'); 10 | const root = createRoot(container!); 11 | 12 | root.render( 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /extension/src/pages/Popup/Popup.css: -------------------------------------------------------------------------------- 1 | .popup-container { 2 | margin: 1rem; 3 | margin-left: 0; 4 | } 5 | 6 | .popup-button { 7 | width: 4rem; 8 | height: 3rem; 9 | background-color: white; 10 | border-width: 1.5px; 11 | border-radius: 4px; 12 | border-color: #111111; 13 | margin: 0 1rem 0 1rem; 14 | cursor: pointer; 15 | } 16 | 17 | .popup-button img { 18 | width: 1.75rem; 19 | height: 1.75rem; 20 | } 21 | 22 | .popup-button:hover { 23 | transition: 300ms; 24 | background-color: rgb(231, 231, 231); 25 | } 26 | 27 | .popup-button:active { 28 | transition: 100ms; 29 | background-color: rgb(82, 82, 82); 30 | } 31 | -------------------------------------------------------------------------------- /extension/src/pages/Popup/Popup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './Popup.css'; 3 | import sendMessageToChrome from '../../messenger'; 4 | 5 | import google from '../../assets/img/google.png'; 6 | import upload from '../../assets/img/upload.png'; 7 | 8 | const Popup = () => { 9 | const [svelteVersion, setSvelteVersion] = useState(null); 10 | const [errorMessage, setErrorMessage] = useState(''); 11 | 12 | useEffect(() => { 13 | // Sends a message to ContentScriptIsolated, telling it to get the 14 | // current tab's Svelte version. If our response is null, that means 15 | // the app on the current tab is not using Svelte 16 | async function getSvelteVersion() { 17 | // Get tab the user is on 18 | const [tab] = await chrome.tabs.query({ 19 | active: true, 20 | lastFocusedWindow: true, 21 | }); 22 | // Tabs without webpages on them (like new tabs and the extension page) 23 | // All start like 'chrome://' We obviously can't get any DOM data from 24 | // them, so we'll exit the function here, and display an error message 25 | if (tab.url!.startsWith('chrome://')) { 26 | setErrorMessage( 27 | 'This is a restricted browser page. Svelte DevTools+ cannot access this page.' 28 | ); 29 | return; 30 | } 31 | sendMessageToChrome('getSvelteVersion', { tab }); 32 | } 33 | getSvelteVersion(); 34 | }, []); 35 | 36 | async function getProfilingData() { 37 | const [tab] = await chrome.tabs.query({ 38 | active: true, 39 | lastFocusedWindow: true, 40 | }); 41 | sendMessageToChrome('getProfilingData', { tab }); 42 | } 43 | 44 | // Listen for response from ContentScriptIsolated. This is where we 45 | // get the current tab's Svelte version, and update Popup's state 46 | chrome.runtime.onMessage.addListener(function (message) { 47 | if (message.type === 'returnSvelteVersion') { 48 | // If message.svelteVersion is null, the app is not using Svelte 49 | if (message.svelteVersion) setSvelteVersion(message.svelteVersion); 50 | } else if (message.type === 'returnProfilingData') { 51 | console.log(message); 52 | console.log('message.eventTimes:', message.eventTimes); 53 | } 54 | }); 55 | 56 | const openNewTab = () => { 57 | window.open('https://localhost:3001', '_blank'); 58 | }; 59 | 60 | return ( 61 |
62 | {errorMessage ? ( 63 |

{errorMessage}

64 | ) : ( 65 |
66 | {svelteVersion ? ( 67 |
68 |

69 | This page is using Svelte! 70 |
71 | Version: {svelteVersion} 72 |

73 |
74 | ) : ( 75 |

76 | This page is not using a development build of 77 | Svelte 78 |

79 | )} 80 |
81 | )} 82 | {/*
83 |

84 | Login with 85 |

86 | 94 |
95 | 96 |
97 |

98 | Get profiling data 99 |

100 | 108 |
*/} 109 |
110 | ); 111 | }; 112 | 113 | export default Popup; 114 | -------------------------------------------------------------------------------- /extension/src/pages/Popup/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-width: 360px; 3 | min-height: 33px; 4 | background-color: #e4e3e3; 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 6 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 7 | sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | border: 1px solid lightseagreen; 11 | /* position: relative; */ 12 | } 13 | 14 | body p { 15 | align-items: center; 16 | display: block; 17 | margin-block-start: 1em; 18 | margin-block-end: 1em; 19 | margin-inline-start: 0px; 20 | margin-inline-end: 0px; 21 | padding-inline-start: 17px; 22 | font-weight: 300; 23 | } 24 | code { 25 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 26 | monospace; 27 | } 28 | -------------------------------------------------------------------------------- /extension/src/pages/Popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Popup 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /extension/src/pages/Popup/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import Popup from './Popup'; 5 | import './index.css'; 6 | 7 | const container = document.getElementById('app-container'); 8 | const root = createRoot(container); // createRoot(container!) if you use TypeScript 9 | root.render(); 10 | -------------------------------------------------------------------------------- /extension/src/pages/Popup/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import Popup from './Popup'; 5 | import './index.css'; 6 | 7 | const container = document.getElementById('app-container'); 8 | const root = createRoot(container!); 9 | root.render(); 10 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/README.md: -------------------------------------------------------------------------------- 1 | # Svelte + TS + Vite 2 | 3 | This template should help get you started developing with Svelte and TypeScript in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). 8 | 9 | ## Need an official Svelte framework? 10 | 11 | Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. 12 | 13 | ## Technical considerations 14 | 15 | **Why use this over SvelteKit?** 16 | 17 | - It brings its own routing solution which might not be preferable for some users. 18 | - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. 19 | 20 | This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. 21 | 22 | Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. 23 | 24 | **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** 25 | 26 | Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. 27 | 28 | **Why include `.vscode/extensions.json`?** 29 | 30 | Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. 31 | 32 | **Why enable `allowJs` in the TS template?** 33 | 34 | While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. 35 | 36 | **Why is HMR not preserving my local component state?** 37 | 38 | HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). 39 | 40 | If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. 41 | 42 | ```ts 43 | // store.ts 44 | // An extremely simple external store 45 | import { writable } from 'svelte/store'; 46 | export default writable(0); 47 | ``` 48 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Svelte + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "panel", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" 11 | }, 12 | "devDependencies": { 13 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 14 | "@tsconfig/svelte": "^5.0.4", 15 | "@types/chrome": "^0.0.301", 16 | "@types/d3": "^7.4.3", 17 | "svelte": "^5.15.0", 18 | "svelte-check": "^4.1.1", 19 | "typescript": "~5.6.2", 20 | "vite": "^6.0.5" 21 | }, 22 | "dependencies": { 23 | "d3": "^7.9.0", 24 | "svelte-highlight": "^7.8.2", 25 | "svelte-spa-router": "^4.0.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/App.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 | 9 | 11 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelComponents/ChartTree/ChartTree.svelte: -------------------------------------------------------------------------------- 1 | 88 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelComponents/ComponentInfo/ComponentInfo.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 |

{componentInfo.tagName}

12 |
13 |
14 |

State

15 | {#if componentInfo.detail.ctx.length > 0} 16 |
    17 | {#each componentInfo.detail.ctx as state, index (state.key)} 18 |
  • 19 |

    {state.key}:

    20 | 26 |
  • 27 | {/each} 28 |
29 | {/if} 30 | 31 |

Props

32 | {#if componentInfo.detail.attributes.length > 0} 33 |
    34 | {#each componentInfo.detail.attributes as prop, index (prop.key)} 35 |
  • 36 |

    {prop.key}:

    37 | 43 |
  • 44 | {/each} 45 |
46 | {/if} 47 |
48 |
49 | 50 | 112 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelComponents/Navbar/Navbar.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 52 | 53 | 74 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelComponents/Panel/Panel.svelte: -------------------------------------------------------------------------------- 1 | 71 | 72 | 73 | 74 | 75 | 76 |
77 |
78 |
79 |
80 | 81 |
82 | {#if unableToGetComponentData} 83 |

84 | Unable to get component data 85 |

86 | {:else} 87 | 97 |

{lastUpdateMessage}

98 | {/if} 99 |
100 |
101 | 102 |
103 |
104 | 109 |
110 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelComponents/Rewinder/Rewinder.svelte: -------------------------------------------------------------------------------- 1 | 40 | 41 |
42 | 45 |
46 | 57 |
58 | 61 | 64 |
65 | 66 | 161 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelComponents/StateModifier/StateModifier.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 |
50 | {#if editing} 51 | 60 | {:else} 61 |
{ 64 | editing = true; 65 | }} 66 | data-testid={`modifier-${stateKey}`} 67 | aria-label="Modifiable State" 68 | > 69 |

{inputValue !== '' ? inputValue : '""'}

70 |
71 | {/if} 72 |
73 | 74 | 90 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelComponents/StateValue/StateValue.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | {#if Array.isArray(value)} 19 |
20 | Array [{value.length}] 21 |
    22 | {#each value as item, index} 23 |
  • 24 | 25 |
  • 26 | {/each} 27 |
28 |
29 | {:else if value === null} 30 | 'null' 31 | {:else if value === false} 32 | 'false' 33 | {:else if value === true} 34 | 'true' 35 | {:else if typeof value === 'number' || typeof value === 'string'} 36 | {#if !isArray} 37 | 38 | {:else} 39 |
{value}
40 | {/if} 41 | {:else if typeof value === 'object'} 42 | {#if 'isFunction' in value && value.__isFunction} 43 |
44 |
45 | function 46 |
47 | 48 |
49 |
50 |
51 | {/if} 52 | {/if} 53 |
54 | 55 | 81 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelComponents/TreeComponent/TreeComponent.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 |
28 | {#if componentData.children && componentData.children.length > 0} 29 |
30 | 37 | 40 | {#if open} 41 |
45 | {#each componentData.children as child (child.id)} 46 | 47 | {/each} 48 |
49 | {/if} 50 |
51 | {:else} 52 | 55 | {/if} 56 |
57 | 58 | 228 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelComponents/custom-tree.css: -------------------------------------------------------------------------------- 1 | .node__root > circle { 2 | fill: white; 3 | color: white; 4 | } 5 | 6 | .node__branch > circle { 7 | fill: white; 8 | } 9 | 10 | .node__leaf > circle { 11 | fill: white; 12 | } 13 | 14 | .rd3t-label__title > circle { 15 | color: white; 16 | } 17 | 18 | .text { 19 | fill: white; 20 | } 21 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelPages/ListPage/ListPage.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
10 | {#if rootComponentData.rootComponent} 11 | 12 | {/if} 13 |
14 |
15 | 16 | 23 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/PanelPages/TreePage/TreePage.svelte: -------------------------------------------------------------------------------- 1 | 91 | 92 | 93 | 110 | 111 |
112 | 113 |
-------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/Store/currentSnapshot.ts: -------------------------------------------------------------------------------- 1 | import { currentSnapshot } from './store'; 2 | import { Component } from '../types'; 3 | 4 | export function setCurrentSnapshot(rootComponent: Component) { 5 | currentSnapshot.update((state) => { 6 | return { ...state, rootComponent }; 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/Store/highlightedComponent.ts: -------------------------------------------------------------------------------- 1 | import { highlightedComponent } from './store'; 2 | import type { Component } from '../types'; 3 | 4 | export function setHighlightedComponent(payload: Component) { 5 | highlightedComponent.set(payload); 6 | } 7 | 8 | export function updateHighlightedComponent(payload: Component) { 9 | highlightedComponent.update((current) => ({ 10 | ...current, 11 | detail: payload.detail, 12 | tagName: payload.tagName, 13 | })); 14 | } 15 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/Store/store.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import type { 3 | Component, 4 | Snapshot, 5 | TimedEventsState, 6 | TreeHistory, 7 | } from '../types'; 8 | 9 | // Define initial states 10 | const highlightedComponentInitial: Component = { 11 | tagName: '', 12 | detail: [], 13 | children: [], 14 | id: -1, 15 | }; 16 | 17 | const currentSnapshotInitial = { 18 | rootComponent: null, 19 | }; 20 | 21 | const treeHistoryInitial = { 22 | treeHistory: [], 23 | }; 24 | 25 | const timedEventsInitial = { 26 | newTimeStart: -1, 27 | eventTimes: [], 28 | }; 29 | 30 | // Create stores 31 | export const highlightedComponent = writable( 32 | highlightedComponentInitial 33 | ); 34 | export const currentSnapshot = writable(currentSnapshotInitial); 35 | export const treeHistory = writable(treeHistoryInitial); 36 | export const timedEvents = writable(timedEventsInitial); 37 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/Store/timedEvents.ts: -------------------------------------------------------------------------------- 1 | import { timedEvents } from './store'; 2 | import type { TimedEventPayload } from '../types'; 3 | 4 | export function addNewEvent(payload: TimedEventPayload) { 5 | timedEvents.update((state) => { 6 | if (payload.type === 'sendMessage' && state.newTimeStart === -1) { 7 | return { ...state, newTimeStart: payload.data }; 8 | } else if (payload.type === 'receiveMessage') { 9 | if (state.newTimeStart !== -1) { 10 | const newEventTime = payload.data - state.newTimeStart; 11 | return { 12 | ...state, 13 | eventTimes: [...state.eventTimes, newEventTime], 14 | newTimeStart: -1, 15 | }; 16 | } 17 | } 18 | return state; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/Store/treeHistory.ts: -------------------------------------------------------------------------------- 1 | import type { Component, TreeHistory } from '../types'; 2 | import { treeHistory } from './store'; 3 | 4 | export function addNewSnapshot(newSnapshot: Component) { 5 | treeHistory.update((history: TreeHistory) => ({ 6 | treeHistory: [...history.treeHistory, newSnapshot], 7 | })); 8 | } 9 | 10 | export function deleteAllSnapshots() { 11 | treeHistory.update(() => ({ 12 | treeHistory: [], 13 | })); 14 | } 15 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | .card { 39 | padding: 2em; 40 | } 41 | 42 | #app { 43 | max-width: 1280px; 44 | margin: 0 auto; 45 | padding: 2rem; 46 | text-align: center; 47 | } 48 | 49 | button { 50 | border-radius: 8px; 51 | border: 1px solid transparent; 52 | padding: 0.6em 1.2em; 53 | font-size: 1em; 54 | font-weight: 500; 55 | font-family: inherit; 56 | background-color: #1a1a1a; 57 | cursor: pointer; 58 | transition: border-color 0.25s; 59 | } 60 | button:hover { 61 | border-color: #646cff; 62 | } 63 | button:focus, 64 | button:focus-visible { 65 | outline: 4px auto -webkit-focus-ring-color; 66 | } 67 | 68 | @media (prefers-color-scheme: light) { 69 | :root { 70 | color: #213547; 71 | background-color: #ffffff; 72 | } 73 | a:hover { 74 | color: #747bff; 75 | } 76 | button { 77 | background-color: #f9f9f9; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/assets/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/declarations.ts: -------------------------------------------------------------------------------- 1 | declare module 'd3'; 2 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/lib/Counter.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/main.ts: -------------------------------------------------------------------------------- 1 | import { mount } from 'svelte'; 2 | import './app.css'; 3 | import App from './App.svelte'; 4 | 5 | const app = mount(App, { 6 | target: document.getElementById('app')!, 7 | }); 8 | 9 | export default app; 10 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Component { 2 | tagName: string; 3 | // A components detail can have any kind of data inside of it 4 | detail: any; 5 | children: Array; 6 | id: number; 7 | } 8 | 9 | export interface TreeHistory { 10 | treeHistory: Array; 11 | } 12 | 13 | export type TimedEventsType = 'sendMessage' | 'receiveMessage'; 14 | 15 | export type TimedEventsState = { 16 | newTimeStart: number; 17 | eventTimes: number[]; 18 | }; 19 | 20 | export interface TimedEventPayload { 21 | type: TimedEventsType; 22 | data: number; 23 | } 24 | 25 | export interface Snapshot { 26 | rootComponent: Component | null; 27 | } 28 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | }; 8 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | /** 9 | * Typecheck JS in `.svelte` and `.js` files by default. 10 | * Disable checkJs if you'd like to use dynamic types in JS. 11 | * Note that setting allowJs false does not prevent the use 12 | * of JS in `.svelte` files. 13 | */ 14 | "allowJs": true, 15 | "checkJs": true, 16 | "isolatedModules": true, 17 | "moduleDetection": "force" 18 | }, 19 | "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] 20 | } 21 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "baseUrl": ".", 11 | "types": ["svelte", "chrome"], 12 | "paths": { 13 | "*": ["src/*", "node_modules/*"] 14 | } 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /extension/src/pages/SveltePanel/src/panel/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | }); 8 | -------------------------------------------------------------------------------- /extension/src/pages/types.tsx: -------------------------------------------------------------------------------- 1 | export interface KeyValuePair { 2 | key: string; 3 | value: any; 4 | } 5 | -------------------------------------------------------------------------------- /extension/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import mockChrome, { MockChrome } from '../__mocks__/chrome'; 3 | 4 | declare global { 5 | var chrome: MockChrome; 6 | } 7 | global.chrome = mockChrome; 8 | -------------------------------------------------------------------------------- /extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": false, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "noEmit": false, 15 | "jsx": "react-jsx", 16 | "noImplicitAny": true, 17 | "types": ["jest", "node"], 18 | "typeRoots": ["./node_modules/@types", "./types"] 19 | }, 20 | "include": ["src", "jest.config.ts", "fileTransformer.js", "Store"], 21 | "exclude": ["build", "node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /extension/utils/build.js: -------------------------------------------------------------------------------- 1 | // Do this as the first thing so that any code reading it knows the right env. 2 | process.env.BABEL_ENV = 'production'; 3 | process.env.NODE_ENV = 'production'; 4 | process.env.ASSET_PATH = '/'; 5 | 6 | const webpack = require('webpack'); 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | const config = require('../webpack.config'); 10 | const ZipPlugin = require('zip-webpack-plugin'); 11 | 12 | delete config.chromeExtensionBoilerplate; 13 | 14 | config.mode = 'production'; 15 | 16 | var packageInfo = JSON.parse(fs.readFileSync('package.json', 'utf-8')); 17 | 18 | config.plugins = (config.plugins || []).concat( 19 | new ZipPlugin({ 20 | filename: `${packageInfo.name}-${packageInfo.version}.zip`, 21 | path: path.join(__dirname, '../', 'zip'), 22 | }) 23 | ); 24 | 25 | webpack(config, function (err) { 26 | if (err) throw err; 27 | }); 28 | -------------------------------------------------------------------------------- /extension/utils/env.js: -------------------------------------------------------------------------------- 1 | // tiny wrapper with default env vars 2 | module.exports = { 3 | NODE_ENV: process.env.NODE_ENV || 'development', 4 | PORT: process.env.PORT || 3000, 5 | }; 6 | -------------------------------------------------------------------------------- /extension/utils/webserver.js: -------------------------------------------------------------------------------- 1 | const WebpackDevServer = require('webpack-dev-server'); 2 | const webpack = require('webpack'); 3 | const config = require('../webpack.config'); 4 | const path = require('path'); 5 | const options = config.chromeExtensionBoilerplate || {}; 6 | const excludeEntriesToHotReload = options.notHotReload || []; 7 | require('dotenv').config(); 8 | 9 | for (const entryName in config.entry) { 10 | if (excludeEntriesToHotReload.indexOf(entryName) === -1) { 11 | config.entry[entryName] = [ 12 | 'webpack/hot/dev-server', 13 | `webpack-dev-server/client?hot=true&hostname=localhost&port=${process.env.PORT}`, 14 | ].concat(config.entry[entryName]); 15 | } 16 | } 17 | 18 | delete config.chromeExtensionBoilerplate; 19 | 20 | const compiler = webpack(config); 21 | 22 | const server = new WebpackDevServer( 23 | { 24 | https: true, 25 | hot: true, 26 | liveReload: false, 27 | client: { 28 | webSocketTransport: 'ws', 29 | }, 30 | webSocketServer: 'ws', 31 | host: 'localhost', 32 | port: process.env.PORT, 33 | static: { 34 | directory: path.join(__dirname, '../build'), 35 | }, 36 | devMiddleware: { 37 | publicPath: `http://localhost:${process.env.PORT}/`, 38 | writeToDisk: true, 39 | }, 40 | headers: { 41 | 'Access-Control-Allow-Origin': '*', 42 | }, 43 | allowedHosts: 'all', 44 | }, 45 | compiler 46 | ); 47 | 48 | (async () => { 49 | await server.start(); 50 | })(); 51 | -------------------------------------------------------------------------------- /server/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | }, 7 | extends: ["eslint:recommended", "plugin:react/recommended"], 8 | overrides: [ 9 | { 10 | env: { 11 | node: true, 12 | }, 13 | files: [".eslintrc.{js,cjs,jsx,tsx}"], 14 | parserOptions: { 15 | sourceType: "script", 16 | }, 17 | }, 18 | ], 19 | parser: "@typescript-eslint/parser", 20 | parserOptions: { 21 | ecmaVersion: "latest", 22 | sourceType: "module", 23 | }, 24 | plugins: ["@typescript-eslint", "react"], 25 | rules: { 26 | semi: ["error", "always"], 27 | quotes: [ 28 | "error", 29 | "double", 30 | { allowTemplateLiterals: true, avoidEscape: true }, 31 | ], 32 | "no-unused-vars": [ 33 | "error", 34 | { 35 | vars: "all", 36 | args: "after-used", 37 | caughtErrors: "all", 38 | ignoreRestSiblings: false, 39 | argsIgnorePattern: "*" 40 | }, 41 | ], 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /server/.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ -------------------------------------------------------------------------------- /server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false 3 | } 4 | -------------------------------------------------------------------------------- /server/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from "@eslint/js"; 4 | import tseslint from "typescript-eslint"; 5 | 6 | export default tseslint.config( 7 | eslint.configs.recommended, 8 | ...tseslint.configs.recommended, 9 | ); 10 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdtp-server", 3 | "version": "1.0.0", 4 | "description": "Svelte DevTools+ Server for User Data", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon src/server/server.ts", 9 | "start": "ts-node src/server/server.ts", 10 | "prettier": "npx prettier . --write" 11 | }, 12 | "author": "Alex Vranas", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@aws-sdk/client-dynamodb": "^3.637.0", 16 | "@aws-sdk/lib-dynamodb": "^3.637.0", 17 | "@types/passport-local": "^1.0.38", 18 | "aws-sdk": "^2.1687.0", 19 | "axios": "^1.6.8", 20 | "bcrypt": "^5.1.1", 21 | "body-parser": "^1.20.2", 22 | "dotenv": "^16.4.5", 23 | "dotenv-webpack": "^8.1.0", 24 | "express": "^4.19.2", 25 | "express-session": "^1.18.0", 26 | "passport": "^0.7.0", 27 | "passport-google-oauth2": "^0.2.0", 28 | "passport-google-oauth20": "^2.0.0", 29 | "passport-local": "^1.0.0", 30 | "pg": "^8.12.0", 31 | "react": "^18.2.0", 32 | "react-dom": "^18.2.0", 33 | "uuid": "^9.0.1" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.24.3", 37 | "@babel/preset-env": "^7.24.3", 38 | "@babel/preset-react": "^7.24.1", 39 | "@types/bcrypt": "^5.0.2", 40 | "@types/express": "^4.17.21", 41 | "@types/express-session": "^1.18.0", 42 | "@types/node": "^20.12.3", 43 | "@types/passport-google-oauth2": "^0.1.10", 44 | "@types/passport-google-oauth20": "^2.0.16", 45 | "@types/react": "^18.2.74", 46 | "@types/react-dom": "^18.2.23", 47 | "@types/uuid": "^9.0.8", 48 | "@typescript-eslint/eslint-plugin": "^7.5.0", 49 | "@typescript-eslint/parser": "^7.5.0", 50 | "babel-loader": "^9.1.3", 51 | "css-loader": "^7.0.0", 52 | "eslint": "^8.57.0", 53 | "eslint-plugin-react": "^7.34.1", 54 | "html-webpack-plugin": "^5.6.0", 55 | "nodemon": "^3.1.0", 56 | "prettier": "3.2.5", 57 | "style-loader": "^3.3.4", 58 | "ts-loader": "^9.5.1", 59 | "ts-node": "^10.9.2", 60 | "typescript": "^5.5.4", 61 | "typescript-eslint": "^7.5.0", 62 | "webpack": "^5.91.0", 63 | "webpack-cli": "^5.1.4", 64 | "webpack-dev-server": "^5.0.4" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /server/src/server/controllers/authController.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/server/src/server/controllers/authController.ts -------------------------------------------------------------------------------- /server/src/server/controllers/dataController.ts: -------------------------------------------------------------------------------- 1 | interface DataController {} 2 | 3 | export const dataController: DataController = { 4 | }; 5 | 6 | export default dataController; 7 | -------------------------------------------------------------------------------- /server/src/server/controllers/userController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; 3 | import { 4 | DynamoDBDocumentClient, 5 | GetCommand, 6 | PutCommand, 7 | PutCommandInput, 8 | GetCommandInput, 9 | } from "@aws-sdk/lib-dynamodb"; 10 | import { v4 as uuidv4 } from "uuid"; 11 | import bcrypt from "bcrypt"; 12 | 13 | const tableName = "Users"; 14 | 15 | // Configure DynamoDB Client 16 | const db = DynamoDBDocumentClient.from(new DynamoDBClient()); 17 | 18 | // Function to add a new user 19 | export const addUser = async (req: Request, res: Response) => { 20 | const { username, password } = req.body; 21 | if (!username || !password) { 22 | return res.status(400).json({ error: "Missing required fields" }); 23 | } 24 | 25 | try { 26 | const newId = uuidv4(); 27 | const saltRounds = 10; 28 | bcrypt.genSalt(saltRounds, (err, salt) => { 29 | bcrypt.hash(password, salt, async (err, hashedPassword) => { 30 | if (err) { 31 | throw err; 32 | } 33 | 34 | const newUser = { 35 | id: newId, 36 | username, 37 | password: hashedPassword, 38 | }; 39 | 40 | const params: PutCommandInput = { 41 | TableName: tableName, 42 | Item: newUser, 43 | }; 44 | 45 | await db.send(new PutCommand(params)); 46 | }); 47 | }); 48 | res.status(201).json({ message: "User added successfully: ", newId }); 49 | 50 | 51 | } catch (error) { 52 | console.error("Error adding user:", error); 53 | res.status(500).json({ error: "Could not add user" }); 54 | } 55 | }; 56 | 57 | // Function to get a user by ID 58 | export const getUserById = async (req: Request, res: Response) => { 59 | const { id } = req.params; 60 | try { 61 | const params: GetCommandInput = { 62 | TableName: tableName, 63 | Key: { id }, 64 | }; 65 | 66 | const { Item } = await db.send(new GetCommand(params)); 67 | if (!Item) { 68 | return res.status(404).json({ error: "User not found" }); 69 | } 70 | res.status(200).json(Item); 71 | } catch (error) { 72 | console.error("Error getting user:", error); 73 | res.status(500).json({ error: "Could not retrieve user" }); 74 | } 75 | }; 76 | 77 | // Function to get a user by Username 78 | export const getUserByUsername = async (req: Request, res: Response) => { 79 | const { username } = req.params; 80 | 81 | try { 82 | const params: GetCommandInput = { 83 | TableName: tableName, 84 | Key: { username }, 85 | }; 86 | 87 | const { Item } = await db.send(new GetCommand(params)); 88 | if (!Item) { 89 | return res.status(404).json({ error: "User not found" }); 90 | } 91 | res.status(200).json(Item); 92 | } catch (error) { 93 | console.error("Error getting user:", error); 94 | res.status(500).json({ error: "Could not retrieve user" }); 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /server/src/server/db.ts: -------------------------------------------------------------------------------- 1 | import AWS from "aws-sdk"; 2 | 3 | 4 | // Connect to DynamoDB database 5 | AWS.config.update({ 6 | region: process.env.AWS_REGION, 7 | accessKeyId: process.env.ACCESS_KEY_ID, 8 | secretAccessKey: process.env.SECRET_ACCESS_KEY, 9 | }); -------------------------------------------------------------------------------- /server/src/server/passport-config.ts: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { Strategy as LocalStrategy } from 'passport-local'; 3 | import bcrypt from 'bcrypt'; 4 | import AWS from "aws-sdk"; 5 | import DynamoDB, { PutItemInput, ScanInput } from "aws-sdk/clients/dynamodb"; 6 | import * as dotenv from "dotenv"; 7 | dotenv.config({ path: __dirname+'/../../.env' }); 8 | 9 | // Ensure AWS region is defined in the environment variables 10 | const region = process.env.AWS_REGION; 11 | if (!region) { 12 | throw new Error('AWS_REGION is not set in environment variables.'); 13 | } 14 | 15 | // Configure DynamoDB Client 16 | const db = new AWS.DynamoDB.DocumentClient({ region }); 17 | const tableName = "Users"; 18 | 19 | // User model functions interacting with DynamoDB 20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 21 | // User model functions interacting with DynamoDB 22 | const getUserById = async (id: string) => { 23 | const getParams: DynamoDB.DocumentClient.GetItemInput = { 24 | TableName: tableName, 25 | Key: { id }, 26 | }; 27 | const getResult = await db.get(getParams).promise(); 28 | // DynamoDB will return an empty object if it can't find the item 29 | if (!getResult.Item) { 30 | return null; 31 | } 32 | return getResult.Item; 33 | }; 34 | 35 | const getUserByUsername = async (username: string) => { 36 | 37 | const params: DynamoDB.DocumentClient.QueryInput = { 38 | TableName: tableName, 39 | IndexName: 'username-index', // The GSI name 40 | KeyConditionExpression: 'username = :username', 41 | ExpressionAttributeValues: { 42 | ':username': username 43 | } 44 | }; 45 | 46 | const getResult = await db.query(params).promise(); 47 | // DynamoDB will return an empty object if it can't find the item 48 | if (!getResult.Items) { 49 | return null; 50 | } 51 | console.log('getResult', getResult) 52 | 53 | return getResult.Items[0]; 54 | }; 55 | 56 | // const getUserByGoogleId = async (googleId: string) => { 57 | // const params = { 58 | // TableName: tableName, 59 | // Key: { googleId }, 60 | // }; 61 | // const { Item } = await ddbDocClient.send(new GetCommand(params)); 62 | // return Item; 63 | // }; 64 | 65 | // // eslint-disable-next-line @typescript-eslint/no-explicit-any 66 | // const saveUser = async (user: any) => { 67 | // const params = { 68 | // TableName: tableName, 69 | // Item: user, 70 | // }; 71 | // await ddbDocClient.send(new PutCommand(params)); 72 | // }; 73 | 74 | // Passport Local Strategy for login 75 | passport.use( 76 | new LocalStrategy(async (username, password, done) => { 77 | try { 78 | const user = await getUserByUsername(username); 79 | if (!user) { 80 | return done(null, false, { message: 'Incorrect username.' }); 81 | } 82 | // Add your password validation logic here 83 | 84 | 85 | 86 | 87 | // if (!user.password) { 88 | // return done(null, false); 89 | // } 90 | // if (await bcrypt.compare(password, user.password)) { 91 | // return done(null, user); 92 | // } 93 | 94 | 95 | 96 | 97 | 98 | return done(null, false, { message: 'Incorrect password.' }); 99 | } catch (error) { 100 | return done(error); 101 | } 102 | }) 103 | ); 104 | 105 | // Passport Google OAuth Strategy 106 | // passport.use( 107 | // new GoogleStrategy( 108 | // { 109 | // clientID: process.env.GOOGLE_CLIENT_ID, 110 | // clientSecret: process.env.GOOGLE_CLIENT_SECRET, 111 | // callbackURL: process.env.GOOGLE_CALLBACK_URL, 112 | // }, 113 | // async (accessToken, refreshToken, profile, done) => { 114 | // try { 115 | // let user = await getUserByGoogleId(profile.id); 116 | // if (!user) { 117 | // // Create a new user if not found 118 | // user = { 119 | // id: `google-${profile.id}`, 120 | // googleId: profile.id, 121 | // username: profile.displayName, 122 | // email: profile.emails?.[0].value, 123 | // provider: 'google', 124 | // }; 125 | // await saveUser(user); 126 | // } 127 | // return done(null, user); 128 | // } catch (error) { 129 | // return done(error); 130 | // } 131 | // } 132 | // ) 133 | // ); 134 | 135 | // Serialize user to store in session 136 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 137 | passport.serializeUser((user: any, done) => { 138 | done(null, user.id); 139 | }); 140 | 141 | // Deserialize user from session 142 | passport.deserializeUser(async (id: string, done) => { 143 | try { 144 | const user = await getUserById(id); 145 | done(null, user); 146 | } catch (error) { 147 | done(error); 148 | } 149 | }); 150 | 151 | export default passport; 152 | -------------------------------------------------------------------------------- /server/src/server/routes/authRouter.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | // import authController from "../controllers/authController.js"; 3 | import passport from "../passport-config"; 4 | import { Request, Response } from "express"; 5 | 6 | const router = express.Router(); 7 | // eslint-disable-next-line @typescript-eslint/no-var-requires 8 | const dotenv = require("dotenv"); 9 | dotenv.config(); 10 | 11 | router.post( 12 | "/login", 13 | passport.authenticate("local"), 14 | (req: Request, res: Response) => { 15 | if (!req.user) { 16 | return res.status(500).send("Failed to get user data"); 17 | } 18 | return res.status(200).json({ username: req.user.username }); 19 | }, 20 | ); 21 | 22 | router.get("/logout", (req, res, next) => { 23 | req.logout((err) => { 24 | if (err) { 25 | return next(err); 26 | } else res.status(200).send("You are now logged out"); 27 | }); 28 | }); 29 | 30 | export default router; 31 | -------------------------------------------------------------------------------- /server/src/server/routes/dataRoute.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | const router = express.Router(); 4 | 5 | export default router; 6 | -------------------------------------------------------------------------------- /server/src/server/routes/userRouter.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { 3 | addUser, 4 | getUserById, 5 | getUserByUsername, 6 | } from "../controllers/userController"; 7 | import { Request, Response } from "express"; 8 | 9 | const router = express.Router(); 10 | // Route to add a new user 11 | router.post("/", addUser); 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | router.get("/", (req: Request, res: Response, next) => { 15 | res.send("yay"); 16 | }); 17 | 18 | // Route to get a user by ID 19 | router.get("/:id", getUserById); 20 | 21 | // Route to get a user by username 22 | router.get("/username/:username", getUserByUsername); 23 | 24 | export default router; 25 | -------------------------------------------------------------------------------- /server/src/server/server.ts: -------------------------------------------------------------------------------- 1 | import bodyParser from "body-parser"; 2 | import express, { 3 | type Request, 4 | type Response, 5 | type NextFunction, 6 | } from "express"; 7 | import { HttpError } from "../types"; 8 | import passport from "./passport-config"; 9 | import session from 'express-session'; 10 | import AWS from "aws-sdk"; 11 | 12 | import userRouter from "./routes/userRouter"; 13 | import authRouter from "./routes/authRouter"; 14 | 15 | // eslint-disable-next-line @typescript-eslint/no-var-requires 16 | require("dotenv").config(); 17 | 18 | const port = process.env.PORT || 3000; 19 | const nodeEnv = process.env.NODE_ENV; 20 | const app = express(); 21 | 22 | app.use( 23 | session({ 24 | secret: process.env.SESSION_SECRET as string, 25 | resave: false, 26 | saveUninitialized: false, 27 | cookie: { httpOnly: true, secure: false, maxAge: 24 * 60 * 60 * 1000 }, 28 | }) 29 | ); 30 | 31 | // Connect to DynamoDB database 32 | AWS.config.update({ 33 | region: process.env.AWS_REGION, 34 | accessKeyId: process.env.ACCESS_KEY_ID, 35 | secretAccessKey: process.env.SECRET_ACCESS_KEY, 36 | }); 37 | 38 | app.use(passport.initialize()); 39 | app.use(passport.session()); 40 | app.use(bodyParser.json()); 41 | 42 | app.use("/users", userRouter); 43 | app.use("/auth", authRouter); 44 | 45 | app.get("/", (req: Request, res: Response) => { 46 | return res.send("Hello World!"); 47 | }); 48 | 49 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 50 | app.use((err: HttpError, req: Request, res: Response, next: NextFunction) => { 51 | if (nodeEnv === "development") { 52 | console.log(err); 53 | } 54 | return res.status(err.status || 500).send(err.message); 55 | }); 56 | 57 | // Serves React app on all other routes. This must be the last route defined 58 | app.get("*", (req, res) => { 59 | return res.status(404).send(); 60 | }); 61 | 62 | app.listen(port, () => { 63 | console.log(`Starting server. Listening on port ${port}`); 64 | }); 65 | -------------------------------------------------------------------------------- /server/src/server/util.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Svelte-DevTools-Plus/bda24886be2d078eb202c25d458485c59506fd25/server/src/server/util.ts -------------------------------------------------------------------------------- /server/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface HttpError { 2 | status: number; 3 | message: string; 4 | } 5 | 6 | export interface User { 7 | id: string; 8 | username: string; 9 | password: string; 10 | } 11 | 12 | export interface Item extends User { 13 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 14 | [key: string]: any; 15 | } 16 | 17 | declare module 'express-serve-static-core' { 18 | interface Request { 19 | user?: User; 20 | } 21 | } -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "jsx": "react", 5 | "module": "CommonJS", 6 | "esModuleInterop": true, 7 | "allowJs": true, 8 | "noImplicitAny": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "outDir": "./build" 12 | }, 13 | "include": ["src/**/*"] 14 | } 15 | --------------------------------------------------------------------------------