@praxisjs/devtools 0.1.1 → 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.
- package/CHANGELOG.md +22 -0
- package/dist/index.d.ts +5 -7
- package/dist/index.js +1035 -860
- package/package.json +8 -7
- package/src/decorators/debug.ts +102 -77
- package/src/decorators/trace.ts +15 -11
- package/src/icons/ellipsis-vertical.tsx +25 -19
- package/src/icons/panel-bottom.tsx +24 -18
- package/src/icons/panel-left.tsx +24 -18
- package/src/icons/panel-right.tsx +24 -18
- package/src/icons/panel-top.tsx +24 -18
- package/src/icons/x.tsx +24 -18
- package/src/plugins/components/components/component-detail.tsx +59 -54
- package/src/plugins/components/components/component-row.tsx +36 -33
- package/src/plugins/components/components/detail-row.tsx +21 -18
- package/src/plugins/components/components/detail-section.tsx +14 -12
- package/src/plugins/components/components/status-dot.tsx +20 -11
- package/src/plugins/components/components-tab.tsx +66 -61
- package/src/plugins/signals/components/signal-detail.tsx +35 -27
- package/src/plugins/signals/components/signal-row.tsx +32 -28
- package/src/plugins/signals/signals-tab.tsx +92 -79
- package/src/plugins/timeline/components/badge.tsx +15 -9
- package/src/plugins/timeline/components/timeline-row.tsx +52 -45
- package/src/plugins/timeline/timeline-tab.tsx +90 -77
- package/src/plugins/types.ts +2 -2
- package/src/ui/dev-tools.tsx +45 -39
- package/src/ui/panel.tsx +152 -146
- package/src/ui/shared/empty-state.tsx +17 -11
- package/src/ui/shared/icon-button.tsx +28 -25
- package/src/ui/shared/panel-section.tsx +14 -12
- package/src/ui/shared/search-input.tsx +19 -15
- package/src/ui/shared/side-panel.tsx +16 -13
- package/vite.config.ts +3 -9
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { EmptyState } from "@shared/empty-state";
|
|
2
2
|
import { SearchInput } from "@shared/search-input";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { StatefulComponent } from "@praxisjs/core";
|
|
5
|
+
import { Component, State } from "@praxisjs/decorators";
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
import { SignalDetail } from "./components/signal-detail";
|
|
7
9
|
import { SignalRow } from "./components/signal-row";
|
|
@@ -9,91 +11,102 @@ import { SignalRow } from "./components/signal-row";
|
|
|
9
11
|
import type { Registry } from "@core/registry";
|
|
10
12
|
import type { SignalEntry } from "@core/types";
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
@Component()
|
|
15
|
+
export class SignalsTab extends StatefulComponent {
|
|
16
|
+
@State() signals: SignalEntry[] = [];
|
|
17
|
+
@State() search = "";
|
|
18
|
+
@State() selectedId: string | null = null;
|
|
19
|
+
|
|
20
|
+
private _handlers: Array<() => void> = [];
|
|
21
|
+
|
|
22
|
+
private get registry() {
|
|
23
|
+
return this.props.registry as Registry;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
onMount() {
|
|
27
|
+
this.signals = this.registry.getSignals();
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
registry.bus.on("signal:registered", () => {
|
|
21
|
-
signals.set(registry.getSignals());
|
|
29
|
+
this._handlers = [
|
|
30
|
+
this.registry.bus.on("signal:registered", () => {
|
|
31
|
+
this.signals = this.registry.getSignals();
|
|
22
32
|
}),
|
|
23
|
-
registry.bus.on("signal:changed", () => {
|
|
24
|
-
signals.
|
|
33
|
+
this.registry.bus.on("signal:changed", () => {
|
|
34
|
+
this.signals = this.registry.getSignals();
|
|
25
35
|
}),
|
|
26
36
|
];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
onUnmount(() => {
|
|
30
|
-
handlers.forEach((off) => {
|
|
31
|
-
off();
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div class="flex h-full overflow-hidden">
|
|
37
|
-
<div class="flex-1 flex flex-col overflow-hidden min-w-0">
|
|
38
|
-
<div class="px-3 py-2 border-b border-border bg-bg shrink-0">
|
|
39
|
-
<SearchInput
|
|
40
|
-
placeholder="Search signals…"
|
|
41
|
-
onInput={(v) => {
|
|
42
|
-
search.set(v);
|
|
43
|
-
}}
|
|
44
|
-
/>
|
|
45
|
-
</div>
|
|
37
|
+
}
|
|
46
38
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<span>Value</span>
|
|
51
|
-
<span>Age</span>
|
|
52
|
-
</div>
|
|
39
|
+
onUnmount() {
|
|
40
|
+
this._handlers.forEach((off) => { off(); });
|
|
41
|
+
}
|
|
53
42
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
43
|
+
render() {
|
|
44
|
+
return (
|
|
45
|
+
<div class="flex h-full overflow-hidden">
|
|
46
|
+
<div class="flex-1 flex flex-col overflow-hidden min-w-0">
|
|
47
|
+
<div class="px-3 py-2 border-b border-border bg-bg shrink-0">
|
|
48
|
+
<SearchInput
|
|
49
|
+
placeholder="Search signals…"
|
|
50
|
+
onInput={(v) => {
|
|
51
|
+
this.search = v;
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="grid grid-cols-[1.2fr_0.8fr_1fr_auto] items-center px-3 h-7 text-[9px] text-subtle font-bold tracking-[0.12em] uppercase border-b border-border bg-section gap-2 shrink-0">
|
|
57
|
+
<span>Signal</span>
|
|
58
|
+
<span>Component</span>
|
|
59
|
+
<span>Value</span>
|
|
60
|
+
<span>Age</span>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="flex-1 overflow-y-auto">
|
|
64
|
+
{() => {
|
|
65
|
+
const q = this.search.toLowerCase();
|
|
66
|
+
const filtered =
|
|
67
|
+
q === ""
|
|
68
|
+
? this.signals
|
|
69
|
+
: this.signals.filter(
|
|
70
|
+
(s) =>
|
|
71
|
+
s.label.toLowerCase().includes(q) ||
|
|
72
|
+
s.componentName.toLowerCase().includes(q),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (filtered.length === 0) {
|
|
76
|
+
return (
|
|
77
|
+
<EmptyState
|
|
78
|
+
message={
|
|
79
|
+
this.signals.length === 0
|
|
80
|
+
? "No signals tracked. Add @Debug() on top of @State() properties."
|
|
81
|
+
: "No signals match your search."
|
|
82
|
+
}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return filtered.map((s) => (
|
|
88
|
+
<SignalRow
|
|
89
|
+
key={s.id}
|
|
90
|
+
entry={s}
|
|
91
|
+
selected={this.selectedId === s.id}
|
|
92
|
+
onClick={() => {
|
|
93
|
+
this.selectedId =
|
|
94
|
+
this.selectedId === s.id ? null : s.id;
|
|
95
|
+
}}
|
|
74
96
|
/>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return filtered.map((s) => (
|
|
79
|
-
<SignalRow
|
|
80
|
-
key={s.id}
|
|
81
|
-
entry={s}
|
|
82
|
-
selected={selectedId() === s.id}
|
|
83
|
-
onClick={() => {
|
|
84
|
-
selectedId.update((id) => (id === s.id ? null : s.id));
|
|
85
|
-
}}
|
|
86
|
-
/>
|
|
87
|
-
));
|
|
88
|
-
}}
|
|
97
|
+
));
|
|
98
|
+
}}
|
|
99
|
+
</div>
|
|
89
100
|
</div>
|
|
90
|
-
</div>
|
|
91
101
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
{() => {
|
|
103
|
+
const id = this.selectedId;
|
|
104
|
+
const entry = id
|
|
105
|
+
? (this.signals.find((s) => s.id === id) ?? null)
|
|
106
|
+
: null;
|
|
107
|
+
return entry ? <SignalDetail entry={entry} /> : null;
|
|
108
|
+
}}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
99
112
|
}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import { StatelessComponent } from "@praxisjs/core";
|
|
2
|
+
import { Component } from "@praxisjs/decorators";
|
|
3
|
+
|
|
1
4
|
import { TYPE_META } from "../constants";
|
|
2
5
|
|
|
3
6
|
import type { TimelineEventType } from "@core/types";
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
@Component()
|
|
9
|
+
export class Badge extends StatelessComponent<{ type: TimelineEventType }> {
|
|
10
|
+
render() {
|
|
11
|
+
const meta = TYPE_META[this.props.type];
|
|
12
|
+
return (
|
|
13
|
+
<span
|
|
14
|
+
class={`text-[9px] px-[6px] py-[2px] rounded font-bold uppercase tracking-[0.07em] shrink-0 ${meta.cls}`}
|
|
15
|
+
>
|
|
16
|
+
{meta.label}
|
|
17
|
+
</span>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
14
20
|
}
|
|
@@ -1,55 +1,62 @@
|
|
|
1
1
|
import { time } from "@utils/format-time";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { StatefulComponent } from "@praxisjs/core";
|
|
4
|
+
import { Component, State } from "@praxisjs/decorators";
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
import { Badge } from "./badge";
|
|
6
8
|
|
|
7
9
|
import type { TimelineEntry } from "@core/types";
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</span>
|
|
30
|
-
{hasData && (
|
|
31
|
-
<span class="text-subtle text-[11px] shrink-0 w-4 text-center">
|
|
32
|
-
{() => (open() ? "▾" : "▸")}
|
|
11
|
+
@Component()
|
|
12
|
+
export class TimelineRow extends StatefulComponent {
|
|
13
|
+
@State() open = false;
|
|
14
|
+
|
|
15
|
+
render() {
|
|
16
|
+
const { entry } = this.props as { entry: TimelineEntry };
|
|
17
|
+
const hasData = Object.keys(entry.data).length > 0;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div class="border-b border-border">
|
|
21
|
+
<div
|
|
22
|
+
onClick={() => {
|
|
23
|
+
if (hasData) this.open = !this.open;
|
|
24
|
+
}}
|
|
25
|
+
class={`flex items-center gap-2 px-3 py-2 transition-colors duration-100 ${
|
|
26
|
+
hasData ? "cursor-pointer hover:bg-section" : "cursor-default"
|
|
27
|
+
}`}
|
|
28
|
+
>
|
|
29
|
+
<span class="text-subtle text-[10px] w-10 shrink-0 text-right tabular-nums font-mono">
|
|
30
|
+
{time(entry.timestamp)}
|
|
33
31
|
</span>
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
<Badge type={entry.type} />
|
|
33
|
+
<span class="flex-1 text-text font-mono text-[11px] truncate">
|
|
34
|
+
{entry.label}
|
|
35
|
+
</span>
|
|
36
|
+
{hasData && (
|
|
37
|
+
<span class="text-subtle text-[11px] shrink-0 w-4 text-center">
|
|
38
|
+
{() => (this.open ? "▾" : "▸")}
|
|
39
|
+
</span>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
{() =>
|
|
44
|
+
this.open && hasData ? (
|
|
45
|
+
<div class="px-3 py-2 pl-[56px] font-mono text-[11px] bg-section border-t border-border">
|
|
46
|
+
{Object.entries(entry.data).map(([k, v]) => (
|
|
47
|
+
<div key={k} class="flex gap-3 py-[3px]">
|
|
48
|
+
<span class="text-accent shrink-0">{k}:</span>
|
|
49
|
+
<span class="text-muted truncate">
|
|
50
|
+
{typeof v === "object"
|
|
51
|
+
? JSON.stringify(v)
|
|
52
|
+
: String(v as unknown)}
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
) : null
|
|
58
|
+
}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
55
62
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { EmptyState } from "@shared/empty-state";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { StatefulComponent } from "@praxisjs/core";
|
|
4
|
+
import { Component, State } from "@praxisjs/decorators";
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
import { TimelineRow } from "./components/timeline-row";
|
|
6
8
|
import { FILTERS, type Filter } from "./constants";
|
|
@@ -8,94 +10,105 @@ import { FILTERS, type Filter } from "./constants";
|
|
|
8
10
|
import type { Registry } from "@core/registry";
|
|
9
11
|
import type { TimelineEntry } from "@core/types";
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
@Component()
|
|
14
|
+
export class TimelineTab extends StatefulComponent {
|
|
15
|
+
@State() entries: TimelineEntry[] = [];
|
|
16
|
+
@State() filter: Filter = "all";
|
|
17
|
+
@State() paused = false;
|
|
18
|
+
|
|
19
|
+
private _handlers: Array<() => void> = [];
|
|
20
|
+
|
|
21
|
+
private get registry() {
|
|
22
|
+
return this.props.registry as Registry;
|
|
23
|
+
}
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
onMount() {
|
|
26
|
+
this.entries = this.registry.getTimeline();
|
|
27
|
+
|
|
28
|
+
this._handlers = [
|
|
29
|
+
this.registry.bus.on("timeline:push", () => {
|
|
30
|
+
if (!this.paused) this.entries = this.registry.getTimeline();
|
|
21
31
|
}),
|
|
22
32
|
];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
onUnmount(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onUnmount() {
|
|
36
|
+
this._handlers.forEach((off) => { off(); });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
render() {
|
|
40
|
+
return (
|
|
41
|
+
<div class="flex flex-col h-full overflow-hidden">
|
|
42
|
+
<div class="flex items-center gap-[3px] px-2 py-2 border-b border-border bg-bg shrink-0 flex-wrap">
|
|
43
|
+
{FILTERS.map((f) => (
|
|
44
|
+
<button
|
|
45
|
+
key={f.value}
|
|
46
|
+
onClick={() => {
|
|
47
|
+
this.filter = f.value;
|
|
48
|
+
}}
|
|
49
|
+
class={() =>
|
|
50
|
+
this.filter === f.value
|
|
51
|
+
? "text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans bg-soft text-accent font-semibold"
|
|
52
|
+
: "text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans text-muted hover:text-text hover:bg-section transition-colors duration-150"
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
{f.label}
|
|
56
|
+
</button>
|
|
57
|
+
))}
|
|
58
|
+
|
|
59
|
+
<div class="flex-1" />
|
|
60
|
+
|
|
35
61
|
<button
|
|
36
|
-
key={f.value}
|
|
37
62
|
onClick={() => {
|
|
38
|
-
|
|
63
|
+
if (this.paused) this.entries = this.registry.getTimeline();
|
|
64
|
+
this.paused = !this.paused;
|
|
39
65
|
}}
|
|
40
66
|
class={() =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
`text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans border border-border transition-colors duration-150 ${
|
|
68
|
+
this.paused
|
|
69
|
+
? "text-warn border-warn"
|
|
70
|
+
: "text-muted hover:text-text"
|
|
71
|
+
}`
|
|
44
72
|
}
|
|
45
73
|
>
|
|
46
|
-
{
|
|
74
|
+
{() => (this.paused ? "Resume" : "Pause")}
|
|
47
75
|
</button>
|
|
48
|
-
))}
|
|
49
76
|
|
|
50
|
-
|
|
77
|
+
<button
|
|
78
|
+
onClick={() => {
|
|
79
|
+
this.entries = [];
|
|
80
|
+
}}
|
|
81
|
+
class="text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans border border-border text-muted hover:text-text transition-colors duration-150"
|
|
82
|
+
>
|
|
83
|
+
Clear
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
51
86
|
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class="text-[11px] px-2 py-[3px] rounded cursor-pointer font-sans border border-border text-muted hover:text-text transition-colors duration-150"
|
|
71
|
-
>
|
|
72
|
-
Clear
|
|
73
|
-
</button>
|
|
74
|
-
</div>
|
|
87
|
+
<div class="flex-1 overflow-y-auto">
|
|
88
|
+
{() => {
|
|
89
|
+
const f = this.filter;
|
|
90
|
+
const filtered =
|
|
91
|
+
f === "all"
|
|
92
|
+
? this.entries
|
|
93
|
+
: this.entries.filter(
|
|
94
|
+
(e) =>
|
|
95
|
+
e.type === f ||
|
|
96
|
+
(f === "component:mount" &&
|
|
97
|
+
e.type === "component:unmount"),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (filtered.length === 0) {
|
|
101
|
+
return (
|
|
102
|
+
<EmptyState message="No events yet. Interact with your app to see the timeline." />
|
|
103
|
+
);
|
|
104
|
+
}
|
|
75
105
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
? entries()
|
|
82
|
-
: entries().filter(
|
|
83
|
-
(e) =>
|
|
84
|
-
e.type === f ||
|
|
85
|
-
(f === "component:mount" && e.type === "component:unmount"),
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
if (filtered.length === 0) {
|
|
89
|
-
return (
|
|
90
|
-
<EmptyState message="No events yet. Interact with your app to see the timeline." />
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return [...filtered]
|
|
95
|
-
.reverse()
|
|
96
|
-
.map((e) => <TimelineRow key={e.id} entry={e} />);
|
|
97
|
-
}}
|
|
106
|
+
return [...filtered]
|
|
107
|
+
.reverse()
|
|
108
|
+
.map((e) => <TimelineRow key={e.id} entry={e} />);
|
|
109
|
+
}}
|
|
110
|
+
</div>
|
|
98
111
|
</div>
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
);
|
|
113
|
+
}
|
|
101
114
|
}
|
package/src/plugins/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ComponentElement } from "@praxisjs/shared";
|
|
2
2
|
|
|
3
3
|
import type { Registry } from "@core/registry";
|
|
4
4
|
|
|
@@ -6,5 +6,5 @@ export interface DevtoolsPlugin {
|
|
|
6
6
|
id: string;
|
|
7
7
|
label: string;
|
|
8
8
|
setup?: (registry: Registry) => void;
|
|
9
|
-
component:
|
|
9
|
+
component: ComponentElement;
|
|
10
10
|
}
|
package/src/ui/dev-tools.tsx
CHANGED
|
@@ -6,7 +6,8 @@ import { SignalsPlugin } from "@plugins/signals";
|
|
|
6
6
|
import { TimelinePlugin } from "@plugins/timeline";
|
|
7
7
|
import unoReset from "@unocss/reset/tailwind-v4.css?inline";
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { StatefulComponent } from "@praxisjs/core";
|
|
10
|
+
import { Component, State } from "@praxisjs/decorators";
|
|
10
11
|
import { render } from "@praxisjs/runtime";
|
|
11
12
|
|
|
12
13
|
import { Panel } from "./panel";
|
|
@@ -23,42 +24,47 @@ export interface DevToolsOptions {
|
|
|
23
24
|
plugins?: DevtoolsPlugin[];
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
27
|
+
@Component()
|
|
28
|
+
class DevToolsApp extends StatefulComponent {
|
|
29
|
+
@State() open = false;
|
|
30
|
+
|
|
31
|
+
private get p() {
|
|
32
|
+
return this.props as unknown as {
|
|
33
|
+
plugins: DevtoolsPlugin[];
|
|
34
|
+
registry: Registry;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
render() {
|
|
39
|
+
const { plugins, registry } = this.p;
|
|
40
|
+
return (
|
|
41
|
+
<div>
|
|
42
|
+
{() =>
|
|
43
|
+
this.open ? (
|
|
44
|
+
<Panel
|
|
45
|
+
plugins={plugins}
|
|
46
|
+
registry={registry}
|
|
47
|
+
onClose={() => {
|
|
48
|
+
this.open = false;
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
) : (
|
|
52
|
+
<button
|
|
53
|
+
onClick={() => {
|
|
54
|
+
this.open = true;
|
|
55
|
+
}}
|
|
56
|
+
class="fixed bottom-5 right-5 z-[2147483647] flex items-center gap-1 pl-[10px] pr-[14px] h-[32px] rounded-xl font-sans font-semibold text-[12px] text-accent bg-header border border-border cursor-pointer select-none shadow-[0_8px_32px_rgba(0,0,0,0.7)] transition-all duration-200 hover:border-accent hover:bg-soft hover:shadow-[0_8px_32px_rgba(56,189,248,0.1)]"
|
|
57
|
+
>
|
|
58
|
+
<img class="h-[13px] w-[13px]" src={logo} />
|
|
59
|
+
<span class="text-[10px] font-bold text-accent tracking-widest uppercase">
|
|
60
|
+
devtools
|
|
61
|
+
</span>
|
|
62
|
+
</button>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
export const DevTools = {
|
|
@@ -90,7 +96,7 @@ export const DevTools = {
|
|
|
90
96
|
shadow.appendChild(container);
|
|
91
97
|
|
|
92
98
|
this.cleanup = render(
|
|
93
|
-
<DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
|
|
99
|
+
() => <DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
|
|
94
100
|
container,
|
|
95
101
|
);
|
|
96
102
|
},
|
|
@@ -114,7 +120,7 @@ export const DevTools = {
|
|
|
114
120
|
|
|
115
121
|
this.cleanup?.();
|
|
116
122
|
this.cleanup = render(
|
|
117
|
-
<DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
|
|
123
|
+
() => <DevToolsApp plugins={this.plugins} registry={Registry.instance} />,
|
|
118
124
|
container,
|
|
119
125
|
);
|
|
120
126
|
},
|