@firecms/core 3.0.0-canary.0 → 3.0.0-canary.10

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 (177) hide show
  1. package/README.md +1 -1
  2. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +1 -1
  3. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  4. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +2 -2
  5. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +1 -2
  6. package/dist/components/EntityCollectionView/useSelectionController.d.ts +2 -0
  7. package/dist/components/EntityPreview.d.ts +25 -7
  8. package/dist/components/EntityView.d.ts +11 -0
  9. package/dist/components/FieldCaption.d.ts +5 -0
  10. package/dist/components/HomePage/NavigationCard.d.ts +8 -0
  11. package/dist/components/HomePage/{NavigationCollectionCard.d.ts → NavigationCardBinding.d.ts} +2 -2
  12. package/dist/components/HomePage/SmallNavigationCard.d.ts +6 -0
  13. package/dist/components/HomePage/index.d.ts +3 -1
  14. package/dist/components/ReferenceWidget.d.ts +3 -3
  15. package/dist/components/VirtualTable/VirtualTableProps.d.ts +1 -1
  16. package/dist/components/index.d.ts +4 -3
  17. package/dist/contexts/AuthControllerContext.d.ts +1 -1
  18. package/dist/{internal/EntityView.d.ts → core/EntityEditView.d.ts} +2 -2
  19. package/dist/core/SideEntityView.d.ts +7 -0
  20. package/dist/core/index.d.ts +0 -2
  21. package/dist/form/EntityForm.d.ts +1 -1
  22. package/dist/form/components/StorageItemPreview.d.ts +3 -2
  23. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  24. package/dist/form/components/index.d.ts +1 -0
  25. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  26. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  27. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +4 -3
  28. package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -2
  29. package/dist/form/index.d.ts +1 -0
  30. package/dist/form/validation.d.ts +1 -1
  31. package/dist/hooks/data/delete.d.ts +2 -2
  32. package/dist/hooks/data/save.d.ts +1 -1
  33. package/dist/hooks/data/useDataSource.d.ts +2 -2
  34. package/dist/hooks/data/useEntityFetch.d.ts +3 -3
  35. package/dist/hooks/index.d.ts +3 -1
  36. package/dist/{core → hooks}/useBuildModeController.d.ts +1 -1
  37. package/dist/hooks/useBuildNavigationController.d.ts +5 -2
  38. package/dist/hooks/useProjectLog.d.ts +6 -2
  39. package/dist/hooks/useStorageSource.d.ts +2 -2
  40. package/dist/hooks/useValidateAuthenticator.d.ts +25 -0
  41. package/dist/index.es.js +8118 -7754
  42. package/dist/index.es.js.map +1 -1
  43. package/dist/index.umd.js +5 -5
  44. package/dist/index.umd.js.map +1 -1
  45. package/dist/internal/useBuildDataSource.d.ts +4 -0
  46. package/dist/preview/PropertyPreview.d.ts +1 -1
  47. package/dist/preview/PropertyPreviewProps.d.ts +1 -4
  48. package/dist/preview/components/BooleanPreview.d.ts +5 -1
  49. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  50. package/dist/preview/components/ReferencePreview.d.ts +1 -7
  51. package/dist/types/analytics.d.ts +1 -1
  52. package/dist/types/auth.d.ts +37 -1
  53. package/dist/types/collections.d.ts +17 -4
  54. package/dist/types/datasource.d.ts +1 -1
  55. package/dist/types/entities.d.ts +1 -0
  56. package/dist/types/entity_callbacks.d.ts +2 -2
  57. package/dist/types/entity_overrides.d.ts +6 -0
  58. package/dist/types/index.d.ts +2 -0
  59. package/dist/types/navigation.d.ts +10 -9
  60. package/dist/types/permissions.d.ts +5 -1
  61. package/dist/types/plugins.d.ts +17 -19
  62. package/dist/types/properties.d.ts +2 -2
  63. package/dist/types/property_config.d.ts +2 -2
  64. package/dist/types/roles.d.ts +31 -0
  65. package/dist/types/user.d.ts +5 -0
  66. package/dist/util/collections.d.ts +9 -1
  67. package/dist/util/icons.d.ts +8 -2
  68. package/dist/util/index.d.ts +1 -0
  69. package/dist/util/permissions.d.ts +4 -4
  70. package/dist/util/references.d.ts +4 -2
  71. package/dist/util/resolutions.d.ts +1 -1
  72. package/dist/util/useTraceUpdate.d.ts +1 -0
  73. package/package.json +31 -23
  74. package/src/components/DeleteEntityDialog.tsx +4 -4
  75. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -3
  76. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +273 -277
  77. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  78. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +13 -13
  79. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +9 -16
  80. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +3 -3
  81. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
  82. package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +9 -5
  83. package/src/components/EntityCollectionView/EntityCollectionView.tsx +28 -49
  84. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
  85. package/src/components/EntityCollectionView/useSelectionController.tsx +30 -0
  86. package/src/components/EntityPreview.tsx +204 -70
  87. package/src/components/EntityView.tsx +84 -0
  88. package/src/components/FieldCaption.tsx +14 -0
  89. package/src/components/FireCMSAppBar.tsx +8 -0
  90. package/src/components/HomePage/DefaultHomePage.tsx +14 -10
  91. package/src/components/HomePage/NavigationCard.tsx +69 -0
  92. package/src/components/HomePage/NavigationCardBinding.tsx +116 -0
  93. package/src/components/HomePage/SmallNavigationCard.tsx +45 -0
  94. package/src/components/HomePage/index.tsx +3 -1
  95. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -4
  96. package/src/components/ReferenceWidget.tsx +7 -7
  97. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +11 -19
  98. package/src/components/VirtualTable/VirtualTableProps.tsx +1 -1
  99. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +1 -1
  100. package/src/components/index.tsx +4 -3
  101. package/src/contexts/AuthControllerContext.tsx +1 -1
  102. package/src/core/Drawer.tsx +66 -39
  103. package/src/{internal/EntityView.tsx → core/EntityEditView.tsx} +22 -39
  104. package/src/core/EntitySidePanel.tsx +2 -2
  105. package/src/core/FireCMS.tsx +18 -2
  106. package/src/core/NavigationRoutes.tsx +8 -0
  107. package/src/core/SideEntityView.tsx +38 -0
  108. package/src/core/index.tsx +0 -2
  109. package/src/form/EntityForm.tsx +20 -12
  110. package/src/form/components/StorageItemPreview.tsx +5 -3
  111. package/src/form/components/StorageUploadProgress.tsx +6 -5
  112. package/src/form/components/index.tsx +1 -0
  113. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -3
  114. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +12 -15
  115. package/src/form/field_bindings/BlockFieldBinding.tsx +2 -3
  116. package/src/form/field_bindings/DateTimeFieldBinding.tsx +3 -3
  117. package/src/form/field_bindings/KeyValueFieldBinding.tsx +18 -18
  118. package/src/form/field_bindings/MapFieldBinding.tsx +17 -17
  119. package/src/form/field_bindings/MarkdownFieldBinding.tsx +1 -2
  120. package/src/form/field_bindings/MultiSelectBinding.tsx +2 -3
  121. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +3 -3
  122. package/src/form/field_bindings/ReferenceFieldBinding.tsx +6 -4
  123. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
  124. package/src/form/field_bindings/SelectFieldBinding.tsx +2 -3
  125. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +15 -6
  126. package/src/form/field_bindings/SwitchFieldBinding.tsx +2 -3
  127. package/src/form/field_bindings/TextFieldBinding.tsx +10 -9
  128. package/src/form/index.tsx +1 -0
  129. package/src/form/validation.ts +3 -4
  130. package/src/hooks/data/delete.ts +3 -3
  131. package/src/hooks/data/save.ts +1 -1
  132. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  133. package/src/hooks/data/useDataSource.tsx +8 -3
  134. package/src/hooks/data/useEntityFetch.tsx +4 -4
  135. package/src/hooks/index.tsx +5 -1
  136. package/src/{core → hooks}/useBuildModeController.tsx +1 -1
  137. package/src/hooks/useBuildNavigationController.tsx +134 -75
  138. package/src/hooks/useProjectLog.tsx +16 -6
  139. package/src/hooks/useReferenceDialog.tsx +2 -2
  140. package/src/hooks/useStorageSource.tsx +7 -2
  141. package/src/hooks/useValidateAuthenticator.tsx +135 -0
  142. package/src/internal/useBuildDataSource.ts +7 -2
  143. package/src/preview/PropertyPreview.tsx +2 -2
  144. package/src/preview/PropertyPreviewProps.tsx +1 -11
  145. package/src/preview/components/BooleanPreview.tsx +19 -4
  146. package/src/preview/components/EnumValuesChip.tsx +1 -1
  147. package/src/preview/components/ReferencePreview.tsx +55 -147
  148. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
  149. package/src/types/analytics.ts +1 -0
  150. package/src/types/auth.tsx +50 -1
  151. package/src/types/collections.ts +19 -4
  152. package/src/types/datasource.ts +1 -1
  153. package/src/types/entities.ts +4 -0
  154. package/src/types/entity_actions.tsx +4 -0
  155. package/src/types/entity_callbacks.ts +2 -2
  156. package/src/types/entity_overrides.tsx +7 -0
  157. package/src/types/firecms.tsx +0 -1
  158. package/src/types/index.ts +2 -0
  159. package/src/types/navigation.ts +11 -10
  160. package/src/types/permissions.ts +6 -1
  161. package/src/types/plugins.tsx +24 -27
  162. package/src/types/properties.ts +3 -2
  163. package/src/types/property_config.tsx +2 -2
  164. package/src/types/roles.ts +41 -0
  165. package/src/types/side_entity_controller.tsx +1 -0
  166. package/src/types/user.ts +7 -0
  167. package/src/util/collections.ts +22 -0
  168. package/src/util/icons.tsx +11 -3
  169. package/src/util/index.ts +1 -0
  170. package/src/util/permissions.ts +11 -8
  171. package/src/util/references.ts +36 -5
  172. package/src/util/useTraceUpdate.tsx +1 -1
  173. package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
  174. /package/dist/{components → form/components}/LabelWithIcon.d.ts +0 -0
  175. /package/dist/{core → hooks}/useBuildLocalConfigurationPersistence.d.ts +0 -0
  176. /package/src/{components → form/components}/LabelWithIcon.tsx +0 -0
  177. /package/src/{core → hooks}/useBuildLocalConfigurationPersistence.tsx +0 -0
