@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
@@ -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
+ }
@@ -0,0 +1,45 @@
1
+ import { ArrowForwardIcon, cardClickableMixin, cardMixin, cn, focusedMixin, Typography, } from "@firecms/ui";
2
+
3
+ import { Link as ReactLink } from "react-router-dom";
4
+
5
+ export type SmallNavigationCardProps = {
6
+ name: string,
7
+ url: string;
8
+ icon: React.ReactElement;
9
+ };
10
+
11
+ export function SmallNavigationCard({
12
+ name,
13
+ url,
14
+ icon,
15
+ }: SmallNavigationCardProps) {
16
+
17
+ return (
18
+ <>
19
+
20
+ <ReactLink
21
+ tabIndex={0}
22
+ className={cn(cardMixin,
23
+ cardClickableMixin,
24
+ focusedMixin,
25
+ "cursor-pointer flex flex-row items-center px-4 py-2 text-inherit dark:text-inherit visited:text-inherit visited:dark:text-inherit hover:text-inherit hover:dark:text-inherit ")}
26
+ to={url}
27
+ >
28
+
29
+ <div className="flex flex-row items-center flex-grow gap-2 ">
30
+ {icon}
31
+
32
+ <Typography gutterBottom variant="h5"
33
+ component="h2"
34
+ className="mb-0 ml-4">
35
+ {name}
36
+ </Typography>
37
+ </div>
38
+
39
+ <div className={"p-4"}>
40
+ <ArrowForwardIcon color="primary"/>
41
+ </div>
42
+ </ReactLink>
43
+
44
+ </>);
45
+ }
@@ -1,3 +1,5 @@
1
1
  export * from "./DefaultHomePage";
2
- export * from "./NavigationCollectionCard";
2
+ export * from "./NavigationCardBinding";
3
3
  export * from "./NavigationGroup";
