@humbdb/ui 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/LICENSE +21 -0
- package/README.md +6 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +1221 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1221 @@
|
|
|
1
|
+
// src/components/console-log.tsx
|
|
2
|
+
import { Trash2 } from "lucide-react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var LEVEL_COLOR = {
|
|
5
|
+
info: void 0,
|
|
6
|
+
warn: "var(--c-amber)",
|
|
7
|
+
error: "var(--c-red)"
|
|
8
|
+
};
|
|
9
|
+
function formatTime(timestamp) {
|
|
10
|
+
return new Date(timestamp).toLocaleTimeString("en-US", { hour12: false });
|
|
11
|
+
}
|
|
12
|
+
function ConsoleLog({ events, onClear }) {
|
|
13
|
+
return /* @__PURE__ */ jsxs(
|
|
14
|
+
"div",
|
|
15
|
+
{
|
|
16
|
+
"data-testid": "console-log",
|
|
17
|
+
className: "flex h-full flex-col overflow-hidden rounded-[3px] border border-border",
|
|
18
|
+
children: [
|
|
19
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2", children: [
|
|
20
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] uppercase tracking-widest text-muted-foreground", children: "Console" }),
|
|
21
|
+
/* @__PURE__ */ jsxs(
|
|
22
|
+
"button",
|
|
23
|
+
{
|
|
24
|
+
type: "button",
|
|
25
|
+
onClick: onClear,
|
|
26
|
+
className: "ml-auto flex items-center gap-1 rounded-[3px] px-2 py-1 text-[11px] text-muted-foreground hover:bg-accent",
|
|
27
|
+
children: [
|
|
28
|
+
/* @__PURE__ */ jsx(Trash2, { className: "h-2.5 w-2.5" }),
|
|
29
|
+
"Clear"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
] }),
|
|
34
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-0.5 overflow-auto p-3", children: [
|
|
35
|
+
events.length === 0 ? /* @__PURE__ */ jsx("p", { className: "font-mono text-[11px] text-muted-foreground/50", children: "No events yet." }) : events.map((event) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 py-0.5 font-mono text-[11px]", children: [
|
|
36
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 tabular-nums text-muted-foreground/30", children: formatTime(event.timestamp) }),
|
|
37
|
+
/* @__PURE__ */ jsx(
|
|
38
|
+
"span",
|
|
39
|
+
{
|
|
40
|
+
className: "w-9 shrink-0 text-right text-[10px]",
|
|
41
|
+
style: { color: LEVEL_COLOR[event.level] },
|
|
42
|
+
children: event.level === "info" ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: event.level }) : event.level
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
/* @__PURE__ */ jsx(
|
|
46
|
+
"span",
|
|
47
|
+
{
|
|
48
|
+
className: event.level === "info" ? "text-foreground/65" : "",
|
|
49
|
+
style: { color: LEVEL_COLOR[event.level] },
|
|
50
|
+
children: event.message
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
] }, event.id)),
|
|
54
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 pt-2", children: [
|
|
55
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[11px]", style: { color: "var(--c-green)" }, children: "\u276F" }),
|
|
56
|
+
/* @__PURE__ */ jsx("span", { className: "animate-pulse font-mono text-[11px] text-muted-foreground/25", children: "\u2588" })
|
|
57
|
+
] })
|
|
58
|
+
] })
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/components/files-browser.tsx
|
|
65
|
+
import { File, FileCode2, FolderOpen } from "lucide-react";
|
|
66
|
+
import { useState } from "react";
|
|
67
|
+
|
|
68
|
+
// src/cn.ts
|
|
69
|
+
import { clsx } from "clsx";
|
|
70
|
+
import { twMerge } from "tailwind-merge";
|
|
71
|
+
function cn(...inputs) {
|
|
72
|
+
return twMerge(clsx(inputs));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/components/spinner.tsx
|
|
76
|
+
import { Loader2 } from "lucide-react";
|
|
77
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
78
|
+
function Spinner({ className }) {
|
|
79
|
+
return /* @__PURE__ */ jsx2(
|
|
80
|
+
Loader2,
|
|
81
|
+
{
|
|
82
|
+
className: cn("h-3 w-3 animate-spin text-muted-foreground", className),
|
|
83
|
+
"aria-hidden": "true"
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/components/files-browser.tsx
|
|
89
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
90
|
+
function TreeRow({
|
|
91
|
+
node,
|
|
92
|
+
depth,
|
|
93
|
+
selectedPath,
|
|
94
|
+
onSelectFile
|
|
95
|
+
}) {
|
|
96
|
+
const [open, setOpen] = useState(depth < 1);
|
|
97
|
+
const isSelected = node.type === "file" && node.path === selectedPath;
|
|
98
|
+
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
99
|
+
/* @__PURE__ */ jsxs2(
|
|
100
|
+
"div",
|
|
101
|
+
{
|
|
102
|
+
role: node.type === "file" ? "button" : void 0,
|
|
103
|
+
"aria-pressed": node.type === "file" ? isSelected : void 0,
|
|
104
|
+
className: cn(
|
|
105
|
+
"mx-1 flex cursor-pointer select-none items-center gap-1.5 rounded-[2px] py-[3px] pr-2 hover:bg-sidebar-accent",
|
|
106
|
+
isSelected && "bg-primary/10"
|
|
107
|
+
),
|
|
108
|
+
style: { paddingLeft: `${8 + depth * 14}px` },
|
|
109
|
+
onClick: () => {
|
|
110
|
+
if (node.type === "directory") setOpen((current) => !current);
|
|
111
|
+
else onSelectFile(node.path);
|
|
112
|
+
},
|
|
113
|
+
children: [
|
|
114
|
+
node.type === "directory" ? /* @__PURE__ */ jsx3(
|
|
115
|
+
"svg",
|
|
116
|
+
{
|
|
117
|
+
viewBox: "0 0 24 24",
|
|
118
|
+
className: cn(
|
|
119
|
+
"h-2.5 w-2.5 shrink-0 text-muted-foreground/60 transition-transform",
|
|
120
|
+
open && "rotate-90"
|
|
121
|
+
),
|
|
122
|
+
fill: "none",
|
|
123
|
+
stroke: "currentColor",
|
|
124
|
+
strokeWidth: 2,
|
|
125
|
+
children: /* @__PURE__ */ jsx3("path", { d: "m9 18 6-6-6-6", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
126
|
+
}
|
|
127
|
+
) : /* @__PURE__ */ jsx3("span", { className: "w-2.5 shrink-0" }),
|
|
128
|
+
node.type === "directory" ? /* @__PURE__ */ jsx3(FolderOpen, { className: "h-3 w-3 shrink-0", style: { color: "var(--c-amber)" } }) : /* @__PURE__ */ jsx3(File, { className: "h-3 w-3 shrink-0", style: { color: "var(--c-blue)" } }),
|
|
129
|
+
/* @__PURE__ */ jsx3(
|
|
130
|
+
"span",
|
|
131
|
+
{
|
|
132
|
+
className: cn(
|
|
133
|
+
"truncate font-mono text-[11px] text-foreground/70",
|
|
134
|
+
isSelected ? "text-foreground" : "hover:text-foreground"
|
|
135
|
+
),
|
|
136
|
+
children: node.name
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
),
|
|
142
|
+
node.type === "directory" && open && /* @__PURE__ */ jsx3("div", { children: node.children?.map((child) => /* @__PURE__ */ jsx3(
|
|
143
|
+
TreeRow,
|
|
144
|
+
{
|
|
145
|
+
node: child,
|
|
146
|
+
depth: depth + 1,
|
|
147
|
+
selectedPath,
|
|
148
|
+
onSelectFile
|
|
149
|
+
},
|
|
150
|
+
child.path
|
|
151
|
+
)) })
|
|
152
|
+
] });
|
|
153
|
+
}
|
|
154
|
+
function FilesBrowser({
|
|
155
|
+
tree,
|
|
156
|
+
selectedPath,
|
|
157
|
+
onSelectFile,
|
|
158
|
+
content,
|
|
159
|
+
isContentLoading,
|
|
160
|
+
contentError
|
|
161
|
+
}) {
|
|
162
|
+
const lines = content?.split("\n") ?? [];
|
|
163
|
+
return /* @__PURE__ */ jsxs2(
|
|
164
|
+
"div",
|
|
165
|
+
{
|
|
166
|
+
"data-testid": "files-browser",
|
|
167
|
+
className: "flex h-full overflow-hidden rounded-[3px] border border-border",
|
|
168
|
+
children: [
|
|
169
|
+
/* @__PURE__ */ jsx3("nav", { className: "w-48 shrink-0 overflow-y-auto border-r border-border bg-sidebar py-1", children: tree.map((node) => /* @__PURE__ */ jsx3(
|
|
170
|
+
TreeRow,
|
|
171
|
+
{
|
|
172
|
+
node,
|
|
173
|
+
depth: 0,
|
|
174
|
+
selectedPath,
|
|
175
|
+
onSelectFile
|
|
176
|
+
},
|
|
177
|
+
node.path
|
|
178
|
+
)) }),
|
|
179
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: [
|
|
180
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 items-center gap-2 border-b border-border bg-card px-3 py-2", children: [
|
|
181
|
+
/* @__PURE__ */ jsx3(FileCode2, { className: "h-3 w-3 shrink-0", style: { color: "var(--c-blue)" } }),
|
|
182
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate font-mono text-[11px] text-foreground", children: selectedPath ?? "Select a file" })
|
|
183
|
+
] }),
|
|
184
|
+
isContentLoading ? /* @__PURE__ */ jsxs2("p", { className: "flex items-center gap-1.5 p-3 text-[13px] text-muted-foreground", children: [
|
|
185
|
+
/* @__PURE__ */ jsx3(Spinner, {}),
|
|
186
|
+
" Loading file..."
|
|
187
|
+
] }) : contentError ? /* @__PURE__ */ jsx3("p", { className: "p-3 text-[13px] text-muted-foreground", children: contentError }) : content === void 0 ? /* @__PURE__ */ jsx3("p", { className: "p-3 font-mono text-[11px] text-muted-foreground", children: "No file selected." }) : /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 overflow-auto", children: [
|
|
188
|
+
/* @__PURE__ */ jsx3(
|
|
189
|
+
"div",
|
|
190
|
+
{
|
|
191
|
+
"aria-hidden": "true",
|
|
192
|
+
className: "shrink-0 select-none border-r border-border bg-background pr-3 pt-3 text-right font-mono text-[11px] text-muted-foreground/30",
|
|
193
|
+
style: { minWidth: "44px" },
|
|
194
|
+
children: lines.map((_, index) => /* @__PURE__ */ jsx3("div", { style: { lineHeight: "20px" }, children: index + 1 }, index))
|
|
195
|
+
}
|
|
196
|
+
),
|
|
197
|
+
/* @__PURE__ */ jsx3("pre", { className: "flex-1 whitespace-pre p-3 font-mono text-[12px] leading-5 text-foreground/80", children: content })
|
|
198
|
+
] })
|
|
199
|
+
] })
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/components/table-detail.tsx
|
|
206
|
+
import { Link, Table2 } from "lucide-react";
|
|
207
|
+
|
|
208
|
+
// src/components/type-icon.tsx
|
|
209
|
+
import { Calendar, Hash, ToggleLeft, Type } from "lucide-react";
|
|
210
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
211
|
+
function classify(dataType) {
|
|
212
|
+
const type = dataType.toLowerCase();
|
|
213
|
+
if (type.startsWith("int") || type.startsWith("numeric") || type.startsWith("decimal") || type.startsWith("float") || type.startsWith("double") || type.startsWith("real") || type.startsWith("serial")) {
|
|
214
|
+
return "numeric";
|
|
215
|
+
}
|
|
216
|
+
if (type.startsWith("bool")) return "boolean";
|
|
217
|
+
if (type.startsWith("timestamp") || type.startsWith("date") || type.startsWith("time")) {
|
|
218
|
+
return "datetime";
|
|
219
|
+
}
|
|
220
|
+
if (type.startsWith("char") || type.startsWith("varchar") || type.startsWith("text") || type.startsWith("uuid") || type.startsWith("json")) {
|
|
221
|
+
return "text";
|
|
222
|
+
}
|
|
223
|
+
return "other";
|
|
224
|
+
}
|
|
225
|
+
function TypeIcon({ dataType }) {
|
|
226
|
+
switch (classify(dataType)) {
|
|
227
|
+
case "numeric":
|
|
228
|
+
return /* @__PURE__ */ jsx4(Hash, { className: "h-2.5 w-2.5 shrink-0", style: { color: "var(--c-amber)" } });
|
|
229
|
+
case "text":
|
|
230
|
+
return /* @__PURE__ */ jsx4(Type, { className: "h-2.5 w-2.5 shrink-0", style: { color: "var(--c-blue)" } });
|
|
231
|
+
case "boolean":
|
|
232
|
+
return /* @__PURE__ */ jsx4(ToggleLeft, { className: "h-2.5 w-2.5 shrink-0", style: { color: "var(--c-green)" } });
|
|
233
|
+
case "datetime":
|
|
234
|
+
return /* @__PURE__ */ jsx4(Calendar, { className: "h-2.5 w-2.5 shrink-0", style: { color: "var(--c-purple)" } });
|
|
235
|
+
default:
|
|
236
|
+
return /* @__PURE__ */ jsx4(Hash, { className: "h-2.5 w-2.5 shrink-0 text-muted-foreground" });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/components/table-detail.tsx
|
|
241
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
242
|
+
function TableDetail({ table }) {
|
|
243
|
+
return /* @__PURE__ */ jsxs3(
|
|
244
|
+
"div",
|
|
245
|
+
{
|
|
246
|
+
"data-testid": "table-detail",
|
|
247
|
+
className: "max-w-xl overflow-hidden rounded-[3px] border border-border",
|
|
248
|
+
children: [
|
|
249
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 border-b border-border bg-card px-3 py-2", children: [
|
|
250
|
+
/* @__PURE__ */ jsx5(Table2, { className: "h-3 w-3 shrink-0", style: { color: "var(--c-blue)" } }),
|
|
251
|
+
/* @__PURE__ */ jsx5("span", { className: "font-mono text-[12px] font-medium text-foreground", children: table.name }),
|
|
252
|
+
table.rowCount !== void 0 && /* @__PURE__ */ jsxs3("span", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: [
|
|
253
|
+
"~",
|
|
254
|
+
table.rowCount.toLocaleString(),
|
|
255
|
+
" row",
|
|
256
|
+
table.rowCount === 1 ? "" : "s"
|
|
257
|
+
] })
|
|
258
|
+
] }),
|
|
259
|
+
/* @__PURE__ */ jsx5("div", { className: "bg-background", children: table.columns.map((column, index) => /* @__PURE__ */ jsxs3(
|
|
260
|
+
"div",
|
|
261
|
+
{
|
|
262
|
+
className: "flex items-center gap-2 px-3 py-1.5 font-mono text-[11px] hover:bg-accent/50" + (index !== 0 ? " border-t border-border-subtle" : ""),
|
|
263
|
+
children: [
|
|
264
|
+
/* @__PURE__ */ jsx5(TypeIcon, { dataType: column.dataType }),
|
|
265
|
+
/* @__PURE__ */ jsx5(
|
|
266
|
+
"span",
|
|
267
|
+
{
|
|
268
|
+
className: column.isPrimaryKey || column.isForeignKey ? "" : "text-foreground/80",
|
|
269
|
+
style: {
|
|
270
|
+
color: column.isPrimaryKey ? "var(--c-amber)" : column.isForeignKey ? "var(--c-blue)" : void 0
|
|
271
|
+
},
|
|
272
|
+
children: column.name
|
|
273
|
+
}
|
|
274
|
+
),
|
|
275
|
+
column.isPrimaryKey && /* @__PURE__ */ jsx5(
|
|
276
|
+
"span",
|
|
277
|
+
{
|
|
278
|
+
className: "rounded-[2px] border px-1 text-[8px]",
|
|
279
|
+
style: {
|
|
280
|
+
color: "var(--c-amber)",
|
|
281
|
+
borderColor: "color-mix(in srgb, var(--c-amber) 30%, transparent)"
|
|
282
|
+
},
|
|
283
|
+
children: "PK"
|
|
284
|
+
}
|
|
285
|
+
),
|
|
286
|
+
column.isForeignKey && /* @__PURE__ */ jsxs3(
|
|
287
|
+
"span",
|
|
288
|
+
{
|
|
289
|
+
className: "flex items-center gap-0.5 rounded-[2px] border px-1 text-[8px]",
|
|
290
|
+
style: {
|
|
291
|
+
color: "var(--c-blue)",
|
|
292
|
+
borderColor: "color-mix(in srgb, var(--c-blue) 30%, transparent)"
|
|
293
|
+
},
|
|
294
|
+
children: [
|
|
295
|
+
/* @__PURE__ */ jsx5(Link, { className: "h-2 w-2" }),
|
|
296
|
+
"FK"
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
),
|
|
300
|
+
/* @__PURE__ */ jsx5("span", { className: "ml-auto text-[9px] text-muted-foreground/40", children: column.dataType }),
|
|
301
|
+
column.nullable && /* @__PURE__ */ jsx5("span", { className: "text-[9px] text-muted-foreground/30", children: "null" })
|
|
302
|
+
]
|
|
303
|
+
},
|
|
304
|
+
column.name
|
|
305
|
+
)) }),
|
|
306
|
+
table.indexes && table.indexes.length > 0 && /* @__PURE__ */ jsx5("div", { className: "border-t border-border bg-card px-3 py-2", children: /* @__PURE__ */ jsx5("ul", { className: "space-y-0.5 font-mono text-[10px] text-muted-foreground", children: table.indexes.map((index) => /* @__PURE__ */ jsxs3("li", { children: [
|
|
307
|
+
index.name,
|
|
308
|
+
" (",
|
|
309
|
+
index.columns.join(", "),
|
|
310
|
+
")",
|
|
311
|
+
index.primary ? " \xB7 primary key" : index.unique ? " \xB7 unique" : ""
|
|
312
|
+
] }, index.name)) }) })
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/components/schema-grid.tsx
|
|
319
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
320
|
+
function SchemaGrid({ tables }) {
|
|
321
|
+
return /* @__PURE__ */ jsx6(
|
|
322
|
+
"div",
|
|
323
|
+
{
|
|
324
|
+
"data-testid": "schema-grid",
|
|
325
|
+
className: "grid gap-3",
|
|
326
|
+
style: { gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))" },
|
|
327
|
+
children: tables.map((table) => /* @__PURE__ */ jsx6(TableDetail, { table }, `${table.schema}.${table.name}`))
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/components/schema-tree.tsx
|
|
333
|
+
import { Circle, FolderOpen as FolderOpen2, Table2 as Table22 } from "lucide-react";
|
|
334
|
+
import { useMemo, useState as useState2 } from "react";
|
|
335
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
336
|
+
var STATUS_DOT_COLOR = {
|
|
337
|
+
connected: "var(--c-green)",
|
|
338
|
+
disconnected: "var(--c-red)",
|
|
339
|
+
unconfigured: "rgb(var(--muted-foreground))"
|
|
340
|
+
};
|
|
341
|
+
function buildTree(target, schemas) {
|
|
342
|
+
return {
|
|
343
|
+
id: "connection",
|
|
344
|
+
name: target ?? "not connected",
|
|
345
|
+
type: "connection",
|
|
346
|
+
children: schemas.map((schema) => ({
|
|
347
|
+
id: `schema:${schema.name}`,
|
|
348
|
+
name: schema.name,
|
|
349
|
+
type: "schema",
|
|
350
|
+
children: schema.tables.map((table) => ({
|
|
351
|
+
id: `table:${schema.name}:${table}`,
|
|
352
|
+
name: table,
|
|
353
|
+
type: "table",
|
|
354
|
+
schema: schema.name
|
|
355
|
+
}))
|
|
356
|
+
}))
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function collectMatchIds(node, query, ancestors = []) {
|
|
360
|
+
const result = /* @__PURE__ */ new Set();
|
|
361
|
+
const path = [...ancestors, node.id];
|
|
362
|
+
if (node.name.toLowerCase().includes(query.toLowerCase())) {
|
|
363
|
+
for (const id of path) result.add(id);
|
|
364
|
+
}
|
|
365
|
+
for (const child of node.children ?? []) {
|
|
366
|
+
for (const id of collectMatchIds(child, query, path)) result.add(id);
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
function highlight(text, query) {
|
|
371
|
+
if (!query) return text;
|
|
372
|
+
const index = text.toLowerCase().indexOf(query.toLowerCase());
|
|
373
|
+
if (index === -1) return text;
|
|
374
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
375
|
+
text.slice(0, index),
|
|
376
|
+
/* @__PURE__ */ jsx7("mark", { className: "rounded-[1px] bg-[color-mix(in_srgb,var(--c-blue)_25%,transparent)] text-[var(--c-blue)]", children: text.slice(index, index + query.length) }),
|
|
377
|
+
text.slice(index + query.length)
|
|
378
|
+
] });
|
|
379
|
+
}
|
|
380
|
+
function TreeRow2({
|
|
381
|
+
node,
|
|
382
|
+
depth,
|
|
383
|
+
query,
|
|
384
|
+
matchIds,
|
|
385
|
+
selected,
|
|
386
|
+
onSelect,
|
|
387
|
+
status
|
|
388
|
+
}) {
|
|
389
|
+
const [manualOpen, setManualOpen] = useState2(depth < 2);
|
|
390
|
+
const forceOpen = query.length > 0 && matchIds.has(node.id);
|
|
391
|
+
const open = query.length > 0 ? forceOpen : manualOpen;
|
|
392
|
+
if (query.length > 0 && !matchIds.has(node.id)) return null;
|
|
393
|
+
const hasChildren = Boolean(node.children && node.children.length > 0);
|
|
394
|
+
const isSelected = node.type === "table" && selected?.schema === node.schema && selected?.table === node.name;
|
|
395
|
+
return /* @__PURE__ */ jsxs4("div", { children: [
|
|
396
|
+
/* @__PURE__ */ jsxs4(
|
|
397
|
+
"div",
|
|
398
|
+
{
|
|
399
|
+
role: node.type === "table" ? "button" : void 0,
|
|
400
|
+
"aria-pressed": node.type === "table" ? isSelected : void 0,
|
|
401
|
+
className: cn(
|
|
402
|
+
"mx-1 flex cursor-pointer select-none items-center gap-1.5 rounded-[2px] py-[3px] pr-2 hover:bg-sidebar-accent",
|
|
403
|
+
isSelected && "bg-primary/10"
|
|
404
|
+
),
|
|
405
|
+
style: { paddingLeft: `${8 + depth * 14}px` },
|
|
406
|
+
onClick: () => {
|
|
407
|
+
if (hasChildren) setManualOpen((current) => !current);
|
|
408
|
+
if (node.type === "table" && node.schema) onSelect(node.schema, node.name);
|
|
409
|
+
},
|
|
410
|
+
children: [
|
|
411
|
+
hasChildren ? /* @__PURE__ */ jsx7(
|
|
412
|
+
"svg",
|
|
413
|
+
{
|
|
414
|
+
viewBox: "0 0 24 24",
|
|
415
|
+
className: cn(
|
|
416
|
+
"h-2.5 w-2.5 shrink-0 text-muted-foreground/60 transition-transform",
|
|
417
|
+
open && "rotate-90"
|
|
418
|
+
),
|
|
419
|
+
fill: "none",
|
|
420
|
+
stroke: "currentColor",
|
|
421
|
+
strokeWidth: 2,
|
|
422
|
+
children: /* @__PURE__ */ jsx7("path", { d: "m9 18 6-6-6-6", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
423
|
+
}
|
|
424
|
+
) : /* @__PURE__ */ jsx7("span", { className: "w-2.5 shrink-0" }),
|
|
425
|
+
node.type === "connection" && /* @__PURE__ */ jsx7(
|
|
426
|
+
Circle,
|
|
427
|
+
{
|
|
428
|
+
className: "h-1.5 w-1.5 shrink-0",
|
|
429
|
+
fill: STATUS_DOT_COLOR[status],
|
|
430
|
+
style: { color: STATUS_DOT_COLOR[status] }
|
|
431
|
+
}
|
|
432
|
+
),
|
|
433
|
+
node.type === "schema" && /* @__PURE__ */ jsx7(FolderOpen2, { className: "h-3 w-3 shrink-0", style: { color: "var(--c-amber)" } }),
|
|
434
|
+
node.type === "table" && /* @__PURE__ */ jsx7(Table22, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
|
|
435
|
+
/* @__PURE__ */ jsx7(
|
|
436
|
+
"span",
|
|
437
|
+
{
|
|
438
|
+
className: cn(
|
|
439
|
+
"truncate font-mono text-[11px] text-foreground/70",
|
|
440
|
+
isSelected ? "text-foreground" : "hover:text-foreground"
|
|
441
|
+
),
|
|
442
|
+
children: highlight(node.name, query)
|
|
443
|
+
}
|
|
444
|
+
)
|
|
445
|
+
]
|
|
446
|
+
}
|
|
447
|
+
),
|
|
448
|
+
hasChildren && open && /* @__PURE__ */ jsx7("div", { children: node.children?.map((child) => /* @__PURE__ */ jsx7(
|
|
449
|
+
TreeRow2,
|
|
450
|
+
{
|
|
451
|
+
node: child,
|
|
452
|
+
depth: depth + 1,
|
|
453
|
+
query,
|
|
454
|
+
matchIds,
|
|
455
|
+
selected,
|
|
456
|
+
onSelect,
|
|
457
|
+
status
|
|
458
|
+
},
|
|
459
|
+
child.id
|
|
460
|
+
)) })
|
|
461
|
+
] });
|
|
462
|
+
}
|
|
463
|
+
function SchemaTree({
|
|
464
|
+
target,
|
|
465
|
+
status,
|
|
466
|
+
schemas,
|
|
467
|
+
selected,
|
|
468
|
+
onSelect
|
|
469
|
+
}) {
|
|
470
|
+
const [query, setQuery] = useState2("");
|
|
471
|
+
const tree = useMemo(() => buildTree(target, schemas), [target, schemas]);
|
|
472
|
+
const matchIds = useMemo(
|
|
473
|
+
() => query.trim().length > 1 ? collectMatchIds(tree, query.trim()) : /* @__PURE__ */ new Set(),
|
|
474
|
+
[tree, query]
|
|
475
|
+
);
|
|
476
|
+
const trimmedQuery = query.trim();
|
|
477
|
+
return /* @__PURE__ */ jsxs4("div", { "data-testid": "schema-tree", className: "flex h-full flex-col", children: [
|
|
478
|
+
/* @__PURE__ */ jsx7("div", { className: "shrink-0 border-b border-border px-2 py-2", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5 rounded-[3px] border border-border bg-background px-2 py-1.5", children: [
|
|
479
|
+
/* @__PURE__ */ jsxs4(
|
|
480
|
+
"svg",
|
|
481
|
+
{
|
|
482
|
+
viewBox: "0 0 24 24",
|
|
483
|
+
className: "h-2.5 w-2.5 shrink-0 text-muted-foreground/50",
|
|
484
|
+
fill: "none",
|
|
485
|
+
stroke: "currentColor",
|
|
486
|
+
strokeWidth: 2,
|
|
487
|
+
children: [
|
|
488
|
+
/* @__PURE__ */ jsx7("circle", { cx: "11", cy: "11", r: "8" }),
|
|
489
|
+
/* @__PURE__ */ jsx7("path", { d: "m21 21-4.35-4.35", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
490
|
+
]
|
|
491
|
+
}
|
|
492
|
+
),
|
|
493
|
+
/* @__PURE__ */ jsx7(
|
|
494
|
+
"input",
|
|
495
|
+
{
|
|
496
|
+
type: "text",
|
|
497
|
+
value: query,
|
|
498
|
+
onChange: (event) => setQuery(event.target.value),
|
|
499
|
+
placeholder: "Search tables, schemas...",
|
|
500
|
+
"aria-label": "Search tables",
|
|
501
|
+
className: "w-full min-w-0 bg-transparent font-mono text-[11px] text-foreground outline-none placeholder:text-muted-foreground/40"
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
] }) }),
|
|
505
|
+
/* @__PURE__ */ jsx7("nav", { className: "flex-1 overflow-y-auto py-1", children: trimmedQuery.length > 1 && matchIds.size === 0 ? /* @__PURE__ */ jsx7("div", { className: "px-3 py-4 text-center font-mono text-[11px] text-muted-foreground/40", children: "no results" }) : /* @__PURE__ */ jsx7(
|
|
506
|
+
TreeRow2,
|
|
507
|
+
{
|
|
508
|
+
node: tree,
|
|
509
|
+
depth: 0,
|
|
510
|
+
query: trimmedQuery.length > 1 ? trimmedQuery : "",
|
|
511
|
+
matchIds,
|
|
512
|
+
selected,
|
|
513
|
+
onSelect,
|
|
514
|
+
status
|
|
515
|
+
}
|
|
516
|
+
) })
|
|
517
|
+
] });
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/components/sidebar.tsx
|
|
521
|
+
import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
|
|
522
|
+
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
523
|
+
function Sidebar({
|
|
524
|
+
target,
|
|
525
|
+
status,
|
|
526
|
+
schemas,
|
|
527
|
+
selected,
|
|
528
|
+
onSelect,
|
|
529
|
+
isLoading,
|
|
530
|
+
isError,
|
|
531
|
+
onRetry,
|
|
532
|
+
open,
|
|
533
|
+
onOpenChange
|
|
534
|
+
}) {
|
|
535
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
536
|
+
open && /* @__PURE__ */ jsx8(
|
|
537
|
+
"div",
|
|
538
|
+
{
|
|
539
|
+
className: "fixed inset-0 z-40 bg-black/50 md:hidden",
|
|
540
|
+
onClick: () => onOpenChange(false),
|
|
541
|
+
"aria-hidden": "true"
|
|
542
|
+
}
|
|
543
|
+
),
|
|
544
|
+
/* @__PURE__ */ jsx8(
|
|
545
|
+
"aside",
|
|
546
|
+
{
|
|
547
|
+
className: cn(
|
|
548
|
+
"fixed inset-y-0 left-0 z-50 flex w-64 flex-col border-r border-sidebar-border bg-sidebar transition-transform duration-150",
|
|
549
|
+
"md:static md:z-auto md:translate-x-0 md:transition-none",
|
|
550
|
+
open ? "translate-x-0" : "-translate-x-full",
|
|
551
|
+
!open && "md:w-8"
|
|
552
|
+
),
|
|
553
|
+
children: open ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
554
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2", children: [
|
|
555
|
+
/* @__PURE__ */ jsx8("span", { className: "font-mono text-[9px] uppercase tracking-widest text-muted-foreground/60", children: "Explorer" }),
|
|
556
|
+
/* @__PURE__ */ jsx8(
|
|
557
|
+
"button",
|
|
558
|
+
{
|
|
559
|
+
type: "button",
|
|
560
|
+
"aria-label": "Collapse sidebar",
|
|
561
|
+
onClick: () => onOpenChange(false),
|
|
562
|
+
className: "ml-auto rounded p-0.5 text-muted-foreground hover:bg-sidebar-accent hover:text-foreground",
|
|
563
|
+
children: /* @__PURE__ */ jsx8(PanelLeftClose, { className: "h-3 w-3" })
|
|
564
|
+
}
|
|
565
|
+
)
|
|
566
|
+
] }),
|
|
567
|
+
/* @__PURE__ */ jsx8("div", { className: "min-h-0 flex-1", children: isLoading ? /* @__PURE__ */ jsxs5("p", { className: "flex items-center gap-1.5 px-3 py-2 font-mono text-[11px] text-muted-foreground", children: [
|
|
568
|
+
/* @__PURE__ */ jsx8(Spinner, {}),
|
|
569
|
+
" Loading schemas..."
|
|
570
|
+
] }) : isError ? /* @__PURE__ */ jsxs5("div", { className: "px-3 py-2 font-mono text-[11px] text-muted-foreground", children: [
|
|
571
|
+
"Failed to load schemas.",
|
|
572
|
+
" ",
|
|
573
|
+
/* @__PURE__ */ jsx8("button", { type: "button", onClick: onRetry, className: "text-primary underline", children: "Retry" })
|
|
574
|
+
] }) : /* @__PURE__ */ jsx8(
|
|
575
|
+
SchemaTree,
|
|
576
|
+
{
|
|
577
|
+
target,
|
|
578
|
+
status,
|
|
579
|
+
schemas,
|
|
580
|
+
selected,
|
|
581
|
+
onSelect: (schema, table) => {
|
|
582
|
+
onSelect(schema, table);
|
|
583
|
+
if (window.innerWidth < 768) onOpenChange(false);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
) })
|
|
587
|
+
] }) : /* @__PURE__ */ jsx8("div", { className: "hidden flex-col items-center gap-2 px-1.5 py-2 md:flex", children: /* @__PURE__ */ jsx8(
|
|
588
|
+
"button",
|
|
589
|
+
{
|
|
590
|
+
type: "button",
|
|
591
|
+
"aria-label": "Expand sidebar",
|
|
592
|
+
onClick: () => onOpenChange(true),
|
|
593
|
+
className: "rounded-md p-1 text-muted-foreground hover:bg-sidebar-accent hover:text-foreground",
|
|
594
|
+
children: /* @__PURE__ */ jsx8(PanelLeftOpen, { className: "h-3.5 w-3.5" })
|
|
595
|
+
}
|
|
596
|
+
) })
|
|
597
|
+
}
|
|
598
|
+
)
|
|
599
|
+
] });
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/components/status-bar.tsx
|
|
603
|
+
import { Clock } from "lucide-react";
|
|
604
|
+
import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
605
|
+
var STATUS_LABEL = {
|
|
606
|
+
connected: "connected",
|
|
607
|
+
disconnected: "disconnected",
|
|
608
|
+
unconfigured: "no database"
|
|
609
|
+
};
|
|
610
|
+
var STATUS_COLOR = {
|
|
611
|
+
connected: "var(--c-green)",
|
|
612
|
+
disconnected: "var(--c-red)",
|
|
613
|
+
unconfigured: "rgb(var(--muted-foreground))"
|
|
614
|
+
};
|
|
615
|
+
function Separator() {
|
|
616
|
+
return /* @__PURE__ */ jsx9("span", { className: "text-border", children: "\xB7" });
|
|
617
|
+
}
|
|
618
|
+
function StatusBar({
|
|
619
|
+
status,
|
|
620
|
+
engine,
|
|
621
|
+
engineVersion,
|
|
622
|
+
schema,
|
|
623
|
+
lastQueryMs
|
|
624
|
+
}) {
|
|
625
|
+
const engineLabel = engineVersion ?? engine;
|
|
626
|
+
return /* @__PURE__ */ jsxs6("footer", { className: "flex h-6 shrink-0 items-center justify-between gap-3 border-t border-border bg-sidebar px-3 sm:px-4 font-mono text-[10px]", children: [
|
|
627
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 items-center gap-3 text-muted-foreground/60", children: [
|
|
628
|
+
/* @__PURE__ */ jsx9("span", { style: { color: STATUS_COLOR[status] }, children: STATUS_LABEL[status] }),
|
|
629
|
+
engineLabel && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
630
|
+
/* @__PURE__ */ jsx9(Separator, {}),
|
|
631
|
+
/* @__PURE__ */ jsx9("span", { className: "hidden sm:inline", children: engineLabel })
|
|
632
|
+
] }),
|
|
633
|
+
schema && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
634
|
+
/* @__PURE__ */ jsx9(Separator, {}),
|
|
635
|
+
/* @__PURE__ */ jsx9("span", { className: "hidden truncate sm:inline", children: schema })
|
|
636
|
+
] })
|
|
637
|
+
] }),
|
|
638
|
+
/* @__PURE__ */ jsxs6("div", { className: "hidden shrink-0 items-center gap-3 text-muted-foreground/40 sm:flex", children: [
|
|
639
|
+
/* @__PURE__ */ jsx9("span", { children: "UTF-8" }),
|
|
640
|
+
lastQueryMs !== void 0 && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
641
|
+
/* @__PURE__ */ jsx9(Separator, {}),
|
|
642
|
+
/* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1", children: [
|
|
643
|
+
/* @__PURE__ */ jsx9(Clock, { className: "h-2.5 w-2.5" }),
|
|
644
|
+
lastQueryMs,
|
|
645
|
+
"ms"
|
|
646
|
+
] })
|
|
647
|
+
] })
|
|
648
|
+
] })
|
|
649
|
+
] });
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/components/tab-bar.tsx
|
|
653
|
+
import { FileCode2 as FileCode22, FolderOpen as FolderOpen3, Network, Table2 as Table23, Terminal } from "lucide-react";
|
|
654
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
655
|
+
var TABS = [
|
|
656
|
+
{ id: "sql-editor", label: "SQL Editor", icon: FileCode22 },
|
|
657
|
+
{ id: "tables", label: "Tables", icon: Table23 },
|
|
658
|
+
{ id: "schema", label: "Schema", icon: Network },
|
|
659
|
+
{ id: "files", label: "Files", icon: FolderOpen3 },
|
|
660
|
+
{ id: "console", label: "Console", icon: Terminal }
|
|
661
|
+
];
|
|
662
|
+
function TabBar({ active, onChange }) {
|
|
663
|
+
return /* @__PURE__ */ jsx10(
|
|
664
|
+
"div",
|
|
665
|
+
{
|
|
666
|
+
role: "tablist",
|
|
667
|
+
className: "flex shrink-0 items-end gap-0.5 overflow-x-auto overflow-y-hidden border-b border-border bg-card px-2 pt-1",
|
|
668
|
+
children: TABS.map(({ id, label, icon: Icon }) => {
|
|
669
|
+
const isActive = id === active;
|
|
670
|
+
return /* @__PURE__ */ jsxs7(
|
|
671
|
+
"button",
|
|
672
|
+
{
|
|
673
|
+
type: "button",
|
|
674
|
+
role: "tab",
|
|
675
|
+
"aria-selected": isActive,
|
|
676
|
+
onClick: () => onChange(id),
|
|
677
|
+
className: cn(
|
|
678
|
+
"flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-t-[3px] px-3 py-1.5 text-[11px] font-medium transition-colors",
|
|
679
|
+
isActive ? "-mb-px border border-border border-b-background bg-background text-foreground" : "text-muted-foreground hover:bg-accent/50 hover:text-foreground"
|
|
680
|
+
),
|
|
681
|
+
children: [
|
|
682
|
+
/* @__PURE__ */ jsx10(Icon, { className: "h-3 w-3" }),
|
|
683
|
+
label
|
|
684
|
+
]
|
|
685
|
+
},
|
|
686
|
+
id
|
|
687
|
+
);
|
|
688
|
+
})
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/components/title-bar.tsx
|
|
694
|
+
import { ChevronRight, Menu, Moon, RefreshCw, Settings, Sun } from "lucide-react";
|
|
695
|
+
import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
696
|
+
var STATUS_DOT_COLOR2 = {
|
|
697
|
+
connected: "bg-[var(--c-green)]",
|
|
698
|
+
disconnected: "bg-[var(--c-red)]",
|
|
699
|
+
unconfigured: "bg-muted-foreground"
|
|
700
|
+
};
|
|
701
|
+
var STATUS_LABEL2 = {
|
|
702
|
+
connected: "Connected",
|
|
703
|
+
disconnected: "Disconnected",
|
|
704
|
+
unconfigured: "No database"
|
|
705
|
+
};
|
|
706
|
+
function splitTarget(target) {
|
|
707
|
+
const lastSlash = target.lastIndexOf("/");
|
|
708
|
+
if (lastSlash === -1) return { name: target };
|
|
709
|
+
return { prefix: target.slice(0, lastSlash), name: target.slice(lastSlash + 1) };
|
|
710
|
+
}
|
|
711
|
+
function TitleBar({
|
|
712
|
+
status,
|
|
713
|
+
target,
|
|
714
|
+
theme,
|
|
715
|
+
onToggleTheme,
|
|
716
|
+
onRefresh,
|
|
717
|
+
isRefreshing,
|
|
718
|
+
onToggleSidebar
|
|
719
|
+
}) {
|
|
720
|
+
const breadcrumb = target ? splitTarget(target) : null;
|
|
721
|
+
return /* @__PURE__ */ jsxs8("header", { className: "flex h-9 shrink-0 items-center border-b border-border bg-card px-3 sm:px-4", children: [
|
|
722
|
+
/* @__PURE__ */ jsx11(
|
|
723
|
+
"button",
|
|
724
|
+
{
|
|
725
|
+
type: "button",
|
|
726
|
+
onClick: onToggleSidebar,
|
|
727
|
+
"aria-label": "Toggle sidebar",
|
|
728
|
+
className: "mr-2 rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground md:hidden",
|
|
729
|
+
children: /* @__PURE__ */ jsx11(Menu, { className: "h-4 w-4" })
|
|
730
|
+
}
|
|
731
|
+
),
|
|
732
|
+
/* @__PURE__ */ jsx11("h1", { className: "m-0 mr-4 shrink-0 font-mono text-[13px] font-semibold tracking-tight text-foreground", children: "Humb" }),
|
|
733
|
+
/* @__PURE__ */ jsx11("div", { className: "mr-4 hidden h-3.5 w-px shrink-0 bg-border sm:block" }),
|
|
734
|
+
/* @__PURE__ */ jsx11("div", { className: "flex min-w-0 items-center gap-0 truncate font-mono text-[11px]", children: breadcrumb ? /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
735
|
+
breadcrumb.prefix && /* @__PURE__ */ jsx11("span", { className: "hidden truncate text-muted-foreground/60 sm:inline", children: breadcrumb.prefix }),
|
|
736
|
+
breadcrumb.prefix && /* @__PURE__ */ jsx11(ChevronRight, { className: "mx-1 hidden h-3 w-3 shrink-0 text-border sm:inline" }),
|
|
737
|
+
/* @__PURE__ */ jsx11("span", { className: "truncate font-medium text-foreground/80", children: breadcrumb.name })
|
|
738
|
+
] }) : /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground/60", children: "not connected" }) }),
|
|
739
|
+
/* @__PURE__ */ jsxs8(
|
|
740
|
+
"span",
|
|
741
|
+
{
|
|
742
|
+
"data-testid": "status-badge",
|
|
743
|
+
"data-status": status,
|
|
744
|
+
title: STATUS_LABEL2[status],
|
|
745
|
+
className: "ml-3 flex shrink-0 items-center gap-1.5",
|
|
746
|
+
children: [
|
|
747
|
+
/* @__PURE__ */ jsx11("span", { className: cn("h-1.5 w-1.5 rounded-full", STATUS_DOT_COLOR2[status]) }),
|
|
748
|
+
/* @__PURE__ */ jsx11(
|
|
749
|
+
"span",
|
|
750
|
+
{
|
|
751
|
+
"data-testid": "connection-summary",
|
|
752
|
+
className: "font-mono text-[11px] text-muted-foreground",
|
|
753
|
+
children: STATUS_LABEL2[status]
|
|
754
|
+
}
|
|
755
|
+
)
|
|
756
|
+
]
|
|
757
|
+
}
|
|
758
|
+
),
|
|
759
|
+
/* @__PURE__ */ jsxs8("div", { className: "ml-auto flex shrink-0 items-center gap-0.5", children: [
|
|
760
|
+
/* @__PURE__ */ jsx11(
|
|
761
|
+
"button",
|
|
762
|
+
{
|
|
763
|
+
type: "button",
|
|
764
|
+
onClick: onToggleTheme,
|
|
765
|
+
title: theme === "dark" ? "Switch to light" : "Switch to dark",
|
|
766
|
+
"aria-label": "Toggle theme",
|
|
767
|
+
className: "rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
768
|
+
children: theme === "dark" ? /* @__PURE__ */ jsx11(Sun, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx11(Moon, { className: "h-3.5 w-3.5" })
|
|
769
|
+
}
|
|
770
|
+
),
|
|
771
|
+
/* @__PURE__ */ jsx11(
|
|
772
|
+
"button",
|
|
773
|
+
{
|
|
774
|
+
type: "button",
|
|
775
|
+
onClick: onRefresh,
|
|
776
|
+
disabled: isRefreshing,
|
|
777
|
+
"aria-label": "Refresh",
|
|
778
|
+
className: "rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50",
|
|
779
|
+
children: /* @__PURE__ */ jsx11(RefreshCw, { className: cn("h-3.5 w-3.5", isRefreshing && "animate-spin") })
|
|
780
|
+
}
|
|
781
|
+
),
|
|
782
|
+
/* @__PURE__ */ jsx11(
|
|
783
|
+
"button",
|
|
784
|
+
{
|
|
785
|
+
type: "button",
|
|
786
|
+
"aria-label": "Settings",
|
|
787
|
+
disabled: true,
|
|
788
|
+
className: "cursor-not-allowed rounded-md p-1.5 text-muted-foreground opacity-50",
|
|
789
|
+
children: /* @__PURE__ */ jsx11(Settings, { className: "h-3.5 w-3.5" })
|
|
790
|
+
}
|
|
791
|
+
)
|
|
792
|
+
] })
|
|
793
|
+
] });
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/components/rows-table.tsx
|
|
797
|
+
import {
|
|
798
|
+
ArrowUpDown,
|
|
799
|
+
ChevronLeft,
|
|
800
|
+
ChevronRight as ChevronRight2,
|
|
801
|
+
Copy,
|
|
802
|
+
Download,
|
|
803
|
+
RefreshCw as RefreshCw2,
|
|
804
|
+
Search,
|
|
805
|
+
X
|
|
806
|
+
} from "lucide-react";
|
|
807
|
+
import { useMemo as useMemo2, useState as useState3 } from "react";
|
|
808
|
+
|
|
809
|
+
// src/format-cell.ts
|
|
810
|
+
function formatCell(value) {
|
|
811
|
+
if (value === null || value === void 0) {
|
|
812
|
+
return "";
|
|
813
|
+
}
|
|
814
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/components/rows-table.tsx
|
|
818
|
+
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
819
|
+
function toCsv(columns, rows) {
|
|
820
|
+
const escape = (value) => {
|
|
821
|
+
const text = formatCell(value);
|
|
822
|
+
return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
823
|
+
};
|
|
824
|
+
const lines = [columns.map(escape).join(",")];
|
|
825
|
+
for (const row of rows) lines.push(columns.map((column) => escape(row[column])).join(","));
|
|
826
|
+
return lines.join("\n");
|
|
827
|
+
}
|
|
828
|
+
function downloadCsv(filename, csv) {
|
|
829
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
|
|
830
|
+
const url = URL.createObjectURL(blob);
|
|
831
|
+
const link = document.createElement("a");
|
|
832
|
+
link.href = url;
|
|
833
|
+
link.download = filename;
|
|
834
|
+
link.click();
|
|
835
|
+
URL.revokeObjectURL(url);
|
|
836
|
+
}
|
|
837
|
+
function RowsTable({
|
|
838
|
+
rowPage,
|
|
839
|
+
columns = [],
|
|
840
|
+
tableName,
|
|
841
|
+
approxRowCount,
|
|
842
|
+
page,
|
|
843
|
+
canGoPrevious,
|
|
844
|
+
canGoNext,
|
|
845
|
+
onPrevious,
|
|
846
|
+
onNext,
|
|
847
|
+
onRefresh
|
|
848
|
+
}) {
|
|
849
|
+
const [search, setSearch] = useState3("");
|
|
850
|
+
const [sortCol, setSortCol] = useState3(null);
|
|
851
|
+
const [sortDir, setSortDir] = useState3(null);
|
|
852
|
+
const [selected, setSelected] = useState3(/* @__PURE__ */ new Set());
|
|
853
|
+
const columnByName = useMemo2(
|
|
854
|
+
() => new Map(columns.map((column) => [column.name, column])),
|
|
855
|
+
[columns]
|
|
856
|
+
);
|
|
857
|
+
const indexed = useMemo2(() => rowPage.rows.map((row, index) => ({ row, index })), [rowPage.rows]);
|
|
858
|
+
const filtered = useMemo2(() => {
|
|
859
|
+
const query = search.trim().toLowerCase();
|
|
860
|
+
if (!query) return indexed;
|
|
861
|
+
return indexed.filter(
|
|
862
|
+
({ row }) => Object.values(row).some((value) => formatCell(value).toLowerCase().includes(query))
|
|
863
|
+
);
|
|
864
|
+
}, [indexed, search]);
|
|
865
|
+
const sorted = useMemo2(() => {
|
|
866
|
+
if (!sortCol || !sortDir) return filtered;
|
|
867
|
+
const copy = [...filtered];
|
|
868
|
+
copy.sort((a, b) => {
|
|
869
|
+
const left = formatCell(a.row[sortCol]);
|
|
870
|
+
const right = formatCell(b.row[sortCol]);
|
|
871
|
+
const cmp = left < right ? -1 : left > right ? 1 : 0;
|
|
872
|
+
return sortDir === "asc" ? cmp : -cmp;
|
|
873
|
+
});
|
|
874
|
+
return copy;
|
|
875
|
+
}, [filtered, sortCol, sortDir]);
|
|
876
|
+
function handleSort(column) {
|
|
877
|
+
if (sortCol !== column) {
|
|
878
|
+
setSortCol(column);
|
|
879
|
+
setSortDir("asc");
|
|
880
|
+
} else if (sortDir === "asc") {
|
|
881
|
+
setSortDir("desc");
|
|
882
|
+
} else {
|
|
883
|
+
setSortCol(null);
|
|
884
|
+
setSortDir(null);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function toggleRow(index) {
|
|
888
|
+
setSelected((current) => {
|
|
889
|
+
const next = new Set(current);
|
|
890
|
+
if (next.has(index)) next.delete(index);
|
|
891
|
+
else next.add(index);
|
|
892
|
+
return next;
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
async function copySelected() {
|
|
896
|
+
const rows = sorted.filter(({ index }) => selected.has(index)).map(({ row }) => row);
|
|
897
|
+
await navigator.clipboard.writeText(toCsv(rowPage.columns, rows));
|
|
898
|
+
}
|
|
899
|
+
function exportCsv() {
|
|
900
|
+
downloadCsv(
|
|
901
|
+
`${tableName ?? "rows"}.csv`,
|
|
902
|
+
toCsv(
|
|
903
|
+
rowPage.columns,
|
|
904
|
+
sorted.map(({ row }) => row)
|
|
905
|
+
)
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex h-full flex-col", children: [
|
|
909
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex shrink-0 flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: [
|
|
910
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5 rounded-[3px] bg-accent px-2 py-1 font-mono text-[11px] text-muted-foreground", children: [
|
|
911
|
+
/* @__PURE__ */ jsx12(Search, { className: "h-2.5 w-2.5" }),
|
|
912
|
+
/* @__PURE__ */ jsx12(
|
|
913
|
+
"input",
|
|
914
|
+
{
|
|
915
|
+
value: search,
|
|
916
|
+
onChange: (event) => setSearch(event.target.value),
|
|
917
|
+
placeholder: "Filter this page...",
|
|
918
|
+
"aria-label": "Filter this page",
|
|
919
|
+
title: "Filters only the rows currently loaded on this page, not the whole table",
|
|
920
|
+
className: "w-28 min-w-0 bg-transparent text-foreground outline-none placeholder:text-muted-foreground sm:w-36"
|
|
921
|
+
}
|
|
922
|
+
),
|
|
923
|
+
search && /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setSearch(""), "aria-label": "Clear filter", children: /* @__PURE__ */ jsx12(X, { className: "h-2.5 w-2.5" }) })
|
|
924
|
+
] }),
|
|
925
|
+
/* @__PURE__ */ jsxs9("div", { className: "ml-auto flex items-center gap-2", children: [
|
|
926
|
+
selected.size > 0 && /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
927
|
+
/* @__PURE__ */ jsxs9("span", { className: "font-mono text-[10px]", style: { color: "var(--c-blue)" }, children: [
|
|
928
|
+
selected.size,
|
|
929
|
+
" selected"
|
|
930
|
+
] }),
|
|
931
|
+
/* @__PURE__ */ jsxs9(
|
|
932
|
+
"button",
|
|
933
|
+
{
|
|
934
|
+
type: "button",
|
|
935
|
+
onClick: () => void copySelected(),
|
|
936
|
+
className: "flex items-center gap-1 rounded-[3px] px-2 py-1 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
937
|
+
children: [
|
|
938
|
+
/* @__PURE__ */ jsx12(Copy, { className: "h-3 w-3" }),
|
|
939
|
+
" Copy as CSV"
|
|
940
|
+
]
|
|
941
|
+
}
|
|
942
|
+
)
|
|
943
|
+
] }),
|
|
944
|
+
/* @__PURE__ */ jsx12(
|
|
945
|
+
"button",
|
|
946
|
+
{
|
|
947
|
+
type: "button",
|
|
948
|
+
onClick: exportCsv,
|
|
949
|
+
"aria-label": "Export this page as CSV",
|
|
950
|
+
title: "Exports the rows currently loaded on this page, not the whole table",
|
|
951
|
+
className: "rounded-[3px] p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
952
|
+
children: /* @__PURE__ */ jsx12(Download, { className: "h-3 w-3" })
|
|
953
|
+
}
|
|
954
|
+
),
|
|
955
|
+
onRefresh && /* @__PURE__ */ jsx12(
|
|
956
|
+
"button",
|
|
957
|
+
{
|
|
958
|
+
type: "button",
|
|
959
|
+
onClick: onRefresh,
|
|
960
|
+
"aria-label": "Refresh rows",
|
|
961
|
+
className: "rounded-[3px] p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
962
|
+
children: /* @__PURE__ */ jsx12(RefreshCw2, { className: "h-3 w-3" })
|
|
963
|
+
}
|
|
964
|
+
)
|
|
965
|
+
] })
|
|
966
|
+
] }),
|
|
967
|
+
columns.length > 0 && /* @__PURE__ */ jsxs9("div", { className: "flex shrink-0 overflow-x-auto overflow-y-hidden border-b border-border bg-card", children: [
|
|
968
|
+
/* @__PURE__ */ jsx12("div", { className: "w-8 shrink-0 border-r border-border" }),
|
|
969
|
+
/* @__PURE__ */ jsx12("div", { className: "w-8 shrink-0 border-r border-border" }),
|
|
970
|
+
rowPage.columns.map((columnName) => {
|
|
971
|
+
const meta = columnByName.get(columnName);
|
|
972
|
+
return /* @__PURE__ */ jsxs9(
|
|
973
|
+
"div",
|
|
974
|
+
{
|
|
975
|
+
className: "flex min-w-[120px] shrink-0 items-center gap-1 whitespace-nowrap border-r border-border px-3 py-1 font-mono text-[9px] text-muted-foreground",
|
|
976
|
+
children: [
|
|
977
|
+
meta && /* @__PURE__ */ jsx12(TypeIcon, { dataType: meta.dataType }),
|
|
978
|
+
/* @__PURE__ */ jsx12("span", { children: meta?.dataType ?? "unknown" }),
|
|
979
|
+
meta?.isPrimaryKey && /* @__PURE__ */ jsx12("span", { className: "ml-1 font-bold", style: { color: "var(--c-amber)" }, children: "PK" })
|
|
980
|
+
]
|
|
981
|
+
},
|
|
982
|
+
columnName
|
|
983
|
+
);
|
|
984
|
+
})
|
|
985
|
+
] }),
|
|
986
|
+
rowPage.rows.length === 0 ? /* @__PURE__ */ jsx12("div", { "data-testid": "rows-table", className: "flex-1 p-3", children: /* @__PURE__ */ jsx12("p", { className: "font-mono text-[11px] text-muted-foreground", children: "No rows in this table." }) }) : /* @__PURE__ */ jsx12("div", { "data-testid": "rows-table", className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxs9("table", { className: "w-full border-collapse font-mono text-[11px]", children: [
|
|
987
|
+
/* @__PURE__ */ jsx12("thead", { className: "sticky top-0 z-10 bg-card", children: /* @__PURE__ */ jsxs9("tr", { children: [
|
|
988
|
+
/* @__PURE__ */ jsx12("th", { className: "w-8 border-b border-r border-border px-2 py-2" }),
|
|
989
|
+
/* @__PURE__ */ jsx12("th", { className: "w-8 border-b border-r border-border px-2 py-2 text-right font-normal text-muted-foreground/40", children: "#" }),
|
|
990
|
+
rowPage.columns.map((columnName) => /* @__PURE__ */ jsx12(
|
|
991
|
+
"th",
|
|
992
|
+
{
|
|
993
|
+
onClick: () => handleSort(columnName),
|
|
994
|
+
className: "group cursor-pointer whitespace-nowrap border-b border-r border-border px-3 py-2 text-left font-medium text-muted-foreground hover:text-foreground",
|
|
995
|
+
children: /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5", children: [
|
|
996
|
+
columnName,
|
|
997
|
+
/* @__PURE__ */ jsx12(
|
|
998
|
+
ArrowUpDown,
|
|
999
|
+
{
|
|
1000
|
+
className: cn(
|
|
1001
|
+
"h-2.5 w-2.5 transition-opacity",
|
|
1002
|
+
sortCol === columnName ? "text-primary opacity-100" : "opacity-0 group-hover:opacity-40"
|
|
1003
|
+
)
|
|
1004
|
+
}
|
|
1005
|
+
)
|
|
1006
|
+
] })
|
|
1007
|
+
},
|
|
1008
|
+
columnName
|
|
1009
|
+
))
|
|
1010
|
+
] }) }),
|
|
1011
|
+
/* @__PURE__ */ jsx12("tbody", { children: sorted.map(({ row, index }, displayIndex) => /* @__PURE__ */ jsxs9(
|
|
1012
|
+
"tr",
|
|
1013
|
+
{
|
|
1014
|
+
onClick: () => toggleRow(index),
|
|
1015
|
+
className: cn(
|
|
1016
|
+
"cursor-pointer border-b border-border-subtle hover:bg-accent/40",
|
|
1017
|
+
selected.has(index) && "bg-primary/5"
|
|
1018
|
+
),
|
|
1019
|
+
children: [
|
|
1020
|
+
/* @__PURE__ */ jsx12("td", { className: "w-8 border-r border-border-subtle px-2 py-1.5 text-center", children: /* @__PURE__ */ jsx12(
|
|
1021
|
+
"input",
|
|
1022
|
+
{
|
|
1023
|
+
type: "checkbox",
|
|
1024
|
+
checked: selected.has(index),
|
|
1025
|
+
onChange: () => toggleRow(index),
|
|
1026
|
+
onClick: (event) => event.stopPropagation(),
|
|
1027
|
+
className: "h-3 w-3 accent-primary",
|
|
1028
|
+
"aria-label": `Select row ${displayIndex + 1}`
|
|
1029
|
+
}
|
|
1030
|
+
) }),
|
|
1031
|
+
/* @__PURE__ */ jsx12("td", { className: "w-8 border-r border-border-subtle px-2 py-1.5 text-right text-muted-foreground/30", children: displayIndex + 1 }),
|
|
1032
|
+
rowPage.columns.map((columnName) => /* @__PURE__ */ jsx12(
|
|
1033
|
+
"td",
|
|
1034
|
+
{
|
|
1035
|
+
className: "whitespace-nowrap border-r border-border-subtle px-3 py-1.5 text-foreground/80",
|
|
1036
|
+
children: row[columnName] === null || row[columnName] === void 0 ? /* @__PURE__ */ jsx12("span", { className: "italic text-muted-foreground/30", children: "null" }) : formatCell(row[columnName])
|
|
1037
|
+
},
|
|
1038
|
+
columnName
|
|
1039
|
+
))
|
|
1040
|
+
]
|
|
1041
|
+
},
|
|
1042
|
+
index
|
|
1043
|
+
)) })
|
|
1044
|
+
] }) }),
|
|
1045
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex shrink-0 items-center justify-between border-t border-border bg-card px-3 py-1.5", children: [
|
|
1046
|
+
/* @__PURE__ */ jsxs9("span", { className: "font-mono text-[10px] text-muted-foreground", children: [
|
|
1047
|
+
sorted.length.toLocaleString(),
|
|
1048
|
+
" of",
|
|
1049
|
+
" ",
|
|
1050
|
+
approxRowCount !== void 0 ? `~${approxRowCount.toLocaleString()}` : sorted.length.toLocaleString(),
|
|
1051
|
+
" ",
|
|
1052
|
+
"rows",
|
|
1053
|
+
tableName && /* @__PURE__ */ jsxs9(Fragment5, { children: [
|
|
1054
|
+
" \xB7 ",
|
|
1055
|
+
tableName
|
|
1056
|
+
] })
|
|
1057
|
+
] }),
|
|
1058
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1", children: [
|
|
1059
|
+
/* @__PURE__ */ jsx12(
|
|
1060
|
+
"button",
|
|
1061
|
+
{
|
|
1062
|
+
type: "button",
|
|
1063
|
+
disabled: !canGoPrevious,
|
|
1064
|
+
onClick: onPrevious,
|
|
1065
|
+
"aria-label": "Previous page",
|
|
1066
|
+
className: "rounded-[2px] p-0.5 text-muted-foreground hover:bg-accent disabled:opacity-30",
|
|
1067
|
+
children: /* @__PURE__ */ jsx12(ChevronLeft, { className: "h-3.5 w-3.5" })
|
|
1068
|
+
}
|
|
1069
|
+
),
|
|
1070
|
+
/* @__PURE__ */ jsxs9("span", { className: "px-1 font-mono text-[10px] text-muted-foreground", children: [
|
|
1071
|
+
"Page ",
|
|
1072
|
+
page + 1
|
|
1073
|
+
] }),
|
|
1074
|
+
/* @__PURE__ */ jsx12(
|
|
1075
|
+
"button",
|
|
1076
|
+
{
|
|
1077
|
+
type: "button",
|
|
1078
|
+
disabled: !canGoNext,
|
|
1079
|
+
onClick: onNext,
|
|
1080
|
+
"aria-label": "Next page",
|
|
1081
|
+
className: "rounded-[2px] p-0.5 text-muted-foreground hover:bg-accent disabled:opacity-30",
|
|
1082
|
+
children: /* @__PURE__ */ jsx12(ChevronRight2, { className: "h-3.5 w-3.5" })
|
|
1083
|
+
}
|
|
1084
|
+
)
|
|
1085
|
+
] })
|
|
1086
|
+
] })
|
|
1087
|
+
] });
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/components/query-runner.tsx
|
|
1091
|
+
import { Play } from "lucide-react";
|
|
1092
|
+
import { useRef } from "react";
|
|
1093
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1094
|
+
function QueryRunner({
|
|
1095
|
+
sql,
|
|
1096
|
+
onSqlChange,
|
|
1097
|
+
onRun,
|
|
1098
|
+
isRunning,
|
|
1099
|
+
result,
|
|
1100
|
+
error
|
|
1101
|
+
}) {
|
|
1102
|
+
const canRun = !isRunning && sql.trim().length > 0;
|
|
1103
|
+
const lineCount = sql.split("\n").length;
|
|
1104
|
+
const gutterRef = useRef(null);
|
|
1105
|
+
function handleKeyDown(event) {
|
|
1106
|
+
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
|
|
1107
|
+
event.preventDefault();
|
|
1108
|
+
if (canRun) onRun();
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function handleScroll(event) {
|
|
1112
|
+
if (gutterRef.current) {
|
|
1113
|
+
gutterRef.current.scrollTop = event.currentTarget.scrollTop;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return /* @__PURE__ */ jsxs10(
|
|
1117
|
+
"div",
|
|
1118
|
+
{
|
|
1119
|
+
"data-testid": "query-runner",
|
|
1120
|
+
className: "flex h-full flex-col overflow-hidden rounded-[3px] border border-border",
|
|
1121
|
+
children: [
|
|
1122
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-2", children: [
|
|
1123
|
+
/* @__PURE__ */ jsxs10(
|
|
1124
|
+
"button",
|
|
1125
|
+
{
|
|
1126
|
+
type: "button",
|
|
1127
|
+
onClick: onRun,
|
|
1128
|
+
disabled: !canRun,
|
|
1129
|
+
className: "flex items-center gap-1.5 rounded-[3px] bg-primary px-3 py-1 text-[11px] font-medium text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-50",
|
|
1130
|
+
children: [
|
|
1131
|
+
isRunning ? /* @__PURE__ */ jsx13(Spinner, { className: "h-2.5 w-2.5 text-primary-foreground" }) : /* @__PURE__ */ jsx13(Play, { className: "h-2.5 w-2.5" }),
|
|
1132
|
+
isRunning ? "Running..." : "Run"
|
|
1133
|
+
]
|
|
1134
|
+
}
|
|
1135
|
+
),
|
|
1136
|
+
/* @__PURE__ */ jsx13("span", { className: "hidden font-mono text-[10px] text-muted-foreground sm:inline", children: "\u2318 Enter" }),
|
|
1137
|
+
/* @__PURE__ */ jsxs10("span", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: [
|
|
1138
|
+
lineCount,
|
|
1139
|
+
" line",
|
|
1140
|
+
lineCount === 1 ? "" : "s"
|
|
1141
|
+
] })
|
|
1142
|
+
] }),
|
|
1143
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex min-h-[8rem] flex-1 overflow-hidden", children: [
|
|
1144
|
+
/* @__PURE__ */ jsx13(
|
|
1145
|
+
"div",
|
|
1146
|
+
{
|
|
1147
|
+
ref: gutterRef,
|
|
1148
|
+
"aria-hidden": "true",
|
|
1149
|
+
className: "shrink-0 select-none overflow-hidden border-r border-border bg-background pr-3 pt-3 text-right font-mono text-[11px] text-muted-foreground/30",
|
|
1150
|
+
style: { minWidth: "44px" },
|
|
1151
|
+
children: Array.from({ length: lineCount }, (_, index) => /* @__PURE__ */ jsx13("div", { style: { lineHeight: "20px" }, children: index + 1 }, index))
|
|
1152
|
+
}
|
|
1153
|
+
),
|
|
1154
|
+
/* @__PURE__ */ jsx13(
|
|
1155
|
+
"textarea",
|
|
1156
|
+
{
|
|
1157
|
+
value: sql,
|
|
1158
|
+
onChange: (event) => onSqlChange(event.target.value),
|
|
1159
|
+
onKeyDown: handleKeyDown,
|
|
1160
|
+
onScroll: handleScroll,
|
|
1161
|
+
placeholder: "SELECT * FROM my_table LIMIT 10",
|
|
1162
|
+
spellCheck: false,
|
|
1163
|
+
className: "flex-1 resize-none overflow-auto bg-background p-3 font-mono text-[12px] leading-5 text-foreground outline-none",
|
|
1164
|
+
style: { caretColor: "var(--c-blue)" }
|
|
1165
|
+
}
|
|
1166
|
+
)
|
|
1167
|
+
] }),
|
|
1168
|
+
error && /* @__PURE__ */ jsx13(
|
|
1169
|
+
"p",
|
|
1170
|
+
{
|
|
1171
|
+
"data-testid": "query-error",
|
|
1172
|
+
className: "border-t border-border px-3 py-2 font-mono text-[11px]",
|
|
1173
|
+
style: { color: "var(--c-red)" },
|
|
1174
|
+
children: error
|
|
1175
|
+
}
|
|
1176
|
+
),
|
|
1177
|
+
result && !error && /* @__PURE__ */ jsx13(
|
|
1178
|
+
"div",
|
|
1179
|
+
{
|
|
1180
|
+
"data-testid": "query-result",
|
|
1181
|
+
className: "max-h-64 shrink-0 overflow-auto border-t border-border",
|
|
1182
|
+
children: result.rows.length === 0 ? /* @__PURE__ */ jsx13("p", { className: "p-3 font-mono text-[11px] text-muted-foreground", children: "Query returned no rows." }) : /* @__PURE__ */ jsxs10("table", { className: "w-full border-collapse font-mono text-[11px]", children: [
|
|
1183
|
+
/* @__PURE__ */ jsx13("thead", { className: "sticky top-0 bg-card", children: /* @__PURE__ */ jsx13("tr", { children: result.columns.map((column) => /* @__PURE__ */ jsx13(
|
|
1184
|
+
"th",
|
|
1185
|
+
{
|
|
1186
|
+
className: "whitespace-nowrap border-b border-r border-border px-3 py-1.5 text-left font-medium text-muted-foreground",
|
|
1187
|
+
children: column
|
|
1188
|
+
},
|
|
1189
|
+
column
|
|
1190
|
+
)) }) }),
|
|
1191
|
+
/* @__PURE__ */ jsx13("tbody", { children: result.rows.map((row, rowIndex) => /* @__PURE__ */ jsx13("tr", { className: "border-b border-border-subtle", children: result.columns.map((column) => /* @__PURE__ */ jsx13(
|
|
1192
|
+
"td",
|
|
1193
|
+
{
|
|
1194
|
+
className: "whitespace-nowrap border-r border-border-subtle px-3 py-1.5 text-foreground/80",
|
|
1195
|
+
children: formatCell(row[column])
|
|
1196
|
+
},
|
|
1197
|
+
column
|
|
1198
|
+
)) }, rowIndex)) })
|
|
1199
|
+
] })
|
|
1200
|
+
}
|
|
1201
|
+
)
|
|
1202
|
+
]
|
|
1203
|
+
}
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
export {
|
|
1207
|
+
ConsoleLog,
|
|
1208
|
+
FilesBrowser,
|
|
1209
|
+
QueryRunner,
|
|
1210
|
+
RowsTable,
|
|
1211
|
+
SchemaGrid,
|
|
1212
|
+
SchemaTree,
|
|
1213
|
+
Sidebar,
|
|
1214
|
+
Spinner,
|
|
1215
|
+
StatusBar,
|
|
1216
|
+
TabBar,
|
|
1217
|
+
TableDetail,
|
|
1218
|
+
TitleBar,
|
|
1219
|
+
TypeIcon
|
|
1220
|
+
};
|
|
1221
|
+
//# sourceMappingURL=index.js.map
|