@firecms/core 3.0.0-canary.9 → 3.0.0-canary.91

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 (247) hide show
  1. package/README.md +1 -1
  2. package/dist/app/AppBar.d.ts +12 -0
  3. package/dist/app/Drawer.d.ts +17 -0
  4. package/dist/app/Scaffold.d.ts +30 -0
  5. package/dist/app/index.d.ts +4 -0
  6. package/dist/app/useApp.d.ts +16 -0
  7. package/dist/components/CircularProgressCenter.d.ts +1 -1
  8. package/dist/components/ClearFilterSortButton.d.ts +5 -0
  9. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +11 -12
  10. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  11. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +5 -3
  12. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +1 -0
  13. package/dist/components/EntityCollectionTable/column_utils.d.ts +1 -2
  14. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +2 -0
  15. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
  16. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +2 -2
  17. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +1 -1
  18. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +12 -2
  19. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
  20. package/dist/components/EntityPreview.d.ts +5 -4
  21. package/dist/components/ErrorView.d.ts +1 -1
  22. package/dist/components/HomePage/DefaultHomePage.d.ts +1 -1
  23. package/dist/components/HomePage/NavigationCardBinding.d.ts +1 -1
  24. package/dist/components/ReferenceWidget.d.ts +3 -1
  25. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  26. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
  27. package/dist/components/VirtualTable/VirtualTableProps.d.ts +15 -12
  28. package/dist/components/VirtualTable/types.d.ts +3 -3
  29. package/dist/components/{EntityCollectionTable/internal → common}/default_entity_actions.d.ts +1 -1
  30. package/dist/components/common/index.d.ts +1 -0
  31. package/dist/components/common/table_height.d.ts +5 -0
  32. package/dist/components/common/types.d.ts +4 -6
  33. package/dist/components/common/useColumnsIds.d.ts +2 -1
  34. package/dist/components/common/useDataSourceEntityCollectionTableController.d.ts +3 -0
  35. package/dist/components/index.d.ts +2 -1
  36. package/dist/contexts/AuthControllerContext.d.ts +1 -1
  37. package/dist/{components/FireCMSAppBar.d.ts → core/DefaultAppBar.d.ts} +6 -9
  38. package/dist/core/DefaultDrawer.d.ts +19 -0
  39. package/dist/core/DrawerNavigationItem.d.ts +9 -0
  40. package/dist/core/EntityEditView.d.ts +17 -3
  41. package/dist/core/FireCMS.d.ts +1 -1
  42. package/dist/core/NavigationRoutes.d.ts +3 -3
  43. package/dist/core/index.d.ts +3 -4
  44. package/dist/form/PropertiesForm.d.ts +8 -0
  45. package/dist/form/components/ErrorFocus.d.ts +1 -1
  46. package/dist/form/components/FieldHelperText.d.ts +3 -3
  47. package/dist/form/components/StorageItemPreview.d.ts +4 -4
  48. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  49. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +2 -4
  50. package/dist/form/index.d.ts +0 -2
  51. package/dist/hooks/data/delete.d.ts +2 -2
  52. package/dist/hooks/data/save.d.ts +2 -3
  53. package/dist/hooks/data/useDataSource.d.ts +1 -1
  54. package/dist/hooks/data/useEntityFetch.d.ts +3 -3
  55. package/dist/hooks/index.d.ts +2 -0
  56. package/dist/hooks/useBuildNavigationController.d.ts +1 -2
  57. package/dist/hooks/useProjectLog.d.ts +2 -2
  58. package/dist/hooks/useValidateAuthenticator.d.ts +21 -0
  59. package/dist/index.d.ts +1 -0
  60. package/dist/index.es.js +15825 -12165
  61. package/dist/index.es.js.map +1 -1
  62. package/dist/index.umd.js +19684 -7
  63. package/dist/index.umd.js.map +1 -1
  64. package/dist/internal/useBuildDataSource.d.ts +1 -16
  65. package/dist/internal/useRestoreScroll.d.ts +1 -1
  66. package/dist/preview/PropertyPreviewProps.d.ts +6 -4
  67. package/dist/preview/components/ReferencePreview.d.ts +2 -1
  68. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  69. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  70. package/dist/types/auth.d.ts +26 -2
  71. package/dist/types/collections.d.ts +31 -7
  72. package/dist/types/datasource.d.ts +34 -20
  73. package/dist/types/entities.d.ts +5 -1
  74. package/dist/types/entity_actions.d.ts +14 -0
  75. package/dist/types/entity_callbacks.d.ts +2 -2
  76. package/dist/types/fields.d.ts +31 -30
  77. package/dist/types/index.d.ts +0 -1
  78. package/dist/types/navigation.d.ts +5 -5
  79. package/dist/types/plugins.d.ts +16 -6
  80. package/dist/types/properties.d.ts +17 -4
  81. package/dist/types/storage.d.ts +11 -3
  82. package/dist/util/collections.d.ts +1 -1
  83. package/dist/util/entities.d.ts +1 -1
  84. package/dist/util/icon_synonyms.d.ts +1 -97
  85. package/dist/util/icons.d.ts +2 -2
  86. package/dist/util/navigation_utils.d.ts +2 -2
  87. package/dist/util/objects.d.ts +1 -1
  88. package/dist/util/plurals.d.ts +0 -2
  89. package/dist/util/resolutions.d.ts +13 -13
  90. package/dist/util/storage.d.ts +23 -2
  91. package/dist/util/useStorageUploadController.d.ts +1 -1
  92. package/dist/util/useTraceUpdate.d.ts +1 -0
  93. package/package.json +130 -119
  94. package/src/app/AppBar.tsx +18 -0
  95. package/src/app/Drawer.tsx +25 -0
  96. package/src/app/Scaffold.tsx +249 -0
  97. package/src/app/index.ts +4 -0
  98. package/src/app/useApp.tsx +32 -0
  99. package/src/components/CircularProgressCenter.tsx +1 -1
  100. package/src/components/ClearFilterSortButton.tsx +41 -0
  101. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +9 -18
  102. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +21 -20
  103. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +10 -6
  104. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +38 -34
  105. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  106. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +11 -2
  107. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +14 -6
  108. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +29 -34
  109. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +16 -12
  110. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +4 -5
  111. package/src/components/EntityCollectionView/EntityCollectionView.tsx +69 -45
  112. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  113. package/src/components/EntityCollectionView/useSelectionController.tsx +19 -7
  114. package/src/components/EntityPreview.tsx +15 -9
  115. package/src/components/EntityView.tsx +5 -5
  116. package/src/components/ErrorView.tsx +1 -1
  117. package/src/components/HomePage/DefaultHomePage.tsx +3 -3
  118. package/src/components/HomePage/NavigationCard.tsx +3 -3
  119. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  120. package/src/components/HomePage/SmallNavigationCard.tsx +5 -5
  121. package/src/components/PropertyIdCopyTooltipContent.tsx +2 -3
  122. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +1 -0
  123. package/src/components/ReferenceWidget.tsx +22 -12
  124. package/src/components/SearchIconsView.tsx +5 -5
  125. package/src/components/SelectableTable/SelectableTable.tsx +7 -7
  126. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  127. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +22 -7
  128. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +28 -6
  129. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
  130. package/src/components/VirtualTable/VirtualTable.tsx +70 -37
  131. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -1
  132. package/src/components/VirtualTable/VirtualTableHeader.tsx +4 -4
  133. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
  134. package/src/components/VirtualTable/VirtualTableProps.tsx +18 -14
  135. package/src/components/VirtualTable/VirtualTableRow.tsx +4 -5
  136. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
  137. package/src/components/VirtualTable/types.tsx +2 -3
  138. package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +2 -2
  139. package/src/components/common/index.ts +1 -0
  140. package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
  141. package/src/components/common/types.tsx +4 -6
  142. package/src/components/common/useColumnsIds.tsx +23 -2
  143. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +11 -0
  144. package/src/components/common/useTableSearchHelper.ts +52 -12
  145. package/src/components/index.tsx +2 -1
  146. package/src/contexts/AuthControllerContext.tsx +1 -1
  147. package/src/contexts/DialogsProvider.tsx +2 -2
  148. package/src/{components/FireCMSAppBar.tsx → core/DefaultAppBar.tsx} +52 -37
  149. package/src/core/DefaultDrawer.tsx +177 -0
  150. package/src/core/DrawerNavigationItem.tsx +62 -0
  151. package/src/core/EntityEditView.tsx +711 -139
  152. package/src/core/EntitySidePanel.tsx +1 -2
  153. package/src/core/FireCMS.tsx +39 -44
  154. package/src/core/NavigationRoutes.tsx +7 -8
  155. package/src/core/field_configs.tsx +2 -3
  156. package/src/core/index.tsx +3 -4
  157. package/src/form/PropertiesForm.tsx +81 -0
  158. package/src/form/PropertyFieldBinding.tsx +29 -7
  159. package/src/form/components/FieldHelperText.tsx +3 -3
  160. package/src/form/components/StorageItemPreview.tsx +20 -11
  161. package/src/form/components/StorageUploadProgress.tsx +3 -3
  162. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +8 -5
  163. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +8 -5
  164. package/src/form/field_bindings/BlockFieldBinding.tsx +2 -2
  165. package/src/form/field_bindings/KeyValueFieldBinding.tsx +44 -39
  166. package/src/form/field_bindings/MapFieldBinding.tsx +11 -3
  167. package/src/form/field_bindings/MarkdownFieldBinding.tsx +2 -2
  168. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -9
  169. package/src/form/field_bindings/ReferenceFieldBinding.tsx +15 -13
  170. package/src/form/field_bindings/RepeatFieldBinding.tsx +10 -7
  171. package/src/form/field_bindings/SelectFieldBinding.tsx +3 -3
  172. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +22 -43
  173. package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -1
  174. package/src/form/index.tsx +4 -4
  175. package/src/form/validation.ts +1 -17
  176. package/src/hooks/data/delete.ts +3 -3
  177. package/src/hooks/data/save.ts +4 -2
  178. package/src/hooks/data/useDataSource.tsx +2 -2
  179. package/src/hooks/data/useEntityFetch.tsx +3 -3
  180. package/src/hooks/index.tsx +3 -0
  181. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +8 -10
  182. package/src/hooks/useBuildModeController.tsx +11 -5
  183. package/src/hooks/useBuildNavigationController.tsx +137 -61
  184. package/src/hooks/useProjectLog.tsx +21 -8
  185. package/src/hooks/useResolvedNavigationFrom.tsx +1 -1
  186. package/src/hooks/useValidateAuthenticator.tsx +115 -0
  187. package/src/index.ts +1 -0
  188. package/src/internal/useBuildDataSource.ts +56 -49
  189. package/src/internal/useBuildSideEntityController.tsx +88 -21
  190. package/src/preview/PropertyPreview.tsx +9 -16
  191. package/src/preview/PropertyPreviewProps.tsx +4 -8
  192. package/src/preview/components/BooleanPreview.tsx +4 -2
  193. package/src/preview/components/EnumValuesChip.tsx +1 -1
  194. package/src/preview/components/ImagePreview.tsx +21 -33
  195. package/src/preview/components/ReferencePreview.tsx +23 -23
  196. package/src/preview/components/StorageThumbnail.tsx +5 -1
  197. package/src/preview/components/UrlComponentPreview.tsx +44 -11
  198. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +0 -1
  199. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +2 -1
  200. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +0 -1
  201. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +0 -1
  202. package/src/preview/property_previews/ArrayOneOfPreview.tsx +2 -3
  203. package/src/preview/property_previews/ArrayPropertyPreview.tsx +2 -3
  204. package/src/preview/property_previews/MapPropertyPreview.tsx +5 -5
  205. package/src/preview/property_previews/StringPropertyPreview.tsx +2 -2
  206. package/src/types/auth.tsx +35 -2
  207. package/src/types/collections.ts +37 -8
  208. package/src/types/customization_controller.tsx +0 -1
  209. package/src/types/datasource.ts +41 -24
  210. package/src/types/entities.ts +9 -1
  211. package/src/types/entity_actions.tsx +16 -3
  212. package/src/types/entity_callbacks.ts +2 -2
  213. package/src/types/fields.tsx +33 -33
  214. package/src/types/index.ts +0 -1
  215. package/src/types/navigation.ts +6 -7
  216. package/src/types/plugins.tsx +18 -8
  217. package/src/types/properties.ts +22 -6
  218. package/src/types/storage.ts +12 -3
  219. package/src/util/collections.ts +1 -1
  220. package/src/util/entities.ts +5 -4
  221. package/src/util/enums.ts +1 -1
  222. package/src/util/icon_list.ts +2 -2
  223. package/src/util/icon_synonyms.ts +3 -99
  224. package/src/util/navigation_utils.ts +6 -6
  225. package/src/util/objects.ts +25 -28
  226. package/src/util/permissions.ts +1 -0
  227. package/src/util/plurals.ts +0 -2
  228. package/src/util/resolutions.ts +32 -31
  229. package/src/util/storage.ts +75 -21
  230. package/src/util/strings.ts +2 -2
  231. package/src/util/useStorageUploadController.tsx +21 -3
  232. package/src/util/useTraceUpdate.tsx +2 -1
  233. package/dist/components/VirtualTable/common.d.ts +0 -2
  234. package/dist/core/Drawer.d.ts +0 -23
  235. package/dist/core/Scaffold.d.ts +0 -55
  236. package/dist/core/SideEntityView.d.ts +0 -7
  237. package/dist/form/EntityForm.d.ts +0 -77
  238. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  239. package/dist/internal/useLocaleConfig.d.ts +0 -1
  240. package/dist/types/appcheck.d.ts +0 -26
  241. package/src/core/Drawer.tsx +0 -191
  242. package/src/core/Scaffold.tsx +0 -281
  243. package/src/core/SideEntityView.tsx +0 -38
  244. package/src/form/EntityForm.tsx +0 -728
  245. package/src/internal/useBuildCustomizationController.tsx +0 -5
  246. package/src/internal/useLocaleConfig.tsx +0 -18
  247. package/src/types/appcheck.ts +0 -29
