@firecms/core 3.0.0-canary.5 → 3.0.0-canary.51

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 (218) hide show
  1. package/README.md +2 -2
  2. package/dist/components/ClearFilterSortButton.d.ts +5 -0
  3. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +11 -11
  4. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  5. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +5 -3
  6. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +3 -2
  7. package/dist/components/EntityCollectionTable/column_utils.d.ts +1 -2
  8. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
  9. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +1 -1
  10. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +12 -3
  11. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
  12. package/dist/components/EntityCollectionView/useSelectionController.d.ts +2 -0
  13. package/dist/components/EntityPreview.d.ts +25 -7
  14. package/dist/components/EntityView.d.ts +11 -0
  15. package/dist/components/FieldCaption.d.ts +5 -0
  16. package/dist/components/FireCMSAppBar.d.ts +3 -2
  17. package/dist/components/HomePage/NavigationCard.d.ts +8 -0
  18. package/dist/components/HomePage/{NavigationCollectionCard.d.ts → NavigationCardBinding.d.ts} +2 -2
  19. package/dist/components/HomePage/SmallNavigationCard.d.ts +6 -0
  20. package/dist/components/HomePage/index.d.ts +3 -1
  21. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  22. package/dist/components/VirtualTable/VirtualTableProps.d.ts +1 -1
  23. package/dist/components/common/types.d.ts +4 -6
  24. package/dist/components/common/useDataSourceEntityCollectionTableController.d.ts +3 -0
  25. package/dist/components/index.d.ts +4 -2
  26. package/dist/contexts/AuthControllerContext.d.ts +1 -1
  27. package/dist/core/Drawer.d.ts +5 -12
  28. package/dist/core/DrawerNavigationItem.d.ts +9 -0
  29. package/dist/core/{EntityView.d.ts → EntityEditView.d.ts} +2 -2
  30. package/dist/core/NavigationRoutes.d.ts +1 -1
  31. package/dist/core/Scaffold.d.ts +7 -10
  32. package/dist/core/index.d.ts +3 -4
  33. package/dist/form/EntityForm.d.ts +1 -1
  34. package/dist/form/components/ErrorFocus.d.ts +1 -1
  35. package/dist/form/components/StorageItemPreview.d.ts +3 -2
  36. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  37. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  38. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  39. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +4 -3
  40. package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -2
  41. package/dist/form/validation.d.ts +1 -1
  42. package/dist/hooks/data/delete.d.ts +2 -2
  43. package/dist/hooks/data/save.d.ts +2 -3
  44. package/dist/hooks/data/useDataSource.d.ts +2 -2
  45. package/dist/hooks/data/useEntityFetch.d.ts +3 -3
  46. package/dist/hooks/index.d.ts +2 -0
  47. package/dist/hooks/useBuildNavigationController.d.ts +6 -4
  48. package/dist/hooks/useProjectLog.d.ts +6 -2
  49. package/dist/hooks/useStorageSource.d.ts +2 -2
  50. package/dist/hooks/useValidateAuthenticator.d.ts +21 -0
  51. package/dist/index.es.js +9644 -9122
  52. package/dist/index.es.js.map +1 -1
  53. package/dist/index.umd.js +5 -5
  54. package/dist/index.umd.js.map +1 -1
  55. package/dist/internal/useBuildDataSource.d.ts +1 -12
  56. package/dist/preview/PropertyPreview.d.ts +1 -1
  57. package/dist/preview/PropertyPreviewProps.d.ts +1 -4
  58. package/dist/preview/components/BooleanPreview.d.ts +5 -1
  59. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  60. package/dist/preview/components/ReferencePreview.d.ts +1 -7
  61. package/dist/types/analytics.d.ts +1 -1
  62. package/dist/types/auth.d.ts +37 -1
  63. package/dist/types/collections.d.ts +29 -5
  64. package/dist/types/datasource.d.ts +3 -6
  65. package/dist/types/entities.d.ts +5 -1
  66. package/dist/types/entity_actions.d.ts +14 -0
  67. package/dist/types/entity_callbacks.d.ts +2 -2
  68. package/dist/types/entity_overrides.d.ts +6 -0
  69. package/dist/types/index.d.ts +2 -1
  70. package/dist/types/navigation.d.ts +14 -13
  71. package/dist/types/permissions.d.ts +5 -1
  72. package/dist/types/plugins.d.ts +20 -20
  73. package/dist/types/properties.d.ts +4 -4
  74. package/dist/types/property_config.d.ts +2 -2
  75. package/dist/types/roles.d.ts +31 -0
  76. package/dist/types/storage.d.ts +11 -3
  77. package/dist/types/user.d.ts +5 -0
  78. package/dist/util/collections.d.ts +9 -1
  79. package/dist/util/entities.d.ts +1 -1
  80. package/dist/util/icon_synonyms.d.ts +2 -4
  81. package/dist/util/icons.d.ts +8 -2
  82. package/dist/util/navigation_utils.d.ts +2 -2
  83. package/dist/util/permissions.d.ts +4 -4
  84. package/dist/util/references.d.ts +4 -2
  85. package/dist/util/resolutions.d.ts +9 -13
  86. package/dist/util/useTraceUpdate.d.ts +1 -0
  87. package/package.json +139 -119
  88. package/src/components/ClearFilterSortButton.tsx +41 -0
  89. package/src/components/DeleteEntityDialog.tsx +4 -4
  90. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
  91. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +275 -278
  92. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +9 -5
  93. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +44 -44
  94. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  95. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +9 -16
  96. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +3 -3
  97. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +27 -32
  98. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +11 -6
  99. package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +9 -5
  100. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +2 -4
  101. package/src/components/EntityCollectionView/EntityCollectionView.tsx +560 -554
  102. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
  103. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  104. package/src/components/EntityCollectionView/useSelectionController.tsx +30 -0
  105. package/src/components/EntityPreview.tsx +207 -70
  106. package/src/components/EntityView.tsx +84 -0
  107. package/src/components/FieldCaption.tsx +14 -0
  108. package/src/components/FireCMSAppBar.tsx +33 -11
  109. package/src/components/HomePage/DefaultHomePage.tsx +15 -11
  110. package/src/components/HomePage/NavigationCard.tsx +69 -0
  111. package/src/components/HomePage/NavigationCardBinding.tsx +116 -0
  112. package/src/components/HomePage/SmallNavigationCard.tsx +45 -0
  113. package/src/components/HomePage/index.tsx +3 -1
  114. package/src/components/PropertyIdCopyTooltipContent.tsx +2 -3
  115. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +4 -4
  116. package/src/components/ReferenceWidget.tsx +5 -5
  117. package/src/components/SearchIconsView.tsx +4 -4
  118. package/src/components/SelectableTable/SelectableTable.tsx +1 -1
  119. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  120. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +23 -8
  121. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +35 -24
  122. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
  123. package/src/components/VirtualTable/VirtualTable.tsx +28 -20
  124. package/src/components/VirtualTable/VirtualTableProps.tsx +1 -1
  125. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
  126. package/src/components/common/types.tsx +4 -6
  127. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +12 -1
  128. package/src/components/index.tsx +4 -2
  129. package/src/contexts/AuthControllerContext.tsx +1 -1
  130. package/src/core/Drawer.tsx +78 -103
  131. package/src/core/DrawerNavigationItem.tsx +62 -0
  132. package/src/core/{EntityView.tsx → EntityEditView.tsx} +21 -40
  133. package/src/core/EntitySidePanel.tsx +2 -2
  134. package/src/core/FireCMS.tsx +22 -7
  135. package/src/core/NavigationRoutes.tsx +11 -4
  136. package/src/core/Scaffold.tsx +76 -61
  137. package/src/core/field_configs.tsx +1 -2
  138. package/src/core/index.tsx +3 -4
  139. package/src/form/EntityForm.tsx +42 -22
  140. package/src/form/PropertyFieldBinding.tsx +0 -2
  141. package/src/form/components/StorageItemPreview.tsx +5 -3
  142. package/src/form/components/StorageUploadProgress.tsx +7 -6
  143. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +8 -12
  144. package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
  145. package/src/form/field_bindings/KeyValueFieldBinding.tsx +15 -15
  146. package/src/form/field_bindings/MapFieldBinding.tsx +15 -15
  147. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +1 -1
  148. package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -0
  149. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +14 -5
  150. package/src/form/field_bindings/TextFieldBinding.tsx +7 -5
  151. package/src/form/validation.ts +3 -4
  152. package/src/hooks/data/delete.ts +3 -3
  153. package/src/hooks/data/save.ts +2 -2
  154. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  155. package/src/hooks/data/useDataSource.tsx +8 -3
  156. package/src/hooks/data/useEntityFetch.tsx +4 -4
  157. package/src/hooks/index.tsx +3 -0
  158. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +9 -10
  159. package/src/hooks/useBuildModeController.tsx +11 -5
  160. package/src/hooks/useBuildNavigationController.tsx +199 -81
  161. package/src/hooks/useProjectLog.tsx +17 -7
  162. package/src/hooks/useReferenceDialog.tsx +2 -2
  163. package/src/hooks/useStorageSource.tsx +7 -2
  164. package/src/hooks/useValidateAuthenticator.tsx +115 -0
  165. package/src/internal/useBuildDataSource.ts +42 -44
  166. package/src/internal/useBuildSideEntityController.tsx +86 -20
  167. package/src/preview/PropertyPreview.tsx +3 -14
  168. package/src/preview/PropertyPreviewProps.tsx +1 -11
  169. package/src/preview/components/BooleanPreview.tsx +19 -4
  170. package/src/preview/components/EnumValuesChip.tsx +1 -1
  171. package/src/preview/components/ReferencePreview.tsx +55 -147
  172. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +0 -1
  173. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +0 -1
  174. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +0 -1
  175. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +0 -1
  176. package/src/preview/property_previews/ArrayOneOfPreview.tsx +0 -1
  177. package/src/preview/property_previews/ArrayPropertyPreview.tsx +0 -1
  178. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
  179. package/src/types/analytics.ts +1 -0
  180. package/src/types/auth.tsx +50 -1
  181. package/src/types/collections.ts +33 -5
  182. package/src/types/datasource.ts +8 -5
  183. package/src/types/entities.ts +9 -1
  184. package/src/types/entity_actions.tsx +17 -0
  185. package/src/types/entity_callbacks.ts +2 -2
  186. package/src/types/entity_overrides.tsx +7 -0
  187. package/src/types/firecms.tsx +0 -1
  188. package/src/types/index.ts +2 -1
  189. package/src/types/navigation.ts +17 -16
  190. package/src/types/permissions.ts +6 -1
  191. package/src/types/plugins.tsx +26 -28
  192. package/src/types/properties.ts +8 -6
  193. package/src/types/property_config.tsx +2 -2
  194. package/src/types/roles.ts +41 -0
  195. package/src/types/side_entity_controller.tsx +1 -0
  196. package/src/types/storage.ts +12 -3
  197. package/src/types/user.ts +7 -0
  198. package/src/util/collections.ts +22 -0
  199. package/src/util/entities.ts +1 -1
  200. package/src/util/icon_list.ts +2 -2
  201. package/src/util/icon_synonyms.ts +4 -6
  202. package/src/util/icons.tsx +11 -3
  203. package/src/util/navigation_utils.ts +6 -6
  204. package/src/util/objects.ts +0 -14
  205. package/src/util/permissions.ts +11 -8
  206. package/src/util/references.ts +36 -5
  207. package/src/util/resolutions.ts +6 -24
  208. package/src/util/strings.ts +2 -2
  209. package/src/util/useTraceUpdate.tsx +2 -1
  210. package/dist/core/SideEntityView.d.ts +0 -7
  211. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  212. package/dist/internal/useLocaleConfig.d.ts +0 -1
  213. package/dist/types/appcheck.d.ts +0 -26
  214. package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
  215. package/src/core/SideEntityView.tsx +0 -38
  216. package/src/internal/useBuildCustomizationController.tsx +0 -5
  217. package/src/internal/useLocaleConfig.tsx +0 -18
  218. package/src/types/appcheck.ts +0 -29
