@firecms/collection_editor 3.0.0-canary.2 → 3.0.0-canary.200

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 (81) hide show
  1. package/LICENSE +114 -21
  2. package/dist/ConfigControllerProvider.d.ts +2 -2
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.es.js +10058 -4774
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +10751 -3
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/types/collection_editor_controller.d.ts +4 -2
  9. package/dist/types/collection_inference.d.ts +1 -1
  10. package/dist/types/config_permissions.d.ts +2 -2
  11. package/dist/types/persisted_collection.d.ts +2 -2
  12. package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
  13. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  14. package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
  15. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +3 -1
  16. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +4 -3
  17. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
  18. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  19. package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
  20. package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
  21. package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
  22. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  23. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  24. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  25. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  26. package/dist/ui/collection_editor/utils/supported_fields.d.ts +2 -2
  27. package/dist/useCollectionEditorPlugin.d.ts +8 -11
  28. package/dist/utils/collections.d.ts +6 -0
  29. package/package.json +25 -36
  30. package/src/ConfigControllerProvider.tsx +68 -65
  31. package/src/index.ts +1 -0
  32. package/src/types/collection_editor_controller.tsx +7 -4
  33. package/src/types/collection_inference.ts +1 -1
  34. package/src/types/config_permissions.ts +1 -1
  35. package/src/types/persisted_collection.ts +3 -4
  36. package/src/ui/CollectionViewHeaderAction.tsx +10 -5
  37. package/src/ui/EditorCollectionAction.tsx +10 -63
  38. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  39. package/src/ui/HomePageEditorCollectionAction.tsx +19 -13
  40. package/src/ui/MissingReferenceWidget.tsx +2 -1
  41. package/src/ui/NewCollectionButton.tsx +12 -10
  42. package/src/ui/NewCollectionCard.tsx +3 -3
  43. package/src/ui/PropertyAddColumnComponent.tsx +11 -6
  44. package/src/ui/collection_editor/CollectionDetailsForm.tsx +105 -29
  45. package/src/ui/collection_editor/CollectionEditorDialog.tsx +114 -43
  46. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +8 -7
  47. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +39 -36
  48. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
  49. package/src/ui/collection_editor/EnumForm.tsx +12 -9
  50. package/src/ui/collection_editor/GetCodeDialog.tsx +56 -26
  51. package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
  52. package/src/ui/collection_editor/PropertyEditView.tsx +258 -80
  53. package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -10
  54. package/src/ui/collection_editor/PropertyTree.tsx +9 -7
  55. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
  56. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +3 -5
  57. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +26 -9
  58. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
  59. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
  60. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +7 -8
  61. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +55 -49
  62. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
  63. package/src/ui/collection_editor/properties/MapPropertyField.tsx +10 -10
  64. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  65. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +5 -4
  66. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  67. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +34 -19
  68. package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
  69. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
  70. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +3 -4
  71. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
  72. package/src/ui/collection_editor/templates/pages_template.ts +1 -6
  73. package/src/ui/collection_editor/utils/supported_fields.tsx +3 -3
  74. package/src/useCollectionEditorPlugin.tsx +35 -36
  75. package/src/utils/collections.ts +36 -0
  76. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  77. package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
  78. package/dist/ui/collection_editor/properties/FieldHelperView.d.ts +0 -4
  79. package/src/ui/RootCollectionSuggestions.tsx +0 -63
  80. package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
  81. package/src/ui/collection_editor/properties/FieldHelperView.tsx +0 -13
@@ -1,11 +1,12 @@
1
1
  import React, { useEffect, useState } from "react";
