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

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 (238) 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/{components/FireCMSAppBar.d.ts → core/DefaultAppBar.d.ts} +6 -9
  37. package/dist/core/DefaultDrawer.d.ts +19 -0
  38. package/dist/core/DrawerNavigationItem.d.ts +9 -0
  39. package/dist/core/EntityEditView.d.ts +18 -4
  40. package/dist/core/FireCMS.d.ts +1 -1
  41. package/dist/core/NavigationRoutes.d.ts +3 -3
  42. package/dist/core/index.d.ts +3 -4
  43. package/dist/form/PropertiesForm.d.ts +8 -0
  44. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  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/save.d.ts +1 -2
  52. package/dist/hooks/index.d.ts +1 -0
  53. package/dist/hooks/useBuildNavigationController.d.ts +1 -2
  54. package/dist/hooks/useProjectLog.d.ts +2 -2
  55. package/dist/hooks/useValidateAuthenticator.d.ts +4 -8
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.es.js +15833 -12220
  58. package/dist/index.es.js.map +1 -1
  59. package/dist/index.umd.js +19713 -7
  60. package/dist/index.umd.js.map +1 -1
  61. package/dist/internal/useBuildDataSource.d.ts +1 -16
  62. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  63. package/dist/preview/components/ReferencePreview.d.ts +2 -1
  64. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  65. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  66. package/dist/types/auth.d.ts +3 -3
  67. package/dist/types/collections.d.ts +28 -4
  68. package/dist/types/datasource.d.ts +33 -19
  69. package/dist/types/entities.d.ts +5 -1
  70. package/dist/types/entity_actions.d.ts +14 -0
  71. package/dist/types/fields.d.ts +36 -30
  72. package/dist/types/index.d.ts +0 -1
  73. package/dist/types/navigation.d.ts +5 -5
  74. package/dist/types/plugins.d.ts +14 -4
  75. package/dist/types/properties.d.ts +32 -19
  76. package/dist/types/storage.d.ts +11 -3
  77. package/dist/util/entities.d.ts +1 -1
  78. package/dist/util/icon_list.d.ts +5 -1
  79. package/dist/util/icon_synonyms.d.ts +1 -97
  80. package/dist/util/icons.d.ts +2 -2
  81. package/dist/util/navigation_utils.d.ts +2 -2
  82. package/dist/util/objects.d.ts +1 -1
  83. package/dist/util/plurals.d.ts +0 -2
  84. package/dist/util/resolutions.d.ts +14 -13
  85. package/dist/util/storage.d.ts +23 -2
  86. package/dist/util/useStorageUploadController.d.ts +1 -1
  87. package/package.json +128 -119
  88. package/src/app/AppBar.tsx +18 -0
  89. package/src/app/Drawer.tsx +25 -0
  90. package/src/app/Scaffold.tsx +251 -0
  91. package/src/app/index.ts +4 -0
  92. package/src/app/useApp.tsx +32 -0
  93. package/src/components/ArrayContainer.tsx +1 -1
  94. package/src/components/CircularProgressCenter.tsx +1 -1
  95. package/src/components/ClearFilterSortButton.tsx +41 -0
  96. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +9 -18
  97. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +21 -20
  98. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +10 -6
  99. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +38 -34
  100. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  101. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +11 -2
  102. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +14 -6
  103. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +29 -34
  104. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +16 -12
  105. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +4 -5
  106. package/src/components/EntityCollectionView/EntityCollectionView.tsx +69 -45
  107. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  108. package/src/components/EntityCollectionView/useSelectionController.tsx +19 -7
  109. package/src/components/EntityPreview.tsx +15 -9
  110. package/src/components/EntityView.tsx +5 -5
  111. package/src/components/ErrorView.tsx +1 -1
  112. package/src/components/HomePage/DefaultHomePage.tsx +24 -15
  113. package/src/components/HomePage/NavigationCard.tsx +3 -3
  114. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  115. package/src/components/HomePage/SmallNavigationCard.tsx +5 -5
  116. package/src/components/PropertyIdCopyTooltipContent.tsx +2 -3
  117. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +1 -0
  118. package/src/components/ReferenceWidget.tsx +22 -12
  119. package/src/components/SearchIconsView.tsx +9 -6
  120. package/src/components/SelectableTable/SelectableTable.tsx +7 -7
  121. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  122. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +22 -7
  123. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +28 -6
  124. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
  125. package/src/components/VirtualTable/VirtualTable.tsx +70 -37
  126. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -1
  127. package/src/components/VirtualTable/VirtualTableHeader.tsx +4 -4
  128. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
  129. package/src/components/VirtualTable/VirtualTableProps.tsx +18 -14
  130. package/src/components/VirtualTable/VirtualTableRow.tsx +4 -5
  131. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
  132. package/src/components/VirtualTable/types.tsx +2 -3
  133. package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +2 -2
  134. package/src/components/common/index.ts +1 -0
  135. package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
  136. package/src/components/common/types.tsx +4 -6
  137. package/src/components/common/useColumnsIds.tsx +23 -2
  138. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +11 -0
  139. package/src/components/common/useTableSearchHelper.ts +52 -12
  140. package/src/components/index.tsx +2 -1
  141. package/src/contexts/DialogsProvider.tsx +2 -2
  142. package/src/{components/FireCMSAppBar.tsx → core/DefaultAppBar.tsx} +52 -37
  143. package/src/core/DefaultDrawer.tsx +177 -0
  144. package/src/core/DrawerNavigationItem.tsx +62 -0
  145. package/src/core/EntityEditView.tsx +709 -140
  146. package/src/core/EntitySidePanel.tsx +1 -2
  147. package/src/core/FireCMS.tsx +39 -44
  148. package/src/core/NavigationRoutes.tsx +7 -8
  149. package/src/core/SideDialogs.tsx +1 -0
  150. package/src/core/field_configs.tsx +2 -3
  151. package/src/core/index.tsx +3 -4
  152. package/src/form/PropertiesForm.tsx +81 -0
  153. package/src/form/PropertyFieldBinding.tsx +38 -9
  154. package/src/form/components/FieldHelperText.tsx +3 -3
  155. package/src/form/components/StorageItemPreview.tsx +20 -11
  156. package/src/form/components/StorageUploadProgress.tsx +3 -3
  157. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +8 -5
  158. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +8 -5
  159. package/src/form/field_bindings/BlockFieldBinding.tsx +2 -2
  160. package/src/form/field_bindings/KeyValueFieldBinding.tsx +44 -39
  161. package/src/form/field_bindings/MapFieldBinding.tsx +11 -3
  162. package/src/form/field_bindings/MarkdownFieldBinding.tsx +2 -2
  163. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -9
  164. package/src/form/field_bindings/ReferenceFieldBinding.tsx +15 -13
  165. package/src/form/field_bindings/RepeatFieldBinding.tsx +11 -8
  166. package/src/form/field_bindings/SelectFieldBinding.tsx +3 -3
  167. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +22 -43
  168. package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -1
  169. package/src/form/index.tsx +4 -4
  170. package/src/form/validation.ts +1 -17
  171. package/src/hooks/data/save.ts +3 -1
  172. package/src/hooks/data/useDataSource.tsx +1 -1
  173. package/src/hooks/index.tsx +1 -0
  174. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +8 -10
  175. package/src/hooks/useBuildModeController.tsx +11 -5
  176. package/src/hooks/useBuildNavigationController.tsx +86 -29
  177. package/src/hooks/useProjectLog.tsx +19 -8
  178. package/src/hooks/useResolvedNavigationFrom.tsx +1 -1
  179. package/src/hooks/useValidateAuthenticator.tsx +21 -41
  180. package/src/index.ts +1 -0
  181. package/src/internal/useBuildDataSource.ts +56 -49
  182. package/src/internal/useBuildSideEntityController.tsx +88 -21
  183. package/src/preview/PropertyPreview.tsx +9 -16
  184. package/src/preview/PropertyPreviewProps.tsx +6 -0
  185. package/src/preview/components/BooleanPreview.tsx +2 -2
  186. package/src/preview/components/EnumValuesChip.tsx +1 -1
  187. package/src/preview/components/ImagePreview.tsx +21 -33
  188. package/src/preview/components/ReferencePreview.tsx +23 -23
  189. package/src/preview/components/StorageThumbnail.tsx +5 -1
  190. package/src/preview/components/UrlComponentPreview.tsx +44 -11
  191. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +0 -1
  192. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +2 -1
  193. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +0 -1
  194. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +0 -1
  195. package/src/preview/property_previews/ArrayOneOfPreview.tsx +2 -3
  196. package/src/preview/property_previews/ArrayPropertyPreview.tsx +2 -3
  197. package/src/preview/property_previews/MapPropertyPreview.tsx +5 -5
  198. package/src/preview/property_previews/StringPropertyPreview.tsx +2 -2
  199. package/src/types/auth.tsx +3 -3
  200. package/src/types/collections.ts +35 -6
  201. package/src/types/customization_controller.tsx +0 -1
  202. package/src/types/datasource.ts +40 -23
  203. package/src/types/entities.ts +9 -1
  204. package/src/types/entity_actions.tsx +16 -3
  205. package/src/types/fields.tsx +39 -33
  206. package/src/types/index.ts +0 -1
  207. package/src/types/navigation.ts +6 -7
  208. package/src/types/plugins.tsx +16 -6
  209. package/src/types/properties.ts +37 -20
  210. package/src/types/storage.ts +12 -3
  211. package/src/util/entities.ts +5 -4
  212. package/src/util/enums.ts +1 -1
  213. package/src/util/icon_list.ts +15 -10
  214. package/src/util/icon_synonyms.ts +3 -99
  215. package/src/util/navigation_utils.ts +6 -6
  216. package/src/util/objects.ts +25 -28
  217. package/src/util/permissions.ts +1 -0
  218. package/src/util/plurals.ts +0 -2
  219. package/src/util/resolutions.ts +44 -39
  220. package/src/util/storage.ts +75 -21
  221. package/src/util/strings.ts +2 -2
  222. package/src/util/useStorageUploadController.tsx +21 -3
  223. package/src/util/useTraceUpdate.tsx +1 -0
  224. package/dist/components/VirtualTable/common.d.ts +0 -2
  225. package/dist/core/Drawer.d.ts +0 -23
  226. package/dist/core/Scaffold.d.ts +0 -55
  227. package/dist/core/SideEntityView.d.ts +0 -7
  228. package/dist/form/EntityForm.d.ts +0 -77
  229. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  230. package/dist/internal/useLocaleConfig.d.ts +0 -1
  231. package/dist/types/appcheck.d.ts +0 -26
  232. package/src/core/Drawer.tsx +0 -191
  233. package/src/core/Scaffold.tsx +0 -281
  234. package/src/core/SideEntityView.tsx +0 -38
  235. package/src/form/EntityForm.tsx +0 -728
  236. package/src/internal/useBuildCustomizationController.tsx +0 -5
  237. package/src/internal/useLocaleConfig.tsx +0 -18
  238. 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({
@@ -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,
@@ -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
 
@@ -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";
@@ -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>;
@@ -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,38 @@ 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 {
167
- console.debug("Refreshing navigation")
193
+
168
194
  const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
169
195
  resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
170
196
  resolveCMSViews(viewsProp, authController, dataSourceDelegate),
171
197
  resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
172
198
  ]
173
199
  );
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
- ) {
200
+
201
+ let shouldUpdateTopLevelNav = false;
202
+ if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
203
+ collectionsRef.current = resolvedCollections;
204
+ shouldUpdateTopLevelNav = true;
205
+ }
206
+ if (collectionsRef.current === undefined) {
180
207
  collectionsRef.current = resolvedCollections;
208
+ shouldUpdateTopLevelNav = true;
209
+ }
210
+ if (!equal(viewsRef.current, resolvedViews)) {
181
211
  viewsRef.current = resolvedViews;
212
+ shouldUpdateTopLevelNav = true;
213
+ }
214
+ if (!equal(adminViewsRef.current, resolvedAdminViews)) {
182
215
  adminViewsRef.current = resolvedAdminViews;
183
- setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
216
+ shouldUpdateTopLevelNav = true;
217
+ }
218
+
219
+ const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder);
220
+ if (shouldUpdateTopLevelNav && !equal(topLevelNavigation, computedTopLevelNav)) {
221
+ setTopLevelNavigation(computedTopLevelNav);
184
222
  }
