@firecms/core 3.0.0-beta.12 → 3.0.0-beta.13

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 (84) hide show
  1. package/dist/app/Scaffold.d.ts +4 -0
  2. package/dist/components/ArrayContainer.d.ts +12 -5
  3. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  4. package/dist/components/EntityJsonPreview.d.ts +3 -0
  5. package/dist/core/DefaultAppBar.d.ts +8 -2
  6. package/dist/core/EntityEditView.d.ts +8 -1
  7. package/dist/core/FireCMS.d.ts +2 -2
  8. package/dist/form/EntityForm.d.ts +5 -1
  9. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  10. package/dist/hooks/data/save.d.ts +1 -1
  11. package/dist/index.es.js +8630 -8578
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.umd.js +8663 -8612
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/internal/useBuildDataSource.d.ts +3 -2
  16. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  17. package/dist/types/collections.d.ts +6 -0
  18. package/dist/types/fields.d.ts +4 -1
  19. package/dist/types/firecms.d.ts +1 -1
  20. package/dist/types/navigation.d.ts +4 -0
  21. package/dist/types/plugins.d.ts +1 -0
  22. package/dist/types/properties.d.ts +22 -1
  23. package/dist/util/createFormexStub.d.ts +2 -0
  24. package/dist/util/navigation_utils.d.ts +2 -1
  25. package/dist/util/objects.d.ts +1 -1
  26. package/dist/util/property_utils.d.ts +2 -2
  27. package/dist/util/references.d.ts +2 -2
  28. package/dist/util/resolutions.d.ts +11 -6
  29. package/package.json +6 -5
  30. package/src/app/Scaffold.tsx +13 -3
  31. package/src/components/ArrayContainer.tsx +40 -23
  32. package/src/components/DeleteEntityDialog.tsx +4 -2
  33. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +8 -6
  34. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -2
  35. package/src/components/EntityCollectionView/EntityCollectionView.tsx +22 -6
  36. package/src/components/EntityJsonPreview.tsx +66 -0
  37. package/src/components/EntityPreview.tsx +10 -3
  38. package/src/components/EntityView.tsx +4 -1
  39. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -1
  40. package/src/components/VirtualTable/VirtualTable.tsx +29 -1
  41. package/src/components/common/default_entity_actions.tsx +15 -4
  42. package/src/core/DefaultAppBar.tsx +17 -5
  43. package/src/core/EntityEditView.tsx +111 -35
  44. package/src/core/FireCMS.tsx +13 -12
  45. package/src/form/EntityForm.tsx +31 -23
  46. package/src/form/PropertyFieldBinding.tsx +8 -5
  47. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -1
  48. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -0
  49. package/src/form/field_bindings/BlockFieldBinding.tsx +1 -1
  50. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  51. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +6 -2
  52. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -2
  53. package/src/form/field_bindings/RepeatFieldBinding.tsx +8 -2
  54. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
  55. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +5 -2
  56. package/src/hooks/data/save.ts +24 -29
  57. package/src/hooks/useBuildNavigationController.tsx +12 -1
  58. package/src/hooks/useFireCMSContext.tsx +0 -30
  59. package/src/internal/useBuildDataSource.ts +9 -5
  60. package/src/internal/useBuildSideEntityController.tsx +26 -20
  61. package/src/preview/PropertyPreview.tsx +4 -2
  62. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +4 -3
  63. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
  64. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
  65. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
  66. package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
  67. package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
  68. package/src/routes/FireCMSRoute.tsx +15 -4
  69. package/src/types/collections.ts +7 -0
  70. package/src/types/fields.tsx +5 -1
  71. package/src/types/firecms.tsx +1 -1
  72. package/src/types/navigation.ts +5 -0
  73. package/src/types/plugins.tsx +1 -0
  74. package/src/types/properties.ts +27 -1
  75. package/src/types/side_entity_controller.tsx +5 -0
  76. package/src/util/createFormexStub.tsx +62 -0
  77. package/src/util/join_collections.ts +3 -1
  78. package/src/util/navigation_from_path.ts +5 -1
  79. package/src/util/navigation_utils.ts +64 -15
  80. package/src/util/objects.ts +5 -1
  81. package/src/util/property_utils.tsx +7 -3
  82. package/src/util/references.ts +8 -6
  83. package/src/util/resolutions.ts +17 -9
  84. package/src/util/useStorageUploadController.tsx +21 -2
