@geekmidas/studio 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -0
- package/dist/server/hono.cjs +5 -5
- package/dist/server/hono.cjs.map +1 -1
- package/dist/server/hono.mjs +5 -5
- package/dist/server/hono.mjs.map +1 -1
- package/package.json +3 -3
- package/src/ui-assets.ts +5 -5
- package/ui/node_modules/.bin/tsc +2 -2
- package/ui/node_modules/.bin/tsserver +2 -2
- package/ui/node_modules/.bin/vite +2 -2
- package/ui/src/App.tsx +146 -47
- package/ui/src/components/FilterPanel.tsx +213 -0
- package/ui/src/components/RowDetail.tsx +85 -79
- package/ui/src/components/TableList.tsx +37 -30
- package/ui/src/components/TableView.tsx +374 -74
- package/ui/src/styles.css +167 -9
- package/ui/tsconfig.tsbuildinfo +1 -1
- package/ui/node_modules/.bin/browserslist +0 -21
- package/ui/node_modules/.bin/jiti +0 -21
- package/ui/node_modules/.bin/terser +0 -21
- package/ui/node_modules/.bin/tsx +0 -21
|
@@ -18,96 +18,102 @@ export function RowDetail({ row, columns, onClose }: RowDetailProps) {
|
|
|
18
18
|
const getValueClass = (value: unknown): string => {
|
|
19
19
|
if (value === null) return 'text-slate-500 italic';
|
|
20
20
|
if (typeof value === 'boolean')
|
|
21
|
-
return value ? 'text-
|
|
21
|
+
return value ? 'text-emerald-400' : 'text-red-400';
|
|
22
22
|
if (typeof value === 'number') return 'text-blue-400';
|
|
23
|
-
if (typeof value === 'string' && value.length > 100)
|
|
24
|
-
return 'text-slate-300 text-xs';
|
|
25
23
|
return 'text-slate-300';
|
|
26
24
|
};
|
|
27
25
|
|
|
28
26
|
return (
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
<aside className="w-96 bg-studio-surface border-l border-studio-border flex flex-col shrink-0 overflow-hidden">
|
|
28
|
+
{/* Header */}
|
|
29
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-studio-border shrink-0">
|
|
30
|
+
<h2 className="text-sm font-medium text-slate-200">Row Details</h2>
|
|
31
|
+
<button
|
|
32
|
+
onClick={onClose}
|
|
33
|
+
className="p-1 hover:bg-studio-hover rounded transition-colors"
|
|
34
|
+
>
|
|
35
|
+
<svg
|
|
36
|
+
className="w-4 h-4 text-slate-400"
|
|
37
|
+
fill="none"
|
|
38
|
+
stroke="currentColor"
|
|
39
|
+
viewBox="0 0 24 24"
|
|
37
40
|
>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const isLongValue =
|
|
48
|
-
typeof value === 'string' && value.length > 100;
|
|
49
|
-
const isJson = typeof value === 'object' && value !== null;
|
|
41
|
+
<path
|
|
42
|
+
strokeLinecap="round"
|
|
43
|
+
strokeLinejoin="round"
|
|
44
|
+
strokeWidth={2}
|
|
45
|
+
d="M6 18L18 6M6 6l12 12"
|
|
46
|
+
/>
|
|
47
|
+
</svg>
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
</span>
|
|
58
|
-
<span className="text-xs text-slate-500">
|
|
59
|
-
{col.rawType}
|
|
60
|
-
</span>
|
|
61
|
-
{col.isPrimaryKey && (
|
|
62
|
-
<span className="text-xs text-amber-400 bg-amber-400/10 px-1.5 py-0.5 rounded">
|
|
63
|
-
PK
|
|
64
|
-
</span>
|
|
65
|
-
)}
|
|
66
|
-
{col.isForeignKey && (
|
|
67
|
-
<span className="text-xs text-blue-400 bg-blue-400/10 px-1.5 py-0.5 rounded">
|
|
68
|
-
FK
|
|
69
|
-
</span>
|
|
70
|
-
)}
|
|
71
|
-
{col.nullable && (
|
|
72
|
-
<span className="text-xs text-slate-500">nullable</span>
|
|
73
|
-
)}
|
|
74
|
-
</div>
|
|
51
|
+
{/* Content */}
|
|
52
|
+
<div className="flex-1 overflow-y-auto">
|
|
53
|
+
{columns.map((col) => {
|
|
54
|
+
const value = row[col.name];
|
|
55
|
+
const isLongValue = typeof value === 'string' && value.length > 50;
|
|
56
|
+
const isJson = typeof value === 'object' && value !== null;
|
|
75
57
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
key={col.name}
|
|
61
|
+
className="px-4 py-3 border-b border-studio-border"
|
|
62
|
+
>
|
|
63
|
+
{/* Column name and metadata */}
|
|
64
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
65
|
+
<span className="text-sm font-medium text-slate-300">
|
|
66
|
+
{col.name}
|
|
67
|
+
</span>
|
|
68
|
+
<span className="text-xs text-slate-600">{col.rawType}</span>
|
|
69
|
+
{col.isPrimaryKey && (
|
|
70
|
+
<span className="text-[10px] px-1 py-0.5 rounded bg-amber-500/20 text-amber-400">
|
|
71
|
+
PK
|
|
72
|
+
</span>
|
|
73
|
+
)}
|
|
74
|
+
{col.isForeignKey && (
|
|
75
|
+
<span className="text-[10px] px-1 py-0.5 rounded bg-blue-500/20 text-blue-400">
|
|
76
|
+
FK
|
|
77
|
+
</span>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)}
|
|
81
|
+
{/* Value */}
|
|
82
|
+
{isLongValue || isJson ? (
|
|
83
|
+
<pre
|
|
84
|
+
className={`${getValueClass(value)} bg-studio-bg text-xs p-2 rounded overflow-x-auto whitespace-pre-wrap break-words max-h-48`}
|
|
85
|
+
>
|
|
86
|
+
{formatValue(value)}
|
|
87
|
+
</pre>
|
|
88
|
+
) : (
|
|
89
|
+
<div className={`text-sm ${getValueClass(value)}`}>
|
|
90
|
+
{formatValue(value)}
|
|
95
91
|
</div>
|
|
96
|
-
)
|
|
97
|
-
})}
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
92
|
+
)}
|
|
100
93
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
94
|
+
{/* Foreign key reference */}
|
|
95
|
+
{col.isForeignKey && col.foreignKeyTable && (
|
|
96
|
+
<div className="mt-1.5 text-xs text-blue-400 flex items-center gap-1">
|
|
97
|
+
<svg
|
|
98
|
+
className="w-3 h-3"
|
|
99
|
+
fill="none"
|
|
100
|
+
stroke="currentColor"
|
|
101
|
+
viewBox="0 0 24 24"
|
|
102
|
+
>
|
|
103
|
+
<path
|
|
104
|
+
strokeLinecap="round"
|
|
105
|
+
strokeLinejoin="round"
|
|
106
|
+
strokeWidth={2}
|
|
107
|
+
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
|
|
108
|
+
/>
|
|
109
|
+
</svg>
|
|
110
|
+
{col.foreignKeyTable}.{col.foreignKeyColumn}
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
})}
|
|
110
116
|
</div>
|
|
111
|
-
</
|
|
117
|
+
</aside>
|
|
112
118
|
);
|
|
113
119
|
}
|
|
@@ -2,50 +2,57 @@ import type { TableSummary } from '../types';
|
|
|
2
2
|
|
|
3
3
|
interface TableListProps {
|
|
4
4
|
tables: TableSummary[];
|
|
5
|
+
selectedTable: string | null;
|
|
5
6
|
onSelect: (tableName: string) => void;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
export function TableList({ tables, onSelect }: TableListProps) {
|
|
9
|
+
export function TableList({ tables, selectedTable, onSelect }: TableListProps) {
|
|
9
10
|
if (tables.length === 0) {
|
|
10
11
|
return (
|
|
11
|
-
<div className="
|
|
12
|
-
<
|
|
13
|
-
<p className="text-
|
|
14
|
-
Make sure your database has tables in the public schema.
|
|
15
|
-
</p>
|
|
12
|
+
<div className="p-4 text-center text-slate-500 text-sm">
|
|
13
|
+
<p>No tables found</p>
|
|
14
|
+
<p className="mt-1 text-xs">Check your database schema.</p>
|
|
16
15
|
</div>
|
|
17
16
|
);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
return (
|
|
21
|
-
<div className="
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
<div className="py-1">
|
|
21
|
+
{tables.map((table) => (
|
|
22
|
+
<button
|
|
23
|
+
key={table.name}
|
|
24
|
+
onClick={() => onSelect(table.name)}
|
|
25
|
+
className={`w-full text-left px-3 py-2 flex items-center gap-3 transition-colors ${
|
|
26
|
+
selectedTable === table.name
|
|
27
|
+
? 'bg-studio-hover text-white'
|
|
28
|
+
: 'text-slate-400 hover:bg-studio-hover hover:text-slate-200'
|
|
29
|
+
}`}
|
|
30
|
+
>
|
|
31
|
+
{/* Table icon */}
|
|
32
|
+
<svg
|
|
33
|
+
className={`w-4 h-4 shrink-0 ${selectedTable === table.name ? 'text-emerald-400' : 'text-slate-500'}`}
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke="currentColor"
|
|
36
|
+
viewBox="0 0 24 24"
|
|
28
37
|
>
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<div className="mt-2 text-xs text-slate-500">
|
|
43
|
-
PK: {table.primaryKey.join(', ')}
|
|
38
|
+
<path
|
|
39
|
+
strokeLinecap="round"
|
|
40
|
+
strokeLinejoin="round"
|
|
41
|
+
strokeWidth={1.5}
|
|
42
|
+
d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
|
|
43
|
+
/>
|
|
44
|
+
</svg>
|
|
45
|
+
|
|
46
|
+
<div className="flex-1 min-w-0">
|
|
47
|
+
<div className="truncate text-sm">{table.name}</div>
|
|
48
|
+
{table.estimatedRowCount !== undefined && (
|
|
49
|
+
<div className="text-xs text-slate-500">
|
|
50
|
+
{table.estimatedRowCount.toLocaleString()} rows
|
|
44
51
|
</div>
|
|
45
52
|
)}
|
|
46
53
|
</div>
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
</button>
|
|
55
|
+
))}
|
|
49
56
|
</div>
|
|
50
57
|
);
|
|
51
58
|
}
|