@firecms/core 3.0.0-canary.4 → 3.0.0-canary.41

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 (195) hide show
  1. package/README.md +2 -2
  2. package/dist/components/ClearFilterSortButton.d.ts +5 -0
  3. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +11 -11
  4. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  5. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +2 -2
  6. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
  7. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +12 -3
  8. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
  9. package/dist/components/EntityCollectionView/useSelectionController.d.ts +2 -0
  10. package/dist/components/EntityPreview.d.ts +25 -7
  11. package/dist/components/EntityView.d.ts +11 -0
  12. package/dist/components/FieldCaption.d.ts +5 -0
  13. package/dist/components/HomePage/NavigationCard.d.ts +8 -0
  14. package/dist/components/HomePage/{NavigationCollectionCard.d.ts → NavigationCardBinding.d.ts} +2 -2
  15. package/dist/components/HomePage/SmallNavigationCard.d.ts +6 -0
  16. package/dist/components/HomePage/index.d.ts +3 -1
  17. package/dist/components/VirtualTable/VirtualTableProps.d.ts +1 -1
  18. package/dist/components/index.d.ts +4 -3
  19. package/dist/contexts/AuthControllerContext.d.ts +1 -1
  20. package/dist/{internal/EntityView.d.ts → core/EntityEditView.d.ts} +2 -2
  21. package/dist/core/SideEntityView.d.ts +7 -0
  22. package/dist/core/index.d.ts +0 -2
  23. package/dist/form/EntityForm.d.ts +1 -1
  24. package/dist/form/components/ErrorFocus.d.ts +1 -1
  25. package/dist/form/components/StorageItemPreview.d.ts +3 -2
  26. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  27. package/dist/form/components/index.d.ts +1 -0
  28. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  29. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  30. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +4 -3
  31. package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -2
  32. package/dist/form/index.d.ts +1 -0
  33. package/dist/form/validation.d.ts +1 -1
  34. package/dist/hooks/data/delete.d.ts +2 -2
  35. package/dist/hooks/data/save.d.ts +1 -1
  36. package/dist/hooks/data/useDataSource.d.ts +2 -2
  37. package/dist/hooks/data/useEntityFetch.d.ts +3 -3
  38. package/dist/hooks/index.d.ts +3 -1
  39. package/dist/{core → hooks}/useBuildModeController.d.ts +1 -1
  40. package/dist/hooks/useBuildNavigationController.d.ts +6 -4
  41. package/dist/hooks/useProjectLog.d.ts +6 -2
  42. package/dist/hooks/useStorageSource.d.ts +2 -2
  43. package/dist/hooks/useValidateAuthenticator.d.ts +25 -0
  44. package/dist/index.es.js +8258 -7767
  45. package/dist/index.es.js.map +1 -1
  46. package/dist/index.umd.js +5 -5
  47. package/dist/index.umd.js.map +1 -1
  48. package/dist/internal/useBuildDataSource.d.ts +4 -0
  49. package/dist/preview/PropertyPreview.d.ts +1 -1
  50. package/dist/preview/PropertyPreviewProps.d.ts +1 -4
  51. package/dist/preview/components/BooleanPreview.d.ts +5 -1
  52. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  53. package/dist/preview/components/ReferencePreview.d.ts +1 -7
  54. package/dist/types/analytics.d.ts +1 -1
  55. package/dist/types/auth.d.ts +37 -1
  56. package/dist/types/collections.d.ts +22 -5
  57. package/dist/types/datasource.d.ts +1 -1
  58. package/dist/types/entities.d.ts +1 -1
  59. package/dist/types/entity_callbacks.d.ts +2 -2
  60. package/dist/types/entity_overrides.d.ts +6 -0
  61. package/dist/types/index.d.ts +2 -0
  62. package/dist/types/navigation.d.ts +14 -13
  63. package/dist/types/permissions.d.ts +5 -1
  64. package/dist/types/plugins.d.ts +20 -20
  65. package/dist/types/properties.d.ts +2 -2
  66. package/dist/types/property_config.d.ts +2 -2
  67. package/dist/types/roles.d.ts +31 -0
  68. package/dist/types/storage.d.ts +11 -3
  69. package/dist/types/user.d.ts +5 -0
  70. package/dist/util/collections.d.ts +9 -1
  71. package/dist/util/entities.d.ts +1 -1
  72. package/dist/util/icons.d.ts +8 -2
  73. package/dist/util/navigation_utils.d.ts +2 -2
  74. package/dist/util/permissions.d.ts +4 -4
  75. package/dist/util/references.d.ts +4 -2
  76. package/dist/util/resolutions.d.ts +6 -6
  77. package/dist/util/useTraceUpdate.d.ts +1 -0
  78. package/package.json +27 -24
  79. package/src/components/ClearFilterSortButton.tsx +41 -0
  80. package/src/components/DeleteEntityDialog.tsx +4 -4
  81. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
  82. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +268 -277
  83. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  84. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +13 -13
  85. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +9 -16
  86. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +3 -3
  87. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +27 -32
  88. package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +9 -5
  89. package/src/components/EntityCollectionView/EntityCollectionView.tsx +51 -52
  90. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
  91. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  92. package/src/components/EntityCollectionView/useSelectionController.tsx +30 -0
  93. package/src/components/EntityPreview.tsx +207 -70
  94. package/src/components/EntityView.tsx +84 -0
  95. package/src/components/FieldCaption.tsx +14 -0
  96. package/src/components/FireCMSAppBar.tsx +8 -0
  97. package/src/components/HomePage/DefaultHomePage.tsx +14 -10
  98. package/src/components/HomePage/NavigationCard.tsx +69 -0
  99. package/src/components/HomePage/NavigationCardBinding.tsx +116 -0
  100. package/src/components/HomePage/SmallNavigationCard.tsx +45 -0
  101. package/src/components/HomePage/index.tsx +3 -1
  102. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -4
  103. package/src/components/ReferenceWidget.tsx +4 -4
  104. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +23 -8
  105. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +35 -24
  106. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
  107. package/src/components/VirtualTable/VirtualTable.tsx +17 -7
  108. package/src/components/VirtualTable/VirtualTableProps.tsx +1 -1
  109. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
  110. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +1 -1
  111. package/src/components/index.tsx +4 -3
  112. package/src/contexts/AuthControllerContext.tsx +1 -1
  113. package/src/core/Drawer.tsx +66 -39
  114. package/src/{internal/EntityView.tsx → core/EntityEditView.tsx} +22 -39
  115. package/src/core/EntitySidePanel.tsx +2 -2
  116. package/src/core/FireCMS.tsx +18 -3
  117. package/src/core/NavigationRoutes.tsx +8 -0
  118. package/src/core/SideEntityView.tsx +38 -0
  119. package/src/core/field_configs.tsx +1 -2
  120. package/src/core/index.tsx +0 -2
  121. package/src/form/EntityForm.tsx +20 -12
  122. package/src/form/components/StorageItemPreview.tsx +5 -3
  123. package/src/form/components/StorageUploadProgress.tsx +6 -5
  124. package/src/form/components/index.tsx +1 -0
  125. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -3
  126. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +12 -15
  127. package/src/form/field_bindings/BlockFieldBinding.tsx +2 -3
  128. package/src/form/field_bindings/DateTimeFieldBinding.tsx +4 -4
  129. package/src/form/field_bindings/KeyValueFieldBinding.tsx +18 -18
  130. package/src/form/field_bindings/MapFieldBinding.tsx +17 -17
  131. package/src/form/field_bindings/MarkdownFieldBinding.tsx +1 -2
  132. package/src/form/field_bindings/MultiSelectBinding.tsx +2 -3
  133. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +3 -3
  134. package/src/form/field_bindings/ReferenceFieldBinding.tsx +5 -3
  135. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
  136. package/src/form/field_bindings/SelectFieldBinding.tsx +2 -3
  137. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +15 -6
  138. package/src/form/field_bindings/SwitchFieldBinding.tsx +2 -3
  139. package/src/form/field_bindings/TextFieldBinding.tsx +10 -9
  140. package/src/form/index.tsx +1 -0
  141. package/src/form/validation.ts +3 -4
  142. package/src/hooks/data/delete.ts +3 -3
  143. package/src/hooks/data/save.ts +1 -1
  144. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  145. package/src/hooks/data/useDataSource.tsx +8 -3
  146. package/src/hooks/data/useEntityFetch.tsx +4 -4
  147. package/src/hooks/index.tsx +5 -1
  148. package/src/{core → hooks}/useBuildLocalConfigurationPersistence.tsx +9 -10
  149. package/src/{core → hooks}/useBuildModeController.tsx +12 -6
  150. package/src/hooks/useBuildNavigationController.tsx +197 -79
  151. package/src/hooks/useProjectLog.tsx +17 -7
  152. package/src/hooks/useReferenceDialog.tsx +2 -2
  153. package/src/hooks/useStorageSource.tsx +7 -2
  154. package/src/hooks/useValidateAuthenticator.tsx +135 -0
  155. package/src/internal/useBuildDataSource.ts +6 -1
  156. package/src/internal/useBuildSideEntityController.tsx +18 -12
  157. package/src/preview/PropertyPreview.tsx +1 -1
  158. package/src/preview/PropertyPreviewProps.tsx +1 -11
  159. package/src/preview/components/BooleanPreview.tsx +19 -4
  160. package/src/preview/components/EnumValuesChip.tsx +1 -1
  161. package/src/preview/components/ReferencePreview.tsx +55 -147
  162. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
  163. package/src/types/analytics.ts +1 -0
  164. package/src/types/auth.tsx +50 -1
  165. package/src/types/collections.ts +24 -5
  166. package/src/types/datasource.ts +1 -1
  167. package/src/types/entities.ts +1 -1
  168. package/src/types/entity_actions.tsx +4 -0
  169. package/src/types/entity_callbacks.ts +2 -2
  170. package/src/types/entity_overrides.tsx +7 -0
  171. package/src/types/firecms.tsx +0 -1
  172. package/src/types/index.ts +2 -0
  173. package/src/types/navigation.ts +17 -16
  174. package/src/types/permissions.ts +6 -1
  175. package/src/types/plugins.tsx +26 -28
  176. package/src/types/properties.ts +3 -2
  177. package/src/types/property_config.tsx +2 -2
  178. package/src/types/roles.ts +41 -0
  179. package/src/types/side_entity_controller.tsx +1 -0
  180. package/src/types/storage.ts +12 -3
  181. package/src/types/user.ts +7 -0
  182. package/src/util/collections.ts +22 -0
  183. package/src/util/entities.ts +1 -1
  184. package/src/util/icons.tsx +11 -3
  185. package/src/util/navigation_utils.ts +6 -6
  186. package/src/util/permissions.ts +11 -8
  187. package/src/util/references.ts +36 -5
  188. package/src/util/strings.ts +2 -2
  189. package/src/util/useTraceUpdate.tsx +2 -1
  190. package/dist/internal/useLocaleConfig.d.ts +0 -1
  191. package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
  192. package/src/internal/useLocaleConfig.tsx +0 -18
  193. /package/dist/{components → form/components}/LabelWithIcon.d.ts +0 -0
  194. /package/dist/{core → hooks}/useBuildLocalConfigurationPersistence.d.ts +0 -0
  195. /package/src/{components → form/components}/LabelWithIcon.tsx +0 -0