@@ -3,7 +3,7 @@ import { ResolvedStringProperty } from "../../types";
3
3
 
4
4
  import { resolveArrayProperty } from "../../util";
5
5
  import { PropertyPreviewProps, StringPropertyPreview } from "../../preview";
6
- import { useCustomizationController } from "../../hooks";
6
+ import { useAuthController, useCustomizationController } from "../../hooks";
7
7
  import { ErrorBoundary } from "../../components";
8
8
 
9
9
  /**
@@ -16,12 +16,13 @@ export function ArrayOfStringsPreview({
16
16
  // entity,
17
17
  size
18
18
  }: PropertyPreviewProps<string[]>) {
19
-
19
+ const authController = useAuthController();
20
20
  const customizationController = useCustomizationController();
21
21
  const property = resolveArrayProperty({
22
22
  propertyKey,
23
23
  property: inputProperty,
24
- propertyConfigs: customizationController.propertyConfigs
24
+ propertyConfigs: customizationController.propertyConfigs,
25
+ authController
25
26
  });
26
27
 
27
28
  if (Array.isArray(property.of)) {
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { resolveArrayProperty } from "../../util";
3
3
  import { ResolvedProperty } from "../../types";
4
- import { useCustomizationController } from "../../hooks";
4
+ import { useAuthController, useCustomizationController } from "../../hooks";
5
5
  import { PreviewSize, PropertyPreviewProps } from "../PropertyPreviewProps";
6
6
  import { PropertyPreview } from "../PropertyPreview";
7
7
  import { cls, defaultBorderMixin } from "@firecms/ui";
@@ -19,11 +19,13 @@ export function ArrayOneOfPreview({
19
19
  // entity
20
20
  }: PropertyPreviewProps<any[]>) {
21
21
 
22
+ const authController = useAuthController();
22
23
  const customizationController = useCustomizationController();
23
24
  const property = resolveArrayProperty({
24
25
  propertyKey,
25
26
  property: inputProperty,
26
- propertyConfigs: customizationController.propertyConfigs
27
+ propertyConfigs: customizationController.propertyConfigs,
28
+ authController
27
29
  });
28
30
 
29
31
  if (property?.dataType !== "array")
@@ -2,7 +2,7 @@ import React from "react";
2
2
 
3
3
  import { resolveArrayProperty } from "../../util";
4
4
  import { ResolvedProperty } from "../../types";
5
- import { useCustomizationController } from "../../hooks";
5
+ import { useAuthController, useCustomizationController } from "../../hooks";
6
6
  import { PreviewSize, PropertyPreviewProps } from "../PropertyPreviewProps";
7
7
  import { PropertyPreview } from "../PropertyPreview";
8
8
  import { cls, defaultBorderMixin } from "@firecms/ui";
@@ -19,11 +19,13 @@ export function ArrayPropertyPreview({
19
19
  size
20
20
  }: PropertyPreviewProps<any[]>) {
21
21
 
22
+ const authController = useAuthController();
22
23
  const customizationController = useCustomizationController();
23
24
  const property = resolveArrayProperty({
24
25
  propertyKey,
25
26
  property: inputProperty,
26
- propertyConfigs: customizationController.propertyConfigs
27
+ propertyConfigs: customizationController.propertyConfigs,
28
+ authController
27
29
  });
28
30
 
29
31
  if (!property.of) {
@@ -13,6 +13,7 @@ import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
13
13
  import { toArray } from "../util/arrays";
14
14
  import { EntityCollectionView, NotFoundPage } from "../components";
15
15
  import { UnsavedChangesDialog } from "../components/UnsavedChangesDialog";
16
+ import { EntityCollection } from "../types";
16
17
 
17
18
  export function FireCMSRoute() {
18
19
 
@@ -68,7 +69,10 @@ export function FireCMSRoute() {
68
69
  }
69
70
 
70
71
  if (navigationEntries.length === 1 && navigationEntries[0].type === "collection") {
71
- const collection = navigation.getCollection(navigationEntries[0].path);
72
+ let collection: EntityCollection<any> | undefined;
73
+ collection = navigation.getCollectionById(navigationEntries[0].id);
74
+ if (!collection)
75
+ collection = navigation.getCollection(navigationEntries[0].path);
72
76
  if (!collection)
73
77
  return null;
74
78
  return <EntityCollectionView
@@ -84,11 +88,16 @@ export function FireCMSRoute() {
84
88
  if (isSidePanel) {
85
89
  const lastCollectionEntry = navigationEntries.findLast((entry) => entry.type === "collection");
86
90
  if (lastCollectionEntry) {
87
- const collection = navigation.getCollection(lastCollectionEntry.path);
91
+ let collection: EntityCollection<any> | undefined;
92
+ const firstEntry = navigationEntries[0] as NavigationViewCollectionInternal<any>;
93
+ collection = navigation.getCollectionById(firstEntry.id);
94
+ if (!collection)
95
+ collection = navigation.getCollection(firstEntry.path);
88
96
  if (!collection)
89
97
  return null;
90
98
  return <EntityCollectionView
91
99
  key={`collection_view_${collection.id ?? collection.path}`}
100
+ fullIdPath={collection.id}
92
101
  isSubCollection={false}
93
102
  parentCollectionIds={[]}
94
103
  fullPath={collection.id}
@@ -175,7 +184,7 @@ function EntityFullScreenRoute({
175
184
  return blocked.current;
176
185
  });
177
186
  } catch (e) {
178
- console.warn("Blocker not available, navigation will not be blocked");
187
+ // console.warn("Blocker not available, navigation will not be blocked");
179
188
  }
180
189
 
181
190
  const lastCollectionEntry = navigationEntries.findLast((entry) => entry.type === "collection");
@@ -189,12 +198,14 @@ function EntityFullScreenRoute({
189
198
  }
190
199
 
191
200
  const collection = isNew ? lastCollectionEntry!.collection : lastEntityEntry!.parentCollection;
192
- const collectionPath = navigation.resolveIdsFrom(isNew ? lastCollectionEntry!.path : lastEntityEntry!.path);
201
+ const fullIdPath = isNew ? lastCollectionEntry!.path : lastEntityEntry!.path;
202
+ const collectionPath = navigation.resolveIdsFrom(fullIdPath);
193
203
 
194
204
  return <>
195
205
  <EntityEditView
196
206
  key={collection.id + "_" + (isNew ? "new" : (isCopy ? entityId + "_copy" : entityId))}
197
207
  entityId={isNew ? undefined : entityId}
208
+ fullIdPath={fullIdPath}
198
209
  collection={collection}
199
210
  layout={"full_screen"}
200
211
  path={collectionPath}
@@ -215,6 +215,7 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
215
215
  * Force a filter in this view. If applied, the rest of the filters will
216
216
  * be disabled. Filters applied with this prop cannot be changed.
217
217
  * e.g. `forceFilter: { age: [">=", 18] }`
218
+ * e.g. `forceFilter: { related_user: ["==", new EntityReference("sdc43dsw2", "users")] }`
218
219
  */