185
223
  } catch (e) {
186
224
  console.error(e);
@@ -205,9 +243,8 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
205
243
  refreshNavigation();
206
244
  }, [refreshNavigation]);
207
245
 
208
- const getCollection = useCallback(<EC extends EntityCollection>(
246
+ const getCollection = useCallback((
209
247
  idOrPath: string,
210
- entityId?: string,
211
248
  includeUserOverride = false
212
249
  ): EC | undefined => {
213
250
  const collections = collectionsRef.current;
@@ -217,8 +254,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
217
254
  const baseCollection = getCollectionByPathOrId(removeInitialAndTrailingSlashes(idOrPath), collections);
218
255
 
219
256
  const userOverride = includeUserOverride ? userConfigPersistence?.getCollectionConfig(idOrPath) : undefined;
220
-
221
- const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride) : undefined;
257
+ const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride ?? {}) : undefined;
222
258
 
223
259
  let result: Partial<EntityCollection> | undefined = overriddenCollection;
224
260
 
@@ -238,17 +274,14 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
238
274
 
239
275
  return { ...overriddenCollection, ...result } as EC;
240
276
 
241
- }, [
242
- basePath,
243
- baseCollectionPath,
244
- ]);
277
+ }, [userConfigPersistence]);
245
278
 
