@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,70 +1,75 @@
|
|
|
1
1
|
import { SidePanel } from "@shared/side-panel";
|
|
2
2
|
import { formatValue } from "@utils/format-value";
|
|
3
3
|
|
|
4
|
+
import { StatelessComponent } from "@praxisjs/core";
|
|
5
|
+
import { Component } from "@praxisjs/decorators";
|
|
6
|
+
|
|
7
|
+
|
|
4
8
|
import { DetailRow } from "./detail-row";
|
|
5
9
|
import { DetailSection } from "./detail-section";
|
|
6
10
|
import { StatusDot } from "./status-dot";
|
|
7
11
|
|
|
8
12
|
import type { ComponentEntry, SignalEntry } from "@core/types";
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
signals,
|
|
13
|
-
}: {
|
|
14
|
+
@Component()
|
|
15
|
+
export class ComponentDetail extends StatelessComponent<{
|
|
14
16
|
entry: ComponentEntry;
|
|
15
17
|
signals: SignalEntry[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<DetailSection label="Stats">
|
|
28
|
-
<DetailRow k="renders" v={String(entry.renderCount)} />
|
|
29
|
-
<DetailRow
|
|
30
|
-
k="last render"
|
|
31
|
-
v={`${entry.lastRenderDuration.toFixed(3)}ms`}
|
|
32
|
-
/>
|
|
33
|
-
<DetailRow k="status" v={entry.status} />
|
|
34
|
-
</DetailSection>
|
|
18
|
+
}> {
|
|
19
|
+
render() {
|
|
20
|
+
const { entry, signals } = this.props;
|
|
21
|
+
return (
|
|
22
|
+
<SidePanel>
|
|
23
|
+
<div class="px-3 py-2 border-b border-border flex items-center gap-2 bg-bg shrink-0">
|
|
24
|
+
<StatusDot status={entry.status} />
|
|
25
|
+
<span class="text-accent font-mono text-[11px] font-semibold truncate pl-1">
|
|
26
|
+
<{entry.name}>
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
<DetailSection label="
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/>
|
|
45
|
-
))}
|
|
30
|
+
<div class="flex-1 overflow-y-auto">
|
|
31
|
+
<DetailSection label="Stats">
|
|
32
|
+
<DetailRow k="renders" v={String(entry.renderCount)} />
|
|
33
|
+
<DetailRow
|
|
34
|
+
k="last render"
|
|
35
|
+
v={`${entry.lastRenderDuration.toFixed(3)}ms`}
|
|
36
|
+
/>
|
|
37
|
+
<DetailRow k="status" v={entry.status} />
|
|
46
38
|
</DetailSection>
|
|
47
|
-
)}
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
>
|
|
59
|
-
<span class="text-[11px] text-text font-mono">{ev.hook}</span>
|
|
60
|
-
<span class="text-[10px] text-subtle tabular-nums">
|
|
61
|
-
{new Date(ev.timestamp).toLocaleTimeString()}
|
|
62
|
-
</span>
|
|
63
|
-
</div>
|
|
40
|
+
{signals.length > 0 && (
|
|
41
|
+
<DetailSection label="State">
|
|
42
|
+
{signals.map((s) => (
|
|
43
|
+
<DetailRow
|
|
44
|
+
key={s.id}
|
|
45
|
+
k={s.label}
|
|
46
|
+
v={formatValue(s.value)}
|
|
47
|
+
signal
|
|
48
|
+
/>
|
|
64
49
|
))}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
50
|
+
</DetailSection>
|
|
51
|
+
)}
|
|
52
|
+
|
|
53
|
+
{entry.lifecycle.length > 0 && (
|
|
54
|
+
<DetailSection label="Lifecycle">
|
|
55
|
+
{[...entry.lifecycle]
|
|
56
|
+
.reverse()
|
|
57
|
+
.slice(0, 20)
|
|
58
|
+
.map((ev, i) => (
|
|
59
|
+
<div
|
|
60
|
+
key={String(i)}
|
|
61
|
+
class="px-3 py-2 flex justify-between items-center border-b border-border"
|
|
62
|
+
>
|
|
63
|
+
<span class="text-[11px] text-text font-mono">{ev.hook}</span>
|
|
64
|
+
<span class="text-[10px] text-subtle tabular-nums">
|
|
65
|
+
{new Date(ev.timestamp).toLocaleTimeString()}
|
|
66
|
+
</span>
|
|
67
|
+
</div>
|
|
68
|
+
))}
|
|
69
|
+
</DetailSection>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
</SidePanel>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
70
75
|
}
|
|
@@ -1,42 +1,45 @@
|
|
|
1
|
+
import { StatelessComponent } from "@praxisjs/core";
|
|
2
|
+
import { Component } from "@praxisjs/decorators";
|
|
3
|
+
|
|
1
4
|
import { StatusDot } from "./status-dot";
|
|
2
5
|
|
|
3
6
|
import type { ComponentEntry } from "@core/types";
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
selected,
|
|
8
|
-
onClick,
|
|
9
|
-
}: {
|
|
8
|
+
@Component()
|
|
9
|
+
export class ComponentRow extends StatelessComponent<{
|
|
10
10
|
entry: ComponentEntry;
|
|
11
11
|
selected: () => boolean;
|
|
12
12
|
onClick: () => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
selected() && (
|
|
25
|
-
<span class="absolute left-0 top-0 bottom-0 w-[2px] bg-accent rounded-r" />
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
<StatusDot status={entry.status} />
|
|
29
|
-
<span class="text-accent font-mono text-[11px] flex-1 truncate pl-1">
|
|
30
|
-
<{entry.name}>
|
|
31
|
-
</span>
|
|
32
|
-
<span class="text-muted text-[11px] tabular-nums">
|
|
33
|
-
×{entry.renderCount}
|
|
34
|
-
</span>
|
|
35
|
-
<span
|
|
36
|
-
class={`text-[10px] tabular-nums w-12 text-right ${entry.lastRenderDuration > 16 ? "text-warn" : "text-subtle"}`}
|
|
13
|
+
}> {
|
|
14
|
+
render() {
|
|
15
|
+
const { entry, selected, onClick } = this.props;
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
onClick={onClick}
|
|
19
|
+
class={() =>
|
|
20
|
+
`relative flex items-center gap-2 px-3 py-2 cursor-pointer border-b border-border transition-colors duration-100 ${
|
|
21
|
+
selected() ? "bg-selected" : "hover:bg-section"
|
|
22
|
+
}`
|
|
23
|
+
}
|
|
37
24
|
>
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
{() =>
|
|
26
|
+
selected() && (
|
|
27
|
+
<span class="absolute left-0 top-0 bottom-0 w-[2px] bg-accent rounded-r" />
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
<StatusDot status={entry.status} />
|
|
31
|
+
<span class="text-accent font-mono text-[11px] flex-1 truncate pl-1">
|
|
32
|
+
<{entry.name}>
|
|
33
|
+
</span>
|
|
34
|
+
<span class="text-muted text-[11px] tabular-nums">
|
|
35
|
+
×{entry.renderCount}
|
|
36
|
+
</span>
|
|
37
|
+
<span
|
|
38
|
+
class={`text-[10px] tabular-nums w-12 text-right ${entry.lastRenderDuration > 16 ? "text-warn" : "text-subtle"}`}
|
|
39
|
+
>
|
|
40
|
+
{entry.lastRenderDuration.toFixed(1)}ms
|
|
41
|
+
</span>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
42
45
|
}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { StatelessComponent } from "@praxisjs/core";
|
|
2
|
+
import { Component } from "@praxisjs/decorators";
|
|
3
|
+
|
|
4
|
+
@Component()
|
|
5
|
+
export class DetailRow extends StatelessComponent<{
|
|
6
6
|
k: string;
|
|
7
7
|
v: string;
|
|
8
8
|
signal?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
}> {
|
|
10
|
+
render() {
|
|
11
|
+
const { k, v, signal: isSignal } = this.props;
|
|
12
|
+
return (
|
|
13
|
+
<div class="flex justify-between items-center px-3 py-2 border-b border-border gap-3">
|
|
14
|
+
<span
|
|
15
|
+
class={`text-[11px] font-mono shrink-0 ${isSignal ? "text-accent" : "text-muted"}`}
|
|
16
|
+
>
|
|
17
|
+
{k}
|
|
18
|
+
</span>
|
|
19
|
+
<span class="text-[11px] text-text font-mono truncate text-right">
|
|
20
|
+
{v}
|
|
21
|
+
</span>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
22
25
|
}
|
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
children,
|
|
6
|
-
}: {
|
|
5
|
+
@Component()
|
|
6
|
+
export class DetailSection extends StatelessComponent<{
|
|
7
7
|
label: string;
|
|
8
8
|
children: Children;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<div class="
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
18
|
+
);
|
|
19
|
+
}
|
|
18
20
|
}
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
+
import { StatelessComponent } from "@praxisjs/core";
|
|
2
|
+
import { Component } from "@praxisjs/decorators";
|
|
3
|
+
|
|
1
4
|
import type { ComponentEntry } from "@core/types";
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
@Component()
|
|
7
|
+
export class StatusDot extends StatelessComponent<{
|
|
8
|
+
status: ComponentEntry["status"];
|
|
9
|
+
}> {
|
|
10
|
+
render() {
|
|
11
|
+
const { status } = this.props;
|
|
12
|
+
return (
|
|
13
|
+
<span
|
|
14
|
+
class={`inline-block w-[6px] h-[6px] rounded-full shrink-0 ${status === "mounted" ? "bg-success" : "bg-subtle"}`}
|
|
15
|
+
style={
|
|
16
|
+
status === "mounted"
|
|
17
|
+
? { boxShadow: "0 0 6px rgba(14,165,122,0.7)" }
|
|
18
|
+
: undefined
|
|
19
|
+
}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
14
23
|
}
|
|
@@ -1,85 +1,90 @@
|
|
|
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 { ComponentDetail } from "./components/component-detail";
|
|
6
8
|
import { ComponentRow } from "./components/component-row";
|
|
7
9
|
|
|
8
10
|
import type { Registry } from "@core/registry";
|
|
9
|
-
import type { ComponentEntry
|
|
11
|
+
import type { ComponentEntry } from "@core/types";
|
|
12
|
+
|
|
13
|
+
@Component()
|
|
14
|
+
export class ComponentsTab extends StatefulComponent {
|
|
15
|
+
@State() components: ComponentEntry[] = [];
|
|
16
|
+
@State() selectedId: string | null = null;
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
const components = signal<ComponentEntry[]>(registry.getComponents());
|
|
13
|
-
const selectedId = signal<string | null>(null);
|
|
14
|
-
const sigs = signal<SignalEntry[]>([]);
|
|
18
|
+
private _handlers: Array<() => void> = [];
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
private get registry() {
|
|
21
|
+
return this.props.registry as Registry;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
onMount() {
|
|
25
|
+
this.components = this.registry.getComponents();
|
|
20
26
|
|
|
21
|
-
let handlers: Array<() => void> = [];
|
|
22
|
-
onMount(() => {
|
|
23
27
|
const refresh = () => {
|
|
24
|
-
components.
|
|
25
|
-
const id = peek(selectedId);
|
|
26
|
-
if (id) sigs.set(registry.getSignalsByComponent(id));
|
|
28
|
+
this.components = this.registry.getComponents();
|
|
27
29
|
};
|
|
28
|
-
|
|
30
|
+
|
|
31
|
+
this._handlers = [
|
|
29
32
|
"component:registered",
|
|
30
33
|
"component:render",
|
|
31
34
|
"component:unmount",
|
|
32
35
|
"lifecycle",
|
|
33
36
|
"signal:registered",
|
|
34
37
|
"signal:changed",
|
|
35
|
-
].map((ev) => registry.bus.on(ev, refresh));
|
|
36
|
-
}
|
|
38
|
+
].map((ev) => this.registry.bus.on(ev, refresh));
|
|
39
|
+
}
|
|
37
40
|
|
|
38
|
-
onUnmount(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
off();
|
|
42
|
-
});
|
|
43
|
-
});
|
|
41
|
+
onUnmount() {
|
|
42
|
+
this._handlers.forEach((off) => { off(); });
|
|
43
|
+
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
render() {
|
|
46
|
+
const { registry } = this;
|
|
47
|
+
return (
|
|
48
|
+
<div class="flex h-full overflow-hidden">
|
|
49
|
+
<div class="flex-1 flex flex-col overflow-hidden min-w-0">
|
|
50
|
+
<div class="flex 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">
|
|
51
|
+
<span class="flex-1">Component</span>
|
|
52
|
+
<span>Renders</span>
|
|
53
|
+
<span class="w-12 text-right">Last</span>
|
|
54
|
+
</div>
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
<div class="flex-1 overflow-y-auto">
|
|
57
|
+
{() =>
|
|
58
|
+
this.components.length === 0 ? (
|
|
59
|
+
<EmptyState message="No components tracked. Add @Trace() to component classes." />
|
|
60
|
+
) : (
|
|
61
|
+
this.components.map((c) => (
|
|
62
|
+
<ComponentRow
|
|
63
|
+
key={c.id}
|
|
64
|
+
entry={c}
|
|
65
|
+
selected={() => this.selectedId === c.id}
|
|
66
|
+
onClick={() => {
|
|
67
|
+
this.selectedId =
|
|
68
|
+
this.selectedId === c.id ? null : c.id;
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
))
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
</div>
|
|
71
75
|
</div>
|
|
72
|
-
</div>
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
{() => {
|
|
78
|
+
const id = this.selectedId;
|
|
79
|
+
const entry = id
|
|
80
|
+
? (this.components.find((c) => c.id === id) ?? null)
|
|
81
|
+
: null;
|
|
82
|
+
const sigs = id ? registry.getSignalsByComponent(id) : [];
|
|
83
|
+
return entry ? (
|
|
84
|
+
<ComponentDetail entry={entry} signals={sigs} />
|
|
85
|
+
) : null;
|
|
86
|
+
}}
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
85
90
|
}
|
|
@@ -1,35 +1,43 @@
|
|
|
1
|
+
|
|
1
2
|
import { PanelSection } from "@shared/panel-section";
|
|
2
3
|
import { SidePanel } from "@shared/side-panel";
|
|
3
4
|
import { time } from "@utils/format-time";
|
|
4
5
|
import { formatValue } from "@utils/format-value";
|
|
5
6
|
|
|
7
|
+
import { StatelessComponent } from "@praxisjs/core";
|
|
8
|
+
import { Component } from "@praxisjs/decorators";
|
|
9
|
+
|
|
6
10
|
import type { SignalEntry } from "@core/types";
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
12
|
+
@Component()
|
|
13
|
+
export class SignalDetail extends StatelessComponent<{ entry: SignalEntry }> {
|
|
14
|
+
render() {
|
|
15
|
+
const { entry } = this.props;
|
|
16
|
+
return (
|
|
17
|
+
<SidePanel width="260px">
|
|
18
|
+
<PanelSection label="History">
|
|
19
|
+
<div class="overflow-y-auto">
|
|
20
|
+
{[...entry.history].reverse().map((h, i) => (
|
|
21
|
+
<div
|
|
22
|
+
key={String(i)}
|
|
23
|
+
class="px-3 py-2 border-b border-border flex justify-between items-center gap-3"
|
|
24
|
+
>
|
|
25
|
+
<span class="font-mono text-[11px] text-text truncate">
|
|
26
|
+
{formatValue(h.value)}
|
|
27
|
+
</span>
|
|
28
|
+
<span class="text-[10px] text-subtle shrink-0 tabular-nums">
|
|
29
|
+
{time(h.timestamp, "ago")}
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
))}
|
|
33
|
+
{entry.history.length === 0 && (
|
|
34
|
+
<p class="px-3 py-6 text-[11px] text-subtle text-center">
|
|
35
|
+
No history yet.
|
|
36
|
+
</p>
|
|
37
|
+
)}
|
|
38
|
+
</div>
|
|
39
|
+
</PanelSection>
|
|
40
|
+
</SidePanel>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
35
43
|
}
|
|
@@ -1,37 +1,41 @@
|
|
|
1
1
|
import { time } from "@utils/format-time";
|
|
2
2
|
import { formatValue } from "@utils/format-value";
|
|
3
3
|
|
|
4
|
+
import { StatelessComponent } from "@praxisjs/core";
|
|
5
|
+
import { Component } from "@praxisjs/decorators";
|
|
6
|
+
|
|
7
|
+
|
|
4
8
|
import type { SignalEntry } from "@core/types";
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
selected,
|
|
9
|
-
onClick,
|
|
10
|
-
}: {
|
|
10
|
+
@Component()
|
|
11
|
+
export class SignalRow extends StatelessComponent<{
|
|
11
12
|
entry: SignalEntry;
|
|
12
13
|
selected: boolean;
|
|
13
14
|
onClick: () => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
15
|
+
}> {
|
|
16
|
+
render() {
|
|
17
|
+
const { entry, selected, onClick } = this.props;
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
onClick={onClick}
|
|
21
|
+
class={`relative grid grid-cols-[1.2fr_0.8fr_1fr_auto] items-center px-3 py-2 cursor-pointer border-b border-border transition-colors duration-100 ${
|
|
22
|
+
selected ? "bg-selected" : "hover:bg-section"
|
|
23
|
+
}`}
|
|
24
|
+
>
|
|
25
|
+
{selected && (
|
|
26
|
+
<span class="absolute left-0 top-0 bottom-0 w-[2px] bg-accent rounded-r" />
|
|
27
|
+
)}
|
|
28
|
+
<span class="text-accent font-mono text-[11px] truncate pl-1">
|
|
29
|
+
{entry.label}
|
|
30
|
+
</span>
|
|
31
|
+
<span class="text-muted text-[11px] truncate">{entry.componentName}</span>
|
|
32
|
+
<span class="text-text font-mono text-[11px] truncate">
|
|
33
|
+
{formatValue(entry.value)}
|
|
34
|
+
</span>
|
|
35
|
+
<span class="text-subtle text-[10px] text-right tabular-nums">
|
|
36
|
+
{time(entry.changedAt, "ago")}
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
37
41
|
}
|