219
220
  forceFilter?: FilterValues<Extract<keyof M, string>>;
220
221
 
@@ -222,6 +223,7 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
222
223
  * Initial filters applied to the collection this collection is related to.
223
224
  * Defaults to none. Filters applied with this prop can be changed.
224
225
  * e.g. `initialFilter: { age: [">=", 18] }`
226
+ * e.g. `initialFilter: { related_user: ["==", new EntityReference("sdc43dsw2", "users")] }`
225
227
  */
226
228
  initialFilter?: FilterValues<Extract<keyof M, string>>; // setting FilterValues<M> can break defining collections by code
227
229
 
@@ -330,6 +332,11 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
330
332
  * Defaults to false.
331
333
  */
332
334
  alwaysApplyDefaultValues?: boolean;
335
+
336
+ /**
337
+ * If set to true, a tab including the JSON representation of the entity will be included.
338
+ */
339
+ includeJsonView?: boolean;
333
340
  }
334
341
 
335
342
  /**
@@ -171,9 +171,13 @@ export interface FormContext<M extends Record<string, any> = any> {
171
171
  openEntityMode: "side_panel" | "full_screen";
172
172
 
173
173
  /**
174
- * This is the underlying formex controller that powers the form
174
+ * This is the underlying formex controller that powers the form.
175
+ * If you are in a red only mode, the formex controller is there, but you can't
176
+ * operate with it
175
177
  */
176
178
  formex: FormexController<M>;
179
+
180
+ disabled: boolean;
177
181
  }