@@ -1,5 +1,5 @@
1
- import { useCallback, useEffect, useMemo, useState } from "react";
2
- import { useLocation } from "react-router-dom";
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import equal from "react-fast-compare"
3
3
 
4
4
  import {
5
5
  AuthController,
@@ -10,12 +10,14 @@ import {
10
10
  EntityCollectionsBuilder,
11
11
  EntityReference,
12
12
  NavigationController,
13
+ PermissionsBuilder,
13
14
  TopNavigationEntry,
14
15
  TopNavigationResult,
15
16
  User,
16
17
  UserConfigurationPersistence
17
18
  } from "../types";
18
19
  import {
20
+ applyPermissionsFunctionIfEmpty,
19
21
  getCollectionByPathOrId,
20
22
  mergeDeep,
21
23
  removeInitialAndTrailingSlashes,
@@ -32,7 +34,10 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
32
34
  baseCollectionPath?: string,
33
35
  authController: AuthController<UserType>;
34
36
  collections?: EC[] | EntityCollectionsBuilder<EC>;
37
+ collectionPermissions?: PermissionsBuilder;
35
38
  views?: CMSView[] | CMSViewsBuilder;
39
+ adminViews?: CMSView[] | CMSViewsBuilder;
40
+ viewsOrder?: string[];
36
41
  userConfigPersistence?: UserConfigurationPersistence;
37
42
  dataSourceDelegate: DataSourceDelegate;
38
43
  /**
@@ -45,21 +50,25 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
45
50
  injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
46
51
  };
47
52
 
48
- export function useBuildNavigationController<EC extends EntityCollection, UserType extends User>({
49
- basePath = DEFAULT_BASE_PATH,
50
- baseCollectionPath = DEFAULT_COLLECTION_PATH,
51
- authController,
52
- collections: baseCollections,
53
- views: baseViews,
54
- userConfigPersistence,
55
- dataSourceDelegate,
56
- injectCollections
57
- }: BuildNavigationContextProps<EC, UserType>): NavigationController {
58
-
59
- const location = useLocation();
60
-
61
- const [collections, setCollections] = useState<EntityCollection[] | undefined>();
62
- const [views, setViews] = useState<CMSView[] | undefined>();
53
+ export function useBuildNavigationController<EC extends EntityCollection, UserType extends User>(props: BuildNavigationContextProps<EC, UserType>): NavigationController {
54
+ const {
55
+ basePath = DEFAULT_BASE_PATH,
56
+ baseCollectionPath = DEFAULT_COLLECTION_PATH,
57
+ authController,
58
+ collections: collectionsProp,
59
+ collectionPermissions,
60
+ views: viewsProp,
61
+ adminViews: adminViewsProp,
62
+ viewsOrder,
63
+ userConfigPersistence,
64
+ dataSourceDelegate,
65
+ injectCollections
66
+ } = props;
67
+
68
+ const collectionsRef = useRef<EntityCollection[] | undefined>();
69
+ const viewsRef = useRef<CMSView[] | undefined>();
70
+ const adminViewsRef = useRef<CMSView[] | undefined>();
71
+
63
72
  const [initialised, setInitialised] = useState<boolean>(false);
64
73
 
65
74
  const [topLevelNavigation, setTopLevelNavigation] = useState<TopNavigationResult | undefined>(undefined);
@@ -79,19 +88,18 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
79
88
  const buildUrlCollectionPath = useCallback((path: string): string => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path)}`,
80
89
  [baseCollectionPath]);
81
90
 
82
- const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[]): TopNavigationResult => {
83
- // return (collection.editable && resolvePermissions(collection, authController, paths).editCollection) ?? DEFAULT_PERMISSIONS.editCollection;
84
- const navigationEntries: TopNavigationEntry[] = [
91
+ const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[], adminViews: CMSView[], viewsOrder?: string[]): TopNavigationResult => {
92
+ let navigationEntries: TopNavigationEntry[] = [
85
93
  ...(collections ?? []).map(collection => (!collection.hideFromNavigation
86
- ? {
94
+ ? ({
87
95
  url: buildUrlCollectionPath(collection.id ?? collection.path),
88
96
  type: "collection",
89
97
  name: collection.name.trim(),
90
98
  path: collection.id ?? collection.path,
91
99
  collection,
92
100
  description: collection.description?.trim(),
93
- group: collection.group?.trim()
94
- }
101
+ group: getGroup(collection)
102
+ } satisfies TopNavigationEntry)
95
103
  : undefined))
96
104
  .filter(Boolean) as TopNavigationEntry[],
97
105
  ...(views ?? []).map(view =>
@@ -100,18 +108,50 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
100
108
  url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
101
109
  name: view.name.trim(),
102
110
  type: "view",
111
+ path: view.path,
103
112
  view,
104
113
  description: view.description?.trim(),
105
- group: view.group?.trim()
106
- })
114
+ group: getGroup(view)
115
+ } satisfies TopNavigationEntry)
116
+ : undefined)
117
+ .filter(Boolean) as TopNavigationEntry[],
118
+ ...(adminViews ?? []).map(view =>
119
+ !view.hideFromNavigation
120
+ ? ({
121
+ url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
122
+ name: view.name.trim(),
123
+ type: "admin",
124
+ path: view.path,
125
+ view,
126
+ description: view.description?.trim(),
127
+ group: "Admin"
128
+ } satisfies TopNavigationEntry)
107
129
  : undefined)
108
130
  .filter(Boolean) as TopNavigationEntry[]
109
131
  ];
110
132
 
133
+ if (viewsOrder) {
134
+ navigationEntries = navigationEntries.sort((a, b) => {
135
+ const aIndex = viewsOrder.indexOf(a.path);
136
+ const bIndex = viewsOrder.indexOf(b.path);
137
+ if (aIndex === -1 && bIndex === -1) {
138
+ return 0;
139
+ }
140
+ if (aIndex === -1) {
141
+ return 1;
142
+ }
143
+ if (bIndex === -1) {
144
+ return -1;
145
+ }
146
+ return aIndex - bIndex;
147
+ });
148
+ }
149
+
111
150
  const groups: string[] = Object.values(navigationEntries)
112
151
  .map(e => e.group)
113
152
  .filter(Boolean)
114
153
  .filter((value, index, array) => array.indexOf(value) === index) as string[];
154
+
115
155
  return {
116
156
  navigationEntries,
117
157
  groups
@@ -124,15 +164,24 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
124
164
  return;
125
165
 
126
166
  try {
127
- const [resolvedCollections = [], resolvedViews = []] = await Promise.all([
128
- resolveCollections(baseCollections, authController, dataSourceDelegate, injectCollections),
129
- resolveCMSViews(baseViews, authController, dataSourceDelegate)
167
+ console.debug("Refreshing navigation")
168
+ const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
169
+ resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
170
+ resolveCMSViews(viewsProp, authController, dataSourceDelegate),
171
+ resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
130
172
  ]
131
173
  );
132
-
133
- setCollections(resolvedCollections);
134
- setViews(resolvedViews);
135
- setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews));
174
+ if (
175
+ !equal(collectionsRef.current, resolvedCollections) ||
176
+ !equal(viewsRef.current, resolvedViews) ||
177
+ !equal(adminViewsRef.current, resolvedAdminViews) ||
178
+ !equal(topLevelNavigation, computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder))
179
+ ) {
180
+ collectionsRef.current = resolvedCollections;
181
+ viewsRef.current = resolvedViews;
182
+ adminViewsRef.current = resolvedAdminViews;
183
+ setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
184
+ }
136
185
  } catch (e) {
137
186
  console.error(e);
138
187
  setNavigationLoadingError(e as any);
@@ -140,7 +189,17 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
140
189
 
141
190
  setNavigationLoading(false);
142
191
  setInitialised(true);
143
- }, [baseCollections, authController.user, authController.initialLoading, baseViews, computeTopNavigation, injectCollections]);
192
+
193
+ }, [
194
+ collectionsProp,
195
+ collectionPermissions,
196
+ authController.user,
197
+ authController.initialLoading,
198
+ viewsProp,
199
+ adminViewsProp,
200
+ computeTopNavigation,
201
+ injectCollections
202
+ ]);
144
203
 
145
204
  useEffect(() => {
146
205
  refreshNavigation();
@@ -151,7 +210,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
151
210
  entityId?: string,
152
211
  includeUserOverride = false
153
212
  ): EC | undefined => {
154
-
213
+ const collections = collectionsRef.current;
155
214
  if (!collections)
156
215
  return undefined;
157
216
 
@@ -182,11 +241,12 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
182
241
  }, [
183
242
  basePath,
184
243
  baseCollectionPath,
185
- collections,
186
244
  ]);
187
245
 
188
246
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
189
- let currentCollections = collections;
247
+
248
+ const collections = collectionsRef.current;
249
+ let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
190
250
  if (!currentCollections)
191
251
  throw Error("Collections have not been initialised yet");
192
252
 
@@ -202,10 +262,12 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
202
262
 
203
263
  return undefined;
204
264
 
205
- }, [collections]);
265
+ }, []);
206
266
 
207
267
  const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
208
- let currentCollections = collections;
268
+
269
+ const collections = collectionsRef.current;
270
+ let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
209
271
  if (!currentCollections)
210
272
  throw Error("Collections have not been initialised yet");
211
273
 
@@ -221,7 +283,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
221
283
 
222
284
  return undefined;
223
285
 
224
- }, [collections]);
286
+ }, []);
225
287
 
226
288
  const isUrlCollectionPath = useCallback(
227
289
  (path: string): boolean => removeInitialAndTrailingSlashes(path + "/").startsWith(removeInitialAndTrailingSlashes(fullCollectionPath) + "/"),
@@ -243,25 +305,19 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
243
305
  []);
244
306
 
245
307
  const resolveAliasesFrom = useCallback((path: string): string => {
308
+ const collections = collectionsRef.current;
246
309
  if (!collections)
247
310
  throw Error("Collections have not been initialised yet");
248
311
  return resolveCollectionPathIds(path, collections);
249
- }, [collections]);
250
-
251
- const state = location.state as any;
252
- /**
253
- * The location can be overridden if `base_location` is set in the
254
- * state field of the current location. This can happen if you open
255
- * a side entity, like `products`, from a different one, like `users`
256
- */
257
- const baseLocation = state && state.base_location ? state.base_location : location;
312
+ }, []);
258
313
 
