@firecms/core 3.0.0-beta.2 → 3.0.0-beta.4.pre.1

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 (157) hide show
  1. package/README.md +1 -1
  2. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +3 -7
  3. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  4. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +2 -2
  5. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +1 -2
  6. package/dist/components/EntityCollectionView/useSelectionController.d.ts +2 -0
  7. package/dist/components/EntityPreview.d.ts +25 -7
  8. package/dist/components/EntityView.d.ts +11 -0
  9. package/dist/components/FieldCaption.d.ts +4 -0
  10. package/dist/components/HomePage/NavigationCard.d.ts +8 -0
  11. package/dist/components/HomePage/{NavigationCollectionCard.d.ts → NavigationCardBinding.d.ts} +2 -2
  12. package/dist/components/HomePage/SmallNavigationCard.d.ts +6 -0
  13. package/dist/components/HomePage/index.d.ts +3 -1
  14. package/dist/components/ReferenceWidget.d.ts +3 -3
  15. package/dist/components/VirtualTable/VirtualTableProps.d.ts +1 -1
  16. package/dist/components/index.d.ts +4 -3
  17. package/dist/{internal/EntityView.d.ts → core/EntityEditView.d.ts} +2 -2
  18. package/dist/core/SideEntityView.d.ts +7 -0
  19. package/dist/core/index.d.ts +0 -2
  20. package/dist/form/EntityForm.d.ts +1 -1
  21. package/dist/form/components/StorageItemPreview.d.ts +3 -2
  22. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  23. package/dist/form/components/index.d.ts +1 -0
  24. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  25. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  26. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +4 -3
  27. package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -2
  28. package/dist/form/index.d.ts +1 -0
  29. package/dist/form/validation.d.ts +1 -1
  30. package/dist/hooks/data/useDataSource.d.ts +2 -2
  31. package/dist/hooks/index.d.ts +2 -1
  32. package/dist/{core → hooks}/useBuildModeController.d.ts +1 -1
  33. package/dist/hooks/useBuildNavigationController.d.ts +5 -2
  34. package/dist/hooks/useProjectLog.d.ts +5 -1
  35. package/dist/hooks/useStorageSource.d.ts +2 -2
  36. package/dist/index.es.js +8444 -8152
  37. package/dist/index.es.js.map +1 -1
  38. package/dist/index.umd.js +5 -5
  39. package/dist/index.umd.js.map +1 -1
  40. package/dist/internal/useBuildDataSource.d.ts +4 -0
  41. package/dist/internal/useRestoreScroll.d.ts +1 -1
  42. package/dist/preview/PropertyPreview.d.ts +1 -1
  43. package/dist/preview/components/BooleanPreview.d.ts +5 -1
  44. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  45. package/dist/preview/components/ReferencePreview.d.ts +1 -7
  46. package/dist/types/analytics.d.ts +1 -1
  47. package/dist/types/auth.d.ts +8 -1
  48. package/dist/types/collections.d.ts +14 -1
  49. package/dist/types/entities.d.ts +1 -0
  50. package/dist/types/entity_overrides.d.ts +6 -0
  51. package/dist/types/index.d.ts +1 -0
  52. package/dist/types/navigation.d.ts +10 -9
  53. package/dist/types/permissions.d.ts +5 -1
  54. package/dist/types/plugins.d.ts +15 -17
  55. package/dist/types/properties.d.ts +2 -2
  56. package/dist/types/property_config.d.ts +2 -2
  57. package/dist/util/collections.d.ts +9 -1
  58. package/dist/util/icons.d.ts +8 -2
  59. package/dist/util/index.d.ts +1 -0
  60. package/dist/util/permissions.d.ts +4 -4
  61. package/dist/util/references.d.ts +4 -2
  62. package/dist/util/resolutions.d.ts +1 -1
  63. package/package.json +31 -23
  64. package/src/components/DeleteEntityDialog.tsx +4 -4
  65. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +35 -39
  66. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +276 -282
  67. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  68. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +13 -13
  69. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +9 -16
  70. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +3 -3
  71. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
  72. package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +9 -5
  73. package/src/components/EntityCollectionView/EntityCollectionView.tsx +37 -58
  74. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
  75. package/src/components/EntityCollectionView/useSelectionController.tsx +30 -0
  76. package/src/components/EntityPreview.tsx +204 -70
  77. package/src/components/EntityView.tsx +84 -0
  78. package/src/components/FieldCaption.tsx +13 -0
  79. package/src/components/FireCMSAppBar.tsx +8 -0
  80. package/src/components/HomePage/DefaultHomePage.tsx +13 -9
  81. package/src/components/HomePage/NavigationCard.tsx +69 -0
  82. package/src/components/HomePage/NavigationCardBinding.tsx +116 -0
  83. package/src/components/HomePage/SmallNavigationCard.tsx +45 -0
  84. package/src/components/HomePage/index.tsx +3 -1
  85. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -4
  86. package/src/components/ReferenceWidget.tsx +7 -7
  87. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +11 -19
  88. package/src/components/VirtualTable/VirtualTableProps.tsx +1 -1
  89. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +1 -1
  90. package/src/components/index.tsx +4 -3
  91. package/src/core/Drawer.tsx +66 -39
  92. package/src/{internal/EntityView.tsx → core/EntityEditView.tsx} +22 -39
  93. package/src/core/EntitySidePanel.tsx +2 -2
  94. package/src/core/FireCMS.tsx +18 -2
  95. package/src/core/NavigationRoutes.tsx +8 -0
  96. package/src/core/SideEntityView.tsx +38 -0
  97. package/src/core/index.tsx +0 -2
  98. package/src/form/EntityForm.tsx +19 -11
  99. package/src/form/components/StorageItemPreview.tsx +5 -3
  100. package/src/form/components/StorageUploadProgress.tsx +6 -5
  101. package/src/form/components/index.tsx +1 -0
  102. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -3
  103. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +12 -15
  104. package/src/form/field_bindings/BlockFieldBinding.tsx +2 -3
  105. package/src/form/field_bindings/DateTimeFieldBinding.tsx +3 -3
  106. package/src/form/field_bindings/KeyValueFieldBinding.tsx +18 -18
  107. package/src/form/field_bindings/MapFieldBinding.tsx +17 -17
  108. package/src/form/field_bindings/MarkdownFieldBinding.tsx +1 -2
  109. package/src/form/field_bindings/MultiSelectBinding.tsx +2 -3
  110. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +3 -3
  111. package/src/form/field_bindings/ReferenceFieldBinding.tsx +6 -4
  112. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
  113. package/src/form/field_bindings/SelectFieldBinding.tsx +2 -3
  114. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +15 -6
  115. package/src/form/field_bindings/SwitchFieldBinding.tsx +2 -3
  116. package/src/form/field_bindings/TextFieldBinding.tsx +10 -9
  117. package/src/form/index.tsx +1 -0
  118. package/src/form/validation.ts +3 -4
  119. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  120. package/src/hooks/data/useDataSource.tsx +8 -3
  121. package/src/hooks/data/useEntityFetch.tsx +1 -1
  122. package/src/hooks/index.tsx +3 -1
  123. package/src/{core → hooks}/useBuildModeController.tsx +1 -1
  124. package/src/hooks/useBuildNavigationController.tsx +105 -49
  125. package/src/hooks/useProjectLog.tsx +11 -3
  126. package/src/hooks/useReferenceDialog.tsx +2 -2
  127. package/src/hooks/useStorageSource.tsx +7 -2
  128. package/src/internal/useBuildDataSource.ts +7 -2
  129. package/src/preview/PropertyPreview.tsx +2 -2
  130. package/src/preview/components/BooleanPreview.tsx +16 -3
  131. package/src/preview/components/EnumValuesChip.tsx +1 -1
  132. package/src/preview/components/ReferencePreview.tsx +55 -147
  133. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
  134. package/src/types/analytics.ts +1 -0
  135. package/src/types/auth.tsx +11 -1
  136. package/src/types/collections.ts +16 -1
  137. package/src/types/entities.ts +4 -0
  138. package/src/types/entity_actions.tsx +4 -0
  139. package/src/types/entity_overrides.tsx +7 -0
  140. package/src/types/firecms.tsx +0 -1
  141. package/src/types/index.ts +1 -0
  142. package/src/types/navigation.ts +11 -10
  143. package/src/types/permissions.ts +6 -1
  144. package/src/types/plugins.tsx +22 -25
  145. package/src/types/properties.ts +1 -1
  146. package/src/types/property_config.tsx +2 -2
  147. package/src/types/side_entity_controller.tsx +1 -0
  148. package/src/util/collections.ts +22 -0
  149. package/src/util/icons.tsx +11 -3
  150. package/src/util/index.ts +1 -0
  151. package/src/util/permissions.ts +11 -8
  152. package/src/util/references.ts +36 -5
  153. package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
  154. /package/dist/{components → form/components}/LabelWithIcon.d.ts +0 -0
  155. /package/dist/{core → hooks}/useBuildLocalConfigurationPersistence.d.ts +0 -0
  156. /package/src/{components → form/components}/LabelWithIcon.tsx +0 -0
  157. /package/src/{core → hooks}/useBuildLocalConfigurationPersistence.tsx +0 -0