178
182
 
179
183
  /**
@@ -47,7 +47,7 @@ export type CMSViewsBuilder = (params: {
47
47
  /**
48
48
  * @group Models
49
49
  */
50
- export type FireCMSProps<USER extends User, EC extends EntityCollection> = {
50
+ export type FireCMSProps<USER extends User> = {
51
51
 
52
52
  /**
53
53
  * Use this function to return the components you want to render under
@@ -59,6 +59,11 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
59
59
  */
60
60
  getCollection: (pathOrId: string, includeUserOverride?: boolean) => EC | undefined;
61
61
 
62
+ /**
63
+ * Get the top level collection configuration for a given id
64
+ */
65
+ getCollectionById: (id: string) => EC | undefined;
66
+
62
67
  /**
63
68
  * Get the collection configuration from its parent ids.
64
69
  */
@@ -192,6 +192,7 @@ export interface PluginFormActionProps<USER extends User = User, EC extends Enti
192
192
  path: string;
193
193
  status: EntityStatus;
194
194
  collection: EC;
195
+ disabled: boolean;
195
196
  formContext?: FormContext<any>;
196
197
  context: FireCMSContext<USER>;
197
198
  currentEntityId?: string;
@@ -5,6 +5,7 @@ import { EntityReference, EntityValues, GeoPoint, Vector } from "./entities";
5
5
  import { ResolvedArrayProperty, ResolvedStringProperty } from "./resolved_entities";
6
6
  import { FilterValues } from "./collections";
7
7
  import { ChipColorKey, ChipColorScheme } from "@firecms/ui";
8
+ import { AuthController } from "./auth";
8
9
 
9
10
  /**
10
11
  * @group Entity properties
@@ -270,6 +271,11 @@ export type PropertyBuilderProps<M extends Record<string, any> = any> =
270
271
  * Entity ID
271
272
  */
272
273
  entityId?: string;
274
+
275
+ /**
276
+ * Controller to manage authentication
277
+ */
278
+ authController: AuthController;
273
279
  };
274
280
 
275
281
  /**
@@ -285,7 +291,8 @@ export type PropertyBuilder<T extends CMSType = any, M extends Record<string, an
285
291
  propertyValue,
286
292
  index,
287
293
  path,
288
- entityId
294
+ entityId,
295
+ authController
289
296
  }: PropertyBuilderProps<M>) => Property<T> | null;
290
297
 
291
298
  /**
@@ -481,6 +488,18 @@ export interface ArrayProperty<T extends ArrayT[] = any[], ArrayT extends CMSTyp
481
488
  */
482
489
  minimalistView?: boolean;
483
490
 
491
+ /**
492
+ * Can the elements in this array be reordered. Defaults to `true`.
493
+ * This prop has no effect if `disabled` is set to true.
494
+ */
495
+ sortable?: boolean;
496
+
497
+ /**
498
+ * Can the elements in this array be added. Defaults to `true`
499
+ * This prop has no effect if `disabled` is set to true.
500
+ */
501
+ canAddElements?: boolean;
502
+
484
503
  }
485
504
 
486
505
  /**
@@ -796,6 +815,13 @@ export type StorageConfig = {
796
815
  */
797
816
  maxSize?: number,
798
817
 
818
+ /**
819
+ * Use this callback to process the file before uploading it to the storage.
820
+ * If nothing is returned, the file is uploaded as it is.
821
+ * @param file
822
+ */
823
+ processFile?: (file: File) => Promise<File> | undefined;
824
+
799
825
  /**
800
826
  * Postprocess the saved value (storage path or URL)
801
827
  * after it has been resolved.
@@ -20,6 +20,11 @@ export interface EntitySidePanelProps<M extends Record<string, any> = any> {
20
20
  */
21
21
  entityId?: string;
22
22
 
23
+ // /**
24
+ // * Navigation path with ids of the entity
25
+ // */
26
+ // fullIdPath: string;
27
+
23
28
  /**
24
29
  * Set this flag to true if you want to make a copy of an existing entity
25
30
  */