@@ -112,23 +112,7 @@ export function getYupMapObjectSchema({
112
112
  if (validation?.required) {
113
113
  return shape.required(validation?.requiredMessage ? validation.requiredMessage : "Required").nullable(true);
114
114
  }
115
- return shape.nullable(true);
116
- // const object: ObjectSchema<any> = yup.object().shape(objectSchema);
117
- // return validation?.required
118
- // ? object.required(validation?.requiredMessage ? validation.requiredMessage : "Required").nullable(true)
119
- // : yup.object().optional().default(undefined).notRequired().nullable(true).test(
120
- // "empty-check",
121
- // "Optional map can be empty",
122
- // (o: any, testContext: any) => {
123
- // try {
124
- // if (!o || Object.keys(o).length === 0) return true;
125
- // return object.validateSync(o);
126
- // } catch (e) {
127
- // testContext.createError(e);
128
- // console.error(e);
129
- // return false;
130
- // }
131
- // });
115
+ return yup.object().shape(shape.fields).default(undefined).notRequired().nullable(true);
132
116
  }
133
117
 
134
118
  function getYupStringSchema({
@@ -12,10 +12,10 @@ import {
12
12
  /**
13
13
  * @group Hooks and utilities
14
14
  */
15
- export type DeleteEntityWithCallbacksProps<M extends Record<string, any>> =
15
+ export type DeleteEntityWithCallbacksProps<M extends Record<string, any>, UserType extends User = User> =
16
16
  DeleteEntityProps<M>
17
17
  & {
18
- callbacks?: EntityCallbacks<M>;
18
+ callbacks?: EntityCallbacks<M, UserType>;
19
19
  onDeleteSuccess?: (entity: Entity<M>) => void;
20
20
  onDeleteFailure?: (entity: Entity<M>, e: Error) => void;
21
21
  onPreDeleteHookError?: (entity: Entity<M>, e: Error) => void;
@@ -62,7 +62,7 @@ export async function deleteEntityWithCallbacks<M extends Record<string, any>, U
62
62
 
63
63
  console.debug("Deleting entity", entity.path, entity.id);
64
64
 
65
- const entityDeleteProps: EntityOnDeleteProps<M, UserType> = {
65
+ const entityDeleteProps: EntityOnDeleteProps<M, any> = {
66
66
  entity,
67
67
  collection,
68
68
  entityId: entity.id,
@@ -17,7 +17,6 @@ import { resolveCollection } from "../../util";
17
17
  export type SaveEntityWithCallbacksProps<M extends Record<string, any>> =
18
18
  SaveEntityProps<M> &
19
19
  {
20
- callbacks?: EntityCallbacks<M>;
21
20
  onSaveSuccess?: (updatedEntity: Entity<M>) => void,
22
21
  onSaveFailure?: (e: Error) => void,
23
22
  onPreSaveHookError?: (e: Error) => void,
@@ -64,7 +63,7 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, Use
64
63
  onPreSaveHookError,
65
64
  onSaveSuccessHookError
66
65
  }: SaveEntityWithCallbacksProps<M> & {
67
- collection: EntityCollection<M>,
66
+ collection: EntityCollection<M, UserType>,
68
67
  dataSource: DataSource,
69
68
  context: FireCMSContext<UserType>,
70
69
  }
@@ -109,6 +108,7 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, Use
109
108
  updatedValues = values;
110
109
  }
111
110
 
111
+ console.log("Saving entity", entityId, updatedValues);
112
112
  return dataSource.saveEntity({
113
113
  collection,
114
114
  path: resolvedPath,
@@ -117,6 +117,7 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, Use
117
117
  previousValues,
118
118
  status
119
119
  }).then((entity) => {
120
+ console.log("Entity saved");
120
121
  try {
121
122
  if (callbacks?.onSaveSuccess) {
122
123
  const resolvedCollection = resolveCollection<M>({
@@ -145,6 +146,7 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, Use
145
146
  onSaveSuccess(entity);
146
147
  })
147
148
  .catch((e) => {
149
+ console.error("!!!", e);
148
150
  if (callbacks?.onSaveFailure) {
149
151
 
150
152
  const resolvedCollection = resolveCollection<M>({
@@ -1,4 +1,4 @@
1
- import { useContext, useMemo } from "react";
1
+ import { useContext } from "react";
2
2
  import { DataSource, EntityCollection } from "../../types";
3
3
  import { DataSourceContext } from "../../contexts/DataSourceContext";
4
4
 
@@ -6,7 +6,7 @@ import { DataSourceContext } from "../../contexts/DataSourceContext";
6
6
  * Use this hook to get the datasource being used
7
7
  * @group Hooks and utilities
8
8
  */
9
- export const useDataSource = (collection?: EntityCollection): DataSource => {
9
+ export const useDataSource = (collection?: EntityCollection<any, any>): DataSource => {
10
10
  const defaultDataSource = useContext(DataSourceContext);
11
11
  if (collection?.overrides?.dataSource)
12
12
  return collection?.overrides.dataSource;
@@ -7,10 +7,10 @@ import { useFireCMSContext } from "../useFireCMSContext";
7
7
  /**
8
8
  * @group Hooks and utilities
9
9
  */
10
- export interface EntityFetchProps<M extends Record<string, any>> {
10
+ export interface EntityFetchProps<M extends Record<string, any>, UserType extends User = User> {
11
11
  path: string;
12
12
  entityId?: string;
13
- collection: EntityCollection<M>;
13
+ collection: EntityCollection<M, UserType>;
14
14
  useCache?: boolean;
15
15
  }
16
16
 
@@ -41,7 +41,7 @@ export function useEntityFetch<M extends Record<string, any>, UserType extends U
41
41
  entityId,
42
42
  collection,
43
43
  useCache = false
44
- }: EntityFetchProps<M>): EntityFetchResult<M> {
44
+ }: EntityFetchProps<M, UserType>): EntityFetchResult<M> {
45
45
 
46
46
  const dataSource = useDataSource(collection);
47
47
  const navigationController = useNavigationController();
@@ -10,6 +10,7 @@ export * from "./useResolvedNavigationFrom";
10
10
 
11
11
  export * from "./useStorageSource";
12
12
  export * from "./useAuthController";
13
+ export * from "./useDialogsController";
13
14
  export * from "./useSideDialogsController";
14
15
  export * from "./useSideEntityController";
15
16
  export * from "./useFireCMSContext";
@@ -25,3 +26,5 @@ export * from "./useBuildNavigationController";
25
26
 
26
27
  export * from "./useBuildLocalConfigurationPersistence";
27
28
  export * from "./useBuildModeController";
29
+
30
+ export * from "./useValidateAuthenticator";
@@ -1,10 +1,10 @@
1
- import { useCallback, useEffect, useState } from "react";
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { PartialEntityCollection, UserConfigurationPersistence } from "../types";
3
3
  import { mergeDeep, stripCollectionPath } from "../util";
4
4
 
5
5
  export function useBuildLocalConfigurationPersistence(): UserConfigurationPersistence {
6
6
 
7
- const [configCache, setConfigCache] = useState<Record<string, PartialEntityCollection>>({});
7
+ const configCache = useRef<Record<string, PartialEntityCollection>>({});
8
8
 
9
9
  const getCollectionFromStorage = useCallback((storageKey: string) => {
10
10
  const item = localStorage.getItem(storageKey);
@@ -13,20 +13,18 @@ export function useBuildLocalConfigurationPersistence(): UserConfigurationPersis
13
13
 
14
14
  const getCollectionConfig = useCallback(<M extends Record<string, any>>(path: string): PartialEntityCollection<M> => {
15
15
  const storageKey = `collection_config::${stripCollectionPath(path)}`;
16
- if (configCache[storageKey]) {
17
- return configCache[storageKey] as PartialEntityCollection<M>;
16
+ if (configCache.current[storageKey]) {
17
+ return configCache.current[storageKey] as PartialEntityCollection<M>;
18
18
  }
19
19
  return getCollectionFromStorage(storageKey);
20
- }, [configCache, getCollectionFromStorage]);
20
+ }, [getCollectionFromStorage]);
21
21
 
22
22
  const onCollectionModified = useCallback(<M extends Record<string, any>>(path: string, data: PartialEntityCollection<M>) => {
23
23
  const storageKey = `collection_config::${stripCollectionPath(path)}`;
24
24
  localStorage.setItem(storageKey, JSON.stringify(data));
25
- setConfigCache((currentCache) => {
26
- const cachedConfig = currentCache[storageKey];
27
- const newConfig = mergeDeep(cachedConfig ?? getCollectionFromStorage(path), data);
28
- return (mergeDeep(currentCache, newConfig));
29
- });
25
+ const cachedConfig = configCache.current[storageKey];
26
+ const newConfig = mergeDeep(cachedConfig ?? getCollectionFromStorage(path), data);
27
+ configCache.current[storageKey] = mergeDeep(configCache.current[storageKey], newConfig);
30
28
  }, [getCollectionFromStorage]);
31
29
 
32
30
  const [recentlyVisitedPaths, _setRecentlyVisitedPaths] = useState<string[]>([]);
@@ -8,8 +8,13 @@ import { ModeController } from "./index";
8
8
  */
9
9
  export function useBuildModeController(): ModeController {
10
10
 
11
- const prefersDarkModeQuery = typeof window !== "undefined" &&
12
- window.matchMedia("(prefers-color-scheme: dark)");
11
+ const prefersDarkModeQuery = useCallback((): boolean => {
12
+ if (typeof window === "undefined")
13
+ return false;
14
+ const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
15
+ return mediaQueryList.matches;
16
+ }, []);
17
+
13
18
  const prefersDarkModeStorage: boolean | null = localStorage.getItem("prefers-dark-mode") != null ? localStorage.getItem("prefers-dark-mode") === "true" : null;
14
19
  const prefersDarkMode = prefersDarkModeStorage ?? prefersDarkModeQuery;
15
20
  const [mode, setMode] = useState<"light" | "dark">(prefersDarkMode ? "dark" : "light");
@@ -23,7 +28,7 @@ export function useBuildModeController(): ModeController {
23
28
  const setDarkMode = useCallback(() => {
24
29
  setMode("dark");
25
30
  setDocumentMode("dark");
26
- }, [prefersDarkModeQuery]);
31
+ }, []);
27
32
 
28
33
  const setLightMode = useCallback(() => {
29
34
  setMode("light");
@@ -37,14 +42,15 @@ export function useBuildModeController(): ModeController {
37
42
 
38
43
  const toggleMode = useCallback(() => {
39
44
 
45
+ const prefersDarkModeQueryResult = prefersDarkModeQuery();
40
46
  if (mode === "light") {
41
- if (!prefersDarkModeQuery)
47
+ if (!prefersDarkModeQueryResult)
42
48
  localStorage.setItem("prefers-dark-mode", "true");
43
49
  else
44
50
  localStorage.removeItem("prefers-dark-mode");
45
51
  setDarkMode();
46
52
  } else {
47
- if (prefersDarkModeQuery)
53
+ if (prefersDarkModeQueryResult)
48
54
  localStorage.setItem("prefers-dark-mode", "false");
49
55
  else
50
56
  localStorage.removeItem("prefers-dark-mode");
@@ -29,7 +29,7 @@ import { getParentReferencesFromPath } from "../util/parent_references_from_path
29
29
  const DEFAULT_BASE_PATH = "/";
30
30
  const DEFAULT_COLLECTION_PATH = "/c";
31
31
 
32
- type BuildNavigationContextProps<EC extends EntityCollection, UserType extends User> = {
32
+ export type BuildNavigationContextProps<EC extends EntityCollection, UserType extends User> = {
33
33
  basePath?: string,
34
34
  baseCollectionPath?: string,
35
35
  authController: AuthController<UserType>;
@@ -65,10 +65,10 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
65
65
  injectCollections
66
66
  } = props;
67
67
 
68
- const collectionsRef = useRef<EntityCollection[] | null>();
69
- const [collections, setCollections] = useState<EntityCollection[] | undefined>();
70
- const [views, setViews] = useState<CMSView[] | undefined>();
71
- const [adminViews, setAdminViews] = useState<CMSView[] | undefined>();
68
+ const collectionsRef = useRef<EntityCollection[] | undefined>();
69
+ const viewsRef = useRef<CMSView[] | undefined>();
70
+ const adminViewsRef = useRef<CMSView[] | undefined>();
71
+
72
72
  const [initialised, setInitialised] = useState<boolean>(false);
73
73
 
74
74
  const [topLevelNavigation, setTopLevelNavigation] = useState<TopNavigationResult | undefined>(undefined);
@@ -98,7 +98,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
98
98
  path: collection.id ?? collection.path,
99
99
  collection,
100
100
  description: collection.description?.trim(),
101
- group: collection.group?.trim() ?? "Views"
101
+ group: getGroup(collection)
102
102
  } satisfies TopNavigationEntry)
103
103
  : undefined))
104
104
  .filter(Boolean) as TopNavigationEntry[],
@@ -111,8 +111,8 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
111
111
  path: view.path,
112
112
  view,
113
113
  description: view.description?.trim(),
114
- group: view.group?.trim() ?? "Views"
115
- }satisfies TopNavigationEntry)
114
+ group: getGroup(view)
115
+ } satisfies TopNavigationEntry)
116
116
  : undefined)
117
117
  .filter(Boolean) as TopNavigationEntry[],
118
118
  ...(adminViews ?? []).map(view =>
@@ -130,6 +130,30 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
130
130
  .filter(Boolean) as TopNavigationEntry[]
131
131
  ];
132
132
 
133
+ // Sort by group, entries with group "Admin" will go last, and second to last will be the group "Views"
134
+ navigationEntries = navigationEntries.sort((a, b) => {
135
+ if (a.group !== "Views" && a.group !== "Admin" && (b.group === "Views" || b.group === "Admin")) {
136
+ return -1;
137
+ }
138
+ if (b.group !== "Views" && b.group !== "Admin" && (a.group === "Views" || a.group === "Admin")) {
139
+ return 1;
140
+ }
141
+ if (a.group === "Admin" && b.group !== "Admin") {
142
+ return 1;
143
+ }
144
+ if (a.group !== "Admin" && b.group === "Admin") {
145
+ return -1;
146
+ }
147
+ if (a.group === "Views" && b.group !== "Views") {
148
+ return -1;
149
+ }
150
+ if (a.group !== "Views" && b.group === "Views") {
151
+ return 1;
152
+ }
153
+ return 0;
154
+
155
+ });
156
+
133
157
  if (viewsOrder) {
134
158
  navigationEntries = navigationEntries.sort((a, b) => {
135
159
  const aIndex = viewsOrder.indexOf(a.path);
@@ -163,24 +187,34 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
163
187
  if (authController.initialLoading)
164
188
  return;
165
189
 
190
+ console.debug("Refreshing navigation");
191
+
166
192
  try {
193
+
167
194
  const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
168
195
  resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
169
196
  resolveCMSViews(viewsProp, authController, dataSourceDelegate),
170
197
  resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
171
198
  ]
172
199
  );
173
- if (
174
- !equal(collectionsRef.current, resolvedCollections) ||
175
- !equal(views, resolvedViews) ||
176
- !equal(adminViews, resolvedAdminViews) ||
177
- !equal(topLevelNavigation, computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder))
178
- ) {
200
+
201
+ let shouldUpdateTopLevelNav = false;
202
+ if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
179
203
  collectionsRef.current = resolvedCollections;
180
- setCollections(resolvedCollections);
181
- setViews(resolvedViews);
182
- setAdminViews(resolvedAdminViews);
183
- setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
204
+ shouldUpdateTopLevelNav = true;
205
+ }
206
+ if (!equal(viewsRef.current, resolvedViews)) {
207
+ viewsRef.current = resolvedViews;
208
+ shouldUpdateTopLevelNav = true;
209
+ }
210
+ if (!equal(adminViewsRef.current, resolvedAdminViews)) {
211
+ adminViewsRef.current = resolvedAdminViews;
212
+ shouldUpdateTopLevelNav = true;
213
+ }
214
+
215
+ const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder);
216
+ if (shouldUpdateTopLevelNav && !equal(topLevelNavigation, computedTopLevelNav)) {
217
+ setTopLevelNavigation(computedTopLevelNav);
184
218
  }
185
219
  } catch (e) {
186
220
  console.error(e);
@@ -189,26 +223,34 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
189
223
 
190
224
  setNavigationLoading(false);
191
225
  setInitialised(true);
192
- }, [collectionsProp, collectionPermissions, authController.user, authController.initialLoading, viewsProp, adminViewsProp, computeTopNavigation, injectCollections]);
226
+
227
+ }, [
228
+ collectionsProp,
229
+ collectionPermissions,
230
+ authController.user,
231
+ authController.initialLoading,
232
+ viewsProp,
233
+ adminViewsProp,
234
+ computeTopNavigation,
235
+ injectCollections
236
+ ]);
193
237
 
194
238
  useEffect(() => {
195
239
  refreshNavigation();
196
240
  }, [refreshNavigation]);
197
241
 
198
- const getCollection = useCallback(<EC extends EntityCollection>(
242
+ const getCollection = useCallback((
199
243
  idOrPath: string,
200
- entityId?: string,
201
244
  includeUserOverride = false
202
245
  ): EC | undefined => {
203
-
246
+ const collections = collectionsRef.current;
204
247
  if (!collections)
205
248
  return undefined;
206
249
 
207
250
  const baseCollection = getCollectionByPathOrId(removeInitialAndTrailingSlashes(idOrPath), collections);
208
251
 
209
252
  const userOverride = includeUserOverride ? userConfigPersistence?.getCollectionConfig(idOrPath) : undefined;
210
-
211
- const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride) : undefined;
253
+ const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride ?? {}) : undefined;
212
254
 
213
255
  let result: Partial<EntityCollection> | undefined = overriddenCollection;
214
256
 
@@ -228,16 +270,14 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
228
270
 
229
271
  return { ...overriddenCollection, ...result } as EC;
230
272
 
231
- }, [
232
- basePath,
233
- baseCollectionPath,
234
- collections
235
- ]);
273
+ }, [userConfigPersistence]);
236
274
 
237
275
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
238
- let currentCollections = collections;
239
- if (!currentCollections)
240
- throw Error("Collections have not been initialised yet");
276
+
277
+ const collections = collectionsRef.current;
278
+ if (collections === undefined)
279
+ throw Error("getCollectionFromPaths: Collections have not been initialised yet");
280
+ let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
241
281
 
242
282
  for (let i = 0; i < pathSegments.length; i++) {
243
283
  const pathSegment = pathSegments[i];
@@ -251,12 +291,14 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
251
291
 
252
292
  return undefined;
253
293
 
254
- }, [collections]);
294
+ }, []);
255
295
 
256
296
  const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
257
- let currentCollections = collections;
258
- if (!currentCollections)
259
- throw Error("Collections have not been initialised yet");
297
+
298
+ const collections = collectionsRef.current;
299
+ if (collections === undefined)
300
+ throw Error("getCollectionFromIds: Collections have not been initialised yet");
301
+ let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
260
302
 
261
303
  for (let i = 0; i < ids.length; i++) {
262
304
  const id = ids[i];
@@ -270,7 +312,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
270
312
 
271
313
  return undefined;
272
314
 
273
- }, [collections]);
315
+ }, []);
274
316
 
275
317
  const isUrlCollectionPath = useCallback(
276
318
  (path: string): boolean => removeInitialAndTrailingSlashes(path + "/").startsWith(removeInitialAndTrailingSlashes(fullCollectionPath) + "/"),
@@ -292,17 +334,17 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
292
334
  []);
293
335
 
294
336
  const resolveAliasesFrom = useCallback((path: string): string => {
295
- if (!collections)
296
- throw Error("Collections have not been initialised yet");
337
+ const collections = collectionsRef.current ?? [];
297
338
  return resolveCollectionPathIds(path, collections);
298
- }, [collections]);
339
+ }, []);
299
340
 
300
341
  const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
342
+ const collections = collectionsRef.current ?? [];
301
343
  return getParentReferencesFromPath({
302
344
  path,
303
345
  collections
304
346
  });
305
- }, [collections]);
347
+ }, []);
306
348
 
