@praxisjs/devtools 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/index.d.ts +5 -7
  3. package/dist/index.js +1035 -860
  4. package/package.json +8 -7
  5. package/src/decorators/debug.ts +102 -77
  6. package/src/decorators/trace.ts +15 -11
  7. package/src/icons/ellipsis-vertical.tsx +25 -19
  8. package/src/icons/panel-bottom.tsx +24 -18
  9. package/src/icons/panel-left.tsx +24 -18
  10. package/src/icons/panel-right.tsx +24 -18
  11. package/src/icons/panel-top.tsx +24 -18
  12. package/src/icons/x.tsx +24 -18
  13. package/src/plugins/components/components/component-detail.tsx +59 -54
  14. package/src/plugins/components/components/component-row.tsx +36 -33
  15. package/src/plugins/components/components/detail-row.tsx +21 -18
  16. package/src/plugins/components/components/detail-section.tsx +14 -12
  17. package/src/plugins/components/components/status-dot.tsx +20 -11
  18. package/src/plugins/components/components-tab.tsx +66 -61
  19. package/src/plugins/signals/components/signal-detail.tsx +35 -27
  20. package/src/plugins/signals/components/signal-row.tsx +32 -28
  21. package/src/plugins/signals/signals-tab.tsx +92 -79
  22. package/src/plugins/timeline/components/badge.tsx +15 -9
  23. package/src/plugins/timeline/components/timeline-row.tsx +52 -45
  24. package/src/plugins/timeline/timeline-tab.tsx +90 -77
  25. package/src/plugins/types.ts +2 -2
  26. package/src/ui/dev-tools.tsx +45 -39
  27. package/src/ui/panel.tsx +152 -146
  28. package/src/ui/shared/empty-state.tsx +17 -11
  29. package/src/ui/shared/icon-button.tsx +28 -25
  30. package/src/ui/shared/panel-section.tsx +14 -12
  31. package/src/ui/shared/search-input.tsx +19 -15
  32. package/src/ui/shared/side-panel.tsx +16 -13
  33. package/vite.config.ts +3 -9
package/src/ui/panel.tsx CHANGED
@@ -9,8 +9,9 @@ import {
9
9
  } from "@icons";
10
10
  import { IconButton } from "@shared/icon-button";
11
11
 
12
- import { signal } from "@praxisjs/core";
13
- import type { Component } from "@praxisjs/shared";
12
+ import { StatefulComponent } from "@praxisjs/core";
13
+ import { Component, State } from "@praxisjs/decorators";
14
+ import type { ComponentElement } from "@praxisjs/shared";
14
15
 
15
16
  import type { Registry } from "@core/registry";
16
17
  import type { DevtoolsPlugin } from "@plugins/types";
@@ -28,67 +29,69 @@ const DEFAULT_HEIGHT = 300;
28
29
  const MIN_WIDTH = 240;
29
30
  const DEFAULT_WIDTH = 380;
30
31
 