@@ -0,0 +1,62 @@
1
+ import { FormexController } from "@firecms/formex";
2
+
3
+ export function createFormexStub<T extends object>(values: T): FormexController<T> {
4
+ const errorMessage = "You are in a read-only context. You cannot modify the formex controller.";
5
+
6
+ return {
7
+ values,
8
+ initialValues: values,
9
+ touched: {} as Record<string, boolean>,
10
+ dirty: false,
11
+ errors: {} as Record<string, string>,
12
+ submitCount: 0,
13
+ isSubmitting: false,
14
+ isValidating: false,
15
+ version: 0,
16
+ canUndo: false,
17
+ canRedo: false,
18
+
19
+ setValues: () => {
20
+ throw new Error(errorMessage);
21
+ },
22
+ setFieldValue: () => {
23
+ throw new Error(errorMessage);
24
+ },
25
+ setFieldTouched: () => {
26
+ throw new Error(errorMessage);
27
+ },
28
+ setDirty: () => {
29
+ throw new Error(errorMessage);
30
+ },
31
+ setSubmitCount: () => {
32
+ throw new Error(errorMessage);
33
+ },
34
+ setFieldError: () => {
35
+ throw new Error(errorMessage);
36
+ },
37
+ handleChange: () => {
38
+ throw new Error(errorMessage);
39
+ },
40
+ handleBlur: () => {
41
+ throw new Error(errorMessage);
42
+ },
43
+ handleSubmit: () => {
44
+ throw new Error(errorMessage);
45
+ },
46
+ validate: () => {
47
+ throw new Error(errorMessage);
48
+ },
49
+ resetForm: () => {
50
+ throw new Error(errorMessage);
51
+ },
52
+ setSubmitting: () => {
53
+ throw new Error(errorMessage);
54
+ },
55
+ undo: () => {
56
+ throw new Error(errorMessage);
57
+ },
58
+ redo: () => {
59
+ throw new Error(errorMessage);
60
+ }
61
+ };
62
+ }
@@ -78,6 +78,7 @@ export function mergeCollection(target: EntityCollection,
78
78
  modifyCollection?: (props: ModifyCollectionProps) => EntityCollection | void
79
79
  ): EntityCollection {
80
80
 
81
+
81
82
  const subcollectionsMerged = joinCollectionLists(
82
83
  target?.subcollections ?? [],
83
84
  source?.subcollections ?? [],
@@ -94,7 +95,7 @@ export function mergeCollection(target: EntityCollection,
94
95
  propertiesMerged[key] = source.properties[key] as PropertyOrBuilder;
95
96
  });
96
97
 
97
- const mergedCollection = mergeDeep(target, source);
98
+ const mergedCollection = mergeDeep(target, source, true);
98
99
  const targetPropertiesOrder = getCollectionKeys(target);
99
100
  const sourcePropertiesOrder = getCollectionKeys(source);
100
101
  const mergedPropertiesOrder = [...new Set([...sourcePropertiesOrder, ...targetPropertiesOrder])];
@@ -118,6 +119,7 @@ export function mergeCollection(target: EntityCollection,
118
119
 
119
120
  // @ts-ignore
120
121
  resultCollection["merged"] = true;
122
+
121
123
  return resultCollection
122
124
  }
123
125
 
@@ -51,7 +51,11 @@ export function getNavigationEntriesFromPath(props: {
51
51
  for (let i = 0; i < subpathCombinations.length; i++) {
52
52
  const subpathCombination = subpathCombinations[i];
53
53
 
54
- const collection: EntityCollection<any> | undefined = collections && collections.find((entry) => entry.id === subpathCombination || entry.path === subpathCombination);
54
+ let collection: EntityCollection<any> | undefined;
55
+ collection = collections && collections.find((entry) => entry.id === subpathCombination);
56
+ if (!collection) {
57
+ collection = collections && collections.find((entry) => entry.path === subpathCombination);
58
+ }
55
59
 
56
60
  if (collection) {
57
61
  const pathOrAlias = collection.id ?? collection.path;
@@ -32,29 +32,75 @@ export function getLastSegment(path: string) {
32
32
  }
33
33
 
34
34
  export function resolveCollectionPathIds(path: string, allCollections: EntityCollection[]): string {
35
-
36
35
  const cleanPath = removeInitialAndTrailingSlashes(path);
37
36
  const subpaths = cleanPath.split("/");
37
+
38
38
  if (subpaths.length % 2 === 0) {
39
- throw Error(`resolveCollectionPathAliases: Collection paths must have an odd number of segments: ${path}`);
39
+ throw Error(`resolveCollectionPathIds: Collection paths must have an odd number of segments: ${path}`);
40
40
  }
41
41
 
42
- const aliasedCollection = allCollections.find((col) => col.id === subpaths[0]);
43
- let resolvedAliased;
44
- if (aliasedCollection) {
45
- resolvedAliased = aliasedCollection.path;
42
+ // Check if the path exactly matches a collection path
43
+ const exactMatch = allCollections.find(col => col.path === cleanPath);
44
+ if (exactMatch) {
45
+ return exactMatch.path;
46
46
  }
47
47
 
48
- if (subpaths.length > 1) {
49
- const segmentCollection = getCollectionByPathOrId(resolvedAliased ?? subpaths[0], allCollections);
50
- if (!segmentCollection?.subcollections) {
48
+ if (subpaths.length === 1) {
49
+ // Find collection by ID and return its path
50
+ const aliasedCollection = allCollections.find((col) => col.id === subpaths[0]);
51
+ return aliasedCollection?.path ?? subpaths[0];
52
+ }
53
+
54
+ // Try to match a multi-segment collection path
55
+ let matchingCollection: EntityCollection | undefined;
56
+ let entityIndex = 1;
57
+
58
+ // Check if the path starts with a multi-segment collection path
59
+ for (const collection of allCollections) {
60
+ const pathSegments = collection.path.split("/");
61
+ if (pathSegments.length > 1 &&
62
+ subpaths.slice(0, pathSegments.length).join("/") === collection.path) {
63
+ matchingCollection = collection;
64
+ entityIndex = pathSegments.length;
65
+ break;
66
+ }
67
+ }
68
+
69
+ // If no multi-segment match, fall back to single segment matching
70
+ if (!matchingCollection) {
71
+ const matchingCollections = allCollections.filter(col =>
72
+ col.id === subpaths[0] || col.path === subpaths[0]
73
+ );
74
+
75
+ if (!matchingCollections.length) {
51
76
  return cleanPath;
52
77
  }
53
- const restOfThePath = cleanPath.split("/").slice(2).join("/");
54
- return (resolvedAliased ?? subpaths[0]) + "/" + subpaths[1] + "/" + resolveCollectionPathIds(restOfThePath, segmentCollection.subcollections);
55
- } else {
56
- return resolvedAliased ?? cleanPath;
78
+
79
+ matchingCollection = matchingCollections[0];
57
80
  }
81
+
82
+ const entityId = subpaths[entityIndex];
83
+ const remainingPath = subpaths.slice(entityIndex + 1);
84
+
85
+ // If we have a subcollection ID, try to resolve it
86
+ if (remainingPath.length > 0) {
87
+ const subcollectionId = remainingPath[0];
88
+ const subcollection = matchingCollection.subcollections?.find(
89
+ subcol => subcol.id === subcollectionId
90
+ );
91
+
92
+ if (subcollection) {
93
+ return `${matchingCollection.path}/${entityId}/${subcollection.path}`;
94
+ }
95
+ }
96
+
97
+ // If there are no remaining path segments, just return the collection path with entity ID
98
+ if (remainingPath.length === 0) {
99
+ return `${matchingCollection.path}/${entityId}`;
100
+ }
101
+
102
+ // Default case - couldn't match subcollection
103
+ return `${matchingCollection.path}/${entityId}/${remainingPath.join("/")}`;
58
104
  }
59
105
 
60
106
  /**
@@ -116,6 +162,7 @@ export function navigateToEntity({
116
162
  entityId,
117
163
  copy,
118
164
  path,
165
+ fullIdPath,
119
166
  selectedTab,
120
167
  sideEntityController,
121
168
  onClose,
@@ -129,6 +176,7 @@ export function navigateToEntity({
129
176
  selectedTab?: string;
130
177
  copy?: boolean;
131
178
  path: string;
179
+ fullIdPath?: string;
132
180
  sideEntityController: SideEntityController;
133
181
  onClose?: () => void;
134
182
  navigation: NavigationController
@@ -138,7 +186,8 @@ export function navigateToEntity({
138
186
 
139
187
  sideEntityController.open({
140
188
  entityId,
141
- path,
189
+ path: fullIdPath ?? path,
190
+ // fullIdPath,
142
191
  copy,
143
192
  selectedTab,
144
193
  collection,
@@ -147,7 +196,7 @@ export function navigateToEntity({
147
196
  });
148
197
 
149
198
  } else {
150
- let to = navigation.buildUrlCollectionPath(entityId ? `${path}/${entityId}` : path);
199
+ let to = navigation.buildUrlCollectionPath(entityId ? `${fullIdPath ?? path}/${entityId}` : fullIdPath ?? path);
151
200
  if (entityId && selectedTab) {
152
201
  to += `/${selectedTab}`;
153
202
  }
@@ -12,12 +12,16 @@ export function isObject(item: any) {
12
12
  return item && typeof item === "object" && !Array.isArray(item);
13
13
  }
14
14
 
15
- export function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U): T & U {
15
+ export function mergeDeep<T extends Record<any, any>, U extends Record<any, any>>(target: T, source: U, ignoreUndefined: boolean = false): T & U {
16
16
  const targetIsObject = isObject(target);
17
17
  const output = targetIsObject ? { ...target } : target;
18
18
  if (targetIsObject && isObject(source)) {
19
19
  Object.keys(source).forEach(key => {
20
20
  const sourceElement = source[key];
21
+ // Skip undefined values when ignoreUndefined is true
22
+ if (ignoreUndefined && sourceElement === undefined) {
23
+ return;
24
+ }
21
25
  if (sourceElement instanceof Date) {
22
26
  // Assign a new Date instance with the same time value
23
27
  Object.assign(output, { [key]: new Date(sourceElement.getTime()) });
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
 
3
3
  import {
4
+ AuthController,
4
5
  EntityCollection,
5
6
  PropertiesOrBuilders,
6
7
  PropertyConfig,
@@ -13,12 +14,15 @@ import { resolveProperty } from "./resolutions";
13
14
  import { CircleIcon, FunctionsIcon } from "@firecms/ui";
14
15
  import { getFieldConfig } from "../core";
15
16
 
16
- export function isReferenceProperty(propertyOrBuilder: PropertyOrBuilder,
17
- fields: Record<string, PropertyConfig>) {
17
+ export function isReferenceProperty(
18
+ authController: AuthController,
19
+ propertyOrBuilder: PropertyOrBuilder,
20
+ fields: Record<string, PropertyConfig>) {
18
21
  const resolvedProperty = resolveProperty({
19
22
  propertyKey: "ignore", // TODO
20
23
  propertyOrBuilder,
21
- propertyConfigs: fields
24
+ propertyConfigs: fields,
25
+ authController
22
26
  });
23
27
  if (!resolvedProperty) return null;
24
28
  if (resolvedProperty.dataType === "reference") {
@@ -1,12 +1,14 @@
1
- import { EntityCollection, PropertyConfig, ResolvedEntityCollection } from "../types";
1
+ import { AuthController, EntityCollection, PropertyConfig, ResolvedEntityCollection } from "../types";
2
2
  import { isReferenceProperty } from "./property_utils";
3
3
  import { isPropertyBuilder } from "./entities";
4
4
  import { getFieldConfig } from "../core";
5
5
 
6
- export function getEntityPreviewKeys(targetCollection: EntityCollection<any>,
7
- fields: Record<string, PropertyConfig>,
8
- previewProperties?: string[],
9
- limit = 3) {
6
+ export function getEntityPreviewKeys(
7
+ authController: AuthController,
8
+ targetCollection: EntityCollection<any>,
9
+ fields: Record<string, PropertyConfig>,
10
+ previewProperties?: string[],
11
+ limit = 3) {
10
12
  const allProperties = Object.keys(targetCollection.properties);
11
13
  let listProperties = previewProperties?.filter(p => allProperties.includes(p as string));
12
14
  if (!listProperties && targetCollection.previewProperties) {
@@ -18,7 +20,7 @@ export function getEntityPreviewKeys(targetCollection: EntityCollection<any>,
18
20
  listProperties = allProperties;
19
21
  return listProperties.filter(key => {
20
22
  const propertyOrBuilder = targetCollection.properties[key];
21
- return propertyOrBuilder && !isPropertyBuilder(propertyOrBuilder) && !isReferenceProperty(propertyOrBuilder, fields);
23
+ return propertyOrBuilder && !isPropertyBuilder(propertyOrBuilder) && !isReferenceProperty(authController, propertyOrBuilder, fields);
22
24
  }).slice(0, limit);
23
25
  }
24
26
  }