@nbt-dev/devtools 0.0.1
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/README.md +73 -0
- package/dist/components/devtools/console-tab.d.ts +3 -0
- package/dist/components/devtools/data-browser/bulk-decoder.d.ts +16 -0
- package/dist/components/devtools/data-browser/data-store.d.ts +18 -0
- package/dist/components/devtools/data-browser/data-table.d.ts +9 -0
- package/dist/components/devtools/data-tab.d.ts +3 -0
- package/dist/components/devtools/dev-tools.d.ts +3 -0
- package/dist/components/devtools/devtools-context.d.ts +35 -0
- package/dist/components/devtools/entity-graph/diagram-tab.d.ts +2 -0
- package/dist/components/devtools/entity-graph/entity-graph-utils.d.ts +92 -0
- package/dist/components/devtools/entity-graph/entity-node.d.ts +9 -0
- package/dist/components/devtools/network-tab.d.ts +3 -0
- package/dist/components/devtools/settings-tab.d.ts +3 -0
- package/dist/components/ui/button.d.ts +10 -0
- package/dist/components/ui/sheet.d.ts +14 -0
- package/dist/config.d.ts +10 -0
- package/dist/generated/bulk-protocol.d.ts +38 -0
- package/dist/hooks/use-bulk-stream.d.ts +24 -0
- package/dist/hooks/use-cartridge-info.d.ts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +2377 -0
- package/dist/index.js.map +7 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/styles.css +2 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2377 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/config.tsx
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
var DevToolsConfigContext = React.createContext({
|
|
7
|
+
apiBaseUrl: ""
|
|
8
|
+
});
|
|
9
|
+
var DevToolsConfigProvider = ({ apiBaseUrl = "", children }) => {
|
|
10
|
+
const value = React.useMemo(() => ({ apiBaseUrl }), [apiBaseUrl]);
|
|
11
|
+
return /* @__PURE__ */ jsx(DevToolsConfigContext.Provider, { value, children });
|
|
12
|
+
};
|
|
13
|
+
function useDevToolsConfig() {
|
|
14
|
+
return React.useContext(DevToolsConfigContext);
|
|
15
|
+
}
|
|
16
|
+
function wsBaseFrom(apiBaseUrl) {
|
|
17
|
+
if (!apiBaseUrl) {
|
|
18
|
+
const loc = window.location;
|
|
19
|
+
const proto = loc.protocol === "https:" ? "wss:" : "ws:";
|
|
20
|
+
return `${proto}//${loc.host}`;
|
|
21
|
+
}
|
|
22
|
+
return apiBaseUrl.replace(/^http(s?):/, "ws$1:");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/components/devtools/devtools-context.tsx
|
|
26
|
+
import React2, { useEffect } from "react";
|
|
27
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
28
|
+
var Ctx = React2.createContext(null);
|
|
29
|
+
var DevToolsProvider = ({ children, defaultActiveTab }) => {
|
|
30
|
+
const [open, setOpen] = React2.useState(false);
|
|
31
|
+
const [dock, setDock] = React2.useState("bottom");
|
|
32
|
+
const [activeTab, setActiveTab] = React2.useState(
|
|
33
|
+
defaultActiveTab ?? null
|
|
34
|
+
);
|
|
35
|
+
const [size, setSize] = React2.useState({ h: 320, w: 480 });
|
|
36
|
+
const [maximized, setMaximized] = React2.useState(false);
|
|
37
|
+
const [dataCart, setDataCart] = React2.useState(null);
|
|
38
|
+
const [dataEntity, setDataEntity] = React2.useState(null);
|
|
39
|
+
const toggle = React2.useCallback(() => setOpen((o) => !o), []);
|
|
40
|
+
const value = React2.useMemo(
|
|
41
|
+
() => ({
|
|
42
|
+
open,
|
|
43
|
+
setOpen,
|
|
44
|
+
toggle,
|
|
45
|
+
dock,
|
|
46
|
+
setDock,
|
|
47
|
+
activeTab,
|
|
48
|
+
setActiveTab,
|
|
49
|
+
size,
|
|
50
|
+
setSize,
|
|
51
|
+
maximized,
|
|
52
|
+
setMaximized,
|
|
53
|
+
dataCart,
|
|
54
|
+
setDataCart,
|
|
55
|
+
dataEntity,
|
|
56
|
+
setDataEntity
|
|
57
|
+
}),
|
|
58
|
+
[open, toggle, dock, activeTab, size, maximized, dataCart, dataEntity]
|
|
59
|
+
);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const listen = () => toggle();
|
|
62
|
+
window.addEventListener("devtools-toggle", listen);
|
|
63
|
+
return () => window.removeEventListener("devtools-toggle", listen);
|
|
64
|
+
}, [open, toggle]);
|
|
65
|
+
return /* @__PURE__ */ jsx2(Ctx.Provider, { value, children });
|
|
66
|
+
};
|
|
67
|
+
function useDevTools() {
|
|
68
|
+
const v = React2.useContext(Ctx);
|
|
69
|
+
if (!v) throw new Error("useDevTools must be used inside <DevToolsProvider>");
|
|
70
|
+
return v;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/components/devtools/dev-tools.tsx
|
|
74
|
+
import React8 from "react";
|
|
75
|
+
import { Maximize2, Minimize2, PanelBottom, PanelRight, X } from "lucide-react";
|
|
76
|
+
|
|
77
|
+
// src/lib/utils.ts
|
|
78
|
+
import { clsx } from "clsx";
|
|
79
|
+
import { twMerge } from "tailwind-merge";
|
|
80
|
+
function cn(...inputs) {
|
|
81
|
+
return twMerge(clsx(inputs));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/components/devtools/console-tab.tsx
|
|
85
|
+
import React3 from "react";
|
|
86
|
+
import {
|
|
87
|
+
ChevronRight,
|
|
88
|
+
Pause,
|
|
89
|
+
Play,
|
|
90
|
+
Trash2
|
|
91
|
+
} from "lucide-react";
|
|
92
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
93
|
+
var MAX_ENTRIES = 5e3;
|
|
94
|
+
var RECONNECT_MIN_MS = 500;
|
|
95
|
+
var RECONNECT_MAX_MS = 5e3;
|
|
96
|
+
function resolveWsUrl(apiBaseUrl, path) {
|
|
97
|
+
return `${wsBaseFrom(apiBaseUrl)}${path}`;
|
|
98
|
+
}
|
|
99
|
+
function classifyLine(line) {
|
|
100
|
+
if (line.parsed) {
|
|
101
|
+
try {
|
|
102
|
+
const p = JSON.parse(line.raw);
|
|
103
|
+
const lvl = p.level === "info" || p.level === "warn" || p.level === "error" ? p.level : "plain";
|
|
104
|
+
return {
|
|
105
|
+
id: _nextId(),
|
|
106
|
+
raw: line.raw,
|
|
107
|
+
level: lvl,
|
|
108
|
+
parsed: p
|
|
109
|
+
};
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { id: _nextId(), raw: line.raw, level: "plain", parsed: null };
|
|
114
|
+
}
|
|
115
|
+
var _idCounter = 0;
|
|
116
|
+
function _nextId() {
|
|
117
|
+
_idCounter += 1;
|
|
118
|
+
return _idCounter;
|
|
119
|
+
}
|
|
120
|
+
var LEVEL_BADGE = {
|
|
121
|
+
info: "bg-blue-500/20 text-blue-300 border-blue-400/30",
|
|
122
|
+
warn: "bg-yellow-500/20 text-yellow-300 border-yellow-400/30",
|
|
123
|
+
error: "bg-red-500/20 text-red-300 border-red-400/30",
|
|
124
|
+
plain: "bg-zinc-700/40 text-zinc-300 border-zinc-500/30"
|
|
125
|
+
};
|
|
126
|
+
var ConsoleTab = () => {
|
|
127
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
128
|
+
const [entries, setEntries] = React3.useState([]);
|
|
129
|
+
const [paused, setPaused] = React3.useState(false);
|
|
130
|
+
const [filter, setFilter] = React3.useState({
|
|
131
|
+
info: true,
|
|
132
|
+
warn: true,
|
|
133
|
+
error: true,
|
|
134
|
+
plain: true
|
|
135
|
+
});
|
|
136
|
+
const [expanded, setExpanded] = React3.useState({});
|
|
137
|
+
const scrollerRef = React3.useRef(null);
|
|
138
|
+
const stickToBottomRef = React3.useRef(true);
|
|
139
|
+
const pausedRef = React3.useRef(paused);
|
|
140
|
+
pausedRef.current = paused;
|
|
141
|
+
React3.useEffect(() => {
|
|
142
|
+
let alive = true;
|
|
143
|
+
let socket = null;
|
|
144
|
+
let backoff = RECONNECT_MIN_MS;
|
|
145
|
+
let reconnectTimer = null;
|
|
146
|
+
const connect = () => {
|
|
147
|
+
if (!alive) return;
|
|
148
|
+
try {
|
|
149
|
+
socket = new WebSocket(resolveWsUrl(apiBaseUrl, "/_console/logs/ws"));
|
|
150
|
+
} catch {
|
|
151
|
+
scheduleReconnect();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
socket.onopen = () => {
|
|
155
|
+
backoff = RECONNECT_MIN_MS;
|
|
156
|
+
};
|
|
157
|
+
socket.onmessage = (ev) => {
|
|
158
|
+
if (pausedRef.current) return;
|
|
159
|
+
if (typeof ev.data !== "string") return;
|
|
160
|
+
let frame;
|
|
161
|
+
try {
|
|
162
|
+
frame = JSON.parse(ev.data);
|
|
163
|
+
} catch {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (!frame.lines || frame.lines.length === 0) return;
|
|
167
|
+
const batch = frame.lines.map(classifyLine);
|
|
168
|
+
setEntries((prev) => {
|
|
169
|
+
const next = prev.concat(batch);
|
|
170
|
+
if (next.length > MAX_ENTRIES) {
|
|
171
|
+
return next.slice(next.length - MAX_ENTRIES);
|
|
172
|
+
}
|
|
173
|
+
return next;
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
socket.onerror = () => {
|
|
177
|
+
};
|
|
178
|
+
socket.onclose = () => {
|
|
179
|
+
socket = null;
|
|
180
|
+
scheduleReconnect();
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
const scheduleReconnect = () => {
|
|
184
|
+
if (!alive) return;
|
|
185
|
+
reconnectTimer = setTimeout(() => {
|
|
186
|
+
backoff = Math.min(backoff * 2, RECONNECT_MAX_MS);
|
|
187
|
+
connect();
|
|
188
|
+
}, backoff);
|
|
189
|
+
};
|
|
190
|
+
connect();
|
|
191
|
+
return () => {
|
|
192
|
+
alive = false;
|
|
193
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
194
|
+
if (socket) socket.close();
|
|
195
|
+
};
|
|
196
|
+
}, []);
|
|
197
|
+
const handleScroll = React3.useCallback(() => {
|
|
198
|
+
const el = scrollerRef.current;
|
|
199
|
+
if (!el) return;
|
|
200
|
+
const atBottom = el.scrollHeight - (el.scrollTop + el.clientHeight) < 8;
|
|
201
|
+
stickToBottomRef.current = atBottom;
|
|
202
|
+
}, []);
|
|
203
|
+
const scrollRafRef = React3.useRef(0);
|
|
204
|
+
React3.useEffect(() => {
|
|
205
|
+
if (!stickToBottomRef.current) return;
|
|
206
|
+
if (scrollRafRef.current) return;
|
|
207
|
+
scrollRafRef.current = requestAnimationFrame(() => {
|
|
208
|
+
scrollRafRef.current = 0;
|
|
209
|
+
const el = scrollerRef.current;
|
|
210
|
+
if (!el) return;
|
|
211
|
+
el.scrollTop = el.scrollHeight;
|
|
212
|
+
});
|
|
213
|
+
}, [entries]);
|
|
214
|
+
React3.useEffect(
|
|
215
|
+
() => () => {
|
|
216
|
+
if (scrollRafRef.current) cancelAnimationFrame(scrollRafRef.current);
|
|
217
|
+
},
|
|
218
|
+
[]
|
|
219
|
+
);
|
|
220
|
+
const visible = React3.useMemo(
|
|
221
|
+
() => entries.filter((e) => filter[e.level]),
|
|
222
|
+
[entries, filter]
|
|
223
|
+
);
|
|
224
|
+
const toggleFilter = React3.useCallback(
|
|
225
|
+
(k) => setFilter((f) => ({ ...f, [k]: !f[k] })),
|
|
226
|
+
[]
|
|
227
|
+
);
|
|
228
|
+
const onRowClick = React3.useCallback((e) => {
|
|
229
|
+
let el = e.target;
|
|
230
|
+
while (el && el !== e.currentTarget) {
|
|
231
|
+
const id = el.dataset.rowId;
|
|
232
|
+
if (id) {
|
|
233
|
+
const n = Number(id);
|
|
234
|
+
if (Number.isFinite(n)) {
|
|
235
|
+
setExpanded((prev) => ({ ...prev, [n]: !prev[n] }));
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
el = el.parentElement;
|
|
240
|
+
}
|
|
241
|
+
}, []);
|
|
242
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
|
|
243
|
+
/* @__PURE__ */ jsxs("div", { className: "flex h-7 shrink-0 items-center gap-1 border-b border-border bg-background px-1.5 select-none", children: [
|
|
244
|
+
/* @__PURE__ */ jsx3(
|
|
245
|
+
ToolbarButton,
|
|
246
|
+
{
|
|
247
|
+
"aria-label": paused ? "Resume" : "Pause",
|
|
248
|
+
onClick: () => setPaused((p) => !p),
|
|
249
|
+
children: paused ? /* @__PURE__ */ jsx3(Play, { className: "size-3.5" }) : /* @__PURE__ */ jsx3(Pause, { className: "size-3.5" })
|
|
250
|
+
}
|
|
251
|
+
),
|
|
252
|
+
/* @__PURE__ */ jsx3(
|
|
253
|
+
ToolbarButton,
|
|
254
|
+
{
|
|
255
|
+
"aria-label": "Clear",
|
|
256
|
+
onClick: () => {
|
|
257
|
+
setEntries([]);
|
|
258
|
+
setExpanded({});
|
|
259
|
+
},
|
|
260
|
+
children: /* @__PURE__ */ jsx3(Trash2, { className: "size-3.5" })
|
|
261
|
+
}
|
|
262
|
+
),
|
|
263
|
+
/* @__PURE__ */ jsx3("div", { className: "mx-1 h-3 w-px bg-border/60" }),
|
|
264
|
+
["info", "warn", "error", "plain"].map((k) => /* @__PURE__ */ jsx3(
|
|
265
|
+
FilterChip,
|
|
266
|
+
{
|
|
267
|
+
label: k,
|
|
268
|
+
active: filter[k],
|
|
269
|
+
level: k,
|
|
270
|
+
onClick: () => toggleFilter(k)
|
|
271
|
+
},
|
|
272
|
+
k
|
|
273
|
+
)),
|
|
274
|
+
/* @__PURE__ */ jsx3("div", { className: "flex-1" }),
|
|
275
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground tabular-nums", children: [
|
|
276
|
+
visible.length,
|
|
277
|
+
" / ",
|
|
278
|
+
entries.length
|
|
279
|
+
] })
|
|
280
|
+
] }),
|
|
281
|
+
/* @__PURE__ */ jsx3(
|
|
282
|
+
"div",
|
|
283
|
+
{
|
|
284
|
+
ref: scrollerRef,
|
|
285
|
+
onScroll: handleScroll,
|
|
286
|
+
style: { overflowAnchor: "none" },
|
|
287
|
+
className: "min-h-0 flex-1 overflow-auto font-mono text-[11px] leading-[1.45]",
|
|
288
|
+
children: visible.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "p-3 text-muted-foreground", children: "Waiting for console output\u2026" }) : /* @__PURE__ */ jsx3("ul", { onClick: onRowClick, children: visible.map((e) => /* @__PURE__ */ jsx3(
|
|
289
|
+
LogRow,
|
|
290
|
+
{
|
|
291
|
+
entry: e,
|
|
292
|
+
expanded: !!expanded[e.id]
|
|
293
|
+
},
|
|
294
|
+
e.id
|
|
295
|
+
)) })
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
] });
|
|
299
|
+
};
|
|
300
|
+
var ToolbarButton = ({ className, children, ...rest }) => /* @__PURE__ */ jsx3(
|
|
301
|
+
"button",
|
|
302
|
+
{
|
|
303
|
+
type: "button",
|
|
304
|
+
className: cn(
|
|
305
|
+
"grid size-5 place-items-center rounded-md text-foreground outline-none hover:bg-accent hover:text-accent-foreground",
|
|
306
|
+
className
|
|
307
|
+
),
|
|
308
|
+
...rest,
|
|
309
|
+
children
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
var FilterChip = ({ label, level, active, onClick }) => /* @__PURE__ */ jsx3(
|
|
313
|
+
"button",
|
|
314
|
+
{
|
|
315
|
+
type: "button",
|
|
316
|
+
onClick,
|
|
317
|
+
"data-active": active,
|
|
318
|
+
className: cn(
|
|
319
|
+
"h-5 rounded-md border px-1.5 text-[11px] leading-none transition-colors",
|
|
320
|
+
"data-[active=false]:opacity-40 data-[active=false]:hover:opacity-70",
|
|
321
|
+
LEVEL_BADGE[level]
|
|
322
|
+
),
|
|
323
|
+
children: label
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
var _LogRow = ({ entry, expanded }) => {
|
|
327
|
+
if (entry.level === "plain" || !entry.parsed) {
|
|
328
|
+
return /* @__PURE__ */ jsx3(
|
|
329
|
+
"li",
|
|
330
|
+
{
|
|
331
|
+
className: "border-b border-border/30 px-2 py-0.5 text-zinc-400 whitespace-pre-wrap break-words",
|
|
332
|
+
style: { contain: "content" },
|
|
333
|
+
children: entry.raw
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
const p = entry.parsed;
|
|
338
|
+
const hasStack = Array.isArray(p.stack) && p.stack.length > 0;
|
|
339
|
+
return /* @__PURE__ */ jsxs(
|
|
340
|
+
"li",
|
|
341
|
+
{
|
|
342
|
+
className: "border-b border-border/30",
|
|
343
|
+
style: { contain: "content" },
|
|
344
|
+
"data-row-id": hasStack ? entry.id : void 0,
|
|
345
|
+
children: [
|
|
346
|
+
/* @__PURE__ */ jsxs(
|
|
347
|
+
"div",
|
|
348
|
+
{
|
|
349
|
+
className: cn(
|
|
350
|
+
"flex items-start gap-1.5 px-2 py-0.5",
|
|
351
|
+
hasStack && "cursor-pointer hover:bg-accent/30"
|
|
352
|
+
),
|
|
353
|
+
children: [
|
|
354
|
+
hasStack ? /* @__PURE__ */ jsx3(
|
|
355
|
+
ChevronRight,
|
|
356
|
+
{
|
|
357
|
+
className: cn(
|
|
358
|
+
"mt-0.5 size-3 shrink-0 transition-transform",
|
|
359
|
+
expanded && "rotate-90"
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
) : /* @__PURE__ */ jsx3("span", { className: "inline-block size-3 shrink-0" }),
|
|
363
|
+
/* @__PURE__ */ jsx3(
|
|
364
|
+
"span",
|
|
365
|
+
{
|
|
366
|
+
className: cn(
|
|
367
|
+
"shrink-0 rounded border px-1 text-[10px] font-medium uppercase tracking-wide",
|
|
368
|
+
LEVEL_BADGE[entry.level]
|
|
369
|
+
),
|
|
370
|
+
children: entry.level
|
|
371
|
+
}
|
|
372
|
+
),
|
|
373
|
+
p.ts && /* @__PURE__ */ jsx3("span", { className: "shrink-0 text-zinc-500 tabular-nums", children: p.ts }),
|
|
374
|
+
/* @__PURE__ */ jsx3("span", { className: "break-words text-zinc-200", children: p.msg ?? entry.raw })
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
),
|
|
378
|
+
hasStack && expanded && /* @__PURE__ */ jsx3("ul", { className: "border-t border-border/20 bg-accent/10 px-7 py-1", children: p.stack.map((f, i) => /* @__PURE__ */ jsxs(
|
|
379
|
+
"li",
|
|
380
|
+
{
|
|
381
|
+
className: "text-zinc-400 whitespace-pre-wrap break-words",
|
|
382
|
+
children: [
|
|
383
|
+
/* @__PURE__ */ jsx3("span", { className: "text-zinc-300", children: f.proc || "<anon>" }),
|
|
384
|
+
/* @__PURE__ */ jsx3("span", { className: "text-zinc-500", children: " @ " }),
|
|
385
|
+
/* @__PURE__ */ jsx3("span", { children: f.file }),
|
|
386
|
+
/* @__PURE__ */ jsx3("span", { className: "text-zinc-500", children: ":" }),
|
|
387
|
+
/* @__PURE__ */ jsx3("span", { className: "tabular-nums text-zinc-300", children: f.line })
|
|
388
|
+
]
|
|
389
|
+
},
|
|
390
|
+
i
|
|
391
|
+
)) })
|
|
392
|
+
]
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
};
|
|
396
|
+
var LogRow = React3.memo(
|
|
397
|
+
_LogRow,
|
|
398
|
+
(prev, next) => prev.entry === next.entry && prev.expanded === next.expanded
|
|
399
|
+
);
|
|
400
|
+
var console_tab_default = ConsoleTab;
|
|
401
|
+
|
|
402
|
+
// src/components/devtools/network-tab.tsx
|
|
403
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
404
|
+
var NetworkTab = () => {
|
|
405
|
+
return /* @__PURE__ */ jsx4("div", { className: "p-3 text-muted-foreground", children: "Network placeholder" });
|
|
406
|
+
};
|
|
407
|
+
var network_tab_default = NetworkTab;
|
|
408
|
+
|
|
409
|
+
// src/components/devtools/data-tab.tsx
|
|
410
|
+
import React7 from "react";
|
|
411
|
+
import { Network, Table2 } from "lucide-react";
|
|
412
|
+
|
|
413
|
+
// src/hooks/use-cartridge-info.ts
|
|
414
|
+
import { useEffect as useEffect2, useState } from "react";
|
|
415
|
+
function buildRegistry(contracts) {
|
|
416
|
+
const reg = {};
|
|
417
|
+
for (const c of contracts) {
|
|
418
|
+
const cart = c.cartridge;
|
|
419
|
+
if (!cart || !c.owns) continue;
|
|
420
|
+
const entities = [];
|
|
421
|
+
for (const [name, ent] of Object.entries(c.owns)) {
|
|
422
|
+
const sf = Array.isArray(ent?.searchFields) ? ent.searchFields : [];
|
|
423
|
+
entities.push({
|
|
424
|
+
name,
|
|
425
|
+
route: `/_ws/bulk/${cart}/${name.toLowerCase()}`,
|
|
426
|
+
searchFields: sf
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
if (entities.length > 0) reg[cart] = entities;
|
|
430
|
+
}
|
|
431
|
+
return reg;
|
|
432
|
+
}
|
|
433
|
+
function useLiveBulkRegistry() {
|
|
434
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
435
|
+
const [registry, setRegistry] = useState({});
|
|
436
|
+
const [loading, setLoading] = useState(true);
|
|
437
|
+
const [error, setError] = useState(null);
|
|
438
|
+
useEffect2(() => {
|
|
439
|
+
const ac = new AbortController();
|
|
440
|
+
let cancelled = false;
|
|
441
|
+
(async () => {
|
|
442
|
+
try {
|
|
443
|
+
const r = await fetch(`${apiBaseUrl}/_console/contracts`, {
|
|
444
|
+
signal: ac.signal,
|
|
445
|
+
credentials: "include"
|
|
446
|
+
});
|
|
447
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
448
|
+
const data = await r.json();
|
|
449
|
+
if (!Array.isArray(data)) throw new Error("malformed contracts response");
|
|
450
|
+
if (cancelled) return;
|
|
451
|
+
setRegistry(buildRegistry(data));
|
|
452
|
+
} catch (e) {
|
|
453
|
+
if (cancelled || e.name === "AbortError") return;
|
|
454
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
455
|
+
} finally {
|
|
456
|
+
if (!cancelled) setLoading(false);
|
|
457
|
+
}
|
|
458
|
+
})();
|
|
459
|
+
return () => {
|
|
460
|
+
cancelled = true;
|
|
461
|
+
ac.abort();
|
|
462
|
+
};
|
|
463
|
+
}, []);
|
|
464
|
+
const carts = Object.keys(registry).sort();
|
|
465
|
+
return { registry, carts, loading, error };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/hooks/use-bulk-stream.ts
|
|
469
|
+
import React4, {
|
|
470
|
+
createContext,
|
|
471
|
+
useContext,
|
|
472
|
+
useEffect as useEffect3,
|
|
473
|
+
useRef,
|
|
474
|
+
useState as useState2
|
|
475
|
+
} from "react";
|
|
476
|
+
|
|
477
|
+
// src/generated/bulk-protocol.ts
|
|
478
|
+
var FRAME_SCHEMA = 1;
|
|
479
|
+
var FRAME_DATA = 2;
|
|
480
|
+
var FRAME_DATA_END = 3;
|
|
481
|
+
var FRAME_DELTA_INS = 4;
|
|
482
|
+
var FRAME_DELTA_UPD = 5;
|
|
483
|
+
var FRAME_DELTA_DEL = 6;
|
|
484
|
+
var FRAME_SEARCH_RESULT = 7;
|
|
485
|
+
var FRAME_SEARCH_END = 8;
|
|
486
|
+
var FRAME_ERROR = 255;
|
|
487
|
+
var TYPE_U8 = 1;
|
|
488
|
+
var TYPE_U16 = 2;
|
|
489
|
+
var TYPE_U32 = 3;
|
|
490
|
+
var TYPE_U64 = 4;
|
|
491
|
+
var TYPE_S8 = 5;
|
|
492
|
+
var TYPE_S16 = 6;
|
|
493
|
+
var TYPE_S32 = 7;
|
|
494
|
+
var TYPE_S64 = 8;
|
|
495
|
+
var TYPE_BOOL = 9;
|
|
496
|
+
var TYPE_FLOAT32 = 10;
|
|
497
|
+
var TYPE_FLOAT64 = 11;
|
|
498
|
+
var TYPE_STRING = 12;
|
|
499
|
+
var TYPE_DATETIME = 13;
|
|
500
|
+
var TYPE_DOCUMENT = 14;
|
|
501
|
+
function fixedSizeForType(type) {
|
|
502
|
+
switch (type) {
|
|
503
|
+
case TYPE_U8:
|
|
504
|
+
return 1;
|
|
505
|
+
case TYPE_U16:
|
|
506
|
+
return 2;
|
|
507
|
+
case TYPE_U32:
|
|
508
|
+
return 4;
|
|
509
|
+
case TYPE_U64:
|
|
510
|
+
return 8;
|
|
511
|
+
case TYPE_S8:
|
|
512
|
+
return 1;
|
|
513
|
+
case TYPE_S16:
|
|
514
|
+
return 2;
|
|
515
|
+
case TYPE_S32:
|
|
516
|
+
return 4;
|
|
517
|
+
case TYPE_S64:
|
|
518
|
+
return 8;
|
|
519
|
+
case TYPE_BOOL:
|
|
520
|
+
return 1;
|
|
521
|
+
case TYPE_FLOAT32:
|
|
522
|
+
return 4;
|
|
523
|
+
case TYPE_FLOAT64:
|
|
524
|
+
return 8;
|
|
525
|
+
case TYPE_STRING:
|
|
526
|
+
return 0;
|
|
527
|
+
case TYPE_DATETIME:
|
|
528
|
+
return 8;
|
|
529
|
+
case TYPE_DOCUMENT:
|
|
530
|
+
return 0;
|
|
531
|
+
default:
|
|
532
|
+
return 0;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/components/devtools/data-browser/data-store.ts
|
|
537
|
+
var BulkDataStore = class {
|
|
538
|
+
constructor() {
|
|
539
|
+
this.columns = [];
|
|
540
|
+
this.rows = [];
|
|
541
|
+
this.totalRows = 0;
|
|
542
|
+
this.searchActive = false;
|
|
543
|
+
this._fullRows = [];
|
|
544
|
+
this._fullTotalRows = 0;
|
|
545
|
+
this._idColIndex = -1;
|
|
546
|
+
}
|
|
547
|
+
applySchema(schema) {
|
|
548
|
+
this.columns = schema.columns;
|
|
549
|
+
this.totalRows = schema.totalRows;
|
|
550
|
+
this.rows = [];
|
|
551
|
+
this._idColIndex = schema.columns.findIndex((c) => c.name === "id");
|
|
552
|
+
}
|
|
553
|
+
appendChunk(chunk) {
|
|
554
|
+
for (let i = 0; i < chunk.length; i++) this.rows.push(chunk[i]);
|
|
555
|
+
}
|
|
556
|
+
applyDelta(delta) {
|
|
557
|
+
const target = this.searchActive ? this._fullRows : this.rows;
|
|
558
|
+
if (delta.op === FRAME_DELTA_INS && delta.rowData) {
|
|
559
|
+
target.push(delta.rowData);
|
|
560
|
+
if (this.searchActive) this._fullTotalRows++;
|
|
561
|
+
else this.totalRows++;
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (delta.op === FRAME_DELTA_UPD && delta.rowData) {
|
|
565
|
+
const idx = this._findRowById(target, delta.rowData);
|
|
566
|
+
if (idx >= 0) target[idx] = delta.rowData;
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (delta.op === FRAME_DELTA_DEL && delta.id !== void 0) {
|
|
570
|
+
const idStr = String(delta.id);
|
|
571
|
+
const idx = this._findRowByIdStr(target, idStr);
|
|
572
|
+
if (idx >= 0) {
|
|
573
|
+
target.splice(idx, 1);
|
|
574
|
+
if (this.searchActive) this._fullTotalRows--;
|
|
575
|
+
else this.totalRows--;
|
|
576
|
+
}
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
enterSearch(schema) {
|
|
581
|
+
if (!this.searchActive) {
|
|
582
|
+
this._fullRows = this.rows;
|
|
583
|
+
this._fullTotalRows = this.totalRows;
|
|
584
|
+
}
|
|
585
|
+
this.searchActive = true;
|
|
586
|
+
this.columns = schema.columns;
|
|
587
|
+
this.totalRows = schema.totalRows;
|
|
588
|
+
this.rows = [];
|
|
589
|
+
}
|
|
590
|
+
exitSearch() {
|
|
591
|
+
if (!this.searchActive) return;
|
|
592
|
+
this.rows = this._fullRows;
|
|
593
|
+
this.totalRows = this._fullTotalRows;
|
|
594
|
+
this._fullRows = [];
|
|
595
|
+
this._fullTotalRows = 0;
|
|
596
|
+
this.searchActive = false;
|
|
597
|
+
}
|
|
598
|
+
getRowCount() {
|
|
599
|
+
return this.rows.length;
|
|
600
|
+
}
|
|
601
|
+
_findRowById(rows, rowData) {
|
|
602
|
+
if (this._idColIndex < 0) return -1;
|
|
603
|
+
const id = rowData[this._idColIndex];
|
|
604
|
+
for (let i = 0; i < rows.length; i++) {
|
|
605
|
+
if (rows[i][this._idColIndex] === id) return i;
|
|
606
|
+
}
|
|
607
|
+
return -1;
|
|
608
|
+
}
|
|
609
|
+
_findRowByIdStr(rows, idStr) {
|
|
610
|
+
if (this._idColIndex < 0) return -1;
|
|
611
|
+
for (let i = 0; i < rows.length; i++) {
|
|
612
|
+
if (rows[i][this._idColIndex] === idStr) return i;
|
|
613
|
+
}
|
|
614
|
+
return -1;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// src/components/devtools/data-browser/bulk-decoder.ts
|
|
619
|
+
var textDecoder = new TextDecoder();
|
|
620
|
+
var SID_BYTES = 2;
|
|
621
|
+
var HEAD = 1 + SID_BYTES;
|
|
622
|
+
function epochToDate(epoch) {
|
|
623
|
+
const abs = Math.abs(epoch);
|
|
624
|
+
if (abs < 1e10) return new Date(epoch * 1e3);
|
|
625
|
+
if (abs < 1e13) return new Date(epoch);
|
|
626
|
+
if (abs < 1e16) return new Date(epoch / 1e3);
|
|
627
|
+
return new Date(epoch / 1e6);
|
|
628
|
+
}
|
|
629
|
+
function formatCellValue(view, offset, col) {
|
|
630
|
+
switch (col.type) {
|
|
631
|
+
case TYPE_U8:
|
|
632
|
+
return [String(view.getUint8(offset)), 1];
|
|
633
|
+
case TYPE_S8:
|
|
634
|
+
return [String(view.getInt8(offset)), 1];
|
|
635
|
+
case TYPE_BOOL:
|
|
636
|
+
return [view.getUint8(offset) ? "true" : "false", 1];
|
|
637
|
+
case TYPE_U16:
|
|
638
|
+
return [String(view.getUint16(offset, true)), 2];
|
|
639
|
+
case TYPE_S16:
|
|
640
|
+
return [String(view.getInt16(offset, true)), 2];
|
|
641
|
+
case TYPE_U32:
|
|
642
|
+
return [String(view.getUint32(offset, true)), 4];
|
|
643
|
+
case TYPE_S32:
|
|
644
|
+
return [String(view.getInt32(offset, true)), 4];
|
|
645
|
+
case TYPE_FLOAT32:
|
|
646
|
+
return [String(view.getFloat32(offset, true)), 4];
|
|
647
|
+
case TYPE_U64:
|
|
648
|
+
return [String(view.getBigUint64(offset, true)), 8];
|
|
649
|
+
case TYPE_S64:
|
|
650
|
+
return [String(view.getBigInt64(offset, true)), 8];
|
|
651
|
+
case TYPE_FLOAT64:
|
|
652
|
+
return [String(view.getFloat64(offset, true)), 8];
|
|
653
|
+
case TYPE_DATETIME: {
|
|
654
|
+
const epoch = Number(view.getBigInt64(offset, true));
|
|
655
|
+
if (epoch === 0) return ["", 8];
|
|
656
|
+
return [epochToDate(epoch).toISOString(), 8];
|
|
657
|
+
}
|
|
658
|
+
case TYPE_STRING:
|
|
659
|
+
case TYPE_DOCUMENT: {
|
|
660
|
+
const len = view.getUint32(offset, true);
|
|
661
|
+
const bytes = new Uint8Array(view.buffer, view.byteOffset + offset + 4, len);
|
|
662
|
+
return [textDecoder.decode(bytes), 4 + len];
|
|
663
|
+
}
|
|
664
|
+
default:
|
|
665
|
+
return ["", 0];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function getFrameType(buf) {
|
|
669
|
+
return new DataView(buf).getUint8(0);
|
|
670
|
+
}
|
|
671
|
+
function getFrameSid(buf) {
|
|
672
|
+
return new DataView(buf).getUint16(1, true);
|
|
673
|
+
}
|
|
674
|
+
function parseSchema(buf) {
|
|
675
|
+
const view = new DataView(buf);
|
|
676
|
+
let offset = 0;
|
|
677
|
+
const frameType = view.getUint8(offset);
|
|
678
|
+
offset += 1;
|
|
679
|
+
if (frameType !== FRAME_SCHEMA) {
|
|
680
|
+
throw new Error(`Expected SCHEMA frame (0x01), got 0x${frameType.toString(16)}`);
|
|
681
|
+
}
|
|
682
|
+
offset += SID_BYTES;
|
|
683
|
+
const totalRows = view.getUint32(offset, true);
|
|
684
|
+
offset += 4;
|
|
685
|
+
const columnCount = view.getUint16(offset, true);
|
|
686
|
+
offset += 2;
|
|
687
|
+
const columns = [];
|
|
688
|
+
for (let i = 0; i < columnCount; i++) {
|
|
689
|
+
const type = view.getUint8(offset);
|
|
690
|
+
offset += 1;
|
|
691
|
+
const nameLen = view.getUint16(offset, true);
|
|
692
|
+
offset += 2;
|
|
693
|
+
const nameBytes = new Uint8Array(buf, offset, nameLen);
|
|
694
|
+
const name = textDecoder.decode(nameBytes);
|
|
695
|
+
offset += nameLen;
|
|
696
|
+
columns.push({ name, type, fixedSize: fixedSizeForType(type) });
|
|
697
|
+
}
|
|
698
|
+
return { totalRows, columns };
|
|
699
|
+
}
|
|
700
|
+
function parseDataChunk(buf, columns) {
|
|
701
|
+
const view = new DataView(buf);
|
|
702
|
+
let offset = 0;
|
|
703
|
+
const frameType = view.getUint8(offset);
|
|
704
|
+
offset += 1;
|
|
705
|
+
if (frameType !== FRAME_DATA && frameType !== FRAME_SEARCH_RESULT) {
|
|
706
|
+
throw new Error(`Expected DATA/SEARCH_RESULT frame, got 0x${frameType.toString(16)}`);
|
|
707
|
+
}
|
|
708
|
+
offset += SID_BYTES;
|
|
709
|
+
const rowCount = view.getUint16(offset, true);
|
|
710
|
+
offset += 2;
|
|
711
|
+
const rows = [];
|
|
712
|
+
for (let r = 0; r < rowCount; r++) {
|
|
713
|
+
const row = [];
|
|
714
|
+
for (let c = 0; c < columns.length; c++) {
|
|
715
|
+
const [value, bytesRead] = formatCellValue(view, offset, columns[c]);
|
|
716
|
+
row.push(value);
|
|
717
|
+
offset += bytesRead;
|
|
718
|
+
}
|
|
719
|
+
rows.push(row);
|
|
720
|
+
}
|
|
721
|
+
return rows;
|
|
722
|
+
}
|
|
723
|
+
function parseDelta(buf, columns) {
|
|
724
|
+
const view = new DataView(buf);
|
|
725
|
+
let offset = 0;
|
|
726
|
+
const op = view.getUint8(offset);
|
|
727
|
+
offset += 1;
|
|
728
|
+
offset += SID_BYTES;
|
|
729
|
+
if (op === FRAME_DELTA_DEL) {
|
|
730
|
+
const len = view.getUint16(offset, true);
|
|
731
|
+
offset += 2;
|
|
732
|
+
const bytes = new Uint8Array(buf, offset, len);
|
|
733
|
+
return { op, id: textDecoder.decode(bytes) };
|
|
734
|
+
}
|
|
735
|
+
if (op === FRAME_DELTA_INS || op === FRAME_DELTA_UPD) {
|
|
736
|
+
const rowData = [];
|
|
737
|
+
for (let c = 0; c < columns.length; c++) {
|
|
738
|
+
const [value, bytesRead] = formatCellValue(view, offset, columns[c]);
|
|
739
|
+
rowData.push(value);
|
|
740
|
+
offset += bytesRead;
|
|
741
|
+
}
|
|
742
|
+
return { op, rowData };
|
|
743
|
+
}
|
|
744
|
+
throw new Error(`Unknown delta op 0x${op.toString(16)}`);
|
|
745
|
+
}
|
|
746
|
+
function parseError(buf) {
|
|
747
|
+
const view = new DataView(buf);
|
|
748
|
+
const len = view.getUint16(HEAD, true);
|
|
749
|
+
const bytes = new Uint8Array(buf, HEAD + 2, len);
|
|
750
|
+
return textDecoder.decode(bytes);
|
|
751
|
+
}
|
|
752
|
+
function encodeSchemaCmd(sid, cart, entity) {
|
|
753
|
+
return JSON.stringify({ cmd: "schema", sid, cart, entity });
|
|
754
|
+
}
|
|
755
|
+
function encodeStreamCmd(sid, cart, entity, opts) {
|
|
756
|
+
return JSON.stringify({ cmd: "sub", sid, cart, entity, ...opts });
|
|
757
|
+
}
|
|
758
|
+
function encodeSearchCmd(sid, query) {
|
|
759
|
+
return JSON.stringify({ cmd: "search", sid, q: query });
|
|
760
|
+
}
|
|
761
|
+
function encodeClearSearchCmd(sid) {
|
|
762
|
+
return JSON.stringify({ cmd: "clear_search", sid });
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/hooks/use-bulk-stream.ts
|
|
766
|
+
var BulkStreamContext = createContext(null);
|
|
767
|
+
function base64UrlEncode(value) {
|
|
768
|
+
if (typeof btoa === "function") {
|
|
769
|
+
return btoa(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
770
|
+
}
|
|
771
|
+
throw new Error("No base64 encoder available for bulk WS auth");
|
|
772
|
+
}
|
|
773
|
+
function bulkAuthProtocols(token) {
|
|
774
|
+
return ["nimbit-bulk", `auth-${base64UrlEncode(token)}`];
|
|
775
|
+
}
|
|
776
|
+
var _wsTokenCache = null;
|
|
777
|
+
var WS_TOKEN_TTL_MS = 6e4;
|
|
778
|
+
async function fetchWsToken(signal, apiBaseUrl) {
|
|
779
|
+
const now = Date.now();
|
|
780
|
+
if (_wsTokenCache && now - _wsTokenCache.at < WS_TOKEN_TTL_MS) {
|
|
781
|
+
return _wsTokenCache.token;
|
|
782
|
+
}
|
|
783
|
+
const r = await fetch(`${apiBaseUrl}/api/auth/session/current`, {
|
|
784
|
+
method: "POST",
|
|
785
|
+
signal,
|
|
786
|
+
credentials: "include",
|
|
787
|
+
headers: { "content-type": "application/json" }
|
|
788
|
+
});
|
|
789
|
+
if (!r.ok) return null;
|
|
790
|
+
const j = await r.json();
|
|
791
|
+
const token = j.session?.token;
|
|
792
|
+
if (!token) return null;
|
|
793
|
+
_wsTokenCache = { token, at: now };
|
|
794
|
+
return token;
|
|
795
|
+
}
|
|
796
|
+
function invalidateWsToken() {
|
|
797
|
+
_wsTokenCache = null;
|
|
798
|
+
}
|
|
799
|
+
function notify(view) {
|
|
800
|
+
view.listeners.forEach((cb) => cb());
|
|
801
|
+
}
|
|
802
|
+
function BulkStreamProvider({ registry, children }) {
|
|
803
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
804
|
+
const wsRef = useRef(null);
|
|
805
|
+
const sendQueueRef = useRef([]);
|
|
806
|
+
const sidCounterRef = useRef(1);
|
|
807
|
+
const viewsBySidRef = useRef(/* @__PURE__ */ new Map());
|
|
808
|
+
const viewsByKeyRef = useRef(/* @__PURE__ */ new Map());
|
|
809
|
+
const preloadedRef = useRef(false);
|
|
810
|
+
const [connected, setConnected] = useState2(false);
|
|
811
|
+
const [error, setError] = useState2(null);
|
|
812
|
+
const sendCmd = (cmd) => {
|
|
813
|
+
const ws = wsRef.current;
|
|
814
|
+
if (ws && ws.readyState === WebSocket.OPEN) ws.send(cmd);
|
|
815
|
+
else sendQueueRef.current.push(cmd);
|
|
816
|
+
};
|
|
817
|
+
const flushQueue = () => {
|
|
818
|
+
const ws = wsRef.current;
|
|
819
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
820
|
+
const q = sendQueueRef.current;
|
|
821
|
+
sendQueueRef.current = [];
|
|
822
|
+
for (const cmd of q) ws.send(cmd);
|
|
823
|
+
};
|
|
824
|
+
const getView = (cart, entity) => {
|
|
825
|
+
const entLower = entity.toLowerCase();
|
|
826
|
+
const key = `${cart}/${entLower}`;
|
|
827
|
+
const existing = viewsByKeyRef.current.get(key);
|
|
828
|
+
if (existing) return existing;
|
|
829
|
+
const sid = sidCounterRef.current++;
|
|
830
|
+
const view = {
|
|
831
|
+
sid,
|
|
832
|
+
cart,
|
|
833
|
+
entity: entLower,
|
|
834
|
+
store: new BulkDataStore(),
|
|
835
|
+
columns: [],
|
|
836
|
+
totalRows: 0,
|
|
837
|
+
loadedRows: 0,
|
|
838
|
+
streaming: false,
|
|
839
|
+
error: null,
|
|
840
|
+
streamRequested: false,
|
|
841
|
+
searchStreaming: false,
|
|
842
|
+
onRender: null,
|
|
843
|
+
listeners: /* @__PURE__ */ new Set()
|
|
844
|
+
};
|
|
845
|
+
viewsByKeyRef.current.set(key, view);
|
|
846
|
+
viewsBySidRef.current.set(sid, view);
|
|
847
|
+
return view;
|
|
848
|
+
};
|
|
849
|
+
const ensureStreamed = (view) => {
|
|
850
|
+
if (view.streamRequested) return;
|
|
851
|
+
view.streamRequested = true;
|
|
852
|
+
sendCmd(encodeStreamCmd(view.sid, view.cart, view.entity));
|
|
853
|
+
};
|
|
854
|
+
const handleMessage = (ev) => {
|
|
855
|
+
const buf = ev.data;
|
|
856
|
+
const sid = getFrameSid(buf);
|
|
857
|
+
const view = viewsBySidRef.current.get(sid);
|
|
858
|
+
if (!view) return;
|
|
859
|
+
const ft = getFrameType(buf);
|
|
860
|
+
const store = view.store;
|
|
861
|
+
switch (ft) {
|
|
862
|
+
case FRAME_SCHEMA: {
|
|
863
|
+
const schema = parseSchema(buf);
|
|
864
|
+
if (view.searchStreaming) store.enterSearch(schema);
|
|
865
|
+
else store.applySchema(schema);
|
|
866
|
+
view.columns = schema.columns;
|
|
867
|
+
view.totalRows = schema.totalRows;
|
|
868
|
+
view.loadedRows = 0;
|
|
869
|
+
view.streaming = true;
|
|
870
|
+
notify(view);
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
case FRAME_DATA:
|
|
874
|
+
case FRAME_SEARCH_RESULT: {
|
|
875
|
+
const rows = parseDataChunk(buf, store.columns);
|
|
876
|
+
store.appendChunk(rows);
|
|
877
|
+
view.loadedRows = store.getRowCount();
|
|
878
|
+
view.onRender?.();
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
881
|
+
case FRAME_DATA_END:
|
|
882
|
+
view.streaming = false;
|
|
883
|
+
notify(view);
|
|
884
|
+
break;
|
|
885
|
+
case FRAME_SEARCH_END:
|
|
886
|
+
view.searchStreaming = false;
|
|
887
|
+
view.streaming = false;
|
|
888
|
+
notify(view);
|
|
889
|
+
break;
|
|
890
|
+
case FRAME_DELTA_INS:
|
|
891
|
+
case FRAME_DELTA_UPD:
|
|
892
|
+
case FRAME_DELTA_DEL: {
|
|
893
|
+
const delta = parseDelta(buf, store.columns);
|
|
894
|
+
store.applyDelta(delta);
|
|
895
|
+
view.totalRows = store.totalRows;
|
|
896
|
+
view.loadedRows = store.getRowCount();
|
|
897
|
+
view.onRender?.();
|
|
898
|
+
notify(view);
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
case FRAME_ERROR:
|
|
902
|
+
view.error = parseError(buf);
|
|
903
|
+
view.streaming = false;
|
|
904
|
+
notify(view);
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
useEffect3(() => {
|
|
909
|
+
const ac = new AbortController();
|
|
910
|
+
let cancelled = false;
|
|
911
|
+
let opened = false;
|
|
912
|
+
let ws = null;
|
|
913
|
+
setError(null);
|
|
914
|
+
(async () => {
|
|
915
|
+
let token = null;
|
|
916
|
+
try {
|
|
917
|
+
token = await fetchWsToken(ac.signal, apiBaseUrl);
|
|
918
|
+
} catch {
|
|
919
|
+
if (!cancelled) setError("Failed to obtain WS session token");
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
if (cancelled) return;
|
|
923
|
+
if (!token) {
|
|
924
|
+
setError("No session \u2014 sign in to view data");
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
ws = new WebSocket(`${wsBaseFrom(apiBaseUrl)}/_ws/bulk`, bulkAuthProtocols(token));
|
|
928
|
+
ws.binaryType = "arraybuffer";
|
|
929
|
+
wsRef.current = ws;
|
|
930
|
+
ws.onopen = () => {
|
|
931
|
+
if (cancelled || wsRef.current !== ws) return;
|
|
932
|
+
opened = true;
|
|
933
|
+
setConnected(true);
|
|
934
|
+
setError(null);
|
|
935
|
+
flushQueue();
|
|
936
|
+
};
|
|
937
|
+
ws.onmessage = handleMessage;
|
|
938
|
+
ws.onerror = () => {
|
|
939
|
+
};
|
|
940
|
+
ws.onclose = () => {
|
|
941
|
+
if (cancelled || wsRef.current !== ws) return;
|
|
942
|
+
setConnected(false);
|
|
943
|
+
wsRef.current = null;
|
|
944
|
+
if (!opened) {
|
|
945
|
+
invalidateWsToken();
|
|
946
|
+
setError("WebSocket connection closed");
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
})();
|
|
950
|
+
return () => {
|
|
951
|
+
cancelled = true;
|
|
952
|
+
ac.abort();
|
|
953
|
+
if (ws) {
|
|
954
|
+
ws.onopen = ws.onmessage = ws.onerror = ws.onclose = null;
|
|
955
|
+
try {
|
|
956
|
+
ws.close();
|
|
957
|
+
} catch {
|
|
958
|
+
}
|
|
959
|
+
if (wsRef.current === ws) wsRef.current = null;
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}, []);
|
|
963
|
+
useEffect3(() => {
|
|
964
|
+
if (!connected) return;
|
|
965
|
+
if (preloadedRef.current) return;
|
|
966
|
+
const keys = Object.keys(registry);
|
|
967
|
+
if (keys.length === 0) return;
|
|
968
|
+
preloadedRef.current = true;
|
|
969
|
+
for (const cart of keys) {
|
|
970
|
+
for (const ent of registry[cart] ?? []) {
|
|
971
|
+
const view = getView(cart, ent.name);
|
|
972
|
+
sendCmd(encodeSchemaCmd(view.sid, view.cart, view.entity));
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}, [connected, registry]);
|
|
976
|
+
const search = (view, query) => {
|
|
977
|
+
if (!query) {
|
|
978
|
+
view.store.exitSearch();
|
|
979
|
+
view.totalRows = view.store.totalRows;
|
|
980
|
+
view.loadedRows = view.store.getRowCount();
|
|
981
|
+
sendCmd(encodeClearSearchCmd(view.sid));
|
|
982
|
+
notify(view);
|
|
983
|
+
view.onRender?.();
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
view.searchStreaming = true;
|
|
987
|
+
sendCmd(encodeSearchCmd(view.sid, query));
|
|
988
|
+
};
|
|
989
|
+
const clearSearch = (view) => {
|
|
990
|
+
view.store.exitSearch();
|
|
991
|
+
view.totalRows = view.store.totalRows;
|
|
992
|
+
view.loadedRows = view.store.getRowCount();
|
|
993
|
+
sendCmd(encodeClearSearchCmd(view.sid));
|
|
994
|
+
notify(view);
|
|
995
|
+
view.onRender?.();
|
|
996
|
+
};
|
|
997
|
+
const subscribe = (view, cb) => {
|
|
998
|
+
view.listeners.add(cb);
|
|
999
|
+
return () => {
|
|
1000
|
+
view.listeners.delete(cb);
|
|
1001
|
+
};
|
|
1002
|
+
};
|
|
1003
|
+
const ctx = {
|
|
1004
|
+
getView,
|
|
1005
|
+
ensureStreamed,
|
|
1006
|
+
search,
|
|
1007
|
+
clearSearch,
|
|
1008
|
+
subscribe,
|
|
1009
|
+
connected,
|
|
1010
|
+
error
|
|
1011
|
+
};
|
|
1012
|
+
return React4.createElement(BulkStreamContext.Provider, { value: ctx }, children);
|
|
1013
|
+
}
|
|
1014
|
+
function useBulkRowCounts(registry) {
|
|
1015
|
+
const ctx = useContext(BulkStreamContext);
|
|
1016
|
+
if (!ctx) throw new Error("useBulkRowCounts must be used within BulkStreamProvider");
|
|
1017
|
+
const [, force] = useState2(0);
|
|
1018
|
+
const pairs = [];
|
|
1019
|
+
for (const cart of Object.keys(registry)) {
|
|
1020
|
+
for (const ent of registry[cart] ?? []) {
|
|
1021
|
+
pairs.push({ id: `${cart}:${ent.name}`, view: ctx.getView(cart, ent.name) });
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
const key = pairs.map((p) => p.id).join("|");
|
|
1025
|
+
useEffect3(() => {
|
|
1026
|
+
const unsubs = pairs.map((p) => ctx.subscribe(p.view, () => force((n) => n + 1)));
|
|
1027
|
+
return () => unsubs.forEach((u) => u());
|
|
1028
|
+
}, [key]);
|
|
1029
|
+
const counts = {};
|
|
1030
|
+
for (const p of pairs) counts[p.id] = p.view.totalRows;
|
|
1031
|
+
return counts;
|
|
1032
|
+
}
|
|
1033
|
+
function useBulkSubscription(cart, entity) {
|
|
1034
|
+
const ctx = useContext(BulkStreamContext);
|
|
1035
|
+
if (!ctx) throw new Error("useBulkSubscription must be used within BulkStreamProvider");
|
|
1036
|
+
const view = ctx.getView(cart, entity);
|
|
1037
|
+
const [, force] = useState2(0);
|
|
1038
|
+
useEffect3(() => {
|
|
1039
|
+
const unsub = ctx.subscribe(view, () => force((n) => n + 1));
|
|
1040
|
+
ctx.ensureStreamed(view);
|
|
1041
|
+
return unsub;
|
|
1042
|
+
}, [view]);
|
|
1043
|
+
return {
|
|
1044
|
+
store: view.store,
|
|
1045
|
+
connected: ctx.connected,
|
|
1046
|
+
streaming: view.streaming,
|
|
1047
|
+
error: view.error ?? ctx.error,
|
|
1048
|
+
columns: view.columns,
|
|
1049
|
+
totalRows: view.totalRows,
|
|
1050
|
+
loadedRows: view.loadedRows,
|
|
1051
|
+
search: (q) => ctx.search(view, q),
|
|
1052
|
+
clearSearch: () => ctx.clearSearch(view),
|
|
1053
|
+
setOnRender: (fn) => {
|
|
1054
|
+
view.onRender = fn;
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/components/devtools/data-browser/data-table.tsx
|
|
1060
|
+
import React5 from "react";
|
|
1061
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1062
|
+
var ROW_H = 22;
|
|
1063
|
+
var OVERSCAN = 6;
|
|
1064
|
+
var MIN_COL_W = 80;
|
|
1065
|
+
function colWidth(name) {
|
|
1066
|
+
if (name === "id") return 220;
|
|
1067
|
+
if (name === "createdAt" || name === "updatedAt") return 180;
|
|
1068
|
+
if (name === "email" || name === "phone") return 180;
|
|
1069
|
+
if (name.endsWith("At")) return 180;
|
|
1070
|
+
return Math.max(MIN_COL_W, Math.min(240, name.length * 9 + 24));
|
|
1071
|
+
}
|
|
1072
|
+
var DataTable = ({ cart, entity, searchFields, onSelectRow }) => {
|
|
1073
|
+
const stream = useBulkSubscription(cart, entity);
|
|
1074
|
+
const scrollerRef = React5.useRef(null);
|
|
1075
|
+
const [scrollTop, setScrollTop] = React5.useState(0);
|
|
1076
|
+
const [viewportH, setViewportH] = React5.useState(0);
|
|
1077
|
+
const [query, setQuery] = React5.useState("");
|
|
1078
|
+
const [tick, setTick] = React5.useState(0);
|
|
1079
|
+
React5.useEffect(() => {
|
|
1080
|
+
stream.setOnRender(() => setTick((n) => n + 1));
|
|
1081
|
+
return () => stream.setOnRender(null);
|
|
1082
|
+
}, [stream]);
|
|
1083
|
+
React5.useEffect(() => {
|
|
1084
|
+
const el = scrollerRef.current;
|
|
1085
|
+
if (!el) return;
|
|
1086
|
+
const ro = new ResizeObserver(() => setViewportH(el.clientHeight));
|
|
1087
|
+
ro.observe(el);
|
|
1088
|
+
setViewportH(el.clientHeight);
|
|
1089
|
+
return () => ro.disconnect();
|
|
1090
|
+
}, []);
|
|
1091
|
+
const rows = stream.store.rows;
|
|
1092
|
+
const columns = stream.columns;
|
|
1093
|
+
const totalH = rows.length * ROW_H;
|
|
1094
|
+
const start = Math.max(0, Math.floor(scrollTop / ROW_H) - OVERSCAN);
|
|
1095
|
+
const visibleCount = Math.max(0, Math.ceil(viewportH / ROW_H) + OVERSCAN * 2);
|
|
1096
|
+
const end = Math.min(rows.length, start + visibleCount);
|
|
1097
|
+
const onSearchKey = (e) => {
|
|
1098
|
+
if (e.key === "Enter") {
|
|
1099
|
+
stream.search(query.trim());
|
|
1100
|
+
} else if (e.key === "Escape") {
|
|
1101
|
+
setQuery("");
|
|
1102
|
+
stream.clearSearch();
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
const handleRowClick = (rowIdx) => {
|
|
1106
|
+
const r = rows[rowIdx];
|
|
1107
|
+
if (!r) return;
|
|
1108
|
+
const out = {};
|
|
1109
|
+
for (let i = 0; i < columns.length; i++) out[columns[i].name] = r[i] ?? "";
|
|
1110
|
+
onSelectRow(out);
|
|
1111
|
+
};
|
|
1112
|
+
const searchDisabled = searchFields.length === 0;
|
|
1113
|
+
const totalColW = columns.reduce((s, c) => s + colWidth(c.name), 0);
|
|
1114
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex h-full flex-col bg-background", children: [
|
|
1115
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex h-7 shrink-0 items-center gap-2 border-b border-border px-2", children: [
|
|
1116
|
+
/* @__PURE__ */ jsx5(
|
|
1117
|
+
"input",
|
|
1118
|
+
{
|
|
1119
|
+
type: "text",
|
|
1120
|
+
value: query,
|
|
1121
|
+
disabled: searchDisabled,
|
|
1122
|
+
onChange: (e) => setQuery(e.target.value),
|
|
1123
|
+
onKeyDown: onSearchKey,
|
|
1124
|
+
placeholder: searchDisabled ? "Search disabled (no @@search declared)" : `Search ${searchFields.join(", ")} \u23CE`,
|
|
1125
|
+
className: cn(
|
|
1126
|
+
"h-5 flex-1 rounded-sm border border-border bg-background px-2 text-[12px] outline-none",
|
|
1127
|
+
"placeholder:text-muted-foreground/70 focus:border-accent-foreground/30",
|
|
1128
|
+
searchDisabled && "opacity-50 cursor-not-allowed"
|
|
1129
|
+
)
|
|
1130
|
+
}
|
|
1131
|
+
),
|
|
1132
|
+
/* @__PURE__ */ jsx5("div", { className: "shrink-0 text-[11px] text-muted-foreground tabular-nums", children: stream.error ? /* @__PURE__ */ jsx5("span", { className: "text-red-400", children: stream.error }) : !stream.connected ? /* @__PURE__ */ jsx5("span", { children: "connecting\u2026" }) : /* @__PURE__ */ jsxs2("span", { children: [
|
|
1133
|
+
stream.loadedRows.toLocaleString(),
|
|
1134
|
+
" / ",
|
|
1135
|
+
stream.totalRows.toLocaleString(),
|
|
1136
|
+
" rows",
|
|
1137
|
+
stream.streaming ? " \xB7 streaming" : ""
|
|
1138
|
+
] }) })
|
|
1139
|
+
] }),
|
|
1140
|
+
columns.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "flex flex-1 items-center justify-center text-[12px] text-muted-foreground", children: stream.error ? stream.error : "Waiting for schema\u2026" }) : /* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 flex-1 flex-col", children: [
|
|
1141
|
+
/* @__PURE__ */ jsx5("div", { className: "overflow-hidden border-b border-border bg-muted/30", children: /* @__PURE__ */ jsx5(
|
|
1142
|
+
"div",
|
|
1143
|
+
{
|
|
1144
|
+
className: "flex h-6 select-none text-[11px] uppercase tracking-wider text-muted-foreground",
|
|
1145
|
+
style: {
|
|
1146
|
+
width: totalColW,
|
|
1147
|
+
transform: `translateX(${-(scrollerRef.current?.scrollLeft ?? 0)}px)`
|
|
1148
|
+
},
|
|
1149
|
+
children: columns.map((c) => /* @__PURE__ */ jsx5(
|
|
1150
|
+
"div",
|
|
1151
|
+
{
|
|
1152
|
+
style: { width: colWidth(c.name) },
|
|
1153
|
+
className: "flex items-center overflow-hidden border-r border-border px-2",
|
|
1154
|
+
children: /* @__PURE__ */ jsx5("span", { className: "truncate", children: c.name })
|
|
1155
|
+
},
|
|
1156
|
+
c.name
|
|
1157
|
+
))
|
|
1158
|
+
}
|
|
1159
|
+
) }),
|
|
1160
|
+
/* @__PURE__ */ jsx5(
|
|
1161
|
+
"div",
|
|
1162
|
+
{
|
|
1163
|
+
ref: scrollerRef,
|
|
1164
|
+
onScroll: (e) => {
|
|
1165
|
+
setScrollTop(e.target.scrollTop);
|
|
1166
|
+
setTick((n) => n + 1);
|
|
1167
|
+
},
|
|
1168
|
+
className: "relative min-h-0 flex-1 overflow-auto font-mono",
|
|
1169
|
+
children: /* @__PURE__ */ jsx5("div", { style: { height: totalH, width: totalColW, position: "relative" }, children: rows.slice(start, end).map((row, i) => {
|
|
1170
|
+
const rowIdx = start + i;
|
|
1171
|
+
return /* @__PURE__ */ jsx5(
|
|
1172
|
+
"div",
|
|
1173
|
+
{
|
|
1174
|
+
onClick: () => handleRowClick(rowIdx),
|
|
1175
|
+
style: {
|
|
1176
|
+
top: rowIdx * ROW_H,
|
|
1177
|
+
height: ROW_H,
|
|
1178
|
+
width: totalColW
|
|
1179
|
+
},
|
|
1180
|
+
className: cn(
|
|
1181
|
+
"absolute left-0 flex cursor-pointer text-[12px] leading-none",
|
|
1182
|
+
rowIdx % 2 === 0 ? "bg-background" : "bg-muted/20",
|
|
1183
|
+
"hover:bg-accent hover:text-accent-foreground"
|
|
1184
|
+
),
|
|
1185
|
+
children: columns.map((c, ci) => /* @__PURE__ */ jsx5(
|
|
1186
|
+
"div",
|
|
1187
|
+
{
|
|
1188
|
+
style: { width: colWidth(c.name) },
|
|
1189
|
+
className: "flex items-center overflow-hidden border-r border-border/60 px-2",
|
|
1190
|
+
title: row[ci],
|
|
1191
|
+
children: /* @__PURE__ */ jsx5("span", { className: "truncate", children: row[ci] })
|
|
1192
|
+
},
|
|
1193
|
+
c.name
|
|
1194
|
+
))
|
|
1195
|
+
},
|
|
1196
|
+
rowIdx
|
|
1197
|
+
);
|
|
1198
|
+
}) })
|
|
1199
|
+
}
|
|
1200
|
+
)
|
|
1201
|
+
] }),
|
|
1202
|
+
tick < 0 && /* @__PURE__ */ jsx5("span", {})
|
|
1203
|
+
] });
|
|
1204
|
+
};
|
|
1205
|
+
var data_table_default = DataTable;
|
|
1206
|
+
|
|
1207
|
+
// src/components/devtools/entity-graph/diagram-tab.tsx
|
|
1208
|
+
import React6 from "react";
|
|
1209
|
+
import {
|
|
1210
|
+
Background,
|
|
1211
|
+
BackgroundVariant,
|
|
1212
|
+
Controls,
|
|
1213
|
+
MarkerType,
|
|
1214
|
+
ReactFlow,
|
|
1215
|
+
ReactFlowProvider,
|
|
1216
|
+
useNodesInitialized,
|
|
1217
|
+
useReactFlow
|
|
1218
|
+
} from "@xyflow/react";
|
|
1219
|
+
import { Database as Database2, Eye, EyeOff, GitBranch, Rows3 } from "lucide-react";
|
|
1220
|
+
|
|
1221
|
+
// src/components/devtools/entity-graph/entity-node.tsx
|
|
1222
|
+
import { Handle, Position } from "@xyflow/react";
|
|
1223
|
+
import { Database, FileText, KeyRound, Link2 } from "lucide-react";
|
|
1224
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1225
|
+
function EntityNode({ data, selected }) {
|
|
1226
|
+
const node = data;
|
|
1227
|
+
const highlight = node.highlight;
|
|
1228
|
+
const highlightedFields = new Set(highlight?.fields ?? []);
|
|
1229
|
+
const hiddenHandleClass = "!h-1 !w-1 !border-0 !bg-transparent !opacity-0";
|
|
1230
|
+
return /* @__PURE__ */ jsxs3(
|
|
1231
|
+
"div",
|
|
1232
|
+
{
|
|
1233
|
+
className: [
|
|
1234
|
+
"relative w-[260px] overflow-visible rounded-xl border bg-zinc-900 font-mono text-[11px] text-zinc-100 shadow-lg shadow-black/40 transition-opacity",
|
|
1235
|
+
highlight?.focused ? "border-blue-500 ring-2 ring-blue-500/30" : highlight?.connected ? "border-blue-400/60 ring-2 ring-blue-500/10" : "border-zinc-700",
|
|
1236
|
+
highlight?.dimmed ? "opacity-35" : "",
|
|
1237
|
+
selected && !highlight?.focused ? "ring-2 ring-zinc-100/10" : ""
|
|
1238
|
+
].join(" "),
|
|
1239
|
+
children: [
|
|
1240
|
+
/* @__PURE__ */ jsxs3("div", { className: "rounded-t-xl border-b border-zinc-700 bg-zinc-800 px-3 py-2", children: [
|
|
1241
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1242
|
+
/* @__PURE__ */ jsx6(Database, { className: "h-3.5 w-3.5 shrink-0 text-zinc-400" }),
|
|
1243
|
+
/* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate text-[13px] font-semibold leading-none text-zinc-50", children: node.entity }),
|
|
1244
|
+
/* @__PURE__ */ jsx6("span", { className: "shrink-0 rounded bg-zinc-700 px-1.5 py-0.5 text-[10px] font-medium tabular-nums text-zinc-200", children: node.fieldCount })
|
|
1245
|
+
] }),
|
|
1246
|
+
/* @__PURE__ */ jsx6("div", { className: "mt-1 truncate text-[10px] text-zinc-500", children: node.cartridge })
|
|
1247
|
+
] }),
|
|
1248
|
+
/* @__PURE__ */ jsx6("div", { className: "bg-zinc-900 py-1", children: node.fields.length === 0 ? /* @__PURE__ */ jsx6("div", { className: "px-3 py-2 text-[10px] text-zinc-500", children: "No fields" }) : node.fields.map((field) => {
|
|
1249
|
+
const type = `${field.type}${field.array ? "[]" : ""}${field.optional ? "?" : ""}`;
|
|
1250
|
+
const isId = field.displayName.toLowerCase() === "id";
|
|
1251
|
+
const isRelation = field.kind === "relation";
|
|
1252
|
+
const isDocument = field.kind === "document";
|
|
1253
|
+
const fieldHighlighted = highlightedFields.has(field.displayName);
|
|
1254
|
+
return /* @__PURE__ */ jsxs3(
|
|
1255
|
+
"div",
|
|
1256
|
+
{
|
|
1257
|
+
className: [
|
|
1258
|
+
"relative grid min-h-[26px] grid-cols-[minmax(0,1fr)_auto] items-center gap-3 px-3 py-1 text-zinc-300",
|
|
1259
|
+
fieldHighlighted ? "bg-blue-500/15 text-blue-200" : ""
|
|
1260
|
+
].join(" "),
|
|
1261
|
+
children: [
|
|
1262
|
+
isId ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1263
|
+
/* @__PURE__ */ jsx6(
|
|
1264
|
+
Handle,
|
|
1265
|
+
{
|
|
1266
|
+
id: `target-${field.displayName}-left`,
|
|
1267
|
+
type: "target",
|
|
1268
|
+
position: Position.Left,
|
|
1269
|
+
className: `!left-0 ${hiddenHandleClass}`,
|
|
1270
|
+
style: { top: "50%" }
|
|
1271
|
+
}
|
|
1272
|
+
),
|
|
1273
|
+
/* @__PURE__ */ jsx6(
|
|
1274
|
+
Handle,
|
|
1275
|
+
{
|
|
1276
|
+
id: `target-${field.displayName}-right`,
|
|
1277
|
+
type: "target",
|
|
1278
|
+
position: Position.Right,
|
|
1279
|
+
className: `!right-0 ${hiddenHandleClass}`,
|
|
1280
|
+
style: { top: "50%" }
|
|
1281
|
+
}
|
|
1282
|
+
)
|
|
1283
|
+
] }) : null,
|
|
1284
|
+
isRelation ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1285
|
+
/* @__PURE__ */ jsx6(
|
|
1286
|
+
Handle,
|
|
1287
|
+
{
|
|
1288
|
+
id: `source-${field.displayName}-left`,
|
|
1289
|
+
type: "source",
|
|
1290
|
+
position: Position.Left,
|
|
1291
|
+
className: `!left-0 ${hiddenHandleClass}`,
|
|
1292
|
+
style: { top: "50%" }
|
|
1293
|
+
}
|
|
1294
|
+
),
|
|
1295
|
+
/* @__PURE__ */ jsx6(
|
|
1296
|
+
Handle,
|
|
1297
|
+
{
|
|
1298
|
+
id: `source-${field.displayName}-right`,
|
|
1299
|
+
type: "source",
|
|
1300
|
+
position: Position.Right,
|
|
1301
|
+
className: `!right-0 ${hiddenHandleClass}`,
|
|
1302
|
+
style: { top: "50%" }
|
|
1303
|
+
}
|
|
1304
|
+
)
|
|
1305
|
+
] }) : null,
|
|
1306
|
+
/* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
1307
|
+
isId ? /* @__PURE__ */ jsx6(KeyRound, { className: "h-3 w-3 shrink-0 text-zinc-500" }) : null,
|
|
1308
|
+
isRelation ? /* @__PURE__ */ jsx6(Link2, { className: ["h-3 w-3 shrink-0", fieldHighlighted ? "text-blue-300" : "text-blue-400"].join(" ") }) : null,
|
|
1309
|
+
isDocument ? /* @__PURE__ */ jsx6(FileText, { className: "h-3 w-3 shrink-0 text-zinc-500" }) : null,
|
|
1310
|
+
/* @__PURE__ */ jsx6("span", { className: "truncate", children: field.displayName })
|
|
1311
|
+
] }),
|
|
1312
|
+
/* @__PURE__ */ jsx6("span", { className: "max-w-[96px] truncate text-right text-zinc-500", children: type })
|
|
1313
|
+
]
|
|
1314
|
+
},
|
|
1315
|
+
`${field.name}:${field.displayName}`
|
|
1316
|
+
);
|
|
1317
|
+
}) }),
|
|
1318
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between gap-2 rounded-b-xl border-t border-zinc-700 bg-zinc-800 px-3 py-1.5 text-[10px] text-zinc-500", children: [
|
|
1319
|
+
/* @__PURE__ */ jsxs3("span", { className: "tabular-nums", children: [
|
|
1320
|
+
node.rowCount.toLocaleString(),
|
|
1321
|
+
" rows"
|
|
1322
|
+
] }),
|
|
1323
|
+
/* @__PURE__ */ jsxs3("span", { className: "tabular-nums", children: [
|
|
1324
|
+
node.relationCount,
|
|
1325
|
+
" rel \xB7 ",
|
|
1326
|
+
node.scalarCount,
|
|
1327
|
+
" scalar"
|
|
1328
|
+
] })
|
|
1329
|
+
] })
|
|
1330
|
+
]
|
|
1331
|
+
}
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// src/components/devtools/entity-graph/entity-graph-utils.ts
|
|
1336
|
+
import dagre from "@dagrejs/dagre";
|
|
1337
|
+
var NODE_WIDTH = 260;
|
|
1338
|
+
var MIN_NODE_HEIGHT = 150;
|
|
1339
|
+
var FIELD_ROW_HEIGHT = 26;
|
|
1340
|
+
var COLUMN_WIDTH = 390;
|
|
1341
|
+
function cartsFromContracts(contracts) {
|
|
1342
|
+
const carts = [];
|
|
1343
|
+
for (const c of contracts) {
|
|
1344
|
+
if (!c.cartridge || !c.owns) continue;
|
|
1345
|
+
const entities = [];
|
|
1346
|
+
for (const [name, ent] of Object.entries(c.owns)) {
|
|
1347
|
+
const fields = (ent.fields ?? []).map((f) => ({
|
|
1348
|
+
name: f.name,
|
|
1349
|
+
type: f.type,
|
|
1350
|
+
optional: f.optional,
|
|
1351
|
+
array: f.array,
|
|
1352
|
+
kind: f.kind,
|
|
1353
|
+
target: f.target,
|
|
1354
|
+
targetCart: f.target_cart,
|
|
1355
|
+
relationKind: f.relation_kind,
|
|
1356
|
+
fkField: f.fk_field
|
|
1357
|
+
}));
|
|
1358
|
+
entities.push({ name, fields });
|
|
1359
|
+
}
|
|
1360
|
+
if (entities.length > 0) carts.push({ name: c.cartridge, entities });
|
|
1361
|
+
}
|
|
1362
|
+
return carts;
|
|
1363
|
+
}
|
|
1364
|
+
function entityGraphId(cartridge, entity) {
|
|
1365
|
+
return `${cartridge}:${entity}`;
|
|
1366
|
+
}
|
|
1367
|
+
function fieldKind(field) {
|
|
1368
|
+
return field.kind ?? (field.target ? "relation" : "scalar");
|
|
1369
|
+
}
|
|
1370
|
+
function graphField(field) {
|
|
1371
|
+
const kind = fieldKind(field);
|
|
1372
|
+
return {
|
|
1373
|
+
name: field.name,
|
|
1374
|
+
displayName: kind === "relation" ? field.fkField ?? field.name : field.name,
|
|
1375
|
+
type: kind === "relation" && field.target ? field.target : field.type,
|
|
1376
|
+
kind,
|
|
1377
|
+
optional: field.optional === true,
|
|
1378
|
+
array: field.array === true,
|
|
1379
|
+
target: field.target,
|
|
1380
|
+
targetCart: field.targetCart,
|
|
1381
|
+
relationKind: field.relationKind,
|
|
1382
|
+
fkField: field.fkField
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
function makeGraphFields(fields) {
|
|
1386
|
+
const out = fields.map(graphField);
|
|
1387
|
+
if (!out.some((field) => field.displayName.toLowerCase() === "id")) {
|
|
1388
|
+
out.unshift({
|
|
1389
|
+
name: "id",
|
|
1390
|
+
displayName: "id",
|
|
1391
|
+
type: "id",
|
|
1392
|
+
kind: "scalar",
|
|
1393
|
+
optional: false,
|
|
1394
|
+
array: false,
|
|
1395
|
+
implicit: true
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
return out;
|
|
1399
|
+
}
|
|
1400
|
+
function normalizeName(value) {
|
|
1401
|
+
return value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
1402
|
+
}
|
|
1403
|
+
function words(value) {
|
|
1404
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).map((part) => part.toLowerCase()).filter(Boolean);
|
|
1405
|
+
}
|
|
1406
|
+
function entityAliases(entity) {
|
|
1407
|
+
const parts = words(entity);
|
|
1408
|
+
const aliases = /* @__PURE__ */ new Set([normalizeName(entity)]);
|
|
1409
|
+
const last = parts.length > 0 ? parts[parts.length - 1] : void 0;
|
|
1410
|
+
if (last) aliases.add(last);
|
|
1411
|
+
if (parts.length > 1 && last) {
|
|
1412
|
+
aliases.add(`${parts.slice(0, -1).map((part) => part[0]).join("")}${last}`);
|
|
1413
|
+
aliases.add(parts.map((part) => part[0]).join(""));
|
|
1414
|
+
}
|
|
1415
|
+
return [...aliases].filter((alias) => alias.length >= 2);
|
|
1416
|
+
}
|
|
1417
|
+
function inferTargetNode(fieldName, nodes) {
|
|
1418
|
+
if (!/id$/i.test(fieldName) || /^id$/i.test(fieldName)) return null;
|
|
1419
|
+
const base = normalizeName(fieldName.replace(/id$/i, ""));
|
|
1420
|
+
if (base.length < 3) return null;
|
|
1421
|
+
const candidates = nodes.flatMap(
|
|
1422
|
+
(node) => entityAliases(node.entity).map((alias) => {
|
|
1423
|
+
const exact = base === alias;
|
|
1424
|
+
const suffix = !exact && alias.length >= 4 && base.endsWith(alias);
|
|
1425
|
+
if (!exact && !suffix) return null;
|
|
1426
|
+
return {
|
|
1427
|
+
node,
|
|
1428
|
+
score: (exact ? 1e3 : 500) + alias.length
|
|
1429
|
+
};
|
|
1430
|
+
})
|
|
1431
|
+
).filter((candidate) => candidate !== null).sort((a, b) => b.score - a.score || a.node.entity.localeCompare(b.node.entity));
|
|
1432
|
+
const first = candidates[0];
|
|
1433
|
+
if (!first) return null;
|
|
1434
|
+
const second = candidates[1];
|
|
1435
|
+
if (second && first.score === second.score) return null;
|
|
1436
|
+
return first.node;
|
|
1437
|
+
}
|
|
1438
|
+
function markRelationField(node, fieldName, target) {
|
|
1439
|
+
const field = node?.fields.find(
|
|
1440
|
+
(candidate) => candidate.displayName === fieldName || candidate.name === fieldName
|
|
1441
|
+
);
|
|
1442
|
+
if (!field || field.displayName.toLowerCase() === "id") return;
|
|
1443
|
+
field.kind = "relation";
|
|
1444
|
+
field.target = target.entity;
|
|
1445
|
+
field.targetCart = target.cartridge;
|
|
1446
|
+
field.relationKind ?? (field.relationKind = "inferred");
|
|
1447
|
+
field.fkField ?? (field.fkField = fieldName);
|
|
1448
|
+
}
|
|
1449
|
+
function recomputeNodeCounts(nodes) {
|
|
1450
|
+
for (const node of nodes) {
|
|
1451
|
+
node.scalarCount = node.fields.filter((field) => field.kind === "scalar").length;
|
|
1452
|
+
node.relationCount = node.fields.filter((field) => field.kind === "relation").length;
|
|
1453
|
+
node.documentCount = node.fields.filter((field) => field.kind === "document").length;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
function nodeHeight(node) {
|
|
1457
|
+
return Math.max(MIN_NODE_HEIGHT, 74 + node.fields.length * FIELD_ROW_HEIGHT);
|
|
1458
|
+
}
|
|
1459
|
+
function layoutNodes(nodes, edges) {
|
|
1460
|
+
const degree = new Map(nodes.map((n) => [n.id, 0]));
|
|
1461
|
+
for (const edge of edges) {
|
|
1462
|
+
degree.set(edge.source, (degree.get(edge.source) ?? 0) + 1);
|
|
1463
|
+
degree.set(edge.target, (degree.get(edge.target) ?? 0) + 1);
|
|
1464
|
+
}
|
|
1465
|
+
const connected = nodes.filter((n) => (degree.get(n.id) ?? 0) > 0);
|
|
1466
|
+
const isolated = nodes.filter((n) => (degree.get(n.id) ?? 0) === 0);
|
|
1467
|
+
const connectedIds = new Set(connected.map((n) => n.id));
|
|
1468
|
+
let maxX = 0;
|
|
1469
|
+
let maxY = 0;
|
|
1470
|
+
if (connected.length > 0) {
|
|
1471
|
+
const g = new dagre.graphlib.Graph();
|
|
1472
|
+
g.setGraph({ rankdir: "LR", ranksep: 130, nodesep: 36, marginx: 40, marginy: 40 });
|
|
1473
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
1474
|
+
for (const node of connected) {
|
|
1475
|
+
g.setNode(node.id, { width: NODE_WIDTH, height: nodeHeight(node) });
|
|
1476
|
+
}
|
|
1477
|
+
for (const edge of edges) {
|
|
1478
|
+
if (edge.source === edge.target) continue;
|
|
1479
|
+
if (!connectedIds.has(edge.source) || !connectedIds.has(edge.target)) continue;
|
|
1480
|
+
g.setEdge(edge.source, edge.target);
|
|
1481
|
+
}
|
|
1482
|
+
dagre.layout(g);
|
|
1483
|
+
for (const node of connected) {
|
|
1484
|
+
const p = g.node(node.id);
|
|
1485
|
+
const h = nodeHeight(node);
|
|
1486
|
+
node.position = { x: p.x - NODE_WIDTH / 2, y: p.y - h / 2 };
|
|
1487
|
+
maxX = Math.max(maxX, node.position.x + NODE_WIDTH);
|
|
1488
|
+
maxY = Math.max(maxY, node.position.y + h);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (isolated.length > 0) {
|
|
1492
|
+
isolated.sort((a, b) => a.entity.localeCompare(b.entity));
|
|
1493
|
+
const startX = connected.length > 0 ? maxX + COLUMN_WIDTH : 0;
|
|
1494
|
+
const perColumn = Math.max(1, Math.ceil(isolated.length / Math.ceil(isolated.length / 6)));
|
|
1495
|
+
let x = startX;
|
|
1496
|
+
let y = 0;
|
|
1497
|
+
let inColumn = 0;
|
|
1498
|
+
for (const node of isolated) {
|
|
1499
|
+
node.position = { x, y };
|
|
1500
|
+
y += nodeHeight(node) + 48;
|
|
1501
|
+
inColumn += 1;
|
|
1502
|
+
if (inColumn >= perColumn) {
|
|
1503
|
+
inColumn = 0;
|
|
1504
|
+
y = 0;
|
|
1505
|
+
x += COLUMN_WIDTH;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
function makeTotals(nodes, edges) {
|
|
1511
|
+
return {
|
|
1512
|
+
entities: nodes.length,
|
|
1513
|
+
relationships: edges.length,
|
|
1514
|
+
rows: nodes.reduce((sum, node) => sum + node.rowCount, 0)
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
function buildEntityGraphModel(cartridges) {
|
|
1518
|
+
const sortedCarts = [...cartridges].sort((a, b) => a.name.localeCompare(b.name));
|
|
1519
|
+
const nodes = [];
|
|
1520
|
+
sortedCarts.forEach((cart, cartIndex) => {
|
|
1521
|
+
const entities = [...cart.entities].sort((a, b) => a.name.localeCompare(b.name));
|
|
1522
|
+
let y = 0;
|
|
1523
|
+
entities.forEach((entity) => {
|
|
1524
|
+
const id = entityGraphId(cart.name, entity.name);
|
|
1525
|
+
const fields = entity.fields ?? [];
|
|
1526
|
+
const graphFields = makeGraphFields(fields);
|
|
1527
|
+
nodes.push({
|
|
1528
|
+
id,
|
|
1529
|
+
cartridge: cart.name,
|
|
1530
|
+
entity: entity.name,
|
|
1531
|
+
fields: graphFields,
|
|
1532
|
+
fieldCount: graphFields.length,
|
|
1533
|
+
scalarCount: graphFields.filter((field) => field.kind === "scalar").length,
|
|
1534
|
+
relationCount: graphFields.filter((field) => field.kind === "relation").length,
|
|
1535
|
+
documentCount: graphFields.filter((field) => field.kind === "document").length,
|
|
1536
|
+
rowCount: 0,
|
|
1537
|
+
position: { x: cartIndex * COLUMN_WIDTH, y }
|
|
1538
|
+
});
|
|
1539
|
+
y += Math.max(MIN_NODE_HEIGHT, 74 + graphFields.length * FIELD_ROW_HEIGHT) + 56;
|
|
1540
|
+
});
|
|
1541
|
+
});
|
|
1542
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
1543
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
1544
|
+
const edges = [];
|
|
1545
|
+
const edgeKeys = /* @__PURE__ */ new Set();
|
|
1546
|
+
for (const cart of sortedCarts) {
|
|
1547
|
+
for (const entity of cart.entities) {
|
|
1548
|
+
const source = entityGraphId(cart.name, entity.name);
|
|
1549
|
+
const sourceNode = nodeById.get(source);
|
|
1550
|
+
for (const field of entity.fields ?? []) {
|
|
1551
|
+
const explicitRelation = fieldKind(field) === "relation" && !!field.target;
|
|
1552
|
+
const inferredTargetNode = explicitRelation ? null : inferTargetNode(field.name, nodes);
|
|
1553
|
+
if (!explicitRelation && !inferredTargetNode) continue;
|
|
1554
|
+
const targetCart = explicitRelation ? field.targetCart ?? cart.name : inferredTargetNode?.cartridge;
|
|
1555
|
+
const targetEntity = explicitRelation ? field.target : inferredTargetNode?.entity;
|
|
1556
|
+
if (!targetCart || !targetEntity) continue;
|
|
1557
|
+
const target = entityGraphId(targetCart, targetEntity);
|
|
1558
|
+
if (!nodeIds.has(source) || !nodeIds.has(target)) continue;
|
|
1559
|
+
const targetNode = nodeById.get(target);
|
|
1560
|
+
const idField = targetNode?.fields.find((candidate) => candidate.displayName.toLowerCase() === "id");
|
|
1561
|
+
const targetField = idField?.displayName ?? "__entity";
|
|
1562
|
+
const sourceGraphField = sourceNode?.fields.find(
|
|
1563
|
+
(candidate) => candidate.name === field.name || candidate.displayName === field.name
|
|
1564
|
+
);
|
|
1565
|
+
const sourceField = sourceGraphField?.displayName ?? field.fkField ?? field.name;
|
|
1566
|
+
const edgeKey = `${source}:${sourceField}->${target}:${targetField}`;
|
|
1567
|
+
if (edgeKeys.has(edgeKey)) continue;
|
|
1568
|
+
edgeKeys.add(edgeKey);
|
|
1569
|
+
if (!explicitRelation && targetNode) markRelationField(sourceNode, sourceField, targetNode);
|
|
1570
|
+
edges.push({
|
|
1571
|
+
id: `${source}:${field.name}->${target}`,
|
|
1572
|
+
source,
|
|
1573
|
+
target,
|
|
1574
|
+
sourceField,
|
|
1575
|
+
targetField,
|
|
1576
|
+
label: field.name,
|
|
1577
|
+
relationKind: field.relationKind ?? (!explicitRelation ? "inferred" : void 0),
|
|
1578
|
+
fkField: field.fkField ?? (!explicitRelation ? field.name : void 0)
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
recomputeNodeCounts(nodes);
|
|
1584
|
+
layoutNodes(nodes, edges);
|
|
1585
|
+
return {
|
|
1586
|
+
nodes,
|
|
1587
|
+
edges,
|
|
1588
|
+
totals: makeTotals(nodes, edges)
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
function filterEntityGraphModel(model, visibleIds) {
|
|
1592
|
+
const nodes = model.nodes.filter((node) => visibleIds.has(node.id));
|
|
1593
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
1594
|
+
const edges = model.edges.filter((edge) => ids.has(edge.source) && ids.has(edge.target));
|
|
1595
|
+
return {
|
|
1596
|
+
nodes,
|
|
1597
|
+
edges,
|
|
1598
|
+
totals: makeTotals(nodes, edges)
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// src/components/devtools/entity-graph/diagram-tab.tsx
|
|
1603
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1604
|
+
var API_BASE = typeof NIMBIT_API_ENDPOINT !== "undefined" && NIMBIT_API_ENDPOINT || "";
|
|
1605
|
+
var STORAGE_KEY = "nimbit:inspector:entity-graph:hidden-carts";
|
|
1606
|
+
var nodeTypes = { entity: EntityNode };
|
|
1607
|
+
var FitOnReady = ({ fitKey }) => {
|
|
1608
|
+
const initialized = useNodesInitialized();
|
|
1609
|
+
const { fitView } = useReactFlow();
|
|
1610
|
+
React6.useEffect(() => {
|
|
1611
|
+
if (initialized) fitView({ padding: 0.25, duration: 200 });
|
|
1612
|
+
}, [initialized, fitKey, fitView]);
|
|
1613
|
+
return null;
|
|
1614
|
+
};
|
|
1615
|
+
function useContracts() {
|
|
1616
|
+
const [contracts, setContracts] = React6.useState([]);
|
|
1617
|
+
const [loading, setLoading] = React6.useState(true);
|
|
1618
|
+
const [error, setError] = React6.useState(null);
|
|
1619
|
+
React6.useEffect(() => {
|
|
1620
|
+
const ac = new AbortController();
|
|
1621
|
+
let cancelled = false;
|
|
1622
|
+
(async () => {
|
|
1623
|
+
try {
|
|
1624
|
+
const r = await fetch(`${API_BASE}/_console/contracts`, {
|
|
1625
|
+
signal: ac.signal,
|
|
1626
|
+
credentials: "include"
|
|
1627
|
+
});
|
|
1628
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
1629
|
+
const data = await r.json();
|
|
1630
|
+
if (!Array.isArray(data)) throw new Error("malformed contracts response");
|
|
1631
|
+
if (!cancelled) setContracts(data);
|
|
1632
|
+
} catch (e) {
|
|
1633
|
+
if (cancelled || e.name === "AbortError") return;
|
|
1634
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1635
|
+
} finally {
|
|
1636
|
+
if (!cancelled) setLoading(false);
|
|
1637
|
+
}
|
|
1638
|
+
})();
|
|
1639
|
+
return () => {
|
|
1640
|
+
cancelled = true;
|
|
1641
|
+
ac.abort();
|
|
1642
|
+
};
|
|
1643
|
+
}, []);
|
|
1644
|
+
return { contracts, loading, error };
|
|
1645
|
+
}
|
|
1646
|
+
function registryFromContracts(contracts) {
|
|
1647
|
+
const reg = {};
|
|
1648
|
+
for (const c of contracts) {
|
|
1649
|
+
if (!c.cartridge || !c.owns) continue;
|
|
1650
|
+
const entities = Object.keys(c.owns).map((name) => ({
|
|
1651
|
+
name,
|
|
1652
|
+
route: `/_ws/bulk/${c.cartridge}/${name.toLowerCase()}`,
|
|
1653
|
+
searchFields: []
|
|
1654
|
+
}));
|
|
1655
|
+
if (entities.length > 0) reg[c.cartridge] = entities;
|
|
1656
|
+
}
|
|
1657
|
+
return reg;
|
|
1658
|
+
}
|
|
1659
|
+
var DiagramView = () => {
|
|
1660
|
+
const { contracts, loading, error } = useContracts();
|
|
1661
|
+
const graph = React6.useMemo(
|
|
1662
|
+
() => buildEntityGraphModel(cartsFromContracts(contracts)),
|
|
1663
|
+
[contracts]
|
|
1664
|
+
);
|
|
1665
|
+
const registry = React6.useMemo(() => registryFromContracts(contracts), [contracts]);
|
|
1666
|
+
if (graph.nodes.length === 0) {
|
|
1667
|
+
let msg = "No installed cartridges with entities.";
|
|
1668
|
+
if (loading) msg = "Loading entity graph\u2026";
|
|
1669
|
+
else if (error) msg = `Failed to load contracts: ${error}`;
|
|
1670
|
+
return /* @__PURE__ */ jsx7("div", { className: "p-3 text-[12px] text-muted-foreground", children: msg });
|
|
1671
|
+
}
|
|
1672
|
+
return /* @__PURE__ */ jsx7(DiagramInner, { graph, registry });
|
|
1673
|
+
};
|
|
1674
|
+
var DiagramInner = ({
|
|
1675
|
+
graph,
|
|
1676
|
+
registry
|
|
1677
|
+
}) => {
|
|
1678
|
+
const rowCounts = useBulkRowCounts(registry);
|
|
1679
|
+
const [hiddenCarts, setHiddenCarts] = React6.useState(() => {
|
|
1680
|
+
if (typeof window === "undefined") return /* @__PURE__ */ new Set();
|
|
1681
|
+
try {
|
|
1682
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
1683
|
+
const saved = raw ? JSON.parse(raw) : [];
|
|
1684
|
+
return new Set(Array.isArray(saved) ? saved.filter((n) => typeof n === "string") : []);
|
|
1685
|
+
} catch {
|
|
1686
|
+
return /* @__PURE__ */ new Set();
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
const [focusedNodeId, setFocusedNodeId] = React6.useState(null);
|
|
1690
|
+
React6.useEffect(() => {
|
|
1691
|
+
if (typeof window === "undefined") return;
|
|
1692
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify([...hiddenCarts]));
|
|
1693
|
+
}, [hiddenCarts]);
|
|
1694
|
+
const cartGroups = React6.useMemo(() => {
|
|
1695
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1696
|
+
for (const node of graph.nodes) counts.set(node.cartridge, (counts.get(node.cartridge) ?? 0) + 1);
|
|
1697
|
+
return [...counts.entries()].map(([name, total]) => ({ name, total })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1698
|
+
}, [graph.nodes]);
|
|
1699
|
+
const visibleIds = React6.useMemo(
|
|
1700
|
+
() => new Set(graph.nodes.filter((n) => !hiddenCarts.has(n.cartridge)).map((n) => n.id)),
|
|
1701
|
+
[graph.nodes, hiddenCarts]
|
|
1702
|
+
);
|
|
1703
|
+
const visibleGraph = React6.useMemo(
|
|
1704
|
+
() => filterEntityGraphModel(graph, visibleIds),
|
|
1705
|
+
[graph, visibleIds]
|
|
1706
|
+
);
|
|
1707
|
+
React6.useEffect(() => {
|
|
1708
|
+
if (focusedNodeId && !visibleIds.has(focusedNodeId)) setFocusedNodeId(null);
|
|
1709
|
+
}, [focusedNodeId, visibleIds]);
|
|
1710
|
+
const focusedConnections = React6.useMemo(() => {
|
|
1711
|
+
if (!focusedNodeId) {
|
|
1712
|
+
return {
|
|
1713
|
+
connectedIds: /* @__PURE__ */ new Set(),
|
|
1714
|
+
edgeIds: /* @__PURE__ */ new Set(),
|
|
1715
|
+
fieldsByNode: /* @__PURE__ */ new Map()
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
const connectedIds = /* @__PURE__ */ new Set([focusedNodeId]);
|
|
1719
|
+
const edgeIds = /* @__PURE__ */ new Set();
|
|
1720
|
+
const fieldsByNode = /* @__PURE__ */ new Map();
|
|
1721
|
+
const addField = (nodeId, fieldName) => {
|
|
1722
|
+
const fields = fieldsByNode.get(nodeId) ?? /* @__PURE__ */ new Set();
|
|
1723
|
+
fields.add(fieldName);
|
|
1724
|
+
fieldsByNode.set(nodeId, fields);
|
|
1725
|
+
};
|
|
1726
|
+
for (const edge of visibleGraph.edges) {
|
|
1727
|
+
if (edge.source !== focusedNodeId && edge.target !== focusedNodeId) continue;
|
|
1728
|
+
edgeIds.add(edge.id);
|
|
1729
|
+
connectedIds.add(edge.source);
|
|
1730
|
+
connectedIds.add(edge.target);
|
|
1731
|
+
addField(edge.source, edge.sourceField);
|
|
1732
|
+
addField(edge.target, edge.targetField);
|
|
1733
|
+
}
|
|
1734
|
+
return { connectedIds, edgeIds, fieldsByNode };
|
|
1735
|
+
}, [focusedNodeId, visibleGraph.edges]);
|
|
1736
|
+
const liveRows = React6.useMemo(
|
|
1737
|
+
() => visibleGraph.nodes.reduce((sum, n) => sum + (rowCounts[n.id] ?? 0), 0),
|
|
1738
|
+
[visibleGraph.nodes, rowCounts]
|
|
1739
|
+
);
|
|
1740
|
+
const nodes = React6.useMemo(
|
|
1741
|
+
() => visibleGraph.nodes.map((node) => ({
|
|
1742
|
+
id: node.id,
|
|
1743
|
+
type: "entity",
|
|
1744
|
+
position: node.position,
|
|
1745
|
+
data: {
|
|
1746
|
+
...node,
|
|
1747
|
+
rowCount: rowCounts[node.id] ?? node.rowCount,
|
|
1748
|
+
highlight: focusedNodeId ? {
|
|
1749
|
+
focused: node.id === focusedNodeId,
|
|
1750
|
+
connected: focusedConnections.connectedIds.has(node.id) && node.id !== focusedNodeId,
|
|
1751
|
+
dimmed: !focusedConnections.connectedIds.has(node.id),
|
|
1752
|
+
fields: [...focusedConnections.fieldsByNode.get(node.id) ?? []]
|
|
1753
|
+
} : void 0
|
|
1754
|
+
}
|
|
1755
|
+
})),
|
|
1756
|
+
[focusedConnections, focusedNodeId, visibleGraph.nodes, rowCounts]
|
|
1757
|
+
);
|
|
1758
|
+
const edges = React6.useMemo(() => {
|
|
1759
|
+
const nodeById = new Map(visibleGraph.nodes.map((node) => [node.id, node]));
|
|
1760
|
+
const laneCounts = /* @__PURE__ */ new Map();
|
|
1761
|
+
return visibleGraph.edges.map((edge) => {
|
|
1762
|
+
const source = nodeById.get(edge.source);
|
|
1763
|
+
const target = nodeById.get(edge.target);
|
|
1764
|
+
const sourceSide = source && target && source.position.x > target.position.x ? "left" : "right";
|
|
1765
|
+
const targetSide = sourceSide === "left" ? "right" : "left";
|
|
1766
|
+
const laneKey = `${edge.target}:${edge.targetField}:${targetSide}`;
|
|
1767
|
+
const lane = laneCounts.get(laneKey) ?? 0;
|
|
1768
|
+
laneCounts.set(laneKey, lane + 1);
|
|
1769
|
+
const highlighted = !focusedNodeId || focusedConnections.edgeIds.has(edge.id);
|
|
1770
|
+
return {
|
|
1771
|
+
id: edge.id,
|
|
1772
|
+
source: edge.source,
|
|
1773
|
+
target: edge.target,
|
|
1774
|
+
sourceHandle: `source-${edge.sourceField}-${sourceSide}`,
|
|
1775
|
+
targetHandle: `target-${edge.targetField}-${targetSide}`,
|
|
1776
|
+
type: "smoothstep",
|
|
1777
|
+
pathOptions: { borderRadius: 8, offset: 22 + lane % 5 * 12 },
|
|
1778
|
+
markerEnd: {
|
|
1779
|
+
type: MarkerType.ArrowClosed,
|
|
1780
|
+
color: highlighted ? "#2563eb" : "#64748b"
|
|
1781
|
+
},
|
|
1782
|
+
style: {
|
|
1783
|
+
strokeWidth: focusedNodeId && highlighted ? 2.5 : 1.5,
|
|
1784
|
+
stroke: highlighted ? "#2563eb" : "#64748b",
|
|
1785
|
+
opacity: highlighted ? 1 : 0.16
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1788
|
+
});
|
|
1789
|
+
}, [focusedConnections.edgeIds, focusedNodeId, visibleGraph.edges, visibleGraph.nodes]);
|
|
1790
|
+
const toggleCart = React6.useCallback((name) => {
|
|
1791
|
+
setHiddenCarts((prev) => {
|
|
1792
|
+
const next = new Set(prev);
|
|
1793
|
+
if (next.has(name)) next.delete(name);
|
|
1794
|
+
else next.add(name);
|
|
1795
|
+
return next;
|
|
1796
|
+
});
|
|
1797
|
+
}, []);
|
|
1798
|
+
const onNodeClick = React6.useCallback((_, node) => {
|
|
1799
|
+
const data = node.data;
|
|
1800
|
+
const nodeId = entityGraphId(data.cartridge, data.entity);
|
|
1801
|
+
setFocusedNodeId((prev) => prev === nodeId ? null : nodeId);
|
|
1802
|
+
}, []);
|
|
1803
|
+
const clearFocusedNode = React6.useCallback(() => setFocusedNodeId(null), []);
|
|
1804
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex h-full min-h-0 w-full flex-col", children: [
|
|
1805
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex h-7 shrink-0 items-center gap-3 border-b border-border bg-background px-2 text-[11px] text-muted-foreground select-none", children: [
|
|
1806
|
+
/* @__PURE__ */ jsxs4("span", { className: "inline-flex items-center gap-1 text-foreground", children: [
|
|
1807
|
+
/* @__PURE__ */ jsx7(GitBranch, { className: "h-3.5 w-3.5" }),
|
|
1808
|
+
"Entity Graph"
|
|
1809
|
+
] }),
|
|
1810
|
+
/* @__PURE__ */ jsxs4("span", { className: "ml-auto inline-flex items-center gap-1", children: [
|
|
1811
|
+
/* @__PURE__ */ jsx7(Database2, { className: "h-3 w-3" }),
|
|
1812
|
+
visibleGraph.totals.entities,
|
|
1813
|
+
" entities"
|
|
1814
|
+
] }),
|
|
1815
|
+
/* @__PURE__ */ jsxs4("span", { className: "inline-flex items-center gap-1", children: [
|
|
1816
|
+
/* @__PURE__ */ jsx7(GitBranch, { className: "h-3 w-3" }),
|
|
1817
|
+
visibleGraph.totals.relationships,
|
|
1818
|
+
" relationships"
|
|
1819
|
+
] }),
|
|
1820
|
+
/* @__PURE__ */ jsxs4("span", { className: "inline-flex items-center gap-1", children: [
|
|
1821
|
+
/* @__PURE__ */ jsx7(Rows3, { className: "h-3 w-3" }),
|
|
1822
|
+
liveRows.toLocaleString(),
|
|
1823
|
+
" rows"
|
|
1824
|
+
] })
|
|
1825
|
+
] }),
|
|
1826
|
+
/* @__PURE__ */ jsxs4("div", { className: "relative min-h-0 flex-1 bg-zinc-950", children: [
|
|
1827
|
+
cartGroups.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "pointer-events-none absolute left-2 top-2 z-10 max-h-[calc(100%-1rem)]", children: /* @__PURE__ */ jsx7("div", { className: "pointer-events-auto flex max-h-full flex-col gap-0.5 overflow-y-auto rounded-md border border-zinc-700 bg-zinc-900/95 p-1 shadow-sm backdrop-blur", children: cartGroups.map((g) => {
|
|
1828
|
+
const hidden = hiddenCarts.has(g.name);
|
|
1829
|
+
return /* @__PURE__ */ jsxs4(
|
|
1830
|
+
"button",
|
|
1831
|
+
{
|
|
1832
|
+
type: "button",
|
|
1833
|
+
onClick: () => toggleCart(g.name),
|
|
1834
|
+
className: cn(
|
|
1835
|
+
"flex items-center gap-2 rounded px-2 py-1 text-left font-mono text-[11px] hover:bg-zinc-800",
|
|
1836
|
+
hidden ? "text-zinc-500" : "text-zinc-100"
|
|
1837
|
+
),
|
|
1838
|
+
title: hidden ? `Show ${g.name}` : `Hide ${g.name}`,
|
|
1839
|
+
children: [
|
|
1840
|
+
hidden ? /* @__PURE__ */ jsx7(EyeOff, { className: "h-3.5 w-3.5 shrink-0" }) : /* @__PURE__ */ jsx7(Eye, { className: "h-3.5 w-3.5 shrink-0 text-orange-400" }),
|
|
1841
|
+
/* @__PURE__ */ jsx7("span", { className: "min-w-0 flex-1 truncate", children: g.name }),
|
|
1842
|
+
/* @__PURE__ */ jsx7("span", { className: "shrink-0 text-[10px] tabular-nums text-zinc-500", children: g.total })
|
|
1843
|
+
]
|
|
1844
|
+
},
|
|
1845
|
+
g.name
|
|
1846
|
+
);
|
|
1847
|
+
}) }) }) : null,
|
|
1848
|
+
nodes.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "flex h-full items-center justify-center text-[12px] text-zinc-500", children: "No entities are visible." }) : /* @__PURE__ */ jsx7(ReactFlowProvider, { children: /* @__PURE__ */ jsxs4(
|
|
1849
|
+
ReactFlow,
|
|
1850
|
+
{
|
|
1851
|
+
colorMode: "dark",
|
|
1852
|
+
nodes,
|
|
1853
|
+
edges,
|
|
1854
|
+
nodeTypes,
|
|
1855
|
+
fitView: true,
|
|
1856
|
+
fitViewOptions: { padding: 0.2 },
|
|
1857
|
+
minZoom: 0.1,
|
|
1858
|
+
maxZoom: 1.6,
|
|
1859
|
+
nodesDraggable: false,
|
|
1860
|
+
nodesConnectable: false,
|
|
1861
|
+
elementsSelectable: true,
|
|
1862
|
+
panOnDrag: true,
|
|
1863
|
+
panOnScroll: true,
|
|
1864
|
+
zoomOnScroll: false,
|
|
1865
|
+
zoomOnPinch: true,
|
|
1866
|
+
selectionOnDrag: false,
|
|
1867
|
+
onNodeClick,
|
|
1868
|
+
onPaneClick: clearFocusedNode,
|
|
1869
|
+
proOptions: { hideAttribution: true },
|
|
1870
|
+
children: [
|
|
1871
|
+
/* @__PURE__ */ jsx7(FitOnReady, { fitKey: [...visibleIds].sort().join("|") }),
|
|
1872
|
+
/* @__PURE__ */ jsx7(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1, color: "#3f3f46" }),
|
|
1873
|
+
/* @__PURE__ */ jsx7(Controls, { showInteractive: false })
|
|
1874
|
+
]
|
|
1875
|
+
}
|
|
1876
|
+
) })
|
|
1877
|
+
] })
|
|
1878
|
+
] });
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
// src/components/ui/sheet.tsx
|
|
1882
|
+
import { Dialog as SheetPrimitive } from "radix-ui";
|
|
1883
|
+
|
|
1884
|
+
// src/components/ui/button.tsx
|
|
1885
|
+
import { cva } from "class-variance-authority";
|
|
1886
|
+
import { Slot } from "radix-ui";
|
|
1887
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1888
|
+
var buttonVariants = cva(
|
|
1889
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
1890
|
+
{
|
|
1891
|
+
variants: {
|
|
1892
|
+
variant: {
|
|
1893
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
1894
|
+
outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
1895
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
1896
|
+
ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
1897
|
+
destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
1898
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
1899
|
+
},
|
|
1900
|
+
size: {
|
|
1901
|
+
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
1902
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
1903
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
1904
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
1905
|
+
icon: "size-8",
|
|
1906
|
+
"icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
1907
|
+
"icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
1908
|
+
"icon-lg": "size-9"
|
|
1909
|
+
}
|
|
1910
|
+
},
|
|
1911
|
+
defaultVariants: {
|
|
1912
|
+
variant: "default",
|
|
1913
|
+
size: "default"
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
);
|
|
1917
|
+
function Button({
|
|
1918
|
+
className,
|
|
1919
|
+
variant = "default",
|
|
1920
|
+
size = "default",
|
|
1921
|
+
asChild = false,
|
|
1922
|
+
...props
|
|
1923
|
+
}) {
|
|
1924
|
+
const Comp = asChild ? Slot.Root : "button";
|
|
1925
|
+
return /* @__PURE__ */ jsx8(
|
|
1926
|
+
Comp,
|
|
1927
|
+
{
|
|
1928
|
+
"data-slot": "button",
|
|
1929
|
+
"data-variant": variant,
|
|
1930
|
+
"data-size": size,
|
|
1931
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
1932
|
+
...props
|
|
1933
|
+
}
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// src/components/ui/sheet.tsx
|
|
1938
|
+
import { XIcon } from "lucide-react";
|
|
1939
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1940
|
+
function Sheet({ ...props }) {
|
|
1941
|
+
return /* @__PURE__ */ jsx9(SheetPrimitive.Root, { "data-slot": "sheet", ...props });
|
|
1942
|
+
}
|
|
1943
|
+
function SheetPortal({
|
|
1944
|
+
...props
|
|
1945
|
+
}) {
|
|
1946
|
+
return /* @__PURE__ */ jsx9(SheetPrimitive.Portal, { "data-slot": "sheet-portal", ...props });
|
|
1947
|
+
}
|
|
1948
|
+
function SheetOverlay({
|
|
1949
|
+
className,
|
|
1950
|
+
...props
|
|
1951
|
+
}) {
|
|
1952
|
+
return /* @__PURE__ */ jsx9(
|
|
1953
|
+
SheetPrimitive.Overlay,
|
|
1954
|
+
{
|
|
1955
|
+
"data-slot": "sheet-overlay",
|
|
1956
|
+
className: cn(
|
|
1957
|
+
"nimbit-devtools dark fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
|
1958
|
+
className
|
|
1959
|
+
),
|
|
1960
|
+
...props
|
|
1961
|
+
}
|
|
1962
|
+
);
|
|
1963
|
+
}
|
|
1964
|
+
function SheetContent({
|
|
1965
|
+
className,
|
|
1966
|
+
children,
|
|
1967
|
+
side = "right",
|
|
1968
|
+
showCloseButton = true,
|
|
1969
|
+
...props
|
|
1970
|
+
}) {
|
|
1971
|
+
return /* @__PURE__ */ jsxs5(SheetPortal, { children: [
|
|
1972
|
+
/* @__PURE__ */ jsx9(SheetOverlay, {}),
|
|
1973
|
+
/* @__PURE__ */ jsxs5(
|
|
1974
|
+
SheetPrimitive.Content,
|
|
1975
|
+
{
|
|
1976
|
+
"data-slot": "sheet-content",
|
|
1977
|
+
"data-side": side,
|
|
1978
|
+
className: cn(
|
|
1979
|
+
"nimbit-devtools dark fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10",
|
|
1980
|
+
className
|
|
1981
|
+
),
|
|
1982
|
+
...props,
|
|
1983
|
+
children: [
|
|
1984
|
+
children,
|
|
1985
|
+
showCloseButton && /* @__PURE__ */ jsx9(SheetPrimitive.Close, { "data-slot": "sheet-close", asChild: true, children: /* @__PURE__ */ jsxs5(
|
|
1986
|
+
Button,
|
|
1987
|
+
{
|
|
1988
|
+
variant: "ghost",
|
|
1989
|
+
className: "absolute top-3 right-3",
|
|
1990
|
+
size: "icon-sm",
|
|
1991
|
+
children: [
|
|
1992
|
+
/* @__PURE__ */ jsx9(
|
|
1993
|
+
XIcon,
|
|
1994
|
+
{}
|
|
1995
|
+
),
|
|
1996
|
+
/* @__PURE__ */ jsx9("span", { className: "sr-only", children: "Close" })
|
|
1997
|
+
]
|
|
1998
|
+
}
|
|
1999
|
+
) })
|
|
2000
|
+
]
|
|
2001
|
+
}
|
|
2002
|
+
)
|
|
2003
|
+
] });
|
|
2004
|
+
}
|
|
2005
|
+
function SheetHeader({ className, ...props }) {
|
|
2006
|
+
return /* @__PURE__ */ jsx9(
|
|
2007
|
+
"div",
|
|
2008
|
+
{
|
|
2009
|
+
"data-slot": "sheet-header",
|
|
2010
|
+
className: cn("flex flex-col gap-0.5 p-4", className),
|
|
2011
|
+
...props
|
|
2012
|
+
}
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
2015
|
+
function SheetTitle({
|
|
2016
|
+
className,
|
|
2017
|
+
...props
|
|
2018
|
+
}) {
|
|
2019
|
+
return /* @__PURE__ */ jsx9(
|
|
2020
|
+
SheetPrimitive.Title,
|
|
2021
|
+
{
|
|
2022
|
+
"data-slot": "sheet-title",
|
|
2023
|
+
className: cn(
|
|
2024
|
+
"text-base font-medium text-foreground",
|
|
2025
|
+
className
|
|
2026
|
+
),
|
|
2027
|
+
...props
|
|
2028
|
+
}
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
function SheetDescription({
|
|
2032
|
+
className,
|
|
2033
|
+
...props
|
|
2034
|
+
}) {
|
|
2035
|
+
return /* @__PURE__ */ jsx9(
|
|
2036
|
+
SheetPrimitive.Description,
|
|
2037
|
+
{
|
|
2038
|
+
"data-slot": "sheet-description",
|
|
2039
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
2040
|
+
...props
|
|
2041
|
+
}
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
// src/components/devtools/data-tab.tsx
|
|
2046
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2047
|
+
var DataTab = () => {
|
|
2048
|
+
const { dataCart, setDataCart, dataEntity, setDataEntity } = useDevTools();
|
|
2049
|
+
const [detail, setDetail] = React7.useState(null);
|
|
2050
|
+
const [view, setView] = React7.useState("table");
|
|
2051
|
+
const { registry, carts, loading, error } = useLiveBulkRegistry();
|
|
2052
|
+
const activeCart = dataCart && registry[dataCart] ? dataCart : carts[0] ?? null;
|
|
2053
|
+
const entities = activeCart ? registry[activeCart] ?? [] : [];
|
|
2054
|
+
const activeEntity = entities.find((e) => e.name === dataEntity) ?? entities[0] ?? null;
|
|
2055
|
+
React7.useEffect(() => {
|
|
2056
|
+
if (activeCart && activeCart !== dataCart) setDataCart(activeCart);
|
|
2057
|
+
if (activeEntity && activeEntity.name !== dataEntity) setDataEntity(activeEntity.name);
|
|
2058
|
+
}, [activeCart, activeEntity, dataCart, dataEntity, setDataCart, setDataEntity]);
|
|
2059
|
+
if (carts.length === 0) {
|
|
2060
|
+
let msg = "No installed cartridges with entities.";
|
|
2061
|
+
if (loading) msg = "Loading cartridges\u2026";
|
|
2062
|
+
else if (error) msg = `Failed to load cartridges: ${error}`;
|
|
2063
|
+
return /* @__PURE__ */ jsx10("div", { className: "p-3 text-[12px] text-muted-foreground", children: msg });
|
|
2064
|
+
}
|
|
2065
|
+
return /* @__PURE__ */ jsx10(BulkStreamProvider, { registry, children: /* @__PURE__ */ jsxs6("div", { className: "flex h-full flex-col", children: [
|
|
2066
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex h-7 shrink-0 items-center gap-1 overflow-x-auto border-b border-border bg-background px-1 select-none", children: [
|
|
2067
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex shrink-0 items-center gap-0.5 pr-1", children: [
|
|
2068
|
+
/* @__PURE__ */ jsx10(ViewBtn, { active: view === "table", onClick: () => setView("table"), label: "Table", children: /* @__PURE__ */ jsx10(Table2, { className: "size-3" }) }),
|
|
2069
|
+
/* @__PURE__ */ jsx10(ViewBtn, { active: view === "diagram", onClick: () => setView("diagram"), label: "Graph", children: /* @__PURE__ */ jsx10(Network, { className: "size-3" }) })
|
|
2070
|
+
] }),
|
|
2071
|
+
view === "table" ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
2072
|
+
/* @__PURE__ */ jsx10("div", { className: "h-4 w-px shrink-0 bg-border" }),
|
|
2073
|
+
carts.map((cart) => {
|
|
2074
|
+
const isActive = cart === activeCart;
|
|
2075
|
+
return /* @__PURE__ */ jsx10(
|
|
2076
|
+
"button",
|
|
2077
|
+
{
|
|
2078
|
+
type: "button",
|
|
2079
|
+
"data-active": isActive,
|
|
2080
|
+
onClick: () => {
|
|
2081
|
+
setDataCart(cart);
|
|
2082
|
+
const first = registry[cart]?.[0];
|
|
2083
|
+
setDataEntity(first ? first.name : null);
|
|
2084
|
+
},
|
|
2085
|
+
className: cn(
|
|
2086
|
+
"h-5 shrink-0 rounded-full px-2.5 text-[11px] leading-none outline-none transition-colors",
|
|
2087
|
+
"border border-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
2088
|
+
"data-[active=true]:border-border data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
2089
|
+
),
|
|
2090
|
+
children: cart
|
|
2091
|
+
},
|
|
2092
|
+
cart
|
|
2093
|
+
);
|
|
2094
|
+
})
|
|
2095
|
+
] }) : null
|
|
2096
|
+
] }),
|
|
2097
|
+
view === "diagram" ? /* @__PURE__ */ jsx10("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx10(DiagramView, {}) }) : /* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 flex-1", children: [
|
|
2098
|
+
/* @__PURE__ */ jsx10("div", { className: "flex w-44 shrink-0 flex-col overflow-y-auto border-r border-border bg-muted/10 py-1", children: entities.length === 0 ? /* @__PURE__ */ jsx10("div", { className: "px-2 py-1 text-[11px] text-muted-foreground", children: "No entities" }) : entities.map((e) => {
|
|
2099
|
+
const isActive = e.name === activeEntity?.name;
|
|
2100
|
+
return /* @__PURE__ */ jsx10(
|
|
2101
|
+
"button",
|
|
2102
|
+
{
|
|
2103
|
+
type: "button",
|
|
2104
|
+
"data-active": isActive,
|
|
2105
|
+
onClick: () => setDataEntity(e.name),
|
|
2106
|
+
className: cn(
|
|
2107
|
+
"h-6 shrink-0 text-left px-2 text-[12px] leading-none outline-none transition-colors",
|
|
2108
|
+
"text-foreground hover:bg-accent hover:text-accent-foreground",
|
|
2109
|
+
"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
2110
|
+
),
|
|
2111
|
+
children: e.name
|
|
2112
|
+
},
|
|
2113
|
+
e.name
|
|
2114
|
+
);
|
|
2115
|
+
}) }),
|
|
2116
|
+
/* @__PURE__ */ jsx10("div", { className: "min-h-0 flex-1", children: activeEntity ? /* @__PURE__ */ jsx10(
|
|
2117
|
+
data_table_default,
|
|
2118
|
+
{
|
|
2119
|
+
cart: activeCart,
|
|
2120
|
+
entity: activeEntity.name,
|
|
2121
|
+
searchFields: activeEntity.searchFields,
|
|
2122
|
+
onSelectRow: (row) => setDetail(row)
|
|
2123
|
+
},
|
|
2124
|
+
`${activeCart}:${activeEntity.name}`
|
|
2125
|
+
) : /* @__PURE__ */ jsx10("div", { className: "p-3 text-[12px] text-muted-foreground", children: "Pick an entity." }) })
|
|
2126
|
+
] }),
|
|
2127
|
+
/* @__PURE__ */ jsx10(Sheet, { open: !!detail, onOpenChange: (o) => !o && setDetail(null), children: /* @__PURE__ */ jsxs6(SheetContent, { side: "right", className: "w-[480px] sm:max-w-[480px]", children: [
|
|
2128
|
+
/* @__PURE__ */ jsxs6(SheetHeader, { children: [
|
|
2129
|
+
/* @__PURE__ */ jsxs6(SheetTitle, { className: "text-[13px]", children: [
|
|
2130
|
+
activeCart,
|
|
2131
|
+
".",
|
|
2132
|
+
activeEntity?.name,
|
|
2133
|
+
detail?.id ? /* @__PURE__ */ jsx10("span", { className: "ml-2 font-mono text-[11px] text-muted-foreground", children: detail.id }) : null
|
|
2134
|
+
] }),
|
|
2135
|
+
/* @__PURE__ */ jsx10(SheetDescription, { className: "text-[11px]", children: "Row detail" })
|
|
2136
|
+
] }),
|
|
2137
|
+
/* @__PURE__ */ jsx10("div", { className: "min-h-0 flex-1 overflow-auto px-4 pb-4", children: detail ? /* @__PURE__ */ jsx10("table", { className: "w-full text-[12px]", children: /* @__PURE__ */ jsx10("tbody", { children: Object.entries(detail).map(([k, v]) => /* @__PURE__ */ jsxs6("tr", { className: "border-b border-border/40 align-top", children: [
|
|
2138
|
+
/* @__PURE__ */ jsx10("td", { className: "w-32 py-1 pr-2 font-mono text-[11px] text-muted-foreground", children: k }),
|
|
2139
|
+
/* @__PURE__ */ jsx10("td", { className: "break-all py-1 font-mono", children: v || /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground/60", children: "\u2205" }) })
|
|
2140
|
+
] }, k)) }) }) : null })
|
|
2141
|
+
] }) })
|
|
2142
|
+
] }) });
|
|
2143
|
+
};
|
|
2144
|
+
var ViewBtn = ({ active, onClick, label, children }) => /* @__PURE__ */ jsxs6(
|
|
2145
|
+
"button",
|
|
2146
|
+
{
|
|
2147
|
+
type: "button",
|
|
2148
|
+
"data-active": active,
|
|
2149
|
+
onClick,
|
|
2150
|
+
title: label,
|
|
2151
|
+
className: cn(
|
|
2152
|
+
"inline-flex h-5 shrink-0 items-center gap-1 rounded-md px-1.5 text-[11px] leading-none outline-none transition-colors",
|
|
2153
|
+
"text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
2154
|
+
"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
2155
|
+
),
|
|
2156
|
+
children: [
|
|
2157
|
+
children,
|
|
2158
|
+
label
|
|
2159
|
+
]
|
|
2160
|
+
}
|
|
2161
|
+
);
|
|
2162
|
+
var data_tab_default = DataTab;
|
|
2163
|
+
|
|
2164
|
+
// src/components/devtools/settings-tab.tsx
|
|
2165
|
+
import { Fragment as Fragment3, jsx as jsx11 } from "react/jsx-runtime";
|
|
2166
|
+
var SettingsTab = () => {
|
|
2167
|
+
return /* @__PURE__ */ jsx11(Fragment3, {});
|
|
2168
|
+
};
|
|
2169
|
+
var settings_tab_default = SettingsTab;
|
|
2170
|
+
|
|
2171
|
+
// src/components/devtools/dev-tools.tsx
|
|
2172
|
+
import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2173
|
+
var MIN_H = 120;
|
|
2174
|
+
var MIN_W = 240;
|
|
2175
|
+
var DEV_TOOLS_TABS = [
|
|
2176
|
+
{ id: "console", title: "Console", render: () => /* @__PURE__ */ jsx12(console_tab_default, {}) },
|
|
2177
|
+
{ id: "network", title: "Network", render: () => /* @__PURE__ */ jsx12(network_tab_default, {}) },
|
|
2178
|
+
{ id: "data", title: "Data", render: () => /* @__PURE__ */ jsx12(data_tab_default, {}) },
|
|
2179
|
+
{ id: "settings", title: "Settings", render: () => /* @__PURE__ */ jsx12(settings_tab_default, {}) }
|
|
2180
|
+
];
|
|
2181
|
+
var DevTools = () => {
|
|
2182
|
+
const {
|
|
2183
|
+
open,
|
|
2184
|
+
setOpen,
|
|
2185
|
+
dock,
|
|
2186
|
+
setDock,
|
|
2187
|
+
activeTab,
|
|
2188
|
+
setActiveTab,
|
|
2189
|
+
size,
|
|
2190
|
+
setSize,
|
|
2191
|
+
maximized,
|
|
2192
|
+
setMaximized
|
|
2193
|
+
} = useDevTools();
|
|
2194
|
+
const panelRef = React8.useRef(null);
|
|
2195
|
+
React8.useEffect(() => {
|
|
2196
|
+
if (activeTab) return;
|
|
2197
|
+
if (DEV_TOOLS_TABS.length > 0) setActiveTab(DEV_TOOLS_TABS[0].id);
|
|
2198
|
+
}, [activeTab, setActiveTab]);
|
|
2199
|
+
const startResize = React8.useCallback(
|
|
2200
|
+
(e) => {
|
|
2201
|
+
e.preventDefault();
|
|
2202
|
+
const panel = panelRef.current;
|
|
2203
|
+
if (!panel) return;
|
|
2204
|
+
const parent = panel.parentElement;
|
|
2205
|
+
if (!parent) return;
|
|
2206
|
+
const handle = e.currentTarget;
|
|
2207
|
+
handle.setPointerCapture(e.pointerId);
|
|
2208
|
+
const parentRect = parent.getBoundingClientRect();
|
|
2209
|
+
const startX = e.clientX;
|
|
2210
|
+
const startY = e.clientY;
|
|
2211
|
+
const startH = panel.offsetHeight;
|
|
2212
|
+
const startW = panel.offsetWidth;
|
|
2213
|
+
const isBottom = dock === "bottom";
|
|
2214
|
+
let pendingNext = null;
|
|
2215
|
+
let lastApplied = isBottom ? startH : startW;
|
|
2216
|
+
let rafId = 0;
|
|
2217
|
+
const flush = () => {
|
|
2218
|
+
rafId = 0;
|
|
2219
|
+
if (pendingNext == null) return;
|
|
2220
|
+
const v = pendingNext;
|
|
2221
|
+
pendingNext = null;
|
|
2222
|
+
lastApplied = v;
|
|
2223
|
+
if (isBottom) panel.style.height = `${v}px`;
|
|
2224
|
+
else panel.style.width = `${v}px`;
|
|
2225
|
+
};
|
|
2226
|
+
const onMove = (ev) => {
|
|
2227
|
+
ev.preventDefault();
|
|
2228
|
+
if (isBottom) {
|
|
2229
|
+
const dy = startY - ev.clientY;
|
|
2230
|
+
pendingNext = Math.max(
|
|
2231
|
+
MIN_H,
|
|
2232
|
+
Math.min(parentRect.height - 24, startH + dy)
|
|
2233
|
+
);
|
|
2234
|
+
} else {
|
|
2235
|
+
const dx = startX - ev.clientX;
|
|
2236
|
+
pendingNext = Math.max(
|
|
2237
|
+
MIN_W,
|
|
2238
|
+
Math.min(parentRect.width - 24, startW + dx)
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
if (!rafId) rafId = requestAnimationFrame(flush);
|
|
2242
|
+
};
|
|
2243
|
+
const onUp = () => {
|
|
2244
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
2245
|
+
flush();
|
|
2246
|
+
setSize(
|
|
2247
|
+
(prev) => isBottom ? prev.h === lastApplied ? prev : { ...prev, h: lastApplied } : prev.w === lastApplied ? prev : { ...prev, w: lastApplied }
|
|
2248
|
+
);
|
|
2249
|
+
handle.removeEventListener("pointermove", onMove);
|
|
2250
|
+
handle.removeEventListener("pointerup", onUp);
|
|
2251
|
+
handle.removeEventListener("pointercancel", onUp);
|
|
2252
|
+
try {
|
|
2253
|
+
handle.releasePointerCapture(e.pointerId);
|
|
2254
|
+
} catch {
|
|
2255
|
+
}
|
|
2256
|
+
document.body.style.userSelect = "";
|
|
2257
|
+
document.body.style.cursor = "";
|
|
2258
|
+
};
|
|
2259
|
+
document.body.style.userSelect = "none";
|
|
2260
|
+
document.body.style.cursor = dock === "bottom" ? "ns-resize" : "ew-resize";
|
|
2261
|
+
handle.addEventListener("pointermove", onMove);
|
|
2262
|
+
handle.addEventListener("pointerup", onUp);
|
|
2263
|
+
handle.addEventListener("pointercancel", onUp);
|
|
2264
|
+
},
|
|
2265
|
+
[dock, setSize]
|
|
2266
|
+
);
|
|
2267
|
+
if (!open) return null;
|
|
2268
|
+
const active = DEV_TOOLS_TABS.find((t) => t.id === activeTab) ?? DEV_TOOLS_TABS[0];
|
|
2269
|
+
const positionClass = maximized ? "inset-0" : dock === "bottom" ? "inset-x-0 bottom-0" : "inset-y-0 right-0";
|
|
2270
|
+
const sizeStyle = maximized ? {} : dock === "bottom" ? { height: size.h } : { width: size.w };
|
|
2271
|
+
return /* @__PURE__ */ jsxs7(
|
|
2272
|
+
"div",
|
|
2273
|
+
{
|
|
2274
|
+
ref: panelRef,
|
|
2275
|
+
style: sizeStyle,
|
|
2276
|
+
className: cn(
|
|
2277
|
+
"nimbit-devtools dark absolute z-40 flex flex-col border-border bg-background text-foreground text-[12px] shadow-lg",
|
|
2278
|
+
positionClass,
|
|
2279
|
+
dock === "bottom" ? "border-t" : "border-l"
|
|
2280
|
+
),
|
|
2281
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
2282
|
+
children: [
|
|
2283
|
+
!maximized && /* @__PURE__ */ jsx12(
|
|
2284
|
+
"div",
|
|
2285
|
+
{
|
|
2286
|
+
onPointerDown: startResize,
|
|
2287
|
+
style: { touchAction: "none" },
|
|
2288
|
+
className: cn(
|
|
2289
|
+
"absolute z-10 bg-transparent hover:bg-accent/40",
|
|
2290
|
+
dock === "bottom" ? "left-0 right-0 -top-px h-1.5 cursor-ns-resize" : "top-0 bottom-0 -left-px w-1.5 cursor-ew-resize"
|
|
2291
|
+
)
|
|
2292
|
+
}
|
|
2293
|
+
),
|
|
2294
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex h-7 shrink-0 items-center border-b border-border bg-background pl-1 pr-1 select-none", children: [
|
|
2295
|
+
/* @__PURE__ */ jsx12("div", { className: "flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto", children: DEV_TOOLS_TABS.map((tab) => {
|
|
2296
|
+
const isActive = tab.id === active?.id;
|
|
2297
|
+
return /* @__PURE__ */ jsx12(
|
|
2298
|
+
"button",
|
|
2299
|
+
{
|
|
2300
|
+
type: "button",
|
|
2301
|
+
"data-active": isActive,
|
|
2302
|
+
onClick: () => setActiveTab(tab.id),
|
|
2303
|
+
className: cn(
|
|
2304
|
+
"h-5 shrink-0 rounded-md px-2 text-[12px] leading-none outline-none transition-colors",
|
|
2305
|
+
"text-foreground hover:bg-accent hover:text-accent-foreground",
|
|
2306
|
+
"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
2307
|
+
),
|
|
2308
|
+
children: /* @__PURE__ */ jsxs7("span", { className: "inline-flex items-center gap-1", children: [
|
|
2309
|
+
tab.icon,
|
|
2310
|
+
tab.title
|
|
2311
|
+
] })
|
|
2312
|
+
},
|
|
2313
|
+
tab.id
|
|
2314
|
+
);
|
|
2315
|
+
}) }),
|
|
2316
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex shrink-0 items-center gap-0.5 pl-2", children: [
|
|
2317
|
+
/* @__PURE__ */ jsx12(
|
|
2318
|
+
IconBtn,
|
|
2319
|
+
{
|
|
2320
|
+
label: dock === "bottom" ? "Dock right" : "Dock bottom",
|
|
2321
|
+
onClick: () => setDock(dock === "bottom" ? "right" : "bottom"),
|
|
2322
|
+
children: dock === "bottom" ? /* @__PURE__ */ jsx12(PanelRight, { className: "size-3.5" }) : /* @__PURE__ */ jsx12(PanelBottom, { className: "size-3.5" })
|
|
2323
|
+
}
|
|
2324
|
+
),
|
|
2325
|
+
/* @__PURE__ */ jsx12(
|
|
2326
|
+
IconBtn,
|
|
2327
|
+
{
|
|
2328
|
+
label: maximized ? "Restore" : "Maximize",
|
|
2329
|
+
onClick: () => setMaximized(!maximized),
|
|
2330
|
+
children: maximized ? /* @__PURE__ */ jsx12(Minimize2, { className: "size-3.5" }) : /* @__PURE__ */ jsx12(Maximize2, { className: "size-3.5" })
|
|
2331
|
+
}
|
|
2332
|
+
),
|
|
2333
|
+
/* @__PURE__ */ jsx12(IconBtn, { label: "Close", onClick: () => setOpen(false), children: /* @__PURE__ */ jsx12(X, { className: "size-3.5" }) })
|
|
2334
|
+
] })
|
|
2335
|
+
] }),
|
|
2336
|
+
/* @__PURE__ */ jsx12("div", { className: "min-h-0 flex-1 overflow-auto", children: active ? active.render() : null })
|
|
2337
|
+
]
|
|
2338
|
+
}
|
|
2339
|
+
);
|
|
2340
|
+
};
|
|
2341
|
+
var IconBtn = ({ label, className, children, ...rest }) => /* @__PURE__ */ jsx12(
|
|
2342
|
+
"button",
|
|
2343
|
+
{
|
|
2344
|
+
type: "button",
|
|
2345
|
+
"aria-label": label,
|
|
2346
|
+
title: label,
|
|
2347
|
+
className: cn(
|
|
2348
|
+
"grid size-5 place-items-center rounded-md text-foreground outline-none hover:bg-accent hover:text-accent-foreground",
|
|
2349
|
+
className
|
|
2350
|
+
),
|
|
2351
|
+
...rest,
|
|
2352
|
+
children
|
|
2353
|
+
}
|
|
2354
|
+
);
|
|
2355
|
+
var dev_tools_default = DevTools;
|
|
2356
|
+
|
|
2357
|
+
// src/index.tsx
|
|
2358
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
2359
|
+
var NimbitDevTools = ({
|
|
2360
|
+
apiBaseUrl = "",
|
|
2361
|
+
defaultActiveTab
|
|
2362
|
+
}) => /* @__PURE__ */ jsx13(DevToolsConfigProvider, { apiBaseUrl, children: /* @__PURE__ */ jsx13(DevToolsProvider, { defaultActiveTab, children: /* @__PURE__ */ jsx13(dev_tools_default, {}) }) });
|
|
2363
|
+
var index_default = NimbitDevTools;
|
|
2364
|
+
function toggleDevTools() {
|
|
2365
|
+
window.dispatchEvent(new CustomEvent("devtools-toggle"));
|
|
2366
|
+
}
|
|
2367
|
+
export {
|
|
2368
|
+
dev_tools_default as DevTools,
|
|
2369
|
+
DevToolsConfigProvider,
|
|
2370
|
+
DevToolsProvider,
|
|
2371
|
+
NimbitDevTools,
|
|
2372
|
+
index_default as default,
|
|
2373
|
+
toggleDevTools,
|
|
2374
|
+
useDevTools,
|
|
2375
|
+
useDevToolsConfig
|
|
2376
|
+
};
|
|
2377
|
+
//# sourceMappingURL=index.js.map
|