@instantdb/components 1.0.37 → 1.0.38
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/.turbo/turbo-build.log +5 -5
- package/dist/components/explorer/edit-namespace-dialog.d.ts +5 -2
- package/dist/components/explorer/edit-namespace-dialog.d.ts.map +1 -1
- package/dist/components/explorer/explorer-layout.d.ts.map +1 -1
- package/dist/components/explorer/index.d.ts +82 -1
- package/dist/components/explorer/index.d.ts.map +1 -1
- package/dist/components/explorer/inner-explorer.d.ts.map +1 -1
- package/dist/index.cjs +7 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2207 -2168
- package/package.json +6 -6
- package/src/components/explorer/edit-namespace-dialog.tsx +177 -176
- package/src/components/explorer/explorer-layout.tsx +29 -16
- package/src/components/explorer/index.tsx +48 -4
- package/src/components/explorer/inner-explorer.tsx +28 -14
- package/src/index.tsx +5 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instantdb/components",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.38",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Instant's UI components",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -94,11 +94,11 @@
|
|
|
94
94
|
"swr": "^2.2.4",
|
|
95
95
|
"tailwind-merge": "^2.2.1",
|
|
96
96
|
"uuid": "^11.1.0",
|
|
97
|
-
"@instantdb/admin": "1.0.
|
|
98
|
-
"@instantdb/
|
|
99
|
-
"@instantdb/
|
|
100
|
-
"@instantdb/
|
|
101
|
-
"@instantdb/
|
|
97
|
+
"@instantdb/admin": "1.0.38",
|
|
98
|
+
"@instantdb/platform": "1.0.38",
|
|
99
|
+
"@instantdb/core": "1.0.38",
|
|
100
|
+
"@instantdb/version": "1.0.38",
|
|
101
|
+
"@instantdb/react": "1.0.38"
|
|
102
102
|
},
|
|
103
103
|
"scripts": {
|
|
104
104
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
jobIsCompleted,
|
|
53
53
|
jobIsErrored,
|
|
54
54
|
} from '@lib/utils/indexingJobs';
|
|
55
|
-
import { useExplorerProps, useExplorerState } from './index';
|
|
55
|
+
import { EditSchemaScreen, useExplorerProps, useExplorerState } from './index';
|
|
56
56
|
import { useClose } from '@headlessui/react';
|
|
57
57
|
import {
|
|
58
58
|
PendingJob,
|
|
@@ -69,6 +69,8 @@ export function EditNamespaceDialog({
|
|
|
69
69
|
namespaces,
|
|
70
70
|
onClose,
|
|
71
71
|
isSystemCatalogNs,
|
|
72
|
+
screen,
|
|
73
|
+
onScreenChange,
|
|
72
74
|
}: {
|
|
73
75
|
db: InstantReactWebDatabase<any>;
|
|
74
76
|
namespace: SchemaNamespace;
|
|
@@ -76,18 +78,20 @@ export function EditNamespaceDialog({
|
|
|
76
78
|
onClose: (p?: { ok: boolean }) => void;
|
|
77
79
|
readOnly: boolean;
|
|
78
80
|
isSystemCatalogNs: boolean;
|
|
81
|
+
screen: EditSchemaScreen;
|
|
82
|
+
onScreenChange: (screen: EditSchemaScreen) => void;
|
|
79
83
|
}) {
|
|
80
84
|
const props = useExplorerProps();
|
|
81
85
|
const appId = props.appId;
|
|
82
86
|
const { history, explorerState } = useExplorerState();
|
|
83
87
|
const { mutate } = useSWRConfig();
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
|
|
89
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
90
|
+
|
|
91
|
+
const setScreen = (s: EditSchemaScreen) => {
|
|
92
|
+
setIsDeleting(false);
|
|
93
|
+
onScreenChange(s);
|
|
94
|
+
};
|
|
91
95
|
|
|
92
96
|
const [renameNsInput, setRenameNsInput] = useState(namespace.name);
|
|
93
97
|
const [renameNsErrorText, setRenameNsErrorText] = useState<string | null>(
|
|
@@ -121,189 +125,183 @@ export function EditNamespaceDialog({
|
|
|
121
125
|
);
|
|
122
126
|
successToast('Renamed namespace to ' + newName);
|
|
123
127
|
setRenameNsInput('');
|
|
124
|
-
setScreen({
|
|
128
|
+
setScreen({ kind: 'main' });
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
const notes = useAttrNotes();
|
|
128
132
|
|
|
129
133
|
const screenAttr = useMemo(() => {
|
|
130
|
-
return
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
(a) => a.id === screen.attrId && a.isForward === screen.isForward,
|
|
134
|
-
)
|
|
134
|
+
if (screen.kind !== 'edit-attr') return undefined;
|
|
135
|
+
return namespace.attrs.find(
|
|
136
|
+
(a) => a.id === screen.attrId && a.isForward === screen.isForward,
|
|
135
137
|
);
|
|
136
138
|
}, [
|
|
137
|
-
screen.
|
|
138
|
-
screen.
|
|
139
|
+
screen.kind === 'edit-attr' ? screen.attrId : null,
|
|
140
|
+
screen.kind === 'edit-attr' ? screen.isForward : null,
|
|
139
141
|
namespace.attrs,
|
|
140
142
|
]);
|
|
141
143
|
|
|
142
|
-
return (
|
|
143
|
-
|
|
144
|
-
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
144
|
+
return isDeleting ? (
|
|
145
|
+
<DeleteForm
|
|
146
|
+
name={namespace.name}
|
|
147
|
+
type="namespace"
|
|
148
|
+
onClose={onClose}
|
|
149
|
+
onConfirm={deleteNs}
|
|
150
|
+
/>
|
|
151
|
+
) : screen.kind === 'rename' ? (
|
|
152
|
+
<div className="px-2">
|
|
153
|
+
<button
|
|
154
|
+
onClick={() => {
|
|
155
|
+
setScreen({ kind: 'main' });
|
|
156
|
+
}}
|
|
157
|
+
className="mb-3"
|
|
158
|
+
>
|
|
159
|
+
<ArrowLeftIcon className="h-4 w-4 cursor-pointer" />
|
|
160
|
+
</button>
|
|
161
|
+
<h6 className="text-md pb-2 font-bold">Rename {namespace.name}</h6>
|
|
162
|
+
<form
|
|
163
|
+
onSubmit={(e) => {
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
renameNs(renameNsInput);
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<Content className="pb-2 text-sm">
|
|
169
|
+
This will immediately rename the namespace. You'll need to{' '}
|
|
170
|
+
<strong className="dark:text-white">update your code</strong> to the
|
|
171
|
+
new name.
|
|
172
|
+
</Content>
|
|
173
|
+
<TextInput
|
|
174
|
+
disabled={isSystemCatalogNs}
|
|
175
|
+
value={renameNsInput}
|
|
176
|
+
onChange={(n) => setRenameNsInput(n)}
|
|
177
|
+
/>
|
|
178
|
+
<div className="flex flex-col gap-2 rounded-sm py-2">
|
|
179
|
+
<Button
|
|
180
|
+
type="submit"
|
|
181
|
+
disabled={
|
|
182
|
+
renameNsInput.startsWith('$') || renameNsInput.length === 0
|
|
183
|
+
}
|
|
162
184
|
>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
<strong className="dark:text-white">update your code</strong> to
|
|
166
|
-
the new name.
|
|
167
|
-
</Content>
|
|
168
|
-
<TextInput
|
|
169
|
-
disabled={isSystemCatalogNs}
|
|
170
|
-
value={renameNsInput}
|
|
171
|
-
onChange={(n) => setRenameNsInput(n)}
|
|
172
|
-
/>
|
|
173
|
-
<div className="flex flex-col gap-2 rounded-sm py-2">
|
|
174
|
-
<Button
|
|
175
|
-
type="submit"
|
|
176
|
-
disabled={
|
|
177
|
-
renameNsInput.startsWith('$') || renameNsInput.length === 0
|
|
178
|
-
}
|
|
179
|
-
>
|
|
180
|
-
Rename {namespace.name} → {renameNsInput}
|
|
181
|
-
</Button>
|
|
182
|
-
</div>
|
|
183
|
-
</form>{' '}
|
|
185
|
+
Rename {namespace.name} → {renameNsInput}
|
|
186
|
+
</Button>
|
|
184
187
|
</div>
|
|
185
|
-
|
|
188
|
+
</form>
|
|
189
|
+
</div>
|
|
190
|
+
) : screen.kind === 'main' ? (
|
|
191
|
+
<div className="flex flex-col gap-4 px-2">
|
|
192
|
+
<div className="mr-8 flex gap-1">
|
|
193
|
+
<h5 className="flex items-center text-lg font-bold">
|
|
194
|
+
{namespace.name}
|
|
195
|
+
</h5>
|
|
196
|
+
<IconButton
|
|
197
|
+
variant="subtle"
|
|
198
|
+
onClick={() => {
|
|
199
|
+
setScreen({ kind: 'rename' });
|
|
200
|
+
}}
|
|
201
|
+
icon={
|
|
202
|
+
<PencilSquareIcon className="h-4 w-4 opacity-50"></PencilSquareIcon>
|
|
203
|
+
}
|
|
204
|
+
label="Rename"
|
|
205
|
+
></IconButton>
|
|
186
206
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
<Button
|
|
205
|
-
className="ml-4"
|
|
206
|
-
disabled={isSystemCatalogNs}
|
|
207
|
-
title={
|
|
208
|
-
isSystemCatalogNs
|
|
209
|
-
? `The ${namespace.name} namespace can't be deleted.`
|
|
210
|
-
: undefined
|
|
211
|
-
}
|
|
212
|
-
size="mini"
|
|
213
|
-
variant="secondary"
|
|
214
|
-
onClick={() => setScreen({ type: 'delete' })}
|
|
215
|
-
>
|
|
216
|
-
<TrashIcon className="inline" height="1rem" />
|
|
217
|
-
Delete
|
|
218
|
-
</Button>
|
|
219
|
-
</div>
|
|
207
|
+
<Button
|
|
208
|
+
className="ml-4"
|
|
209
|
+
disabled={isSystemCatalogNs}
|
|
210
|
+
title={
|
|
211
|
+
isSystemCatalogNs
|
|
212
|
+
? `The ${namespace.name} namespace can't be deleted.`
|
|
213
|
+
: undefined
|
|
214
|
+
}
|
|
215
|
+
size="mini"
|
|
216
|
+
variant="secondary"
|
|
217
|
+
onClick={() => setIsDeleting(true)}
|
|
218
|
+
>
|
|
219
|
+
<TrashIcon className="inline" height="1rem" />
|
|
220
|
+
Delete
|
|
221
|
+
</Button>
|
|
222
|
+
</div>
|
|
220
223
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
<div className="flex flex-col gap-2">
|
|
225
|
+
{namespace.attrs.map((attr) => (
|
|
226
|
+
<div key={attr.id + '-' + attr.name} className="flex justify-between">
|
|
227
|
+
<div className="flex items-center gap-3">
|
|
228
|
+
<span className="py-0.5 font-bold">{attr.name}</span>
|
|
229
|
+
{notes.notes[attr.id]?.message && (
|
|
230
|
+
<InfoTip>
|
|
231
|
+
<div className="px-2 text-xs text-gray-500 dark:text-neutral-400">
|
|
232
|
+
{notes.notes[attr.id].message}
|
|
233
|
+
</div>
|
|
234
|
+
</InfoTip>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
{attr.name !== 'id' ? (
|
|
238
|
+
<Button
|
|
239
|
+
className="px-2"
|
|
240
|
+
size="mini"
|
|
241
|
+
variant="subtle"
|
|
242
|
+
onClick={() => {
|
|
243
|
+
notes.removeNote(attr.id);
|
|
244
|
+
setScreen({
|
|
245
|
+
kind: 'edit-attr',
|
|
246
|
+
attrId: attr.id,
|
|
247
|
+
isForward: attr.isForward,
|
|
248
|
+
});
|
|
249
|
+
}}
|
|
226
250
|
>
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
<InfoTip>
|
|
231
|
-
<div className="px-2 text-xs text-gray-500 dark:text-neutral-400">
|
|
232
|
-
{notes.notes[attr.id].message}
|
|
233
|
-
</div>
|
|
234
|
-
</InfoTip>
|
|
235
|
-
)}
|
|
236
|
-
</div>
|
|
237
|
-
{attr.name !== 'id' ? (
|
|
238
|
-
<Button
|
|
239
|
-
className="px-2"
|
|
240
|
-
size="mini"
|
|
241
|
-
variant="subtle"
|
|
242
|
-
onClick={() => {
|
|
243
|
-
notes.removeNote(attr.id);
|
|
244
|
-
setScreen({
|
|
245
|
-
type: 'edit',
|
|
246
|
-
attrId: attr.id,
|
|
247
|
-
isForward: attr.isForward,
|
|
248
|
-
});
|
|
249
|
-
}}
|
|
250
|
-
>
|
|
251
|
-
Edit
|
|
252
|
-
</Button>
|
|
253
|
-
) : null}
|
|
254
|
-
</div>
|
|
255
|
-
))}
|
|
251
|
+
Edit
|
|
252
|
+
</Button>
|
|
253
|
+
) : null}
|
|
256
254
|
</div>
|
|
255
|
+
))}
|
|
256
|
+
</div>
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
</>
|
|
306
|
-
);
|
|
258
|
+
<div>
|
|
259
|
+
<Button
|
|
260
|
+
size="mini"
|
|
261
|
+
variant="secondary"
|
|
262
|
+
onClick={() => setScreen({ kind: 'add-attr', attrKind: 'data' })}
|
|
263
|
+
>
|
|
264
|
+
<PlusIcon className="inline" height="12px" />
|
|
265
|
+
New attribute
|
|
266
|
+
</Button>
|
|
267
|
+
</div>
|
|
268
|
+
<RecentlyDeletedAttrs
|
|
269
|
+
notes={notes}
|
|
270
|
+
db={db}
|
|
271
|
+
appId={appId}
|
|
272
|
+
namespace={namespace}
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
) : screen.kind === 'add-attr' ? (
|
|
276
|
+
<AddAttrForm
|
|
277
|
+
db={db}
|
|
278
|
+
namespace={namespace}
|
|
279
|
+
namespaces={namespaces}
|
|
280
|
+
onClose={() => setScreen({ kind: 'main' })}
|
|
281
|
+
constraints={getSystemConstraints({
|
|
282
|
+
namespaceName: namespace.name,
|
|
283
|
+
isSystemCatalogNs,
|
|
284
|
+
})}
|
|
285
|
+
attrType={screen.attrKind === 'link' ? 'ref' : 'blob'}
|
|
286
|
+
onAttrTypeChange={(t) =>
|
|
287
|
+
onScreenChange({
|
|
288
|
+
kind: 'add-attr',
|
|
289
|
+
attrKind: t === 'ref' ? 'link' : 'data',
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
/>
|
|
293
|
+
) : screen.kind === 'edit-attr' && screenAttr ? (
|
|
294
|
+
<EditAttrForm
|
|
295
|
+
db={db}
|
|
296
|
+
attr={screenAttr}
|
|
297
|
+
onClose={() => setScreen({ kind: 'main' })}
|
|
298
|
+
constraints={getSystemConstraints({
|
|
299
|
+
namespaceName: namespace.name,
|
|
300
|
+
isSystemCatalogNs: isSystemCatalogNs,
|
|
301
|
+
attr: screenAttr,
|
|
302
|
+
})}
|
|
303
|
+
/>
|
|
304
|
+
) : null;
|
|
307
305
|
}
|
|
308
306
|
|
|
309
307
|
function DeleteForm({
|
|
@@ -350,12 +348,16 @@ function AddAttrForm({
|
|
|
350
348
|
namespaces,
|
|
351
349
|
onClose,
|
|
352
350
|
constraints,
|
|
351
|
+
attrType,
|
|
352
|
+
onAttrTypeChange,
|
|
353
353
|
}: {
|
|
354
354
|
db: InstantReactWebDatabase<any>;
|
|
355
355
|
namespace: SchemaNamespace;
|
|
356
356
|
namespaces: SchemaNamespace[];
|
|
357
357
|
onClose: () => void;
|
|
358
358
|
constraints: SystemConstraints;
|
|
359
|
+
attrType: 'blob' | 'ref';
|
|
360
|
+
onAttrTypeChange: (attrType: 'blob' | 'ref') => void;
|
|
359
361
|
}) {
|
|
360
362
|
const [isRequired, setIsRequired] = useState(false);
|
|
361
363
|
const [isIndex, setIsIndex] = useState(false);
|
|
@@ -364,7 +366,6 @@ function AddAttrForm({
|
|
|
364
366
|
const [isCascadeReverse, setIsCascadeReverse] = useState(false);
|
|
365
367
|
const [checkedDataType, setCheckedDataType] =
|
|
366
368
|
useState<CheckedDataType | null>(null);
|
|
367
|
-
const [attrType, setAttrType] = useState<'blob' | 'ref'>('blob');
|
|
368
369
|
const [relationship, setRelationship] =
|
|
369
370
|
useState<RelationshipKinds>('many-many');
|
|
370
371
|
|
|
@@ -455,7 +456,7 @@ function AddAttrForm({
|
|
|
455
456
|
{ id: 'blob', label: 'Data' },
|
|
456
457
|
{ id: 'ref', label: 'Link' },
|
|
457
458
|
]}
|
|
458
|
-
onChange={(item) =>
|
|
459
|
+
onChange={(item) => onAttrTypeChange(item.id as 'blob' | 'ref')}
|
|
459
460
|
/>
|
|
460
461
|
</div>
|
|
461
462
|
{attrType === 'blob' ? (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { useExplorerProps } from '.';
|
|
2
|
+
import { useExplorerDialog, useExplorerProps } from '.';
|
|
3
3
|
import { SchemaNamespace } from '@lib/types';
|
|
4
|
-
import { Button, cn, Dialog, ToggleCollection
|
|
4
|
+
import { Button, cn, Dialog, ToggleCollection } from '../ui';
|
|
5
5
|
import {
|
|
6
6
|
RecentlyDeletedNamespaces,
|
|
7
7
|
useRecentlyDeletedNamespaces,
|
|
@@ -28,9 +28,19 @@ export const ExplorerLayout = ({
|
|
|
28
28
|
appId: string;
|
|
29
29
|
}) => {
|
|
30
30
|
const props = useExplorerProps();
|
|
31
|
+
const { dialog, setDialog } = useExplorerDialog();
|
|
31
32
|
|
|
32
|
-
const recentlyDeletedNsDialog =
|
|
33
|
-
|
|
33
|
+
const recentlyDeletedNsDialog = {
|
|
34
|
+
open: dialog?.type === 'recently-deleted-ns',
|
|
35
|
+
onOpen: () => setDialog({ type: 'recently-deleted-ns' }),
|
|
36
|
+
onClose: () => setDialog(null),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const newNsDialog = {
|
|
40
|
+
open: dialog?.type === 'new-namespace',
|
|
41
|
+
onOpen: () => setDialog({ type: 'new-namespace' }),
|
|
42
|
+
onClose: () => setDialog(null),
|
|
43
|
+
};
|
|
34
44
|
|
|
35
45
|
const selectedNamespace = namespaces.find(
|
|
36
46
|
(ns) => ns.id === props.explorerState?.namespace,
|
|
@@ -58,20 +68,24 @@ export const ExplorerLayout = ({
|
|
|
58
68
|
);
|
|
59
69
|
|
|
60
70
|
// Auto-select first namespace if none selected
|
|
71
|
+
const { setExplorerState } = props;
|
|
61
72
|
useEffect(() => {
|
|
62
73
|
if (!selectedNamespace && namespaces.length > 0) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
props.setExplorerState({ namespace: namespaces[0].id });
|
|
72
|
-
}
|
|
74
|
+
const savedNamespace = recentExplorerNamespaceId
|
|
75
|
+
? namespaces.find((ns) => ns.id === recentExplorerNamespaceId)
|
|
76
|
+
: undefined;
|
|
77
|
+
const namespaceId = savedNamespace?.id ?? namespaces[0].id;
|
|
78
|
+
setExplorerState((prev) => ({
|
|
79
|
+
...(prev ?? {}),
|
|
80
|
+
namespace: namespaceId,
|
|
81
|
+
}));
|
|
73
82
|
}
|
|
74
|
-
}, [
|
|
83
|
+
}, [
|
|
84
|
+
selectedNamespace,
|
|
85
|
+
namespaces,
|
|
86
|
+
recentExplorerNamespaceId,
|
|
87
|
+
setExplorerState,
|
|
88
|
+
]);
|
|
75
89
|
|
|
76
90
|
const deletedNamespaces = useRecentlyDeletedNamespaces(props.appId);
|
|
77
91
|
|
|
@@ -90,7 +104,6 @@ export const ExplorerLayout = ({
|
|
|
90
104
|
db={db}
|
|
91
105
|
onClose={(p) => {
|
|
92
106
|
newNsDialog.onClose();
|
|
93
|
-
|
|
94
107
|
if (p?.name) {
|
|
95
108
|
props.setExplorerState({ namespace: p.name });
|
|
96
109
|
}
|
|
@@ -16,6 +16,17 @@ import { useSchemaQuery } from '@lib/hooks/explorer';
|
|
|
16
16
|
import { useStableDB } from '@lib/hooks/useStableDB';
|
|
17
17
|
import ErrorBoundary from '@lib/components/error-boundary';
|
|
18
18
|
|
|
19
|
+
export type SetExplorerStateOptions = {
|
|
20
|
+
// Use 'replace' for transitions that shouldn't add a back-button step,
|
|
21
|
+
// such as switching screens within an already-open dialog. Defaults to 'push'.
|
|
22
|
+
history?: 'push' | 'replace';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type SetExplorerState = (
|
|
26
|
+
action: React.SetStateAction<ExplorerNav | null>,
|
|
27
|
+
options?: SetExplorerStateOptions,
|
|
28
|
+
) => void;
|
|
29
|
+
|
|
19
30
|
interface ExplorerProps {
|
|
20
31
|
appId: string;
|
|
21
32
|
adminToken: string;
|
|
@@ -30,9 +41,7 @@ interface ExplorerProps {
|
|
|
30
41
|
// When null: controlled mode with no selection
|
|
31
42
|
// When ExplorerNav: controlled mode with a selection
|
|
32
43
|
explorerState: HasDefault<ExplorerNav | null | undefined>;
|
|
33
|
-
setExplorerState: HasDefault<
|
|
34
|
-
React.Dispatch<React.SetStateAction<ExplorerNav | null>>
|
|
35
|
-
>;
|
|
44
|
+
setExplorerState: HasDefault<SetExplorerState>;
|
|
36
45
|
useShadowDOM: HasDefault<boolean>;
|
|
37
46
|
}
|
|
38
47
|
|
|
@@ -68,6 +77,27 @@ export const useExplorerState = () => {
|
|
|
68
77
|
return { explorerState: ctx.props.explorerState, history: ctx.history };
|
|
69
78
|
};
|
|
70
79
|
|
|
80
|
+
export const useExplorerDialog = () => {
|
|
81
|
+
const ctx = useContext(ExplorerPropsContext);
|
|
82
|
+
if (!ctx.props) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
'useExplorerDialog must be used within an Explorer component',
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const props = ctx.props;
|
|
88
|
+
const dialog = props.explorerState?.dialog ?? null;
|
|
89
|
+
const setDialog = useCallback(
|
|
90
|
+
(d: ExplorerDialog | null, options?: SetExplorerStateOptions) => {
|
|
91
|
+
props.setExplorerState(
|
|
92
|
+
(prev) => (prev ? { ...prev, dialog: d } : prev),
|
|
93
|
+
options,
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
[props],
|
|
97
|
+
);
|
|
98
|
+
return { dialog, setDialog };
|
|
99
|
+
};
|
|
100
|
+
|
|
71
101
|
const isControlled = (props: WithOptional<ExplorerProps>): boolean => {
|
|
72
102
|
// Component is controlled if explorerState prop is explicitly provided
|
|
73
103
|
// (even if null - that means "no selection" in controlled mode)
|
|
@@ -79,7 +109,7 @@ const isControlled = (props: WithOptional<ExplorerProps>): boolean => {
|
|
|
79
109
|
const fillPropsWithDefaults = (
|
|
80
110
|
input: WithOptional<ExplorerProps>,
|
|
81
111
|
_explorerState: ExplorerNav | null,
|
|
82
|
-
setExplorerState:
|
|
112
|
+
setExplorerState: SetExplorerState,
|
|
83
113
|
): WithDefaults<ExplorerProps> => {
|
|
84
114
|
const controlled = isControlled(input);
|
|
85
115
|
return {
|
|
@@ -105,6 +135,19 @@ export type SearchFilterOp =
|
|
|
105
135
|
|
|
106
136
|
export type SearchFilter = [string, SearchFilterOp, any];
|
|
107
137
|
|
|
138
|
+
export type EditSchemaScreen =
|
|
139
|
+
| { kind: 'main' }
|
|
140
|
+
| { kind: 'rename' }
|
|
141
|
+
| { kind: 'add-attr'; attrKind: 'data' | 'link' }
|
|
142
|
+
| { kind: 'edit-attr'; attrId: string; isForward: boolean };
|
|
143
|
+
|
|
144
|
+
export type ExplorerDialog =
|
|
145
|
+
| { type: 'add-row' }
|
|
146
|
+
| { type: 'edit-row'; rowId: string }
|
|
147
|
+
| { type: 'edit-schema'; screen: EditSchemaScreen }
|
|
148
|
+
| { type: 'new-namespace' }
|
|
149
|
+
| { type: 'recently-deleted-ns' };
|
|
150
|
+
|
|
108
151
|
export interface ExplorerNav {
|
|
109
152
|
namespace: string;
|
|
110
153
|
where?: [string, any];
|
|
@@ -113,6 +156,7 @@ export interface ExplorerNav {
|
|
|
113
156
|
filters?: SearchFilter[];
|
|
114
157
|
limit?: number;
|
|
115
158
|
page?: number;
|
|
159
|
+
dialog?: ExplorerDialog | null;
|
|
116
160
|
}
|
|
117
161
|
|
|
118
162
|
export const Explorer = (_props: WithOptional<ExplorerProps>) => {
|