2
- import { EntityCollection, IconForView, SearchIconsView, singular, toSnakeCase, } from "@firecms/core";
2
+ import { EntityCollection, FieldCaption, IconForView, SearchIconsView, singular, toSnakeCase, } from "@firecms/core";
3
3
  import {
4
4
  Autocomplete,
5
5
  AutocompleteItem,
6
6
  BooleanSwitchWithLabel,
7
7
  Chip,
8
- cn,
8
+ CloseIcon,
9
+ cls,
9
10
  Container,
10
11
  DebouncedTextField,
11
12
  Dialog,
@@ -20,8 +21,9 @@ import {
20
21
  useAutoComplete
21
22
  } from "@firecms/ui";
22
23
 
23
- import { FieldHelperView } from "./properties/FieldHelperView";
24
24
  import { Field, getIn, useFormex } from "@firecms/formex";
25
+ import { useCollectionEditorController } from "../../useCollectionEditorController";
26
+ import { LayoutModeSwitch } from "./LayoutModeSwitch";
25
27
 
26
28
  export function CollectionDetailsForm({
27
29
  isNewCollection,
@@ -29,7 +31,8 @@ export function CollectionDetailsForm({
29
31
  existingPaths,
30
32
  existingIds,
31
33
  groups,
32
- parentCollection
34
+ parentCollection,
35
+ children
33
36
  }: {
34
37
  isNewCollection: boolean,
35
38
  reservedGroups?: string[];
@@ -38,6 +41,7 @@ export function CollectionDetailsForm({
38
41
  groups: string[] | null;
39
42
  parentCollection?: EntityCollection;
40
43
  parentCollectionIds?: string[];
44
+ children?: React.ReactNode;
41
45
  }) {
42
46
 
43
47
  const groupRef = React.useRef<HTMLInputElement>(null);
@@ -52,9 +56,15 @@ export function CollectionDetailsForm({
52
56
  submitCount
53
57
  } = useFormex<EntityCollection>();
54
58
 
59
+ const collectionEditor = useCollectionEditorController();
60
+
55
61
  const [iconDialogOpen, setIconDialogOpen] = useState(false);
56
62
  const [advancedPanelExpanded, setAdvancedPanelExpanded] = useState(false);
57
63
 
64
+ const updateDatabaseId = (databaseId: string) => {
65
+ setFieldValue("databaseId", databaseId ?? undefined);
66
+ }
67
+
58
68
  const updateName = (name: string) => {
59
69
  setFieldValue("name", name);
60
70
 
@@ -113,11 +123,15 @@ export function CollectionDetailsForm({
113
123
 
114
124
  <div>
115
125
  <div
116
- className="flex flex-row py-2 pt-3 items-center">
126
+ className="flex flex-row gap-2 py-2 pt-3 items-center">
117
127
  <Typography variant={!isNewCollection ? "h5" : "h4"} className={"flex-grow"}>
118
128
  {isNewCollection ? "New collection" : `${values?.name} collection`}
119
129
  </Typography>
120
- <Tooltip title={"Change icon"}>
130
+ <DefaultDatabaseField databaseId={values.databaseId}
131
+ onDatabaseIdUpdate={updateDatabaseId}/>
132
+
133
+ <Tooltip title={"Change icon"}
134
+ asChild={true}>
121
135
  <IconButton
122
136
  shape={"square"}
123
137
  onClick={() => setIconDialogOpen(true)}>
@@ -140,14 +154,15 @@ export function CollectionDetailsForm({
140
154
  value={values.name ?? ""}
141
155
  onChange={(e: any) => updateName(e.target.value)}
142
156
  label={"Name"}
157
+ autoFocus={true}
143
158
  required
144
159
  error={showErrors && Boolean(errors.name)}/>
145
- <FieldHelperView error={touched.name && Boolean(errors.name)}>
146
- {touched.name && Boolean(errors.name) ? errors.name : "Name of in this collection, usually a plural name (e.g. Products)"}
147
- </FieldHelperView>
160
+ <FieldCaption error={touched.name && Boolean(errors.name)}>
161
+ {touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
162
+ </FieldCaption>
148
163
  </div>
149
164
 
150
- <div className={cn("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
165
+ <div className={cls("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
151
166
  <Field name={"path"}
152
167
  as={DebouncedTextField}
153
168
  label={"Path"}
@@ -155,11 +170,11 @@ export function CollectionDetailsForm({
155
170
  required
156
171
  error={showErrors && Boolean(errors.path)}/>
157
172
 
158
- <FieldHelperView error={touched.path && Boolean(errors.path)}>
173
+ <FieldCaption error={touched.path && Boolean(errors.path)}>
159
174
  {touched.path && Boolean(errors.path)
160
175
  ? errors.path
161
176
  : isSubcollection ? "Relative path to the parent (no need to include the parent path)" : "Path that this collection is stored in, in the database"}
162
- </FieldHelperView>
177
+ </FieldCaption>
163
178
 
164
179
  </div>
165
180
 
@@ -190,17 +205,24 @@ export function CollectionDetailsForm({
190
205
  </AutocompleteItem>;
191
206
  })}
192
207
  </Autocomplete>
193
- <FieldHelperView>
194
- {showErrors && Boolean(errors.group) ? errors.group : "Group of the collection"}
195
- </FieldHelperView>
208
+ <FieldCaption>
209
+ {showErrors && Boolean(errors.group) ? errors.group : "Group in the home page"}
210
+ </FieldCaption>
211
+
212
+
196
213
  </div>}
197
214
 
198
- <div className={"col-span-12"}>
215
+ <LayoutModeSwitch
216
+ className={"col-span-12"}
217
+ value={values.openEntityMode ?? "side_panel"}
218
+ onChange={(value) => setFieldValue("openEntityMode", value)}/>
219
+
220
+ <div className={"col-span-12 mt-8"}>
199
221
  <ExpandablePanel
200
222
  expanded={advancedPanelExpanded}
201
223
  onExpandedChange={setAdvancedPanelExpanded}
202
224
  title={
203
- <div className="flex flex-row text-gray-500">
225
+ <div className="flex flex-row text-surface-500">
204
226
  <SettingsIcon/>
205
227
  <Typography variant={"subtitle2"}
206
228
  className="ml-2">
@@ -216,9 +238,9 @@ export function CollectionDetailsForm({
216
238
  disabled={!isNewCollection}
217
239
  label={"Collection id"}
218
240
  error={showErrors && Boolean(errors.id)}/>
219
- <FieldHelperView error={touched.id && Boolean(errors.id)}>
220
- {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
221
- </FieldHelperView>
241
+ <FieldCaption error={touched.id && Boolean(errors.id)}>
242
+ {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
243
+ </FieldCaption>
222
244
  </div>
223
245
 
224
246
  <div className={"col-span-12"}>
@@ -232,9 +254,38 @@ export function CollectionDetailsForm({
232
254
  }}
233
255
  value={values.singularName ?? ""}
234
256
  label={"Singular name"}/>
235
- <FieldHelperView error={showErrors && Boolean(errors.singularName)}>
257
+ <FieldCaption error={showErrors && Boolean(errors.singularName)}>
236
258
  {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
237
- </FieldHelperView>
259
+ </FieldCaption>
260
+ </div>
261
+ <div className={"col-span-12"}>
262
+ <TextField
263
+ error={showErrors && Boolean(errors.sideDialogWidth)}
264
+ name={"sideDialogWidth"}
265
+ type={"number"}
266
+ aria-describedby={"sideDialogWidth-helper"}
267
+ onChange={(e) => {
268
+ setFieldTouched("sideDialogWidth", true);
269
+ const value = e.target.value;
270
+ if (!value) {
271
+ setFieldValue("sideDialogWidth", null);
272
+ } else if (!isNaN(Number(value))) {
273
+ setFieldValue("sideDialogWidth", Number(value));
274
+ }
275
+ }}
276
+ endAdornment={<IconButton
277
+ size={"small"}
278
+ onClick={() => {
279
+ setFieldValue("sideDialogWidth", null);
280
+ }}
281
+ disabled={!values.sideDialogWidth}>
282
+ <CloseIcon size={"small"}/>
283
+ </IconButton>}
284
+ value={values.sideDialogWidth ?? ""}
285
+ label={"Side dialog width"}/>
286
+ <FieldCaption error={showErrors && Boolean(errors.singularName)}>
287
+ {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
288
+ </FieldCaption>
238
289
  </div>
239
290
  <div className={"col-span-12"}>
240
291
  <TextField
@@ -247,14 +298,16 @@ export function CollectionDetailsForm({
247
298
  aria-describedby="description-helper-text"
248
299
  label="Description"
249
300
  />
250
- <FieldHelperView error={showErrors && Boolean(errors.description)}>
301
+ <FieldCaption error={showErrors && Boolean(errors.description)}>
251
302
  {showErrors && Boolean(errors.description) ? errors.description : "Description of the collection, you can use markdown"}
252
- </FieldHelperView>
303
+ </FieldCaption>
253
304
  </div>
254
305
 
255
306
  <div className={"col-span-12"}>
256
307
  <Select
257
308
  name="defaultSize"
309
+ size={"large"}
310
+ fullWidth={true}
258
311
  label="Default row size"
259
312
  position={"item-aligned"}
260
313
  onChange={handleChange}
@@ -273,8 +326,10 @@ export function CollectionDetailsForm({
273
326
  <div className={"col-span-12"}>
274
327
  <Select
275
328
  name="customId"
276
- label="Data IDs generation"
329
+ label="Document IDs generation"
277
330
  position={"item-aligned"}
331
+ size={"large"}
332
+ fullWidth={true}
278
333
  disabled={customIdValue === "code_defined"}
279
334
  onValueChange={(v) => {
280
335
  if (v === "code_defined")
@@ -316,11 +371,11 @@ export function CollectionDetailsForm({
316
371
  onValueChange={(v) => setFieldValue("collectionGroup", v)}
317
372
  value={values.collectionGroup ?? false}
318
373
  />
319
- <FieldHelperView>
374
+ <FieldCaption>
320
375
  A collection group consists of all collections with the same path. This allows
321
376
  you
322
377
  to query over multiple collections at once.
323
- </FieldHelperView>
378
+ </FieldCaption>
324
379
  </div>
325
380
  <div className={"col-span-12"}>
326
381
  <BooleanSwitchWithLabel
@@ -329,15 +384,19 @@ export function CollectionDetailsForm({
329
384
  onValueChange={(v) => setFieldValue("textSearchEnabled", v)}
330
385
  value={values.textSearchEnabled ?? false}
331
386
  />
332
- <FieldHelperView>
387
+ <FieldCaption>
333
388
  Allow text search for this collection. If you have not specified a text search
334
389
  delegate, this will use the built-in local text search. This is not recommended
335
390
  for large collections, as it may incur in performance and cost issues.
336
- </FieldHelperView>
391
+ </FieldCaption>
337
392
  </div>
393
+
394
+
338
395
  </div>
339
396
  </ExpandablePanel>
340
397
 
398
+ {children}
399
+
341
400
  </div>
342
401
 
343
402
  </div>
@@ -364,3 +423,20 @@ export function CollectionDetailsForm({
364
423
  </div>
365
424
  );
366
425
  }
426
+
427
+ function DefaultDatabaseField({
428
+ databaseId,
429
+ onDatabaseIdUpdate
430
+ }: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
431
+
432
+ return <Tooltip title={"Database ID"}
433
+ side={"top"}
434
+ align={"start"}>
435
+ <TextField size={"small"}
436
+ invisible={true}
437
+ inputClassName={"text-end"}
438
+ value={databaseId ?? ""}
439
+ onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
440
+ placeholder={"(default)"}></TextField>
441
+ </Tooltip>
442
+ }
@@ -1,7 +1,9 @@
1
1
  import * as React from "react";
2
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useEffect, useRef, useState } from "react";
3
3
  import {
4
4
  CircularProgressCenter,
5
+ ConfirmationDialog,
6
+ Entity,
5
7
  EntityCollection,
6
8
  ErrorView,
7
9
  isPropertyBuilder,
@@ -25,17 +27,19 @@ import {
25
27
  import {
26
28
  ArrowBackIcon,
27
29
  Button,
28
- cn,
30
+ CheckIcon,
31
+ cls,
29
32
  coolIconKeys,
30
33
  defaultBorderMixin,
31
34
  Dialog,
32
35
  DialogActions,
33
36
  DialogContent,
34
- DoneIcon,
37
+ DialogTitle,
35
38
  IconButton,
36
39
  LoadingButton,
37
40
  Tab,
38
- Tabs
41
+ Tabs,
42
+ Typography
39
43
  } from "@firecms/ui";
40
44
  import { YupSchema } from "./CollectionYupValidation";
41
45
  import { CollectionDetailsForm } from "./CollectionDetailsForm";
@@ -76,9 +80,10 @@ export interface CollectionEditorDialogProps {
76
80
  icon: React.ReactNode
77
81
  };
78
82
  pathSuggestions?: (path?: string) => Promise<string[]>;
79
- getUser: (uid: string) => User | null;
83
+ getUser?: (uid: string) => User | null;
80
84
  getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
81
85
  parentCollection?: PersistedCollection;
86
+ existingEntities?: Entity<any>[];
82
87
  }
83
88
 
84
89
  export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
@@ -88,13 +93,13 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
88
93
  const [formDirty, setFormDirty] = React.useState<boolean>(false);
89
94
  const [unsavedChangesDialogOpen, setUnsavedChangesDialogOpen] = React.useState<boolean>(false);
90
95
 
91
- const handleCancel = useCallback(() => {
96
+ const handleCancel = () => {
92
97
  if (!formDirty) {
93
98
  props.handleClose(undefined);
94
99
  } else {
95
100
  setUnsavedChangesDialogOpen(true);
96
101
  }
97
- }, [formDirty, props.handleClose]);
102
+ };
98
103
 
99
104
  useEffect(() => {
100
105
  if (!open) {
@@ -112,6 +117,7 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
112
117
  maxWidth={"7xl"}
113
118
  onOpenChange={(open) => !open ? handleCancel() : undefined}
114
119
  >
120
+ <DialogTitle hidden>Collection editor</DialogTitle>
115
121
  {open && <CollectionEditor {...props}
116
122
  handleCancel={handleCancel}
117
123
  setFormDirty={setFormDirty}/>}
@@ -136,7 +142,7 @@ type EditorView = "welcome"
136
142
  | "extra_view"
137
143
  | "subcollections";
138
144
 
139
- export function CollectionEditor<M extends Record<string, any>>(props: CollectionEditorDialogProps & {
145
+ export function CollectionEditor(props: CollectionEditorDialogProps & {
140
146
  handleCancel: () => void,
141
147
  setFormDirty: (dirty: boolean) => void
142
148
  }) {
@@ -154,14 +160,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
154
160
  const collectionsInThisLevel = (props.parentCollection ? props.parentCollection.subcollections : collections) ?? [];
155
161
  const existingPaths = collectionsInThisLevel.map(col => col.path.trim().toLowerCase());
156
162
  const existingIds = collectionsInThisLevel.map(col => col.id?.trim().toLowerCase()).filter(Boolean) as string[];
157
- const [collection, setCollection] = React.useState<PersistedCollection<M> | undefined>();
163
+ const [collection, setCollection] = React.useState<PersistedCollection<any> | undefined>();
158
164
  const [initialLoadingCompleted, setInitialLoadingCompleted] = React.useState(false);
159
165
 
160
166
  useEffect(() => {
161
167
  try {
162
168
  if (navigation.initialised) {
163
169
  if (props.editedCollectionId) {
164
- setCollection(navigation.getCollectionFromPaths<PersistedCollection<M>>([...(props.parentCollectionIds ?? []), props.editedCollectionId]));
170
+ setCollection(navigation.getCollectionFromPaths([...(props.parentCollectionIds ?? []), props.editedCollectionId]));
165
171
  } else {
166
172
  setCollection(undefined);
167
173
  }
@@ -170,7 +176,8 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
170
176
  } catch (e) {
171
177
  console.error(e);
172
178
  }
173
- }, [navigation.getCollectionFromPaths, props.editedCollectionId, props.parentCollectionIds, navigation.initialised]);
179
+ }, [props.editedCollectionId, props.parentCollectionIds, navigation.initialised, navigation.getCollectionFromPaths]);
180
+
174
181
  if (!topLevelNavigation) {
175
182
  throw Error("Internal: Navigation not ready in collection editor");
176
183
  }
@@ -186,14 +193,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
186
193
  }
187
194
  : undefined;
188
195
 
189
- const initialValues: PersistedCollection<M> = initialCollection
196
+ const initialValues: PersistedCollection<any> = initialCollection
190
197
  ? applyPropertyConfigs(initialCollection, propertyConfigs)
191
198
  : {
192
199
  id: initialValuesProp?.path ?? randomString(16),
193
200
  path: initialValuesProp?.path ?? "",
194
201
  name: initialValuesProp?.name ?? "",
195
202
  group: initialValuesProp?.group ?? "",
196
- properties: {} as PropertiesOrBuilders<M>,
203
+ properties: {} as PropertiesOrBuilders,
197
204
  propertiesOrder: [],
198
205
  icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
199
206
  ownerId: authController.user?.uid ?? ""
@@ -243,7 +250,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
243
250
  setCollection,
244
251
  initialValues,
245
252
  propertyConfigs,
246
- groups
253
+ groups,
254
+ existingEntities
247
255
  }: CollectionEditorDialogProps & {
248
256
  handleCancel: () => void,
249
257
  setFormDirty: (dirty: boolean) => void,
@@ -272,6 +280,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
272
280
 
273
281
  const saveCollection = (updatedCollection: PersistedCollection<M>): Promise<boolean> => {
274
282
  const id = updatedCollection.id || updatedCollection.path;
283
+
275
284
  return configController.saveCollection({
276
285
  id,
277
286
  collectionData: updatedCollection,
@@ -293,7 +302,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
293
302
  });
294
303
  };
295
304
 
296
- const setNextMode = useCallback(() => {
305
+ const setNextMode = () => {
297
306
  if (currentView === "details") {
298
307
  if (importConfig.inUse) {
299
308
  setCurrentView("import_data_saving");
@@ -314,14 +323,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
314
323
  setCurrentView("details");
315
324
  }
316
325
 
317
- }, [currentView, importConfig.inUse, extraView]);
326
+ };
318
327
 
319
- const doCollectionInference = useCallback((collection: PersistedCollection<any>) => {
328
+ const doCollectionInference = (collection: PersistedCollection<any>) => {
320
329
  if (!collectionInference) return undefined;
321
- return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentCollectionIds ?? []);
322
- }, [collectionInference, parentCollectionIds]);
330
+ return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentPaths ?? []);
331
+ };
323
332
 
324
- const inferCollectionFromData = useCallback(async (newCollection: PersistedCollection<M>) => {
333
+ const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
325
334
 
326
335
  try {
327
336
  if (!doCollectionInference) {
@@ -365,15 +374,15 @@ function CollectionEditorInternal<M extends Record<string, any>>({
365
374
  });
366
375
  return newCollection;
367
376
  }
368
- }, [parentCollectionIds, doCollectionInference]);
377
+ };
369
378
 
370
379
  const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
371
- console.log("Submitting collection", newCollectionState);
380
+ console.debug("Submitting collection", newCollectionState);
372
381
  try {
373
382
 
374
383
  if (!isNewCollection) {
375
384
  saveCollection(newCollectionState).then(() => {
376
- formexController.resetForm({ values: initialValues });
385
+ formexController.resetForm();
377
386
  handleClose(newCollectionState);
378
387
  });
379
388
  return;
@@ -462,7 +471,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
462
471
  const formController = useCreateFormex<PersistedCollection<M>>({
463
472
  initialValues,
464
473
  onSubmit,
465
- validation
474
+ validation,
475
+ debugId: "COLLECTION_EDITOR"
466
476
  });
467
477
 
468
478
  const {
@@ -473,31 +483,43 @@ function CollectionEditorInternal<M extends Record<string, any>>({
473
483
  submitCount
474
484
  } = formController;
475
485
 
476
- const path = values.path ?? editedCollectionId;
486
+ // TODO: getting data is only working in root collections with this code
487
+ const path = values.path;
477
488
  const updatedFullPath = fullPath?.includes("/") ? fullPath?.split("/").slice(0, -1).join("/") + "/" + path : path; // TODO: this path is wrong
478
489
  const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
479
490
 
480
491
  const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
481
- const resolvedPath = !pathError ? navigation.resolveAliasesFrom(updatedFullPath) : undefined;
482
- const getDataWithPath = resolvedPath && getData ? () => getData(resolvedPath, parentPaths ?? []) : undefined;
492
+ const resolvedPath = !pathError ? navigation.resolveIdsFrom(updatedFullPath) : undefined;
493
+ const getDataWithPath = resolvedPath && getData ? async () => {
494
+ const data = await getData(resolvedPath, parentPaths ?? []);
495
+ if (existingEntities) {
496
+ const existingData = existingEntities.map(e => e.values);
497
+ data.push(...existingData);
498
+ }
499
+ return data;
500
+ } : undefined;
483
501
 
484
502
  useEffect(() => {
485
503
  setFormDirty(dirty);
486
504
  }, [dirty]);
487
505
 
488
- function onImportDataSet(data: object[]) {
506
+ function onImportDataSet(data: object[], propertiesOrder?: string[]) {
489
507
  importConfig.setInUse(true);
490
508
  buildEntityPropertiesFromData(data, getInferenceType)
491
509
  .then((properties) => {
492
510
  const res = cleanPropertiesFromImport(properties);
493
511
 
494
- setFieldValue("properties", res.properties);
495
- setFieldValue("propertiesOrder", Object.keys(res.properties));
496
-
497
512
  importConfig.setIdColumn(res.idColumn);
498
513
  importConfig.setImportData(data);
499
514
  importConfig.setHeadersMapping(res.headersMapping);
515
+ const filteredHeadingsOrder = ((propertiesOrder ?? [])
516
+ .filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
517
+ importConfig.setHeadingsOrder(filteredHeadingsOrder);
500
518
  importConfig.setOriginProperties(res.properties);
519
+
520
+ const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
521
+ setFieldValue("properties", res.properties);
522
+ setFieldValue("propertiesOrder", mappedHeadings);
501
523
  });
502
524
  }
503
525
 
@@ -513,14 +535,30 @@ function CollectionEditorInternal<M extends Record<string, any>>({
513
535
  };
514
536
 
515
537
  const editable = collection?.editable === undefined || collection?.editable === true;
538
+ // @ts-ignore
539
+ const isMergedCollection = collection?.merged ?? false;
516
540
  const collectionEditable = editable || isNewCollection;
517
541
 
542
+ const [deleteRequested, setDeleteRequested] = useState(false);
543
+
544
+ const deleteCollection = () => {
545
+ if (!collection) return;
546
+ configController?.deleteCollection({ id: collection.id }).then(() => {
547
+ setDeleteRequested(false);
548
+ handleCancel();
549
+ snackbarController.open({
550
+ message: "Collection deleted",
551
+ type: "success"
552
+ });
553
+ });
554
+ };
555
+
518
556
  return <DialogContent fullHeight={true}>
519
557
  <Formex value={formController}>
520
558
 
521
559
  <>
522
560
  {!isNewCollection && <Tabs value={currentView}
523
- className={cn(defaultBorderMixin, "justify-end bg-gray-50 dark:bg-gray-950 border-b")}
561
+ innerClassName={cls(defaultBorderMixin, "px-4 h-14 w-full justify-end bg-surface-50 dark:bg-surface-950 border-b")}
524
562
  onValueChange={(v) => setCurrentView(v as EditorView)}>
525
563
  <Tab value={"details"}>
526
564
  Details
@@ -535,7 +573,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
535
573
 
536
574
  <form noValidate
537
575
  onSubmit={formController.handleSubmit}
538
- className={cn(
576
+ className={cls(
539
577
  isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
540
578
  "flex-grow flex flex-col relative")}>
541
579
 
@@ -550,9 +588,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
550
588
  {currentView === "welcome" &&
551
589
  <CollectionEditorWelcomeView
552
590
  path={path}
553
- onContinue={(importData) => {
591
+ onContinue={(importData, propertiesOrder) => {
592
+ // console.log("Import data", importData, propertiesOrder)
554
593
  if (importData) {
555
- onImportDataSet(importData);
594
+ onImportDataSet(importData, propertiesOrder);
556
595
  setCurrentView("import_data_mapping");
557
596
  } else {
558
597
  setCurrentView("details");
@@ -575,6 +614,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
575
614
  {currentView === "import_data_saving" && importConfig &&
576
615
  <ImportSaveInProgress importConfig={importConfig}
577
616
  collection={values}
617
+ path={path}
578
618
  onImportSuccess={async (importedCollection) => {
579
619
  snackbarController.open({
580
620
  type: "info",
@@ -592,7 +632,18 @@ function CollectionEditorInternal<M extends Record<string, any>>({
592
632
  groups={groups}
593
633
  parentCollectionIds={parentCollectionIds}
594
634
  parentCollection={parentCollection}
595
- isNewCollection={isNewCollection}/>}
635
+ isNewCollection={isNewCollection}>
636
+ {!isNewCollection && isMergedCollection && <div className={"flex flex-col gap-4 mt-8"}>
637
+ <Typography variant={"body2"} color={"secondary"}>This collection is defined in code.
638
+ The changes done in this editor will override the properties defined in code.
639
+ You can delete the overridden values to revert to the state defined in code.
640
+ </Typography>
641
+ <Button variant={"neutral"}
642
+ onClick={() => {
643
+ setDeleteRequested(true);
644
+ }}>Reset to code</Button>
645
+ </div>}
646
+ </CollectionDetailsForm>}
596
647
 
597
648
  {currentView === "subcollections" && collection &&
598
649
  <SubcollectionsEditTab
@@ -704,7 +755,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
704
755
  loading={isSubmitting}
705
756
  disabled={isSubmitting || (currentView === "details" && !validValues)}
706
757
  startIcon={currentView === "properties"
707
- ? <DoneIcon/>
758
+ ? <CheckIcon/>
708
759
  : undefined}
709
760
  >
710
761
  {currentView === "details" && "Next"}
@@ -726,18 +777,35 @@ function CollectionEditorInternal<M extends Record<string, any>>({
726
777
 
727
778
  </Formex>
728
779
 
780
+ <ConfirmationDialog
781
+ open={deleteRequested}
782
+ onAccept={deleteCollection}
783
+ onCancel={() => setDeleteRequested(false)}
784
+ title={<>Delete the stored config?</>}
785
+ body={<> This will <b>not
786
+ delete any data</b>, only
787
+ the stored config, and reset to the code state.</>}/>
788
+
729
789
  </DialogContent>
730
790
 
731
791
  }
732
792
 
733
793
  function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
734
- const { properties, ...rest } = collection;
794
+ const {
795
+ properties,
796
+ ...rest
797
+ } = collection;
735
798
  const propertiesResult: PropertiesOrBuilders<any> = {};
736
- Object.keys(properties).forEach((key) => {
737
- propertiesResult[key] = applyPropertiesConfig(properties[key] as PropertyOrBuilder, propertyConfigs);
738
- });
799
+ if (properties) {
800
+ Object.keys(properties).forEach((key) => {
801
+ propertiesResult[key] = applyPropertiesConfig(properties[key] as PropertyOrBuilder, propertyConfigs);
802
+ });
803
+ }
739
804
 
740
- return { ...rest, properties: propertiesResult };
805
+ return {
806
+ ...rest,
807
+ properties: propertiesResult
808
+ };
741
809
  }
742
810
 
743
811
  function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
@@ -757,7 +825,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
757
825
  Object.keys(internalProperty.properties).forEach((key) => {
758
826
  properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
759
827
  });
760
- internalProperty = { ...internalProperty, properties };
828
+ internalProperty = {
829
+ ...internalProperty,
830
+ properties
831
+ };
761
832
  }
762
833
 
763
834
  }