259
314
  const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
315
+ const collections = collectionsRef.current ?? [];
260
316
  return getParentReferencesFromPath({
261
317
  path,
262
318
  collections
263
319
  });
264
- }, [collections]);
320
+ }, []);
265
321
 
266
322
  const getParentCollectionIds = useCallback((path: string): string[] => {
267
323
 
@@ -276,35 +332,28 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
276
332
  }
277
333
 
278
334
  // for each odd path segment, get the collection
279
- const parentCollectionIds = result.map(r => getCollectionFromPaths(r)?.id).filter(Boolean) as string[];
280
-
281
- getCollectionFromPaths(oddPathSegments);
282
-
283
- // const allParentCollectionsForPath = getAllParentReferencesForPath(path);
284
- // console.log("allParentCollectionsForPath", allParentCollectionsForPath);
285
- // const parentCollectionIds = allParentCollectionsForPath.map(r => r.id);
286
- console.log("getParentCollectionIds", path, parentCollectionIds);
287
- return parentCollectionIds;
335
+ return result.map(r => getCollectionFromPaths(r)?.id).filter(Boolean) as string[];
288
336
  }, [getAllParentReferencesForPath])
289
337
 
290
338
  const convertIdsToPaths = useCallback((ids: string[]): string[] => {
291
- let currentCollections = collections;
292
- const paths: string[] = [];
293
- for (let i = 0; i < ids.length; i++) {
294
- const id = ids[i];
295
- const collection: EntityCollection | undefined = currentCollections!.find(c => c.id === id);
296
- if (!collection)
297
- throw Error(`Collection with id ${id} not found`);
298
- paths.push(collection.path);
299
- currentCollections = collection.subcollections;
300
- }
301
- return paths;
339
+ const collections = collectionsRef.current;
340
+ let currentCollections = collections;
341
+ const paths: string[] = [];
342
+ for (let i = 0; i < ids.length; i++) {
343
+ const id = ids[i];
344
+ const collection: EntityCollection | undefined = currentCollections!.find(c => c.id === id);
345
+ if (!collection)
346
+ throw Error(`Collection with id ${id} not found`);
347
+ paths.push(collection.path);
348
+ currentCollections = collection.subcollections;
302
349
  }
303
- , [getCollectionFromIds]);
350
+ return paths;
351
+ }, [getCollectionFromIds]);
304
352
 