@@ -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);
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
- }, [authController]);
39
+ }, [authController, pluginKeys]);
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,115 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import equal from "react-fast-compare";
3
+
4
+ import { 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 storageSource
12
+ * @param dataSourceDelegate
13
+ */
14
+ export function useValidateAuthenticator<UserType extends User = User, Controller extends AuthController<UserType> = AuthController<UserType>>({
15
+ disabled,
16
+ authController,
17
+ authenticator,
18
+ storageSource,
19
+ dataSourceDelegate
20
+ }:
21
+ {
22
+ disabled?: boolean,
23
+ authController: Controller,
24
+ authenticator?: boolean | Authenticator<UserType, Controller>,
25
+ dataSourceDelegate: DataSourceDelegate;
26
+ storageSource: StorageSource;
27
+ }): {
28
+ canAccessMainView: boolean,
29
+ authLoading: boolean,
30
+ notAllowedError: any,
31
+ authVerified: boolean,
32
+ } {
33
+
34
+ const authenticationEnabled = Boolean(authenticator);
35
+
36
+ const [authLoading, setAuthLoading] = useState<boolean>(authenticationEnabled);
37
+ const [notAllowedError, setNotAllowedError] = useState<any>(false);
38
+ const [authVerified, setAuthVerified] = useState<boolean>(!authenticationEnabled || Boolean(authController.loginSkipped));
39
+
40
+ const canAccessMainView = (authVerified) &&
41
+ (!authenticationEnabled || Boolean(authController.user) || Boolean(authController.loginSkipped)) &&
42
+ !notAllowedError;
43
+
44
+ useEffect(() => {
45
+ if (authController.loginSkipped)
46
+ setAuthVerified(true);
47
+ }, [authController.loginSkipped]);
48
+
49
+ /**
50
+ * We use this ref to check the authentication only if the user has
51
+ * changed.
52
+ */
53
+ const checkedUserRef = useRef<User | undefined>();
54
+
55
+ const checkAuthentication = useCallback(async () => {
56
+
57
+ if (disabled) {
58
+ return;
59
+ }
60
+
61
+ if (authController.initialLoading) {
62
+ return;
63
+ }
64
+
65
+ if (!authController.user && !authController.loginSkipped) {
66
+ checkedUserRef.current = undefined;
67
+ setAuthLoading(false);
68
+ setAuthVerified(false);
69
+ return;
70
+ }
71
+
72
+ const delegateUser = authController.user;
73
+ console.debug("Checking authentication for user", delegateUser);
74
+
75
+ if (authenticator instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
76
+ setAuthLoading(true);
77
+ try {
78
+ const allowed = await authenticator({
79
+ user: delegateUser,
80
+ authController,
81
+ dataSourceDelegate,
82
+ storageSource
83
+ });
84
+ if (!allowed) {
85
+ authController.signOut();
86
+ setNotAllowedError(true);
87
+ }
88
+ } catch (e) {
89
+ setNotAllowedError(e);
90
+ authController.signOut();
91
+ }
92
+ setAuthLoading(false);
93
+ setAuthVerified(true);
94
+ checkedUserRef.current = delegateUser;
95
+ } else {
96
+ setAuthLoading(false);
97
+ }
98
+
99
+ if (!authController.initialLoading && !delegateUser) {
100
+ setAuthVerified(true);
101
+ }
102
+
103
+ }, [disabled, authController, authenticator, dataSourceDelegate, storageSource]);
104
+
105
+ useEffect(() => {
106
+ checkAuthentication();
107
+ }, [checkAuthentication]);
108
+
109
+ return {
110
+ canAccessMainView,
111
+ authLoading: authenticationEnabled && authLoading,
112
+ notAllowedError,
113
+ authVerified
114
+ }
115
+ }
@@ -5,12 +5,10 @@ import {
5
5
  DeleteEntityProps,
6
6
  Entity,
7
7
  EntityCollection,
8
- EntityReference,
9
8
  EntityValues,
10
9
  FetchCollectionProps,
11
10
  FetchEntityProps,
12
11
  FilterValues,
13
- GeoPoint,
14
12
  ListenCollectionProps,
15
13
  ListenEntityProps,
16
14
  NavigationController,
@@ -207,13 +205,10 @@ export function useBuildDataSource({
207
205
 
208
206
  const properties: ResolvedProperties<M> | undefined = resolvedCollection?.properties;
209
207
 
210
- const firestoreValues = cmsToDelegateModel(
208
+ const firestoreValues = delegate.cmsToDelegateModel(
211
209
  values,
212
- delegate.buildReference,
213
- delegate.buildGeoPoint,
214
- delegate.buildDate,
215
- delegate.buildDeleteFieldValue
216
210
  );
211
+
217
212
  const updatedFirestoreValues: EntityValues<M> = properties
218
213
  ? updateDateAutoValues(
219
214
  {
@@ -316,45 +311,48 @@ export function useBuildDataSource({
316
311
  sortBy
317
312
  }
318
313
  )
319
- }, [delegate.isFilterCombinationValid])
314
+ }, [delegate.isFilterCombinationValid]),
320
315
 
321
316
  };
322
317
 
323
318
  }
324
319
 
325
- /**
326
- * Recursive function that converts Firestore data types into CMS or plain
327
- * JS types.
328
- * FireCMS uses Javascript dates internally instead of Firestore timestamps.
329
- * This makes it easier to interact with the rest of the libraries and
330
- * bindings.
331
- * Also, Firestore references are replaced with {@link EntityReference}
332
- * @param data
333
- * @group Firestore
334
- */
335
- export function cmsToDelegateModel(data: any,
336
- buildReference: (reference: EntityReference) => any,
337
- buildGeoPoint: (geoPoint: GeoPoint) => any,
338
- buildDate: (date: Date) => any,
339
- buildDelete: () => any
340
- ): any {
341
- if (data === undefined) {
342
- return buildDelete();
343
- } else if (data === null) {
344
- return null;
345
- } else if (Array.isArray(data)) {
346
- return data.map(v => cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete));
347
- } else if (data.isEntityReference && data.isEntityReference()) {
348
- return buildReference(data);
349
- } else if (data instanceof GeoPoint) {
350
- return buildGeoPoint(data);
351
- } else if (data instanceof Date) {
352
- return buildDate(data);
353
- } else if (data && typeof data === "object") {
354
- return Object.entries(data)
355
- .map(([key, v]) => ({ [key]: cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete) }))
356
- .reduce((a, b) => ({ ...a, ...b }), {});
357
- }
358
- return data;
359
- }
360
-
320
+ // /**
321
+ // * Recursive function that converts Firestore data types into CMS or plain
322
+ // * JS types.
323
+ // * FireCMS uses Javascript dates internally instead of Firestore timestamps.
324
+ // * This makes it easier to interact with the rest of the libraries and
325
+ // * bindings.
326
+ // * Also, Firestore references are replaced with {@link EntityReference}
327
+ // * @param data
328
+ // * @param buildReference
329
+ // * @param buildGeoPoint
330
+ // * @param buildDate
331
+ // * @param buildDelete
332
+ // * @group Firestore
333
+ // */
334
+ // export function cmsToDelegateModel(data: any,
335
+ // buildReference: (reference: EntityReference) => any,
336
+ // buildGeoPoint: (geoPoint: GeoPoint) => any,
337
+ // buildDate: (date: Date) => any,
338
+ // buildDelete: () => any
339
+ // ): any {
340
+ // if (data === undefined) {
341
+ // return buildDelete();
342
+ // } else if (data === null) {
343
+ // return null;
344
+ // } else if (Array.isArray(data)) {
345
+ // return data.map(v => cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete));
346
+ // } else if (data.isEntityReference && data.isEntityReference()) {
347
+ // return buildReference(data);
348
+ // } else if (data instanceof GeoPoint) {
349
+ // return buildGeoPoint(data);
350
+ // } else if (data instanceof Date) {
351
+ // return buildDate(data);
352
+ // } else if (data && typeof data === "object") {
353
+ // return Object.entries(data)
354
+ // .map(([key, v]) => ({ [key]: cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete) }))
355
+ // .reduce((a, b) => ({ ...a, ...b }), {});
356
+ // }
357
+ // return data;
358
+ // }
@@ -3,13 +3,14 @@ import {
3
3
  EntityCollection,
4
4
  EntitySidePanelProps,
5
5
  NavigationController,
6
+ ResolvedProperty,
6
7
  SideDialogPanelProps,
7
8
  SideDialogsController,
8
9
  SideEntityController
9
10
  } from "../types";