@@ -3,6 +3,7 @@ import React, { useCallback } from "react";
3
3
  import {
4
4
  ArrayProperty,
5
5
  Entity,
6
+ EntityCollection,
6
7
  FieldProps,
7
8
  ResolvedArrayProperty,
8
9
  ResolvedStringProperty,
@@ -10,11 +11,10 @@ import {
10
11
  } from "../../types";
11
12
  import { useDropzone } from "react-dropzone";
12
13
  import { PreviewSize } from "../../preview";
13
- import { FieldHelperText } from "../components";
14
- import { LabelWithIcon } from "../../components";
14
+ import { FieldHelperText, LabelWithIcon } from "../components";
15
15
 
16
16
  import { getIconForProperty, isReadOnly } from "../../util";
17
- import { useClearRestoreValue, useSnackbarController, useStorageSource } from "../../hooks";
17
+ import { useSnackbarController, useStorageSource } from "../../hooks";
18
18
  import { DragDropContext, Draggable, Droppable } from "@hello-pangea/dnd";
19
19
  import { StorageFieldItem, useStorageUploadController } from "../../util/useStorageUploadController";
20
20
  import { StorageUploadProgress } from "../components/StorageUploadProgress";
@@ -27,6 +27,7 @@ import {
27
27
  focusedMixin,
28
28
  Typography
29
29
  } from "@firecms/ui";
30
+ import { useClearRestoreValue } from "../useClearRestoreValue";
30
31
 
31
32
  const dropZoneClasses = "box-border relative pt-[2px] items-center border border-transparent min-h-[254px] outline-none rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
32
33
  const disabledClasses = "border-dotted-gray"
@@ -55,13 +56,13 @@ export function StorageUploadFieldBinding({
55
56
  property,
56
57
  includeDescription,
57
58
  context,
58
- isSubmitting
59
+ isSubmitting,
59
60
  }: StorageUploadFieldProps) {
60
61
 
61
62
  if (!context.entityId)
62
63
  throw new Error("StorageUploadFieldBinding: Entity id is null");
63
64
 
64
- const storageSource = useStorageSource();
65
+ const storageSource = useStorageSource(context.collection);
65
66
  const disabled = isReadOnly(property) || !!property.disabled || isSubmitting;
66
67
 
67
68
  const {
@@ -108,6 +109,7 @@ export function StorageUploadFieldBinding({
108
109
 
109
110
  <StorageUpload
110
111
  value={internalValue}
112
+ collection={context.collection}
111
113
  name={propertyKey}
112
114
  disabled={disabled}
113
115
  autoFocus={autoFocus}
@@ -133,6 +135,7 @@ export function StorageUploadFieldBinding({
133
135
 
134
136
  function FileDropComponent({
135
137
  storage,
138
+ collection,
136
139
  disabled,
137
140
  isDraggingOver,
138
141
  onFilesAdded,
@@ -151,6 +154,7 @@ function FileDropComponent({
151
154
  helpText
152
155
  }: {
153
156
  storage: StorageConfig,
157
+ collection: EntityCollection,
154
158
  disabled: boolean,
155
159
  isDraggingOver: boolean,
156
160
  droppableProvided: any,
@@ -231,6 +235,7 @@ function FileDropComponent({
231
235
  if (entry.storagePathOrDownloadUrl) {
232
236
  child = (
233
237
  <StorageItemPreview
238
+ collection={collection}
234
239
  name={`storage_preview_${entry.storagePathOrDownloadUrl}`}
235
240
  property={property}
236
241
  disabled={disabled}
@@ -294,6 +299,7 @@ function FileDropComponent({
294
299
 
295
300
  export interface StorageUploadProps {
296
301
  value: StorageFieldItem[];
302
+ collection: EntityCollection;
297
303
  setInternalValue: (v: StorageFieldItem[]) => void;
298
304
  name: string;
299
305
  property: ResolvedStringProperty | ResolvedArrayProperty<string[]>;
@@ -309,6 +315,7 @@ export interface StorageUploadProps {
309
315
  }
310
316
 
311
317
  export function StorageUpload({
318
+ collection,
312
319
  property,
313
320
  name,
314
321
  value,
@@ -321,7 +328,7 @@ export function StorageUpload({
321
328
  autoFocus,
322
329
  storage,
323
330
  entity,
324
- storagePathBuilder
331
+ storagePathBuilder,
325
332
  }: StorageUploadProps) {
326
333
 
327
334
  if (multipleFilesSupported) {
@@ -401,6 +408,7 @@ export function StorageUpload({
401
408
  className="rounded"
402
409
  >
403
410
  <StorageItemPreview
411
+ collection={collection}
404
412
  name={`storage_preview_${entry.storagePathOrDownloadUrl}`}
405
413
  property={renderProperty}
406
414
  disabled={true}
@@ -414,6 +422,7 @@ export function StorageUpload({
414
422
  >
415
423
  {(provided, snapshot) => {
416
424
  return <FileDropComponent storage={storage}
425
+ collection={collection}
417
426
  disabled={disabled}
418
427
  isDraggingOver={snapshot.isDraggingOver}
419
428
  droppableProvided={provided}
@@ -1,11 +1,10 @@
1
1
  import React from "react";
2
2
 
3
3
  import { FieldProps } from "../../types";
4
- import { useClearRestoreValue } from "../../hooks";
5
4
  import { getIconForProperty } from "../../util";
6
- import { FieldHelperText } from "../components/FieldHelperText";
5
+ import { FieldHelperText, LabelWithIcon } from "../components";
7
6
  import { BooleanSwitchWithLabel } from "@firecms/ui";
8
- import { LabelWithIcon } from "../../components";
7
+ import { useClearRestoreValue } from "../useClearRestoreValue";
9
8
 
10
9
  type SwitchFieldProps = FieldProps<boolean>;
11
10
 
@@ -1,14 +1,13 @@
1
1
  import React, { useCallback } from "react";
2
2
 
3
+ import { ClearIcon, Collapse, IconButton, TextField } from "@firecms/ui";
3
4
  import { FieldProps, PreviewType } from "../../types";
4
- import { FieldHelperText } from "../components";
5
- import { useClearRestoreValue } from "../../hooks";
5
+ import { FieldHelperText, LabelWithIcon } from "../components";
6
6
  import { getIconForProperty } from "../../util";
7
- import { ClearIcon, Collapse, IconButton, TextField } from "@firecms/ui";
8
7
  import { PropertyPreview } from "../../preview";
9
- import { LabelWithIcon } from "../../components";
8
+ import { useClearRestoreValue } from "../useClearRestoreValue";
10
9
 
11
- interface TextFieldProps<T extends string | number> extends FieldProps<T> {
10
+ interface TextFieldBindingProps<T extends string | number> extends FieldProps<T> {
12
11
  allowInfinity?: boolean
13
12
  }
14
13
 
@@ -19,6 +18,7 @@ interface TextFieldProps<T extends string | number> extends FieldProps<T> {
19
18
  * @group Form fields
20
19
  */
21
20
  export function TextFieldBinding<T extends string | number>({
21
+ context,
22
22
  propertyKey,
23
23
  value,
24
24
  setValue,
@@ -28,7 +28,7 @@ export function TextFieldBinding<T extends string | number>({
28
28
  autoFocus,
29
29
  property,
30
30
  includeDescription,
31
- }: TextFieldProps<T>) {
31
+ }: TextFieldBindingProps<T>) {
32
32
 
33
33
  let multiline: boolean | undefined;
34
34
  let url: boolean | PreviewType | undefined;
@@ -97,9 +97,10 @@ export function TextFieldBinding<T extends string | number>({
97
97
  {url && <Collapse
98
98
  className="mt-1 ml-1"
99
99
  in={Boolean(value)}>
100
- <PropertyPreview value={value}
101
- property={property}
102
- size={"medium"}/>
100
+ <PropertyPreview
101
+ value={value}
102
+ property={property}
103
+ size={"medium"}/>
103
104
  </Collapse>}
104
105
 
105
106
  </>
@@ -40,3 +40,4 @@ export {
40
40
  } from "./EntityForm";
41
41
 
42
42
  export { PropertyFieldBinding } from "./PropertyFieldBinding";
43
+ export * from "./useClearRestoreValue";
@@ -9,8 +9,7 @@ import {
9
9
  } from "../types";
10
10
  import * as yup from "yup";
11
11
  import { AnySchema, ArraySchema, BooleanSchema, DateSchema, NumberSchema, ObjectSchema, StringSchema } from "yup";
12
- import { enumToObjectEntries } from "../util/enums";
13
- import { getValueInPath, hydrateRegExp, isPropertyBuilder } from "../util";
12
+ import { enumToObjectEntries, getValueInPath, hydrateRegExp, isPropertyBuilder } from "../util";
14
13
 
15
14
  // Add custom unique function for array values
16
15
  declare module "yup" {
@@ -90,12 +89,12 @@ export function mapPropertyToYup<T extends CMSType>(propertyContext: PropertyCon
90
89
  throw Error("Unsupported data type in yup mapping");
91
90
  }
92
91
 
93
- export function getYupMapObjectSchema<M extends Record<string, any>>({
92
+ export function getYupMapObjectSchema({
94
93
  property,
95
94
  entityId,
96
95
  customFieldValidator,
97
96
  name
98
- }: PropertyContext<M>): ObjectSchema<any> {
97
+ }: PropertyContext<Record<string, any>>): ObjectSchema<any> {
99
98
  const objectSchema: any = {};
100
99
  const validation = property.validation;
101
100
  if (property.properties)
@@ -70,7 +70,7 @@ export function useCollectionFetch<M extends Record<string, any>, UserType exten
70
70
  searchString
71
71
  }: CollectionFetchProps<M>): CollectionFetchResult<M> {
72
72
 
73
- const dataSource = useDataSource();
73
+ const dataSource = useDataSource(collection);
74
74
  const navigationController = useNavigationController();
75
75
 
76
76
  const path = navigationController.resolveAliasesFrom(inputPath);
@@ -1,9 +1,14 @@
1
- import { useContext } from "react";
2
- import { DataSource } from "../../types";
1
+ import { useContext, useMemo } from "react";
2
+ import { DataSource, EntityCollection } from "../../types";
3
3
  import { DataSourceContext } from "../../contexts/DataSourceContext";
4
4
 
5
5
  /**
6
6
  * Use this hook to get the datasource being used
7
7
  * @group Hooks and utilities
8
8
  */
9
- export const useDataSource = (): DataSource => useContext(DataSourceContext);
9
+ export const useDataSource = (collection?: EntityCollection): DataSource => {
10
+ const defaultDataSource = useContext(DataSourceContext);
11
+ if (collection?.overrides?.dataSource)
12
+ return collection?.overrides.dataSource;
13
+ return defaultDataSource;
14
+ };
@@ -43,7 +43,7 @@ export function useEntityFetch<M extends Record<string, any>, UserType extends U
43
43
  useCache = false
44
44
  }: EntityFetchProps<M>): EntityFetchResult<M> {
45
45
 
46
- const dataSource = useDataSource();
46
+ const dataSource = useDataSource(collection);
47
47
  const navigationController = useNavigationController();
48
48
 
49
49
  const path = navigationController.resolveAliasesFrom(inputPath);
@@ -4,7 +4,6 @@ export * from "./data/useEntityFetch";
4
4
  export * from "./data/save";
5
5
  export * from "./data/delete";
6
6
 
7
- export * from "../form/useClearRestoreValue";
8
7
  export * from "./useNavigationController";
9
8
 
10
9
  export * from "./useResolvedNavigationFrom";
@@ -23,3 +22,6 @@ export * from "./useReferenceDialog";
23
22
  export * from "./useBrowserTitleAndIcon";
24
23
  export * from "./useCustomizationController";
25
24
  export * from "./useBuildNavigationController";
25
+
26
+ export * from "./useBuildLocalConfigurationPersistence";
27
+ export * from "./useBuildModeController";
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
2
 
3
- import { ModeController } from "../hooks";
3
+ import { ModeController } from "./index";
4
4
 
5
5
  /**
6
6
  * Use this hook to build a color mode controller that determines
@@ -1,5 +1,5 @@
1
- import { useCallback, useEffect, useMemo, useState } from "react";
2
- import { useLocation } from "react-router-dom";
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import equal from "react-fast-compare"
3
3
 
4
4
  import {
5
5
  AuthController,
@@ -10,12 +10,14 @@ import {
10
10
  EntityCollectionsBuilder,
11
11
  EntityReference,
12
12
  NavigationController,
13
+ PermissionsBuilder,
13
14
  TopNavigationEntry,
14
15
  TopNavigationResult,
15
16
  User,
16
17
  UserConfigurationPersistence
17
18
  } from "../types";
18
19
  import {
20
+ applyPermissionsFunctionIfEmpty,
19
21
  getCollectionByPathOrId,
20
22
  mergeDeep,
21
23
  removeInitialAndTrailingSlashes,
@@ -32,7 +34,10 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
32
34
  baseCollectionPath?: string,
33
35
  authController: AuthController<UserType>;
34
36
  collections?: EC[] | EntityCollectionsBuilder<EC>;
37
+ collectionPermissions?: PermissionsBuilder;
35
38
  views?: CMSView[] | CMSViewsBuilder;
39
+ adminViews?: CMSView[] | CMSViewsBuilder;
40
+ viewsOrder?: string[];
36
41
  userConfigPersistence?: UserConfigurationPersistence;
37
42
  dataSourceDelegate: DataSourceDelegate;
38
43
  /**
@@ -45,21 +50,25 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
45
50
  injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
46
51
  };
47
52
 
48
- export function useBuildNavigationController<EC extends EntityCollection, UserType extends User>({
49
- basePath = DEFAULT_BASE_PATH,
50
- baseCollectionPath = DEFAULT_COLLECTION_PATH,
51
- authController,
52
- collections: baseCollections,
53
- views: baseViews,
54
- userConfigPersistence,
55
- dataSourceDelegate,
56
- injectCollections
57
- }: BuildNavigationContextProps<EC, UserType>): NavigationController {
58
-
59
- const location = useLocation();
60
-
53
+ export function useBuildNavigationController<EC extends EntityCollection, UserType extends User>(props: BuildNavigationContextProps<EC, UserType>): NavigationController {
54
+ const {
55
+ basePath = DEFAULT_BASE_PATH,
56
+ baseCollectionPath = DEFAULT_COLLECTION_PATH,
57
+ authController,
58
+ collections: collectionsProp,
59
+ collectionPermissions,
60
+ views: viewsProp,
61
+ adminViews: adminViewsProp,
62
+ viewsOrder,
63
+ userConfigPersistence,
64
+ dataSourceDelegate,
65
+ injectCollections
66
+ } = props;
67
+
68
+ const collectionsRef = useRef<EntityCollection[] | null>();
61
69
  const [collections, setCollections] = useState<EntityCollection[] | undefined>();
62
70
  const [views, setViews] = useState<CMSView[] | undefined>();
71
+ const [adminViews, setAdminViews] = useState<CMSView[] | undefined>();
63
72
  const [initialised, setInitialised] = useState<boolean>(false);
64
73
 
65
74
  const [topLevelNavigation, setTopLevelNavigation] = useState<TopNavigationResult | undefined>(undefined);
@@ -79,19 +88,18 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
79
88
  const buildUrlCollectionPath = useCallback((path: string): string => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path)}`,
80
89
  [baseCollectionPath]);
81
90
 
82
- const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[]): TopNavigationResult => {
83
- // return (collection.editable && resolvePermissions(collection, authController, paths).editCollection) ?? DEFAULT_PERMISSIONS.editCollection;
84
- const navigationEntries: TopNavigationEntry[] = [
91
+ const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[], adminViews: CMSView[], viewsOrder?: string[]): TopNavigationResult => {
92
+ let navigationEntries: TopNavigationEntry[] = [
85
93
  ...(collections ?? []).map(collection => (!collection.hideFromNavigation
86
- ? {
94
+ ? ({
87
95
  url: buildUrlCollectionPath(collection.id ?? collection.path),
88
96
  type: "collection",
89
97
  name: collection.name.trim(),
90
98
  path: collection.id ?? collection.path,
91
99
  collection,
92
100
  description: collection.description?.trim(),
93
- group: collection.group?.trim()
94
- }
101
+ group: collection.group?.trim() ?? "Views"
102
+ } satisfies TopNavigationEntry)
95
103
  : undefined))
96
104
  .filter(Boolean) as TopNavigationEntry[],
97
105
  ...(views ?? []).map(view =>
@@ -100,18 +108,50 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
100
108
  url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
101
109
  name: view.name.trim(),
102
110
  type: "view",
111
+ path: view.path,
103
112
  view,
104
113
  description: view.description?.trim(),
105
- group: view.group?.trim()
106
- })
114
+ group: view.group?.trim() ?? "Views"
115
+ }satisfies TopNavigationEntry)
116
+ : undefined)
117
+ .filter(Boolean) as TopNavigationEntry[],
118
+ ...(adminViews ?? []).map(view =>
119
+ !view.hideFromNavigation
120
+ ? ({
121
+ url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
122
+ name: view.name.trim(),
123
+ type: "admin",
124
+ path: view.path,
125
+ view,
126
+ description: view.description?.trim(),
127
+ group: "Admin"
128
+ } satisfies TopNavigationEntry)
107
129
  : undefined)
108
130
  .filter(Boolean) as TopNavigationEntry[]
109
131
  ];
110
132
 
133
+ if (viewsOrder) {
134
+ navigationEntries = navigationEntries.sort((a, b) => {
135
+ const aIndex = viewsOrder.indexOf(a.path);
136
+ const bIndex = viewsOrder.indexOf(b.path);
137
+ if (aIndex === -1 && bIndex === -1) {
138
+ return 0;
139
+ }
140
+ if (aIndex === -1) {
141
+ return 1;
142
+ }
143
+ if (bIndex === -1) {
144
+ return -1;
145
+ }
146
+ return aIndex - bIndex;
147
+ });
148
+ }
149
+
111
150
  const groups: string[] = Object.values(navigationEntries)
112
151
  .map(e => e.group)
113
152
  .filter(Boolean)
114
153
  .filter((value, index, array) => array.indexOf(value) === index) as string[];
154
+
115
155
  return {
116
156
  navigationEntries,
117
157
  groups
@@ -124,15 +164,24 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
124
164
  return;
125
165
 
126
166
  try {
127
- const [resolvedCollections = [], resolvedViews = []] = await Promise.all([
128
- resolveCollections(baseCollections, authController, dataSourceDelegate, injectCollections),
129
- resolveCMSViews(baseViews, authController, dataSourceDelegate)
167
+ const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
168
+ resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
169
+ resolveCMSViews(viewsProp, authController, dataSourceDelegate),
170
+ resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
130
171
  ]
131
172
  );
132
-
133
- setCollections(resolvedCollections);
134
- setViews(resolvedViews);
135
- setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews));
173
+ if (
174
+ !equal(collectionsRef.current, resolvedCollections) ||
175
+ !equal(views, resolvedViews) ||
176
+ !equal(adminViews, resolvedAdminViews) ||
177
+ !equal(topLevelNavigation, computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder))
178
+ ) {
179
+ collectionsRef.current = resolvedCollections;
180
+ setCollections(resolvedCollections);
181
+ setViews(resolvedViews);
182
+ setAdminViews(resolvedAdminViews);
183
+ setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
184
+ }
136
185
  } catch (e) {
137
186
  console.error(e);
138
187
  setNavigationLoadingError(e as any);
@@ -140,7 +189,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
140
189
 
141
190
  setNavigationLoading(false);
142
191
  setInitialised(true);
143
- }, [baseCollections, authController.user, authController.initialLoading, baseViews, computeTopNavigation, injectCollections]);
192
+ }, [collectionsProp, collectionPermissions, authController.user, authController.initialLoading, viewsProp, adminViewsProp, computeTopNavigation, injectCollections]);
144
193
 
145
194
  useEffect(() => {
146
195
  refreshNavigation();
@@ -182,7 +231,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
182
231
  }, [
183
232
  basePath,
184
233
  baseCollectionPath,
185
- collections,
234
+ collections
186
235
  ]);
187
236
 
188
237
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
@@ -248,15 +297,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
248
297
  return resolveCollectionPathIds(path, collections);
249
298
  }, [collections]);
250
299
 
251
- const state = location.state as any;
252
- /**
253
- * The location can be overridden if `base_location` is set in the
254
- * state field of the current location. This can happen if you open
255
- * a side entity, like `products`, from a different one, like `users`
256
- */
257
- const baseLocation = state && state.base_location ? state.base_location : location;
258
-
259
- const getAllParentCollectionsForPath = useCallback((path: string): EntityReference[] => {
300
+ const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
260
301
  return getParentReferencesFromPath({
261
302
  path,
262
303
  collections
@@ -264,8 +305,20 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
264
305
  }, [collections]);
265
306
 
266
307
  const getParentCollectionIds = useCallback((path: string): string[] => {
267
- return getAllParentCollectionsForPath(path).map(r => r.id);
268
- }, [getAllParentCollectionsForPath])
308
+
309
+ const strings = path.split("/");
310
+ const oddPathSegments = strings.filter((_, i) => i % 2 === 0);
311
+ oddPathSegments.pop();
312
+
313
+ const result: string[][] = [];
314
+
315
+ for (let i = 1; i <= oddPathSegments.length; i++) {
316
+ result.push(oddPathSegments.slice(0, i));
317
+ }
318
+
319
+ // for each odd path segment, get the collection
320
+ return result.map(r => getCollectionFromPaths(r)?.id).filter(Boolean) as string[];
321
+ }, [getAllParentReferencesForPath])
269
322
 
270
323
  const convertIdsToPaths = useCallback((ids: string[]): string[] => {
271
324
  let currentCollections = collections;
@@ -282,9 +335,10 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
282
335
  }
283
336
  , [getCollectionFromIds]);
284
337
 
285
- return useMemo(() => ({
338
+ return {
286
339
  collections,
287
340
  views,
341
+ adminViews,
288
342
  loading: !initialised || navigationLoading,
289
343
  navigationLoadingError,
290
344
  homeUrl,
@@ -301,12 +355,11 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
301
355
  buildCMSUrlPath,
302
356
  resolveAliasesFrom,
303
357
  topLevelNavigation,
304
- baseLocation,
305
358
  refreshNavigation,
306
- getParentReferencesFromPath: getAllParentCollectionsForPath,
359
+ getParentReferencesFromPath: getAllParentReferencesForPath,
307
360
  getParentCollectionIds,
308
361
  convertIdsToPaths
309
- }), [baseCollectionPath, baseLocation, basePath, buildCMSUrlPath, buildUrlCollectionPath, buildUrlEditCollectionPath, collections, getAllParentCollectionsForPath, getCollection, getCollectionFromPaths, homeUrl, initialised, isUrlCollectionPath, navigationLoading, navigationLoadingError, refreshNavigation, resolveAliasesFrom, topLevelNavigation, urlPathToDataPath, views]);
362
+ };
310
363
  }
311
364
 
312
365
  export function getSidePanelKey(path: string, entityId?: string) {
@@ -326,8 +379,8 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
326
379
  return resolvedCollections
327
380
  .filter((c) => {
328
381
  if (!c.permissions) return true;
329
- const resolvedPermissions = resolvePermissions(c, authController, [c.path], null,)
330
- return resolvedPermissions.read !== false;
382
+ const resolvedPermissions = resolvePermissions(c, authController, c.path, null)
383
+ return resolvedPermissions?.read !== false;
331
384
  })
332
385
  .map((c) => {
333
386
  if (!c.subcollections) return c;
@@ -339,6 +392,7 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
339
392
  }
340
393
 
341
394
  async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
395
+ collectionPermissions: PermissionsBuilder | undefined,
342
396
  authController: AuthController,
343
397
  dataSource: DataSourceDelegate,
344
398
  injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
@@ -353,6 +407,8 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
353
407
  resolvedCollections = collections;
354
408
  }
355
409
 
410
+ resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
411
+
356
412
  resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
357
413
 
358
414
  if (injectCollections) {
@@ -1,9 +1,14 @@
1
- import { useEffect, useRef } from "react";
1
+ import { useEffect, useRef, useState } from "react";
2
2
  import { AuthController } 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
+ export type AccessResponse = {
8
+ blocked?: boolean;
9
+ message?: string;
10
+ }
11
+
7
12
  async function makeRequest(authController: AuthController) {
8
13
  const firebaseToken = await authController.getAuthToken();
9
14
  return fetch(DEFAULT_SERVER + "/access_log",
@@ -17,15 +22,18 @@ async function makeRequest(authController: AuthController) {
17
22
  body: JSON.stringify({})
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): AccessResponse | null {
30
+ const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
24
31
  const accessedUserRef = useRef<string | null>(null);
25
32
  useEffect(() => {
26
33
  if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
27
- makeRequest(authController);
34
+ makeRequest(authController).then(setAccessResponse);
28
35
  accessedUserRef.current = authController.user.uid;
29
36
  }
30
37
  }, [authController]);
38
+ return accessResponse;
31
39
  }
@@ -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
+ };