246
279
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
247
280
 
248
281
  const collections = collectionsRef.current;
282
+ if (collections === undefined)
283
+ throw Error("getCollectionFromPaths: Collections have not been initialised yet");
249
284
  let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
250
- if (!currentCollections)
251
- throw Error("Collections have not been initialised yet");
252
285
 
253
286
  for (let i = 0; i < pathSegments.length; i++) {
254
287
  const pathSegment = pathSegments[i];
@@ -267,9 +300,9 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
267
300
  const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
268
301
 
269
302
  const collections = collectionsRef.current;
303
+ if (collections === undefined)
304
+ throw Error("getCollectionFromIds: Collections have not been initialised yet");
270
305
  let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
271
- if (!currentCollections)
272
- throw Error("Collections have not been initialised yet");
273
306
 
274
307
  for (let i = 0; i < ids.length; i++) {
275
308
  const id = ids[i];
@@ -305,9 +338,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
305
338
  []);
306
339
 
307
340
  const resolveAliasesFrom = useCallback((path: string): string => {
308
- const collections = collectionsRef.current;
309
- if (!collections)
310
- throw Error("Collections have not been initialised yet");
341
+ const collections = collectionsRef.current ?? [];
311
342
  return resolveCollectionPathIds(path, collections);
312
343
  }, []);
313
344
 
@@ -410,7 +441,7 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
410
441
  collectionPermissions: PermissionsBuilder | undefined,
411
442
  authController: AuthController,
412
443
  dataSource: DataSourceDelegate,
413
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
444
+ injectCollections?: (collections: EntityCollection[]) => EntityCollection[]): Promise<EntityCollection[]> {
414
445
  let resolvedCollections: EntityCollection[] = [];
415
446
  if (typeof collections === "function") {
416
447
  resolvedCollections = await collections({
@@ -422,14 +453,14 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
422
453
  resolvedCollections = collections;
423
454
  }
424
455
 
425
- resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
426
-
427
- resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
428
-
429
456
  if (injectCollections) {
430
457
  resolvedCollections = injectCollections(resolvedCollections ?? []);
431
458
  }
432
459
 
460
+ resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
461
+
462
+ resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
463
+
433
464
  return resolvedCollections;
434
465
  }
435
466
 
@@ -454,3 +485,29 @@ function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
454
485
  }
455
486
  return trimmed ?? "Views";
456
487
  }