4
+ export * from "./NavigationCard";
5
+ export * from "./SmallNavigationCard";
@@ -17,7 +17,7 @@ import {
17
17
  import { ErrorView } from "../ErrorView";
18
18
  import { AddIcon, Button, DialogActions, Typography } from "@firecms/ui";
19
19
  import { canCreateEntity, fullPathToCollectionSegments, resolveCollection } from "../../util";
20
- import { useSelectionController } from "../EntityCollectionView/EntityCollectionView";
20
+ import { useSelectionController } from "../EntityCollectionView/useSelectionController";
21
21
  import { useColumnIds, useTableSearchHelper } from "../common";
22
22
  import { useSideDialogContext } from "../../core";
23
23
  import { useAnalyticsController } from "../../hooks/useAnalyticsController";
@@ -109,7 +109,7 @@ export function ReferenceSelectionTable<M extends Record<string, any>>(
109
109
 
110
110
  const fullPath = navigation.resolveAliasesFrom(pathInput);
111
111
 
112
- const dataSource = useDataSource();
112
+ const dataSource = useDataSource(collection);
113
113
 
114
114
  const [entitiesDisplayedFirst, setEntitiesDisplayedFirst] = useState<Entity<any>[]>([]);
115
115
 
@@ -349,7 +349,7 @@ function ReferenceDialogActions({
349
349
  onNewClick();
350
350
  }
351
351
  : undefined;
352
- const addButton = canCreateEntity(collection, authController, fullPathToCollectionSegments(path), null) &&
352
+ const addButton = canCreateEntity(collection, authController, path, null) &&
353
353
  onClick && (largeLayout
354
354
  ? <Button
355
355
  onClick={onClick}
@@ -360,7 +360,6 @@ function ReferenceDialogActions({
360
360
  </Button>
361
361
  : <Button
362
362
  onClick={onClick}
363
- size="medium"
364
363
  variant="outlined"
365
364
  color="primary"
366
365
  >
@@ -50,11 +50,11 @@ export function ReferenceWidget<M extends Record<string, any>>({
50
50
 
51
51
  const collection: EntityCollection | undefined = useMemo(() => {
52
52
  return navigationController.getCollection(path);
53
- }, [path, navigationController]);
53
+ }, [path, navigationController.getCollection]);
54
54
 
55
- if (!collection) {
56
- throw Error(`Couldn't find the corresponding collection for the path: ${path}`);
57
- }
55
+ // if (!collection) {
56
+ // throw Error(`Couldn't find the corresponding collection for the path: ${path}`);
57
+ // }
58
58
 
59
59
  const onSingleEntitySelected = useCallback((entity: Entity<M> | null) => {
60
60
  if (disabled)
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from "react";
2
2
  import { VirtualTableWhereFilterOp } from "../../VirtualTable";
3
- import { DateTimeField, Select, SelectItem } from "@firecms/ui";
3
+ import { Checkbox, DateTimeField, Label, Select, SelectItem } from "@firecms/ui";
4
4
  import { useCustomizationController } from "../../../hooks";
5
5
 
6
6
  interface DateTimeFilterFieldProps {
@@ -43,10 +43,10 @@ export function DateTimeFilterField({
43
43
 
44
44
  const [fieldOperation, fieldValue] = value || [possibleOperations[0], undefined];
45
45
  const [operation, setOperation] = useState<VirtualTableWhereFilterOp>(fieldOperation);
46
- const [internalValue, setInternalValue] = useState<Date | undefined>(fieldValue);
46
+ const [internalValue, setInternalValue] = useState<Date | null | undefined>(fieldValue);
47
47
 
48
- function updateFilter(op: VirtualTableWhereFilterOp, val: Date | undefined) {
49
- let newValue: Date | undefined = val;
48
+ function updateFilter(op: VirtualTableWhereFilterOp, val: Date | undefined | null) {
49
+ let newValue: Date | null | undefined = val;
50
50
  const prevOpIsArray = multipleSelectOperations.includes(operation);
51
51
  const newOpIsArray = multipleSelectOperations.includes(op);
52
52
  if (prevOpIsArray !== newOpIsArray) {
@@ -73,7 +73,7 @@ export function DateTimeFilterField({
73
73
 
74
74
  return (
75
75
 
76
- <div className="flex w-[440px] items-center">
76
+ <div className="flex w-[440px]">
77
77
  <div className="w-[80px]">
78
78
  <Select value={operation}
79
79
  onValueChange={(value) => {
@@ -88,19 +88,34 @@ export function DateTimeFilterField({
88
88
  </Select>
89
89
  </div>
90
90
 
91
- <div className="flex-grow ml-2">
91
+ <div className="flex-grow ml-2 flex flex-col gap-2">
92
92
 
93
93
  <DateTimeField
94
94
  mode={mode}
95
95
  size={"medium"}
96
96
  locale={locale}
97
- value={internalValue}
98
- onChange={(dateValue: Date | null) => {
97
+ value={internalValue ?? undefined}
98
+ onChange={(dateValue: Date | undefined) => {
99
99
  updateFilter(operation, dateValue === null ? undefined : dateValue);
100
100
  }}
101
101
  clearable={true}
102
102
  />
103
103
 
104
+ <Label
105
+ className="border cursor-pointer rounded-md p-2 flex items-center gap-2 [&:has(:checked)]:bg-gray-100 dark:[&:has(:checked)]:bg-gray-800"
106
+ htmlFor="null-filter"
107
+ >
108
+ <Checkbox id="null-filter"
109
+ checked={internalValue === null}
110
+ size={"small"}
111
+ onCheckedChange={(checked) => {
112
+ if (internalValue !== null)
113
+ updateFilter(operation, null);
114
+ else updateFilter(operation, undefined);
115
+ }}/>
116
+ Filter for null values
117
+ </Label>
118
+
104
119
  </div>
105
120
 
106
121
  </div>
@@ -4,7 +4,7 @@ import { Entity, EntityCollection, EntityReference } from "../../../types";
4
4
  import { ReferencePreview } from "../../../preview";
5
5
  import { getReferenceFrom } from "../../../util";
6
6
  import { useNavigationController, useReferenceDialog } from "../../../hooks";
7
- import { Button, Select, SelectItem } from "@firecms/ui";
7
+ import { Button, Checkbox, Label, Select, SelectItem } from "@firecms/ui";
8
8
 
9
9
  interface ReferenceFilterFieldProps {
10
10
  name: string,
@@ -48,33 +48,31 @@ export function ReferenceFilterField({
48
48
  ? ["array-contains"]
49
49
  : ["==", "!=", ">", "<", ">=", "<="];
50
50
 
51
- const [onHover, setOnHover] = React.useState(false);
52
-
53
51
  isArray
54
52
  ? possibleOperations.push("array-contains-any")
55
53
  : possibleOperations.push("in", "not-in");
56
54
 
57
55
  const [fieldOperation, fieldValue] = value || [possibleOperations[0], undefined];
58
56
  const [operation, setOperation] = useState<VirtualTableWhereFilterOp>(fieldOperation);
59
- const [internalValue, setInternalValue] = useState<EntityReference | EntityReference[] | undefined>(fieldValue);
57
+ const [internalValue, setInternalValue] = useState<EntityReference | EntityReference[] | undefined | null>(fieldValue);
60
58
 
61
59
  const selectedEntityIds = internalValue
62
60
  ? (Array.isArray(internalValue) ? internalValue.map((ref) => {
63
- if (!(ref.isEntityReference && ref.isEntityReference())) {
61
+ if (!(ref?.isEntityReference && ref?.isEntityReference())) {
64
62
  return null;
65
63
  }
66
64
  return ref.id;
67
65
  }).filter(Boolean) as string[] : [internalValue.id])
68
66
  : [];
69
67
 
70
- function updateFilter(op: VirtualTableWhereFilterOp, val?: EntityReference | EntityReference[]) {
68
+ function updateFilter(op: VirtualTableWhereFilterOp, val?: EntityReference | EntityReference[] | null) {
71
69
 
72
70
  const prevOpIsArray = multipleSelectOperations.includes(operation);
73
71
  const newOpIsArray = multipleSelectOperations.includes(op);
74
72
  let newValue = val;
75
73
  if (prevOpIsArray !== newOpIsArray) {
76
74
  // @ts-ignore
77
- newValue = newOpIsArray ? (newValue.isEntityReference && newValue.isEntityReference() ? [newValue] : []) : undefined
75
+ newValue = newOpIsArray ? (newValue?.isEntityReference && newValue?.isEntityReference() ? [newValue] : []) : undefined
78
76
  }
79
77
 
80
78
  setOperation(op);
@@ -129,28 +127,22 @@ export function ReferenceFilterField({
129
127
 
130
128
  const buildEntry = (reference: EntityReference) => {
131
129
  return (
132
- <div
133
- className="mb-0.5"
134
- onMouseEnter={() => setOnHover(true)}
135
- onMouseMove={() => setOnHover(true)}
136
- onMouseLeave={() => setOnHover(false)}>
137
- <ReferencePreview
138
- disabled={!path}
139
- previewProperties={previewProperties}
140
- size={"medium"}
141
- onClick={doOpenDialog}
142
- reference={reference}
143
- onHover={onHover}
144
- allowEntityNavigation={false}
145
- />
146
- </div>
130
+ <ReferencePreview
131
+ disabled={!path}
132
+ previewProperties={previewProperties}
133
+ size={"medium"}
134
+ onClick={doOpenDialog}
135
+ reference={reference}
136
+ hover={true}
137
+ allowEntityNavigation={false}
138
+ />
147
139
  );
148
140
  };
149
141
 
150
142
  return (
151
143
 
152
144
  <div className="flex w-[440px] flex-row">
153
- <div className="w-[120px]">
145
+ <div className="w-[140px]">
154
146
  <Select value={operation}
155
147
  onValueChange={(value) => {
156
148
  updateFilter(value as VirtualTableWhereFilterOp, internalValue);
@@ -164,21 +156,40 @@ export function ReferenceFilterField({
164
156
  </Select>
165
157
  </div>
166
158
 
167
- <div className="flex-grow ml-2 h-full">
159
+ <div className="flex-grow ml-2 h-full gap-2 flex flex-col">
168
160
 
169
161
  {internalValue && Array.isArray(internalValue) && <div>
170
162
  {internalValue.map((ref, index) => buildEntry(ref))}
171
163
  </div>}
164
+
172
165
  {internalValue && !Array.isArray(internalValue) && <div>
173
166
  {buildEntry(internalValue)}
174
167
  </div>}
168
+
175
169
  {(!internalValue || (Array.isArray(internalValue) && internalValue.length === 0)) &&
176
170
  <Button onClick={doOpenDialog}
177
171
  variant={"outlined"}
172
+ size={"large"}
178
173
  className="h-full w-full">
179
174
  {multiple ? "Select references" : "Select reference"}
180
175
  </Button>
181
176
  }
177
+
178
+ {!isArray && <Label
179
+ className="border cursor-pointer rounded-md p-2 flex items-center gap-2 [&:has(:checked)]:bg-gray-100 dark:[&:has(:checked)]:bg-gray-800"
180
+ htmlFor="null-filter"
181
+ >
182
+ <Checkbox id="null-filter"
183
+ checked={internalValue === null}
184
+ size={"small"}
185
+ onCheckedChange={(checked) => {
186
+ if (internalValue !== null)
187
+ updateFilter(operation, null);
188
+ else updateFilter(operation, undefined);
189
+ }}/>
190
+ Filter for null values
191
+ </Label>}
192
+
182
193
  </div>
183
194
 
184
195
  </div>
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from "react";
2
2
  import { EnumValuesChip } from "../../../preview";
3
3
  import { VirtualTableWhereFilterOp } from "../../VirtualTable";
4
- import { ClearIcon, IconButton, Select, SelectItem, TextField } from "@firecms/ui";
4
+ import { Checkbox, ClearIcon, IconButton, Label, Select, SelectItem, TextField } from "@firecms/ui";
5
5
  import { EnumValueConfig } from "../../../types";
6
6
 
7
7
  interface StringNumberFilterFieldProps {
@@ -50,15 +50,15 @@ export function StringNumberFilterField({
50
50
 
51
51
  const [fieldOperation, fieldValue] = value || [possibleOperations[0], undefined];
52
52
  const [operation, setOperation] = useState<VirtualTableWhereFilterOp>(fieldOperation);
53
- const [internalValue, setInternalValue] = useState<string | number | string[] | number[] | undefined>(fieldValue);
53
+ const [internalValue, setInternalValue] = useState<string | number | string[] | number[] | null | undefined>(fieldValue);
54
54
 
55
- function updateFilter(op: VirtualTableWhereFilterOp, val: string | number | string[] | number[] | undefined) {
55
+ function updateFilter(op: VirtualTableWhereFilterOp, val: string | number | string[] | number[] | null | undefined) {
56
56
  let newValue = val;
57
57
  const prevOpIsArray = multipleSelectOperations.includes(operation);
58
58
  const newOpIsArray = multipleSelectOperations.includes(op);
59
59
  if (prevOpIsArray !== newOpIsArray) {
60
60
  // @ts-ignore
61
- newValue = newOpIsArray ? (typeof val === "string" || typeof val === "number" ? [val] : []) : "";
61
+ newValue = newOpIsArray ? (typeof val === "string" || typeof val === "number" ? [val] : []) : undefined;
62
62
  }
63
63
 
64
64
  if (typeof newValue === "number" && isNaN(newValue))
@@ -84,7 +84,7 @@ export function StringNumberFilterField({
84
84
  const multiple = multipleSelectOperations.includes(operation);
85
85
  return (
86
86
 
87
- <div className="flex w-[440px] items-center">
87
+ <div className="flex w-[440px]">
88
88
  <div className={"w-[80px]"}>
89
89
  <Select value={operation}
90
90
  position={"item-aligned"}
@@ -100,11 +100,11 @@ export function StringNumberFilterField({
100
100
  </Select>
101
101
  </div>
102
102
 
103
- <div className="flex-grow ml-2">
103
+ <div className="flex-grow ml-2 flex flex-col gap-2">
104
104
 
105
105
  {!enumValues && <TextField
106
106
  type={dataType === "number" ? "number" : undefined}
107
- value={internalValue !== undefined ? String(internalValue) : ""}
107
+ value={internalValue !== undefined && internalValue != null ? String(internalValue) : ""}
108
108
  onChange={(evt) => {
109
109
  const val = dataType === "number"
110
110
  ? parseFloat(evt.target.value)
@@ -118,26 +118,31 @@ export function StringNumberFilterField({
118
118
  />}
119
119
 
120
120
  {enumValues &&
121
-
122
121
  <Select
123
122
  position={"item-aligned"}
124
123
  value={internalValue !== undefined
125
124
  ? (Array.isArray(internalValue) ? internalValue.map(e => String(e)) : String(internalValue))
126
125
  : isArray ? [] : ""}
127
126
  onValueChange={(value) => {
128
- updateFilter(operation, dataType === "number" ? parseInt(value as string) : value as string)
127
+ if (value !== "")
128
+ updateFilter(operation, dataType === "number" ? parseInt(value as string) : value as string)
129
129
  }}
130
130
  multiple={multiple}
131
131
  endAdornment={internalValue && <IconButton
132
- className="absolute right-3 top-2"
132
+ className="absolute right-2 top-3"
133
133
  onClick={(e) => updateFilter(operation, undefined)}>
134
134
  <ClearIcon/>
135
135
  </IconButton>}
136
- renderValue={(enumKey) => <EnumValuesChip
137
- key={`select_value_${name}_${enumKey}`}
138
- enumKey={enumKey}
139
- enumValues={enumValues}
140
- size={"small"}/>}>
136
+ renderValue={(enumKey) => {
137
+ if (enumKey === null)
138
+ return "Filter for null values";
139
+
140
+ return <EnumValuesChip
141
+ key={`select_value_${name}_${enumKey}`}
142
+ enumKey={enumKey}
143
+ enumValues={enumValues}
144
+ size={"small"}/>;
145
+ }}>
141
146
  {enumValues.map((enumConfig) => (
142
147
  <SelectItem key={`select_value_${name}_${enumConfig.id}`}
143
148
  value={String(enumConfig.id)}>
@@ -150,6 +155,21 @@ export function StringNumberFilterField({
150
155
  </Select>
151
156
  }
152
157
 
158
+ {!isArray && <Label
159
+ className="border cursor-pointer rounded-md p-2 flex items-center gap-2 [&:has(:checked)]:bg-gray-100 dark:[&:has(:checked)]:bg-gray-800"
160
+ htmlFor="null-filter"
161
+ >
162
+ <Checkbox id="null-filter"
163
+ checked={internalValue === null}
164
+ size={"small"}
165
+ onCheckedChange={(checked) => {
166
+ if (internalValue !== null)
167
+ updateFilter(operation, null);
168
+ else updateFilter(operation, undefined);
169
+ }}/>
170
+ Filter for null values
171
+ </Label>}
172
+
153
173
  </div>
154
174
 
155
175
  </div>
@@ -20,7 +20,7 @@ import { VirtualTableContextProps } from "./types";
20
20
  import { VirtualTableHeaderRow } from "./VirtualTableHeaderRow";
21
21
  import { VirtualTableRow } from "./VirtualTableRow";
22
22
  import { VirtualTableCell } from "./VirtualTableCell";
23
- import { AssignmentIcon, cn, Markdown, Typography } from "@firecms/ui";
23
+ import { AssignmentIcon, cn, Typography } from "@firecms/ui";
24
24
 
25
25
  const VirtualListContext = createContext<VirtualTableContextProps<any>>({} as any);
26
26
  VirtualListContext.displayName = "VirtualListContext";
@@ -225,11 +225,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
225
225
  }
226
226
 
227
227
  if (onFilterUpdate) onFilterUpdate(newFilterValue);
228
-
229
- if (column.key !== sortByProperty) {
230
- resetSort();
231
- }
232
- }, [checkFilterCombination, currentSort, onFilterUpdate, resetSort, sortByProperty]);
228
+ }, [checkFilterCombination, currentSort, onFilterUpdate, sortByProperty]);
233
229
 
234
230
  const buildErrorView = useCallback(() => (
235
231
  <div
@@ -239,7 +235,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
239
235
  {"Error fetching data from the data source"}
240
236
  </Typography>
241
237
 
242
- {error?.message && <Markdown className={"px-4 break-all"} source={error.message}/>}
238
+ {error?.message && <SafeLinkRenderer text={error.message}/>}
243
239
 
244
240
  </div>
245
241
  ), [error?.message]);
@@ -403,3 +399,17 @@ function MemoizedList({
403
399
  {Row}
404
400
  </List>;
405
401
  }
402
+
403
+ const SafeLinkRenderer: React.FC<{
404
+ text: string;
405
+ }> = ({ text }) => {
406
+ const urlRegex = /https?:\/\/[^\s]+/g;
407
+ const htmlContent = text.replace(urlRegex, (url) => {
408
+ // For each URL found, replace it with an HTML <a> tag
409
+ return `<a href="${url}" target="_blank">Link to your console</a>`;
410
+ });
411
+
412
+ return (
413
+ <div className={"px-4 break-all"} dangerouslySetInnerHTML={{ __html: htmlContent }}/>
414
+ );
415
+ };
@@ -252,7 +252,7 @@ export type VirtualTableFilterValues<Key extends string> = Partial<Record<Key, [
252
252
 
253
253
  /**
254
254
  * Filter conditions in a `Query.where()` clause are specified using the
255
- * strings '<', '<=', '==', '>=', '>', 'array-contains', 'in', 'not-in', and 'array-contains-any'.
255
+ * strings `<`, `<=`, `==`, `>=`, `>`, `array-contains`, `in`, and `array-contains-any`.
256
256
  * @see Table
257
257
  * @group Models
258
258
  */
@@ -25,7 +25,7 @@ export function VirtualTableDateField(props: {
25
25
  return (
26
26
  <DateTimeField
27
27
  value={internalValue ?? undefined}
28
- onChange={(dateValue) => updateValue(dateValue)}
28
+ onChange={(dateValue) => updateValue(dateValue ?? null)}
29
29
  size={"medium"}
30
30
  invisible={true}
31
31
  className={"w-full h-full"}
@@ -65,7 +65,7 @@ export function useDataSourceEntityCollectionTableController<M extends Record<st
65
65
 
66
66
  const [popupCell, setPopupCell] = React.useState<SelectedCellProps<M> | undefined>(undefined);
67
67
  const navigation = useNavigationController();
68
- const dataSource = useDataSource();
68
+ const dataSource = useDataSource(collection);
69
69
  const resolvedPath = useMemo(() => navigation.resolveAliasesFrom(fullPath), [fullPath, navigation.resolveAliasesFrom]);
70
70
 
71
71
  const forceFilter = forceFilterFromProps ?? forceFilterFromCollection;
@@ -1,8 +1,8 @@
1
1
  export type { ErrorViewProps } from "./ErrorView";
2
2
  export { ErrorView } from "./ErrorView";
3
3
 
4
- export type { EntityPreviewProps } from "./EntityPreview";
5
- export { EntityPreview } from "./EntityPreview";
4
+ export type { EntityViewProps } from "./EntityView";
5
+ export { EntityView } from "./EntityView";
6
6
 
7
7
  export type { ReferenceSelectionInnerProps } from "./ReferenceTable/ReferenceSelectionTable";
8
8
  export { ReferenceSelectionTable } from "./ReferenceTable/ReferenceSelectionTable";
@@ -15,6 +15,7 @@ export * from "./HomePage";
15
15
  export * from "./SelectableTable/SelectableTable";
16
16
  export * from "./EntityCollectionView/EntityCollectionView";
17
17
  export * from "./EntityCollectionView/EntityCollectionViewActions";
18
+ export * from "./EntityCollectionView/useSelectionController";
18
19
 
19
20
  export * from "./PropertyConfigBadge";
20
21
 
@@ -31,5 +32,5 @@ export * from "./FireCMSAppBar";
31
32
 
32
33
  export * from "./ArrayContainer";
33
34
  export * from "./ReferenceWidget";
34
- export * from "./LabelWithIcon";
35
35
  export * from "./SearchIconsView";
36
+ export * from "./FieldCaption";
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
2
  import { AuthController } from "../types";
3
3
 
4
- export const AuthControllerContext = React.createContext<AuthController>({} as AuthController);
4
+ export const AuthControllerContext = React.createContext<AuthController<any, any>>({} as AuthController<any, any>);