@aglyn/plugins-data 1.0.0-beta.143

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.
Files changed (39) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +47 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/data-console-page.d.ts +13 -0
  8. package/src/lib/components/data-console-page.js +63 -0
  9. package/src/lib/components/data-console-page.js.map +1 -0
  10. package/src/lib/components/dataset-record-dialog.component.d.ts +52 -0
  11. package/src/lib/components/dataset-record-dialog.component.js +249 -0
  12. package/src/lib/components/dataset-record-dialog.component.js.map +1 -0
  13. package/src/lib/components/dataset-schema-dialog.component.d.ts +51 -0
  14. package/src/lib/components/dataset-schema-dialog.component.js +979 -0
  15. package/src/lib/components/dataset-schema-dialog.component.js.map +1 -0
  16. package/src/lib/components/host-datasets-card.component.d.ts +39 -0
  17. package/src/lib/components/host-datasets-card.component.js +1831 -0
  18. package/src/lib/components/host-datasets-card.component.js.map +1 -0
  19. package/src/lib/constants/bundle-common.d.ts +8 -0
  20. package/src/lib/constants/bundle-common.js +9 -0
  21. package/src/lib/constants/bundle-common.js.map +1 -0
  22. package/src/lib/model/dataset-io.d.ts +43 -0
  23. package/src/lib/model/dataset-io.js +84 -0
  24. package/src/lib/model/dataset-io.js.map +1 -0
  25. package/src/lib/model/dataset-record-view.d.ts +157 -0
  26. package/src/lib/model/dataset-record-view.js +293 -0
  27. package/src/lib/model/dataset-record-view.js.map +1 -0
  28. package/src/lib/model/index.d.ts +22 -0
  29. package/src/lib/model/index.js +22 -0
  30. package/src/lib/model/index.js.map +1 -0
  31. package/src/lib/plugin.d.ts +35 -0
  32. package/src/lib/plugin.js +78 -0
  33. package/src/lib/plugin.js.map +1 -0
  34. package/src/lib/repeat/dataset-repeat-rows.d.ts +45 -0
  35. package/src/lib/repeat/dataset-repeat-rows.js +108 -0
  36. package/src/lib/repeat/dataset-repeat-rows.js.map +1 -0
  37. package/src/lib/repeat/dataset-repeat-source.d.ts +39 -0
  38. package/src/lib/repeat/dataset-repeat-source.js +95 -0
  39. package/src/lib/repeat/dataset-repeat-source.js.map +1 -0
