@flanksource/clicky-ui 0.3.48 → 0.3.49
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/dist/data/DataTable.cjs +18 -8
- package/dist/data/DataTable.cjs.map +1 -1
- package/dist/data/DataTable.d.ts +4 -0
- package/dist/data/DataTable.d.ts.map +1 -1
- package/dist/data/DataTable.js +18 -8
- package/dist/data/DataTable.js.map +1 -1
- package/dist/data/TaskManager.cjs +183 -159
- package/dist/data/TaskManager.cjs.map +1 -1
- package/dist/data/TaskManager.d.ts +3 -1
- package/dist/data/TaskManager.d.ts.map +1 -1
- package/dist/data/TaskManager.js +185 -161
- package/dist/data/TaskManager.js.map +1 -1
- package/dist/data/TaskManagerButton.cjs +1 -0
- package/dist/data/TaskManagerButton.cjs.map +1 -1
- package/dist/data/TaskManagerButton.js +1 -0
- package/dist/data/TaskManagerButton.js.map +1 -1
- package/dist/data/TaskProgress.cjs +13 -7
- package/dist/data/TaskProgress.cjs.map +1 -1
- package/dist/data/TaskProgress.d.ts +3 -1
- package/dist/data/TaskProgress.d.ts.map +1 -1
- package/dist/data/TaskProgress.js +13 -7
- package/dist/data/TaskProgress.js.map +1 -1
- package/dist/data/traces/ProcessTraceTree.cjs +93 -0
- package/dist/data/traces/ProcessTraceTree.cjs.map +1 -0
- package/dist/data/traces/ProcessTraceTree.d.ts +24 -0
- package/dist/data/traces/ProcessTraceTree.d.ts.map +1 -0
- package/dist/data/traces/ProcessTraceTree.js +93 -0
- package/dist/data/traces/ProcessTraceTree.js.map +1 -0
- package/dist/data/traces/Sparkline.cjs +23 -0
- package/dist/data/traces/Sparkline.cjs.map +1 -0
- package/dist/data/traces/Sparkline.d.ts +13 -0
- package/dist/data/traces/Sparkline.d.ts.map +1 -0
- package/dist/data/traces/Sparkline.js +23 -0
- package/dist/data/traces/Sparkline.js.map +1 -0
- package/dist/data/traces/SqlTraceTable.cjs +104 -0
- package/dist/data/traces/SqlTraceTable.cjs.map +1 -0
- package/dist/data/traces/SqlTraceTable.d.ts +19 -0
- package/dist/data/traces/SqlTraceTable.d.ts.map +1 -0
- package/dist/data/traces/SqlTraceTable.js +104 -0
- package/dist/data/traces/SqlTraceTable.js.map +1 -0
- package/dist/data/traces/TerminalCastPlayer.cjs +162 -0
- package/dist/data/traces/TerminalCastPlayer.cjs.map +1 -0
- package/dist/data/traces/TerminalCastPlayer.js +162 -0
- package/dist/data/traces/TerminalCastPlayer.js.map +1 -0
- package/dist/data/traces/TraceHarView.cjs +15 -0
- package/dist/data/traces/TraceHarView.cjs.map +1 -0
- package/dist/data/traces/TraceHarView.js +15 -0
- package/dist/data/traces/TraceHarView.js.map +1 -0
- package/dist/data/traces/TraceView.cjs +54 -0
- package/dist/data/traces/TraceView.cjs.map +1 -0
- package/dist/data/traces/TraceView.d.ts +18 -0
- package/dist/data/traces/TraceView.d.ts.map +1 -0
- package/dist/data/traces/TraceView.js +54 -0
- package/dist/data/traces/TraceView.js.map +1 -0
- package/dist/data/traces/cast.cjs +81 -0
- package/dist/data/traces/cast.cjs.map +1 -0
- package/dist/data/traces/cast.js +81 -0
- package/dist/data/traces/cast.js.map +1 -0
- package/dist/data/traces/har-adapter.cjs +83 -0
- package/dist/data/traces/har-adapter.cjs.map +1 -0
- package/dist/data/traces/har-adapter.js +83 -0
- package/dist/data/traces/har-adapter.js.map +1 -0
- package/dist/data/traces/index.d.ts +12 -0
- package/dist/data/traces/index.d.ts.map +1 -0
- package/dist/data/traces/process-tree.cjs +53 -0
- package/dist/data/traces/process-tree.cjs.map +1 -0
- package/dist/data/traces/process-tree.d.ts +21 -0
- package/dist/data/traces/process-tree.d.ts.map +1 -0
- package/dist/data/traces/process-tree.js +53 -0
- package/dist/data/traces/process-tree.js.map +1 -0
- package/dist/data/traces/sql-trace.cjs +39 -0
- package/dist/data/traces/sql-trace.cjs.map +1 -0
- package/dist/data/traces/sql-trace.d.ts +31 -0
- package/dist/data/traces/sql-trace.d.ts.map +1 -0
- package/dist/data/traces/sql-trace.js +39 -0
- package/dist/data/traces/sql-trace.js.map +1 -0
- package/dist/data/traces/terminal-screen.cjs +270 -0
- package/dist/data/traces/terminal-screen.cjs.map +1 -0
- package/dist/data/traces/terminal-screen.js +270 -0
- package/dist/data/traces/terminal-screen.js.map +1 -0
- package/dist/data/traces/terminal-timeline.cjs +48 -0
- package/dist/data/traces/terminal-timeline.cjs.map +1 -0
- package/dist/data/traces/terminal-timeline.js +48 -0
- package/dist/data/traces/terminal-timeline.js.map +1 -0
- package/dist/data/traces/types.d.ts +1 -3
- package/dist/data/traces/types.d.ts.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.cjs.map +1 -1
- package/dist/data/version-info.js +3 -3
- package/dist/data/version-info.js.map +1 -1
- package/dist/data.cjs +22 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +1 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +22 -0
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +22 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const button = require("../../components/button.cjs");
|
|
6
|
+
const SegmentedControl = require("../../components/SegmentedControl.cjs");
|
|
7
|
+
const utils = require("../../lib/utils.cjs");
|
|
8
|
+
const AnsiHtml = require("../AnsiHtml.cjs");
|
|
9
|
+
const Badge = require("../Badge.cjs");
|
|
10
|
+
const Icon = require("../Icon.cjs");
|
|
11
|
+
const cast = require("./cast.cjs");
|
|
12
|
+
const terminalScreen = require("./terminal-screen.cjs");
|
|
13
|
+
const terminalTimeline = require("./terminal-timeline.cjs");
|
|
14
|
+
const UiPause = require("../../icons/components/UiPause.cjs");
|
|
15
|
+
const UiPlay = require("../../icons/components/UiPlay.cjs");
|
|
16
|
+
const UiSkipForward = require("../../icons/components/UiSkipForward.cjs");
|
|
17
|
+
const TICK_MS = 50;
|
|
18
|
+
const SPEEDS = ["1", "2", "4"];
|
|
19
|
+
const SPEED_OPTIONS = SPEEDS.map((speed) => ({ id: speed, label: `${speed}x` }));
|
|
20
|
+
const VIEW_OPTIONS = [
|
|
21
|
+
{ id: "replay", label: "Replay" },
|
|
22
|
+
{ id: "settled", label: "Settled screen" }
|
|
23
|
+
];
|
|
24
|
+
function resolveEvents(events, castText) {
|
|
25
|
+
if (events === void 0 === (castText === void 0)) {
|
|
26
|
+
throw new Error("TerminalCastPlayer takes either events or castText, not both or neither");
|
|
27
|
+
}
|
|
28
|
+
return events ?? cast.parseCast(castText);
|
|
29
|
+
}
|
|
30
|
+
function seconds(value) {
|
|
31
|
+
return `${value.toFixed(1)}s`;
|
|
32
|
+
}
|
|
33
|
+
function ExitBadge({ exit }) {
|
|
34
|
+
if (exit.exitCode === void 0) return /* @__PURE__ */ jsxRuntime.jsx(Badge.Badge, { variant: "outline", tone: "neutral", size: "sm", children: "exited" });
|
|
35
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Badge.Badge, { variant: "soft", tone: exit.exitCode === 0 ? "success" : "danger", size: "sm", className: "font-mono", children: `exit ${exit.exitCode}` });
|
|
36
|
+
}
|
|
37
|
+
function useReplayScreen(timeline, position) {
|
|
38
|
+
const cache = react.useRef(void 0);
|
|
39
|
+
const count = terminalTimeline.stepCountAt(timeline.steps, position);
|
|
40
|
+
return react.useMemo(() => {
|
|
41
|
+
var _a;
|
|
42
|
+
const previous = ((_a = cache.current) == null ? void 0 : _a.timeline) === timeline ? cache.current.replay : void 0;
|
|
43
|
+
const replay = terminalTimeline.replayTo(previous, timeline, count);
|
|
44
|
+
cache.current = { timeline, replay };
|
|
45
|
+
return { ansi: terminalScreen.screenToAnsi(replay.screen), cols: replay.screen.width, rows: replay.screen.height };
|
|
46
|
+
}, [timeline, count]);
|
|
47
|
+
}
|
|
48
|
+
function TerminalCastPlayer({
|
|
49
|
+
events,
|
|
50
|
+
castText,
|
|
51
|
+
autoPlay = false,
|
|
52
|
+
emptyLabel = "No terminal output recorded",
|
|
53
|
+
className
|
|
54
|
+
}) {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
const timeline = react.useMemo(() => terminalTimeline.buildTimeline(resolveEvents(events, castText)), [events, castText]);
|
|
57
|
+
const [position, setPosition] = react.useState(0);
|
|
58
|
+
const [playing, setPlaying] = react.useState(autoPlay);
|
|
59
|
+
const [speed, setSpeed] = react.useState("1");
|
|
60
|
+
const [view, setView] = react.useState("replay");
|
|
61
|
+
const replay = useReplayScreen(timeline, position);
|
|
62
|
+
react.useEffect(() => {
|
|
63
|
+
if (!playing) return;
|
|
64
|
+
let last = Date.now();
|
|
65
|
+
const timer = setInterval(() => {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const elapsed = (now - last) / 1e3;
|
|
68
|
+
last = now;
|
|
69
|
+
setPosition((current) => Math.min(timeline.duration, current + elapsed * Number(speed)));
|
|
70
|
+
}, TICK_MS);
|
|
71
|
+
return () => clearInterval(timer);
|
|
72
|
+
}, [playing, speed, timeline.duration]);
|
|
73
|
+
react.useEffect(() => {
|
|
74
|
+
if (playing && position >= timeline.duration) setPlaying(false);
|
|
75
|
+
}, [playing, position, timeline.duration]);
|
|
76
|
+
if (timeline.steps.length === 0 && timeline.settled === void 0) {
|
|
77
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-density-6 text-center text-muted-foreground text-sm", children: emptyLabel });
|
|
78
|
+
}
|
|
79
|
+
const hasSettled = timeline.settled !== void 0;
|
|
80
|
+
const showSettled = view === "settled" && hasSettled;
|
|
81
|
+
const play = () => {
|
|
82
|
+
if (position >= timeline.duration) setPosition(0);
|
|
83
|
+
setPlaying(true);
|
|
84
|
+
};
|
|
85
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex min-h-0 flex-col gap-density-2", className), children: [
|
|
86
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-density-2 text-xs text-muted-foreground", children: [
|
|
87
|
+
((_a = timeline.exit) == null ? void 0 : _a.command) ? /* @__PURE__ */ jsxRuntime.jsx("code", { className: "font-mono text-foreground", children: timeline.exit.command }) : null,
|
|
88
|
+
timeline.exit ? /* @__PURE__ */ jsxRuntime.jsx(ExitBadge, { exit: timeline.exit }) : null,
|
|
89
|
+
((_b = timeline.exit) == null ? void 0 : _b.truncated) ? /* @__PURE__ */ jsxRuntime.jsx("span", { title: "Size caps or a full buffer stopped some output events being recorded", children: /* @__PURE__ */ jsxRuntime.jsx(Badge.Badge, { variant: "soft", tone: "warning", size: "sm", children: "output truncated" }) }) : null,
|
|
90
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: `${timeline.width}x${timeline.height}` }),
|
|
91
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1" }),
|
|
92
|
+
hasSettled && timeline.steps.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(SegmentedControl.SegmentedControl, { size: "sm", "aria-label": "Screen view", value: view, options: VIEW_OPTIONS, onChange: setView }) : null
|
|
93
|
+
] }),
|
|
94
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
95
|
+
"div",
|
|
96
|
+
{
|
|
97
|
+
role: "region",
|
|
98
|
+
"aria-label": "Terminal screen",
|
|
99
|
+
"data-cols": showSettled ? void 0 : replay.cols,
|
|
100
|
+
className: "overflow-auto rounded border bg-black p-density-2",
|
|
101
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
102
|
+
AnsiHtml.AnsiHtml,
|
|
103
|
+
{
|
|
104
|
+
text: showSettled ? timeline.settled : replay.ansi,
|
|
105
|
+
className: "font-mono text-xs leading-5 whitespace-pre text-gray-100"
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
),
|
|
110
|
+
timeline.steps.length > 0 && !showSettled ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-density-2", children: [
|
|
111
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
112
|
+
button.Button,
|
|
113
|
+
{
|
|
114
|
+
variant: "ghost",
|
|
115
|
+
size: "icon",
|
|
116
|
+
"aria-label": playing ? "Pause" : "Play",
|
|
117
|
+
onClick: playing ? () => setPlaying(false) : play,
|
|
118
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: playing ? UiPause.UiPause : UiPlay.UiPlay })
|
|
119
|
+
}
|
|
120
|
+
),
|
|
121
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
122
|
+
"input",
|
|
123
|
+
{
|
|
124
|
+
type: "range",
|
|
125
|
+
"aria-label": "Seek",
|
|
126
|
+
className: "min-w-0 flex-1 accent-primary",
|
|
127
|
+
min: 0,
|
|
128
|
+
max: timeline.duration,
|
|
129
|
+
step: "any",
|
|
130
|
+
value: position,
|
|
131
|
+
onChange: (event) => setPosition(Number(event.target.value))
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: `${seconds(position)} / ${seconds(timeline.duration)}` }),
|
|
135
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
136
|
+
SegmentedControl.SegmentedControl,
|
|
137
|
+
{
|
|
138
|
+
size: "sm",
|
|
139
|
+
"aria-label": "Playback speed",
|
|
140
|
+
value: speed,
|
|
141
|
+
options: SPEED_OPTIONS,
|
|
142
|
+
onChange: setSpeed
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
146
|
+
button.Button,
|
|
147
|
+
{
|
|
148
|
+
variant: "ghost",
|
|
149
|
+
size: "icon",
|
|
150
|
+
"aria-label": "Jump to end",
|
|
151
|
+
onClick: () => {
|
|
152
|
+
setPlaying(false);
|
|
153
|
+
setPosition(timeline.duration);
|
|
154
|
+
},
|
|
155
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiSkipForward.UiSkipForward })
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
] }) : null
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
exports.TerminalCastPlayer = TerminalCastPlayer;
|
|
162
|
+
//# sourceMappingURL=TerminalCastPlayer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TerminalCastPlayer.cjs","sources":["../../../src/data/traces/TerminalCastPlayer.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { Button } from \"../../components/button\";\nimport { SegmentedControl } from \"../../components/SegmentedControl\";\nimport { UiPause, UiPlay, UiSkipForward } from \"../../icons\";\nimport { cn } from \"../../lib/utils\";\nimport { AnsiHtml } from \"../AnsiHtml\";\nimport { Badge } from \"../Badge\";\nimport { Icon } from \"../Icon\";\nimport { parseCast } from \"./cast\";\nimport { screenToAnsi } from \"./terminal-screen\";\nimport {\n buildTimeline,\n replayTo,\n stepCountAt,\n type ScreenReplay,\n type TerminalTimeline,\n} from \"./terminal-timeline\";\nimport type { TerminalEvent } from \"./types\";\n\nconst TICK_MS = 50;\nconst SPEEDS = [\"1\", \"2\", \"4\"] as const;\ntype Speed = (typeof SPEEDS)[number];\ntype View = \"replay\" | \"settled\";\n\nconst SPEED_OPTIONS = SPEEDS.map((speed) => ({ id: speed, label: `${speed}x` }));\nconst VIEW_OPTIONS: { id: View; label: string }[] = [\n { id: \"replay\", label: \"Replay\" },\n { id: \"settled\", label: \"Settled screen\" },\n];\n\nexport type TerminalCastPlayerProps = {\n /** `terminal` trace records. Give this or `castText`. */\n events?: TerminalEvent[];\n /** An asciinema v2 cast, such as a downloaded `.cast`. Give this or `events`. */\n castText?: string;\n /** Start playing on mount. */\n autoPlay?: boolean;\n /** Shown when the session recorded no events. */\n emptyLabel?: string;\n /** Classes applied to the player root. */\n className?: string;\n};\n\nfunction resolveEvents(events: TerminalEvent[] | undefined, castText: string | undefined): TerminalEvent[] {\n if ((events === undefined) === (castText === undefined)) {\n throw new Error(\"TerminalCastPlayer takes either events or castText, not both or neither\");\n }\n return events ?? parseCast(castText!);\n}\n\nfunction seconds(value: number): string {\n return `${value.toFixed(1)}s`;\n}\n\nfunction ExitBadge({ exit }: { exit: TerminalEvent }) {\n if (exit.exitCode === undefined) return <Badge variant=\"outline\" tone=\"neutral\" size=\"sm\">exited</Badge>;\n return (\n <Badge variant=\"soft\" tone={exit.exitCode === 0 ? \"success\" : \"danger\"} size=\"sm\" className=\"font-mono\">\n {`exit ${exit.exitCode}`}\n </Badge>\n );\n}\n\n/** Feeds the timeline to a screen as the position moves, reusing the screen while moving forward. */\nfunction useReplayScreen(timeline: TerminalTimeline, position: number) {\n const cache = useRef<{ timeline: TerminalTimeline; replay: ScreenReplay } | undefined>(undefined);\n const count = stepCountAt(timeline.steps, position);\n return useMemo(() => {\n const previous = cache.current?.timeline === timeline ? cache.current.replay : undefined;\n const replay = replayTo(previous, timeline, count);\n cache.current = { timeline, replay };\n return { ansi: screenToAnsi(replay.screen), cols: replay.screen.width, rows: replay.screen.height };\n }, [timeline, count]);\n}\n\n/**\n * Replays a `terminal` trace: output events over time with play/pause, a\n * scrubber, 1x/2x/4x speed and jump-to-end, honouring resizes. A downloaded\n * asciinema cast renders the same way through `castText`. When the capture\n * settled a screen, it is offered as the \"Settled screen\" view.\n */\nexport function TerminalCastPlayer({\n events,\n castText,\n autoPlay = false,\n emptyLabel = \"No terminal output recorded\",\n className,\n}: TerminalCastPlayerProps) {\n const timeline = useMemo(() => buildTimeline(resolveEvents(events, castText)), [events, castText]);\n const [position, setPosition] = useState(0);\n const [playing, setPlaying] = useState(autoPlay);\n const [speed, setSpeed] = useState<Speed>(\"1\");\n const [view, setView] = useState<View>(\"replay\");\n const replay = useReplayScreen(timeline, position);\n\n useEffect(() => {\n if (!playing) return;\n let last = Date.now();\n const timer = setInterval(() => {\n const now = Date.now();\n const elapsed = (now - last) / 1000;\n last = now;\n setPosition((current) => Math.min(timeline.duration, current + elapsed * Number(speed)));\n }, TICK_MS);\n return () => clearInterval(timer);\n }, [playing, speed, timeline.duration]);\n\n useEffect(() => {\n if (playing && position >= timeline.duration) setPlaying(false);\n }, [playing, position, timeline.duration]);\n\n if (timeline.steps.length === 0 && timeline.settled === undefined) {\n return <div className=\"p-density-6 text-center text-muted-foreground text-sm\">{emptyLabel}</div>;\n }\n\n const hasSettled = timeline.settled !== undefined;\n const showSettled = view === \"settled\" && hasSettled;\n const play = () => {\n if (position >= timeline.duration) setPosition(0);\n setPlaying(true);\n };\n\n return (\n <div className={cn(\"flex min-h-0 flex-col gap-density-2\", className)}>\n <div className=\"flex flex-wrap items-center gap-density-2 text-xs text-muted-foreground\">\n {timeline.exit?.command ? <code className=\"font-mono text-foreground\">{timeline.exit.command}</code> : null}\n {timeline.exit ? <ExitBadge exit={timeline.exit} /> : null}\n {timeline.exit?.truncated ? (\n <span title=\"Size caps or a full buffer stopped some output events being recorded\">\n <Badge variant=\"soft\" tone=\"warning\" size=\"sm\">output truncated</Badge>\n </span>\n ) : null}\n <span className=\"tabular-nums\">{`${timeline.width}x${timeline.height}`}</span>\n <span className=\"flex-1\" />\n {hasSettled && timeline.steps.length > 0 ? (\n <SegmentedControl size=\"sm\" aria-label=\"Screen view\" value={view} options={VIEW_OPTIONS} onChange={setView} />\n ) : null}\n </div>\n\n <div\n role=\"region\"\n aria-label=\"Terminal screen\"\n data-cols={showSettled ? undefined : replay.cols}\n className=\"overflow-auto rounded border bg-black p-density-2\"\n >\n <AnsiHtml\n text={showSettled ? timeline.settled! : replay.ansi}\n className=\"font-mono text-xs leading-5 whitespace-pre text-gray-100\"\n />\n </div>\n\n {timeline.steps.length > 0 && !showSettled ? (\n <div className=\"flex items-center gap-density-2\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={playing ? \"Pause\" : \"Play\"}\n onClick={playing ? () => setPlaying(false) : play}\n >\n <Icon icon={playing ? UiPause : UiPlay} />\n </Button>\n <input\n type=\"range\"\n aria-label=\"Seek\"\n className=\"min-w-0 flex-1 accent-primary\"\n min={0}\n max={timeline.duration}\n step=\"any\"\n value={position}\n onChange={(event) => setPosition(Number(event.target.value))}\n />\n <span className=\"shrink-0 text-xs tabular-nums text-muted-foreground\">\n {`${seconds(position)} / ${seconds(timeline.duration)}`}\n </span>\n <SegmentedControl\n size=\"sm\"\n aria-label=\"Playback speed\"\n value={speed}\n options={SPEED_OPTIONS}\n onChange={setSpeed}\n />\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Jump to end\"\n onClick={() => {\n setPlaying(false);\n setPosition(timeline.duration);\n }}\n >\n <Icon icon={UiSkipForward} />\n </Button>\n </div>\n ) : null}\n </div>\n );\n}\n"],"names":["parseCast","jsx","Badge","useRef","stepCountAt","useMemo","replayTo","screenToAnsi","buildTimeline","useState","useEffect","cn","jsxs","SegmentedControl","AnsiHtml","Button","Icon","UiPause","UiPlay","UiSkipForward"],"mappings":"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"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState, useEffect, useRef } from "react";
|
|
3
|
+
import { Button } from "../../components/button.js";
|
|
4
|
+
import { SegmentedControl } from "../../components/SegmentedControl.js";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { AnsiHtml } from "../AnsiHtml.js";
|
|
7
|
+
import { Badge } from "../Badge.js";
|
|
8
|
+
import { Icon } from "../Icon.js";
|
|
9
|
+
import { parseCast } from "./cast.js";
|
|
10
|
+
import { screenToAnsi } from "./terminal-screen.js";
|
|
11
|
+
import { buildTimeline, stepCountAt, replayTo } from "./terminal-timeline.js";
|
|
12
|
+
import { UiPause } from "../../icons/components/UiPause.js";
|
|
13
|
+
import { UiPlay } from "../../icons/components/UiPlay.js";
|
|
14
|
+
import { UiSkipForward } from "../../icons/components/UiSkipForward.js";
|
|
15
|
+
const TICK_MS = 50;
|
|
16
|
+
const SPEEDS = ["1", "2", "4"];
|
|
17
|
+
const SPEED_OPTIONS = SPEEDS.map((speed) => ({ id: speed, label: `${speed}x` }));
|
|
18
|
+
const VIEW_OPTIONS = [
|
|
19
|
+
{ id: "replay", label: "Replay" },
|
|
20
|
+
{ id: "settled", label: "Settled screen" }
|
|
21
|
+
];
|
|
22
|
+
function resolveEvents(events, castText) {
|
|
23
|
+
if (events === void 0 === (castText === void 0)) {
|
|
24
|
+
throw new Error("TerminalCastPlayer takes either events or castText, not both or neither");
|
|
25
|
+
}
|
|
26
|
+
return events ?? parseCast(castText);
|
|
27
|
+
}
|
|
28
|
+
function seconds(value) {
|
|
29
|
+
return `${value.toFixed(1)}s`;
|
|
30
|
+
}
|
|
31
|
+
function ExitBadge({ exit }) {
|
|
32
|
+
if (exit.exitCode === void 0) return /* @__PURE__ */ jsx(Badge, { variant: "outline", tone: "neutral", size: "sm", children: "exited" });
|
|
33
|
+
return /* @__PURE__ */ jsx(Badge, { variant: "soft", tone: exit.exitCode === 0 ? "success" : "danger", size: "sm", className: "font-mono", children: `exit ${exit.exitCode}` });
|
|
34
|
+
}
|
|
35
|
+
function useReplayScreen(timeline, position) {
|
|
36
|
+
const cache = useRef(void 0);
|
|
37
|
+
const count = stepCountAt(timeline.steps, position);
|
|
38
|
+
return useMemo(() => {
|
|
39
|
+
var _a;
|
|
40
|
+
const previous = ((_a = cache.current) == null ? void 0 : _a.timeline) === timeline ? cache.current.replay : void 0;
|
|
41
|
+
const replay = replayTo(previous, timeline, count);
|
|
42
|
+
cache.current = { timeline, replay };
|
|
43
|
+
return { ansi: screenToAnsi(replay.screen), cols: replay.screen.width, rows: replay.screen.height };
|
|
44
|
+
}, [timeline, count]);
|
|
45
|
+
}
|
|
46
|
+
function TerminalCastPlayer({
|
|
47
|
+
events,
|
|
48
|
+
castText,
|
|
49
|
+
autoPlay = false,
|
|
50
|
+
emptyLabel = "No terminal output recorded",
|
|
51
|
+
className
|
|
52
|
+
}) {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
const timeline = useMemo(() => buildTimeline(resolveEvents(events, castText)), [events, castText]);
|
|
55
|
+
const [position, setPosition] = useState(0);
|
|
56
|
+
const [playing, setPlaying] = useState(autoPlay);
|
|
57
|
+
const [speed, setSpeed] = useState("1");
|
|
58
|
+
const [view, setView] = useState("replay");
|
|
59
|
+
const replay = useReplayScreen(timeline, position);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!playing) return;
|
|
62
|
+
let last = Date.now();
|
|
63
|
+
const timer = setInterval(() => {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
const elapsed = (now - last) / 1e3;
|
|
66
|
+
last = now;
|
|
67
|
+
setPosition((current) => Math.min(timeline.duration, current + elapsed * Number(speed)));
|
|
68
|
+
}, TICK_MS);
|
|
69
|
+
return () => clearInterval(timer);
|
|
70
|
+
}, [playing, speed, timeline.duration]);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (playing && position >= timeline.duration) setPlaying(false);
|
|
73
|
+
}, [playing, position, timeline.duration]);
|
|
74
|
+
if (timeline.steps.length === 0 && timeline.settled === void 0) {
|
|
75
|
+
return /* @__PURE__ */ jsx("div", { className: "p-density-6 text-center text-muted-foreground text-sm", children: emptyLabel });
|
|
76
|
+
}
|
|
77
|
+
const hasSettled = timeline.settled !== void 0;
|
|
78
|
+
const showSettled = view === "settled" && hasSettled;
|
|
79
|
+
const play = () => {
|
|
80
|
+
if (position >= timeline.duration) setPosition(0);
|
|
81
|
+
setPlaying(true);
|
|
82
|
+
};
|
|
83
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex min-h-0 flex-col gap-density-2", className), children: [
|
|
84
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-density-2 text-xs text-muted-foreground", children: [
|
|
85
|
+
((_a = timeline.exit) == null ? void 0 : _a.command) ? /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: timeline.exit.command }) : null,
|
|
86
|
+
timeline.exit ? /* @__PURE__ */ jsx(ExitBadge, { exit: timeline.exit }) : null,
|
|
87
|
+
((_b = timeline.exit) == null ? void 0 : _b.truncated) ? /* @__PURE__ */ jsx("span", { title: "Size caps or a full buffer stopped some output events being recorded", children: /* @__PURE__ */ jsx(Badge, { variant: "soft", tone: "warning", size: "sm", children: "output truncated" }) }) : null,
|
|
88
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: `${timeline.width}x${timeline.height}` }),
|
|
89
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
90
|
+
hasSettled && timeline.steps.length > 0 ? /* @__PURE__ */ jsx(SegmentedControl, { size: "sm", "aria-label": "Screen view", value: view, options: VIEW_OPTIONS, onChange: setView }) : null
|
|
91
|
+
] }),
|
|
92
|
+
/* @__PURE__ */ jsx(
|
|
93
|
+
"div",
|
|
94
|
+
{
|
|
95
|
+
role: "region",
|
|
96
|
+
"aria-label": "Terminal screen",
|
|
97
|
+
"data-cols": showSettled ? void 0 : replay.cols,
|
|
98
|
+
className: "overflow-auto rounded border bg-black p-density-2",
|
|
99
|
+
children: /* @__PURE__ */ jsx(
|
|
100
|
+
AnsiHtml,
|
|
101
|
+
{
|
|
102
|
+
text: showSettled ? timeline.settled : replay.ansi,
|
|
103
|
+
className: "font-mono text-xs leading-5 whitespace-pre text-gray-100"
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
),
|
|
108
|
+
timeline.steps.length > 0 && !showSettled ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-density-2", children: [
|
|
109
|
+
/* @__PURE__ */ jsx(
|
|
110
|
+
Button,
|
|
111
|
+
{
|
|
112
|
+
variant: "ghost",
|
|
113
|
+
size: "icon",
|
|
114
|
+
"aria-label": playing ? "Pause" : "Play",
|
|
115
|
+
onClick: playing ? () => setPlaying(false) : play,
|
|
116
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: playing ? UiPause : UiPlay })
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
"input",
|
|
121
|
+
{
|
|
122
|
+
type: "range",
|
|
123
|
+
"aria-label": "Seek",
|
|
124
|
+
className: "min-w-0 flex-1 accent-primary",
|
|
125
|
+
min: 0,
|
|
126
|
+
max: timeline.duration,
|
|
127
|
+
step: "any",
|
|
128
|
+
value: position,
|
|
129
|
+
onChange: (event) => setPosition(Number(event.target.value))
|
|
130
|
+
}
|
|
131
|
+
),
|
|
132
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: `${seconds(position)} / ${seconds(timeline.duration)}` }),
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
SegmentedControl,
|
|
135
|
+
{
|
|
136
|
+
size: "sm",
|
|
137
|
+
"aria-label": "Playback speed",
|
|
138
|
+
value: speed,
|
|
139
|
+
options: SPEED_OPTIONS,
|
|
140
|
+
onChange: setSpeed
|
|
141
|
+
}
|
|
142
|
+
),
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
Button,
|
|
145
|
+
{
|
|
146
|
+
variant: "ghost",
|
|
147
|
+
size: "icon",
|
|
148
|
+
"aria-label": "Jump to end",
|
|
149
|
+
onClick: () => {
|
|
150
|
+
setPlaying(false);
|
|
151
|
+
setPosition(timeline.duration);
|
|
152
|
+
},
|
|
153
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: UiSkipForward })
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
] }) : null
|
|
157
|
+
] });
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
TerminalCastPlayer
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=TerminalCastPlayer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TerminalCastPlayer.js","sources":["../../../src/data/traces/TerminalCastPlayer.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { Button } from \"../../components/button\";\nimport { SegmentedControl } from \"../../components/SegmentedControl\";\nimport { UiPause, UiPlay, UiSkipForward } from \"../../icons\";\nimport { cn } from \"../../lib/utils\";\nimport { AnsiHtml } from \"../AnsiHtml\";\nimport { Badge } from \"../Badge\";\nimport { Icon } from \"../Icon\";\nimport { parseCast } from \"./cast\";\nimport { screenToAnsi } from \"./terminal-screen\";\nimport {\n buildTimeline,\n replayTo,\n stepCountAt,\n type ScreenReplay,\n type TerminalTimeline,\n} from \"./terminal-timeline\";\nimport type { TerminalEvent } from \"./types\";\n\nconst TICK_MS = 50;\nconst SPEEDS = [\"1\", \"2\", \"4\"] as const;\ntype Speed = (typeof SPEEDS)[number];\ntype View = \"replay\" | \"settled\";\n\nconst SPEED_OPTIONS = SPEEDS.map((speed) => ({ id: speed, label: `${speed}x` }));\nconst VIEW_OPTIONS: { id: View; label: string }[] = [\n { id: \"replay\", label: \"Replay\" },\n { id: \"settled\", label: \"Settled screen\" },\n];\n\nexport type TerminalCastPlayerProps = {\n /** `terminal` trace records. Give this or `castText`. */\n events?: TerminalEvent[];\n /** An asciinema v2 cast, such as a downloaded `.cast`. Give this or `events`. */\n castText?: string;\n /** Start playing on mount. */\n autoPlay?: boolean;\n /** Shown when the session recorded no events. */\n emptyLabel?: string;\n /** Classes applied to the player root. */\n className?: string;\n};\n\nfunction resolveEvents(events: TerminalEvent[] | undefined, castText: string | undefined): TerminalEvent[] {\n if ((events === undefined) === (castText === undefined)) {\n throw new Error(\"TerminalCastPlayer takes either events or castText, not both or neither\");\n }\n return events ?? parseCast(castText!);\n}\n\nfunction seconds(value: number): string {\n return `${value.toFixed(1)}s`;\n}\n\nfunction ExitBadge({ exit }: { exit: TerminalEvent }) {\n if (exit.exitCode === undefined) return <Badge variant=\"outline\" tone=\"neutral\" size=\"sm\">exited</Badge>;\n return (\n <Badge variant=\"soft\" tone={exit.exitCode === 0 ? \"success\" : \"danger\"} size=\"sm\" className=\"font-mono\">\n {`exit ${exit.exitCode}`}\n </Badge>\n );\n}\n\n/** Feeds the timeline to a screen as the position moves, reusing the screen while moving forward. */\nfunction useReplayScreen(timeline: TerminalTimeline, position: number) {\n const cache = useRef<{ timeline: TerminalTimeline; replay: ScreenReplay } | undefined>(undefined);\n const count = stepCountAt(timeline.steps, position);\n return useMemo(() => {\n const previous = cache.current?.timeline === timeline ? cache.current.replay : undefined;\n const replay = replayTo(previous, timeline, count);\n cache.current = { timeline, replay };\n return { ansi: screenToAnsi(replay.screen), cols: replay.screen.width, rows: replay.screen.height };\n }, [timeline, count]);\n}\n\n/**\n * Replays a `terminal` trace: output events over time with play/pause, a\n * scrubber, 1x/2x/4x speed and jump-to-end, honouring resizes. A downloaded\n * asciinema cast renders the same way through `castText`. When the capture\n * settled a screen, it is offered as the \"Settled screen\" view.\n */\nexport function TerminalCastPlayer({\n events,\n castText,\n autoPlay = false,\n emptyLabel = \"No terminal output recorded\",\n className,\n}: TerminalCastPlayerProps) {\n const timeline = useMemo(() => buildTimeline(resolveEvents(events, castText)), [events, castText]);\n const [position, setPosition] = useState(0);\n const [playing, setPlaying] = useState(autoPlay);\n const [speed, setSpeed] = useState<Speed>(\"1\");\n const [view, setView] = useState<View>(\"replay\");\n const replay = useReplayScreen(timeline, position);\n\n useEffect(() => {\n if (!playing) return;\n let last = Date.now();\n const timer = setInterval(() => {\n const now = Date.now();\n const elapsed = (now - last) / 1000;\n last = now;\n setPosition((current) => Math.min(timeline.duration, current + elapsed * Number(speed)));\n }, TICK_MS);\n return () => clearInterval(timer);\n }, [playing, speed, timeline.duration]);\n\n useEffect(() => {\n if (playing && position >= timeline.duration) setPlaying(false);\n }, [playing, position, timeline.duration]);\n\n if (timeline.steps.length === 0 && timeline.settled === undefined) {\n return <div className=\"p-density-6 text-center text-muted-foreground text-sm\">{emptyLabel}</div>;\n }\n\n const hasSettled = timeline.settled !== undefined;\n const showSettled = view === \"settled\" && hasSettled;\n const play = () => {\n if (position >= timeline.duration) setPosition(0);\n setPlaying(true);\n };\n\n return (\n <div className={cn(\"flex min-h-0 flex-col gap-density-2\", className)}>\n <div className=\"flex flex-wrap items-center gap-density-2 text-xs text-muted-foreground\">\n {timeline.exit?.command ? <code className=\"font-mono text-foreground\">{timeline.exit.command}</code> : null}\n {timeline.exit ? <ExitBadge exit={timeline.exit} /> : null}\n {timeline.exit?.truncated ? (\n <span title=\"Size caps or a full buffer stopped some output events being recorded\">\n <Badge variant=\"soft\" tone=\"warning\" size=\"sm\">output truncated</Badge>\n </span>\n ) : null}\n <span className=\"tabular-nums\">{`${timeline.width}x${timeline.height}`}</span>\n <span className=\"flex-1\" />\n {hasSettled && timeline.steps.length > 0 ? (\n <SegmentedControl size=\"sm\" aria-label=\"Screen view\" value={view} options={VIEW_OPTIONS} onChange={setView} />\n ) : null}\n </div>\n\n <div\n role=\"region\"\n aria-label=\"Terminal screen\"\n data-cols={showSettled ? undefined : replay.cols}\n className=\"overflow-auto rounded border bg-black p-density-2\"\n >\n <AnsiHtml\n text={showSettled ? timeline.settled! : replay.ansi}\n className=\"font-mono text-xs leading-5 whitespace-pre text-gray-100\"\n />\n </div>\n\n {timeline.steps.length > 0 && !showSettled ? (\n <div className=\"flex items-center gap-density-2\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={playing ? \"Pause\" : \"Play\"}\n onClick={playing ? () => setPlaying(false) : play}\n >\n <Icon icon={playing ? UiPause : UiPlay} />\n </Button>\n <input\n type=\"range\"\n aria-label=\"Seek\"\n className=\"min-w-0 flex-1 accent-primary\"\n min={0}\n max={timeline.duration}\n step=\"any\"\n value={position}\n onChange={(event) => setPosition(Number(event.target.value))}\n />\n <span className=\"shrink-0 text-xs tabular-nums text-muted-foreground\">\n {`${seconds(position)} / ${seconds(timeline.duration)}`}\n </span>\n <SegmentedControl\n size=\"sm\"\n aria-label=\"Playback speed\"\n value={speed}\n options={SPEED_OPTIONS}\n onChange={setSpeed}\n />\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Jump to end\"\n onClick={() => {\n setPlaying(false);\n setPosition(timeline.duration);\n }}\n >\n <Icon icon={UiSkipForward} />\n </Button>\n </div>\n ) : null}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const HarPanel = require("../har/HarPanel.cjs");
|
|
6
|
+
const harAdapter = require("./har-adapter.cjs");
|
|
7
|
+
function TraceHarView({ rows, emptyLabel = "No HTTP exchanges recorded", ...panel }) {
|
|
8
|
+
const entries = react.useMemo(
|
|
9
|
+
() => rows.map((row, index) => harAdapter.exchangeToHarEntry(row, { id: `${row.startedAt}#${index}` })),
|
|
10
|
+
[rows]
|
|
11
|
+
);
|
|
12
|
+
return /* @__PURE__ */ jsxRuntime.jsx(HarPanel.HarPanel, { entries, emptyLabel, ...panel });
|
|
13
|
+
}
|
|
14
|
+
exports.TraceHarView = TraceHarView;
|
|
15
|
+
//# sourceMappingURL=TraceHarView.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TraceHarView.cjs","sources":["../../../src/data/traces/TraceHarView.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { HarPanel, type HarPanelProps } from \"../har/HarPanel\";\nimport { exchangeToHarEntry } from \"./har-adapter\";\nimport type { HttpExchangeRow } from \"./types\";\n\nexport type TraceHarViewProps = Omit<HarPanelProps, \"entries\"> & {\n /** `http` / `http_proxy` trace records. */\n rows: HttpExchangeRow[];\n};\n\n/**\n * `http` / `http_proxy` trace records rendered by the shared HarPanel. Bodies\n * the store parsed into structure are shown as JSON; a tunnelled CONNECT shows\n * its bytes in and out where a body would be.\n */\nexport function TraceHarView({ rows, emptyLabel = \"No HTTP exchanges recorded\", ...panel }: TraceHarViewProps) {\n const entries = useMemo(\n () => rows.map((row, index) => exchangeToHarEntry(row, { id: `${row.startedAt}#${index}` })),\n [rows],\n );\n return <HarPanel entries={entries} emptyLabel={emptyLabel} {...panel} />;\n}\n"],"names":["useMemo","exchangeToHarEntry","jsx","HarPanel"],"mappings":";;;;;;AAeO,SAAS,aAAa,EAAE,MAAM,aAAa,8BAA8B,GAAG,SAA4B;AAC7G,QAAM,UAAUA,MAAAA;AAAAA,IACd,MAAM,KAAK,IAAI,CAAC,KAAK,UAAUC,WAAAA,mBAAmB,KAAK,EAAE,IAAI,GAAG,IAAI,SAAS,IAAI,KAAK,GAAA,CAAI,CAAC;AAAA,IAC3F,CAAC,IAAI;AAAA,EAAA;AAEP,SAAOC,2BAAAA,IAACC,SAAAA,UAAA,EAAS,SAAkB,YAAyB,GAAG,OAAO;AACxE;;"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { HarPanel } from "../har/HarPanel.js";
|
|
4
|
+
import { exchangeToHarEntry } from "./har-adapter.js";
|
|
5
|
+
function TraceHarView({ rows, emptyLabel = "No HTTP exchanges recorded", ...panel }) {
|
|
6
|
+
const entries = useMemo(
|
|
7
|
+
() => rows.map((row, index) => exchangeToHarEntry(row, { id: `${row.startedAt}#${index}` })),
|
|
8
|
+
[rows]
|
|
9
|
+
);
|
|
10
|
+
return /* @__PURE__ */ jsx(HarPanel, { entries, emptyLabel, ...panel });
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
TraceHarView
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=TraceHarView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TraceHarView.js","sources":["../../../src/data/traces/TraceHarView.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { HarPanel, type HarPanelProps } from \"../har/HarPanel\";\nimport { exchangeToHarEntry } from \"./har-adapter\";\nimport type { HttpExchangeRow } from \"./types\";\n\nexport type TraceHarViewProps = Omit<HarPanelProps, \"entries\"> & {\n /** `http` / `http_proxy` trace records. */\n rows: HttpExchangeRow[];\n};\n\n/**\n * `http` / `http_proxy` trace records rendered by the shared HarPanel. Bodies\n * the store parsed into structure are shown as JSON; a tunnelled CONNECT shows\n * its bytes in and out where a body would be.\n */\nexport function TraceHarView({ rows, emptyLabel = \"No HTTP exchanges recorded\", ...panel }: TraceHarViewProps) {\n const entries = useMemo(\n () => rows.map((row, index) => exchangeToHarEntry(row, { id: `${row.startedAt}#${index}` })),\n [rows],\n );\n return <HarPanel entries={entries} emptyLabel={emptyLabel} {...panel} />;\n}\n"],"names":[],"mappings":";;;;AAeO,SAAS,aAAa,EAAE,MAAM,aAAa,8BAA8B,GAAG,SAA4B;AAC7G,QAAM,UAAU;AAAA,IACd,MAAM,KAAK,IAAI,CAAC,KAAK,UAAU,mBAAmB,KAAK,EAAE,IAAI,GAAG,IAAI,SAAS,IAAI,KAAK,GAAA,CAAI,CAAC;AAAA,IAC3F,CAAC,IAAI;AAAA,EAAA;AAEP,SAAO,oBAAC,UAAA,EAAS,SAAkB,YAAyB,GAAG,OAAO;AACxE;"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const utils = require("../../lib/utils.cjs");
|
|
6
|
+
const dataTableUtils = require("../data-table-utils.cjs");
|
|
7
|
+
const DataTable = require("../DataTable.cjs");
|
|
8
|
+
const ProcessTraceTree = require("./ProcessTraceTree.cjs");
|
|
9
|
+
const SqlTraceTable = require("./SqlTraceTable.cjs");
|
|
10
|
+
const TerminalCastPlayer = require("./TerminalCastPlayer.cjs");
|
|
11
|
+
const TraceHarView = require("./TraceHarView.cjs");
|
|
12
|
+
function requireField(kind, rows, field) {
|
|
13
|
+
rows.forEach((row, index) => {
|
|
14
|
+
if (row[field] === void 0 || row[field] === null) {
|
|
15
|
+
throw new Error(`trace kind "${kind}" row ${index} has no "${field}"`);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function GenericTraceTable({ kind, rows }) {
|
|
20
|
+
const columns = react.useMemo(() => dataTableUtils.inferColumns(rows, { literalKeys: true }), [rows]);
|
|
21
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DataTable.DataTable, { data: rows, columns, autoFilter: true, emptyMessage: `No ${kind} records` });
|
|
22
|
+
}
|
|
23
|
+
function ProcessTraceView({ rows, samples }) {
|
|
24
|
+
const [selectedKey, setSelectedKey] = react.useState(null);
|
|
25
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ProcessTraceTree.ProcessTraceTree, { nodes: rows, selectedKey, onSelect: setSelectedKey, ...samples ? { samples } : {} });
|
|
26
|
+
}
|
|
27
|
+
function TraceView({ kind, rows, samples, className }) {
|
|
28
|
+
const body = (() => {
|
|
29
|
+
switch (kind) {
|
|
30
|
+
case "http":
|
|
31
|
+
case "http_proxy":
|
|
32
|
+
requireField(kind, rows, "request");
|
|
33
|
+
requireField(kind, rows, "response");
|
|
34
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TraceHarView.TraceHarView, { rows });
|
|
35
|
+
case "pg_proxy":
|
|
36
|
+
case "sql_statements":
|
|
37
|
+
requireField(kind, rows, "sql");
|
|
38
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SqlTraceTable.SqlTraceTable, { rows });
|
|
39
|
+
case "terminal":
|
|
40
|
+
requireField(kind, rows, "kind");
|
|
41
|
+
requireField(kind, rows, "offset");
|
|
42
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TerminalCastPlayer.TerminalCastPlayer, { events: rows });
|
|
43
|
+
case "process_tree":
|
|
44
|
+
requireField(kind, rows, "key");
|
|
45
|
+
if (samples) requireField("process_samples", samples, "key");
|
|
46
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ProcessTraceView, { rows, samples });
|
|
47
|
+
default:
|
|
48
|
+
return /* @__PURE__ */ jsxRuntime.jsx(GenericTraceTable, { kind, rows });
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils.cn("flex h-full min-h-0 flex-col", className), children: body });
|
|
52
|
+
}
|
|
53
|
+
exports.TraceView = TraceView;
|
|
54
|
+
//# sourceMappingURL=TraceView.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TraceView.cjs","sources":["../../../src/data/traces/TraceView.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { inferColumns } from \"../data-table-utils\";\nimport { DataTable } from \"../DataTable\";\nimport { ProcessTraceTree } from \"./ProcessTraceTree\";\nimport { SqlTraceTable } from \"./SqlTraceTable\";\nimport { TerminalCastPlayer } from \"./TerminalCastPlayer\";\nimport { TraceHarView } from \"./TraceHarView\";\nimport type {\n HttpExchangeRow,\n ProcessNodeRow,\n ProcessSampleRow,\n SqlStatementRow,\n TerminalEvent,\n TraceRow,\n} from \"./types\";\n\nexport type TraceViewProps = {\n /** The record kind: `http`, `http_proxy`, `pg_proxy`, `sql_statements`, `terminal`, `process_tree`, or any other. */\n kind: string;\n /** The kind's records, as its stream stores them. */\n rows: TraceRow[];\n /** `process_samples` records of the same root; used by `process_tree` for CPU sparklines. */\n samples?: TraceRow[];\n /** Classes applied to the viewer root. */\n className?: string;\n};\n\n/** Each row of a kind with a dedicated viewer must carry the field that viewer cannot do without. */\nfunction requireField(kind: string, rows: TraceRow[], field: string): void {\n rows.forEach((row, index) => {\n if (row[field] === undefined || row[field] === null) {\n throw new Error(`trace kind \"${kind}\" row ${index} has no \"${field}\"`);\n }\n });\n}\n\nfunction GenericTraceTable({ kind, rows }: { kind: string; rows: TraceRow[] }) {\n const columns = useMemo(() => inferColumns(rows, { literalKeys: true }), [rows]);\n return <DataTable data={rows} columns={columns} autoFilter emptyMessage={`No ${kind} records`} />;\n}\n\nfunction ProcessTraceView({ rows, samples }: { rows: ProcessNodeRow[]; samples: ProcessSampleRow[] | undefined }) {\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n return <ProcessTraceTree nodes={rows} selectedKey={selectedKey} onSelect={setSelectedKey} {...(samples ? { samples } : {})} />;\n}\n\n/**\n * Renders a trace kind's records with its dedicated viewer: HTTP exchanges in\n * a HAR panel, SQL statements in a table, a terminal session in a cast player,\n * a process tree as a tree. Any other kind gets a table over the row keys.\n */\nexport function TraceView({ kind, rows, samples, className }: TraceViewProps) {\n const body = (() => {\n switch (kind) {\n case \"http\":\n case \"http_proxy\":\n requireField(kind, rows, \"request\");\n requireField(kind, rows, \"response\");\n return <TraceHarView rows={rows as HttpExchangeRow[]} />;\n case \"pg_proxy\":\n case \"sql_statements\":\n requireField(kind, rows, \"sql\");\n return <SqlTraceTable rows={rows as SqlStatementRow[]} />;\n case \"terminal\":\n requireField(kind, rows, \"kind\");\n requireField(kind, rows, \"offset\");\n return <TerminalCastPlayer events={rows as TerminalEvent[]} />;\n case \"process_tree\":\n requireField(kind, rows, \"key\");\n if (samples) requireField(\"process_samples\", samples, \"key\");\n return <ProcessTraceView rows={rows as ProcessNodeRow[]} samples={samples as ProcessSampleRow[] | undefined} />;\n default:\n return <GenericTraceTable kind={kind} rows={rows} />;\n }\n })();\n return <div className={cn(\"flex h-full min-h-0 flex-col\", className)}>{body}</div>;\n}\n"],"names":["useMemo","inferColumns","jsx","DataTable","useState","ProcessTraceTree","TraceHarView","SqlTraceTable","TerminalCastPlayer","cn"],"mappings":";;;;;;;;;;;AA6BA,SAAS,aAAa,MAAc,MAAkB,OAAqB;AACzE,OAAK,QAAQ,CAAC,KAAK,UAAU;AAC3B,QAAI,IAAI,KAAK,MAAM,UAAa,IAAI,KAAK,MAAM,MAAM;AACnD,YAAM,IAAI,MAAM,eAAe,IAAI,SAAS,KAAK,YAAY,KAAK,GAAG;AAAA,IACvE;AAAA,EACF,CAAC;AACH;AAEA,SAAS,kBAAkB,EAAE,MAAM,QAA4C;AAC7E,QAAM,UAAUA,MAAAA,QAAQ,MAAMC,eAAAA,aAAa,MAAM,EAAE,aAAa,KAAA,CAAM,GAAG,CAAC,IAAI,CAAC;AAC/E,SAAOC,2BAAAA,IAACC,UAAAA,WAAA,EAAU,MAAM,MAAM,SAAkB,YAAU,MAAC,cAAc,MAAM,IAAI,WAAA,CAAY;AACjG;AAEA,SAAS,iBAAiB,EAAE,MAAM,WAAgF;AAChH,QAAM,CAAC,aAAa,cAAc,IAAIC,MAAAA,SAAwB,IAAI;AAClE,SAAOF,2BAAAA,IAACG,iBAAAA,kBAAA,EAAiB,OAAO,MAAM,aAA0B,UAAU,gBAAiB,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA,EAAC,CAAI;AAC9H;AAOO,SAAS,UAAU,EAAE,MAAM,MAAM,SAAS,aAA6B;AAC5E,QAAM,QAAQ,MAAM;AAClB,YAAQ,MAAA;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AACH,qBAAa,MAAM,MAAM,SAAS;AAClC,qBAAa,MAAM,MAAM,UAAU;AACnC,eAAOH,+BAACI,aAAAA,gBAAa,MAAiC;AAAA,MACxD,KAAK;AAAA,MACL,KAAK;AACH,qBAAa,MAAM,MAAM,KAAK;AAC9B,eAAOJ,+BAACK,cAAAA,iBAAc,MAAiC;AAAA,MACzD,KAAK;AACH,qBAAa,MAAM,MAAM,MAAM;AAC/B,qBAAa,MAAM,MAAM,QAAQ;AACjC,eAAOL,2BAAAA,IAACM,mBAAAA,oBAAA,EAAmB,QAAQ,KAAA,CAAyB;AAAA,MAC9D,KAAK;AACH,qBAAa,MAAM,MAAM,KAAK;AAC9B,YAAI,QAAS,cAAa,mBAAmB,SAAS,KAAK;AAC3D,eAAON,2BAAAA,IAAC,kBAAA,EAAiB,MAAgC,QAAA,CAAoD;AAAA,MAC/G;AACE,eAAOA,2BAAAA,IAAC,mBAAA,EAAkB,MAAY,KAAA,CAAY;AAAA,IAAA;AAAA,EAExD,GAAA;AACA,wCAAQ,OAAA,EAAI,WAAWO,MAAAA,GAAG,gCAAgC,SAAS,GAAI,UAAA,MAAK;AAC9E;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TraceRow } from './types.js';
|
|
2
|
+
export type TraceViewProps = {
|
|
3
|
+
/** The record kind: `http`, `http_proxy`, `pg_proxy`, `sql_statements`, `terminal`, `process_tree`, or any other. */
|
|
4
|
+
kind: string;
|
|
5
|
+
/** The kind's records, as its stream stores them. */
|
|
6
|
+
rows: TraceRow[];
|
|
7
|
+
/** `process_samples` records of the same root; used by `process_tree` for CPU sparklines. */
|
|
8
|
+
samples?: TraceRow[];
|
|
9
|
+
/** Classes applied to the viewer root. */
|
|
10
|
+
className?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Renders a trace kind's records with its dedicated viewer: HTTP exchanges in
|
|
14
|
+
* a HAR panel, SQL statements in a table, a terminal session in a cast player,
|
|
15
|
+
* a process tree as a tree. Any other kind gets a table over the row keys.
|
|
16
|
+
*/
|
|
17
|
+
export declare function TraceView({ kind, rows, samples, className }: TraceViewProps): import("react").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=TraceView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TraceView.d.ts","sourceRoot":"","sources":["../../../src/data/traces/TraceView.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAMV,QAAQ,EACT,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,cAAc,GAAG;IAC3B,qHAAqH;IACrH,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;IACrB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAqBF;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,cAAc,+BAyB3E"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { inferColumns } from "../data-table-utils.js";
|
|
5
|
+
import { DataTable } from "../DataTable.js";
|
|
6
|
+
import { ProcessTraceTree } from "./ProcessTraceTree.js";
|
|
7
|
+
import { SqlTraceTable } from "./SqlTraceTable.js";
|
|
8
|
+
import { TerminalCastPlayer } from "./TerminalCastPlayer.js";
|
|
9
|
+
import { TraceHarView } from "./TraceHarView.js";
|
|
10
|
+
function requireField(kind, rows, field) {
|
|
11
|
+
rows.forEach((row, index) => {
|
|
12
|
+
if (row[field] === void 0 || row[field] === null) {
|
|
13
|
+
throw new Error(`trace kind "${kind}" row ${index} has no "${field}"`);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function GenericTraceTable({ kind, rows }) {
|
|
18
|
+
const columns = useMemo(() => inferColumns(rows, { literalKeys: true }), [rows]);
|
|
19
|
+
return /* @__PURE__ */ jsx(DataTable, { data: rows, columns, autoFilter: true, emptyMessage: `No ${kind} records` });
|
|
20
|
+
}
|
|
21
|
+
function ProcessTraceView({ rows, samples }) {
|
|
22
|
+
const [selectedKey, setSelectedKey] = useState(null);
|
|
23
|
+
return /* @__PURE__ */ jsx(ProcessTraceTree, { nodes: rows, selectedKey, onSelect: setSelectedKey, ...samples ? { samples } : {} });
|
|
24
|
+
}
|
|
25
|
+
function TraceView({ kind, rows, samples, className }) {
|
|
26
|
+
const body = (() => {
|
|
27
|
+
switch (kind) {
|
|
28
|
+
case "http":
|
|
29
|
+
case "http_proxy":
|
|
30
|
+
requireField(kind, rows, "request");
|
|
31
|
+
requireField(kind, rows, "response");
|
|
32
|
+
return /* @__PURE__ */ jsx(TraceHarView, { rows });
|
|
33
|
+
case "pg_proxy":
|
|
34
|
+
case "sql_statements":
|
|
35
|
+
requireField(kind, rows, "sql");
|
|
36
|
+
return /* @__PURE__ */ jsx(SqlTraceTable, { rows });
|
|
37
|
+
case "terminal":
|
|
38
|
+
requireField(kind, rows, "kind");
|
|
39
|
+
requireField(kind, rows, "offset");
|
|
40
|
+
return /* @__PURE__ */ jsx(TerminalCastPlayer, { events: rows });
|
|
41
|
+
case "process_tree":
|
|
42
|
+
requireField(kind, rows, "key");
|
|
43
|
+
if (samples) requireField("process_samples", samples, "key");
|
|
44
|
+
return /* @__PURE__ */ jsx(ProcessTraceView, { rows, samples });
|
|
45
|
+
default:
|
|
46
|
+
return /* @__PURE__ */ jsx(GenericTraceTable, { kind, rows });
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex h-full min-h-0 flex-col", className), children: body });
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
TraceView
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=TraceView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TraceView.js","sources":["../../../src/data/traces/TraceView.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { inferColumns } from \"../data-table-utils\";\nimport { DataTable } from \"../DataTable\";\nimport { ProcessTraceTree } from \"./ProcessTraceTree\";\nimport { SqlTraceTable } from \"./SqlTraceTable\";\nimport { TerminalCastPlayer } from \"./TerminalCastPlayer\";\nimport { TraceHarView } from \"./TraceHarView\";\nimport type {\n HttpExchangeRow,\n ProcessNodeRow,\n ProcessSampleRow,\n SqlStatementRow,\n TerminalEvent,\n TraceRow,\n} from \"./types\";\n\nexport type TraceViewProps = {\n /** The record kind: `http`, `http_proxy`, `pg_proxy`, `sql_statements`, `terminal`, `process_tree`, or any other. */\n kind: string;\n /** The kind's records, as its stream stores them. */\n rows: TraceRow[];\n /** `process_samples` records of the same root; used by `process_tree` for CPU sparklines. */\n samples?: TraceRow[];\n /** Classes applied to the viewer root. */\n className?: string;\n};\n\n/** Each row of a kind with a dedicated viewer must carry the field that viewer cannot do without. */\nfunction requireField(kind: string, rows: TraceRow[], field: string): void {\n rows.forEach((row, index) => {\n if (row[field] === undefined || row[field] === null) {\n throw new Error(`trace kind \"${kind}\" row ${index} has no \"${field}\"`);\n }\n });\n}\n\nfunction GenericTraceTable({ kind, rows }: { kind: string; rows: TraceRow[] }) {\n const columns = useMemo(() => inferColumns(rows, { literalKeys: true }), [rows]);\n return <DataTable data={rows} columns={columns} autoFilter emptyMessage={`No ${kind} records`} />;\n}\n\nfunction ProcessTraceView({ rows, samples }: { rows: ProcessNodeRow[]; samples: ProcessSampleRow[] | undefined }) {\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n return <ProcessTraceTree nodes={rows} selectedKey={selectedKey} onSelect={setSelectedKey} {...(samples ? { samples } : {})} />;\n}\n\n/**\n * Renders a trace kind's records with its dedicated viewer: HTTP exchanges in\n * a HAR panel, SQL statements in a table, a terminal session in a cast player,\n * a process tree as a tree. Any other kind gets a table over the row keys.\n */\nexport function TraceView({ kind, rows, samples, className }: TraceViewProps) {\n const body = (() => {\n switch (kind) {\n case \"http\":\n case \"http_proxy\":\n requireField(kind, rows, \"request\");\n requireField(kind, rows, \"response\");\n return <TraceHarView rows={rows as HttpExchangeRow[]} />;\n case \"pg_proxy\":\n case \"sql_statements\":\n requireField(kind, rows, \"sql\");\n return <SqlTraceTable rows={rows as SqlStatementRow[]} />;\n case \"terminal\":\n requireField(kind, rows, \"kind\");\n requireField(kind, rows, \"offset\");\n return <TerminalCastPlayer events={rows as TerminalEvent[]} />;\n case \"process_tree\":\n requireField(kind, rows, \"key\");\n if (samples) requireField(\"process_samples\", samples, \"key\");\n return <ProcessTraceView rows={rows as ProcessNodeRow[]} samples={samples as ProcessSampleRow[] | undefined} />;\n default:\n return <GenericTraceTable kind={kind} rows={rows} />;\n }\n })();\n return <div className={cn(\"flex h-full min-h-0 flex-col\", className)}>{body}</div>;\n}\n"],"names":[],"mappings":";;;;;;;;;AA6BA,SAAS,aAAa,MAAc,MAAkB,OAAqB;AACzE,OAAK,QAAQ,CAAC,KAAK,UAAU;AAC3B,QAAI,IAAI,KAAK,MAAM,UAAa,IAAI,KAAK,MAAM,MAAM;AACnD,YAAM,IAAI,MAAM,eAAe,IAAI,SAAS,KAAK,YAAY,KAAK,GAAG;AAAA,IACvE;AAAA,EACF,CAAC;AACH;AAEA,SAAS,kBAAkB,EAAE,MAAM,QAA4C;AAC7E,QAAM,UAAU,QAAQ,MAAM,aAAa,MAAM,EAAE,aAAa,KAAA,CAAM,GAAG,CAAC,IAAI,CAAC;AAC/E,SAAO,oBAAC,WAAA,EAAU,MAAM,MAAM,SAAkB,YAAU,MAAC,cAAc,MAAM,IAAI,WAAA,CAAY;AACjG;AAEA,SAAS,iBAAiB,EAAE,MAAM,WAAgF;AAChH,QAAM,CAAC,aAAa,cAAc,IAAI,SAAwB,IAAI;AAClE,SAAO,oBAAC,kBAAA,EAAiB,OAAO,MAAM,aAA0B,UAAU,gBAAiB,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA,EAAC,CAAI;AAC9H;AAOO,SAAS,UAAU,EAAE,MAAM,MAAM,SAAS,aAA6B;AAC5E,QAAM,QAAQ,MAAM;AAClB,YAAQ,MAAA;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AACH,qBAAa,MAAM,MAAM,SAAS;AAClC,qBAAa,MAAM,MAAM,UAAU;AACnC,eAAO,oBAAC,gBAAa,MAAiC;AAAA,MACxD,KAAK;AAAA,MACL,KAAK;AACH,qBAAa,MAAM,MAAM,KAAK;AAC9B,eAAO,oBAAC,iBAAc,MAAiC;AAAA,MACzD,KAAK;AACH,qBAAa,MAAM,MAAM,MAAM;AAC/B,qBAAa,MAAM,MAAM,QAAQ;AACjC,eAAO,oBAAC,oBAAA,EAAmB,QAAQ,KAAA,CAAyB;AAAA,MAC9D,KAAK;AACH,qBAAa,MAAM,MAAM,KAAK;AAC9B,YAAI,QAAS,cAAa,mBAAmB,SAAS,KAAK;AAC3D,eAAO,oBAAC,kBAAA,EAAiB,MAAgC,QAAA,CAAoD;AAAA,MAC/G;AACE,eAAO,oBAAC,mBAAA,EAAkB,MAAY,KAAA,CAAY;AAAA,IAAA;AAAA,EAExD,GAAA;AACA,6BAAQ,OAAA,EAAI,WAAW,GAAG,gCAAgC,SAAS,GAAI,UAAA,MAAK;AAC9E;"}
|