├── .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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
66 | ❮
67 | ❯
68 | {month}
69 | {year}
70 |
71 |
72 |
73 |
74 |
75 | Sun
76 | Mon
77 | Tue
78 | Wed
79 | Thu
80 | Fri
81 | Sat
82 |
83 |
84 |
85 | {#each Array(calendarCellsQty) as _, i}
86 | {#if i < firstDayIndex || i >= numberOfDays + firstDayIndex}
87 |
88 | {:else}
89 |
96 |
97 | {(i - firstDayIndex) + 1}
98 |
99 | {/if}
100 | {/each}
101 |
102 |
103 |
204 |
--------------------------------------------------------------------------------
/demo-apps/calendar/src/lib/Scheduler.svelte:
--------------------------------------------------------------------------------
1 |
37 |
38 |
42 |
43 |
44 |
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 |
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 |
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 | ADD
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 |
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 |
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 |
28 |
29 |
30 | List
31 |
32 |
33 |
34 |
40 | Tree
41 |
42 |
43 |
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 |
56 | Clear
57 |
58 |
59 |
71 |
72 |
73 | <<
74 |
75 |
80 | >>
81 |
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 |
85 |
86 |
87 | ) : (
88 |
93 |
94 |
95 | )}
96 |
101 | <
102 | {componentData.tagName}
103 | />
104 |
105 |
106 |
112 |
113 | {childrenState.map((item) => item)}
114 |
115 |
116 |
117 | ) : (
118 |
125 | <{componentData.tagName}
126 | />
127 |
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 |
48 |
51 |
54 |
58 |
61 |
62 |
65 |
69 |
73 |
74 |
78 | <
79 |
82 | App
83 |
84 | />
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
95 | Last update took 0.34ms
96 |
97 |
98 |
99 |
100 |
104 |
107 |
110 |
113 |
116 |
117 | State
118 |
119 |
120 | Props
121 |
122 |
123 |
124 |
125 |
126 |
127 |
130 |
134 | Clear
135 |
136 |
143 |
147 | <<
148 |
149 |
153 | >>
154 |
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 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | Get profiling data
99 |
100 |
106 |
107 |
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 |
54 |
63 |
73 |
74 |
--------------------------------------------------------------------------------
/extension/src/pages/SveltePanel/src/panel/src/PanelComponents/Panel/Panel.svelte:
--------------------------------------------------------------------------------
1 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
82 | {#if unableToGetComponentData}
83 |
84 | Unable to get component data
85 |
86 | {:else}
87 |
88 |
96 |
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 |
43 | Clear
44 |
45 |
46 |
57 |
58 |
59 | <<
60 |
61 |
62 | >>
63 |
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 |
31 |
36 |
37 |
38 | <{componentData.tagName} >
39 |
40 | {#if open}
41 |
45 | {#each componentData.children as child (child.id)}
46 |
47 | {/each}
48 |
49 | {/if}
50 |
51 | {:else}
52 |
53 | <{componentData.tagName} >
54 |
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 |
9 | count is {count}
10 |
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 |
--------------------------------------------------------------------------------