307
349
  const getParentCollectionIds = useCallback((path: string): string[] => {
308
350
 
@@ -321,24 +363,24 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
321
363
  }, [getAllParentReferencesForPath])
322
364
 
323
365
  const convertIdsToPaths = useCallback((ids: string[]): string[] => {
324
- let currentCollections = collections;
325
- const paths: string[] = [];
326
- for (let i = 0; i < ids.length; i++) {
327
- const id = ids[i];
328
- const collection: EntityCollection | undefined = currentCollections!.find(c => c.id === id);
329
- if (!collection)
330
- throw Error(`Collection with id ${id} not found`);
331
- paths.push(collection.path);
332
- currentCollections = collection.subcollections;
333
- }
334
- return paths;
366
+ const collections = collectionsRef.current;
367
+ let currentCollections = collections;
368
+ const paths: string[] = [];
369
+ for (let i = 0; i < ids.length; i++) {
370
+ const id = ids[i];
371
+ const collection: EntityCollection | undefined = currentCollections!.find(c => c.id === id);
372
+ if (!collection)
373
+ throw Error(`Collection with id ${id} not found`);
374
+ paths.push(collection.path);
375
+ currentCollections = collection.subcollections;
335
376
  }
336
- , [getCollectionFromIds]);
377
+ return paths;
378
+ }, [getCollectionFromIds]);
337
379
 
