@firecms/core 3.0.0-canary.6 → 3.0.0-canary.8

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 (128) hide show
  1. package/README.md +1 -1
  2. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +1 -1
  3. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  4. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +2 -2
  5. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +1 -2
  6. package/dist/components/EntityCollectionView/useSelectionController.d.ts +2 -0
  7. package/dist/components/EntityPreview.d.ts +25 -7
  8. package/dist/components/EntityView.d.ts +11 -0
  9. package/dist/components/FieldCaption.d.ts +5 -0
  10. package/dist/components/HomePage/NavigationCard.d.ts +8 -0
  11. package/dist/components/HomePage/{NavigationCollectionCard.d.ts → NavigationCardBinding.d.ts} +2 -2
  12. package/dist/components/HomePage/SmallNavigationCard.d.ts +6 -0
  13. package/dist/components/HomePage/index.d.ts +3 -1
  14. package/dist/components/VirtualTable/VirtualTableProps.d.ts +1 -1
  15. package/dist/components/index.d.ts +4 -2
  16. package/dist/core/{EntityView.d.ts → EntityEditView.d.ts} +2 -2
  17. package/dist/core/SideEntityView.d.ts +2 -2
  18. package/dist/form/EntityForm.d.ts +1 -1
  19. package/dist/form/components/StorageItemPreview.d.ts +3 -2
  20. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  21. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  22. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  23. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +4 -3
  24. package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -2
  25. package/dist/form/validation.d.ts +1 -1
  26. package/dist/hooks/data/useDataSource.d.ts +2 -2
  27. package/dist/hooks/useBuildNavigationController.d.ts +5 -2
  28. package/dist/hooks/useProjectLog.d.ts +5 -1
  29. package/dist/hooks/useStorageSource.d.ts +2 -2
  30. package/dist/index.es.js +8333 -8060
  31. package/dist/index.es.js.map +1 -1
  32. package/dist/index.umd.js +5 -5
  33. package/dist/index.umd.js.map +1 -1
  34. package/dist/internal/useRestoreScroll.d.ts +1 -1
  35. package/dist/preview/PropertyPreview.d.ts +1 -1
  36. package/dist/preview/components/BooleanPreview.d.ts +5 -1
  37. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  38. package/dist/preview/components/ReferencePreview.d.ts +1 -7
  39. package/dist/types/analytics.d.ts +1 -1
  40. package/dist/types/auth.d.ts +8 -1
  41. package/dist/types/collections.d.ts +14 -1
  42. package/dist/types/entity_overrides.d.ts +6 -0
  43. package/dist/types/index.d.ts +1 -0
  44. package/dist/types/navigation.d.ts +10 -9
  45. package/dist/types/permissions.d.ts +5 -1
  46. package/dist/types/plugins.d.ts +15 -17
  47. package/dist/types/properties.d.ts +2 -2
  48. package/dist/types/property_config.d.ts +2 -2
  49. package/dist/util/collections.d.ts +9 -1
  50. package/dist/util/icons.d.ts +8 -2
  51. package/dist/util/permissions.d.ts +4 -4
  52. package/dist/util/references.d.ts +4 -2
  53. package/dist/util/resolutions.d.ts +1 -1
  54. package/package.json +23 -23
  55. package/src/components/DeleteEntityDialog.tsx +4 -4
  56. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
  57. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +273 -277
  58. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  59. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +12 -13
  60. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +8 -15
  61. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +3 -3
  62. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
  63. package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +9 -5
  64. package/src/components/EntityCollectionView/EntityCollectionView.tsx +28 -49
  65. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
  66. package/src/components/EntityCollectionView/useSelectionController.tsx +30 -0
  67. package/src/components/EntityPreview.tsx +204 -70
  68. package/src/components/EntityView.tsx +84 -0
  69. package/src/components/FieldCaption.tsx +14 -0
  70. package/src/components/FireCMSAppBar.tsx +8 -0
  71. package/src/components/HomePage/DefaultHomePage.tsx +13 -9
  72. package/src/components/HomePage/NavigationCard.tsx +69 -0
  73. package/src/components/HomePage/NavigationCardBinding.tsx +116 -0
  74. package/src/components/HomePage/SmallNavigationCard.tsx +45 -0
  75. package/src/components/HomePage/index.tsx +3 -1
  76. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -4
  77. package/src/components/ReferenceWidget.tsx +3 -3
  78. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +11 -19
  79. package/src/components/VirtualTable/VirtualTableProps.tsx +1 -1
  80. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +1 -1
  81. package/src/components/index.tsx +4 -2
  82. package/src/core/Drawer.tsx +66 -39
  83. package/src/core/{EntityView.tsx → EntityEditView.tsx} +20 -37
  84. package/src/core/EntitySidePanel.tsx +2 -2
  85. package/src/core/FireCMS.tsx +18 -2
  86. package/src/core/NavigationRoutes.tsx +8 -0
  87. package/src/core/SideEntityView.tsx +2 -2
  88. package/src/form/EntityForm.tsx +19 -11
  89. package/src/form/components/StorageItemPreview.tsx +5 -3
  90. package/src/form/components/StorageUploadProgress.tsx +6 -5
  91. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +8 -12
  92. package/src/form/field_bindings/KeyValueFieldBinding.tsx +15 -15
  93. package/src/form/field_bindings/MapFieldBinding.tsx +15 -15
  94. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +1 -1
  95. package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -0
  96. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +14 -5
  97. package/src/form/field_bindings/TextFieldBinding.tsx +7 -5
  98. package/src/form/validation.ts +3 -4
  99. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  100. package/src/hooks/data/useDataSource.tsx +8 -3
  101. package/src/hooks/data/useEntityFetch.tsx +1 -1
  102. package/src/hooks/useBuildNavigationController.tsx +79 -38
  103. package/src/hooks/useProjectLog.tsx +11 -3
  104. package/src/hooks/useReferenceDialog.tsx +2 -2
  105. package/src/hooks/useStorageSource.tsx +7 -2
  106. package/src/preview/PropertyPreview.tsx +1 -1
  107. package/src/preview/components/BooleanPreview.tsx +16 -3
  108. package/src/preview/components/EnumValuesChip.tsx +1 -1
  109. package/src/preview/components/ReferencePreview.tsx +54 -146
  110. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
  111. package/src/types/analytics.ts +1 -0
  112. package/src/types/auth.tsx +11 -1
  113. package/src/types/collections.ts +16 -1
  114. package/src/types/entity_actions.tsx +4 -0
  115. package/src/types/entity_overrides.tsx +7 -0
  116. package/src/types/firecms.tsx +0 -1
  117. package/src/types/index.ts +1 -0
  118. package/src/types/navigation.ts +11 -10
  119. package/src/types/permissions.ts +6 -1
  120. package/src/types/plugins.tsx +22 -25
  121. package/src/types/properties.ts +3 -2
  122. package/src/types/property_config.tsx +2 -2
  123. package/src/types/side_entity_controller.tsx +1 -0
  124. package/src/util/collections.ts +22 -0
  125. package/src/util/icons.tsx +11 -3
  126. package/src/util/permissions.ts +11 -8
  127. package/src/util/references.ts +36 -5
  128. package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