305
353
  return {
306
- collections,
307
- views,
354
+ collections: collectionsRef.current,
355
+ views: viewsRef.current,
356
+ adminViews: adminViewsRef.current,
308
357
  loading: !initialised || navigationLoading,
309
358
  navigationLoadingError,
310
359
  homeUrl,
@@ -321,7 +370,6 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
321
370
  buildCMSUrlPath,
322
371
  resolveAliasesFrom,
323
372
  topLevelNavigation,
324
- baseLocation,
325
373
  refreshNavigation,
326
374
  getParentReferencesFromPath: getAllParentReferencesForPath,
327
375
  getParentCollectionIds,
@@ -346,8 +394,8 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
346
394
  return resolvedCollections
347
395
  .filter((c) => {
348
396
  if (!c.permissions) return true;
349
- const resolvedPermissions = resolvePermissions(c, authController, [c.path], null,)
350
- return resolvedPermissions.read !== false;
397
+ const resolvedPermissions = resolvePermissions(c, authController, c.path, null)
398
+ return resolvedPermissions?.read !== false;
351
399
  })
352
400
  .map((c) => {
353
401
  if (!c.subcollections) return c;
@@ -359,6 +407,7 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
359
407
  }
360
408
 
361
409
  async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
410
+ collectionPermissions: PermissionsBuilder | undefined,
362
411
  authController: AuthController,
363
412
  dataSource: DataSourceDelegate,
364
413
  injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
@@ -373,6 +422,8 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
373
422
  resolvedCollections = collections;
374
423
  }
375
424
 
425
+ resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
426
+
376
427
  resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
377
428
 
378
429
  if (injectCollections) {
@@ -395,3 +446,11 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
395
446
  }
396
447
  return resolvedViews;
397
448
  }