338
380
  return {
339
- collections,
340
- views,
341
- adminViews,
381
+ collections: collectionsRef.current,
382
+ views: viewsRef.current,
383
+ adminViews: adminViewsRef.current,
342
384
  loading: !initialised || navigationLoading,
343
385
  navigationLoadingError,
344
386
  homeUrl,
@@ -395,7 +437,7 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
395
437
  collectionPermissions: PermissionsBuilder | undefined,
396
438
  authController: AuthController,
397
439
  dataSource: DataSourceDelegate,
398
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
440
+ injectCollections?: (collections: EntityCollection[]) => EntityCollection[]): Promise<EntityCollection[]> {
399
441
  let resolvedCollections: EntityCollection[] = [];
400
442
  if (typeof collections === "function") {
401
443
  resolvedCollections = await collections({
@@ -407,14 +449,14 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
407
449
  resolvedCollections = collections;
408
450
  }
409
451
 
410
- resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
411
-
412
- resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
413
-
414
452
  if (injectCollections) {
415
453
  resolvedCollections = injectCollections(resolvedCollections ?? []);
416
454
  }
417
455
 
456
+ resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
457
+
458
+ resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
459
+
418
460
  return resolvedCollections;
419
461
  }
420
462
 
@@ -431,3 +473,37 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
431
473
  }
432
474
  return resolvedViews;
433
475
  }
