@firecms/collection_editor 3.0.0-canary.15 → 3.0.0-canary.151

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 (67) hide show
  1. package/LICENSE +114 -21
  2. package/dist/ConfigControllerProvider.d.ts +11 -2
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.es.js +4921 -3536
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +6846 -3
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/types/collection_editor_controller.d.ts +14 -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 +1 -1
  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/CollectionEditorDialog.d.ts +4 -3
  16. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
  17. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  18. package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
  19. package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
  20. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  21. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  22. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  23. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  24. package/dist/useCollectionEditorPlugin.d.ts +17 -11
  25. package/dist/utils/collections.d.ts +6 -0
  26. package/package.json +21 -35
  27. package/src/ConfigControllerProvider.tsx +75 -63
  28. package/src/index.ts +1 -0
  29. package/src/types/collection_editor_controller.tsx +14 -4
  30. package/src/types/collection_inference.ts +1 -1
  31. package/src/types/config_permissions.ts +1 -1
  32. package/src/types/persisted_collection.ts +2 -3
  33. package/src/ui/CollectionViewHeaderAction.tsx +10 -5
  34. package/src/ui/EditorCollectionAction.tsx +10 -63
  35. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  36. package/src/ui/HomePageEditorCollectionAction.tsx +18 -13
  37. package/src/ui/NewCollectionButton.tsx +12 -10
  38. package/src/ui/NewCollectionCard.tsx +3 -3
  39. package/src/ui/PropertyAddColumnComponent.tsx +11 -6
  40. package/src/ui/collection_editor/CollectionDetailsForm.tsx +70 -9
  41. package/src/ui/collection_editor/CollectionEditorDialog.tsx +61 -34
  42. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +8 -7
  43. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +37 -34
  44. package/src/ui/collection_editor/EnumForm.tsx +5 -2
  45. package/src/ui/collection_editor/GetCodeDialog.tsx +52 -21
  46. package/src/ui/collection_editor/PropertyEditView.tsx +255 -80
  47. package/src/ui/collection_editor/PropertyFieldPreview.tsx +4 -7
  48. package/src/ui/collection_editor/PropertyTree.tsx +7 -5
  49. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
  50. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +25 -9
  51. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +40 -9
  52. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
  53. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +50 -47
  54. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +1 -1
  55. package/src/ui/collection_editor/properties/MapPropertyField.tsx +7 -6
  56. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  57. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  58. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +32 -17
  59. package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
  60. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
  61. package/src/ui/collection_editor/templates/pages_template.ts +1 -6
  62. package/src/useCollectionEditorPlugin.tsx +41 -31
  63. package/src/utils/collections.ts +30 -0
  64. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  65. package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
  66. package/src/ui/RootCollectionSuggestions.tsx +0 -63
  67. package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
@@ -1,7 +1,8 @@
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
+ Entity,
5
6
  EntityCollection,
6
7
  ErrorView,
7
8
  isPropertyBuilder,