488
+
489
+ function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
490
+ if (a.length !== b.length) {
491
+ return false;
492
+ }
493
+ const aCopy = [...a];
494
+ const bCopy = [...b];
495
+ const aSorted = aCopy.sort((x, y) => x.id.localeCompare(y.id));
496
+ const bSorted = bCopy.sort((x, y) => x.id.localeCompare(y.id));
497
+ return aSorted.every((value, index) => areCollectionsEqual(value, bSorted[index]));
498
+ }
499
+
500
+ function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
501
+ const {
502
+ subcollections: subcollectionsA,
503
+ ...restA
504
+ } = a;
505
+ const {
506
+ subcollections: subcollectionsB,
507
+ ...restB
508
+ } = b;
509
+ if (!areCollectionListsEqual(subcollectionsA ?? [], subcollectionsB ?? [])) {
510
+ return false;
511
+ }
512
+ return equal(restA, restB);
513
+ }
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
- import { AuthController, FireCMSPlugin } 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,17 +9,26 @@ export type AccessResponse = {
9
9
  message?: string;
10
10
  }
11
11
 
12
- async function makeRequest(authController: AuthController, pluginKeys: string | undefined) {
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({ plugins: pluginKeys })
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();
@@ -27,15 +36,17 @@ async function makeRequest(authController: AuthController, pluginKeys: string |
27
36
  }
28
37
 
29
38
  export function useProjectLog(authController: AuthController,
39
+ dataSourceDelegate: DataSourceDelegate,
30
40
  plugins?: FireCMSPlugin<any, any, any>[]): AccessResponse | null {
31
41
  const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
32
42
  const accessedUserRef = useRef<string | null>(null);
33
- const pluginKeys = plugins?.map(plugin => plugin.key).join(",");
43
+ const dataSourceKey = dataSourceDelegate.key;
44
+ const pluginKeys = plugins?.map(plugin => plugin.key);
34
45
  useEffect(() => {
35
46
  if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
36
- makeRequest(authController, pluginKeys).then(setAccessResponse);
47
+ makeRequest(authController, dataSourceKey, pluginKeys).then(setAccessResponse);
37
48
  accessedUserRef.current = authController.user.uid;
38
49
  }
39
- }, [authController]);
50
+ }, [authController, dataSourceKey, pluginKeys]);
40
51
  return accessResponse;