476
+
477
+ function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
478
+ const trimmed = collectionOrView.group?.trim();
479
+ if (!trimmed || trimmed === "") {
480
+ return "Views";
481
+ }
482
+ return trimmed ?? "Views";
483
+ }
484
+
485
+ function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
486
+ if (a.length !== b.length) {
487
+ return false;
488
+ }
489
+ const aCopy = [...a];
490
+ const bCopy = [...b];
491
+ const aSorted = aCopy.sort((x, y) => x.id.localeCompare(y.id));
492
+ const bSorted = bCopy.sort((x, y) => x.id.localeCompare(y.id));
493
+ return aSorted.every((value, index) => areCollectionsEqual(value, bSorted[index]));
494
+ }
495
+
496
+ function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
497
+ const {
498
+ subcollections: subcollectionsA,
499
+ ...restA
500
+ } = a;
501
+ const {
502
+ subcollections: subcollectionsB,
503
+ ...restB
504
+ } = b;
505
+ if (!areCollectionListsEqual(subcollectionsA ?? [], subcollectionsB ?? [])) {
506
+ return false;
507
+ }
508
+ return equal(restA, restB);
509
+ }
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
- import { AuthController } from "../types";
2
+ import { AuthController, DataSourceDelegate, 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";
@@ -9,31 +9,44 @@ export type AccessResponse = {
9
9
  message?: string;
10
10
  }
11
11
 
12
- async function makeRequest(authController: AuthController) {
13
- const firebaseToken = await authController.getAuthToken();
12
+ async function makeRequest(authController: AuthController, dataSourceKey: string, pluginKeys: string[] | undefined) {
13
+ let idToken: string | null;
14
+ try {
15
+ idToken = await authController.getAuthToken();
16
+ } catch (e) {
17
+ idToken = null;
18
+ }
14
19
  return fetch(DEFAULT_SERVER + "/access_log",
15
20
  {
16
21
  // mode: "no-cors",
17
22
  method: "POST",
18
23
  headers: {
19
24
  "Content-Type": "application/json",
20
- Authorization: `Basic ${firebaseToken}`,
25
+ Authorization: `Basic ${idToken}`
21
26
  },
22
- body: JSON.stringify({})
27
+ body: JSON.stringify({
28
+ email: authController.user?.email ?? null,
29
+ datasource: dataSourceKey,
30
+ plugins: pluginKeys
31
+ })
23
32
  })
24
33
  .then(async (res) => {
25
34
  return res.json();
26
35
  });
27
36
  }
28
37
 
29
- export function useProjectLog(authController: AuthController): AccessResponse | null {
38
+ export function useProjectLog(authController: AuthController,
39
+ dataSourceDelegate: DataSourceDelegate,
40
+ plugins?: FireCMSPlugin<any, any, any>[]): AccessResponse | null {
30
41
  const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
31
42
  const accessedUserRef = useRef<string | null>(null);
43
+ const dataSourceKey = dataSourceDelegate.key;
44
+ const pluginKeys = plugins?.map(plugin => plugin.key);
32
45
  useEffect(() => {
33
46
  if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
34
- makeRequest(authController).then(setAccessResponse);
47
+ makeRequest(authController, dataSourceKey, pluginKeys).then(setAccessResponse);
35
48
  accessedUserRef.current = authController.user.uid;
36
49
  }
37
- }, [authController]);
50
+ }, [authController, dataSourceKey, pluginKeys]);
38
51
  return accessResponse;
39
52
  }
@@ -80,7 +80,7 @@ export function resolveNavigationFrom<M extends Record<string, any>, UserType ex
80
80
  if (entry.type === "collection") {
81
81
  return Promise.resolve(entry);
82
82
  } else if (entry.type === "entity") {
83
- const collection = navigation.getCollection(entry.path, entry.entityId);
83
+ const collection = navigation.getCollection(entry.path);
84
84
  if (!collection) {
85
85
  throw Error(`No collection defined in the navigation for the entity with path ${entry.path}`);
86
86
  }