10
11
  import { getNavigationEntriesFromPathInternal, NavigationViewInternal } from "../util/navigation_from_path";
11
12
  import { useLocation } from "react-router-dom";
12
- import { removeInitialAndTrailingSlashes, resolveDefaultSelectedView } from "../util";
13
+ import { removeInitialAndTrailingSlashes, resolveCollection, resolveDefaultSelectedView } from "../util";
13
14
  import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, FORM_CONTAINER_WIDTH } from "./common";
14
15
  import { useLargeLayout } from "../hooks";
15
16
  import { EntitySidePanel } from "../core/EntitySidePanel";
@@ -19,8 +20,66 @@ const NEW_URL_HASH = "new";
19
20
  export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: boolean): string {
20
21
  if (small) return CONTAINER_FULL_WIDTH;
21
22
  const mainViewSelected = !props.selectedSubPath;
22
- const resolvedWidth: string | undefined = typeof props.width === "number" ? `${props.width}px` : props.width;
23
- return !mainViewSelected ? `calc(${ADDITIONAL_TAB_WIDTH} + ${resolvedWidth ?? FORM_CONTAINER_WIDTH})` : resolvedWidth ?? FORM_CONTAINER_WIDTH
23
+ let resolvedWidth: string | undefined;
24
+ if (props.width) {
25
+ resolvedWidth = typeof props.width === "number" ? `${props.width}px` : props.width;
26
+ } else if (props.collection?.sideDialogWidth) {
27
+ resolvedWidth = typeof props.collection.sideDialogWidth === "number" ? `${props.collection.sideDialogWidth}px` : props.collection.sideDialogWidth;
28
+ }
29
+
30
+ if (!mainViewSelected) {
31
+ return `calc(${ADDITIONAL_TAB_WIDTH} + ${resolvedWidth ?? FORM_CONTAINER_WIDTH})`
32
+ } else {
33
+ if (resolvedWidth) {
34
+ return resolvedWidth
35
+ } else if (!props.collection) {
36
+ return FORM_CONTAINER_WIDTH;
37
+ } else {
38
+ return calculateCollectionDesiredWidth(props.collection);
39
+ }
40
+ }
41
+ }
42
+
43
+ const collectionViewWidthCache: { [key: string]: string } = {};
44
+
45
+ function calculateCollectionDesiredWidth(collection: EntityCollection<any>): string {
46
+ if (collectionViewWidthCache[collection.id]) {
47
+ return collectionViewWidthCache[collection.id];
48
+ }
49
+ const resolvedCollection = resolveCollection({
50
+ collection,
51
+ path: "__ignored"
52
+ });
53
+
54
+ let result = FORM_CONTAINER_WIDTH
55
+ if (resolvedCollection?.properties) {
56
+ const values = Object.values(resolvedCollection.properties).map((p: ResolvedProperty) => getNestedPropertiesDepth(p));
57
+ const maxDepth = Math.max(...values);
58
+ if (maxDepth < 3) {
59
+ result = FORM_CONTAINER_WIDTH;
60
+ } else {
61
+ result = 768 + 32 * (maxDepth - 2) + "px";
62
+ }
63
+ }
64
+ collectionViewWidthCache[collection.id] = result;
65
+ return result;
66
+ }
67
+
68
+ function getNestedPropertiesDepth(property: ResolvedProperty, accumulator: number = 0): number {
69
+ if (property.dataType === "map" && property.properties) {
70
+ const values = Object.values(property.properties).flatMap((property) => getNestedPropertiesDepth(property, accumulator + 1));
71
+ return Math.max(...values);
72
+ } else if (property.dataType === "array" && property.oneOf) {
73
+ return accumulator + 3;
74
+ } else if (property.dataType === "array" && property.of) {
75
+ if (Array.isArray(property.of)) {
76
+ return Math.max(...property.of.map((p) => getNestedPropertiesDepth(p, accumulator + 1)));
77
+ } else {
78
+ return getNestedPropertiesDepth(property.of, accumulator + 1);
79
+ }
80
+ } else {
81
+ return accumulator + 1;
82
+ }
24
83
  }