@@ -1,5 +1,4 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
- import { useLocation } from "react-router-dom";
3
2
  import equal from "react-fast-compare"
4
3
 
5
4
  import {
@@ -11,12 +10,14 @@ import {
11
10
  EntityCollectionsBuilder,
12
11
  EntityReference,
13
12
  NavigationController,
13
+ PermissionsBuilder,
14
14
  TopNavigationEntry,
15
15
  TopNavigationResult,
16
16
  User,
17
17
  UserConfigurationPersistence
18
18
  } from "../types";
19
19
  import {
20
+ applyPermissionsFunctionIfEmpty,
20
21
  getCollectionByPathOrId,
21
22
  mergeDeep,
22
23
  removeInitialAndTrailingSlashes,
@@ -33,7 +34,10 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
33
34
  baseCollectionPath?: string,
34
35
  authController: AuthController<UserType>;
35
36
  collections?: EC[] | EntityCollectionsBuilder<EC>;
37
+ collectionPermissions?: PermissionsBuilder;
36
38
  views?: CMSView[] | CMSViewsBuilder;
39
+ adminViews?: CMSView[] | CMSViewsBuilder;
40
+ viewsOrder?: string[];
37
41
  userConfigPersistence?: UserConfigurationPersistence;
38
42
  dataSourceDelegate: DataSourceDelegate;
39
43
  /**
@@ -46,22 +50,25 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
46
50
  injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
47
51
  };
48
52
 
49
- export function useBuildNavigationController<EC extends EntityCollection, UserType extends User>({
50
- basePath = DEFAULT_BASE_PATH,
51
- baseCollectionPath = DEFAULT_COLLECTION_PATH,
52
- authController,
53
- collections: collectionsProp,
54
- views: baseViews,
55
- userConfigPersistence,
56
- dataSourceDelegate,
57
- injectCollections
58
- }: BuildNavigationContextProps<EC, UserType>): NavigationController {
59
-
60
- const location = useLocation();
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;
61
67
 
62
68
  const collectionsRef = useRef<EntityCollection[] | null>();
63
69
  const [collections, setCollections] = useState<EntityCollection[] | undefined>();
64
70
  const [views, setViews] = useState<CMSView[] | undefined>();
71
+ const [adminViews, setAdminViews] = useState<CMSView[] | undefined>();
65
72
  const [initialised, setInitialised] = useState<boolean>(false);
66
73
 
67
74
  const [topLevelNavigation, setTopLevelNavigation] = useState<TopNavigationResult | undefined>(undefined);
@@ -81,18 +88,18 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
81
88
  const buildUrlCollectionPath = useCallback((path: string): string => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path)}`,
82
89
  [baseCollectionPath]);
83
90
 
84
- const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[]): TopNavigationResult => {
85
- const navigationEntries: TopNavigationEntry[] = [
91
+ const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[], adminViews: CMSView[], viewsOrder?: string[]): TopNavigationResult => {
92
+ let navigationEntries: TopNavigationEntry[] = [
86
93
  ...(collections ?? []).map(collection => (!collection.hideFromNavigation
87
- ? {
94
+ ? ({
88
95
  url: buildUrlCollectionPath(collection.id ?? collection.path),
89
96
  type: "collection",
90
97
  name: collection.name.trim(),
91
98
  path: collection.id ?? collection.path,
92
99
  collection,
93
100
  description: collection.description?.trim(),
94
- group: collection.group?.trim()
95
- }
101
+ group: collection.group?.trim() ?? "Views"
102
+ } satisfies TopNavigationEntry)
96
103
  : undefined))
97
104
  .filter(Boolean) as TopNavigationEntry[],
98
105
  ...(views ?? []).map(view =>
@@ -101,18 +108,50 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
101
108
  url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
102
109
  name: view.name.trim(),
103
110
  type: "view",
111
+ path: view.path,
104
112
  view,
105
113
  description: view.description?.trim(),
106
- group: view.group?.trim()
107
- })
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)
108
129
  : undefined)
109
130
  .filter(Boolean) as TopNavigationEntry[]
110
131
  ];
111
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
+
112
150
  const groups: string[] = Object.values(navigationEntries)
113
151
  .map(e => e.group)
114
152
  .filter(Boolean)
115
153
  .filter((value, index, array) => array.indexOf(value) === index) as string[];
154
+
116
155
  return {
117
156
  navigationEntries,
118
157
  groups
@@ -125,16 +164,23 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
125
164
  return;
126
165
 
127
166
  try {
128
- const [resolvedCollections = [], resolvedViews = []] = await Promise.all([
129
- resolveCollections(collectionsProp, authController, dataSourceDelegate, injectCollections),
130
- 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)
131
171
  ]
132
172
  );
133
- if (!equal(collectionsRef.current, resolvedCollections) || !equal(views, resolvedViews) || !equal(topLevelNavigation, 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
+ ) {
134
179
  collectionsRef.current = resolvedCollections;
135
180
  setCollections(resolvedCollections);
136
181
  setViews(resolvedViews);
137
- setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews));
182
+ setAdminViews(resolvedAdminViews);
183
+ setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
138
184
  }
139
185
  } catch (e) {
140
186
  console.error(e);
@@ -143,7 +189,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
143
189
 
144
190
  setNavigationLoading(false);
145
191
  setInitialised(true);
146
- }, [collectionsProp, authController.user, authController.initialLoading, baseViews, computeTopNavigation, injectCollections]);
192
+ }, [collectionsProp, collectionPermissions, authController.user, authController.initialLoading, viewsProp, adminViewsProp, computeTopNavigation, injectCollections]);
147
193
 
148
194
  useEffect(() => {
149
195
  refreshNavigation();
@@ -185,7 +231,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
185
231
  }, [
186
232
  basePath,
187
233
  baseCollectionPath,
188
- collections,
234
+ collections
189
235
  ]);
190
236
 
191
237
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
@@ -251,14 +297,6 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
251
297
  return resolveCollectionPathIds(path, collections);
252
298
  }, [collections]);
253
299
 
254
- const state = location.state as any;
255
- /**
256
- * The location can be overridden if `base_location` is set in the
257
- * state field of the current location. This can happen if you open
258
- * a side entity, like `products`, from a different one, like `users`
259
- */
260
- const baseLocation = state && state.base_location ? state.base_location : location;
261
-
262
300
  const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
263
301
  return getParentReferencesFromPath({
264
302
  path,
@@ -300,6 +338,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
300
338
  return {
301
339
  collections,
302
340
  views,
341
+ adminViews,
303
342
  loading: !initialised || navigationLoading,
304
343
  navigationLoadingError,
305
344
  homeUrl,
@@ -316,7 +355,6 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
316
355
  buildCMSUrlPath,
317
356
  resolveAliasesFrom,
318
357
  topLevelNavigation,
319
- baseLocation,
320
358
  refreshNavigation,
321
359
  getParentReferencesFromPath: getAllParentReferencesForPath,
322
360
  getParentCollectionIds,
@@ -341,8 +379,8 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
341
379
  return resolvedCollections
342
380
  .filter((c) => {
343
381
  if (!c.permissions) return true;
344
- const resolvedPermissions = resolvePermissions(c, authController, [c.path], null,)
345
- return resolvedPermissions.read !== false;
382
+ const resolvedPermissions = resolvePermissions(c, authController, c.path, null)
383
+ return resolvedPermissions?.read !== false;
346
384
  })
347
385
  .map((c) => {
348
386
  if (!c.subcollections) return c;
@@ -354,6 +392,7 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
354
392
  }
355
393
 
356
394
  async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
395
+ collectionPermissions: PermissionsBuilder | undefined,
357
396
  authController: AuthController,
358
397
  dataSource: DataSourceDelegate,
359
398
  injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
@@ -368,6 +407,8 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
368
407
  resolvedCollections = collections;
369
408
  }
370
409
 
410
+ resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
411
+
371
412
  resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
372
413
 
373
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
+ };
@@ -195,7 +195,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
195
195
 
196
196
  } else if (property.dataType === "boolean") {
197
197
  if (typeof value === "boolean") {
198
- content = <BooleanPreview value={value}/>;
198
+ content = <BooleanPreview value={value} size={size} property={property}/>;
199
199
  } else {
200
200
  content = buildWrongValueType(propertyKey, property.dataType, value);
201
201
  }
@@ -1,11 +1,24 @@
1
1
  import React from "react";
2
2
  import { Checkbox } from "@firecms/ui";
3
+ import { PreviewSize } from "../PropertyPreviewProps";
4
+ import { Property } from "../../types";
3
5
 
4
6
  /**
5
7
  * @group Preview components
6
8
  */
7
- export function BooleanPreview({ value }: {
8
- value: boolean
9
+ export function BooleanPreview({
10
+ value,
11
+ size,
12
+ property
13
+ }: {
14
+ value: boolean,
15
+ size: PreviewSize,
16
+ property: Property,
9
17
  }): React.ReactElement {
10
- return <Checkbox checked={value} color={"secondary"}/>;
18
+ return <div className={"flex flex-row gap-2 items-center"}>
19
+ <Checkbox checked={value}
20
+ size={size}
21
+ color={"secondary"}/>
22
+ {property.name && <span className={size === "tiny" ? "text-sm" : ""}>{property.name}</span>}
23
+ </div>;
11
24
  }
@@ -6,7 +6,7 @@ import { Chip } from "@firecms/ui";
6
6
  export interface EnumValuesChipProps {
7
7
  enumValues?: EnumValues;
8
8
  enumKey: string | number;
9
- size: "small" | "medium";
9
+ size: "tiny" | "small" | "medium";
10
10
  className?: string;
11
11
  children?: React.ReactNode;
12
12
  }
@@ -1,21 +1,17 @@
1
1
  import * as React from "react";
2
- import { useMemo } from "react";
3
2
 
4
- import { Entity, EntityCollection, EntityReference, ResolvedProperty } from "../../types";
5
-
6
- import { getReferencePreviewKeys, getValueInPath, resolveCollection } from "../../util";
3
+ import { Entity, EntityCollection, EntityReference } from "../../types";
7
4
  import {
8
5
  useCustomizationController,
9
6
  useEntityFetch,
10
7
  useNavigationController,
11
8
  useSideEntityController
12
9
  } from "../../hooks";
13
- import { PropertyPreview } from "../PropertyPreview";
14
10
  import { PreviewSize } from "../PropertyPreviewProps";
15
- import { SkeletonPropertyComponent } from "../property_previews/SkeletonPropertyComponent";
16
- import { cn, IconButton, KeyboardTabIcon, Skeleton, Tooltip, Typography } from "@firecms/ui";
11
+ import { IconButton, KeyboardTabIcon, Skeleton, Tooltip } from "@firecms/ui";
17
12
  import { ErrorView } from "../../components";
18
13
  import { useAnalyticsController } from "../../hooks/useAnalyticsController";
14
+ import { EntityPreview, EntityPreviewContainer } from "../../components/EntityPreview";
19
15
 
20
16
  export type ReferencePreviewProps = {
21
17
  disabled?: boolean;
@@ -23,7 +19,7 @@ export type ReferencePreviewProps = {
23
19
  size: PreviewSize;
24
20
  previewProperties?: string[];
25
21
  onClick?: (e: React.SyntheticEvent) => void;
26
- onHover?: boolean;
22
+ hover?: boolean;
27
23
  allowEntityNavigation?: boolean;
28
24
  };
29
25
 
@@ -34,12 +30,12 @@ export const ReferencePreview = React.memo<ReferencePreviewProps>(function Refer
34
30
  const reference = props.reference;
35
31
  if (!(typeof reference === "object" && "isEntityReference" in reference && reference.isEntityReference())) {
36
32
  console.warn("Reference preview received value of type", typeof reference);
37
- return <ReferencePreviewContainer
33
+ return <EntityPreviewContainer
38
34
  onClick={props.onClick}
39
35
  size={props.size}>
40
36
  <ErrorView error={"Unexpected value. Click to edit"}
41
37
  tooltip={JSON.stringify(reference)}/>
42
- </ReferencePreviewContainer>;
38
+ </EntityPreviewContainer>;
43
39
  }
44
40
  return <ReferencePreviewInternal {...props} />;
45
41
  }, areEqual) as React.FunctionComponent<ReferencePreviewProps>;
@@ -47,7 +43,7 @@ export const ReferencePreview = React.memo<ReferencePreviewProps>(function Refer
47
43
  function areEqual(prevProps: ReferencePreviewProps, nextProps: ReferencePreviewProps) {
48
44
  return prevProps.disabled === nextProps.disabled &&
49
45
  prevProps.size === nextProps.size &&
50
- prevProps.onHover === nextProps.onHover &&
46
+ prevProps.hover === nextProps.hover &&
51
47
  prevProps.reference?.id === nextProps.reference?.id &&
52
48
  prevProps.reference?.path === nextProps.reference?.path &&
53
49
  prevProps.allowEntityNavigation === nextProps.allowEntityNavigation
@@ -59,7 +55,7 @@ function ReferencePreviewInternal<M extends Record<string, any>>({
59
55
  reference,
60
56
  previewProperties,
61
57
  size,
62
- onHover,
58
+ hover,
63
59
  onClick,
64
60
  allowEntityNavigation = true
65
61
  }: ReferencePreviewProps) {
@@ -85,18 +81,22 @@ function ReferencePreviewInternal<M extends Record<string, any>>({
85
81
  disabled={disabled}
86
82
  allowEntityNavigation={allowEntityNavigation}
87
83
  onClick={onClick}
88
- onHover={onHover}/>
84
+ hover={hover}/>
89
85
  }
90
86
 
91
- function ReferencePreviewExisting<M extends Record<string, any> = any>({ reference, collection, previewProperties, size, disabled, allowEntityNavigation, onClick, onHover }: ReferencePreviewProps & {
87
+ function ReferencePreviewExisting<M extends Record<string, any> = any>({
88
+ reference,
89
+ collection,
90
+ previewProperties,
91
+ size,
92
+ disabled,
93
+ allowEntityNavigation,
94
+ onClick,
95
+ hover
96
+ }: ReferencePreviewProps & {
92
97
  collection: EntityCollection<M>
93
98
  }) {
94
99
 
95
- const customizationController = useCustomizationController();
96
-
97
- const analyticsController = useAnalyticsController();
98
- const sideEntityController = useSideEntityController();
99
-
100
100
  const {
101
101
  entity,
102
102
  dataLoading,
@@ -114,145 +114,53 @@ function ReferencePreviewExisting<M extends Record<string, any> = any>({ referen
114
114
 
115
115
  const usedEntity = entity ?? referencesCache.get(reference.pathWithId);
116
116
 
117
- const resolvedCollection = useMemo(() => resolveCollection({
118
- collection,
119
- path: reference.path,
120
- values: usedEntity?.values,
121
- fields: customizationController.propertyConfigs
122
- }), [collection]);
123
-
124
- const listProperties = useMemo(() => getReferencePreviewKeys(resolvedCollection, customizationController.propertyConfigs, previewProperties, size === "small" || size === "medium" ? 3 : 1),
125
- [previewProperties, resolvedCollection, size]);
126
-
127
117
  let body: React.ReactNode;
128
118
 
129
- if (!resolvedCollection) {
130
- return <ErrorView
131
- error={"Could not find collection with id " + resolvedCollection}/>
132
- }
133
-
134
119
  if (!reference) {
135
120
  body = <ErrorView error={"Reference not set"}/>;
136
121
  } else if (usedEntity && !usedEntity.values) {
137
122
  body = <ErrorView error={"Reference does not exist"}
138
123
  tooltip={reference.path}/>;
139
- } else {
140
- body = (
141
- <>
142
- <div
143
- className="flex flex-col flex-grow w-full max-w-[calc(100%-52px)] m-1">
144
-
145
- {size !== "tiny" && (
146
- reference
147
- ? <div className={`${
148
- size !== "medium"
149
- ? "block whitespace-nowrap overflow-hidden truncate"
150
- : ""
151
- }`}>
152
- <Typography variant={"caption"}
153
- className={"font-mono"}>
154
- {reference.id}
155
- </Typography>
156
- </div>
157
- : <Skeleton/>)}
158
-
159
- {listProperties && listProperties.map((key) => {
160
- const childProperty = resolvedCollection.properties[key as string];
161
- if (!childProperty) return null;
162
-
163
- return (
164
- <div key={"ref_prev_" + (key as string)}
165
- className={listProperties.length > 1 ? "my-0.5" : "my-0"}>
166
- {
167
- usedEntity
168
- ? <PropertyPreview
169
- propertyKey={key as string}
170
- value={getValueInPath(usedEntity.values, key)}
171
- property={childProperty as ResolvedProperty}
172
- // entity={usedEntity}
173
- size={"tiny"}/>
174
- : <SkeletonPropertyComponent
175
- property={childProperty as ResolvedProperty}
176
- size={"tiny"}/>
177
- }
178
- </div>
179
- );
180
- })}
181
-
182
- </div>
183
- <div className={`my-${size === "tiny" ? 2 : 4}`}>
184
- {!disabled && usedEntity && allowEntityNavigation &&
185
- <Tooltip title={`See details for ${usedEntity.id}`}>
186
- <IconButton
187
- color={"inherit"}
188
- size={"small"}
189
- onClick={(e) => {
190
- e.stopPropagation();
191
- analyticsController.onAnalyticsEvent?.("entity_click_from_reference", {
192
- path: usedEntity.path,
193
- entityId: usedEntity.id
194
- });
195
- sideEntityController.open({
196
- entityId: usedEntity.id,
197
- path: usedEntity.path,
198
- collection: resolvedCollection,
199
- updateUrl: true
200
- });
201
- }}>
202
- <KeyboardTabIcon size={"small"}/>
203
- </IconButton>
204
- </Tooltip>}
205
- </div>
206
- </>
124
+ }
125
+ if (body) {
126
+
127
+ return (
128
+ <EntityPreviewContainer onClick={disabled ? undefined : onClick}
129
+ hover={disabled ? undefined : hover}
130
+ size={size}>
131
+ {body}
132
+ </EntityPreviewContainer>
207
133
  );
208
134
  }
209
135
 
210
- return (
211
- <ReferencePreviewContainer onClick={disabled ? undefined : onClick}
212
- onHover={disabled ? undefined : onHover}
213
- size={size}>
214
- {body}
215
- </ReferencePreviewContainer>
216
- );
217
- }
218
-
219
- export function ReferencePreviewContainer({
220
- children,
221
- onHover,
222
- size,
223
- onClick
224
- }: {
225
- children: React.ReactNode;
226
- onHover?: boolean;
227
- size: PreviewSize;
228
- onClick?: (e: React.SyntheticEvent) => void;
229
- }) {
230
- return <Typography variant={"label"}
231
- className={cn("bg-opacity-70 bg-gray-100 dark:bg-gray-800 dark:bg-opacity-60",
232
- "w-full",
233
- "flex",
234
- "rounded-md",
235
- "overflow-hidden",
236
- onHover ? "hover:bg-opacity-90 dark:hover:bg-opacity-90" : "",
237
- size === "medium" ? "p-2" : "p-1",
238
- size === "tiny" ? "items-center" : "",
239
- "transition-colors duration-300 ease-in-out ",
240
- // onHover ? "shadow-outline" : "",
241
- onClick ? "cursor-pointer" : "")}
242
- style={{
243
- // @ts-ignore
244
- tabindex: 0
245
- }}
246
- onClick={(event) => {
247
- if (onClick) {
248
- event.preventDefault();
249
- onClick(event);
250
- }
251
- }}>
136
+ if (dataLoading && !usedEntity) {
137
+ return (
138
+ <EntityPreviewContainer onClick={disabled ? undefined : onClick}
139
+ hover={disabled ? undefined : hover}
140
+ size={size}>
141
+ <Skeleton/>
142
+ </EntityPreviewContainer>
143
+ );
144
+ }
252
145
 
253
- {children}
146
+ if (!usedEntity) {
147
+ return (
148
+ <EntityPreviewContainer onClick={disabled ? undefined : onClick}
149
+ hover={disabled ? undefined : hover}
150
+ size={size}>
151
+ <ErrorView error={"Entity not found"}/>
152
+ </EntityPreviewContainer>
153
+ );
154
+ }
155
+ return <EntityPreview size={size}
156
+ previewProperties={previewProperties}
157
+ disabled={disabled}
158
+ entity={usedEntity}
159
+ collection={collection}
160
+ onClick={onClick}
161
+ includeEntityNavigation={allowEntityNavigation}
162
+ hover={hover}/>;
254
163
 
255
- </Typography>
256
164
  }
257
165
 
258
166
  const referencesCache = new Map<string, Entity<any>>();