@firecms/collection_editor 3.0.0-canary.12 → 3.0.0-canary.121

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 (54) 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 +4675 -3456
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +6680 -3
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/types/collection_editor_controller.d.ts +14 -2
  9. package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
  10. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  11. package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
  12. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +4 -3
  13. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
  14. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  15. package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
  16. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  17. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  18. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  19. package/dist/useCollectionEditorPlugin.d.ts +15 -9
  20. package/dist/utils/collections.d.ts +6 -0
  21. package/package.json +20 -34
  22. package/src/ConfigControllerProvider.tsx +75 -63
  23. package/src/index.ts +1 -0
  24. package/src/types/collection_editor_controller.tsx +14 -4
  25. package/src/ui/CollectionViewHeaderAction.tsx +9 -4
  26. package/src/ui/EditorCollectionAction.tsx +10 -63
  27. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  28. package/src/ui/HomePageEditorCollectionAction.tsx +16 -11
  29. package/src/ui/NewCollectionButton.tsx +12 -10
  30. package/src/ui/NewCollectionCard.tsx +3 -3
  31. package/src/ui/PropertyAddColumnComponent.tsx +9 -4
  32. package/src/ui/collection_editor/CollectionDetailsForm.tsx +67 -7
  33. package/src/ui/collection_editor/CollectionEditorDialog.tsx +56 -31
  34. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +6 -5
  35. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +33 -27
  36. package/src/ui/collection_editor/GetCodeDialog.tsx +15 -3
  37. package/src/ui/collection_editor/PropertyEditView.tsx +15 -7
  38. package/src/ui/collection_editor/PropertyFieldPreview.tsx +3 -6
  39. package/src/ui/collection_editor/PropertySelectItem.tsx +3 -3
  40. package/src/ui/collection_editor/PropertyTree.tsx +7 -5
  41. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +6 -4
  42. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +25 -9
  43. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +9 -7
  44. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +14 -8
  45. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +1 -1
  46. package/src/ui/collection_editor/properties/MapPropertyField.tsx +5 -5
  47. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  48. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  49. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +31 -16
  50. package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
  51. package/src/useCollectionEditorPlugin.tsx +37 -27
  52. package/src/utils/collections.ts +30 -0
  53. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  54. package/src/ui/RootCollectionSuggestions.tsx +0 -63
@@ -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,
@@ -293,7 +297,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
293
297
  });
294
298
  };
295
299
 
296
- const setNextMode = useCallback(() => {
300
+ const setNextMode = () => {
297
301
  if (currentView === "details") {
298
302
  if (importConfig.inUse) {
299
303
  setCurrentView("import_data_saving");
@@ -314,14 +318,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
314
318
  setCurrentView("details");
315
319
  }
316
320
 
317
- }, [currentView, importConfig.inUse, extraView]);
321
+ };
318
322
 
319
- const doCollectionInference = useCallback((collection: PersistedCollection<any>) => {
323
+ const doCollectionInference = (collection: PersistedCollection<any>) => {
320
324
  if (!collectionInference) return undefined;
321
325
  return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentCollectionIds ?? []);
322
- }, [collectionInference, parentCollectionIds]);
326
+ };
323
327
 
324
- const inferCollectionFromData = useCallback(async (newCollection: PersistedCollection<M>) => {
328
+ const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
325
329
 
326
330
  try {
327
331
  if (!doCollectionInference) {
@@ -365,10 +369,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
365
369
  });
366
370
  return newCollection;
367
371
  }
368
- }, [parentCollectionIds, doCollectionInference]);
372
+ };
369
373
 
370
374
  const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
