@instantdb/components 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/.env +2 -0
- package/.turbo/turbo-build.log +18 -0
- package/README.md +78 -0
- package/app/App.css +38 -0
- package/app/App.tsx +61 -0
- package/app/index.css +18 -0
- package/app/main.tsx +10 -0
- package/dist/components/StyleMe.d.ts +15 -0
- package/dist/components/StyleMe.d.ts.map +1 -0
- package/dist/components/error-boundary.d.ts +17 -0
- package/dist/components/error-boundary.d.ts.map +1 -0
- package/dist/components/explorer/edit-namespace-dialog.d.ts +14 -0
- package/dist/components/explorer/edit-namespace-dialog.d.ts.map +1 -0
- package/dist/components/explorer/edit-row-dialog.d.ts +10 -0
- package/dist/components/explorer/edit-row-dialog.d.ts.map +1 -0
- package/dist/components/explorer/expandable-deleted-attr.d.ts +15 -0
- package/dist/components/explorer/expandable-deleted-attr.d.ts.map +1 -0
- package/dist/components/explorer/explorer-layout.d.ts +8 -0
- package/dist/components/explorer/explorer-layout.d.ts.map +1 -0
- package/dist/components/explorer/index.d.ts +44 -0
- package/dist/components/explorer/index.d.ts.map +1 -0
- package/dist/components/explorer/inner-explorer.d.ts +16 -0
- package/dist/components/explorer/inner-explorer.d.ts.map +1 -0
- package/dist/components/explorer/new-namespace-dialog.d.ts +10 -0
- package/dist/components/explorer/new-namespace-dialog.d.ts.map +1 -0
- package/dist/components/explorer/query-inspector.d.ts +11 -0
- package/dist/components/explorer/query-inspector.d.ts.map +1 -0
- package/dist/components/explorer/recently-deleted.d.ts +36 -0
- package/dist/components/explorer/recently-deleted.d.ts.map +1 -0
- package/dist/components/explorer/search-input.d.ts +9 -0
- package/dist/components/explorer/search-input.d.ts.map +1 -0
- package/dist/components/explorer/table-components.d.ts +16 -0
- package/dist/components/explorer/table-components.d.ts.map +1 -0
- package/dist/components/explorer/view-settings.d.ts +10 -0
- package/dist/components/explorer/view-settings.d.ts.map +1 -0
- package/dist/components/rosePineDawnTheme.d.ts +13 -0
- package/dist/components/rosePineDawnTheme.d.ts.map +1 -0
- package/dist/components/select.d.ts +16 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/toast.d.ts +4 -0
- package/dist/components/toast.d.ts.map +1 -0
- package/dist/components/ui.d.ts +336 -0
- package/dist/components/ui.d.ts.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/hooks/explorer.d.ts +29 -0
- package/dist/hooks/explorer.d.ts.map +1 -0
- package/dist/hooks/useAttrNotes.d.ts +10 -0
- package/dist/hooks/useAttrNotes.d.ts.map +1 -0
- package/dist/hooks/useClickOutside.d.ts +3 -0
- package/dist/hooks/useClickOutside.d.ts.map +1 -0
- package/dist/hooks/useColumnVisibility.d.ts +12 -0
- package/dist/hooks/useColumnVisibility.d.ts.map +1 -0
- package/dist/hooks/useEditBlobConstraints.d.ts +32 -0
- package/dist/hooks/useEditBlobConstraints.d.ts.map +1 -0
- package/dist/hooks/useExplorerHistory.d.ts +1 -0
- package/dist/hooks/useExplorerHistory.d.ts.map +1 -0
- package/dist/hooks/useIsOverflow.d.ts +6 -0
- package/dist/hooks/useIsOverflow.d.ts.map +1 -0
- package/dist/hooks/useLocalStorage.d.ts +2 -0
- package/dist/hooks/useLocalStorage.d.ts.map +1 -0
- package/dist/hooks/useMonacoJSONSchema.d.ts +3 -0
- package/dist/hooks/useMonacoJSONSchema.d.ts.map +1 -0
- package/dist/hooks/useStableDB.d.ts +7 -0
- package/dist/hooks/useStableDB.d.ts.map +1 -0
- package/dist/index.cjs +15 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9270 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/style.css +1 -0
- package/dist/types.d.ts +241 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/format.d.ts +2 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/indexingJobs.d.ts +24 -0
- package/dist/utils/indexingJobs.d.ts.map +1 -0
- package/dist/utils/parsePermsJSON.d.ts +11 -0
- package/dist/utils/parsePermsJSON.d.ts.map +1 -0
- package/dist/utils/renames.d.ts +3 -0
- package/dist/utils/renames.d.ts.map +1 -0
- package/dist/utils/tableWidthSize.d.ts +9 -0
- package/dist/utils/tableWidthSize.d.ts.map +1 -0
- package/index.html +13 -0
- package/package.json +109 -0
- package/src/components/StyleMe.tsx +97 -0
- package/src/components/error-boundary.tsx +76 -0
- package/src/components/explorer/edit-namespace-dialog.tsx +1886 -0
- package/src/components/explorer/edit-row-dialog.tsx +1151 -0
- package/src/components/explorer/expandable-deleted-attr.tsx +170 -0
- package/src/components/explorer/explorer-layout.tsx +156 -0
- package/src/components/explorer/index.tsx +217 -0
- package/src/components/explorer/inner-explorer.tsx +1341 -0
- package/src/components/explorer/new-namespace-dialog.tsx +54 -0
- package/src/components/explorer/query-inspector.tsx +394 -0
- package/src/components/explorer/recently-deleted.tsx +344 -0
- package/src/components/explorer/search-input.tsx +358 -0
- package/src/components/explorer/table-components.tsx +341 -0
- package/src/components/explorer/view-settings.tsx +75 -0
- package/src/components/rosePineDawnTheme.ts +45 -0
- package/src/components/select.tsx +198 -0
- package/src/components/toast.tsx +18 -0
- package/src/components/ui.tsx +1561 -0
- package/src/config.ts +61 -0
- package/src/hooks/explorer.tsx +125 -0
- package/src/hooks/useAttrNotes.ts +27 -0
- package/src/hooks/useClickOutside.ts +23 -0
- package/src/hooks/useColumnVisibility.ts +39 -0
- package/src/hooks/useEditBlobConstraints.ts +185 -0
- package/src/hooks/useExplorerHistory.ts +0 -0
- package/src/hooks/useIsOverflow.ts +24 -0
- package/src/hooks/useLocalStorage.ts +51 -0
- package/src/hooks/useMonacoJSONSchema.ts +41 -0
- package/src/hooks/useStableDB.ts +30 -0
- package/src/index.tsx +8 -0
- package/src/schema.ts +285 -0
- package/src/style.css +5 -0
- package/src/types.ts +359 -0
- package/src/utils/format.ts +13 -0
- package/src/utils/indexingJobs.ts +126 -0
- package/src/utils/parsePermsJSON.ts +35 -0
- package/src/utils/renames.ts +42 -0
- package/src/utils/tableWidthSize.ts +62 -0
- package/tailwind.config.cjs +42 -0
- package/tsconfig.json +22 -0
- package/vite-env.d.ts +1 -0
- package/vite.config.ts +49 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { id, InstantReactWebDatabase } from '@instantdb/react';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { DBAttr } from '@lib/types';
|
|
5
|
+
import { ActionButton, ActionForm, TextInput } from '../ui';
|
|
6
|
+
|
|
7
|
+
export function NewNamespaceDialog({
|
|
8
|
+
db,
|
|
9
|
+
onClose,
|
|
10
|
+
}: {
|
|
11
|
+
db: InstantReactWebDatabase<any>;
|
|
12
|
+
onClose: (p?: { id: string; name: string }) => void;
|
|
13
|
+
}) {
|
|
14
|
+
const [name, setName] = useState('');
|
|
15
|
+
|
|
16
|
+
async function onSubmit() {
|
|
17
|
+
const idAttr: DBAttr = {
|
|
18
|
+
id: id(),
|
|
19
|
+
'forward-identity': [id(), name, 'id'],
|
|
20
|
+
'value-type': 'blob',
|
|
21
|
+
cardinality: 'one',
|
|
22
|
+
'unique?': true,
|
|
23
|
+
'index?': false,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const ops = [['add-attr', idAttr]];
|
|
27
|
+
await db.core._reactor.pushOps(ops);
|
|
28
|
+
onClose({ id: idAttr.id, name });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<ActionForm className="flex flex-col gap-4">
|
|
33
|
+
<h5 className="flex items-center text-lg font-bold">
|
|
34
|
+
Create a new namespace
|
|
35
|
+
</h5>
|
|
36
|
+
|
|
37
|
+
<TextInput
|
|
38
|
+
value={name}
|
|
39
|
+
placeholder="Name your namespace"
|
|
40
|
+
onChange={(n) => setName(n)}
|
|
41
|
+
autoFocus
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
<ActionButton
|
|
45
|
+
type="submit"
|
|
46
|
+
label="Create"
|
|
47
|
+
submitLabel="Creating..."
|
|
48
|
+
errorMessage="Failed to create namespace"
|
|
49
|
+
disabled={!name}
|
|
50
|
+
onClick={onSubmit}
|
|
51
|
+
/>
|
|
52
|
+
</ActionForm>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import JsonParser from 'json5';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import * as Tooltip from '@radix-ui/react-tooltip';
|
|
5
|
+
import { StarIcon, TrashIcon } from '@heroicons/react/24/outline';
|
|
6
|
+
import { ArrowRightIcon } from '@heroicons/react/24/solid';
|
|
7
|
+
|
|
8
|
+
import { Button, CodeEditor, cn } from '@lib/components/ui';
|
|
9
|
+
import { errorToast } from '@lib/components/toast';
|
|
10
|
+
import { init, InstantReactWebDatabase } from '@instantdb/react';
|
|
11
|
+
import { DBAttr, SchemaNamespace } from '@lib/types';
|
|
12
|
+
import { attrsToSchema } from '@lib/schema';
|
|
13
|
+
import { apiSchemaToInstantSchemaDef } from '@instantdb/platform';
|
|
14
|
+
import { useExplorerProps } from '.';
|
|
15
|
+
|
|
16
|
+
const SAVED_QUERIES_CACHE_KEY = '__instant:explorer-saved-queries';
|
|
17
|
+
const QUERY_HISTORY_CACHE_KEY = '__instant:explorer-query-history';
|
|
18
|
+
|
|
19
|
+
type CachedQueryItem = {
|
|
20
|
+
ts: number;
|
|
21
|
+
query: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
class QueryInspectorCache {
|
|
25
|
+
savedQueriesCacheKey: string;
|
|
26
|
+
queryHistoryCacheKey: string;
|
|
27
|
+
|
|
28
|
+
constructor(appId: string) {
|
|
29
|
+
this.savedQueriesCacheKey = `${SAVED_QUERIES_CACHE_KEY}:${appId}`;
|
|
30
|
+
this.queryHistoryCacheKey = `${QUERY_HISTORY_CACHE_KEY}:${appId}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get(key: string) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = localStorage.getItem(key);
|
|
36
|
+
|
|
37
|
+
return raw ? JSON.parse(raw) : null;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
set(key: string, value: any) {
|
|
43
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
44
|
+
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
getQueryHistory() {
|
|
48
|
+
return this.get(this.queryHistoryCacheKey) || [];
|
|
49
|
+
}
|
|
50
|
+
setQueryHistory(history: CachedQueryItem[]) {
|
|
51
|
+
return this.set(this.queryHistoryCacheKey, history);
|
|
52
|
+
}
|
|
53
|
+
getSavedQueries() {
|
|
54
|
+
return this.get(this.savedQueriesCacheKey) || [];
|
|
55
|
+
}
|
|
56
|
+
setSavedQueries(queries: CachedQueryItem[]) {
|
|
57
|
+
return this.set(this.savedQueriesCacheKey, queries);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function dbForAttrs(
|
|
62
|
+
baseDb: InstantReactWebDatabase<any>,
|
|
63
|
+
attrs: Record<string, DBAttr> | null,
|
|
64
|
+
): InstantReactWebDatabase<any> {
|
|
65
|
+
if (!attrs) {
|
|
66
|
+
return baseDb;
|
|
67
|
+
}
|
|
68
|
+
const schema = apiSchemaToInstantSchemaDef(
|
|
69
|
+
attrsToSchema(Object.values(attrs)),
|
|
70
|
+
);
|
|
71
|
+
return init({
|
|
72
|
+
...baseDb.core._reactor.config,
|
|
73
|
+
disableValidation: true,
|
|
74
|
+
schema,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function QueryInspector({
|
|
79
|
+
className,
|
|
80
|
+
appId,
|
|
81
|
+
db: baseDb,
|
|
82
|
+
namespaces,
|
|
83
|
+
attrs,
|
|
84
|
+
}: {
|
|
85
|
+
className?: string;
|
|
86
|
+
appId: string;
|
|
87
|
+
db: InstantReactWebDatabase<any>;
|
|
88
|
+
namespaces: SchemaNamespace[] | null;
|
|
89
|
+
attrs: Record<string, DBAttr> | null;
|
|
90
|
+
}) {
|
|
91
|
+
const db = useMemo(() => dbForAttrs(baseDb, attrs), [baseDb, attrs]);
|
|
92
|
+
const cache = new QueryInspectorCache(appId);
|
|
93
|
+
const [query, setQuery] = useState<Record<string, any>>({});
|
|
94
|
+
const [draft, setQueryDraft] = useState('{}');
|
|
95
|
+
const [history, setQueryHistory] = useState<CachedQueryItem[]>([]);
|
|
96
|
+
const [saved, setSavedQueries] = useState<CachedQueryItem[]>([]);
|
|
97
|
+
|
|
98
|
+
const explorerProps = useExplorerProps();
|
|
99
|
+
|
|
100
|
+
const { data, isLoading, error } = (
|
|
101
|
+
db as InstantReactWebDatabase<any>
|
|
102
|
+
).useQuery(query);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
const saved = cache.getSavedQueries();
|
|
106
|
+
const history = cache.getQueryHistory();
|
|
107
|
+
const previousQueryItem = history[0] || saved[0];
|
|
108
|
+
|
|
109
|
+
if (previousQueryItem) {
|
|
110
|
+
const { query: stringified } = previousQueryItem;
|
|
111
|
+
|
|
112
|
+
setQuery(JSON.parse(stringified));
|
|
113
|
+
setQueryDraft(stringified);
|
|
114
|
+
} else {
|
|
115
|
+
setQuery({});
|
|
116
|
+
setQueryDraft('{}');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
setSavedQueries(saved);
|
|
120
|
+
setQueryHistory(history);
|
|
121
|
+
}, [appId]);
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!namespaces) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const isQueryEmpty = Object.keys(query).length === 0;
|
|
129
|
+
|
|
130
|
+
if (isQueryEmpty && namespaces.length > 0) {
|
|
131
|
+
const [first] = namespaces;
|
|
132
|
+
const defaultQuery = {
|
|
133
|
+
[first.name]: { $: { limit: 5 } },
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
setQuery(defaultQuery);
|
|
137
|
+
setQueryDraft(JSON.stringify(defaultQuery, null, 2));
|
|
138
|
+
}
|
|
139
|
+
}, [namespaces]);
|
|
140
|
+
|
|
141
|
+
const handleClearQuery = () => {
|
|
142
|
+
setQuery({});
|
|
143
|
+
setQueryDraft('{}');
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const run = (val: string) => {
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JsonParser.parse(val);
|
|
149
|
+
const stringified = JSON.stringify(parsed, null, 2);
|
|
150
|
+
setQuery(parsed);
|
|
151
|
+
setQueryDraft(stringified);
|
|
152
|
+
setQueryHistory((prev) => {
|
|
153
|
+
const [latest] = prev;
|
|
154
|
+
|
|
155
|
+
if (latest && latest.query === stringified) {
|
|
156
|
+
return prev;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const item = { ts: Date.now(), query: stringified };
|
|
160
|
+
const history = [item, ...prev];
|
|
161
|
+
cache.setQueryHistory(history);
|
|
162
|
+
|
|
163
|
+
return history;
|
|
164
|
+
});
|
|
165
|
+
} catch (e) {
|
|
166
|
+
errorToast('Unable to run query: Invalid JSON');
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const handleRunQuery = () => run(draft);
|
|
171
|
+
|
|
172
|
+
const handleSaveQuery = () => {
|
|
173
|
+
try {
|
|
174
|
+
const parsed = JsonParser.parse(draft);
|
|
175
|
+
const stringified = JSON.stringify(parsed, null, 2);
|
|
176
|
+
setSavedQueries((prev) => {
|
|
177
|
+
const item = { ts: Date.now(), query: stringified };
|
|
178
|
+
// If query was already saved, move it to the top
|
|
179
|
+
if (prev.some((i) => i.query === stringified)) {
|
|
180
|
+
const saved = [item, ...prev.filter((i) => i.query !== stringified)];
|
|
181
|
+
cache.setSavedQueries(saved);
|
|
182
|
+
return saved;
|
|
183
|
+
} else {
|
|
184
|
+
const saved = [item, ...prev];
|
|
185
|
+
cache.setSavedQueries(saved);
|
|
186
|
+
return saved;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
} catch (e) {
|
|
190
|
+
errorToast('Unable to save query: Invalid JSON');
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const handleRemoveFromSaved = (ts: number) => {
|
|
195
|
+
setSavedQueries((prev) => {
|
|
196
|
+
const saved = prev.filter((i) => i.ts !== ts);
|
|
197
|
+
cache.setSavedQueries(saved);
|
|
198
|
+
|
|
199
|
+
return saved;
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const handleRemoveFromHistory = (ts: number) => {
|
|
204
|
+
setQueryHistory((prev) => {
|
|
205
|
+
const history = prev.filter((i) => i.ts !== ts);
|
|
206
|
+
cache.setQueryHistory(history);
|
|
207
|
+
|
|
208
|
+
return history;
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<div className={cn('flex flex-1', className)}>
|
|
214
|
+
<div className="flex max-w-lg flex-1 flex-col dark:bg-neutral-800">
|
|
215
|
+
<h2 className="mt-4 mb-1 px-3 text-sm font-semibold">InstaQL query</h2>
|
|
216
|
+
|
|
217
|
+
<div className="relative h-64 overflow-hidden rounded-sm border-y dark:border-y-neutral-800">
|
|
218
|
+
<CodeEditor
|
|
219
|
+
darkMode={explorerProps.darkMode}
|
|
220
|
+
language="json"
|
|
221
|
+
value={draft}
|
|
222
|
+
onChange={(code) => setQueryDraft(code)}
|
|
223
|
+
onMount={(editor, monaco) => {
|
|
224
|
+
// cmd+S/cmd+Enter bindings to run query
|
|
225
|
+
editor.addCommand(
|
|
226
|
+
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,
|
|
227
|
+
() => run(editor.getValue()),
|
|
228
|
+
);
|
|
229
|
+
editor.addCommand(
|
|
230
|
+
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
|
|
231
|
+
() => run(editor.getValue()),
|
|
232
|
+
);
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
<div className="absolute bottom-0 flex w-full items-center justify-between gap-4 px-3 py-2">
|
|
236
|
+
<Button
|
|
237
|
+
className="group"
|
|
238
|
+
variant="secondary"
|
|
239
|
+
size="mini"
|
|
240
|
+
onClick={handleSaveQuery}
|
|
241
|
+
>
|
|
242
|
+
<StarIcon className="mr-0.5 h-4 w-4 transition-colors group-hover:text-amber-500" />
|
|
243
|
+
Save
|
|
244
|
+
</Button>
|
|
245
|
+
<div className="flex items-center gap-2">
|
|
246
|
+
<Button
|
|
247
|
+
variant="secondary"
|
|
248
|
+
size="mini"
|
|
249
|
+
onClick={handleClearQuery}
|
|
250
|
+
>
|
|
251
|
+
Clear
|
|
252
|
+
</Button>
|
|
253
|
+
<Button variant="primary" size="mini" onClick={handleRunQuery}>
|
|
254
|
+
Run
|
|
255
|
+
</Button>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div className="">
|
|
261
|
+
<h2 className="mt-4 mb-1 px-3 text-sm font-semibold">
|
|
262
|
+
Saved queries
|
|
263
|
+
</h2>
|
|
264
|
+
|
|
265
|
+
<div className="px-3 text-sm">
|
|
266
|
+
{saved.length > 0 ? (
|
|
267
|
+
saved.map((item) => {
|
|
268
|
+
return (
|
|
269
|
+
<div
|
|
270
|
+
key={item.ts}
|
|
271
|
+
className="group mb-1 flex items-center justify-between gap-2 text-gray-700 dark:text-neutral-200"
|
|
272
|
+
>
|
|
273
|
+
<Tooltip.Provider>
|
|
274
|
+
<Tooltip.Root delayDuration={200}>
|
|
275
|
+
<Tooltip.Trigger asChild>
|
|
276
|
+
<div className="truncate font-mono text-xs">
|
|
277
|
+
{item.query}
|
|
278
|
+
</div>
|
|
279
|
+
</Tooltip.Trigger>
|
|
280
|
+
<Tooltip.Portal>
|
|
281
|
+
<Tooltip.Content
|
|
282
|
+
className="animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md border bg-white px-3 py-1 text-sm text-gray-900 shadow-md dark:border-neutral-800"
|
|
283
|
+
side="top"
|
|
284
|
+
align="start"
|
|
285
|
+
sideOffset={8}
|
|
286
|
+
>
|
|
287
|
+
<pre>
|
|
288
|
+
<code className="text-xs">{item.query}</code>
|
|
289
|
+
</pre>
|
|
290
|
+
</Tooltip.Content>
|
|
291
|
+
</Tooltip.Portal>
|
|
292
|
+
</Tooltip.Root>
|
|
293
|
+
</Tooltip.Provider>
|
|
294
|
+
<div className="flex items-center gap-1">
|
|
295
|
+
<Button
|
|
296
|
+
className="px-1 opacity-0 transition-opacity group-hover:opacity-100"
|
|
297
|
+
variant="destructive"
|
|
298
|
+
size="mini"
|
|
299
|
+
onClick={() => handleRemoveFromSaved(item.ts)}
|
|
300
|
+
>
|
|
301
|
+
<TrashIcon className="h-4 w-4" />
|
|
302
|
+
</Button>
|
|
303
|
+
<Button
|
|
304
|
+
className="px-1 opacity-0 transition-opacity group-hover:opacity-100"
|
|
305
|
+
variant="primary"
|
|
306
|
+
size="mini"
|
|
307
|
+
onClick={() => run(item.query)}
|
|
308
|
+
>
|
|
309
|
+
<ArrowRightIcon className="h-4 w-4 -rotate-45" />
|
|
310
|
+
</Button>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
})
|
|
315
|
+
) : (
|
|
316
|
+
<div className="py-1 text-gray-400">Nothing here yet!</div>
|
|
317
|
+
)}
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<div className="mt-4 border-t py-4 dark:border-t-neutral-700">
|
|
322
|
+
<h2 className="mb-1 px-3 text-sm font-semibold">Query history</h2>
|
|
323
|
+
|
|
324
|
+
<div className="px-3 text-sm">
|
|
325
|
+
{history.length > 0 ? (
|
|
326
|
+
history.slice(0, 20).map((item) => {
|
|
327
|
+
return (
|
|
328
|
+
<div
|
|
329
|
+
key={item.ts}
|
|
330
|
+
className="group mb-1 flex items-center justify-between gap-2 text-gray-700 dark:text-neutral-300"
|
|
331
|
+
>
|
|
332
|
+
<Tooltip.Provider>
|
|
333
|
+
<Tooltip.Root delayDuration={200}>
|
|
334
|
+
<Tooltip.Trigger asChild>
|
|
335
|
+
<div className="truncate font-mono text-xs">
|
|
336
|
+
{item.query}
|
|
337
|
+
</div>
|
|
338
|
+
</Tooltip.Trigger>
|
|
339
|
+
<Tooltip.Portal>
|
|
340
|
+
<Tooltip.Content
|
|
341
|
+
className="animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md border bg-white px-3 py-1 text-sm text-gray-900 shadow-md"
|
|
342
|
+
side="top"
|
|
343
|
+
align="start"
|
|
344
|
+
sideOffset={8}
|
|
345
|
+
>
|
|
346
|
+
<pre>
|
|
347
|
+
<code className="text-xs">{item.query}</code>
|
|
348
|
+
</pre>
|
|
349
|
+
</Tooltip.Content>
|
|
350
|
+
</Tooltip.Portal>
|
|
351
|
+
</Tooltip.Root>
|
|
352
|
+
</Tooltip.Provider>
|
|
353
|
+
<div className="flex items-center gap-1">
|
|
354
|
+
<Button
|
|
355
|
+
className="px-1 opacity-0 transition-opacity group-hover:opacity-100"
|
|
356
|
+
variant="destructive"
|
|
357
|
+
size="mini"
|
|
358
|
+
onClick={() => handleRemoveFromHistory(item.ts)}
|
|
359
|
+
>
|
|
360
|
+
<TrashIcon className="h-4 w-4" />
|
|
361
|
+
</Button>
|
|
362
|
+
<Button
|
|
363
|
+
className="px-1 opacity-0 transition-opacity group-hover:opacity-100"
|
|
364
|
+
variant="primary"
|
|
365
|
+
size="mini"
|
|
366
|
+
onClick={() => run(item.query)}
|
|
367
|
+
>
|
|
368
|
+
<ArrowRightIcon className="h-4 w-4 -rotate-45" />
|
|
369
|
+
</Button>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
})
|
|
374
|
+
) : (
|
|
375
|
+
<div className="py-1 text-gray-400">Nothing here yet!</div>
|
|
376
|
+
)}
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
<div className="flex max-h-full flex-1 flex-col overflow-scroll border-l dark:border-l-neutral-700 dark:bg-neutral-800">
|
|
381
|
+
<h2 className="mt-4 mb-1 px-3 text-sm font-semibold">Query results</h2>
|
|
382
|
+
<div className="flex-1 overflow-hidden rounded-sm border-y dark:border-y-neutral-700">
|
|
383
|
+
<CodeEditor
|
|
384
|
+
darkMode={explorerProps.darkMode}
|
|
385
|
+
loading={isLoading}
|
|
386
|
+
language={'json'}
|
|
387
|
+
value={JSON.stringify(data || error || {}, null, 2)}
|
|
388
|
+
onChange={() => {}}
|
|
389
|
+
/>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
);
|
|
394
|
+
}
|