@@ -0,0 +1,979 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
+ import { DATASET_FIELD_TYPE_LABELS, DATASET_FIELD_TYPES, defaultDatasetFieldId, getCustomFieldType, listCustomFieldTypes, validateDatasetFieldId, describeScope, effectiveDatasetModel, HOST_ACCESS_ROLES, hostScopeToken, narrowsScope, scopeCovers, ORG_SCOPE_TOKEN, scopeToStore, storedScope, visibleToHost } from "@aglyn/aglyn";
20
+ import { useConfirmationContext } from "@aglyn/shared-ui-jsx";
21
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
22
+ import { Button, Checkbox, Alert, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, IconButton, MenuItem, Select, Stack, TextField, Typography } from "@mui/material";
23
+ import { collection, doc, limit, query, updateDoc, where } from "firebase/firestore";
24
+ import { useCallback, useEffect, useState } from "react";
25
+ import { useFirestore, useFirestoreCollection, useOrgDataScope, useScopeTokens, useUser, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
26
+ /** Types surfaced in the picker; the rest exist for compat, not authoring. */ const AUTHORABLE_TYPES = [
27
+ 'text',
28
+ 'bool',
29
+ 'int32',
30
+ 'float',
31
+ 'timestamp',
32
+ 'coordinates',
33
+ 'sorted',
34
+ 'reference'
35
+ ];
36
+ /**
37
+ * Model builder (AGL-178): edits the AGL-177 typed model on a dataset —
38
+ * singular/plural display names (dod.ts Schema.Name), add/edit/remove/
39
+ * reorder fields, per-field type/required/default/description/validation.
40
+ * Schema-change policies (documented per the issue): type changes never
41
+ * rewrite documents — non-conforming values surface in the editor when it
42
+ * validates (AGL-179); removed fields leave orphaned values that strip
43
+ * lazily on next document save; fieldIds are stable keys — only display
44
+ * names rename.
45
+ */ export function DatasetSchemaDialog(props) {
46
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _find, _ref8, _ref9, _ref10, _ref11;
47
+ var _editorDefinition_reference, _editorDefinition_reference1, _editorDefinition_reference2, _editorDefinition_reference3, _editorDefinition_validation;
48
+ const { hostId, dataset, datasets, recordCount, seedFromCache, seedUnreadable, onClose } = props;
49
+ // Org-shared data root (AGL-237/239). Null until the async org lookup
50
+ // settles (AGL-1061), and for a host with no owning org. Saving is held
51
+ // on it rather than redirected: the host path this used to fall back to
52
+ // is now denied by the rules outright (AGL-1050), so a save there would
53
+ // fail loudly instead of quietly — but it should not be attempted at all.
54
+ const { scope: dataScope, orgId } = useOrgDataScope({
55
+ hostId,
56
+ orgId: props.orgId
57
+ });
58
+ const firestore = useFirestore();
59
+ const { enqueueSnackbar } = useSnackbar();
60
+ const { confirm } = useConfirmationContext();
61
+ // Sharing scope (AGL-1044) — org datasets only; the legacy host path is
62
+ // site-private by construction.
63
+ // `orgWide` reads TRUE while the member doc loads (AGL-1047), which would
64
+ // offer a scoped collaborator the scope Select for a beat before replacing
65
+ // it with the read-only summary — and offering a control the AGL-1041
66
+ // rules will reject is the thing the branch below exists to avoid. Hold
67
+ // the whole block until `loaded` rather than flash the wrong one.
68
+ const { orgWide: viewerOrgWide, loaded: scopeLoaded } = useScopeTokens(orgId != null ? orgId : undefined);
69
+ // Sites in this org, for naming what a narrowing would cost. Queried
70
+ // here rather than via the console's useOrgHosts — this component lives
71
+ // in a lib and cannot import from an app — so it must repeat that hook's
72
+ // membership filter, which is the part this was missing (AGL-1145).
73
+ //
74
+ // `/hosts/{hostId}` is gated per document on `memberRoles.{uid}`, and
75
+ // Firestore evaluates a LIST against the QUERY rather than its results: it
76
+ // refuses to run one that COULD return a denied document, whatever today's
77
+ // data happens to hold. So filtering on `orgId` alone was not a partial
78
+ // result, it was a flat denial for every non-staff caller — including one
79
+ // who is a member of every site in the org. Proved in the emulator
80
+ // (cloud/hosts-list-constraint.spec.mjs); the reason nobody hit it is that
81
+ // staff match the rule's other branch, and staff are who tested it.
82
+ const { data: user } = useUser();
83
+ const uid = user == null ? void 0 : user.uid;
84
+ const { data: orgHostDocs } = useFirestoreCollection(// Hold rather than substitute a placeholder while the uid resolves. The
85
+ // old `orgId ?? '-none-'` fired a real query on every mount before the
86
+ // scope was known; a sentinel is still a question asked of the server,
87
+ // and here it is one that gets denied.
88
+ ()=>uid && orgId ? query(collection(firestore, 'hosts'), where(`memberRoles.${uid}`, 'in', HOST_ACCESS_ROLES), where('orgId', '==', orgId), limit(200)) : null, [
89
+ firestore,
90
+ orgId,
91
+ uid
92
+ ], {
93
+ idField: '$id'
94
+ });
95
+ const orgHostList = orgHostDocs != null ? orgHostDocs : [];
96
+ const [visibleTo, setVisibleTo] = useState([]);
97
+ /**
98
+ * The dataset stores NO scope (AGL-1484) — as opposed to storing `['org']`.
99
+ *
100
+ * The dialog used to render the two identically, which for a dataset is a
101
+ * worse lie than it was for a media file: `orgs/{orgId}/datasets` is read
102
+ * with `array-contains-any` in `resolve-dataset.ts` and in the console's
103
+ * own `organizations.ts`, so an unstamped dataset is matched by no reader
104
+ * in the product. It renders on no site and is missing from the reference
105
+ * cards even for an org-wide member — while this control said "All sites".
106
+ */ const [scopeUnset, setScopeUnset] = useState(false);
107
+ const [model, setModel] = useState({
108
+ fields: {},
109
+ order: []
110
+ });
111
+ const [names, setNames] = useState({
112
+ singular: '',
113
+ plural: ''
114
+ });
115
+ useEffect(()=>{
116
+ var _ref, _ref1, _ref2;
117
+ var _dataset_names, _dataset_names1;
118
+ if (!dataset) return;
119
+ // The fourth copy of the AGL-1466 substitution (AGL-1484), and the seed
120
+ // half of it. Persisting the default on open was the other option and
121
+ // AGL-1466 ruled it out: opening a dialog would write, the AGL-1042
122
+ // rules refuse that write from anyone who is not org-wide, and it
123
+ // repairs only what somebody happens to look at — a repair mechanism
124
+ // wearing a UI. `previousScope` in `handleSave` reads the SAME helper;
125
+ // see the note there for why the pair is the safety property.
126
+ const storedDatasetScope = storedScope(dataset.visibleTo);
127
+ setVisibleTo(storedDatasetScope != null ? storedDatasetScope : []);
128
+ setScopeUnset(!storedDatasetScope);
129
+ const effective = effectiveDatasetModel(dataset);
130
+ setModel({
131
+ fields: JSON.parse(JSON.stringify(effective.fields)),
132
+ order: [
133
+ ...effective.order
134
+ ]
135
+ });
136
+ setNames({
137
+ singular: (_ref = (_dataset_names = dataset.names) == null ? void 0 : _dataset_names.singular) != null ? _ref : '',
138
+ plural: (_ref1 = (_ref2 = (_dataset_names1 = dataset.names) == null ? void 0 : _dataset_names1.plural) != null ? _ref2 : dataset.displayName) != null ? _ref1 : ''
139
+ });
140
+ }, [
141
+ dataset
142
+ ]);
143
+ // Per-field editor (null fieldId = new field). `idDraft`/`idTouched` back
144
+ // the editable Reference ID (AGL-578): for a new field the draft follows
145
+ // the display name until the user edits it; existing fields keep the id
146
+ // frozen (idTouched=true, input disabled).
147
+ const [fieldEditor, setFieldEditor] = useState(null);
148
+ const openFieldEditor = useCallback((fieldId)=>()=>{
149
+ var _ref;
150
+ var _definition_validation;
151
+ const definition = fieldId ? JSON.parse(JSON.stringify(model.fields[fieldId])) : {
152
+ name: '',
153
+ type: 'text'
154
+ };
155
+ setFieldEditor({
156
+ fieldId,
157
+ definition,
158
+ optionsText: ((_ref = (_definition_validation = definition.validation) == null ? void 0 : _definition_validation.options) != null ? _ref : []).join(', '),
159
+ idDraft: fieldId != null ? fieldId : '',
160
+ idTouched: fieldId != null
161
+ });
162
+ }, [
163
+ model
164
+ ]);
165
+ const handleFieldSave = useCallback(async ()=>{
166
+ var _definition_validation;
167
+ var _definition_description;
168
+ if (!fieldEditor) return;
169
+ const definition = _extends({}, fieldEditor.definition);
170
+ if (!definition.name.trim()) return;
171
+ definition.name = definition.name.trim();
172
+ // Descriptions (AGL-560) surface as hints wherever the field appears;
173
+ // blank ones are dropped so the model doesn't accrete empty strings.
174
+ const description = (_definition_description = definition.description) == null ? void 0 : _definition_description.trim();
175
+ if (description) definition.description = description;
176
+ else delete definition.description;
177
+ const options = fieldEditor.optionsText.split(',').map((option)=>option.trim()).filter(Boolean);
178
+ definition.validation = _extends({}, (_definition_validation = definition.validation) != null ? _definition_validation : {}, options.length ? {
179
+ options
180
+ } : {});
181
+ if (!options.length) delete definition.validation.options;
182
+ if (!Object.keys(definition.validation).length) delete definition.validation;
183
+ let fieldId = fieldEditor.fieldId;
184
+ if (!fieldId) {
185
+ // New field: the reference id comes from the (editable) draft, which
186
+ // defaulted from the display name. Validate before it becomes a key.
187
+ const id = fieldEditor.idDraft.trim();
188
+ const idError = validateDatasetFieldId(id, new Set(model.order.map((existing)=>existing.toLowerCase())));
189
+ if (idError) {
190
+ return void enqueueSnackbar(idError, {
191
+ variant: 'warning',
192
+ persist: false
193
+ });
194
+ }
195
+ fieldId = id;
196
+ } else if (recordCount > 0 && model.fields[fieldId] && model.fields[fieldId].type !== definition.type) {
197
+ // Documented policy: changing a type never rewrites documents;
198
+ // existing values that no longer conform get flagged when edited.
199
+ const confirmed = await confirm({
200
+ title: 'Change field type?',
201
+ description: `${recordCount} document${recordCount === 1 ? '' : 's'} exist. ` + 'Stored values are not rewritten — ones that no longer match ' + `"${DATASET_FIELD_TYPE_LABELS[definition.type]}" will be flagged ` + 'when documents are next edited.',
202
+ confirmationText: 'Change type'
203
+ }).then(()=>true).catch(()=>false);
204
+ if (!confirmed) return;
205
+ }
206
+ setModel((prev)=>({
207
+ fields: _extends({}, prev.fields, {
208
+ [fieldId]: definition
209
+ }),
210
+ order: prev.order.includes(fieldId) ? prev.order : [
211
+ ...prev.order,
212
+ fieldId
213
+ ]
214
+ }));
215
+ setFieldEditor(null);
216
+ }, [
217
+ fieldEditor,
218
+ model,
219
+ recordCount,
220
+ confirm,
221
+ enqueueSnackbar
222
+ ]);
223
+ const handleFieldRemove = useCallback((fieldId)=>async ()=>{
224
+ var _ref;
225
+ var _model_fields_fieldId;
226
+ const confirmed = await confirm({
227
+ title: `Remove field "${(_ref = (_model_fields_fieldId = model.fields[fieldId]) == null ? void 0 : _model_fields_fieldId.name) != null ? _ref : fieldId}"?`,
228
+ description: 'Existing documents keep their stored value until they are next ' + 'saved (it is stripped then). Bindings using this field stop ' + 'resolving.',
229
+ confirmationText: 'Remove',
230
+ confirmationButtonProps: {
231
+ color: 'error'
232
+ }
233
+ }).then(()=>true).catch(()=>false);
234
+ if (!confirmed) return;
235
+ setModel((prev)=>{
236
+ const fields = _extends({}, prev.fields);
237
+ delete fields[fieldId];
238
+ return {
239
+ fields,
240
+ order: prev.order.filter((id)=>id !== fieldId)
241
+ };
242
+ });
243
+ }, [
244
+ model,
245
+ confirm
246
+ ]);
247
+ const moveField = useCallback((fieldId, delta)=>()=>setModel((prev)=>{
248
+ const index = prev.order.indexOf(fieldId);
249
+ const target = index + delta;
250
+ if (index < 0 || target < 0 || target >= prev.order.length) return prev;
251
+ const order = [
252
+ ...prev.order
253
+ ];
254
+ order.splice(index, 1);
255
+ order.splice(target, 0, fieldId);
256
+ return _extends({}, prev, {
257
+ order
258
+ });
259
+ }), []);
260
+ const handleSave = useCallback(async ()=>{
261
+ var _storedScope;
262
+ if (!dataset || !dataScope) return;
263
+ /**
264
+ * What the document actually carries, `[]` when it carries nothing
265
+ * (AGL-1484) — matching the seed above, through the same helper.
266
+ *
267
+ * The pairing is the whole safety property, not tidiness. Both sides
268
+ * used to substitute `[ORG_SCOPE_TOKEN]`, so an unset dataset compared
269
+ * equal to itself and merely OPENING the dialog wrote nothing; the
270
+ * damage was confined to the screen. Correcting the seed alone would
271
+ * make `scopeChanged` true for every unset dataset the moment its
272
+ * dialog opened, turning a lie on screen into a write nobody asked for
273
+ * — one the AGL-1041 rules then reject for any member who is not
274
+ * org-wide, failing the WHOLE schema save with it.
275
+ *
276
+ * `[]` is also the honest previous value in the other direction: a
277
+ * deliberate save of exactly `['org']` on an unset dataset used to
278
+ * compare equal to the default and be dropped, and that is the one save
279
+ * an unstamped dataset most needs to land.
280
+ */ const previousScope = (_storedScope = storedScope(dataset.visibleTo)) != null ? _storedScope : [];
281
+ const scopeChanged = JSON.stringify([
282
+ ...previousScope
283
+ ].sort()) !== JSON.stringify([
284
+ ...visibleTo
285
+ ].sort());
286
+ /**
287
+ * The scope this save writes, or null when it writes none.
288
+ *
289
+ * Only an org-wide member may change the scope — the AGL-1041 rules deny
290
+ * anyone else, and a rejected field fails the WHOLE write.
291
+ *
292
+ * A selection `scopeToStore` cannot store — "Selected sites…" with no site
293
+ * picked, or more sites than one query can match — refuses the save rather
294
+ * than substituting a scope: the org token would share the collection with
295
+ * every site, and anything narrower would drop sites somebody picked.
296
+ */ const scopeWrite = orgId && viewerOrgWide && scopeChanged ? scopeToStore(visibleTo) : null;
297
+ if (scopeWrite == null ? void 0 : scopeWrite.problem) {
298
+ return void enqueueSnackbar(scopeWrite.problem, {
299
+ variant: 'warning',
300
+ persist: false
301
+ });
302
+ }
303
+ if (scopeChanged && narrowsScope(previousScope, visibleTo)) {
304
+ // Narrowing takes a dataset away from sites whose pages may bind it,
305
+ // and a repeatable bound to a dataset it can no longer see renders
306
+ // nothing (AGL-1039). Name the cost before paying it.
307
+ const losing = orgHostList.filter((host)=>visibleToHost(previousScope, host.$id)).filter((host)=>!visibleToHost(visibleTo, host.$id)).map((host)=>{
308
+ var _ref, _host_name;
309
+ return (_ref = (_host_name = host.name) != null ? _host_name : host.subdomain) != null ? _ref : host.$id;
310
+ });
311
+ const proceed = await confirm({
312
+ title: 'Limit which sites can use this collection?',
313
+ description: losing.length ? `${losing.join(', ')} will stop seeing this collection. Pages ` + 'that repeat over it will render nothing. No data is deleted.' : 'No site loses access, so nothing breaks today.',
314
+ confirmationText: 'Limit access'
315
+ }).then(()=>true).catch(()=>false);
316
+ if (!proceed) return;
317
+ }
318
+ if (!model.order.length) {
319
+ return void enqueueSnackbar('A collection needs at least one field', {
320
+ variant: 'warning',
321
+ persist: false
322
+ });
323
+ }
324
+ /**
325
+ * Refuse the write when the seeding listener is unconfirmed (AGL-1358).
326
+ *
327
+ * Everything below is seeded from `dataset` and written back whole, so
328
+ * `updateDoc` field-level merging protects nothing — the untouched
329
+ * fields are all in the payload. Two of them make a stale seed worse
330
+ * than an inconvenience:
331
+ *
332
+ * - `visibleTo` is the AGL-1041/1042 scoping PREDICATE. Rewriting it
333
+ * from a cached seed re-exposes a collection that was narrowed, or
334
+ * hides one that was widened, and the confirmation above is computed
335
+ * against `previousScope` read from that same stale seed — so it does
336
+ * not even warn about the sites it is really taking access from.
337
+ * - `model` is the entire schema. A cached seed reinstates fields that
338
+ * were deleted and drops fields that were added.
339
+ */ await writeGuardedBySeed({
340
+ subject: 'collection schema',
341
+ unreadable: seedUnreadable,
342
+ fromCache: seedFromCache
343
+ }, async ()=>void await updateDoc(doc(firestore, dataScope[0], dataScope[1], 'datasets', dataset.$id), _extends({
344
+ model,
345
+ names: {
346
+ singular: names.singular.trim(),
347
+ plural: names.plural.trim()
348
+ }
349
+ }, names.plural.trim() ? {
350
+ displayName: names.plural.trim()
351
+ } : {}, {
352
+ // v1 compat: keep the flat column list mirroring the model order
353
+ // so unmigrated consumers (AGL-103 bindings) keep working.
354
+ fields: model.order
355
+ }, (scopeWrite == null ? void 0 : scopeWrite.scope) ? {
356
+ visibleTo: scopeWrite.scope
357
+ } : {}))).then((result)=>{
358
+ // A refusal leaves the dialog open with the edited schema intact.
359
+ if (!result.ok) {
360
+ return void enqueueSnackbar(result.message, {
361
+ variant: 'warning',
362
+ persist: false
363
+ });
364
+ }
365
+ enqueueSnackbar('Schema saved', {
366
+ variant: 'success',
367
+ persist: false
368
+ });
369
+ onClose();
370
+ }).catch(()=>enqueueSnackbar('An error has occurred', {
371
+ variant: 'error'
372
+ }));
373
+ }, [
374
+ dataset,
375
+ model,
376
+ names,
377
+ visibleTo,
378
+ viewerOrgWide,
379
+ orgHostList,
380
+ confirm,
381
+ firestore,
382
+ dataScope,
383
+ orgId,
384
+ seedFromCache,
385
+ seedUnreadable,
386
+ enqueueSnackbar,
387
+ onClose
388
+ ]);
389
+ const editorDefinition = fieldEditor == null ? void 0 : fieldEditor.definition;
390
+ // Reference ID editing (AGL-578): the id is editable only while creating a
391
+ // field; live-validate the draft so the input and Save button can react.
392
+ const isNewField = fieldEditor != null && fieldEditor.fieldId == null;
393
+ const fieldIdError = fieldEditor && isNewField ? validateDatasetFieldId(fieldEditor.idDraft, new Set(model.order.map((id)=>id.toLowerCase()))) : null;
394
+ return /*#__PURE__*/ _jsxs(_Fragment, {
395
+ children: [
396
+ /*#__PURE__*/ _jsxs(Dialog, {
397
+ open: Boolean(dataset),
398
+ onClose: onClose,
399
+ maxWidth: "sm",
400
+ fullWidth: true,
401
+ children: [
402
+ /*#__PURE__*/ _jsx(DialogTitle, {
403
+ children: `Schema — ${(_ref = dataset == null ? void 0 : dataset.displayName) != null ? _ref : ''}`
404
+ }),
405
+ /*#__PURE__*/ _jsxs(DialogContent, {
406
+ sx: {
407
+ display: 'flex',
408
+ flexDirection: 'column',
409
+ gap: 2
410
+ },
411
+ children: [
412
+ /*#__PURE__*/ _jsxs(Stack, {
413
+ direction: "row",
414
+ spacing: 1,
415
+ sx: {
416
+ mt: 1
417
+ },
418
+ children: [
419
+ /*#__PURE__*/ _jsx(TextField, {
420
+ size: "small",
421
+ label: "Singular name",
422
+ value: names.singular,
423
+ onChange: (event)=>setNames((prev)=>_extends({}, prev, {
424
+ singular: event.target.value
425
+ })),
426
+ helperText: "e.g. Product"
427
+ }),
428
+ /*#__PURE__*/ _jsx(TextField, {
429
+ size: "small",
430
+ label: "Plural name",
431
+ value: names.plural,
432
+ onChange: (event)=>setNames((prev)=>_extends({}, prev, {
433
+ plural: event.target.value
434
+ })),
435
+ helperText: "e.g. Products (shown as the title)"
436
+ })
437
+ ]
438
+ }),
439
+ orgId ? /*#__PURE__*/ _jsxs(Stack, {
440
+ spacing: 0.5,
441
+ children: [
442
+ /*#__PURE__*/ _jsx(Typography, {
443
+ variant: "caption",
444
+ color: "text.secondary",
445
+ children: 'Shared with'
446
+ }),
447
+ !scopeLoaded ? /*#__PURE__*/ _jsx(Typography, {
448
+ variant: "body2",
449
+ color: "text.secondary",
450
+ children: 'Checking your access…'
451
+ }) : viewerOrgWide ? /*#__PURE__*/ _jsxs(_Fragment, {
452
+ children: [
453
+ scopeUnset ? /*#__PURE__*/ _jsx(Alert, {
454
+ severity: "warning",
455
+ children: 'This collection has never been shared. Nothing is ' + 'stored, so it is hidden from every site and any ' + 'page repeating over it renders nothing. Choose who ' + 'it is shared with to fix that.'
456
+ }) : null,
457
+ /*#__PURE__*/ _jsxs(Select, {
458
+ size: "small",
459
+ value: visibleTo.includes(ORG_SCOPE_TOKEN) ? 'org' : scopeUnset ? 'unset' : 'hosts',
460
+ onChange: (event)=>{
461
+ // Any choice ends the unset state — from here the
462
+ // control shows what was picked, not what was (not)
463
+ // stored.
464
+ setScopeUnset(false);
465
+ setVisibleTo(event.target.value === 'org' ? [
466
+ ORG_SCOPE_TOKEN
467
+ ] : []);
468
+ },
469
+ children: [
470
+ scopeUnset ? /*#__PURE__*/ _jsx(MenuItem, {
471
+ value: "unset",
472
+ disabled: true,
473
+ children: 'Not shared with any site'
474
+ }) : null,
475
+ /*#__PURE__*/ _jsx(MenuItem, {
476
+ value: "org",
477
+ children: 'All sites'
478
+ }),
479
+ /*#__PURE__*/ _jsx(MenuItem, {
480
+ value: "hosts",
481
+ children: 'Selected sites…'
482
+ })
483
+ ]
484
+ }),
485
+ /*#__PURE__*/ _jsx(Typography, {
486
+ variant: "caption",
487
+ color: "text.secondary",
488
+ children: 'Sites you exclude cannot see or use this dataset — in the console or on their pages.'
489
+ }),
490
+ !scopeUnset && !visibleTo.includes(ORG_SCOPE_TOKEN) ? /*#__PURE__*/ _jsx(Stack, {
491
+ direction: "row",
492
+ spacing: 0.5,
493
+ useFlexGap: true,
494
+ sx: {
495
+ flexWrap: 'wrap'
496
+ },
497
+ children: orgHostList.map((host)=>{
498
+ var _ref, _host_name;
499
+ const token = hostScopeToken(host.$id);
500
+ const on = visibleTo.includes(token);
501
+ return /*#__PURE__*/ _jsx(Chip, {
502
+ size: "small",
503
+ label: (_ref = (_host_name = host.name) != null ? _host_name : host.subdomain) != null ? _ref : host.$id,
504
+ color: on ? 'primary' : 'default',
505
+ variant: on ? 'filled' : 'outlined',
506
+ onClick: ()=>setVisibleTo((prev)=>on ? prev.filter((t)=>t !== token) : [
507
+ ...prev,
508
+ token
509
+ ])
510
+ }, host.$id);
511
+ })
512
+ }) : null,
513
+ !scopeUnset && scopeToStore(visibleTo).problem ? /*#__PURE__*/ _jsx(Typography, {
514
+ variant: "caption",
515
+ color: "error",
516
+ children: scopeToStore(visibleTo).problem
517
+ }) : null
518
+ ]
519
+ }) : /*#__PURE__*/ _jsx(Typography, {
520
+ variant: "body2",
521
+ children: scopeUnset ? 'Never shared — hidden from every site' : describeScope(visibleTo, Object.fromEntries(orgHostList.map((host)=>{
522
+ var _ref, _host_name;
523
+ return [
524
+ host.$id,
525
+ (_ref = (_host_name = host.name) != null ? _host_name : host.subdomain) != null ? _ref : host.$id
526
+ ];
527
+ })))
528
+ })
529
+ ]
530
+ }) : null,
531
+ /*#__PURE__*/ _jsx(Stack, {
532
+ spacing: 0.5,
533
+ children: model.order.map((fieldId, index)=>{
534
+ var _DATASET_FIELD_TYPE_LABELS_field_type;
535
+ var _field_validation;
536
+ const field = model.fields[fieldId];
537
+ if (!field) return null;
538
+ return /*#__PURE__*/ _jsxs(Stack, {
539
+ direction: "row",
540
+ spacing: 1,
541
+ sx: {
542
+ alignItems: 'center',
543
+ px: 1,
544
+ py: 0.5,
545
+ border: '1px solid',
546
+ borderColor: 'divider',
547
+ borderRadius: 1
548
+ },
549
+ children: [
550
+ /*#__PURE__*/ _jsxs(Stack, {
551
+ sx: {
552
+ flex: 1,
553
+ minWidth: 0
554
+ },
555
+ children: [
556
+ /*#__PURE__*/ _jsxs(Typography, {
557
+ variant: "body2",
558
+ noWrap: true,
559
+ children: [
560
+ field.name,
561
+ /*#__PURE__*/ _jsx(Typography, {
562
+ component: "span",
563
+ variant: "caption",
564
+ color: "text.secondary",
565
+ children: ` · ${fieldId}`
566
+ })
567
+ ]
568
+ }),
569
+ field.description ? /*#__PURE__*/ _jsx(Typography, {
570
+ variant: "caption",
571
+ color: "text.secondary",
572
+ noWrap: true,
573
+ children: field.description
574
+ }) : null
575
+ ]
576
+ }),
577
+ /*#__PURE__*/ _jsx(Chip, {
578
+ size: "small",
579
+ label: (_DATASET_FIELD_TYPE_LABELS_field_type = DATASET_FIELD_TYPE_LABELS[field.type]) != null ? _DATASET_FIELD_TYPE_LABELS_field_type : field.type
580
+ }),
581
+ field.required || ((_field_validation = field.validation) == null ? void 0 : _field_validation.required) ? /*#__PURE__*/ _jsx(Chip, {
582
+ size: "small",
583
+ color: "primary",
584
+ label: "required"
585
+ }) : null,
586
+ /*#__PURE__*/ _jsx(IconButton, {
587
+ size: "small",
588
+ disabled: index === 0,
589
+ onClick: moveField(fieldId, -1),
590
+ "aria-label": "Move up",
591
+ children: '▲'
592
+ }),
593
+ /*#__PURE__*/ _jsx(IconButton, {
594
+ size: "small",
595
+ disabled: index === model.order.length - 1,
596
+ onClick: moveField(fieldId, 1),
597
+ "aria-label": "Move down",
598
+ children: '▼'
599
+ }),
600
+ /*#__PURE__*/ _jsx(Button, {
601
+ size: "small",
602
+ onClick: openFieldEditor(fieldId),
603
+ children: 'Edit'
604
+ }),
605
+ /*#__PURE__*/ _jsx(Button, {
606
+ size: "small",
607
+ color: "error",
608
+ onClick: handleFieldRemove(fieldId),
609
+ children: 'Remove'
610
+ })
611
+ ]
612
+ }, fieldId);
613
+ })
614
+ }),
615
+ /*#__PURE__*/ _jsx(Button, {
616
+ size: "small",
617
+ variant: "outlined",
618
+ color: "primary",
619
+ onClick: openFieldEditor(null),
620
+ sx: {
621
+ alignSelf: 'flex-start'
622
+ },
623
+ children: 'Add field'
624
+ })
625
+ ]
626
+ }),
627
+ /*#__PURE__*/ _jsxs(DialogActions, {
628
+ children: [
629
+ /*#__PURE__*/ _jsx(Button, {
630
+ onClick: onClose,
631
+ children: 'Cancel'
632
+ }),
633
+ /*#__PURE__*/ _jsx(Button, {
634
+ variant: "contained",
635
+ color: "primary",
636
+ disabled: !dataScope,
637
+ onClick: handleSave,
638
+ children: 'Save schema'
639
+ })
640
+ ]
641
+ })
642
+ ]
643
+ }),
644
+ /*#__PURE__*/ _jsxs(Dialog, {
645
+ open: Boolean(fieldEditor),
646
+ onClose: ()=>setFieldEditor(null),
647
+ maxWidth: "xs",
648
+ fullWidth: true,
649
+ children: [
650
+ /*#__PURE__*/ _jsx(DialogTitle, {
651
+ children: (fieldEditor == null ? void 0 : fieldEditor.fieldId) ? 'Edit field' : 'New field'
652
+ }),
653
+ /*#__PURE__*/ _jsxs(DialogContent, {
654
+ sx: {
655
+ display: 'flex',
656
+ flexDirection: 'column',
657
+ gap: 2
658
+ },
659
+ children: [
660
+ /*#__PURE__*/ _jsx(TextField, {
661
+ size: "small",
662
+ label: "Display name",
663
+ value: (_ref1 = editorDefinition == null ? void 0 : editorDefinition.name) != null ? _ref1 : '',
664
+ autoFocus: true,
665
+ onChange: (event)=>{
666
+ const name = event.target.value;
667
+ setFieldEditor((prev)=>{
668
+ if (!prev) return prev;
669
+ const next = _extends({}, prev, {
670
+ definition: _extends({}, prev.definition, {
671
+ name
672
+ })
673
+ });
674
+ // The reference id follows the display name until the user
675
+ // edits it (new fields only) — AGL-578.
676
+ if (prev.fieldId == null && !prev.idTouched) {
677
+ next.idDraft = defaultDatasetFieldId(name, new Set(model.order.map((id)=>id.toLowerCase())));
678
+ }
679
+ return next;
680
+ });
681
+ },
682
+ sx: {
683
+ mt: 1
684
+ },
685
+ helperText: "Shown to people editing records and wherever the field appears"
686
+ }),
687
+ /*#__PURE__*/ _jsx(TextField, {
688
+ size: "small",
689
+ label: "Reference ID",
690
+ value: isNewField ? (_ref2 = fieldEditor == null ? void 0 : fieldEditor.idDraft) != null ? _ref2 : '' : (_ref3 = fieldEditor == null ? void 0 : fieldEditor.fieldId) != null ? _ref3 : '',
691
+ disabled: !isNewField,
692
+ onChange: (event)=>setFieldEditor((prev)=>prev ? _extends({}, prev, {
693
+ idDraft: event.target.value,
694
+ idTouched: true
695
+ }) : prev),
696
+ error: Boolean(fieldIdError),
697
+ helperText: isNewField ? fieldIdError != null ? fieldIdError : 'Stable key used in bindings ({{item.id}}), forms, and imports. Auto-filled from the display name — edit to override. Fixed once the field is created.' : 'Reference IDs are stable keys and cannot change after creation'
698
+ }),
699
+ /*#__PURE__*/ _jsx(TextField, {
700
+ size: "small",
701
+ label: "Description",
702
+ multiline: true,
703
+ minRows: 2,
704
+ value: (_ref4 = editorDefinition == null ? void 0 : editorDefinition.description) != null ? _ref4 : '',
705
+ onChange: (event)=>setFieldEditor((prev)=>prev ? _extends({}, prev, {
706
+ definition: _extends({}, prev.definition, {
707
+ description: event.target.value
708
+ })
709
+ }) : prev),
710
+ helperText: "What this field is for — shown as a hint wherever the field appears"
711
+ }),
712
+ /*#__PURE__*/ _jsxs(TextField, {
713
+ select: true,
714
+ size: "small",
715
+ label: "Type",
716
+ value: (editorDefinition == null ? void 0 : editorDefinition.customType) ? `custom:${editorDefinition.customType}` : (_ref5 = editorDefinition == null ? void 0 : editorDefinition.type) != null ? _ref5 : 'text',
717
+ onChange: (event)=>setFieldEditor((prev)=>{
718
+ if (!prev) return prev;
719
+ const selected = event.target.value;
720
+ // Custom field types (AGL-434): ride a base storage type
721
+ // and stamp customType so the record editor + validators
722
+ // pick up the plugin behavior.
723
+ if (selected.startsWith('custom:')) {
724
+ const fieldType = getCustomFieldType(selected.slice(7));
725
+ if (!fieldType) return prev;
726
+ return _extends({}, prev, {
727
+ definition: _extends({}, prev.definition, {
728
+ type: fieldType.baseType,
729
+ customType: fieldType.name
730
+ })
731
+ });
732
+ }
733
+ const definition = _extends({}, prev.definition, {
734
+ type: selected
735
+ });
736
+ delete definition.customType;
737
+ return _extends({}, prev, {
738
+ definition
739
+ });
740
+ }),
741
+ children: [
742
+ ((fieldEditor == null ? void 0 : fieldEditor.fieldId) && editorDefinition && !AUTHORABLE_TYPES.includes(editorDefinition.type) ? DATASET_FIELD_TYPES : AUTHORABLE_TYPES).map((type)=>/*#__PURE__*/ _jsx(MenuItem, {
743
+ value: type,
744
+ children: DATASET_FIELD_TYPE_LABELS[type]
745
+ }, type)),
746
+ listCustomFieldTypes().map((fieldType)=>/*#__PURE__*/ _jsx(MenuItem, {
747
+ value: `custom:${fieldType.name}`,
748
+ children: `${fieldType.label} (${fieldType.pluginId})`
749
+ }, `custom:${fieldType.name}`))
750
+ ]
751
+ }),
752
+ (editorDefinition == null ? void 0 : editorDefinition.type) === 'reference' ? /*#__PURE__*/ _jsxs(_Fragment, {
753
+ children: [
754
+ /*#__PURE__*/ _jsx(TextField, {
755
+ select: true,
756
+ size: "small",
757
+ label: "Target collection",
758
+ value: (_ref6 = editorDefinition == null ? void 0 : (_editorDefinition_reference = editorDefinition.reference) == null ? void 0 : _editorDefinition_reference.datasetId) != null ? _ref6 : '',
759
+ onChange: (event)=>setFieldEditor((prev)=>{
760
+ var _prev_definition_reference;
761
+ return prev ? _extends({}, prev, {
762
+ definition: _extends({}, prev.definition, {
763
+ reference: _extends({}, (_prev_definition_reference = prev.definition.reference) != null ? _prev_definition_reference : {
764
+ datasetId: ''
765
+ }, {
766
+ datasetId: event.target.value
767
+ })
768
+ })
769
+ }) : prev;
770
+ }),
771
+ children: (datasets != null ? datasets : []).filter((target)=>target.$id !== (dataset == null ? void 0 : dataset.$id))// Only targets this dataset's sites can actually resolve
772
+ // (AGL-1044). A reference to a dataset a site cannot see
773
+ // renders blank with nothing to say why, so an
774
+ // unresolvable target is not offered. The currently
775
+ // selected one always stays listed — otherwise changing
776
+ // scope would silently blank an existing field.
777
+ .filter((target)=>{
778
+ var _editorDefinition_reference;
779
+ return target.$id === (editorDefinition == null ? void 0 : (_editorDefinition_reference = editorDefinition.reference) == null ? void 0 : _editorDefinition_reference.datasetId) || scopeCovers(target.visibleTo, visibleTo);
780
+ }).map((target)=>{
781
+ var _target_displayName;
782
+ return /*#__PURE__*/ _jsx(MenuItem, {
783
+ value: target.$id,
784
+ children: (_target_displayName = target.displayName) != null ? _target_displayName : target.$id
785
+ }, target.$id);
786
+ })
787
+ }),
788
+ (()=>{
789
+ var _target_displayName;
790
+ var _editorDefinition_reference;
791
+ // Flag an EXISTING reference that this dataset's scope has
792
+ // outgrown — the field was valid when created and quietly
793
+ // is not any more.
794
+ const targetId = editorDefinition == null ? void 0 : (_editorDefinition_reference = editorDefinition.reference) == null ? void 0 : _editorDefinition_reference.datasetId;
795
+ const target = (datasets != null ? datasets : []).find((d)=>d.$id === targetId);
796
+ if (!target || scopeCovers(target.visibleTo, visibleTo)) {
797
+ return null;
798
+ }
799
+ return /*#__PURE__*/ _jsx(Alert, {
800
+ severity: "warning",
801
+ children: `"${(_target_displayName = target.displayName) != null ? _target_displayName : target.$id}" is not shared with ` + 'every site this collection is. Those sites will show ' + 'this field blank. Widen its sharing, or narrow this ' + "collection's."
802
+ });
803
+ })(),
804
+ /*#__PURE__*/ _jsx(TextField, {
805
+ select: true,
806
+ size: "small",
807
+ label: "Display field",
808
+ value: (_ref7 = editorDefinition == null ? void 0 : (_editorDefinition_reference1 = editorDefinition.reference) == null ? void 0 : _editorDefinition_reference1.displayFieldId) != null ? _ref7 : '',
809
+ onChange: (event)=>setFieldEditor((prev)=>(prev == null ? void 0 : prev.definition.reference) ? _extends({}, prev, {
810
+ definition: _extends({}, prev.definition, {
811
+ reference: _extends({}, prev.definition.reference, {
812
+ displayFieldId: event.target.value
813
+ })
814
+ })
815
+ }) : prev),
816
+ helperText: "Shown in pickers and grid cells",
817
+ children: effectiveDatasetModel((_find = (datasets != null ? datasets : []).find((target)=>{
818
+ var _editorDefinition_reference;
819
+ return target.$id === (editorDefinition == null ? void 0 : (_editorDefinition_reference = editorDefinition.reference) == null ? void 0 : _editorDefinition_reference.datasetId);
820
+ })) != null ? _find : {}).order.map((targetFieldId)=>/*#__PURE__*/ _jsx(MenuItem, {
821
+ value: targetFieldId,
822
+ children: targetFieldId
823
+ }, targetFieldId))
824
+ }),
825
+ /*#__PURE__*/ _jsxs(TextField, {
826
+ select: true,
827
+ size: "small",
828
+ label: "When a referenced document is deleted",
829
+ value: (_ref8 = editorDefinition == null ? void 0 : (_editorDefinition_reference2 = editorDefinition.reference) == null ? void 0 : _editorDefinition_reference2.onDelete) != null ? _ref8 : 'setNull',
830
+ onChange: (event)=>setFieldEditor((prev)=>(prev == null ? void 0 : prev.definition.reference) ? _extends({}, prev, {
831
+ definition: _extends({}, prev.definition, {
832
+ reference: _extends({}, prev.definition.reference, {
833
+ onDelete: event.target.value
834
+ })
835
+ })
836
+ }) : prev),
837
+ children: [
838
+ /*#__PURE__*/ _jsx(MenuItem, {
839
+ value: "setNull",
840
+ children: 'Clear the reference'
841
+ }),
842
+ /*#__PURE__*/ _jsx(MenuItem, {
843
+ value: "restrict",
844
+ children: 'Block the delete'
845
+ })
846
+ ]
847
+ }),
848
+ /*#__PURE__*/ _jsx(FormControlLabel, {
849
+ control: /*#__PURE__*/ _jsx(Checkbox, {
850
+ size: "small",
851
+ checked: Boolean(editorDefinition == null ? void 0 : (_editorDefinition_reference3 = editorDefinition.reference) == null ? void 0 : _editorDefinition_reference3.multiple),
852
+ onChange: (event)=>setFieldEditor((prev)=>(prev == null ? void 0 : prev.definition.reference) ? _extends({}, prev, {
853
+ definition: _extends({}, prev.definition, {
854
+ reference: _extends({}, prev.definition.reference, {
855
+ multiple: event.target.checked
856
+ })
857
+ })
858
+ }) : prev)
859
+ }),
860
+ label: "Allow multiple (many-to-many)"
861
+ })
862
+ ]
863
+ }) : null,
864
+ /*#__PURE__*/ _jsx(FormControlLabel, {
865
+ control: /*#__PURE__*/ _jsx(Checkbox, {
866
+ size: "small",
867
+ checked: Boolean(editorDefinition == null ? void 0 : editorDefinition.required),
868
+ onChange: (event)=>setFieldEditor((prev)=>prev ? _extends({}, prev, {
869
+ definition: _extends({}, prev.definition, {
870
+ required: event.target.checked
871
+ })
872
+ }) : prev)
873
+ }),
874
+ label: "Required"
875
+ }),
876
+ /*#__PURE__*/ _jsx(TextField, {
877
+ size: "small",
878
+ label: "Default value",
879
+ value: String((_ref9 = editorDefinition == null ? void 0 : editorDefinition.default) != null ? _ref9 : ''),
880
+ onChange: (event)=>setFieldEditor((prev)=>prev ? _extends({}, prev, {
881
+ definition: _extends({}, prev.definition, event.target.value ? {
882
+ default: event.target.value
883
+ } : {
884
+ default: undefined
885
+ })
886
+ }) : prev),
887
+ helperText: "Pre-filled when creating documents"
888
+ }),
889
+ (editorDefinition == null ? void 0 : editorDefinition.type) === 'text' ? /*#__PURE__*/ _jsxs(_Fragment, {
890
+ children: [
891
+ /*#__PURE__*/ _jsx(TextField, {
892
+ size: "small",
893
+ label: "Pattern (regex)",
894
+ value: (_ref10 = editorDefinition == null ? void 0 : (_editorDefinition_validation = editorDefinition.validation) == null ? void 0 : _editorDefinition_validation.regex) != null ? _ref10 : '',
895
+ onChange: (event)=>setFieldEditor((prev)=>{
896
+ var _prev_definition_validation;
897
+ return prev ? _extends({}, prev, {
898
+ definition: _extends({}, prev.definition, {
899
+ validation: _extends({}, (_prev_definition_validation = prev.definition.validation) != null ? _prev_definition_validation : {}, event.target.value ? {
900
+ regex: event.target.value
901
+ } : {
902
+ regex: undefined
903
+ })
904
+ })
905
+ }) : prev;
906
+ })
907
+ }),
908
+ /*#__PURE__*/ _jsx(TextField, {
909
+ size: "small",
910
+ label: "Options",
911
+ value: (_ref11 = fieldEditor == null ? void 0 : fieldEditor.optionsText) != null ? _ref11 : '',
912
+ onChange: (event)=>setFieldEditor((prev)=>prev ? _extends({}, prev, {
913
+ optionsText: event.target.value
914
+ }) : prev),
915
+ helperText: "Comma-separated enum, e.g. basic, plus"
916
+ })
917
+ ]
918
+ }) : null,
919
+ editorDefinition && [
920
+ 'int32',
921
+ 'int64',
922
+ 'float',
923
+ 'timestamp',
924
+ 'text'
925
+ ].includes(editorDefinition.type) ? /*#__PURE__*/ _jsx(Stack, {
926
+ direction: "row",
927
+ spacing: 1,
928
+ children: [
929
+ 'min',
930
+ 'max'
931
+ ].map((bound)=>{
932
+ var _ref;
933
+ var _editorDefinition_validation;
934
+ return /*#__PURE__*/ _jsx(TextField, {
935
+ size: "small",
936
+ type: "number",
937
+ label: editorDefinition.type === 'text' ? `${bound === 'min' ? 'Min' : 'Max'} length` : bound === 'min' ? 'Min' : 'Max',
938
+ value: (_ref = (_editorDefinition_validation = editorDefinition.validation) == null ? void 0 : _editorDefinition_validation[bound]) != null ? _ref : '',
939
+ onChange: (event)=>setFieldEditor((prev)=>{
940
+ var _prev_definition_validation;
941
+ return prev ? _extends({}, prev, {
942
+ definition: _extends({}, prev.definition, {
943
+ validation: _extends({}, (_prev_definition_validation = prev.definition.validation) != null ? _prev_definition_validation : {}, event.target.value === '' ? {
944
+ [bound]: undefined
945
+ } : {
946
+ [bound]: Number(event.target.value)
947
+ })
948
+ })
949
+ }) : prev;
950
+ })
951
+ }, bound);
952
+ })
953
+ }) : null
954
+ ]
955
+ }),
956
+ /*#__PURE__*/ _jsxs(DialogActions, {
957
+ children: [
958
+ /*#__PURE__*/ _jsx(Button, {
959
+ onClick: ()=>setFieldEditor(null),
960
+ children: 'Cancel'
961
+ }),
962
+ /*#__PURE__*/ _jsx(Button, {
963
+ variant: "contained",
964
+ color: "primary",
965
+ disabled: !(editorDefinition == null ? void 0 : editorDefinition.name.trim()) || Boolean(fieldIdError),
966
+ onClick: handleFieldSave,
967
+ children: (fieldEditor == null ? void 0 : fieldEditor.fieldId) ? 'Save field' : 'Add field'
968
+ })
969
+ ]
970
+ })
971
+ ]
972
+ })
973
+ ]
974
+ });
975
+ }
976
+ DatasetSchemaDialog.displayName = 'DatasetSchemaDialog';
977
+ export default DatasetSchemaDialog;
978
+
979
+ //# sourceMappingURL=dataset-schema-dialog.component.js.map