25
84
 
26
85
  export const useBuildSideEntityController = (navigation: NavigationController,
@@ -34,23 +93,26 @@ export const useBuildSideEntityController = (navigation: NavigationController,
34
93
  // only on initialisation, create panels from URL
35
94
  useEffect(() => {
36
95
  if (!navigation.loading && !initialised.current) {
96
+ console.debug("Initialising side entity controller");
37
97
  if (navigation.isUrlCollectionPath(location.pathname)) {
38
98
  const newFlag = location.hash === `#${NEW_URL_HASH}`;
39
99
  const entityOrCollectionPath = navigation.urlPathToDataPath(location.pathname);
40
100
  const panelsFromUrl = buildSidePanelsFromUrl(entityOrCollectionPath, navigation.collections ?? [], newFlag);
41
101
  for (let i = 0; i < panelsFromUrl.length; i++) {
42
- const panel = panelsFromUrl[i];
102
+ const props = panelsFromUrl[i];
43
103
  setTimeout(() => {
44
104
  if (i === 0)
45
- sideDialogsController.replace(propsToSidePanel(panel, navigation, smallLayout));
105
+ sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout));
46
106
  else
47
- sideDialogsController.open(propsToSidePanel(panel, navigation, smallLayout))
107
+ sideDialogsController.open(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout))
48
108
  }, 1);
49
109
  }
110
+ } else {
111
+ // console.warn("Location path is not a collection path");
50
112
  }
51
113
  initialised.current = true;
52
114
  }
53
- }, [location, navigation, sideDialogsController, smallLayout]);
115
+ }, [location, navigation.loading, navigation.isUrlCollectionPath, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, sideDialogsController, smallLayout, navigation]);
54
116
 