@@ -25,7 +26,7 @@ import {
25
26
  import {
26
27
  ArrowBackIcon,
27
28
  Button,
28
- cn,
29
+ cls,
29
30
  coolIconKeys,
30
31
  defaultBorderMixin,
31
32
  Dialog,
@@ -76,9 +77,10 @@ export interface CollectionEditorDialogProps {
76
77
  icon: React.ReactNode
77
78
  };
78
79
  pathSuggestions?: (path?: string) => Promise<string[]>;
79
- getUser: (uid: string) => User | null;
80
+ getUser?: (uid: string) => User | null;
80
81
  getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
81
82
  parentCollection?: PersistedCollection;
83
+ existingEntities?: Entity<any>[];
82
84
  }
83
85
 
84
86
  export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
@@ -88,13 +90,13 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
88
90
  const [formDirty, setFormDirty] = React.useState<boolean>(false);
89
91
  const [unsavedChangesDialogOpen, setUnsavedChangesDialogOpen] = React.useState<boolean>(false);
90
92
 
91
- const handleCancel = useCallback(() => {
93
+ const handleCancel = () => {
92
94
  if (!formDirty) {
93
95
  props.handleClose(undefined);
94
96
  } else {
95
97
  setUnsavedChangesDialogOpen(true);
96
98
  }
97
- }, [formDirty, props.handleClose]);
99
+ };
98
100
 
99
101
  useEffect(() => {
100
102
  if (!open) {
@@ -136,7 +138,7 @@ type EditorView = "welcome"
136
138
  | "extra_view"
137
139
  | "subcollections";
138
140
 
139
- export function CollectionEditor<M extends Record<string, any>>(props: CollectionEditorDialogProps & {
141
+ export function CollectionEditor(props: CollectionEditorDialogProps & {
140
142
  handleCancel: () => void,
141
143
  setFormDirty: (dirty: boolean) => void
142
144
  }) {
@@ -154,14 +156,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
154
156
  const collectionsInThisLevel = (props.parentCollection ? props.parentCollection.subcollections : collections) ?? [];
155
157
  const existingPaths = collectionsInThisLevel.map(col => col.path.trim().toLowerCase());
156
158
  const existingIds = collectionsInThisLevel.map(col => col.id?.trim().toLowerCase()).filter(Boolean) as string[];
157
- const [collection, setCollection] = React.useState<PersistedCollection<M> | undefined>();
159
+ const [collection, setCollection] = React.useState<PersistedCollection<any> | undefined>();
158
160
  const [initialLoadingCompleted, setInitialLoadingCompleted] = React.useState(false);
159
161
 
160
162
  useEffect(() => {
161
163
  try {
162
164
  if (navigation.initialised) {
163
165
  if (props.editedCollectionId) {
164
- setCollection(navigation.getCollectionFromPaths<PersistedCollection<M>>([...(props.parentCollectionIds ?? []), props.editedCollectionId]));
166
+ setCollection(navigation.getCollectionFromPaths([...(props.parentCollectionIds ?? []), props.editedCollectionId]));
165
167
  } else {
166
168
  setCollection(undefined);
167
169
  }
@@ -170,7 +172,8 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
170
172
  } catch (e) {
171
173
  console.error(e);
172
174
  }
173
- }, [navigation.getCollectionFromPaths, props.editedCollectionId, props.parentCollectionIds, navigation.initialised]);
175
+ }, [props.editedCollectionId, props.parentCollectionIds, navigation.initialised, navigation.getCollectionFromPaths]);
176
+
174
177
  if (!topLevelNavigation) {
175
178
  throw Error("Internal: Navigation not ready in collection editor");
176
179
  }
@@ -186,14 +189,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
186
189
  }
187
190
  : undefined;
188
191
 
189
- const initialValues: PersistedCollection<M> = initialCollection
192
+ const initialValues: PersistedCollection<any> = initialCollection
190
193
  ? applyPropertyConfigs(initialCollection, propertyConfigs)
191
194
  : {
192
195
  id: initialValuesProp?.path ?? randomString(16),
193
196
  path: initialValuesProp?.path ?? "",
194
197
  name: initialValuesProp?.name ?? "",
195
198
  group: initialValuesProp?.group ?? "",
196
- properties: {} as PropertiesOrBuilders<M>,
199
+ properties: {} as PropertiesOrBuilders,
197
200
  propertiesOrder: [],
198
201
  icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
199
202
  ownerId: authController.user?.uid ?? ""
@@ -243,7 +246,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
243
246
  setCollection,
244
247
  initialValues,
245
248
  propertyConfigs,
246
- groups
249
+ groups,
250
+ existingEntities
247
251
  }: CollectionEditorDialogProps & {
248
252
  handleCancel: () => void,
249
253
  setFormDirty: (dirty: boolean) => void,
@@ -272,6 +276,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
272
276
 
273
277
  const saveCollection = (updatedCollection: PersistedCollection<M>): Promise<boolean> => {
274
278
  const id = updatedCollection.id || updatedCollection.path;
279
+
275
280
  return configController.saveCollection({
276
281
  id,
277
282
  collectionData: updatedCollection,
@@ -293,7 +298,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
293
298
  });
294
299
  };
295
300
 
296
- const setNextMode = useCallback(() => {
301
+ const setNextMode = () => {
297
302
  if (currentView === "details") {
298
303
  if (importConfig.inUse) {
299
304
  setCurrentView("import_data_saving");
@@ -314,14 +319,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
314
319
  setCurrentView("details");
315
320
  }
316
321
 
317
- }, [currentView, importConfig.inUse, extraView]);
322
+ };
318
323
 
319
- const doCollectionInference = useCallback((collection: PersistedCollection<any>) => {
324
+ const doCollectionInference = (collection: PersistedCollection<any>) => {
320
325
  if (!collectionInference) return undefined;
321
- return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentCollectionIds ?? []);
322
- }, [collectionInference, parentCollectionIds]);
326
+ return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentPaths ?? []);
327
+ };
323
328
 
324
- const inferCollectionFromData = useCallback(async (newCollection: PersistedCollection<M>) => {
329
+ const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
325
330
 
326
331
  try {
327
332
  if (!doCollectionInference) {
@@ -365,15 +370,15 @@ function CollectionEditorInternal<M extends Record<string, any>>({
365
370
  });
366
371
  return newCollection;
367
372
  }
368
- }, [parentCollectionIds, doCollectionInference]);
373
+ };
369
374
 
370
375
  const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
371
- console.log("Submitting collection", newCollectionState);
376
+ console.debug("Submitting collection", newCollectionState);
372
377
  try {
373
378
 
374
379
  if (!isNewCollection) {
375
380
  saveCollection(newCollectionState).then(() => {
376
- formexController.resetForm({ values: initialValues });
381
+ formexController.resetForm();
377
382
  handleClose(newCollectionState);
378
383
  });
379
384
  return;
@@ -462,7 +467,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
462
467
  const formController = useCreateFormex<PersistedCollection<M>>({
463
468
  initialValues,
464
469
  onSubmit,
465
- validation
470
+ validation,
471
+ debugId: "COLLECTION_EDITOR"
466
472
  });
467
473
 
468
474
  const {
@@ -480,25 +486,36 @@ function CollectionEditorInternal<M extends Record<string, any>>({
480
486
 
481
487
  const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
482
488
  const resolvedPath = !pathError ? navigation.resolveAliasesFrom(updatedFullPath) : undefined;
483
- const getDataWithPath = resolvedPath && getData ? () => getData(resolvedPath, parentPaths ?? []) : undefined;
489
+ const getDataWithPath = resolvedPath && getData ? async () => {
490
+ const data = await getData(resolvedPath, parentPaths ?? []);
491
+ if (existingEntities) {
492
+ const existingData = existingEntities.map(e => e.values);
493
+ data.push(...existingData);
494
+ }
495
+ return data;
496
+ } : undefined;
484
497
 
485
498
  useEffect(() => {
486
499
  setFormDirty(dirty);
487
500
  }, [dirty]);
488
501
 
489
- function onImportDataSet(data: object[]) {
502
+ function onImportDataSet(data: object[], propertiesOrder?: string[]) {
490
503
  importConfig.setInUse(true);
491
504
  buildEntityPropertiesFromData(data, getInferenceType)
492
505
  .then((properties) => {
493
506
  const res = cleanPropertiesFromImport(properties);
494
507
 
495
- setFieldValue("properties", res.properties);
496
- setFieldValue("propertiesOrder", Object.keys(res.properties));
497
-
498
508
  importConfig.setIdColumn(res.idColumn);
499
509
  importConfig.setImportData(data);
500
510
  importConfig.setHeadersMapping(res.headersMapping);
511
+ const filteredHeadingsOrder = ((propertiesOrder ?? [])
512
+ .filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
513
+ importConfig.setHeadingsOrder(filteredHeadingsOrder);
501
514
  importConfig.setOriginProperties(res.properties);
515
+
516
+ const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
517
+ setFieldValue("properties", res.properties);
518
+ setFieldValue("propertiesOrder", mappedHeadings);
502
519
  });
503
520
  }
504
521
 
@@ -521,7 +538,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
521
538
 
522
539
  <>
523
540
  {!isNewCollection && <Tabs value={currentView}
524
- className={cn(defaultBorderMixin, "justify-end bg-gray-50 dark:bg-gray-950 border-b")}
541
+ innerClassName={cls(defaultBorderMixin, "justify-end bg-surface-50 dark:bg-surface-950 border-b")}
525
542
  onValueChange={(v) => setCurrentView(v as EditorView)}>
526
543
  <Tab value={"details"}>
527
544
  Details
@@ -536,7 +553,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
536
553
 
537
554
  <form noValidate
538
555
  onSubmit={formController.handleSubmit}
539
- className={cn(
556
+ className={cls(
540
557
  isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
541
558
  "flex-grow flex flex-col relative")}>
542
559
 
@@ -551,9 +568,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
551
568
  {currentView === "welcome" &&
552
569
  <CollectionEditorWelcomeView
553
570
  path={path}
554
- onContinue={(importData) => {
571
+ onContinue={(importData, propertiesOrder) => {
572
+ // console.log("Import data", importData, propertiesOrder)
555
573
  if (importData) {
556
- onImportDataSet(importData);
574
+ onImportDataSet(importData, propertiesOrder);
557
575
  setCurrentView("import_data_mapping");
558
576
  } else {
559
577
  setCurrentView("details");
@@ -733,7 +751,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
733
751
  }
734
752
 
735
753
  function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
736
- const { properties, ...rest } = collection;
754
+ const {
755
+ properties,
756
+ ...rest
757
+ } = collection;
737
758
  const propertiesResult: PropertiesOrBuilders<any> = {};
738
759
  if (properties) {
739
760
  Object.keys(properties).forEach((key) => {
@@ -741,7 +762,10 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
741
762
  });
742
763
  }
743
764
 
744
- return { ...rest, properties: propertiesResult };
765
+ return {
766
+ ...rest,
767
+ properties: propertiesResult
768
+ };
745
769
  }
746
770
 
747
771
  function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
@@ -761,7 +785,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
761
785
  Object.keys(internalProperty.properties).forEach((key) => {
762
786
  properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
763
787
  });
764
- internalProperty = { ...internalProperty, properties };
788
+ internalProperty = {
789
+ ...internalProperty,
790
+ properties
791
+ };
765
792
  }
766
793
 
767
794
  }
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useState } from "react";
2
2
  import { EntityCollection, unslugify, } from "@firecms/core";
3
- import { Button, Card, Chip, CircularProgress, cn, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
3
+ import { Button, Card, Chip, CircularProgress, cls, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
4
4
 
5
5
  import { productsCollectionTemplate } from "./templates/products_template";
6
6
  import { blogCollectionTemplate } from "./templates/blog_template";
@@ -19,7 +19,7 @@ export function CollectionEditorWelcomeView({
19
19
  path: string;
20
20
  pathSuggestions?: (path: string) => Promise<string[]>;
21
21
  parentCollection?: EntityCollection;
22
- onContinue: (importData?: object[]) => void;
22
+ onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
23
23
  existingCollectionPaths?: string[];
24
24
  }) {
25
25
 
@@ -154,7 +154,7 @@ export function CollectionEditorWelcomeView({
154
154
  ● Create a collection from a file (csv, json, xls, xslx...)
155
155
  </Typography>
156
156
 
157
- <ImportFileUpload onDataAdded={(data) => onContinue(data)}/>
157
+ <ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)}/>
158
158
 
159
159
  </div>}
160
160
 
@@ -185,14 +185,15 @@ export function TemplateButton({
185
185
  }) {
186
186
 
187
187
  return (
188
- <Tooltip title={subtitle}>
188
+ <Tooltip title={subtitle}
189
+ asChild={true}>
189
190
  <Card
190
191
  onClick={onClick}
191
- className={cn(
192
+ className={cls(
192
193
  "my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
193
- "text-gray-700 dark:text-slate-300",
194
+ "text-surface-700 dark:text-surface-accent-300",
194
195
  "hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
195
- "border-gray-400 dark:border-gray-600 "
196
+ "border-surface-400 dark:border-surface-600 "
196
197
  )}
197
198
  >
198
199
  {icon}
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from "react";
1
+ import React, { useEffect, useMemo, useState } from "react";
2
2
 
3
3
  import { Field, getIn, useFormex } from "@firecms/formex";
4
4
  import {
@@ -19,7 +19,7 @@ import {
19
19
  AutoAwesomeIcon,
20
20
  Button,
21
21
  CircularProgress,
22
- cn,
22
+ cls,
23
23
  CodeIcon,
24
24
  DebouncedTextField,
25
25
  defaultBorderMixin,
@@ -43,7 +43,7 @@ type CollectionEditorFormProps = {
43
43
  setDirty?: (dirty: boolean) => void;
44
44
  reservedGroups?: string[];
45
45
  extraIcon: React.ReactNode;
46
- getUser: (uid: string) => User | null;
46
+ getUser?: (uid: string) => User | null;
47
47
  getData?: () => Promise<object[]>;
48
48
  doCollectionInference: (collection: PersistedCollection) => Promise<Partial<EntityCollection> | null> | undefined;
49
49
  propertyConfigs: Record<string, PropertyConfig>;
@@ -158,20 +158,20 @@ export function CollectionPropertiesEditorForm({
158
158
  }
159
159
  : undefined;
160
160
 
161
- const getCurrentPropertiesOrder = useCallback((namespace?: string) => {
162
- if (!namespace) return currentPropertiesOrderRef.current[""];
161
+ const getCurrentPropertiesOrder = (namespace?: string) => {
162
+ if (!namespace) return currentPropertiesOrderRef.current[""] ?? getIn(values, namespaceToPropertiesOrderPath());
163
163
  return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
164
- }, [values]);
164
+ };
165
165
 
166
- const updatePropertiesOrder = useCallback((newPropertiesOrder: string[], namespace?: string) => {
166
+ const updatePropertiesOrder = (newPropertiesOrder: string[], namespace?: string) => {
167
167
  const propertiesOrderPath = namespaceToPropertiesOrderPath(namespace);
168
168
 
169
169
  setFieldValue(propertiesOrderPath, newPropertiesOrder, false);
170
170
  currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
171
171
 
172
- }, [setFieldValue]);
172
+ };
173
173
 
174
- const deleteProperty = useCallback((propertyKey?: string, namespace?: string) => {
174
+ const deleteProperty = (propertyKey?: string, namespace?: string) => {
175
175
  const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
176
176
  if (!fullId)
177
177
  throw Error("collection editor miss config");
@@ -179,15 +179,17 @@ export function CollectionPropertiesEditorForm({
179
179
  setFieldValue(idToPropertiesPath(fullId), undefined, false);
180
180
 
181
181
  const currentPropertiesOrder = getCurrentPropertiesOrder(namespace);
182
- const newPropertiesOrder = currentPropertiesOrder.filter((p) => p !== propertyKey);
183
- updatePropertiesOrder(newPropertiesOrder, namespace);
182
+ if (currentPropertiesOrder) {
183
+ const newPropertiesOrder = currentPropertiesOrder.filter((p) => p !== propertyKey);
184
+ updatePropertiesOrder(newPropertiesOrder, namespace);
185
+ }
184
186
 
185
187
  setNewPropertyDialogOpen(false);
186
188
 
187
189
  setSelectedPropertyIndex(undefined);
188
190
  setSelectedPropertyKey(undefined);
189
191
  setSelectedPropertyNamespace(undefined);
190
- }, [getCurrentPropertiesOrder, setFieldValue, updatePropertiesOrder]);
192
+ };
191
193
 
192
194
  const onPropertyMove = (propertiesOrder: string[], namespace?: string) => {
193
195
  setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
@@ -207,8 +209,8 @@ export function CollectionPropertiesEditorForm({
207
209
  ...(values.properties ?? {}),
208
210
  [id]: property
209
211
  }, false);
210
- const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
211
212
 
213
+ const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
212
214
  updatePropertiesOrder(newPropertiesOrder);
213
215
 
214
216
  setNewPropertyDialogOpen(false);
@@ -226,17 +228,13 @@ export function CollectionPropertiesEditorForm({
226
228
  namespace
227
229
  }: OnPropertyChangedParams) => {
228
230
 
231
+ console.log("!!!!!! onPropertyChanged", property)
232
+
229
233
  const fullId = id ? getFullId(id, namespace) : undefined;
230
234
  const propertyPath = fullId ? idToPropertiesPath(fullId) : undefined;
231
235
 
232
236
  // If the id has changed we need to a little cleanup
233
237
  if (previousId && previousId !== id) {
234
- console.debug("onPropertyChanged, id change", {
235
- id,
236
- property,
237
- previousId,
238
- namespace
239
- })
240
238
 
241
239
  const previousFullId = getFullId(previousId, namespace);
242
240
  const previousPropertyPath = idToPropertiesPath(previousFullId);
@@ -273,7 +271,7 @@ export function CollectionPropertiesEditorForm({
273
271
 
274
272
  };
275
273
 
276
- const onPropertyErrorInternal = useCallback((id: string, namespace?: string, error?: Record<string, any>) => {
274
+ const onPropertyErrorInternal = (id: string, namespace?: string, error?: Record<string, any>) => {
277
275
  const propertyPath = id ? getFullId(id, namespace) : undefined;
278
276
  console.debug("onPropertyErrorInternal", {
279
277
  id,
@@ -286,7 +284,7 @@ export function CollectionPropertiesEditorForm({
286
284
  onPropertyError(id, namespace, hasError ? error : undefined);
287
285
  setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
288
286
  }
289
- }, [])
287
+ }
290
288
 
291
289
  const closePropertyDialog = () => {
292
290
  setSelectedPropertyIndex(undefined);
@@ -301,9 +299,9 @@ export function CollectionPropertiesEditorForm({
301
299
  ? values.propertiesOrder
302
300
  : Object.keys(values.properties)) as string[];
303
301
 
304
- const owner = useMemo(() => values.ownerId ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
302
+ const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
305
303
 
306
- const onPropertyClick = useCallback((propertyKey: string, namespace?: string) => {
304
+ const onPropertyClick = (propertyKey: string, namespace?: string) => {
307
305
  console.debug("CollectionEditor: onPropertyClick", {
308
306
  propertyKey,
309
307
  namespace
@@ -311,11 +309,11 @@ export function CollectionPropertiesEditorForm({
311
309
  setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
312
310
  setSelectedPropertyKey(propertyKey);
313
311
  setSelectedPropertyNamespace(namespace);
314
- }, [usedPropertiesOrder]);
312
+ };
315
313
 
316
314
  const body = (
317
- <div className={"grid grid-cols-12 gap-2 h-full bg-gray-50 dark:bg-gray-900"}>
318
- <div className={cn(
315
+ <div className={"grid grid-cols-12 gap-2 h-full bg-surface-50 dark:bg-surface-900"}>
316
+ <div className={cls(
319
317
  "p-4 md:p-8 pb-20 md:pb-20",
320
318
  "col-span-12 lg:col-span-5 h-full overflow-auto",
321
319
  !asDialog && "border-r " + defaultBorderMixin
@@ -349,7 +347,8 @@ export function CollectionPropertiesEditorForm({
349
347
  </div>}
350
348
 
351
349
  <div className="ml-1 mt-2 flex flex-row gap-2">
352
- <Tooltip title={"Get the code for this collection"}>
350
+ <Tooltip title={"Get the code for this collection"}
351
+ asChild={true}>
353
352
  <IconButton
354
353
  variant={"filled"}
355
354
  disabled={inferringProperties}
@@ -357,7 +356,8 @@ export function CollectionPropertiesEditorForm({
357
356
  <CodeIcon/>
358
357
  </IconButton>
359
358
  </Tooltip>
360
- {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}>
359
+ {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
360
+ asChild={true}>
361
361
  <IconButton
362
362
  variant={"filled"}
363
363
  disabled={inferringProperties}
@@ -365,7 +365,8 @@ export function CollectionPropertiesEditorForm({
365
365
  {inferringProperties ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
366
366
  </IconButton>
367
367
  </Tooltip>}
368
- <Tooltip title={"Add new property"}>
368
+ <Tooltip title={"Add new property"}
369
+ asChild={true}>
369
370
  <Button
370
371
  variant={"outlined"}
371
372
  onClick={() => setNewPropertyDialogOpen(true)}>
@@ -469,6 +470,7 @@ export function CollectionPropertiesEditorForm({
469
470
  getData={getData}
470
471
  propertyConfigs={propertyConfigs}
471
472
  collectionEditable={collectionEditable}
473
+ onCancel={closePropertyDialog}
472
474
  onOkClicked={asDialog
473
475
  ? closePropertyDialog
474
476
  : undefined
@@ -496,11 +498,12 @@ export function CollectionPropertiesEditorForm({
496
498
  collectionEditable={collectionEditable}
497
499
  existingPropertyKeys={values.propertiesOrder as string[]}/>
498
500
 
499
- <GetCodeDialog
500
- collection={values}
501
- open={codeDialogOpen}
502
- onOpenChange={setCodeDialogOpen}/>
503
-
501
+ <ErrorBoundary>
502
+ <GetCodeDialog
503
+ collection={values}
504
+ open={codeDialogOpen}
505
+ onOpenChange={setCodeDialogOpen}/>
506
+ </ErrorBoundary>
504
507
  </>
505
508
  );
506
509
  }
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect } from "react";
2
2
  import equal from "react-fast-compare"
3
3
 
4
- import { ArrayContainer, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
4
+ import { ArrayContainer, ArrayEntryParams, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
5
5
  import {
6
6
  AutoAwesomeIcon,
7
7
  Badge,
@@ -121,7 +121,10 @@ function EnumFormFields({
121
121
  const inferredValuesRef = React.useRef(new Set());
122
122
  const inferredValues = inferredValuesRef.current;
123
123
 
124
- const buildEntry = (index: number, internalId: number) => {
124
+ const buildEntry = ({
125
+ index,
126
+ internalId
127
+ }:ArrayEntryParams) => {
125
128
  const justAdded = lastInternalIdAdded === internalId;
126
129
  const entryError = errors?.enumValues && errors?.enumValues[index];
127
130
  return <EnumEntry index={index}
@@ -1,15 +1,22 @@
1
- import { EntityCollection, useSnackbarController } from "@firecms/core";
1
+ import { EntityCollection, isEmptyObject, useSnackbarController } from "@firecms/core";
2
2
  import { Button, ContentCopyIcon, Dialog, DialogActions, DialogContent, Typography, } from "@firecms/ui";
3
3
  import React from "react";
4
4
  import JSON5 from "json5";
5
5
  import { Highlight, themes } from "prism-react-renderer"
6
6
  import { camelCase } from "./utils/strings";
7
+ import { clone } from "@firecms/formex";
7
8
 
8
- export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange: (open: boolean) => void, collection: any, open: any }) {
9
+ export function GetCodeDialog({
10
+ collection,
11
+ onOpenChange,
12
+ open
13
+ }: { onOpenChange: (open: boolean) => void, collection: any, open: any }) {
9
14
 
10
15
  const snackbarController = useSnackbarController();
11
16
 
12
- const code = "import { EntityCollection } from \"firecms\";\n\nconst " + (collection.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode(collection), null, "\t");
17
+ const code = collection
18
+ ? "import { EntityCollection } from \"@firecms/core\";\n\nconst " + (collection?.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode({ ...collection }), null, "\t")
19
+ : "No collection selected";
13
20
  return <Dialog open={open}
14
21
  onOpenChange={onOpenChange}
15
22
  maxWidth={"4xl"}>
@@ -29,7 +36,13 @@ export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange
29
36
  code={code}
30
37
  language="typescript"
31
38
  >
32
- {({ className, style, tokens, getLineProps, getTokenProps }) => (
39
+ {({
40
+ className,
41
+ style,
42
+ tokens,
43
+ getLineProps,
44
+ getTokenProps
45
+ }) => (
33
46
  <pre style={style} className={"p-4 rounded text-sm"}>
34
47
  {tokens.map((line, i) => (
35
48
  <div key={i} {...getLineProps({ line })}>
@@ -66,24 +79,40 @@ export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange
66
79
 
67
80
  function collectionToCode(collection: EntityCollection): object {
68
81
 
69
- const propertyCleanup = (property: any) => {
70
-
71
- const updatedProperty = {
72
- ...property
73
- };
74
-
75
- delete updatedProperty.fromBuilder;
76
- delete updatedProperty.resolved;
77
- delete updatedProperty.propertiesOrder;
78
- delete updatedProperty.editable;
82
+ const propertyCleanup = (value: any): any => {
83
+ if (value === undefined || value === null) {
84
+ return value;
85
+ }
86
+ const valueCopy = clone(value);
87
+ if (typeof valueCopy === "function") {
88
+ return valueCopy;
89
+ }
90
+ if (Array.isArray(valueCopy)) {
91
+ return valueCopy.map((v: any) => propertyCleanup(v));
92
+ }
93
+ if (typeof valueCopy === "object") {
94
+ if (valueCopy === null)
95
+ return valueCopy;
96
+ Object.keys(valueCopy).forEach((key) => {
97
+ if (!isEmptyObject(valueCopy)) {
98
+ const childRes = propertyCleanup(valueCopy[key]);
99
+ if (childRes !== null && childRes !== undefined && childRes !== false && !isEmptyObject(childRes)) {
100
+ valueCopy[key] = childRes;
101
+ } else {
102
+ delete valueCopy[key];
103
+ }
104
+ }
105
+ });
106
+ delete valueCopy.fromBuilder;
107
+ delete valueCopy.resolved;
108
+ delete valueCopy.propertiesOrder;
109
+ delete valueCopy.propertyConfig;
110
+ delete valueCopy.resolvedProperties;
111
+ delete valueCopy.editable;
79
112
 
80
- if (updatedProperty.type === "map") {
81
- return {
82
- ...updatedProperty,
83
- properties: updatedProperty.properties.map(propertyCleanup)
84
- }
85
113
  }
86
- return updatedProperty;
114
+
115
+ return valueCopy;
87
116
  }
88
117
 
89
118
  return {
@@ -99,7 +128,9 @@ function collectionToCode(collection: EntityCollection): object {
99
128
  customId: collection.customId,
100
129
  initialFilter: collection.initialFilter,
101
130
  initialSort: collection.initialSort,
102
- properties: Object.entries(collection.properties ?? {})
131
+ properties: Object.entries({
132
+ ...(collection.properties ?? {})
133
+ })
103
134
  .map(([key, value]) => ({
104
135
  [key]: propertyCleanup(value)
105
136
  }))