371
- console.log("Submitting collection", newCollectionState);
375
+ console.debug("Submitting collection", newCollectionState);
372
376
  try {
373
377
 
374
378
  if (!isNewCollection) {
@@ -480,25 +484,36 @@ function CollectionEditorInternal<M extends Record<string, any>>({
480
484
 
481
485
  const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
482
486
  const resolvedPath = !pathError ? navigation.resolveAliasesFrom(updatedFullPath) : undefined;
483
- const getDataWithPath = resolvedPath && getData ? () => getData(resolvedPath, parentPaths ?? []) : undefined;
487
+ const getDataWithPath = resolvedPath && getData ? async () => {
488
+ const data = await getData(resolvedPath, parentPaths ?? []);
489
+ if (existingEntities) {
490
+ const existingData = existingEntities.map(e => e.values);
491
+ data.push(...existingData);
492
+ }
493
+ return data;
494
+ } : undefined;
484
495
 
485
496
  useEffect(() => {
486
497
  setFormDirty(dirty);
487
498
  }, [dirty]);
488
499
 
489
- function onImportDataSet(data: object[]) {
500
+ function onImportDataSet(data: object[], propertiesOrder?: string[]) {
490
501
  importConfig.setInUse(true);
491
502
  buildEntityPropertiesFromData(data, getInferenceType)
492
503
  .then((properties) => {
493
504
  const res = cleanPropertiesFromImport(properties);
494
505
 
495
- setFieldValue("properties", res.properties);
496
- setFieldValue("propertiesOrder", Object.keys(res.properties));
497
-
498
506
  importConfig.setIdColumn(res.idColumn);
499
507
  importConfig.setImportData(data);
500
508
  importConfig.setHeadersMapping(res.headersMapping);
509
+ const filteredHeadingsOrder = ((propertiesOrder ?? [])
510
+ .filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
511
+ importConfig.setHeadingsOrder(filteredHeadingsOrder);
501
512
  importConfig.setOriginProperties(res.properties);
513
+
514
+ const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
515
+ setFieldValue("properties", res.properties);
516
+ setFieldValue("propertiesOrder", mappedHeadings);
502
517
  });
503
518
  }
504
519
 
@@ -521,7 +536,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
521
536
 
522
537
  <>
523
538
  {!isNewCollection && <Tabs value={currentView}
524
- className={cn(defaultBorderMixin, "justify-end bg-gray-50 dark:bg-gray-950 border-b")}
539
+ className={cls(defaultBorderMixin, "justify-end bg-gray-50 dark:bg-gray-950 border-b")}
525
540
  onValueChange={(v) => setCurrentView(v as EditorView)}>
526
541
  <Tab value={"details"}>
527
542
  Details
@@ -536,7 +551,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
536
551
 
537
552
  <form noValidate
538
553
  onSubmit={formController.handleSubmit}
539
- className={cn(
554
+ className={cls(
540
555
  isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
541
556
  "flex-grow flex flex-col relative")}>
542
557
 
@@ -551,9 +566,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
551
566
  {currentView === "welcome" &&
552
567
  <CollectionEditorWelcomeView
553
568
  path={path}
554
- onContinue={(importData) => {
569
+ onContinue={(importData, propertiesOrder) => {
570
+ // console.log("Import data", importData, propertiesOrder)
555
571
  if (importData) {
556
- onImportDataSet(importData);
572
+ onImportDataSet(importData, propertiesOrder);
557
573
  setCurrentView("import_data_mapping");
558
574
  } else {
559
575
  setCurrentView("details");
@@ -733,7 +749,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
733
749
  }
734
750
 
735
751
  function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
736
- const { properties, ...rest } = collection;
752
+ const {
753
+ properties,
754
+ ...rest
755
+ } = collection;
737
756
  const propertiesResult: PropertiesOrBuilders<any> = {};
738
757
  if (properties) {
739
758
  Object.keys(properties).forEach((key) => {
@@ -741,7 +760,10 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
741
760
  });
742
761
  }
743
762
 
744
- return { ...rest, properties: propertiesResult };
763
+ return {
764
+ ...rest,
765
+ properties: propertiesResult
766
+ };
745
767
  }
746
768
 
747
769
  function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
@@ -761,7 +783,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
761
783
  Object.keys(internalProperty.properties).forEach((key) => {
762
784
  properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
763
785
  });
764
- internalProperty = { ...internalProperty, properties };
786
+ internalProperty = {
787
+ ...internalProperty,
788
+ properties
789
+ };
765
790
  }
766
791
 
767
792
  }
@@ -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,10 +185,11 @@ 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
194
  "text-gray-700 dark:text-slate-300",
194
195
  "hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
@@ -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);
@@ -273,7 +275,7 @@ export function CollectionPropertiesEditorForm({
273
275
 
274
276
  };
275
277
 
276
- const onPropertyErrorInternal = useCallback((id: string, namespace?: string, error?: Record<string, any>) => {
278
+ const onPropertyErrorInternal = (id: string, namespace?: string, error?: Record<string, any>) => {
277
279
  const propertyPath = id ? getFullId(id, namespace) : undefined;
278
280
  console.debug("onPropertyErrorInternal", {
279
281
  id,
@@ -286,7 +288,7 @@ export function CollectionPropertiesEditorForm({
286
288
  onPropertyError(id, namespace, hasError ? error : undefined);
287
289
  setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
288
290
  }
289
- }, [])
291
+ }
290
292
 
291
293
  const closePropertyDialog = () => {
292
294
  setSelectedPropertyIndex(undefined);
@@ -301,9 +303,9 @@ export function CollectionPropertiesEditorForm({
301
303
  ? values.propertiesOrder
302
304
  : Object.keys(values.properties)) as string[];
303
305
 
304
- const owner = useMemo(() => values.ownerId ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
306
+ const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
305
307
 
306
- const onPropertyClick = useCallback((propertyKey: string, namespace?: string) => {
308
+ const onPropertyClick = (propertyKey: string, namespace?: string) => {
307
309
  console.debug("CollectionEditor: onPropertyClick", {
308
310
  propertyKey,
309
311
  namespace
@@ -311,11 +313,11 @@ export function CollectionPropertiesEditorForm({
311
313
  setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
312
314
  setSelectedPropertyKey(propertyKey);
313
315
  setSelectedPropertyNamespace(namespace);
314
- }, [usedPropertiesOrder]);
316
+ };
315
317
 
316
318
  const body = (
317
319
  <div className={"grid grid-cols-12 gap-2 h-full bg-gray-50 dark:bg-gray-900"}>
318
- <div className={cn(
320
+ <div className={cls(
319
321
  "p-4 md:p-8 pb-20 md:pb-20",
320
322
  "col-span-12 lg:col-span-5 h-full overflow-auto",
321
323
  !asDialog && "border-r " + defaultBorderMixin
@@ -349,7 +351,8 @@ export function CollectionPropertiesEditorForm({
349
351
  </div>}
350
352
 
351
353
  <div className="ml-1 mt-2 flex flex-row gap-2">
352
- <Tooltip title={"Get the code for this collection"}>
354
+ <Tooltip title={"Get the code for this collection"}
355
+ asChild={true}>
353
356
  <IconButton
354
357
  variant={"filled"}
355
358
  disabled={inferringProperties}
@@ -357,7 +360,8 @@ export function CollectionPropertiesEditorForm({
357
360
  <CodeIcon/>
358
361
  </IconButton>
359
362
  </Tooltip>
360
- {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}>
363
+ {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
364
+ asChild={true}>
361
365
  <IconButton
362
366
  variant={"filled"}
363
367
  disabled={inferringProperties}
@@ -365,7 +369,8 @@ export function CollectionPropertiesEditorForm({
365
369
  {inferringProperties ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
366
370
  </IconButton>
367
371
  </Tooltip>}
368
- <Tooltip title={"Add new property"}>
372
+ <Tooltip title={"Add new property"}
373
+ asChild={true}>
369
374
  <Button
370
375
  variant={"outlined"}
371
376
  onClick={() => setNewPropertyDialogOpen(true)}>
@@ -496,11 +501,12 @@ export function CollectionPropertiesEditorForm({
496
501
  collectionEditable={collectionEditable}
497
502
  existingPropertyKeys={values.propertiesOrder as string[]}/>
498
503
 
499
- <GetCodeDialog
500
- collection={values}
501
- open={codeDialogOpen}
502
- onOpenChange={setCodeDialogOpen}/>
503
-
504
+ <ErrorBoundary>
505
+ <GetCodeDialog
506
+ collection={values}
507
+ open={codeDialogOpen}
508
+ onOpenChange={setCodeDialogOpen}/>
509
+ </ErrorBoundary>
504
510
  </>
505
511
  );
506
512
  }
@@ -5,11 +5,17 @@ import JSON5 from "json5";
5
5
  import { Highlight, themes } from "prism-react-renderer"
6
6
  import { camelCase } from "./utils/strings";
7
7
 
8
- export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange: (open: boolean) => void, collection: any, open: any }) {
8
+ export function GetCodeDialog({
9
+ collection,
10
+ onOpenChange,
11
+ open
12
+ }: { onOpenChange: (open: boolean) => void, collection: any, open: any }) {
9
13
 
10
14
  const snackbarController = useSnackbarController();
11
15
 
12
- const code = "import { EntityCollection } from \"firecms\";\n\nconst " + (collection.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode(collection), null, "\t");
16
+ const code = collection
17
+ ? "import { EntityCollection } from \"@firecms/core\";\n\nconst " + (collection?.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode(collection), null, "\t")
18
+ : "No collection selected";
13
19
  return <Dialog open={open}
14
20
  onOpenChange={onOpenChange}
15
21
  maxWidth={"4xl"}>
@@ -29,7 +35,13 @@ export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange
29
35
  code={code}
30
36
  language="typescript"
31
37
  >
32
- {({ className, style, tokens, getLineProps, getTokenProps }) => (
38
+ {({
39
+ className,
40
+ style,
41
+ tokens,
42
+ getLineProps,
43
+ getTokenProps
44
+ }) => (
33
45
  <pre style={style} className={"p-4 rounded text-sm"}>
34
46
  {tokens.map((line, i) => (
35
47
  <div key={i} {...getLineProps({ line })}>
@@ -5,7 +5,6 @@ import { Formex, FormexController, getIn, useCreateFormex } from "@firecms/forme
5
5
  import {
6
6
  DEFAULT_FIELD_CONFIGS,
7
7
  DeleteConfirmationDialog,
8
- PropertyConfigId,
9
8
  getFieldConfig,
10
9
  getFieldId,
11
10
  isPropertyBuilder,
@@ -14,10 +13,11 @@ import {
14
13
  Property,
15
14
  PropertyConfig,
16
15
  PropertyConfigBadge,
16
+ PropertyConfigId,
17
17
  } from "@firecms/core";
18
18
  import {
19
19
  Button,
20
- cn,
20
+ cls,
21
21
  DeleteIcon,
22
22
  Dialog,
23
23
  DialogActions,
@@ -45,6 +45,7 @@ import { updatePropertyFromWidget } from "./utils/update_property_for_widget";
45
45
  import { PropertySelectItem } from "./PropertySelectItem";
46
46
  import { UrlPropertyField } from "./properties/UrlPropertyField";
47
47
  import { supportedFields } from "./utils/supported_fields";
48
+ import { MarkdownPropertyField } from "./properties/MarkdownPropertyField";
48
49
 
49
50
  export type PropertyWithId = Property & {
50
51
  id?: string
@@ -148,7 +149,10 @@ export const PropertyForm = React.memo(
148
149
  } = newPropertyWithId;
149
150
  doOnPropertyChanged({
150
151
  id,
151
- property: { ...property, editable: property.editable ?? true }
152
+ property: {
153
+ ...property,
154
+ editable: property.editable ?? true
155
+ }
152
156
  });
153
157
  if (!existingProperty)
154
158
  controller.resetForm({ values: initialValue });
@@ -228,6 +232,7 @@ export const PropertyForm = React.memo(
228
232
  a.includeIdAndName === b.includeIdAndName &&
229
233
  a.autoOpenTypeSelect === b.autoOpenTypeSelect &&
230
234
  a.autoUpdateId === b.autoUpdateId &&
235
+ a.existingPropertyKeys === b.existingPropertyKeys &&
231
236
  a.existingProperty === b.existingProperty
232
237
  );
233
238
 
@@ -367,13 +372,13 @@ function PropertyEditFormFields({
367
372
  }
368
373
  }
369
374
  }
370
- }, [deferredValues, includeIdAndTitle, onPropertyChanged, propertyNamespace]);
375
+ }, [deferredValues, includeIdAndTitle, propertyNamespace]);
371
376
 
372
377
  useEffect(() => {
373
378
  if (values?.id && onError) {
374
379
  onError(values?.id, propertyNamespace, errors);
375
380
  }
376
- }, [errors, onError, propertyNamespace, values?.id]);
381
+ }, [errors, propertyNamespace, values?.id]);
377
382
 
378
383
  const onWidgetSelectChanged = (newSelectedWidgetId: PropertyConfigId) => {
379
384
  setSelectedFieldConfigId(newSelectedWidgetId);
@@ -387,7 +392,6 @@ function PropertyEditFormFields({
387
392
  let childComponent;
388
393
  if (selectedFieldConfigId === "text_field" ||
389
394
  selectedFieldConfigId === "multiline" ||
390
- selectedFieldConfigId === "markdown" ||
391
395
  selectedFieldConfigId === "email") {
392
396
  childComponent =
393
397
  <StringPropertyField widgetId={selectedFieldConfigId}
@@ -397,6 +401,10 @@ function PropertyEditFormFields({
397
401
  childComponent =
398
402
  <UrlPropertyField disabled={disabled}
399
403
  showErrors={showErrors}/>;
404
+ } else if (selectedFieldConfigId === "markdown") {
405
+ childComponent =
406
+ <MarkdownPropertyField disabled={disabled}
407
+ showErrors={showErrors}/>;
400
408
  } else if (selectedFieldConfigId === "select" ||
401
409
  selectedFieldConfigId === "number_select") {
402
410
  childComponent = <EnumPropertyField
@@ -508,7 +516,7 @@ function PropertyEditFormFields({
508
516
  e.preventDefault();
509
517
  }
510
518
  }}
511
- className={cn(
519
+ className={cls(
512
520
  "flex items-center",
513
521
  optionDisabled ? "w-full pointer-events-none opacity-50" : "")}>
514
522
  <div className={"mr-8"}>
@@ -11,7 +11,7 @@ import {
11
11
  cardClickableMixin,
12
12
  cardMixin,
13
13
  cardSelectedMixin,
14
- cn,
14
+ cls,
15
15
  FunctionsIcon,
16
16
  Paper,
17
17
  RemoveCircleIcon,
@@ -45,9 +45,6 @@ export function PropertyFieldPreview({
45
45
  ? "border-red-500 dark:border-red-500 border-opacity-100 dark:border-opacity-100 ring-0 dark:ring-0"
46
46
  : (selected ? "border-primary" : "border-transparent");
47
47
 
48
- if(hasError)
49
- console.log("PropertyFieldPreview", property)
50
-
51
48
  return <ErrorBoundary>
52
49
  <div
53
50
  onClick={onClick}
@@ -56,7 +53,7 @@ export function PropertyFieldPreview({
56
53
  <PropertyConfigBadge propertyConfig={propertyConfig}/>
57
54
  </div>
58
55
  <Paper
59
- className={cn(
56
+ className={cls(
60
57
  "border",
61
58
  "pl-2 w-full flex flex-row gap-4 items-center",
62
59
  cardMixin,
@@ -139,7 +136,7 @@ export function NonEditablePropertyPreview({
139
136
  <RemoveCircleIcon color={"disabled"} size={"small"} className={"absolute -right-2 -top-2"}/>
140
137
  </div>
141
138
  <Paper
142
- className={cn(
139
+ className={cls(
143
140
  "pl-2 w-full flex flex-row gap-4 items-center",
144
141
  cardMixin,
145
142
  onClick ? cardClickableMixin : "",
@@ -1,5 +1,5 @@
1
1
  import { PropertyConfigBadge, PropertyConfig } from "@firecms/core";
2
- import { cn, SelectItem, Typography } from "@firecms/ui";
2
+ import { cls, SelectItem, Typography } from "@firecms/ui";
3
3
 
4
4
  export interface PropertySelectItemProps {
5
5
  value: string;
@@ -13,8 +13,8 @@ export function PropertySelectItem({ value, optionDisabled, propertyConfig, exis
13
13
  disabled={optionDisabled}
14
14
  className={"flex flex-row items-center"}>
15
15
  <div
16
- className={cn(
17
- "flex flex-row items-center text-base min-h-[52px]",
16
+ className={cls(
17
+ "flex flex-row items-center text-base min-h-[48px]",
18
18
  optionDisabled ? "w-full" : "")}>
19
19
  <div className={"mr-8"}>
20
20
  <PropertyConfigBadge propertyConfig={propertyConfig}/>