41
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
  }
@@ -1,43 +1,38 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import equal from "react-fast-compare";
3
3
 
4
- import { AppCheckTokenResult, AuthController, Authenticator, DataSourceDelegate, StorageSource, User } from "../index";
4
+ import { AuthController, Authenticator, DataSourceDelegate, StorageSource, User } from "../index";
5
5
 
6
6
  /**
7
7
  * This hook is used internally for validating an authenticator.
8
8
  *
9
9
  * @param authController
10
10
  * @param authentication
11
- * @param getAppCheckToken
12
- * @param appCheckForceRefresh
13
11
  * @param storageSource
14
- * @param dataSource
12
+ * @param dataSourceDelegate
15
13
  */
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
- }): {
14
+ export function useValidateAuthenticator<UserType extends User = User, Controller extends AuthController<UserType> = AuthController<UserType>>
15
+ ({
16
+ disabled,
17
+ authController,
18
+ authenticator,
19
+ storageSource,
20
+ dataSourceDelegate
21
+ }:
22
+ {
23
+ disabled?: boolean,
24
+ authController: Controller,
25
+ authenticator?: boolean | Authenticator<UserType, Controller>,
26
+ dataSourceDelegate: DataSourceDelegate;
27
+ storageSource: StorageSource;
28
+ }): {
34
29
  canAccessMainView: boolean,
35
30
  authLoading: boolean,
36
31
  notAllowedError: any,
37
32
  authVerified: boolean,
38
33
  } {
39
34
 
40
- const authenticationEnabled = Boolean(authentication);
35
+ const authenticationEnabled = Boolean(authenticator);
41
36
 
42
37
  const [authLoading, setAuthLoading] = useState<boolean>(authenticationEnabled);
43
38
  const [notAllowedError, setNotAllowedError] = useState<any>(false);
@@ -76,26 +71,11 @@ export function useValidateAuthenticator<UserType extends User = User, Controlle
76
71
  }
77
72
 
78
73
  const delegateUser = authController.user;
79
- console.debug("Checking authentication for user", delegateUser);
80
74
 
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)) {
75
+ if (authenticator instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
96
76
  setAuthLoading(true);
97
77
  try {
98
- const allowed = await authentication({
78
+ const allowed = await authenticator({
99
79
  user: delegateUser,
100
80
  authController,
101
81
  dataSourceDelegate,
@@ -120,7 +100,7 @@ export function useValidateAuthenticator<UserType extends User = User, Controlle
120
100
  setAuthVerified(true);
121
101
  }
122
102
 
123
- }, [disabled, authController, authentication, getAppCheckToken, appCheckForceRefresh, dataSourceDelegate, storageSource]);
103
+ }, [disabled, authController, authenticator, dataSourceDelegate, storageSource]);
124
104
 
125
105
  useEffect(() => {
126
106
  checkAuthentication();
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./core";
2
+ export * from "./app";
2
3
  export * from "./types";
3
4
  export * from "./form";
4
5
  export * from "./preview";