55
117
  const close = useCallback(() => {
56
118
  sideDialogsController.close();
@@ -73,9 +135,9 @@ export const useBuildSideEntityController = (navigation: NavigationController,
73
135
  sideDialogsController.open(propsToSidePanel({
74
136
  selectedSubPath: defaultSelectedView,
75
137
  ...props,
76
- }, navigation, smallLayout));
138
+ }, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout));
77
139
 
78
- }, [sideDialogsController, navigation, smallLayout]);
140
+ }, [sideDialogsController, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout]);
79
141
 
80
142
  const replace = useCallback((props: EntitySidePanelProps<any>) => {
81
143
 
@@ -83,9 +145,9 @@ export const useBuildSideEntityController = (navigation: NavigationController,
83
145
  throw Error("If you want to copy an entity you need to provide an entityId");
84
146
  }
85
147
 
86
- sideDialogsController.replace(propsToSidePanel(props, navigation, smallLayout));
148
+ sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout));
87
149
 
88
- }, [navigation, sideDialogsController, smallLayout]);
150
+ }, [navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, sideDialogsController, smallLayout]);
89
151
 
90
152
  return {
91
153
  close,
@@ -116,7 +178,8 @@ export function buildSidePanelsFromUrl(path: string, collections: EntityCollecti
116
178
  sidePanels.push({
117
179
  path: navigationEntry.path,
118
180
  entityId: navigationEntry.entityId,
119
- copy: false
181
+ copy: false,
182
+ width: navigationEntry.parentCollection?.sideDialogWidth
120
183
  }
121
184
  );
122
185
  } else if (navigationEntry.type === "custom_view") {
@@ -146,27 +209,30 @@ export function buildSidePanelsFromUrl(path: string, collections: EntityCollecti
146
209
  return sidePanels;
147
210
  }
148
211
 
149
- const propsToSidePanel = (props: EntitySidePanelProps<any>, navigation: NavigationController, smallLayout: boolean): SideDialogPanelProps => {
212
+ const propsToSidePanel = (props: EntitySidePanelProps<any>,
213
+ buildUrlCollectionPath: (path: string) => string,
214
+ resolveAliasesFrom: (pathWithAliases: string) => string,
215
+ smallLayout: boolean): SideDialogPanelProps => {
150
216
 
151
217
  const collectionPath = removeInitialAndTrailingSlashes(props.path);
152
218
 
153
219
  const newPath = props.entityId
154
- ? navigation.buildUrlCollectionPath(`${collectionPath}/${props.entityId}/${props.selectedSubPath || ""}`)
155
- : navigation.buildUrlCollectionPath(`${collectionPath}#${NEW_URL_HASH}`);
156
- const resolvedPath = navigation.resolveAliasesFrom(props.path);
220
+ ? buildUrlCollectionPath(`${collectionPath}/${props.entityId}/${props.selectedSubPath || ""}`)
221
+ : buildUrlCollectionPath(`${collectionPath}#${NEW_URL_HASH}`);
222
+ const resolvedPath = resolveAliasesFrom(props.path);
157
223
 
158
224
  const resolvedPanelProps: EntitySidePanelProps<any> = {
159
225
  ...props,
160
- path: resolvedPath
226
+ path: resolvedPath,
161
227
  };
162
228
 
163
- return ({
229
+ return {
164
230
  key: `${props.path}/${props.entityId}`,
165
231
  component: <EntitySidePanel {...resolvedPanelProps}/>,
166
232
  urlPath: newPath,
167
- parentUrlPath: navigation.buildUrlCollectionPath(collectionPath),
233
+ parentUrlPath: buildUrlCollectionPath(collectionPath),
168
234
  width: getEntityViewWidth(props, smallLayout),
169
235
  onClose: props.onClose
170
- });
236
+ };
171
237
  }
172
238
  ;
@@ -53,7 +53,6 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
53
53
  const property = resolveProperty({
54
54
  propertyKey,
55
55
  propertyOrBuilder: inputProperty,
56
- propertyValue: value,
57
56
  fields: customizationController.propertyConfigs
58
57
  });
59
58
 
@@ -92,7 +91,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
92
91
  size={props.size}
93
92
  storagePathOrDownloadUrl={value}/>;
94
93
  } else if (stringProperty.markdown) {
95
- content = <Markdown source={value}/>;
94
+ content = <Markdown source={value} size={"small"}/>;
96
95
  } else {
97
96
  content = <StringPropertyPreview {...props}
98
97
  property={stringProperty}
@@ -113,17 +112,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
113
112
  content = <ArrayPropertyPreview {...props}
114
113
  value={value}
115
114
  property={property as ResolvedArrayProperty}/>;
116
- }
117
- // else if (arrayProperty.of.dataType === "map") {
118
- // content =
119
- // <ArrayOfMapsPreview propertyKey={propertyKey}
120
- // property={property as ResolvedArrayProperty}
121
- // value={value as Record<string, any>[]} // This might be wrong
122
- // entity={entity}
123
- // size={size}
124
- // />;
125
- // }
126
- else if (arrayProperty.of.dataType === "reference") {
115
+ } else if (arrayProperty.of.dataType === "reference") {
127
116
  content = <ArrayOfReferencesPreview {...props}
128
117
  value={value}
129
118
  property={property as ResolvedArrayProperty}/>;
@@ -195,7 +184,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
195
184
 
196
185
  } else if (property.dataType === "boolean") {
197
186
  if (typeof value === "boolean") {
198
- content = <BooleanPreview value={value}/>;
187
+ content = <BooleanPreview value={value} size={size} property={property}/>;
199
188
  } else {
200
189
  content = buildWrongValueType(propertyKey, property.dataType, value);
201
190
  }
@@ -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
  }
@@ -1,11 +1,26 @@
1
1
  import React from "react";
2
- import { Checkbox } from "@firecms/ui";
2
+ import { Checkbox, cn } from "@firecms/ui";
3
+ import { PreviewSize } from "../PropertyPreviewProps";
4
+ import { Property } from "../../types";
3
5
 
4
6
  /**
5
7
  * @group Preview components
6
8
  */
7
- export function BooleanPreview({ value }: {
8
- value: boolean
9
+ export function BooleanPreview({
10
+ value,
11
+ size,
12
+ property
13
+ }: {
14
+ value: boolean,
15
+ size: PreviewSize,
16
+ property: Property,
9
17
  }): React.ReactElement {
10
- return <Checkbox checked={value} color={"secondary"}/>;
18
+ return <div className={"flex flex-row gap-2 items-center"}>
19
+ <Checkbox checked={value}
20
+ padding={false}
21
+ size={size}
22
+ color={"secondary"}/>
23
+ {property.name && <span
24
+ className={cn("text-text-secondary dark:text-text-secondary-dark", size === "tiny" ? "text-sm" : "")}>{property.name}</span>}
25
+ </div>;
11
26
  }
@@ -6,7 +6,7 @@ import { Chip } from "@firecms/ui";
6
6
  export interface EnumValuesChipProps {
7
7
  enumValues?: EnumValues;
8
8
  enumKey: string | number;
9
- size: "small" | "medium";
9
+ size: "tiny" | "small" | "medium";
10
10
  className?: string;
11
11
  children?: React.ReactNode;
12
12
  }