449
+
450
+ function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
451
+ const trimmed = collectionOrView.group?.trim();
452
+ if (!trimmed || trimmed === "") {
453
+ return "Views";
454
+ }
455
+ return trimmed ?? "Views";
456
+ }
@@ -1,10 +1,15 @@
1
- import { useEffect, useRef } from "react";
2
- import { AuthController } from "../types";
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { AuthController, FireCMSPlugin } from "../types";
3
3
 
4
4
  export const DEFAULT_SERVER_DEV = "https://api-kdoe6pj3qq-ey.a.run.app";
5
5
  export const DEFAULT_SERVER = "https://api-drplyi3b6q-ey.a.run.app";
6
6
 
7
- async function makeRequest(authController: AuthController) {
7
+ export type AccessResponse = {
8
+ blocked?: boolean;
9
+ message?: string;
10
+ }
11
+
12
+ async function makeRequest(authController: AuthController, pluginKeys: string | undefined) {
8
13
  const firebaseToken = await authController.getAuthToken();
9
14
  return fetch(DEFAULT_SERVER + "/access_log",
10
15
  {
@@ -14,18 +19,23 @@ async function makeRequest(authController: AuthController) {
14
19
  "Content-Type": "application/json",
15
20
  Authorization: `Basic ${firebaseToken}`,
16
21
  },
17
- body: JSON.stringify({})
22
+ body: JSON.stringify({ plugins: pluginKeys })
18
23
  })
19
24
  .then(async (res) => {
25
+ return res.json();
20
26
  });
21
27
  }
22
28
 
23
- export function useProjectLog(authController: AuthController) {
29
+ export function useProjectLog(authController: AuthController,
30
+ plugins?: FireCMSPlugin<any, any, any>[]): AccessResponse | null {
31
+ const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
24
32
  const accessedUserRef = useRef<string | null>(null);
33
+ const pluginKeys = plugins?.map(plugin => plugin.key).join(",");
25
34
  useEffect(() => {
26
35
  if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
27
- makeRequest(authController);
36
+ makeRequest(authController, pluginKeys).then(setAccessResponse);
28
37
  accessedUserRef.current = authController.user.uid;
29
38
  }
30
39
  }, [authController]);
40
+ return accessResponse;
31
41
  }
@@ -1,5 +1,5 @@
1
1
  import { useSideDialogsController } from "./useSideDialogsController";
2
- import { ReferenceSelectionTable, ReferenceSelectionInnerProps } from "../components";
2
+ import { ReferenceSelectionInnerProps, ReferenceSelectionTable } from "../components";
3
3
  import { useCallback } from "react";
4
4
  import { useNavigationController } from "./useNavigationController";
5
5
 
@@ -27,7 +27,7 @@ export function useReferenceDialog<M extends Record<string, any>>(referenceDialo
27
27
  if (!usedCollection)
28
28
  usedCollection = navigation.getCollection(referenceDialogProps.path);
29
29
  if (!usedCollection)
30
- throw Error("Not able to resolve the collection in useReferenceDialog");
30
+ throw Error("Not able to resolve the collection in useReferenceDialog. Make sure a collection is registered in path " + referenceDialogProps.path);
31
31
  sideDialogsController.open({
32
32
  key: `reference_${referenceDialogProps.path}`,
33
33
  component:
@@ -1,4 +1,4 @@
1
- import { StorageSource } from "../types";
1
+ import { EntityCollection, StorageSource } from "../types";
2
2
  import { StorageSourceContext } from "../contexts/StorageSourceContext";
3
3
  import { useContext } from "react";
4
4
 
@@ -6,4 +6,9 @@ import { useContext } from "react";
6
6
  * Use this hook to get the storage source being used
7
7
  * @group Hooks and utilities
8
8
  */
9
- export const useStorageSource = (): StorageSource => useContext(StorageSourceContext);
9
+ export const useStorageSource = (collection?: EntityCollection): StorageSource => {
10
+ const defaultStorageSource = useContext(StorageSourceContext);
11
+ if (collection?.overrides?.storageSource)
12
+ return collection.overrides.storageSource;
13
+ return defaultStorageSource;
14
+ };
@@ -0,0 +1,135 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import equal from "react-fast-compare";
3
+
4
+ import { AppCheckTokenResult, AuthController, Authenticator, DataSourceDelegate, StorageSource, User } from "../index";
5
+
6
+ /**
7
+ * This hook is used internally for validating an authenticator.
8
+ *
9
+ * @param authController
10
+ * @param authentication
11
+ * @param getAppCheckToken
12
+ * @param appCheckForceRefresh
13
+ * @param storageSource
14
+ * @param dataSource
15
+ */
16
+ export function useValidateAuthenticator<UserType extends User = User, Controller extends AuthController<UserType> = AuthController<UserType>>({
17
+ disabled,
18
+ authController,
19
+ authentication,
20
+ getAppCheckToken,
21
+ appCheckForceRefresh = false,
22
+ storageSource,
23
+ dataSourceDelegate
24
+ }:
25
+ {
26
+ disabled?: boolean,
27
+ authController: Controller,
28
+ authentication?: boolean | Authenticator<UserType, Controller>,
29
+ getAppCheckToken?: (forceRefresh: boolean) => Promise<AppCheckTokenResult> | undefined,
30
+ appCheckForceRefresh?: boolean,
31
+ dataSourceDelegate: DataSourceDelegate;
32
+ storageSource: StorageSource;
33
+ }): {
34
+ canAccessMainView: boolean,
35
+ authLoading: boolean,
36
+ notAllowedError: any,
37
+ authVerified: boolean,
38
+ } {
39
+
40
+ const authenticationEnabled = Boolean(authentication);
41
+
42
+ const [authLoading, setAuthLoading] = useState<boolean>(authenticationEnabled);
43
+ const [notAllowedError, setNotAllowedError] = useState<any>(false);
44
+ const [authVerified, setAuthVerified] = useState<boolean>(!authenticationEnabled || Boolean(authController.loginSkipped));
45
+
46
+ const canAccessMainView = (authVerified) &&
47
+ (!authenticationEnabled || Boolean(authController.user) || Boolean(authController.loginSkipped)) &&
48
+ !notAllowedError;
49
+
50
+ useEffect(() => {
51
+ if (authController.loginSkipped)
52
+ setAuthVerified(true);
53
+ }, [authController.loginSkipped]);
54
+
55
+ /**
56
+ * We use this ref to check the authentication only if the user has
57
+ * changed.
58
+ */
59
+ const checkedUserRef = useRef<User | undefined>();
60
+
61
+ const checkAuthentication = useCallback(async () => {
62
+
63
+ if (disabled) {
64
+ return;
65
+ }
66
+
67
+ if (authController.initialLoading) {
68
+ return;
69
+ }
70
+
71
+ if (!authController.user && !authController.loginSkipped) {
72
+ checkedUserRef.current = undefined;
73
+ setAuthLoading(false);
74
+ setAuthVerified(false);
75
+ return;
76
+ }
77
+
78
+ const delegateUser = authController.user;
79
+ console.debug("Checking authentication for user", delegateUser);
80
+
81
+ if (getAppCheckToken) {
82
+ try {
83
+ if (!await getAppCheckToken(appCheckForceRefresh)) {
84
+ setNotAllowedError("App Check failed.");
85
+ authController.signOut();
86
+ } else {
87
+ console.debug("App Check success.");
88
+ }
89
+ } catch (e: any) {
90
+ setNotAllowedError(e.message);
91
+ authController.signOut();
92
+ }
93
+ }
94
+
95
+ if (authentication instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
96
+ setAuthLoading(true);
97
+ try {
98
+ const allowed = await authentication({
99
+ user: delegateUser,
100
+ authController,
101
+ dataSourceDelegate,
102
+ storageSource
103
+ });
104
+ if (!allowed) {
105
+ authController.signOut();
106
+ setNotAllowedError(true);
107
+ }
108
+ } catch (e) {
109
+ setNotAllowedError(e);
110
+ authController.signOut();
111
+ }
112
+ setAuthLoading(false);
113
+ setAuthVerified(true);
114
+ checkedUserRef.current = delegateUser;
115
+ } else {
116
+ setAuthLoading(false);
117
+ }
118
+
119
+ if (!authController.initialLoading && !delegateUser) {
120
+ setAuthVerified(true);
121
+ }
122
+
123
+ }, [disabled, authController, authentication, getAppCheckToken, appCheckForceRefresh, dataSourceDelegate, storageSource]);
124
+
125
+ useEffect(() => {
126
+ checkAuthentication();
127
+ }, [checkAuthentication]);
128
+
129
+ return {
130
+ canAccessMainView,
131
+ authLoading: authenticationEnabled && authLoading,
132
+ notAllowedError,
133
+ authVerified
134
+ }
135
+ }
@@ -330,6 +330,10 @@ export function useBuildDataSource({
330
330
  * bindings.
331
331
  * Also, Firestore references are replaced with {@link EntityReference}
332
332
  * @param data
333
+ * @param buildReference
334
+ * @param buildGeoPoint
335
+ * @param buildDate
336
+ * @param buildDelete
333
337
  * @group Firestore
334
338
  */
335
339
  export function cmsToDelegateModel(data: any,
@@ -340,9 +344,11 @@ export function cmsToDelegateModel(data: any,
340
344
  ): any {
341
345
  if (data === undefined) {
342
346
  return buildDelete();
347
+ } else if (data === null) {
348
+ return null;
343
349
  } else if (Array.isArray(data)) {
344
350
  return data.map(v => cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete));
345
- } else if (data instanceof EntityReference) {
351
+ } else if (data.isEntityReference && data.isEntityReference()) {
346
352
  return buildReference(data);
347
353
  } else if (data instanceof GeoPoint) {
348
354
  return buildGeoPoint(data);
@@ -355,4 +361,3 @@ export function cmsToDelegateModel(data: any,
355
361
  }
356
362
  return data;
357
363
  }
358
-
@@ -178,7 +178,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
178
178
  }
179
179
  } else if (property.dataType === "reference") {
180
180
  if (typeof property.path === "string") {
181
- if (value instanceof EntityReference) {
181
+ if (typeof value === "object" && "isEntityReference" in value && value.isEntityReference()) {
182
182
  content = <ReferencePreview
183
183
  disabled={!property.path}
184
184
  previewProperties={property.previewProperties}
@@ -195,7 +195,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
195
195
 
196
196
  } else if (property.dataType === "boolean") {
197
197
  if (typeof value === "boolean") {
198
- content = <BooleanPreview value={value}/>;
198
+ content = <BooleanPreview value={value} size={size} property={property}/>;
199
199
  } else {
200
200
  content = buildWrongValueType(propertyKey, property.dataType, value);
201
201
  }
@@ -8,7 +8,7 @@ export type PreviewSize = "medium" | "small" | "tiny";
8
8
  /**
9
9
  * @group Preview components
10
10
  */
11
- export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any, M extends Record<string, any> = Record<string, any>> {
11
+ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any> {
12
12
  /**
13
13
  * Name of the property
14
14
  */
@@ -24,11 +24,6 @@ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
24
24
  */
25
25
  property: Property<T> | ResolvedProperty<T>;
26
26
 
27
- /**
28
- * Click handler
29
- */
30
- // onClick?: () => void;
31
-
32
27
  /**
33
28
  * Desired size of the preview, depending on the context.
34
29
  */
@@ -51,9 +46,4 @@ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
51
46
  */
52
47
  customProps?: CustomProps;
53
48
 
54
- /**
55
- * Entity this property refers to
56
- */
57
- // entity?: Entity<M>;
58
-
59
49
  }