31
- const DOCK_ICONS: Record<Dock, Component> = {
32
+ const DOCK_ICONS: Record<Dock, ComponentElement> = {
32
33
  bottom: PanelBottomIcon,
33
34
  top: PanelTopIcon,
34
35
  left: PanelLeftIcon,
35
36
  right: PanelRightIcon,
36
37
  };
37
38
 
38
- export function Panel({ plugins, registry, onClose }: PanelProps) {
39
- const activeTab = signal(plugins[0]?.id ?? "");
40
- const dock = signal<Dock>("bottom");
41
- const dockMenuOpen = signal(false);
42
- const bottomSize = signal(DEFAULT_HEIGHT);
43
- const vertSize = signal(DEFAULT_WIDTH);
39
+ @Component()
40
+ export class Panel extends StatefulComponent {
41
+ @State() activeTab = "";
42
+ @State() dock: Dock = "bottom";
43
+ @State() dockMenuOpen = false;
44
+ @State() bottomSize = DEFAULT_HEIGHT;
45
+ @State() vertSize = DEFAULT_WIDTH;
44
46
 
45
- const handleMouseDown = (e: MouseEvent) => {
47
+ private get p() {
48
+ return this.props as unknown as PanelProps;
49
+ }
50
+
51
+ onBeforeMount() {
52
+ this.activeTab = this.p.plugins[0]?.id ?? "";
53
+ }
54
+
55
+ handleMouseDown(e: MouseEvent) {
46
56
  e.preventDefault();
47
57
  const startX = e.clientX;
48
58
  const startY = e.clientY;
49
- const d = dock();
50
- const startSize = d === "bottom" || d === "top" ? bottomSize() : vertSize();
59
+ const d = this.dock;
60
+ const startSize =
61
+ d === "bottom" || d === "top" ? this.bottomSize : this.vertSize;
51
62
 
52
63
  const onMove = (ev: MouseEvent) => {
53
- const d = dock();
64
+ const d = this.dock;
54
65
  if (d === "bottom") {
55
- bottomSize.set(
56
- Math.max(
57
- MIN_HEIGHT,
58
- Math.min(
59
- window.innerHeight * 0.85,
60
- startSize + (startY - ev.clientY),
61
- ),
66
+ this.bottomSize = Math.max(
67
+ MIN_HEIGHT,
68
+ Math.min(
69
+ window.innerHeight * 0.85,
70
+ startSize + (startY - ev.clientY),
62
71
  ),
63
72
  );
64
73
  } else if (d === "top") {
65
- bottomSize.set(
66
- Math.max(
67
- MIN_HEIGHT,
68
- Math.min(
69
- window.innerHeight * 0.85,
70
- startSize + (ev.clientY - startY),
71
- ),
74
+ this.bottomSize = Math.max(
75
+ MIN_HEIGHT,
76
+ Math.min(
77
+ window.innerHeight * 0.85,
78
+ startSize + (ev.clientY - startY),
72
79
  ),
73
80
  );
74
81
  } else if (d === "left") {
75
- vertSize.set(
76
- Math.max(
77
- MIN_WIDTH,
78
- Math.min(
79
- window.innerWidth * 0.6,
80
- startSize + (ev.clientX - startX),
81
- ),
82
+ this.vertSize = Math.max(
83
+ MIN_WIDTH,
84
+ Math.min(
85
+ window.innerWidth * 0.6,
86
+ startSize + (ev.clientX - startX),
82
87
  ),
83
88
  );
84
89
  } else {
85
- vertSize.set(
86
- Math.max(
87
- MIN_WIDTH,
88
- Math.min(
89
- window.innerWidth * 0.6,
90
- startSize + (startX - ev.clientX),
91
- ),
90
+ this.vertSize = Math.max(
91
+ MIN_WIDTH,
92
+ Math.min(
93
+ window.innerWidth * 0.6,
94
+ startSize + (startX - ev.clientX),
92
95
  ),
93
96
  );
94
97
  }
@@ -101,125 +104,128 @@ export function Panel({ plugins, registry, onClose }: PanelProps) {
101
104
 
102
105
  document.addEventListener("mousemove", onMove);
103
106
  document.addEventListener("mouseup", onUp);
104
- };
105
-
106
- return (
107
- <div
108
- class={() => {
109
- const d = dock();
110
- if (d === "top")
111
- return "flex flex-col-reverse fixed top-0 left-0 w-full z-[2147483647]";
112
- if (d === "left")
113
- return "flex flex-row-reverse fixed left-0 top-0 h-screen z-[2147483647]";
114
- if (d === "right")
115
- return "flex fixed right-0 top-0 h-screen z-[2147483647]";
116
- return "flex flex-col fixed bottom-0 left-0 w-full z-[2147483647]";
117
- }}
118
- >
119
- <div
120
- class={() => {
121
- const d = dock();
122
- if (d === "bottom" || d === "top")
123
- return "h-1 cursor-[ns-resize] bg-transparent hover:bg-accent transition-colors duration-200";
124
- return "w-1 cursor-[ew-resize] bg-transparent hover:bg-accent transition-colors duration-200";
125
- }}
126
- onMouseDown={handleMouseDown}
127
- />
107
+ }
108
+
109
+ render() {
110
+ const { plugins, registry, onClose } = this.p;
111
+ return (
128
112
  <div
129
113
  class={() => {
130
- const d = dock();
131
- if (d === "top") return "w-full h-full bg-bg border-b border-border";
132
- if (d === "left") return "w-full h-full bg-bg border-r border-border";
114
+ const d = this.dock;
115
+ if (d === "top")
116
+ return "flex flex-col-reverse fixed top-0 left-0 w-full z-[2147483647]";
117
+ if (d === "left")
118
+ return "flex flex-row-reverse fixed left-0 top-0 h-screen z-[2147483647]";
133
119
  if (d === "right")
134
- return "w-full h-full bg-bg border-l border-border";
135
- return "w-full h-full bg-bg border-t border-border";
136
- }}
137
- style={() => {
138
- const d = dock();
139
- return d === "bottom" || d === "top"
140
- ? { height: `${bottomSize().toString()}px` }
141
- : { width: `${vertSize().toString()}px` };
120
+ return "flex fixed right-0 top-0 h-screen z-[2147483647]";
121
+ return "flex flex-col fixed bottom-0 left-0 w-full z-[2147483647]";
142
122
  }}
143
123
  >
144
- <div class="flex flex-col flex-1 w-full h-full overflow-hidden">
145
- <div class="flex h-10 items-stretch border-b border-border bg-header shrink-0 px-2 gap-[2px]">
146
- <div class="flex items-center gap-[6px] pr-3 mr-[2px] border-r border-border shrink-0">
147
- <img class="h-[13px] w-[13px]" src={logo} />
148
- <span class="text-[10px] font-bold text-accent tracking-widest uppercase">
149
- praxisjs
150
- </span>
151
- </div>
124
+ <div
125
+ class={() => {
126
+ const d = this.dock;
127
+ if (d === "bottom" || d === "top")
128
+ return "h-1 cursor-[ns-resize] bg-transparent hover:bg-accent transition-colors duration-200";
129
+ return "w-1 cursor-[ew-resize] bg-transparent hover:bg-accent transition-colors duration-200";
130
+ }}
131
+ onMouseDown={(e: MouseEvent) => { this.handleMouseDown(e); }}
132
+ />
133
+ <div
134
+ class={() => {
135
+ const d = this.dock;
136
+ if (d === "top") return "w-full h-full bg-bg border-b border-border";
137
+ if (d === "left") return "w-full h-full bg-bg border-r border-border";
138
+ if (d === "right")
139
+ return "w-full h-full bg-bg border-l border-border";
140
+ return "w-full h-full bg-bg border-t border-border";
141
+ }}
142
+ style={() => {
143
+ const d = this.dock;
144
+ return d === "bottom" || d === "top"
145
+ ? { height: `${this.bottomSize.toString()}px` }
146
+ : { width: `${this.vertSize.toString()}px` };
147
+ }}
148
+ >
149
+ <div class="flex flex-col flex-1 w-full h-full overflow-hidden">
150
+ <div class="flex h-10 items-stretch border-b border-border bg-header shrink-0 px-2 gap-[2px]">
151
+ <div class="flex items-center gap-[6px] pr-3 mr-[2px] border-r border-border shrink-0">
152
+ <img class="h-[13px] w-[13px]" src={logo} />
153
+ <span class="text-[10px] font-bold text-accent tracking-widest uppercase">
154
+ praxisjs
155
+ </span>
156
+ </div>
152
157
 
153
- <div class="flex items-stretch flex-1">
154
- {plugins.map((p) => (
155
- <button
156
- key={p.id}
157
- class={() =>
158
- activeTab() === p.id
159
- ? "relative flex items-center px-3 text-[11px] font-semibold text-accent cursor-pointer"
160
- : "flex items-center px-3 text-[11px] text-muted cursor-pointer hover:text-text transition-colors duration-150"
161
- }
162
- onClick={() => {
163
- activeTab.set(p.id);
164
- }}
165
- >
166
- {p.label}
158
+ <div class="flex items-stretch flex-1">
159
+ {plugins.map((p) => (
160
+ <button
161
+ key={p.id}
162
+ class={() =>
163
+ this.activeTab === p.id
164
+ ? "relative flex items-center px-3 text-[11px] font-semibold text-accent cursor-pointer"
165
+ : "flex items-center px-3 text-[11px] text-muted cursor-pointer hover:text-text transition-colors duration-150"
166
+ }
167
+ onClick={() => {
168
+ this.activeTab = p.id;
169
+ }}
170
+ >
171
+ {p.label}
172
+ {() =>
173
+ this.activeTab === p.id ? (
174
+ <span class="absolute bottom-0 left-0 right-0 h-[2px] bg-accent" />
175
+ ) : null
176
+ }
177
+ </button>
178
+ ))}
179
+ </div>
180
+
181
+ <div class="flex items-center gap-[2px] shrink-0">
182
+ <div class="relative">
183
+ <IconButton
184
+ title="Dock position"
185
+ icon={EllipsisVerticalIcon}
186
+ active={() => this.dockMenuOpen}
187
+ onClick={() => {
188
+ this.dockMenuOpen = !this.dockMenuOpen;
189
+ }}
190
+ />
167
191
  {() =>
168
- activeTab() === p.id ? (
169
- <span class="absolute bottom-0 left-0 right-0 h-[2px] bg-accent" />
192
+ this.dockMenuOpen ? (
193
+ <div class="absolute right-0 top-full mt-1 flex gap-[2px] bg-bg border border-border rounded-md shadow-lg p-1 z-10">
194
+ {(["bottom", "top", "left", "right"] as Dock[]).map(
195
+ (v) => (
196
+ <IconButton
197
+ key={v}
198
+ title={`Dock ${v}`}
199
+ active={() => this.dock === v}
200
+ icon={DOCK_ICONS[v]}
201
+ onClick={() => {
202
+ this.dock = v;
203
+ this.dockMenuOpen = false;
204
+ }}
205
+ />
206
+ ),
207
+ )}
208
+ </div>
170
209
  ) : null
171
210
  }
172
- </button>
173
- ))}
174
- </div>
211
+ </div>
175
212
 
176
- <div class="flex items-center gap-[2px] shrink-0">
177
- <div class="relative">
178
- <IconButton
179
- title="Dock position"
180
- icon={EllipsisVerticalIcon}
181
- active={() => dockMenuOpen()}
182
- onClick={() => {
183
- dockMenuOpen.set(!dockMenuOpen());
184
- }}
185
- />
186
- {() =>
187
- dockMenuOpen() ? (
188
- <div class="absolute right-0 top-full mt-1 flex gap-[2px] bg-bg border border-border rounded-md shadow-lg p-1 z-10">
189
- {(["bottom", "top", "left", "right"] as Dock[]).map(
190
- (v) => (
191
- <IconButton
192
- key={v}
193
- title={`Dock ${v}`}
194
- active={() => dock() === v}
195
- icon={DOCK_ICONS[v]}
196
- onClick={() => {
197
- dock.set(v);
198
- dockMenuOpen.set(false);
199
- }}
200
- />
201
- ),
202
- )}
203
- </div>
204
- ) : null
205
- }
206
- </div>
213
+ <div class="w-px h-3 bg-border mx-1" />
207
214
 
208
- <div class="w-px h-3 bg-border mx-1" />
209
-
210
- <IconButton title="Close" icon={XIcon} onClick={onClose} />
215
+ <IconButton title="Close" icon={XIcon} onClick={onClose} />
216
+ </div>
211
217
  </div>
212
- </div>
213
218
 
214
- <div class="flex-1 overflow-hidden flex flex-col">
215
- {() => {
216
- const plugin = plugins.find((p) => p.id === activeTab());
217
- const Active = plugin?.component;
218
- return Active ? <Active registry={registry} /> : null;
219
- }}
219
+ <div class="flex-1 overflow-hidden flex flex-col">
220
+ {() => {
221
+ const plugin = plugins.find((p) => p.id === this.activeTab);
222
+ const Active = plugin?.component;
223
+ return Active ? <Active registry={registry} /> : null;
224
+ }}
225
+ </div>
220
226
  </div>
221
227
  </div>
222
228
  </div>
223
- </div>
224
- );
229
+ );
230
+ }
225
231
  }
@@ -1,12 +1,18 @@
1
- export function EmptyState({ message }: { message: string }) {
2
- return (
3
- <div class="flex flex-col items-center justify-center gap-2 py-12">
4
- <span class="text-subtle text-[20px] leading-none select-none font-mono">
5
-
6
- </span>
7
- <p class="text-subtle text-[11px] text-center leading-relaxed max-w-[200px]">
8
- {message}
9
- </p>
10
- </div>
11
- );
1
+ import { StatelessComponent } from "@praxisjs/core";
2
+ import { Component } from "@praxisjs/decorators";
3
+
4
+ @Component()
5
+ export class EmptyState extends StatelessComponent<{ message: string }> {
6
+ render() {
7
+ return (
8
+ <div class="flex flex-col items-center justify-center gap-2 py-12">
9
+ <span class="text-subtle text-[20px] leading-none select-none font-mono">
10
+
11
+ </span>
12
+ <p class="text-subtle text-[11px] text-center leading-relaxed max-w-[200px]">
13
+ {this.props.message}
14
+ </p>
15
+ </div>
16
+ );
17
+ }
12
18
  }
@@ -1,30 +1,33 @@
1
- import type { Component } from "@praxisjs/shared";
1
+ import { StatelessComponent } from "@praxisjs/core";
2
+ import { Component } from "@praxisjs/decorators";
3
+ import type { ComponentElement } from "@praxisjs/shared";
2
4
 
3
- export function IconButton({
4
- title,
5
- active,
6
- icon: Icon,
7
- onClick,
8
- }: {
5
+ interface IconButtonProps {
9
6
  title?: string;
10
7
  active?: boolean | (() => boolean);
11
- icon: Component;
8
+ icon: ComponentElement;
12
9
  onClick: () => void;
13
- }) {
14
- return (
15
- <button
16
- title={title}
17
- onClick={onClick}
18
- class={() => {
19
- const isActive = typeof active === "function" ? active() : active;
20
- return `w-[22px] h-[22px] flex items-center justify-center rounded-md text-[11px] cursor-pointer transition-colors duration-150 ${
21
- isActive
22
- ? "text-accent bg-soft color-white"
23
- : "text-subtle hover:color-muted hover:bg-section"
24
- }`;
25
- }}
26
- >
27
- <Icon />
28
- </button>
29
- );
10
+ }
11
+
12
+ @Component()
13
+ export class IconButton extends StatelessComponent<IconButtonProps> {
14
+ render() {
15
+ const { title, active, icon: Icon, onClick } = this.props;
16
+ return (
17
+ <button
18
+ title={title}
19
+ onClick={onClick}
20
+ class={() => {
21
+ const isActive = typeof active === "function" ? active() : active;
22
+ return `w-[22px] h-[22px] flex items-center justify-center rounded-md text-[11px] cursor-pointer transition-colors duration-150 ${
23
+ isActive
24
+ ? "text-accent bg-soft color-white"
25
+ : "text-subtle hover:color-muted hover:bg-section"
26
+ }`;
27
+ }}
28
+ >
29
+ <Icon />
30
+ </button>
31
+ );
32
+ }
30
33
  }
@@ -1,18 +1,20 @@
1
+ import { StatelessComponent } from "@praxisjs/core";
2
+ import { Component } from "@praxisjs/decorators";
1
3
  import type { Children } from "@praxisjs/shared";
2
4
 
3
- export function PanelSection({
4
- label,
5
- children,
6
- }: {
5
+ @Component()
6
+ export class PanelSection extends StatelessComponent<{
7
7
  label: string;
8
8
  children?: Children;
9
- }) {
10
- return (
11
- <div class="border-b border-border">
12
- <div class="px-3 py-[4px] text-[9px] text-subtle font-bold tracking-[0.12em] uppercase bg-section border-b border-border">
13
- {label}
9
+ }> {
10
+ render() {
11
+ return (
12
+ <div class="border-b border-border">
13
+ <div class="px-3 py-[4px] text-[9px] text-subtle font-bold tracking-[0.12em] uppercase bg-section border-b border-border">
14
+ {this.props.label}
15
+ </div>
16
+ {this.props.children}
14
17
  </div>
15
- {children}
16
- </div>
17
- );
18
+ );
19
+ }
18
20
  }
@@ -1,18 +1,22 @@
1
- export function SearchInput({
2
- placeholder,
3
- onInput,
4
- }: {
1
+ import { StatelessComponent } from "@praxisjs/core";
2
+ import { Component } from "@praxisjs/decorators";
3
+
4
+ @Component()
5
+ export class SearchInput extends StatelessComponent<{
5
6
  placeholder: string;
6
7
  onInput: (value: string) => void;
7
- }) {
8
- return (
9
- <input
10
- type="text"
11
- placeholder={placeholder}
12
- class="w-full bg-input border border-border rounded-lg text-text text-[11px] px-3 py-[5px] outline-none box-border transition-shadow duration-150"
13
- onInput={(e: InputEvent) => {
14
- onInput((e.target as HTMLInputElement).value);
15
- }}
16
- />
17
- );
8
+ }> {
9
+ render() {
10
+ const { placeholder, onInput } = this.props;
11
+ return (
12
+ <input
13
+ type="text"
14
+ placeholder={placeholder}
15
+ class="w-full bg-input border border-border rounded-lg text-text text-[11px] px-3 py-[5px] outline-none box-border transition-shadow duration-150"
16
+ onInput={(e: InputEvent) => {
17
+ onInput((e.target as HTMLInputElement).value);
18
+ }}
19
+ />
20
+ );
21
+ }
18
22
  }
@@ -1,18 +1,21 @@
1
+ import { StatelessComponent } from "@praxisjs/core";
2
+ import { Component } from "@praxisjs/decorators";
1
3
  import type { Children } from "@praxisjs/shared";
2
4
 
3
- export function SidePanel({
4
- children,
5
- width = "280px",
6
- }: {
5
+ @Component()
6
+ export class SidePanel extends StatelessComponent<{
7
7
  children?: Children;
8
8
  width?: string;
9
- }) {
10
- return (
11
- <div
12
- class="shrink-0 border-l border-border flex flex-col overflow-hidden"
13
- style={{ width }}
14
- >
15
- {children}
16
- </div>
17
- );
9
+ }> {
10
+ render() {
11
+ const { children, width = "280px" } = this.props;
12
+ return (
13
+ <div
14
+ class="shrink-0 border-l border-border flex flex-col overflow-hidden"
15
+ style={{ width }}
16
+ >
17
+ {children}
18
+ </div>
19
+ );
20
+ }
18
21
  }
package/vite.config.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import path from "path";
2
2
 
3
3
  import UnoCSS from "unocss/vite";
4
- import { defineConfig } from "vite";
4
+ import { defineConfig, type PluginOption } from "vite";
5
5
  import dts from "vite-plugin-dts";
6
6
 
7
7
  export default defineConfig({
8
- plugins: [UnoCSS(), dts({ rollupTypes: true })],
8
+ plugins: [...UnoCSS(), dts({ rollupTypes: true })] as PluginOption[],
9
9
  esbuild: {
10
10
  jsxImportSource: "@praxisjs/jsx",
11
11
  jsx: "automatic",
@@ -17,13 +17,7 @@ export default defineConfig({
17
17
  fileName: "index",
18
18
  },
19
19
  rollupOptions: {
20
- external: [
21
- "@praxisjs/core",
22
- "@praxisjs/jsx",
23
- "@praxisjs/jsx/jsx-runtime",
24
- "@praxisjs/runtime",
25
- "@praxisjs/shared",
26
- ],
20
+ external: (id) => id.startsWith("@praxisjs/"),
27
21
  },
28
22
  cssCodeSplit: false,
29
23
  },