55 |
{props.children[0]}
56 |
{
63 | setIsDragging(true);
64 | }}
65 | />
66 |
{props.children[1]}
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/ui/src/errors/404.tsx:
--------------------------------------------------------------------------------
1 | export default function NotFound() {
2 | return (
3 |
4 | How did you get here?
5 | You just 404'd DevTools.
6 |
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/src/ui/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --background-color: #202124;
3 | --background-color-light: #292a2d;
4 | --text-color: #cfd0d0;
5 | --text-color-dim: rgb(154, 160, 166);
6 | --accent-color: #494c50;
7 | --accent-color-25: #494c503f;
8 | --accent-color-50: #494c507f;
9 | --accent-color-75: #494c50bf;
10 | }
11 |
12 | a {
13 | color: inherit;
14 | }
15 | * {
16 | position: relative;
17 | user-select: none;
18 | -webkit-user-drag: none;
19 | }
20 |
21 | html,
22 | body,
23 | #root {
24 | height: 100vh;
25 | background-color: var(--background-color);
26 | color: var(--text-color);
27 | font-size: 14px;
28 | overflow: hidden;
29 | margin: 0;
30 | }
31 |
32 | body {
33 | display: grid;
34 | grid-template-rows: auto;
35 | grid-template-columns: auto;
36 | }
37 |
38 | #root {
39 | display: flex;
40 | flex-direction: column;
41 | }
42 |
43 | ::-webkit-scrollbar {
44 | cursor: pointer;
45 | width: 8px;
46 | background-color: var(--background-color);
47 | }
48 | ::-webkit-scrollbar-button {
49 | display: none;
50 | }
51 | ::-webkit-scrollbar-thumb {
52 | border-radius: 3px;
53 | background-color: var(--accent-color-25);
54 | }
55 | ::-webkit-scrollbar-thumb:hover {
56 | background-color: var(--accent-color-50);
57 | }
58 | ::-webkit-scrollbar-thumb:active {
59 | background-color: var(--accent-color-75);
60 | }
61 |
--------------------------------------------------------------------------------
/src/ui/src/index.tsx:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { render } from "solid-js/web";
4 | import { Router } from "solid-app-router";
5 | import App from "./app";
6 |
7 | import "./index.css";
8 |
9 | import Nodes, { NodeID, NodeOptions, NodePoint } from "./stores/Nodes";
10 | // import deepFreeze from "./utils/";
11 | import { selectedStore, setSelectedStore } from "./stores/SelectedNode";
12 | import ResolvedStack from "./utils/ResolvedStack";
13 | import GlobalHistory from "./stores/GlobalHistory";
14 |
15 | // Fix wrong starting URL.
16 | window.history.replaceState({}, "", "/");
17 |
18 | type BaseNodeRequest = {
19 | source: string;
20 | };
21 |
22 | type RegisterNodeRequest = BaseNodeRequest & {
23 | type: "REGISTER_NODE";
24 | options: NodeOptions;
25 | point: NodePoint;
26 | };
27 |
28 | type UpdateNodeRequest = BaseNodeRequest & {
29 | type: "UPDATE_NODE";
30 | point: NodePoint;
31 | id: NodeID;
32 | };
33 |
34 | type UnregisterNodeRequest = BaseNodeRequest & {
35 | type: "UNREGISTER_NODE";
36 | id: NodeID;
37 | };
38 |
39 | type ResetRequest = BaseNodeRequest & { type: "RESET" };
40 |
41 | type AnyRequest =
42 | | RegisterNodeRequest
43 | | UpdateNodeRequest
44 | | UnregisterNodeRequest
45 | | ResetRequest;
46 |
47 | // Create the devtools panel.
48 | chrome.devtools.panels.create(
49 | "Compendium",
50 | null,
51 | "/ui/index.html",
52 | function (panel) {
53 | const tabID = chrome.devtools.inspectedWindow.tabId;
54 |
55 | chrome.runtime.onMessage.addListener(function (
56 | request: AnyRequest,
57 | sender,
58 | sendResponse
59 | ) {
60 | if (
61 | sender.tab.id === tabID &&
62 | request.source === "compendium-devtools-extension"
63 | ) {
64 | switch (request.type) {
65 | case "RESET":
66 | window.location.reload();
67 | return;
68 | case "REGISTER_NODE":
69 | // This is *really* for resetting stores when they are recreated.
70 | // So... Reset the history.
71 |
72 | console.log("Registering node", request);
73 |
74 | // Set up the node in the Nodes store.
75 | Nodes[request.options.id] = {
76 | ...request.options,
77 | history: [request.point],
78 | };
79 |
80 | // Get all the nodes that currently exist and add them to the global history.
81 | GlobalHistory.push({
82 | ...GlobalHistory[GlobalHistory.length - 1],
83 | [request.options.id]: 0,
84 | });
85 | return;
86 | case "UPDATE_NODE":
87 | console.log("Updating node", request);
88 | Nodes[request.id].history.push(request.point);
89 |
90 | // Add one to the history index in the global history.
91 | const currentHistory = GlobalHistory[GlobalHistory.length - 1];
92 | GlobalHistory.push({
93 | ...currentHistory,
94 | [request.id]: currentHistory.id + 1,
95 | });
96 |
97 | return;
98 | case "UNREGISTER_NODE":
99 | console.log("Unregistering node", request);
100 | // Remove the node from the global history ONLY.
101 | // The node should be kept in the Nodes store because it can be referenced by previous points in the global history.
102 | // TODO: Optimze this, I'm rushing.
103 | GlobalHistory.push(
104 | Object.fromEntries(
105 | Object.entries(GlobalHistory[GlobalHistory.length - 1]).filter(
106 | ([id]) => id !== request.id
107 | )
108 | )
109 | );
110 |
111 | return;
112 | }
113 | }
114 | });
115 | }
116 | );
117 |
118 | render(
119 | () => (
120 |
121 |
122 |
123 | ),
124 | document.getElementById("root") as HTMLElement
125 | );
126 |
--------------------------------------------------------------------------------
/src/ui/src/pages/about.tsx:
--------------------------------------------------------------------------------
1 | import { Component, createEffect, createMemo, For, Index, Suspense } from "solid-js";
2 | import { Link, useData } from "solid-app-router";
3 |
4 | import pkg from "../../package.json";
5 |
6 | const dependencies = () => Object.entries(pkg.dependencies);
7 |
8 | export default function AboutPanel() {
9 | return (
10 |
11 | About
12 |
13 |
14 |
15 | Compendium DevTools
16 | {" "}
17 | is an inspector for anything you want to connect.
18 |
19 |
20 | Dependencies
21 |
22 |
23 | {(dep) => {
24 | const name = createMemo(() => dep[0]);
25 | const version = createMemo(() => dep[1].slice(1));
26 | return (
27 |
28 |
29 | {name()}
30 | {" "}
31 |
32 | v{version()}
33 |
34 |
35 | );
36 | }}
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/ui/src/pages/index.module.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CompendiumDevTools/devtools/f729bc25f6905a5de019bedccd1cc6555504cadb/src/ui/src/pages/index.module.css
--------------------------------------------------------------------------------
/src/ui/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { useNavigate } from "solid-app-router";
2 | import { createEffect, createMemo, For } from "solid-js";
3 | import NodeTreeView from "../components/NodeTreeView";
4 | import ResizeBox from "../components/ResizeBox";
5 | import GlobalHistory from "../stores/GlobalHistory";
6 |
7 | import stores from "../stores/Nodes";
8 | import { selectedStore, setSelectedStore } from "../stores/SelectedNode";
9 |
10 | export default function SelectPanel() {
11 | const navigate = useNavigate();
12 |
13 | createEffect(() => {
14 | console.log(GlobalHistory.length);
15 | });
16 |
17 | return (
18 | <>
19 |
20 | >
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/ui/src/pages/inspect.module.css:
--------------------------------------------------------------------------------
1 | .inspectPanel {
2 | display: grid;
3 | height: 100%;
4 | }
5 |
6 | .inspectHistory {
7 | overflow-y: auto;
8 | display: flex;
9 | flex-direction: column;
10 | flex-grow: 1;
11 | gap: 6px;
12 | padding: 6px;
13 | }
14 |
15 | .inspectHistoryButton {
16 | cursor: pointer;
17 | background-color: var(--accent-color-25);
18 |
19 | transition-duration: 0.2s;
20 | transition-property: background-color;
21 |
22 | padding: 3px;
23 | border-radius: 6px;
24 |
25 | /* border-bottom: 1px solid var(--accent-color); */
26 | &:last-of-type {
27 | border-bottom: none;
28 | }
29 |
30 | &:hover {
31 | background-color: var(--accent-color-50);
32 | }
33 | &.selected {
34 | background-color: var(--accent-color-75);
35 | }
36 | }
37 |
38 | .inspectPoint {
39 | display: flex;
40 | flex-direction: column;
41 | overflow-y: hidden;
42 | .inspectPointContent {
43 | padding: 6px;
44 | flex: 1 1 auto;
45 | overflow-y: auto;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/ui/src/pages/inspect.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from "solid-app-router";
2 | import {
3 | Component,
4 | createMemo,
5 | createSignal,
6 | For,
7 | Match,
8 | Switch,
9 | } from "solid-js";
10 |
11 | import stores from "../stores/Nodes";
12 | import { selectedStore } from "../stores/SelectedNode";
13 | import {
14 | selectedTreeView,
15 | setSelectedTreeView,
16 | } from "../stores/SelectedTreeView";
17 |
18 | import styles from "./inspect.module.css";
19 | import ResizeBox from "../components/ResizeBox";
20 | import Tree from "../components/ObjectTree";
21 | import NavBar, { NavBarLink } from "../components/NavBar";
22 |
23 | const InspectPage: Component = () => {
24 | return null;
25 |
26 | /*
27 | const [selectedHistoryPoint, setSelectedHistoryPoint] = createSignal(null);
28 |
29 | const selectedStoreData = () => stores[selectedStore()];
30 | const selectedStoreHistory = () => selectedStoreData()?.history;
31 |
32 | const selectedHistoryPointData = () =>
33 | (selectedStore() != null
34 | ? selectedStoreHistory()[selectedHistoryPoint()]
35 | : selectedStoreHistory()[selectedStoreData().history.length - 1]
36 | )?.data;
37 |
38 | let scroller;
39 |
40 | return (
41 | <>
42 | {selectedStore() == null ? (
43 |
44 | Select a store to inspect it.
45 |
46 | ) : (
47 |
53 |
54 |
55 | {(node, i) => (
56 | {
63 | if (e.button === 0) {
64 | if (selectedHistoryPoint() === i()) {
65 | setSelectedHistoryPoint(null);
66 | } else {
67 | setSelectedHistoryPoint(i());
68 | }
69 | }
70 | }}
71 | value={node.name}
72 | />
73 | )}
74 |
75 |
76 |
77 |
78 | {
80 | setSelectedTreeView("tree");
81 | }}
82 | selected={selectedTreeView() === "tree"}
83 | >
84 | Tree
85 |
86 | {
88 | setSelectedTreeView("diff");
89 | }}
90 | selected={selectedTreeView() === "diff"}
91 | >
92 | Diff
93 |
94 | {
96 | setSelectedTreeView("actions");
97 | }}
98 | selected={selectedTreeView() === "actions"}
99 | >
100 | Actions
101 |
102 |
103 |
104 |
105 |
106 |
111 |
112 | lol
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | )}
121 | >
122 | );
123 | */
124 | };
125 |
126 | export default InspectPage;
127 |
128 | const Actions: Component = (props) => {
129 | return "";
130 | };
131 |
--------------------------------------------------------------------------------
/src/ui/src/routes.ts:
--------------------------------------------------------------------------------
1 | import { lazy } from "solid-js";
2 | import type { RouteDefinition } from "solid-app-router";
3 |
4 | import SelectPanel from "./pages/index";
5 | import InspectPage from "./pages/inspect";
6 | import AboutPanel from "./pages/about";
7 | import NotFound from "./errors/404";
8 |
9 | export const routes: RouteDefinition[] = [
10 | {
11 | path: "/",
12 | component: SelectPanel,
13 | },
14 | {
15 | path: "/inspect",
16 | component: InspectPage,
17 | },
18 | {
19 | path: "/about",
20 | component: AboutPanel,
21 | },
22 | {
23 | path: "**",
24 | component: NotFound,
25 | },
26 | ];
27 |
--------------------------------------------------------------------------------
/src/ui/src/stores/GlobalHistory.ts:
--------------------------------------------------------------------------------
1 | // Points in time for the Node's data.
2 |
3 | import { createMutable } from "solid-js/store";
4 | import { NodeID } from "./Nodes";
5 |
6 | // Each history entry is an ID and the index of the point in history it was at when this entry was created.
7 | export type GlobalHistory = {
8 | [id: NodeID]: number;
9 | }[];
10 |
11 | export default createMutable
([]);
12 |
--------------------------------------------------------------------------------
/src/ui/src/stores/Nodes.ts:
--------------------------------------------------------------------------------
1 | import { createSignal } from "solid-js";
2 | import { createMutable, createStore } from "solid-js/store";
3 |
4 | export type NodeID = string;
5 |
6 | // Points in time for the Node's data.
7 | // Added to the history on update.
8 | export type NodePoint = {
9 | name:
10 | | string
11 | | {
12 | open: string;
13 | close: string;
14 | };
15 | display: boolean;
16 | children: NodeID[];
17 |
18 | time: Date;
19 | callstack: NodeJS.CallSite[];
20 |
21 | state: any;
22 | };
23 |
24 | // Settings that never change.
25 | // Added on register.
26 | export type NodeOptions = {
27 | id: NodeID;
28 | serialize: boolean;
29 | trackHistory: boolean;
30 | actions: Function[];
31 | };
32 |
33 | export type Node = NodeOptions & {
34 | history: NodePoint[];
35 | };
36 |
37 | export type NodeCollection = {
38 | [id: string]: Node;
39 | };
40 |
41 | export default createMutable({});
42 |
--------------------------------------------------------------------------------
/src/ui/src/stores/SelectedNode.ts:
--------------------------------------------------------------------------------
1 | import { createSignal } from "solid-js";
2 |
3 | const [selectedStore, setSelectedStore] = createSignal(
4 | null
5 | );
6 |
7 | export { selectedStore, setSelectedStore };
8 |
--------------------------------------------------------------------------------
/src/ui/src/stores/SelectedTreeView.ts:
--------------------------------------------------------------------------------
1 | import { createSignal } from "solid-js";
2 |
3 | const [selectedTreeView, setSelectedTreeView] = createSignal("tree");
4 |
5 | export { selectedTreeView, setSelectedTreeView };
6 |
--------------------------------------------------------------------------------
/src/ui/src/stores/TabID.ts:
--------------------------------------------------------------------------------
1 | import { createSignal } from "solid-js";
2 |
3 | export default createSignal(null);
4 |
--------------------------------------------------------------------------------
/src/ui/src/utils/ResolvedStack.ts:
--------------------------------------------------------------------------------
1 | type ResolvedStack = {
2 | columnNumber: NodeJS.CallSite["getColumnNumber"];
3 | fileName: NodeJS.CallSite["getFileName"];
4 | functionName: NodeJS.CallSite["getFunctionName"];
5 | lineNumber: NodeJS.CallSite["getLineNumber"];
6 | };
7 |
8 | export default ResolvedStack;
9 |
--------------------------------------------------------------------------------
/src/ui/src/utils/deepFreeze.ts:
--------------------------------------------------------------------------------
1 | export default function deepFreeze(object: Type): Type {
2 | const keys = Object.keys(object);
3 | for (let i = 0; i < keys.length; i++) {
4 | let value = object[keys[i]];
5 | if (value !== null && typeof value === "object") {
6 | deepFreeze(value);
7 | }
8 | }
9 | return Object.freeze(object);
10 | }
11 |
--------------------------------------------------------------------------------
/src/ui/src/utils/get.ts:
--------------------------------------------------------------------------------
1 | export default function get(obj: Type, path: PropertyKey[]): Type {
2 | let current = obj;
3 | for (let i = 0; i < path.length; i++) {
4 | if (current[path[i]] == null) return undefined;
5 | current = current[path[i]];
6 | }
7 | return current;
8 | }
9 |
--------------------------------------------------------------------------------
/src/ui/src/utils/set.ts:
--------------------------------------------------------------------------------
1 | export default function set(
2 | obj: Type,
3 | path: PropertyKey[],
4 | value: any
5 | ): Type {
6 | let current = obj;
7 | for (let i = 0; i < path.length - 1; i++) {
8 | if (current[path[i]] == null) current[path[i]] = {};
9 | current = current[path[i]];
10 | }
11 | // Reflect.set(current as any, path[path.length - 1], value, receiver);
12 | current[path[path.length - 1]] = value;
13 | return obj;
14 | }
15 |
--------------------------------------------------------------------------------
/src/ui/src/utils/symbolJoin.ts:
--------------------------------------------------------------------------------
1 | export default function symbolJoin(
2 | array: PropertyKey[],
3 | joiner: PropertyKey
4 | ): string {
5 | if (array.length === 1) return array[0].toString();
6 | let result = "";
7 | for (let i = 0; i < array.length; i++) {
8 | result += `${i === 0 ? "" : joiner.toString()}${array[i].toString()}`;
9 | }
10 | return result;
11 | }
12 |
--------------------------------------------------------------------------------
/src/ui/src/utils/walkTree.ts:
--------------------------------------------------------------------------------
1 | export default function walkTree(
2 | obj: object,
3 | callback: (node: any, path: PropertyKey[]) => boolean | void
4 | ) {
5 | const walk = (value: any, path: PropertyKey[]) => {
6 | const result = callback(value, [...path]);
7 | // Require explicit false return to stop walking.
8 | if (result === false) return;
9 | if (Array.isArray(value)) {
10 | for (let i = 0; i < value.length; i++) {
11 | walk(value[i], [...path, i]);
12 | }
13 | } else if (typeof value === "object" && value !== null) {
14 | for (const key of Reflect.ownKeys(value)) {
15 | walk(value[key], [...path, key]);
16 | }
17 | }
18 | };
19 | walk(obj, []);
20 | }
21 |
--------------------------------------------------------------------------------
/src/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "moduleResolution": "node",
6 | "allowSyntheticDefaultImports": true,
7 | "esModuleInterop": true,
8 | "jsx": "preserve",
9 | "jsxImportSource": "solid-js",
10 | "types": ["vite/client"],
11 | "resolveJsonModule": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/ui/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import solidPlugin from "vite-plugin-solid";
3 |
4 | export default defineConfig({
5 | plugins: [solidPlugin()],
6 | build: {
7 | outDir: "../../dist/ui",
8 | target: "esnext",
9 | polyfillDynamicImport: false,
10 | },
11 | base: "./",
12 | });
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "CommonJS",
5 | "moduleResolution": "Node",
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "noUnusedParameters": true,
9 | "noUnusedLocals": true,
10 | "removeComments": true,
11 | "downlevelIteration": true
12 | },
13 | "include": ["src/content.ts", "src/utils"],
14 | "exclude": ["node_modules", "src/ui", "vite.config.ts"]
15 | }
16 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 |
3 | export default defineConfig({
4 | build: {
5 | outDir: "./dist",
6 | target: "ESNext",
7 | polyfillDynamicImport: false,
8 | lib: {
9 | entry: "src/content.ts",
10 | formats: ["cjs"],
11 | fileName: () => "content.js",
12 | },
13 | },
14 | base: "./",
15 | });
16 |
--------------------------------------------------------------------------------