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

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 (132) 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 +13 -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 +2 -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/types/roles.d.ts +31 -0
  50. package/dist/types/user.d.ts +5 -0
  51. package/dist/util/collections.d.ts +9 -1
  52. package/dist/util/icons.d.ts +8 -2
  53. package/dist/util/permissions.d.ts +4 -4
  54. package/dist/util/references.d.ts +4 -2
  55. package/dist/util/resolutions.d.ts +1 -1
  56. package/package.json +23 -23
  57. package/src/components/DeleteEntityDialog.tsx +4 -4
  58. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
  59. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +273 -277
  60. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  61. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +12 -13
  62. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +8 -15
  63. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +3 -3
  64. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
  65. package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +9 -5
  66. package/src/components/EntityCollectionView/EntityCollectionView.tsx +28 -49
  67. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
  68. package/src/components/EntityCollectionView/useSelectionController.tsx +30 -0
  69. package/src/components/EntityPreview.tsx +204 -70
  70. package/src/components/EntityView.tsx +84 -0
  71. package/src/components/FieldCaption.tsx +14 -0
  72. package/src/components/FireCMSAppBar.tsx +8 -0
  73. package/src/components/HomePage/DefaultHomePage.tsx +13 -9
  74. package/src/components/HomePage/NavigationCard.tsx +69 -0
  75. package/src/components/HomePage/NavigationCardBinding.tsx +116 -0
  76. package/src/components/HomePage/SmallNavigationCard.tsx +45 -0
  77. package/src/components/HomePage/index.tsx +3 -1
  78. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -4
  79. package/src/components/ReferenceWidget.tsx +3 -3
  80. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +11 -19
  81. package/src/components/VirtualTable/VirtualTableProps.tsx +1 -1
  82. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +1 -1
  83. package/src/components/index.tsx +4 -2
  84. package/src/core/Drawer.tsx +66 -39
  85. package/src/core/{EntityView.tsx → EntityEditView.tsx} +20 -37
  86. package/src/core/EntitySidePanel.tsx +2 -2
  87. package/src/core/FireCMS.tsx +18 -2
  88. package/src/core/NavigationRoutes.tsx +8 -0
  89. package/src/core/SideEntityView.tsx +2 -2
  90. package/src/form/EntityForm.tsx +19 -11
  91. package/src/form/components/StorageItemPreview.tsx +5 -3
  92. package/src/form/components/StorageUploadProgress.tsx +6 -5
  93. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +8 -12
  94. package/src/form/field_bindings/KeyValueFieldBinding.tsx +15 -15
  95. package/src/form/field_bindings/MapFieldBinding.tsx +15 -15
  96. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +1 -1
  97. package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -0
  98. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +14 -5
  99. package/src/form/field_bindings/TextFieldBinding.tsx +7 -5
  100. package/src/form/validation.ts +3 -4
  101. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  102. package/src/hooks/data/useDataSource.tsx +8 -3
  103. package/src/hooks/data/useEntityFetch.tsx +1 -1
  104. package/src/hooks/useBuildNavigationController.tsx +79 -38
  105. package/src/hooks/useProjectLog.tsx +11 -3
  106. package/src/hooks/useReferenceDialog.tsx +2 -2
  107. package/src/hooks/useStorageSource.tsx +7 -2
  108. package/src/preview/PropertyPreview.tsx +1 -1
  109. package/src/preview/components/BooleanPreview.tsx +16 -3
  110. package/src/preview/components/EnumValuesChip.tsx +1 -1
  111. package/src/preview/components/ReferencePreview.tsx +54 -146
  112. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
  113. package/src/types/analytics.ts +1 -0
  114. package/src/types/auth.tsx +17 -1
  115. package/src/types/collections.ts +16 -1
  116. package/src/types/entity_actions.tsx +4 -0
  117. package/src/types/entity_overrides.tsx +7 -0
  118. package/src/types/firecms.tsx +0 -1
  119. package/src/types/index.ts +2 -0
  120. package/src/types/navigation.ts +11 -10
  121. package/src/types/permissions.ts +6 -1
  122. package/src/types/plugins.tsx +22 -25
  123. package/src/types/properties.ts +3 -2
  124. package/src/types/property_config.tsx +2 -2
  125. package/src/types/roles.ts +41 -0
  126. package/src/types/side_entity_controller.tsx +1 -0
  127. package/src/types/user.ts +7 -0
  128. package/src/util/collections.ts +22 -0
  129. package/src/util/icons.tsx +11 -3
  130. package/src/util/permissions.ts +11 -8
  131. package/src/util/references.ts +36 -5
  132. package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