@@ -1,7 +1,7 @@
1
1
  import React, { useCallback, useState } from "react";
2
2
  import equal from "react-fast-compare"
3
3
 
4
- import { ReferencePreview, ReferencePreviewContainer } from "../../../preview";
4
+ import { ReferencePreview } from "../../../preview";
5
5
  import { CollectionSize, Entity, EntityCollection, EntityReference, FilterValues } from "../../../types";
6
6
 
7
7
  import { getPreviewSizeFrom } from "../../../preview/util";
@@ -9,6 +9,7 @@ import { getReferenceFrom } from "../../../util";
9
9
  import { useCustomizationController, useNavigationController, useReferenceDialog } from "../../../hooks";
10
10
  import { ErrorView } from "../../ErrorView";
11
11
  import { Button } from "@firecms/ui";
12
+ import { EntityPreviewContainer } from "../../EntityPreview";
12
13
 
13
14
  type TableReferenceFieldProps = {
14
15
  name: string;
@@ -28,7 +29,7 @@ export function TableReferenceField(props: TableReferenceFieldProps) {
28
29
 
29
30
  const navigationController = useNavigationController();
30
31
  const { path } = props;
31
- const collection = navigationController.getCollection<EntityCollection>(path);
32
+ const collection = navigationController.getCollection(path);
32
33
  if (!collection) {
33
34
  if (customizationController.components?.missingReference) {
34
35
  return <customizationController.components.missingReference path={path}/>;
@@ -57,11 +58,6 @@ export const TableReferenceFieldSuccess = React.memo(
57
58
  collection
58
59
  } = props;
59
60
 
60
- const [onHover, setOnHover] = useState(false);
61
-
62
- const hoverTrue = useCallback(() => setOnHover(true), []);
63
- const hoverFalse = useCallback(() => setOnHover(false), []);
64
-
65
61
  const onSingleEntitySelected = useCallback((entity: Entity<any>) => {
66
62
  updateValue(entity ? getReferenceFrom(entity) : null);
67
63
  }, [updateValue]);
@@ -101,29 +97,29 @@ export const TableReferenceFieldSuccess = React.memo(
101
97
  onClick={disabled ? undefined : handleOpen}
102
98
  size={getPreviewSizeFrom(size)}
103
99
  reference={internalValue as EntityReference}
104
- onHover={onHover}
100
+ hover={!disabled}
105
101
  disabled={!path}
106
102
  previewProperties={previewProperties}
107
103
  />;
108
104
  else
109
- return <ReferencePreviewContainer
105
+ return <EntityPreviewContainer
110
106
  onClick={disabled ? undefined : handleOpen}
111
107
  size={getPreviewSizeFrom(size)}>
112
108
  <ErrorView title="Value is not a reference." error={"Click to edit"}/>
113
- </ReferencePreviewContainer>;
109
+ </EntityPreviewContainer>;
114
110
  };
115
111
 
116
112
  const buildMultipleReferenceField = () => {
117
113
  if (Array.isArray(internalValue))
118
114
  return <>
119
115
  {internalValue.map((reference, index) =>
120
- <div className="m-1 w-full"
116
+ <div className="w-full my-0.5"
121
117
  key={`preview_array_ref_${name}_${index}`}>
122
118
  <ReferencePreview
123
119
  onClick={disabled ? undefined : handleOpen}
124
120
  size={"tiny"}
125
121
  reference={reference}
126
- onHover={onHover}
122
+ hover={!disabled}
127
123
  disabled={!path}
128
124
  previewProperties={previewProperties}
129
125
  />
@@ -139,10 +135,7 @@ export const TableReferenceFieldSuccess = React.memo(
139
135
  return <ErrorView error={"The specified collection does not exist"}/>;
140
136
 
141
137
  return (
142
- <div className="w-full"
143
- onMouseEnter={hoverTrue}
144
- onMouseMove={hoverTrue}
145
- onMouseLeave={hoverFalse}>
138
+ <div className="w-full group">
146
139
 
147
140
  {internalValue && !multiselect && buildSingleReferenceField()}
148
141
 
@@ -51,7 +51,7 @@ export function TableStorageUpload(props: {
51
51
  entity,
52
52
  path,
53
53
  previewSize,
54
- updateValue
54
+ updateValue,
55
55
  } = props;
56
56
 
57
57
  const storageSource = useStorageSource();
@@ -137,7 +137,7 @@ function StorageUpload({
137
137
  storage,
138
138
  onFilesAdded,
139
139
  onFileUploadComplete,
140
- storagePathBuilder
140
+ storagePathBuilder,
141
141
  }: StorageUploadProps) {
142
142
 
143
143
  const [onHover, setOnHover] = useState(false);
@@ -293,7 +293,7 @@ export function TableStorageItemPreview({
293
293
 
294
294
  return (
295
295
  <div
296
- className={"relative m-2"}
296
+ className={"relative p-2 max-w-full"}
297
297
  >
298
298
 
299
299
  {value &&
@@ -16,20 +16,27 @@ import { useLargeLayout } from "../../../hooks";
16
16
 
17
17
  interface CollectionTableToolbarProps {
18
18
  size: CollectionSize;
19
- filterIsSet: boolean;
20
19
  loading: boolean;
21
- forceFilter?: boolean;
22
20
  actionsStart?: React.ReactNode;
23
21
  actions?: React.ReactNode;
24
22
  title?: React.ReactNode,
25
23
  onTextSearchClick?: () => void;
26
24
  onTextSearch?: (searchString?: string) => void;
27
25
  onSizeChanged: (size: CollectionSize) => void;
28
- clearFilter?: () => void;
29
26
  textSearchLoading?: boolean;
30
27
  }
31
28
 
32
- export function CollectionTableToolbar(props: CollectionTableToolbarProps) {
29
+ export function CollectionTableToolbar({
30
+ actions,
31
+ actionsStart,
32
+ loading,
33
+ onSizeChanged,
34
+ onTextSearch,
35
+ onTextSearchClick,
36
+ size,
37
+ textSearchLoading,
38
+ title
39
+ }: CollectionTableToolbarProps) {
33
40
 
34
41
  const searchInputRef = React.useRef<HTMLInputElement>(null);
35
42
  const largeLayout = useLargeLayout();
@@ -37,30 +44,20 @@ export function CollectionTableToolbar(props: CollectionTableToolbarProps) {
37
44
  const searchLoading = React.useRef<boolean>(false);
38
45
 
39
46
  useEffect(() => {
40
- if (searchInputRef.current && searchLoading.current && !props.textSearchLoading) {
47
+ if (searchInputRef.current && searchLoading.current && !textSearchLoading) {
41
48
  searchInputRef.current.focus();
42
49
  }
43
- searchLoading.current = props.textSearchLoading ?? false;
44
- }, [props.textSearchLoading]);
45
-
46
- const clearFilterButton = !props.forceFilter && props.filterIsSet && props.clearFilter &&
47
- <Button
48
- variant={"outlined"}
49
- className="h-fit-content"
50
- aria-label="filter clear"
51
- onClick={props.clearFilter}
52
- size="small">
53
- <FilterListOffIcon/>
54
- Clear filter
55
- </Button>;
50
+ searchLoading.current = textSearchLoading ?? false;
51
+ }, [textSearchLoading]);
52
+
56
53
 
57
54
  const sizeSelect = (
58
55
  <Tooltip title={"Table row size"} side={"right"} sideOffset={4}>
59
56
  <Select
60
- value={props.size as string}
57
+ value={size as string}
61
58
  className="w-16 h-10"
62
59
  size={"small"}
63
- onValueChange={(v) => props.onSizeChanged(v as CollectionSize)}
60
+ onValueChange={(v) => onSizeChanged(v as CollectionSize)}
64
61
  renderValue={(v) => <div className={"font-medium"}>{v.toUpperCase()}</div>}
65
62
  >
66
63
  {["xs", "s", "m", "l", "xl"].map((size) => (
@@ -78,36 +75,34 @@ export function CollectionTableToolbar(props: CollectionTableToolbarProps) {
78
75
 
79
76
  <div className="flex items-center gap-2 md:mr-4 mr-2">
80
77
 
81
- {props.title && <div className={"hidden lg:block"}>
82
- {props.title}
78
+ {title && <div className={"hidden lg:block"}>
79
+ {title}
83
80
  </div>}
84
81
 
85
82
  {sizeSelect}
86
83
 
87
- {props.actionsStart}
88
-
89
- {clearFilterButton}
84
+ {actionsStart}
90
85
 
91
86
  </div>
92
87
 
93
88
  <div className="flex items-center gap-2">
94
89
 
95
90
  {largeLayout && <div className="w-[22px]">
96
- {props.loading &&
91
+ {loading &&
97
92
  <CircularProgress size={"small"}/>}
98
93
  </div>}
99
94
 
100
- {(props.onTextSearch || props.onTextSearchClick) &&
95
+ {(onTextSearch || onTextSearchClick) &&
101
96
  <SearchBar
102
97
  key={"search-bar"}
103
98
  inputRef={searchInputRef}
104
- loading={props.textSearchLoading}
105
- disabled={Boolean(props.onTextSearchClick)}
106
- onClick={props.onTextSearchClick}
107
- onTextSearch={props.onTextSearchClick ? undefined : props.onTextSearch}
99
+ loading={textSearchLoading}
100
+ disabled={Boolean(onTextSearchClick)}
101
+ onClick={onTextSearchClick}
102
+ onTextSearch={onTextSearchClick ? undefined : onTextSearch}
108
103
  expandable={true}/>}
109
104
 
110
- {props.actions}
105
+ {actions}
111
106
 
112
107
  </div>
113
108
 
@@ -9,21 +9,23 @@ export const editEntityAction: EntityAction = {
9
9
  onClick({
10
10
  entity,
11
11
  collection,
12
+ fullPath,
12
13
  context,
13
14
  highlightEntity,
14
- unhighlightEntity
15
+ unhighlightEntity,
15
16
  }): Promise<void> {
16
17
  highlightEntity?.(entity);
17
18
  context.analyticsController?.onAnalyticsEvent?.("entity_click", {
18
19
  path: entity.path,
19
20
  entityId: entity.id
20
21
  });
22
+ const path = collection?.collectionGroup ? entity.path : (fullPath ?? entity.path);
21
23
  context.sideEntityController.open({
22
24
  entityId: entity.id,
23
- path: entity.path,
25
+ path,
24
26
  collection,
25
27
  updateUrl: true,
26
- onClose: () => unhighlightEntity?.(entity)
28
+ onClose: () => unhighlightEntity?.(entity),
27
29
  });
28
30
  return Promise.resolve(undefined);
29
31
  }
@@ -37,7 +39,7 @@ export const copyEntityAction: EntityAction = {
37
39
  collection,
38
40
  context,
39
41
  highlightEntity,
40
- unhighlightEntity
42
+ unhighlightEntity,
41
43
  }): Promise<void> {
42
44
  highlightEntity?.(entity);
43
45
  context.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
@@ -50,11 +52,12 @@ export const copyEntityAction: EntityAction = {
50
52
  copy: true,
51
53
  collection,
52
54
  updateUrl: true,
53
- onClose: () => unhighlightEntity?.(entity)
55
+ onClose: () => unhighlightEntity?.(entity),
54
56
  });
55
57
  return Promise.resolve(undefined);
56
58
  }
57
59
  }
60
+
58
61
  export const archiveEntityAction: EntityAction = {
59
62
  icon: <ArchiveIcon/>,
60
63
  name: "Archive",
@@ -69,6 +72,7 @@ export const archiveEntityAction: EntityAction = {
69
72
  return Promise.resolve(undefined);
70
73
  }
71
74
  }
75
+
72
76
  export const openWebsiteAction: EntityAction = {
73
77
  icon: <OpenInNewIcon/>,
74
78
  name: "See in website",
@@ -12,8 +12,7 @@ import {
12
12
  PartialEntityCollection,
13
13
  PropertyOrBuilder,
14
14
  ResolvedProperty,
15
- SaveEntityProps,
16
- SelectionController
15
+ SaveEntityProps
17
16
  } from "../../types";
18
17
  import {
19
18
  EntityCollectionRowActions,
@@ -25,7 +24,6 @@ import {
25
24
  canCreateEntity,
26
25
  canDeleteEntity,
27
26
  canEditEntity,
28
- fullPathToCollectionSegments,
29
27
  getPropertyInPath,
30
28
  mergeDeep,
31
29
  resolveCollection,
@@ -74,6 +72,8 @@ import {
74
72
  } from "../EntityCollectionTable/internal/default_entity_actions";
75
73
  import { DeleteEntityDialog } from "../DeleteEntityDialog";
76
74
  import { useAnalyticsController } from "../../hooks/useAnalyticsController";
75
+ import { useSelectionController } from "./useSelectionController";
76
+ import { EntityCollectionViewStartActions } from "./EntityCollectionViewStartActions";
77
77
 
78
78
  const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
79
79
 
@@ -81,8 +81,18 @@ const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
81
81
  * @group Components
82
82
  */
83
83
  export type EntityCollectionViewProps<M extends Record<string, any>> = {
84
- fullPath: string;
84
+ /**
85
+ * Complete path where this collection is located.
86
+ * It defaults to the collection path if not provided.
87
+ */
88
+ fullPath?: string;
89
+ /**
90
+ * If this is a subcollection, specify the parent collection ids.
91
+ */
85
92
  parentCollectionIds?: string[];
93
+ /**
94
+ * Whether this is a subcollection or not.
95
+ */
86
96
  isSubCollection?: boolean;
87
97
  className?: string;
88
98
  } & EntityCollection<M>;
@@ -113,7 +123,7 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
113
123
  */
114
124
  export const EntityCollectionView = React.memo(
115
125
  function EntityCollectionView<M extends Record<string, any>>({
116
- fullPath,
126
+ fullPath: fullPathProp,
117
127
  parentCollectionIds,
118
128
  isSubCollection,
119
129
  className,
@@ -121,7 +131,8 @@ export const EntityCollectionView = React.memo(
121
131
  }: EntityCollectionViewProps<M>
122
132
  ) {
123
133
 
124
- const dataSource = useDataSource();
134
+ const fullPath = fullPathProp ?? collectionProp.path;
135
+ const dataSource = useDataSource(collectionProp);
125
136
  const navigation = useNavigationController();
126
137
  const sideEntityController = useSideEntityController();
127
138
  const authController = useAuthController();
@@ -141,7 +152,7 @@ export const EntityCollectionView = React.memo(
141
152
  collectionRef.current = collection;
142
153
  }, [collection]);
143
154
 
144
- const canCreateEntities = canCreateEntity(collection, authController, fullPathToCollectionSegments(fullPath), null);
155
+ const canCreateEntities = canCreateEntity(collection, authController, fullPath, null);
145
156
  const [selectedNavigationEntity, setSelectedNavigationEntity] = useState<Entity<M> | undefined>(undefined);
146
157
  const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
147
158
 
@@ -160,7 +171,7 @@ export const EntityCollectionView = React.memo(
160
171
 
161
172
  const checkInlineEditing = useCallback((entity?: Entity<any>): boolean => {
162
173
  const collection = collectionRef.current;
163
- if (!canEditEntity(collection, authController, fullPathToCollectionSegments(fullPath), entity ?? null)) {
174
+ if (!canEditEntity(collection, authController, fullPath, entity ?? null)) {
164
175
  return false;
165
176
  }
166
177
  return collection.inlineEditing === undefined || collection.inlineEditing;
@@ -175,7 +186,6 @@ export const EntityCollectionView = React.memo(
175
186
  const usedSelectionController = collection.selectionController ?? selectionController;
176
187
  const {
177
188
  selectedEntities,
178
- toggleEntitySelection,
179
189
  isEntitySelected,
180
190
  setSelectedEntities
181
191
  } = usedSelectionController;
@@ -199,6 +209,7 @@ export const EntityCollectionView = React.memo(
199
209
  }, [tableController.setPopupCell]);
200
210
 
201
211
  const onEntityClick = useCallback((clickedEntity: Entity<M>) => {
212
+ console.log("Entity clicked", clickedEntity)
202
213
  const collection = collectionRef.current;
203
214
  setSelectedNavigationEntity(clickedEntity);
204
215
  analyticsController.onAnalyticsEvent?.("edit_entity_clicked", {
@@ -210,9 +221,9 @@ export const EntityCollectionView = React.memo(
210
221
  path: clickedEntity.path,
211
222
  collection,
212
223
  updateUrl: true,
213
- onClose: unselectNavigatedEntity
224
+ onClose: unselectNavigatedEntity,
214
225
  });
215
- }, [unselectNavigatedEntity]);
226
+ }, [unselectNavigatedEntity, sideEntityController]);
216
227
 
217
228
  const onNewClick = useCallback(() => {
218
229
 
@@ -224,9 +235,9 @@ export const EntityCollectionView = React.memo(
224
235
  path: fullPath,
225
236
  collection,
226
237
  updateUrl: true,
227
- onClose: unselectNavigatedEntity
238
+ onClose: unselectNavigatedEntity,
228
239
  });
229
- }, [fullPath]);
240
+ }, [fullPath, sideEntityController]);
230
241
 
231
242
  const onMultipleDeleteClick = () => {
232
243
  analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", {
@@ -288,7 +299,7 @@ export const EntityCollectionView = React.memo(
288
299
  onCollectionModifiedForUser(fullPath, { defaultSize: size })
289
300
  }, [onCollectionModifiedForUser, fullPath, userConfigPersistence]);
290
301
 
291
- const createEnabled = canCreateEntity(collection, authController, fullPathToCollectionSegments(fullPath), null);
302
+ const createEnabled = canCreateEntity(collection, authController, fullPath, null);
292
303
 
293
304
  const uniqueFieldValidator: UniqueFieldValidator = useCallback(
294
305
  ({
@@ -387,7 +398,7 @@ export const EntityCollectionView = React.memo(
387
398
  entityId: entity.id,
388
399
  selectedSubPath: subcollection.id ?? subcollection.path,
389
400
  collection,
390
- updateUrl: true
401
+ updateUrl: true,
391
402
  });
392
403
  }}>
393
404
  {subcollection.name}
@@ -425,7 +436,7 @@ export const EntityCollectionView = React.memo(
425
436
  ...subcollectionColumns,
426
437
  ...collectionGroupParentCollections
427
438
  ];
428
- }, [collection, fullPath]);
439
+ }, [collection, fullPath, sideEntityController]);
429
440
 
430
441
  const updateLastDeleteTimestamp = useCallback(() => {
431
442
  setLastDeleteTimestamp(Date.now());
@@ -433,11 +444,14 @@ export const EntityCollectionView = React.memo(
433
444
 
434
445
  const largeLayout = useLargeLayout();
435
446
 
436
- const getActionsForEntity = ({ entity, customEntityActions }: {
447
+ const getActionsForEntity = ({
448
+ entity,
449
+ customEntityActions
450
+ }: {
437
451
  entity?: Entity<M>,
438
452
  customEntityActions?: EntityAction[]
439
453
  }): EntityAction[] => {
440
- const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPathToCollectionSegments(fullPath), entity) : true;
454
+ const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPath, entity) : true;
441
455
  const actions: EntityAction[] = [editEntityAction];
442
456
  if (createEnabled)
443
457
  actions.push(copyEntityAction);
@@ -448,13 +462,13 @@ export const EntityCollectionView = React.memo(
448
462
  return actions;
449
463
  };
450
464
 
451
- const getIdColumnWidth = useCallback(() => {
465
+ const getIdColumnWidth = () => {
452
466
  const entityActions = getActionsForEntity({});
453
467
  const collapsedActions = entityActions.filter(a => a.collapsed !== false);
454
468
  const uncollapsedActions = entityActions.filter(a => a.collapsed === false);
455
469
  const actionsWidth = uncollapsedActions.length * (largeLayout ? 40 : 30);
456
470
  return (largeLayout ? (80 + actionsWidth) : (70 + actionsWidth)) + (collapsedActions.length > 0 ? (largeLayout ? 40 : 30) : 0);
457
- }, [largeLayout]);
471
+ };
458
472
 
459
473
  const tableRowActionsBuilder = ({
460
474
  entity,
@@ -470,7 +484,10 @@ export const EntityCollectionView = React.memo(
470
484
 
471
485
  const isSelected = isEntitySelected(entity);
472
486
 
473
- const actions = getActionsForEntity({ entity, customEntityActions: collection.entityActions });
487
+ const actions = getActionsForEntity({
488
+ entity,
489
+ customEntityActions: collection.entityActions
490
+ });
474
491
 
475
492
  return (
476
493
  <EntityCollectionRowActions
@@ -577,7 +594,7 @@ export const EntityCollectionView = React.memo(
577
594
  });
578
595
 
579
596
  return (
580
- <div className={cn("overflow-hidden h-full w-full", className)}
597
+ <div className={cn("overflow-hidden h-full w-full rounded-md", className)}
581
598
  ref={containerRef}>
582
599
  <EntityCollectionTable
583
600
  key={`collection_table_${fullPath}`}
@@ -599,6 +616,14 @@ export const EntityCollectionView = React.memo(
599
616
  onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
600
617
  textSearchLoading={textSearchLoading}
601
618
  textSearchEnabled={textSearchEnabled}
619
+ actionsStart={<EntityCollectionViewStartActions
620
+ parentCollectionIds={parentCollectionIds ?? []}
621
+ collection={collection}
622
+ tableController={tableController}
623
+ path={fullPath}
624
+ relativePath={collection.path}
625
+ selectionController={usedSelectionController}
626
+ collectionEntitiesCount={docsCount}/>}
602
627
  actions={<EntityCollectionViewActions
603
628
  parentCollectionIds={parentCollectionIds ?? []}
604
629
  collection={collection}
@@ -676,39 +701,13 @@ export const EntityCollectionView = React.memo(
676
701
  equal(a.selectionController, b.selectionController) &&
677
702
  equal(a.Actions, b.Actions) &&
678
703
  equal(a.defaultSize, b.defaultSize) &&
704
+ equal(a.initialFilter, b.initialFilter) &&
705
+ equal(a.initialSort, b.initialSort) &&
679
706
  equal(a.textSearchEnabled, b.textSearchEnabled) &&
680
707
  equal(a.additionalFields, b.additionalFields) &&
681
708
  equal(a.forceFilter, b.forceFilter);
682
709
  }) as React.FunctionComponent<EntityCollectionViewProps<any>>
683
710
 
684
- export function useSelectionController<M extends Record<string, any> = any>(
685
- onSelectionChange?: (entity: Entity<M>, selected: boolean) => void
686
- ): SelectionController<M> {
687
-
688
- const [selectedEntities, setSelectedEntities] = useState<Entity<M>[]>([]);
689
-
690
- const toggleEntitySelection = useCallback((entity: Entity<M>) => {
691
- let newValue;
692
- if (selectedEntities.map(e => e.id).includes(entity.id)) {
693
- onSelectionChange?.(entity, false);
694
- newValue = selectedEntities.filter((item: Entity<M>) => item.id !== entity.id);
695
- } else {
696
- onSelectionChange?.(entity, true);
697
- newValue = [...selectedEntities, entity];
698
- }
699
- setSelectedEntities(newValue);
700
- }, [selectedEntities]);
701
-
702
- const isEntitySelected = useCallback((entity: Entity<M>) => selectedEntities.map(e => e.id).includes(entity.id), [selectedEntities]);
703
-
704
- return {
705
- selectedEntities,
706
- setSelectedEntities,
707
- isEntitySelected,
708
- toggleEntitySelection
709
- };
710
- }
711
-
712
711
  function EntitiesCount({
713
712
  fullPath,
714
713
  collection,
@@ -723,7 +722,7 @@ function EntitiesCount({
723
722
  onCountChange?: (count: number) => void,
724
723
  }) {
725
724
 
726
- const dataSource = useDataSource();
725
+ const dataSource = useDataSource(collection);
727
726
  const navigation = useNavigationController();
728
727
  const [count, setCount] = useState<number | undefined>(undefined);
729
728
  const [error, setError] = useState<Error | undefined>(undefined);
@@ -799,7 +798,7 @@ function EntityIdHeaderWidget({
799
798
  entityId: searchString,
800
799
  path,
801
800
  collection,
802
- updateUrl: true
801
+ updateUrl: true,
803
802
  });
804
803
  }}
805
804
  className={"text-gray-900 dark:text-white w-96 max-w-full"}>
@@ -44,7 +44,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
44
44
 
45
45
  const selectedEntities = selectionController.selectedEntities;
46
46
 
47
- const addButton = canCreateEntity(collection, authController, fullPathToCollectionSegments(path), null) &&
47
+ const addButton = canCreateEntity(collection, authController, path, null) &&
48
48
  onNewClick && (largeLayout
49
49
  ? <Button
50
50
  id={`add_entity_${path}`}
@@ -57,14 +57,13 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
57
57
  : <Button
58
58
  id={`add_entity_${path}`}
59
59
  onClick={onNewClick}
60
- size="medium"
61
60
  variant="filled"
62
61
  color="primary"
63
62
  >
64
63
  <AddIcon/>
65
64
  </Button>);
66
65
 
67
- const multipleDeleteEnabled = canDeleteEntity(collection, authController, fullPathToCollectionSegments(path), null);
66
+ const multipleDeleteEnabled = canDeleteEntity(collection, authController, path, null);
68
67
 
69
68
  let multipleDeleteButton: React.ReactNode | undefined;
70
69
  if (selectionEnabled) {
@@ -112,11 +111,11 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
112
111
 
113
112
  if (plugins) {
114
113
  plugins.forEach((plugin, i) => {
115
- if (plugin.collections?.CollectionActions) {
116
- actions.push(...toArray(plugin.collections?.CollectionActions)
114
+ if (plugin.collectionView?.CollectionActions) {
115
+ actions.push(...toArray(plugin.collectionView?.CollectionActions)
117
116
  .map((Action, j) => (
118
117
  <ErrorBoundary key={`plugin_actions_${i}_${j}`}>
119
- <Action {...actionProps} {...plugin.collections?.collectionActionsProps}/>
118
+ <Action {...actionProps} {...plugin.collectionView?.collectionActionsProps}/>
120
119
  </ErrorBoundary>
121
120
  )));
122
121
  }
@@ -0,0 +1,68 @@
1
+ import React from "react";
2
+ import { useCustomizationController, useFireCMSContext } from "../../hooks";
3
+ import { CollectionActionsProps, EntityCollection, EntityTableController, SelectionController } from "../../types";
4
+ import { toArray } from "../../util/arrays";
5
+ import { ErrorBoundary } from "../ErrorBoundary";
6
+ import { ClearFilterSortButton } from "../ClearFilterSortButton";
7
+
8
+ export type EntityCollectionViewStartActionsProps<M extends Record<string, any>> = {
9
+ collection: EntityCollection<M>;
10
+ path: string;
11
+ relativePath: string;
12
+ parentCollectionIds: string[];
13
+ selectionController: SelectionController<M>;
14
+ tableController: EntityTableController<M>;
15
+ collectionEntitiesCount: number;
16
+ }
17
+
18
+ export function EntityCollectionViewStartActions<M extends Record<string, any>>({
19
+ collection,
20
+ relativePath,
21
+ parentCollectionIds,
22
+ path,
23
+ selectionController,
24
+ tableController,
25
+ collectionEntitiesCount
26
+ }: EntityCollectionViewStartActionsProps<M>) {
27
+
28
+ const context = useFireCMSContext();
29
+
30
+ const customizationController = useCustomizationController();
31
+ const plugins = customizationController.plugins ?? [];
32
+
33
+ const actionProps: CollectionActionsProps = {
34
+ path,
35
+ relativePath,
36
+ parentCollectionIds,
37
+ collection,
38
+ selectionController,
39
+ context,
40
+ tableController,
41
+ collectionEntitiesCount
42
+ };
43
+ const actions: React.ReactNode[] = [
44
+ <ClearFilterSortButton
45
+ key={"clear_filter"}
46
+ tableController={tableController}
47
+ enabled={!collection.forceFilter}/>
48
+ ];
49
+
50
+ if (plugins) {
51
+ plugins.forEach((plugin, i) => {
52
+ if (plugin.collectionView?.CollectionActionsStart) {
53
+ actions.push(...toArray(plugin.collectionView?.CollectionActionsStart)
54
+ .map((Action, j) => (
55
+ <ErrorBoundary key={`plugin_actions_${i}_${j}`}>
56
+ <Action {...actionProps} {...plugin.collectionView?.collectionActionsStartProps}/>
57
+ </ErrorBoundary>
58
+ )));
59
+ }
60
+ });
61
+ }
62
+
63
+ return (
64
+ <>
65
+ {actions}
66
+ </>
67
+ );
68
+ }