@@ -1,84 +1,218 @@
1
- import React, { useMemo } from "react";
2
- import { PropertyPreview } from "../preview";
3
- import { Entity, EntityCollection, ResolvedEntityCollection, ResolvedProperties } from "../types";
4
- import { resolveCollection } from "../util";
5
- import { cn, defaultBorderMixin, IconButton, OpenInNewIcon } from "@firecms/ui";
6
- import { CustomizationController } from "../types/customization_controller";
7
- import { useCustomizationController } from "../hooks/useCustomizationController";
1
+ import * as React from "react";
2
+ import { useMemo } from "react";
3
+
4
+ import { Entity, EntityCollection, ResolvedProperty } from "../types";
5
+
6
+ import {
7
+ getEntityImagePreviewPropertyKey,
8
+ getEntityPreviewKeys,
9
+ getEntityTitlePropertyKey,
10
+ getValueInPath,
11
+ resolveCollection
12
+ } from "../util";
13
+ import { cn, defaultBorderMixin, IconButton, KeyboardTabIcon, Skeleton, Tooltip, Typography } from "@firecms/ui";
14
+ import { PreviewSize, PropertyPreview, SkeletonPropertyComponent } from "../preview";
15
+ import { useCustomizationController, useNavigationController, useSideEntityController } from "../hooks";
16
+ import { useAnalyticsController } from "../hooks/useAnalyticsController";
17
+
18
+ export type EntityPreviewProps = {
19
+ size: PreviewSize,
20
+ actions?: React.ReactNode,
21
+ collection?: EntityCollection,
22
+ hover?: boolean;
23
+ previewProperties?: string[],
24
+ disabled: undefined | boolean,
25
+ entity: Entity<any>,
26
+ includeEntityNavigation?: boolean,
27
+ onClick?: (e: React.SyntheticEvent) => void;
28
+ };
8
29
 
9
30
  /**
10
- * @group Components
31
+ * This view is used to display a preview of an entity.
32
+ * It is used by default in reference fields and whenever a reference is displayed.
11
33
  */
12
- export interface EntityPreviewProps<M extends Record<string, any>> {
13
- entity: Entity<M>;
14
- collection: EntityCollection<M>;
15
- path: string;
16
- className?: string;
17
- }
34
+ export function EntityPreview({
35
+ actions,
36
+ disabled,
37
+ hover,
38
+ collection: collectionProp,
39
+ previewProperties,
40
+ onClick,
41
+ size,
42
+ includeEntityNavigation,
43
+ entity
44
+ }: EntityPreviewProps) {
18
45
 
19
- export function EntityPreview<M extends Record<string, any>>(
20
- {
21
- entity,
22
- collection,
23
- path,
24
- className
25
- }: EntityPreviewProps<M>) {
46
+ const analyticsController = useAnalyticsController();
47
+ const sideEntityController = useSideEntityController();
48
+ const customizationController = useCustomizationController();
49
+
50
+ const navigationController = useNavigationController();
51
+
52
+ const collection = collectionProp ?? navigationController.getCollection<EntityCollection>(entity.path);
26
53
 
27
- const customizationController: CustomizationController = useCustomizationController();
28
- const resolvedCollection: ResolvedEntityCollection<M> = useMemo(() => resolveCollection<M>({
54
+ if (!collection) {
55
+ throw Error(`Couldn't find the corresponding collection view for the path: ${entity.path}`);
56
+ }
57
+
58
+ const resolvedCollection = React.useMemo(() => resolveCollection({
29
59
  collection,
30
- path,
31
- entityId: entity.id,
60
+ path: entity.path,
32
61
  values: entity.values,
33
62
  fields: customizationController.propertyConfigs
34
- }), [collection, path, entity]);
63
+ }), [collection]);
35
64
 
36
- const properties: ResolvedProperties = resolvedCollection.properties;
65
+ const listProperties = useMemo(() => getEntityPreviewKeys(resolvedCollection, customizationController.propertyConfigs, previewProperties, size === "small" || size === "medium" ? 3 : 1),
66
+ [previewProperties, resolvedCollection, size]);
37
67
 
38
- return (
39
- <div className={"w-full " + className}>
40
- <div className={"w-full mb-4"}>
41
- <div className={cn(defaultBorderMixin, "flex justify-between py-2 border-b last:border-b-0")}>
42
- <div className="flex items-center w-1/4">
43
- <span className="pl-2 text-sm text-gray-600">Id</span>
44
- </div>
45
- <div
46
- className="flex-grow p-2 ml-2 w-3/4 text-gray-900 dark:text-white min-h-[56px] flex items-center">
47
- <span className="flex-grow mr-2">{entity.id}</span>
48
- {customizationController?.entityLinkBuilder &&
49
- <a href={customizationController.entityLinkBuilder({ entity })}
50
- rel="noopener noreferrer"
51
- target="_blank">
52
- <IconButton>
53
- <OpenInNewIcon
54
- size={"small"}/>
55
- </IconButton>
56
- </a>}
68
+ const titleProperty = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
69
+ const imagePropertyKey = getEntityImagePreviewPropertyKey(resolvedCollection);
70
+ const imageProperty = imagePropertyKey ? resolvedCollection.properties[imagePropertyKey] : undefined;
71
+
72
+ const restProperties = listProperties.filter(p => p !== titleProperty && p !== imagePropertyKey);
73
+
74
+ return <EntityPreviewContainer onClick={disabled ? undefined : onClick}
75
+ hover={disabled ? undefined : hover}
76
+ size={size}>
77
+ {imageProperty && (
78
+ <div className={cn("w-10 h-10 mr-2 shrink-0 grow-0", size === "tiny" ? "my-0.5" : "m-2 self-start")}>
79
+ <PropertyPreview property={imageProperty}
80
+ propertyKey={imagePropertyKey as string}
81
+ size={"tiny"}
82
+ value={getValueInPath(entity.values, imagePropertyKey as string)}/>
83
+ </div>
84
+ )}
85
+
86
+ <div className={"flex flex-col flex-grow w-full m-1"}>
87
+
88
+ {size !== "tiny" && (
89
+ entity
90
+ ? <div className={`${
91
+ size !== "medium"
92
+ ? "block whitespace-nowrap overflow-hidden truncate"
93
+ : ""
94
+ }`}>
95
+ <Typography variant={"caption"}
96
+ color={"disabled"}
97
+ className={"font-mono"}>
98
+ {entity.id}
99
+ </Typography>
57
100
  </div>
101
+ : <Skeleton/>)}
102
+
103
+ {titleProperty && (
104
+ <div className={"my-0.5 text-sm font-medium"}>
105
+ {
106
+ entity
107
+ ? <PropertyPreview
108
+ propertyKey={titleProperty as string}
109
+ value={getValueInPath(entity.values, titleProperty)}
110
+ property={resolvedCollection.properties[titleProperty as string] as ResolvedProperty}
111
+ size={"medium"}/>
112
+ : <SkeletonPropertyComponent
113
+ property={resolvedCollection.properties[titleProperty as string] as ResolvedProperty}
114
+ size={"medium"}/>
115
+ }
58
116
  </div>
59
- {Object.entries(properties)
60
- .map(([key, property]) => {
61
- const value = (entity.values)[key];
62
- return (
63
- <div
64
- key={`reference_previews_${key}`}
65
- className={cn(defaultBorderMixin, "flex justify-between py-2 border-b last:border-b-0")}>
66
- <div className="flex items-center w-1/4">
67
- <span className="pl-2 text-sm text-gray-600">{property.name}</span>
68
- </div>
69
- <div
70
- className="flex-grow p-2 ml-2 w-3/4 text-gray-900 dark:text-white min-h-[56px] flex items-center">
71
- <PropertyPreview
72
- propertyKey={key}
73
- value={value}
74
- // entity={entity}
75
- property={property}
76
- size={"medium"}/>
77
- </div>
78
- </div>
79
- )
80
- })}
81
- </div>
117
+ )}
118
+
119
+ {restProperties && restProperties.map((key) => {
120
+ const childProperty = resolvedCollection.properties[key as string];
121
+ if (!childProperty) return null;
122
+
123
+ return (
124
+ <div key={"ref_prev_" + key}
125
+ className={restProperties.length > 1 ? "my-0.5" : "my-0"}>
126
+ {
127
+ entity
128
+ ? <PropertyPreview
129
+ propertyKey={key as string}
130
+ value={getValueInPath(entity.values, key)}
131
+ property={childProperty as ResolvedProperty}
132
+ size={"tiny"}/>
133
+ : <SkeletonPropertyComponent
134
+ property={childProperty as ResolvedProperty}
135
+ size={"tiny"}/>
136
+ }
137
+ </div>
138
+ );
139
+ })}
140
+
82
141
  </div>
83
- );
142
+
143
+ {entity && includeEntityNavigation &&
144
+ <Tooltip title={`See details for ${entity.id}`}
145
+ className={size !== "tiny" ? "self-start" : ""}>
146
+ <IconButton
147
+ color={"inherit"}
148
+ size={"small"}
149
+ onClick={(e) => {
150
+ e.stopPropagation();
151
+ analyticsController.onAnalyticsEvent?.("entity_click_from_reference", {
152
+ path: entity.path,
153
+ entityId: entity.id
154
+ });
155
+ sideEntityController.open({
156
+ entityId: entity.id,
157
+ path: entity.path,
158
+ collection,
159
+ updateUrl: true
160
+ });
161
+ }}>
162
+ <KeyboardTabIcon size={"small"}/>
163
+ </IconButton>
164
+ </Tooltip>}
165
+
166
+ {actions}
167
+
168
+ </EntityPreviewContainer>;
84
169
  }
170
+
171
+ type EntityPreviewContainerProps = {
172
+ children: React.ReactNode;
173
+ hover?: boolean;
174
+ size: PreviewSize;
175
+ className?: string;
176
+ style?: React.CSSProperties;
177
+ onClick?: (e: React.SyntheticEvent) => void;
178
+ };
179
+
180
+ const EntityPreviewContainerInner = React.forwardRef<HTMLDivElement, EntityPreviewContainerProps>(({
181
+ children,
182
+ hover,
183
+ onClick,
184
+ size,
185
+ style,
186
+ className,
187
+ ...props
188
+ }, ref) => {
189
+ return <div
190
+ ref={ref}
191
+ style={{
192
+ ...style,
193
+ // @ts-ignore
194
+ tabindex: 0
195
+ }}
196
+ className={cn(
197
+ "bg-white dark:bg-gray-900",
198
+ "items-center",
199
+ hover ? "hover:bg-slate-50 dark:hover:bg-gray-800 group-hover:bg-slate-50 dark:group-hover:bg-gray-800" : "",
200
+ size === "tiny" ? "p-1" : "p-2",
201
+ "flex border rounded-lg",
202
+ onClick ? "cursor-pointer" : "",
203
+ defaultBorderMixin,
204
+ className)}
205
+ onClick={(event) => {
206
+ if (onClick) {
207
+ event.preventDefault();
208
+ onClick(event);
209
+ }
210
+ }}
211
+ {...props}>
212
+ {children}
213
+ </div>;
214
+ });
215
+
216
+ EntityPreviewContainerInner.displayName = "EntityPreviewContainer";
217
+
218
+ export const EntityPreviewContainer = React.memo(EntityPreviewContainerInner) as React.FC<EntityPreviewContainerProps>;
@@ -0,0 +1,84 @@
1
+ import React, { useMemo } from "react";
2
+ import { PropertyPreview } from "../preview";
3
+ import { Entity, EntityCollection, ResolvedEntityCollection, ResolvedProperties } from "../types";
4
+ import { resolveCollection } from "../util";
5
+ import { cn, defaultBorderMixin, IconButton, OpenInNewIcon } from "@firecms/ui";
6
+ import { CustomizationController } from "../types/customization_controller";
7
+ import { useCustomizationController } from "../hooks/useCustomizationController";
8
+
9
+ /**
10
+ * @group Components
11
+ */
12
+ export interface EntityViewProps<M extends Record<string, any>> {
13
+ entity: Entity<M>;
14
+ collection: EntityCollection<M>;
15
+ path: string;
16
+ className?: string;
17
+ }
18
+
19
+ export function EntityView<M extends Record<string, any>>(
20
+ {
21
+ entity,
22
+ collection,
23
+ path,
24
+ className
25
+ }: EntityViewProps<M>) {
26
+
27
+ const customizationController: CustomizationController = useCustomizationController();
28
+ const resolvedCollection: ResolvedEntityCollection<M> = useMemo(() => resolveCollection<M>({
29
+ collection,
30
+ path,
31
+ entityId: entity.id,
32
+ values: entity.values,
33
+ fields: customizationController.propertyConfigs
34
+ }), [collection, path, entity]);
35
+
36
+ const properties: ResolvedProperties = resolvedCollection.properties;
37
+
38
+ return (
39
+ <div className={"w-full " + className}>
40
+ <div className={"w-full mb-4"}>
41
+ <div className={cn(defaultBorderMixin, "flex justify-between py-2 border-b last:border-b-0")}>
42
+ <div className="flex items-center w-1/4">
43
+ <span className="pl-2 text-sm text-gray-600">Id</span>
44
+ </div>
45
+ <div
46
+ className="flex-grow p-2 ml-2 w-3/4 text-gray-900 dark:text-white min-h-[56px] flex items-center">
47
+ <span className="flex-grow mr-2">{entity.id}</span>
48
+ {customizationController?.entityLinkBuilder &&
49
+ <a href={customizationController.entityLinkBuilder({ entity })}
50
+ rel="noopener noreferrer"
51
+ target="_blank">
52
+ <IconButton>
53
+ <OpenInNewIcon
54
+ size={"small"}/>
55
+ </IconButton>
56
+ </a>}
57
+ </div>
58
+ </div>
59
+ {Object.entries(properties)
60
+ .map(([key, property]) => {
61
+ const value = (entity.values)[key];
62
+ return (
63
+ <div
64
+ key={`reference_previews_${key}`}
65
+ className={cn(defaultBorderMixin, "flex justify-between py-2 border-b last:border-b-0")}>
66
+ <div className="flex items-center w-1/4">
67
+ <span className="pl-2 text-sm text-gray-600">{property.name}</span>
68
+ </div>
69
+ <div
70
+ className="flex-grow p-2 ml-2 w-3/4 text-gray-900 dark:text-white min-h-[56px] flex items-center">
71
+ <PropertyPreview
72
+ propertyKey={key}
73
+ value={value}
74
+ // entity={entity}
75
+ property={property}
76
+ size={"medium"}/>
77
+ </div>
78
+ </div>
79
+ )
80
+ })}
81
+ </div>
82
+ </div>
83
+ );
84
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import { Typography } from "@firecms/ui";
3
+
4
+ export function FieldCaption({
5
+ error,
6
+ children
7
+ }: { error?: boolean, children?: React.ReactNode }) {
8
+ if (!children) return null;
9
+ return (
10
+ <Typography variant={"caption"} color={error ? "error" : "secondary"} className={"ml-3.5 mt-0.5"}>
11
+ {children}
12
+ </Typography>
13
+ );
14
+ }
@@ -141,6 +141,14 @@ export const FireCMSAppBar = function FireCMSAppBar({
141
141
  </IconButton>
142
142
 
143
143
  <Menu trigger={avatarComponent}>
144
+ {user && <div className={"px-4 py-2 mb-2"}>
145
+ {user.displayName && <Typography variant={"body1"} color={"secondary"}>
146
+ {user.displayName}
147
+ </Typography>}
148
+ {user.email && <Typography variant={"body2"} color={"secondary"}>
149
+ {user.email}
150
+ </Typography>}
151
+ </div>}
144
152
 
145
153
  {dropDownActions}
146
154
 
@@ -1,11 +1,11 @@
1
1
  import React, { useCallback, useEffect, useState } from "react";
2
2
 
3
3
  import { useCustomizationController, useFireCMSContext, useNavigationController } from "../../hooks";
4
- import { PluginGenericProps, PluginHomePageAdditionalCardsProps } from "../../types";
4
+ import { CMSAnalyticsEvent, PluginGenericProps, PluginHomePageAdditionalCardsProps } from "../../types";
5
5
 
6
6
  import { toArray } from "../../util/arrays";
7
7
  import { NavigationGroup } from "./NavigationGroup";
8
- import { NavigationCollectionCard } from "./NavigationCollectionCard";
8
+ import { NavigationCardBinding } from "./NavigationCardBinding";
9
9
 
10
10
  // @ts-ignore
11
11
  import * as JsSearch from "js-search";
@@ -173,15 +173,19 @@ export function DefaultHomePage({
173
173
  <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
174
174
  {thisGroupCollections.map((entry) => (
175
175
  <div key={`nav_${entry.group}_${entry.name}`} className="col-span-1">
176
- <NavigationCollectionCard
176
+ <NavigationCardBinding
177
177
  {...entry}
178
178
  onClick={() => {
179
- const event =
180
- entry.type === "collection"
181
- ? "home_navigate_to_collection"
182
- : entry.type === "view"
183
- ? "home_navigate_to_view"
184
- : "unmapped_event";
179
+ let event: CMSAnalyticsEvent;
180
+ if (entry.type === "collection") {
181
+ event = "home_navigate_to_collection";
182
+ } else if (entry.type === "view") {
183
+ event = "home_navigate_to_view";
184
+ } else if (entry.type === "admin") {
185
+ event = "home_navigate_to_admin_view";
186
+ } else {
187
+ event = "unmapped_event";
188
+ }
185
189
  context.analyticsController?.onAnalyticsEvent?.(event, { path: entry.path });
186
190
  }}
187
191
  />
@@ -0,0 +1,69 @@
1
+ import { ArrowForwardIcon, Card, cn, Markdown, Typography, } from "@firecms/ui";
2
+
3
+ export type NavigationCardProps = {
4
+ name: string,
5
+ description?: string;
6
+ actions: React.ReactNode;
7
+ icon: React.ReactNode;
8
+ onClick?: () => void,
9
+ };
10
+
11
+ export function NavigationCard({
12
+ name,
13
+ description,
14
+ icon,
15
+ actions,
16
+ onClick,
17
+ }: NavigationCardProps) {
18
+
19
+ return (<Card
20
+ className={cn("h-full p-4 cursor-pointer min-h-[230px]")}
21
+ onClick={() => {
22
+ onClick?.();
23
+ }}>
24
+
25
+ <div className="flex flex-col items-start h-full">
26
+ <div
27
+ className="flex-grow w-full">
28
+
29
+ <div
30
+ className="h-10 flex items-center w-full justify-between text-gray-300 dark:text-gray-600">
31
+
32
+ {icon}
33
+
34
+ <div
35
+ className="flex items-center gap-1"
36
+ onClick={(event: React.MouseEvent) => {
37
+ event.preventDefault();
38
+ event.stopPropagation();
39
+ }}>
40
+
41
+ {actions}
42
+
43
+ </div>
44
+
45
+ </div>
46
+
47
+ <Typography gutterBottom variant="h5"
48
+ component="h2">
49
+ {name}
50
+ </Typography>
51
+
52
+ {description && <Typography variant="body2"
53
+ color="secondary"
54
+ component="div">
55
+ <Markdown source={description}/>
56
+ </Typography>}
57
+ </div>
58
+
59
+ <div style={{ alignSelf: "flex-end" }}>
60
+
61
+ <div className={"p-4"}>
62
+ <ArrowForwardIcon className="text-primary"/>
63
+ </div>
64
+ </div>
65
+
66
+ </div>
67
+
68
+ </Card>)
69
+ }
@@ -0,0 +1,116 @@
1
+ import { useNavigate } from "react-router-dom";
2
+
3
+ import { useCustomizationController, useFireCMSContext } from "../../hooks";
4
+ import { PluginHomePageActionsProps, TopNavigationEntry } from "../../types";
5
+ import { IconForView } from "../../util";
6
+ import { useUserConfigurationPersistence } from "../../hooks/useUserConfigurationPersistence";
7
+ import { IconButton, StarBorderIcon, StarIcon } from "@firecms/ui";
8
+ import { NavigationCard } from "./NavigationCard";
9
+ import { SmallNavigationCard } from "./SmallNavigationCard";
10
+
11
+ /**
12
+ * This is the component used in the home page to render a card for each
13
+ * collection or view.
14
+ * @group Components
15
+ * @param view
16
+ * @param path
17
+ * @param collection
18
+ * @param url
19
+ * @param name
20
+ * @param description
21
+ * @param onClick
22
+ * @constructor
23
+ */
24
+ export function NavigationCardBinding({
25
+ path,
26
+ collection,
27
+ view,
28
+ url,
29
+ name,
30
+ description,
31
+ onClick,
32
+ type
33
+ }: TopNavigationEntry & {
34
+ onClick?: () => void
35
+ }) {
36
+
37
+ const userConfigurationPersistence = useUserConfigurationPersistence();
38
+ const collectionIcon = <IconForView collectionOrView={collection ?? view}/>;
39
+
40
+ const navigate = useNavigate();
41
+ const context = useFireCMSContext();
42
+ const customizationController = useCustomizationController();
43
+
44
+ const favourite = (userConfigurationPersistence?.favouritePaths ?? []).includes(path);
45
+
46
+ const actionsArray: React.ReactNode[] = userConfigurationPersistence
47
+ ? [
48
+ <IconButton
49
+ key={"favourite"}
50
+ onClick={(e) => {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+ if (favourite) {
54
+ userConfigurationPersistence.setFavouritePaths(
55
+ userConfigurationPersistence.favouritePaths.filter(p => p !== path)
56
+ );
57
+ } else {
58
+ userConfigurationPersistence.setFavouritePaths(
59
+ [...userConfigurationPersistence.favouritePaths, path]
60
+ );
61
+ }
62
+ }}>
63
+ {
64
+ favourite
65
+ ? <StarIcon
66
+ size={18}
67
+ className={"text-secondary"}/>
68
+ : <StarBorderIcon
69
+ size={18}
70
+ className={"text-gray-400 dark:text-gray-500"}/>}
71
+ </IconButton>
72
+ ]
73
+ : [];
74
+
75
+ if (customizationController.plugins && collection) {
76
+ const actionProps: PluginHomePageActionsProps = {
77
+ path,
78
+ collection,
79
+ context
80
+ };
81
+ customizationController.plugins.forEach((plugin, i) => (
82
+ actionsArray.push(plugin.homePage?.CollectionActions
83
+ ? <plugin.homePage.CollectionActions
84
+ key={`actions_${i}`}
85
+ {...actionProps}
86
+ extraProps={plugin.homePage.extraProps}
87
+ />
88
+ : null
89
+ )))
90
+ }
91
+
92
+ const actions: React.ReactNode | undefined = <>
93
+ {actionsArray}
94
+ </>
95
+
96
+ if (type === "admin") {
97
+ return <SmallNavigationCard icon={collectionIcon}
98
+ name={name}
99
+ url={url}/>
100
+ }
101
+
102
+ return <NavigationCard
103
+ icon={collectionIcon}
104
+ name={name}
105
+ description={description}
106
+ actions={actions}
107
+ onClick={() => {
108
+ onClick?.();
109
+ navigate(url);
110
+ if (userConfigurationPersistence) {
111
+ userConfigurationPersistence.setRecentlyVisitedPaths(
112
+ [path, ...(userConfigurationPersistence.recentlyVisitedPaths ?? []).filter(p => p !== path)]
113
+ );
114
+ }
115
+ }}/>;
116
+ }