@firecms/core 3.0.0-canary.12 → 3.0.0-canary.121

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 (286) hide show
  1. package/README.md +1 -1
  2. package/dist/app/AppBar.d.ts +12 -0
  3. package/dist/app/Drawer.d.ts +17 -0
  4. package/dist/app/Scaffold.d.ts +30 -0
  5. package/dist/app/index.d.ts +4 -0
  6. package/dist/app/useApp.d.ts +16 -0
  7. package/dist/components/ArrayContainer.d.ts +2 -1
  8. package/dist/components/CircularProgressCenter.d.ts +1 -1
  9. package/dist/components/ClearFilterSortButton.d.ts +5 -0
  10. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +11 -12
  11. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  12. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +5 -3
  13. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +1 -0
  14. package/dist/components/EntityCollectionTable/column_utils.d.ts +1 -2
  15. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +3 -1
  16. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
  17. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +2 -2
  18. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +2 -2
  19. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +12 -2
  20. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
  21. package/dist/components/EntityPreview.d.ts +6 -5
  22. package/dist/components/ErrorView.d.ts +1 -1
  23. package/dist/components/HomePage/DefaultHomePage.d.ts +1 -1
  24. package/dist/components/HomePage/NavigationCardBinding.d.ts +1 -1
  25. package/dist/components/PropertyIdCopyTooltip.d.ts +8 -0
  26. package/dist/components/ReferenceWidget.d.ts +3 -1
  27. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  28. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
  29. package/dist/components/VirtualTable/VirtualTableProps.d.ts +15 -12
  30. package/dist/components/VirtualTable/types.d.ts +3 -3
  31. package/dist/components/{EntityCollectionTable/internal → common}/default_entity_actions.d.ts +1 -1
  32. package/dist/components/common/index.d.ts +1 -0
  33. package/dist/components/common/table_height.d.ts +5 -0
  34. package/dist/components/common/types.d.ts +4 -6
  35. package/dist/components/common/useColumnsIds.d.ts +2 -1
  36. package/dist/components/common/useDataSourceEntityCollectionTableController.d.ts +3 -0
  37. package/dist/components/index.d.ts +3 -1
  38. package/dist/{components/FireCMSAppBar.d.ts → core/DefaultAppBar.d.ts} +6 -9
  39. package/dist/core/DefaultDrawer.d.ts +19 -0
  40. package/dist/core/DrawerNavigationItem.d.ts +10 -0
  41. package/dist/core/EntityEditView.d.ts +18 -4
  42. package/dist/core/FireCMS.d.ts +1 -1
  43. package/dist/core/NavigationRoutes.d.ts +3 -3
  44. package/dist/core/index.d.ts +3 -4
  45. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  46. package/dist/form/components/ErrorFocus.d.ts +1 -1
  47. package/dist/form/components/FieldHelperText.d.ts +3 -3
  48. package/dist/form/components/FormikArrayContainer.d.ts +2 -1
  49. package/dist/form/components/LabelWithIcon.d.ts +1 -1
  50. package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
  51. package/dist/form/components/StorageItemPreview.d.ts +4 -4
  52. package/dist/form/components/index.d.ts +1 -0
  53. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  54. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  55. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  56. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  57. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  58. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +11 -0
  59. package/dist/form/field_bindings/{MultiSelectBinding.d.ts → MultiSelectFieldBinding.d.ts} +1 -1
  60. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  61. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  62. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  63. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -5
  64. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  65. package/dist/form/index.d.ts +3 -5
  66. package/dist/form/useClearRestoreValue.d.ts +2 -2
  67. package/dist/hooks/data/save.d.ts +1 -2
  68. package/dist/hooks/index.d.ts +1 -0
  69. package/dist/hooks/useBuildNavigationController.d.ts +1 -2
  70. package/dist/hooks/useProjectLog.d.ts +2 -2
  71. package/dist/hooks/useValidateAuthenticator.d.ts +4 -8
  72. package/dist/index.d.ts +1 -0
  73. package/dist/index.es.js +15748 -12448
  74. package/dist/index.es.js.map +1 -1
  75. package/dist/index.umd.js +19981 -587
  76. package/dist/index.umd.js.map +1 -1
  77. package/dist/internal/useBuildDataSource.d.ts +1 -16
  78. package/dist/preview/PropertyPreviewProps.d.ts +6 -1
  79. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  80. package/dist/preview/components/ReferencePreview.d.ts +3 -2
  81. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  82. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  83. package/dist/types/auth.d.ts +3 -3
  84. package/dist/types/collections.d.ts +39 -5
  85. package/dist/types/datasource.d.ts +55 -35
  86. package/dist/types/entities.d.ts +6 -1
  87. package/dist/types/entity_actions.d.ts +14 -0
  88. package/dist/types/entity_overrides.d.ts +2 -2
  89. package/dist/types/fields.d.ts +47 -41
  90. package/dist/types/index.d.ts +0 -1
  91. package/dist/types/navigation.d.ts +6 -5
  92. package/dist/types/plugins.d.ts +14 -4
  93. package/dist/types/properties.d.ts +34 -21
  94. package/dist/types/side_dialogs_controller.d.ts +10 -0
  95. package/dist/types/storage.d.ts +86 -3
  96. package/dist/util/entities.d.ts +2 -2
  97. package/dist/util/icon_list.d.ts +5 -1
  98. package/dist/util/icon_synonyms.d.ts +1 -97
  99. package/dist/util/icons.d.ts +3 -2
  100. package/dist/util/index.d.ts +1 -0
  101. package/dist/util/navigation_utils.d.ts +2 -2
  102. package/dist/util/objects.d.ts +1 -1
  103. package/dist/util/plurals.d.ts +0 -2
  104. package/dist/util/property_utils.d.ts +1 -1
  105. package/dist/util/resolutions.d.ts +27 -13
  106. package/dist/util/storage.d.ts +23 -2
  107. package/dist/util/useStorageUploadController.d.ts +3 -3
  108. package/package.json +128 -119
  109. package/src/app/AppBar.tsx +18 -0
  110. package/src/app/Drawer.tsx +25 -0
  111. package/src/app/Scaffold.tsx +245 -0
  112. package/src/app/index.ts +4 -0
  113. package/src/app/useApp.tsx +32 -0
  114. package/src/components/ArrayContainer.tsx +15 -10
  115. package/src/components/CircularProgressCenter.tsx +1 -1
  116. package/src/components/ClearFilterSortButton.tsx +41 -0
  117. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +12 -19
  118. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +21 -20
  119. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +10 -6
  120. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +41 -35
  121. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  122. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +32 -17
  123. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +14 -6
  124. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +29 -34
  125. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +32 -30
  126. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +19 -7
  127. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +7 -10
  128. package/src/components/EntityCollectionView/EntityCollectionView.tsx +75 -51
  129. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  130. package/src/components/EntityCollectionView/useSelectionController.tsx +19 -7
  131. package/src/components/EntityPreview.tsx +38 -34
  132. package/src/components/EntityView.tsx +5 -5
  133. package/src/components/ErrorView.tsx +1 -1
  134. package/src/components/HomePage/DefaultHomePage.tsx +26 -19
  135. package/src/components/HomePage/NavigationCard.tsx +3 -3
  136. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  137. package/src/components/HomePage/SmallNavigationCard.tsx +5 -6
  138. package/src/components/PropertyIdCopyTooltip.tsx +48 -0
  139. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +8 -6
  140. package/src/components/ReferenceWidget.tsx +22 -12
  141. package/src/components/SearchIconsView.tsx +11 -7
  142. package/src/components/SelectableTable/SelectableTable.tsx +7 -7
  143. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  144. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +22 -7
  145. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +28 -6
  146. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +89 -21
  147. package/src/components/VirtualTable/VirtualTable.tsx +70 -37
  148. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -9
  149. package/src/components/VirtualTable/VirtualTableHeader.tsx +4 -4
  150. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
  151. package/src/components/VirtualTable/VirtualTableProps.tsx +18 -14
  152. package/src/components/VirtualTable/VirtualTableRow.tsx +4 -5
  153. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +4 -4
  154. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +2 -1
  155. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
  156. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +14 -28
  157. package/src/components/VirtualTable/types.tsx +2 -3
  158. package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +2 -2
  159. package/src/components/common/index.ts +1 -0
  160. package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
  161. package/src/components/common/types.tsx +4 -6
  162. package/src/components/common/useColumnsIds.tsx +23 -2
  163. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +11 -0
  164. package/src/components/common/useTableSearchHelper.ts +53 -12
  165. package/src/components/index.tsx +3 -1
  166. package/src/contexts/DialogsProvider.tsx +2 -2
  167. package/src/{components/FireCMSAppBar.tsx → core/DefaultAppBar.tsx} +52 -37
  168. package/src/core/DefaultDrawer.tsx +179 -0
  169. package/src/core/DrawerNavigationItem.tsx +66 -0
  170. package/src/core/EntityEditView.tsx +716 -149
  171. package/src/core/EntitySidePanel.tsx +1 -2
  172. package/src/core/FireCMS.tsx +39 -44
  173. package/src/core/NavigationRoutes.tsx +7 -8
  174. package/src/core/SideDialogs.tsx +2 -0
  175. package/src/core/field_configs.tsx +7 -8
  176. package/src/core/index.tsx +3 -4
  177. package/src/form/PropertyFieldBinding.tsx +53 -21
  178. package/src/form/components/CustomIdField.tsx +4 -2
  179. package/src/form/components/FieldHelperText.tsx +4 -4
  180. package/src/form/components/FormikArrayContainer.tsx +4 -1
  181. package/src/form/components/LabelWithIcon.tsx +27 -19
  182. package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
  183. package/src/form/components/StorageItemPreview.tsx +21 -11
  184. package/src/form/components/StorageUploadProgress.tsx +3 -4
  185. package/src/form/components/index.tsx +1 -0
  186. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +26 -15
  187. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +29 -24
  188. package/src/form/field_bindings/BlockFieldBinding.tsx +14 -12
  189. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -14
  190. package/src/form/field_bindings/KeyValueFieldBinding.tsx +52 -46
  191. package/src/form/field_bindings/MapFieldBinding.tsx +17 -17
  192. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +150 -0
  193. package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +25 -20
  194. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +11 -16
  195. package/src/form/field_bindings/ReferenceFieldBinding.tsx +41 -30
  196. package/src/form/field_bindings/RepeatFieldBinding.tsx +32 -19
  197. package/src/form/field_bindings/SelectFieldBinding.tsx +20 -13
  198. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +36 -46
  199. package/src/form/field_bindings/SwitchFieldBinding.tsx +9 -5
  200. package/src/form/field_bindings/TextFieldBinding.tsx +26 -23
  201. package/src/form/index.tsx +4 -9
  202. package/src/form/useClearRestoreValue.tsx +2 -2
  203. package/src/form/validation.ts +1 -17
  204. package/src/hooks/data/delete.ts +2 -1
  205. package/src/hooks/data/save.ts +6 -1
  206. package/src/hooks/data/useDataSource.tsx +11 -3
  207. package/src/hooks/index.tsx +1 -0
  208. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +8 -10
  209. package/src/hooks/useBuildModeController.tsx +11 -5
  210. package/src/hooks/useBuildNavigationController.tsx +91 -31
  211. package/src/hooks/useProjectLog.tsx +19 -8
  212. package/src/hooks/useResolvedNavigationFrom.tsx +1 -1
  213. package/src/hooks/useValidateAuthenticator.tsx +21 -41
  214. package/src/index.ts +1 -0
  215. package/src/internal/useBuildDataSource.ts +71 -81
  216. package/src/internal/useBuildSideDialogsController.tsx +1 -0
  217. package/src/internal/useBuildSideEntityController.tsx +102 -22
  218. package/src/internal/useUnsavedChangesDialog.tsx +3 -1
  219. package/src/preview/PropertyPreview.tsx +10 -17
  220. package/src/preview/PropertyPreviewProps.tsx +7 -1
  221. package/src/preview/components/BooleanPreview.tsx +2 -2
  222. package/src/preview/components/EnumValuesChip.tsx +2 -2
  223. package/src/preview/components/ImagePreview.tsx +24 -34
  224. package/src/preview/components/ReferencePreview.tsx +23 -34
  225. package/src/preview/components/StorageThumbnail.tsx +5 -1
  226. package/src/preview/components/UrlComponentPreview.tsx +44 -11
  227. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -2
  228. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +3 -2
  229. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +1 -2
  230. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +0 -1
  231. package/src/preview/property_previews/ArrayOneOfPreview.tsx +3 -4
  232. package/src/preview/property_previews/ArrayPropertyPreview.tsx +3 -4
  233. package/src/preview/property_previews/MapPropertyPreview.tsx +6 -6
  234. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +2 -2
  235. package/src/preview/property_previews/StringPropertyPreview.tsx +3 -3
  236. package/src/preview/util.ts +2 -2
  237. package/src/types/auth.tsx +3 -3
  238. package/src/types/collections.ts +48 -7
  239. package/src/types/customization_controller.tsx +0 -1
  240. package/src/types/datasource.ts +65 -43
  241. package/src/types/entities.ts +11 -1
  242. package/src/types/entity_actions.tsx +16 -3
  243. package/src/types/entity_overrides.tsx +2 -2
  244. package/src/types/fields.tsx +51 -45
  245. package/src/types/index.ts +0 -1
  246. package/src/types/navigation.ts +7 -7
  247. package/src/types/plugins.tsx +16 -6
  248. package/src/types/properties.ts +39 -22
  249. package/src/types/side_dialogs_controller.tsx +13 -0
  250. package/src/types/storage.ts +94 -3
  251. package/src/util/entities.ts +6 -5
  252. package/src/util/enums.ts +1 -1
  253. package/src/util/icon_list.ts +15 -10
  254. package/src/util/icon_synonyms.ts +3 -99
  255. package/src/util/icons.tsx +8 -2
  256. package/src/util/index.ts +1 -0
  257. package/src/util/navigation_utils.ts +6 -6
  258. package/src/util/objects.ts +25 -28
  259. package/src/util/permissions.ts +1 -0
  260. package/src/util/plurals.ts +0 -2
  261. package/src/util/property_utils.tsx +1 -1
  262. package/src/util/references.ts +3 -0
  263. package/src/util/resolutions.ts +74 -41
  264. package/src/util/storage.ts +79 -21
  265. package/src/util/strings.ts +2 -2
  266. package/src/util/useStorageUploadController.tsx +33 -7
  267. package/src/util/useTraceUpdate.tsx +1 -0
  268. package/dist/components/PropertyIdCopyTooltipContent.d.ts +0 -3
  269. package/dist/components/VirtualTable/common.d.ts +0 -2
  270. package/dist/core/Drawer.d.ts +0 -23
  271. package/dist/core/Scaffold.d.ts +0 -55
  272. package/dist/core/SideEntityView.d.ts +0 -7
  273. package/dist/form/EntityForm.d.ts +0 -77
  274. package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
  275. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  276. package/dist/internal/useLocaleConfig.d.ts +0 -1
  277. package/dist/types/appcheck.d.ts +0 -26
  278. package/src/components/PropertyIdCopyTooltipContent.tsx +0 -28
  279. package/src/core/Drawer.tsx +0 -191
  280. package/src/core/Scaffold.tsx +0 -281
  281. package/src/core/SideEntityView.tsx +0 -38
  282. package/src/form/EntityForm.tsx +0 -728
  283. package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -695
  284. package/src/internal/useBuildCustomizationController.tsx +0 -5
  285. package/src/internal/useLocaleConfig.tsx +0 -18
  286. package/src/types/appcheck.ts +0 -29
@@ -1,18 +1,41 @@
1
- import React, { useCallback, useEffect, useRef, useState } from "react";
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import {
3
+ CMSAnalyticsEvent,
3
4
  Entity,
5
+ EntityAction,
4
6
  EntityCollection,
5
7
  EntityCustomView,
6
8
  EntityStatus,
7
9
  EntityValues,
8
10
  FireCMSPlugin,
9
11
  FormContext,
12
+ PluginFormActionProps,
13
+ PropertyFieldBindingProps,
14
+ ResolvedEntityCollection,
10
15
  User
11
16
  } from "../types";
12
- import { CircularProgressCenter, EntityCollectionView, EntityView, ErrorBoundary, } from "../components";
17
+ import equal from "react-fast-compare"
18
+
19
+ import {
20
+ CircularProgressCenter,
21
+ copyEntityAction,
22
+ deleteEntityAction,
23
+ EntityCollectionView,
24
+ EntityView,
25
+ ErrorBoundary,
26
+ getFormFieldKeys,
27
+ } from "../components";
13
28
  import {
29
+ canCreateEntity,
30
+ canDeleteEntity,
14
31
  canEditEntity,
32
+ getDefaultValuesFor,
33
+ getEntityTitlePropertyKey,
34
+ getValueInPath,
35
+ isHidden,
36
+ isReadOnly,
15
37
  removeInitialAndTrailingSlashes,
38
+ resolveCollection,
16
39
  resolveDefaultSelectedView,
17
40
  resolveEntityView,
18
41
  useDebouncedCallback
@@ -28,11 +51,29 @@ import {
28
51
  useSideEntityController,
29
52
  useSnackbarController
30
53
  } from "../hooks";
31
- import { EntityForm } from "../form";
32
- import { CircularProgress, CloseIcon, cn, defaultBorderMixin, IconButton, Tab, Tabs, Typography } from "@firecms/ui";
33
- import { EntityFormSaveParams } from "../form/EntityForm";
34
- import { FORM_CONTAINER_WIDTH } from "../internal/common";
54
+ import {
55
+ Alert,
56
+ Button,
57
+ CircularProgress,
58
+ CloseIcon,
59
+ cls,
60
+ defaultBorderMixin,
61
+ DialogActions,
62
+ IconButton,
63
+ NotesIcon,
64
+ paperMixin,
65
+ Tab,
66
+ Tabs,
67
+ Typography
68
+ } from "@firecms/ui";
35
69
  import { useSideDialogContext } from "./index";
70
+ import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
71
+ import { useAnalyticsController } from "../hooks/useAnalyticsController";
72
+ import { CustomIdField } from "../form/components/CustomIdField";
73
+ import { CustomFieldValidator, getYupEntitySchema } from "../form/validation";
74
+ import { ErrorFocus } from "../form/components/ErrorFocus";
75
+ import { LabelWithIconAndTooltip, PropertyFieldBinding } from "../form";
76
+ import { ValidationError } from "yup";
36
77
 
37
78
  const MAIN_TAB_VALUE = "main_##Q$SC^#S6";
38
79
 
@@ -43,7 +84,6 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
43
84
  copy?: boolean;
44
85
  selectedSubPath?: string;
45
86
  parentCollectionIds: string[];
46
- formWidth?: number | string;
47
87
  onValuesAreModified: (modified: boolean) => void;
48
88
  onUpdate?: (params: { entity: Entity<any> }) => void;
49
89
  onClose?: () => void;
@@ -53,20 +93,54 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
53
93
  * This is the default view that is used as the content of a side panel when
54
94
  * an entity is opened.
55
95
  * You probably don't want to use this view directly since it is bound to the
56
- * side panel. Instead, you might want to use {@link EntityForm} or {@link EntityCollectionView}
96
+ * side panel.
57
97
  */
58
98
  export function EntityEditView<M extends Record<string, any>, UserType extends User>({
59
- path,
60
99
  entityId,
61
- selectedSubPath,
62
- copy,
63
- collection,
64
- parentCollectionIds,
65
- onValuesAreModified,
66
- formWidth,
67
- onUpdate,
68
- onClose,
100
+ ...props
69
101
  }: EntityEditViewProps<M>) {
102
+ const {
103
+ entity,
104
+ dataLoading,
105
+ // eslint-disable-next-line no-unused-vars
106
+ dataLoadingError
107
+ } = useEntityFetch<M, UserType>({
108
+ path: props.path,
109
+ entityId: entityId,
110
+ collection: props.collection,
111
+ useCache: false
112
+ });
113
+
114
+ if (dataLoading) {
115
+ return <CircularProgressCenter/>
116
+ }
117
+
118
+ if (entityId && !entity) {
119
+ console.error(`Entity with id ${entityId} not found in collection ${props.collection.path}`);
120
+ }
121
+
122
+ return <EntityEditViewInner<M> {...props}
123
+ entityId={entityId}
124
+ entity={entity}
125
+ dataLoading={dataLoading}/>;
126
+ }
127
+
128
+ export function EntityEditViewInner<M extends Record<string, any>>({
129
+ path,
130
+ entityId: entityIdProp,
131
+ selectedSubPath: selectedSubPathProp,
132
+ copy,
133
+ collection,
134
+ parentCollectionIds,
135
+ onValuesAreModified,
136
+ onUpdate,
137
+ onClose,
138
+ entity,
139
+ dataLoading
140
+ }: EntityEditViewProps<M> & {
141
+ entity?: Entity<M>,
142
+ dataLoading: boolean
143
+ }) {
70
144
 
71
145
  if (collection.customId && collection.formAutoSave) {
72
146
  console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
@@ -91,33 +165,65 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
91
165
 
92
166
  // const largeLayout = useLargeLayout();
93
167
  // const largeLayoutTabSelected = useRef(!largeLayout);
168
+ // const resolvedFormWidth: string = typeof formWidth === "number" ? `${formWidth}px` : formWidth ?? FORM_CONTAINER_WIDTH;
94
169
 
95
- const resolvedFormWidth: string = typeof formWidth === "number" ? `${formWidth}px` : formWidth ?? FORM_CONTAINER_WIDTH;
170
+ const inputCollection = collection;
96
171
 
172
+ const authController = useAuthController();
97
173
  const dataSource = useDataSource(collection);
98
174
  const sideDialogContext = useSideDialogContext();
99
175
  const sideEntityController = useSideEntityController();
100
176
  const snackbarController = useSnackbarController();
101
177
  const customizationController = useCustomizationController();
102
178
  const context = useFireCMSContext();
103
- const authController = useAuthController<UserType>();
104
179
 
105
- const [formContext, setFormContext] = useState<FormContext<M> | undefined>(undefined);
180
+ const closeAfterSaveRef = useRef(false);
181
+
182
+ const analyticsController = useAnalyticsController();
183
+
184
+ const initialResolvedCollection = useMemo(() => resolveCollection({
185
+ collection: inputCollection,
186
+ path,
187
+ values: entity?.values,
188
+ fields: customizationController.propertyConfigs
189
+ }), [entity?.values, path, customizationController.propertyConfigs]);
190
+
191
+ const initialStatus = copy ? "copy" : (entityIdProp ? "existing" : "new");
192
+ const [status, setStatus] = useState<EntityStatus>(initialStatus);
193
+
194
+ const mustSetCustomId: boolean = (status === "new" || status === "copy") &&
195
+ (Boolean(initialResolvedCollection.customId) && initialResolvedCollection.customId !== "optional");
196
+
197
+ const initialEntityId: string | undefined = useMemo((): string | undefined => {
198
+ if (status === "new" || status === "copy") {
199
+ if (mustSetCustomId) {
200
+ return undefined;
201
+ } else {
202
+ return dataSource.generateEntityId(path, collection);
203
+ }
204
+ } else {
205
+ return entityIdProp;
206
+ }
207
+ }, [entityIdProp, status]);
106
208
 
107
- const [status, setStatus] = useState<EntityStatus>(copy ? "copy" : (entityId ? "existing" : "new"));
209
+ const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
108
210
 
109
- const modifiedValuesRef = useRef<EntityValues<M> | undefined>(undefined);
110
- const modifiedValues = modifiedValuesRef.current;
211
+ // const doOnValuesChanges = (values?: EntityValues<M>) => {
212
+ // const initialValues = formex.initialValues;
213
+ // setInternalValues(values);
214
+ // if (onValuesChanged)
215
+ // onValuesChanged(values);
216
+ // if (autoSave && values && !equal(values, initialValues)) {
217
+ // save(values);
218
+ // }
219
+ // };
111
220
 
112
- const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
113
- const subcollectionsCount = subcollections?.length ?? 0;
114
- const customViews = collection.entityViews;
115
- const customViewsCount = customViews?.length ?? 0;
116
- const autoSave = collection.formAutoSave && !collection.customId;
221
+ const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
222
+ const [savingError, setSavingError] = React.useState<Error | undefined>();
117
223
 
118
- const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
224
+ const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
119
225
 
120
- const defaultSelectedView = selectedSubPath ?? resolveDefaultSelectedView(
226
+ const defaultSelectedView = selectedSubPathProp ?? resolveDefaultSelectedView(
121
227
  collection ? collection.defaultSelectedView : undefined,
122
228
  {
123
229
  status,
@@ -125,25 +231,23 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
125
231
  }
126
232
  );
127
233
 
128
- const selectedTabRef = useRef<string>(defaultSelectedView ?? MAIN_TAB_VALUE);
234
+ const [selectedTab, setSelectedTab] = useState<string>(defaultSelectedView ?? MAIN_TAB_VALUE);
129
235
 
130
- const mainViewVisible = selectedTabRef.current === MAIN_TAB_VALUE;
236
+ const mainViewVisible = selectedTab === MAIN_TAB_VALUE;
131
237
 
132
- const {
133
- entity,
134
- dataLoading,
135
- // eslint-disable-next-line no-unused-vars
136
- dataLoadingError
137
- } = useEntityFetch<M, UserType>({
138
- path,
139
- entityId,
140
- collection,
141
- useCache: false
142
- });
238
+ const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
239
+ const subcollectionsCount = subcollections?.length ?? 0;
240
+ const customViews = collection.entityViews;
241
+ const customViewsCount = customViews?.length ?? 0;
242
+ const autoSave = collection.formAutoSave && !collection.customId;
243
+
244
+ const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
143
245
 
144
246
  const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
145
247
  const [readOnly, setReadOnly] = useState<boolean | undefined>(undefined);
146
248
 
249
+ const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, usedEntity));
250
+
147
251
  useEffect(() => {
148
252
  if (entity)
149
253
  setUsedEntity(entity);
@@ -195,6 +299,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
195
299
  onUpdate({ entity: updatedEntity });
196
300
 
197
301
  if (closeAfterSave) {
302
+ console.log("Closing side dialog")
198
303
  sideDialogContext.setBlocked(false);
199
304
  sideDialogContext.close(true);
200
305
  onClose?.();
@@ -202,7 +307,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
202
307
  sideEntityController.replace({
203
308
  path,
204
309
  entityId: updatedEntity.id,
205
- selectedSubPath: selectedTabRef.current,
310
+ selectedSubPath: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab,
206
311
  updateUrl: true,
207
312
  collection,
208
313
  });
@@ -238,7 +343,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
238
343
  closeAfterSave: boolean,
239
344
  }) => {
240
345
  setSaving(true);
241
- saveEntityWithCallbacks({
346
+ return saveEntityWithCallbacks({
242
347
  path,
243
348
  entityId,
244
349
  values,
@@ -269,7 +374,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
269
374
  if (autoSave) {
270
375
  setValuesToBeSaved(values);
271
376
  } else {
272
- saveEntity({
377
+ return saveEntity({
273
378
  collection,
274
379
  path,
275
380
  entityId,
@@ -280,17 +385,121 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
280
385
  }
281
386
  };
282
387
 
388
+ const onSubmit = (values: EntityValues<M>, formexController: FormexController<EntityValues<M>>) => {
389
+
390
+ if (mustSetCustomId && !entityId) {
391
+ console.error("Missing custom Id");
392
+ setEntityIdError(true);
393
+ formexController.setSubmitting(false);
394
+ return;
395
+ }
396
+
397
+ setSavingError(undefined);
398
+ setEntityIdError(false);
399
+
400
+ if (status === "existing") {
401
+ if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
402
+ } else if (status === "new" || status === "copy") {
403
+ if (inputCollection.customId) {
404
+ if (inputCollection.customId !== "optional" && !entityId) {
405
+ throw Error("Form misconfiguration when saving, entityId should be set");
406
+ }
407
+ }
408
+ } else {
409
+ throw Error("New FormType added, check EntityForm");
410
+ }
411
+
412
+ return save(values)
413
+ ?.then(_ => {
414
+ formexController.resetForm({
415
+ values,
416
+ submitCount: 0,
417
+ touched: {}
418
+ });
419
+ })
420
+ .finally(() => {
421
+ formexController.setSubmitting(false);
422
+ });
423
+
424
+ };
425
+
426
+ const formex: FormexController<M> = useCreateFormex<M>({
427
+ initialValues: baseDataSourceValuesRef.current as M,
428
+ onSubmit,
429
+ validation: (values) => {
430
+ return validationSchema?.validate(values, { abortEarly: false })
431
+ .then(() => {
432
+ return {};
433
+ })
434
+ .catch((e: any) => {
435
+ const errors: Record<string, string> = {};
436
+ e.inner.forEach((error: any) => {
437
+ errors[error.path] = error.message;
438
+ });
439
+ return yupToFormErrors(e);
440
+ });
441
+ }
442
+ });
443
+
444
+ const resolvedCollection = resolveCollection<M>({
445
+ collection: inputCollection,
446
+ path,
447
+ entityId,
448
+ values: formex.values,
449
+ previousValues: formex.initialValues,
450
+ fields: customizationController.propertyConfigs
451
+ });
452
+
453
+ const lastSavedValues = useRef<EntityValues<M> | undefined>(entity?.values);
454
+
455
+ const save = (values: EntityValues<M>): Promise<void> => {
456
+ lastSavedValues.current = values;
457
+ return onSaveEntityRequest({
458
+ collection: resolvedCollection,
459
+ path,
460
+ entityId,
461
+ values,
462
+ previousValues: entity?.values,
463
+ closeAfterSave: closeAfterSaveRef.current,
464
+ autoSave: autoSave ?? false
465
+ }).then(_ => {
466
+ const eventName: CMSAnalyticsEvent = status === "new"
467
+ ? "new_entity_saved"
468
+ : (status === "copy" ? "entity_copied" : (status === "existing" ? "entity_edited" : "unmapped_event"));
469
+ analyticsController.onAnalyticsEvent?.(eventName, { path });
470
+ }).catch(e => {
471
+ console.error(e);
472
+ setSavingError(e);
473
+ }).finally(() => {
474
+ closeAfterSaveRef.current = false;
475
+ });
476
+ };
477
+
478
+ const formContext: FormContext<M> = {
479
+ // @ts-ignore
480
+ setFieldValue: useCallback(formex.setFieldValue, []),
481
+ values: formex.values,
482
+ collection: resolvedCollection,
483
+ entityId: entityId as string,
484
+ path,
485
+ save,
486
+ formex
487
+ };
488
+
283
489
  const resolvedEntityViews = customViews ? customViews
284
490
  .map(e => resolveEntityView(e, customizationController.entityViews))
285
491
  .filter(Boolean) as EntityCustomView[]
286
492
  : [];
287
493
 
494
+ const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTab);
495
+ const shouldShowEntityActions = !autoSave && (selectedTab === MAIN_TAB_VALUE || selectedEntityView?.includeActions);
496
+
288
497
  const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
289
498
  .map(
290
499
  (customView, colIndex) => {
291
500
  if (!customView)
292
501
  return null;
293
- if (selectedTabRef.current !== customView.key)
502
+ if (selectedTab !== customView.key)
294
503
  return null;
295
504
  const Builder = customView.Builder;
296
505
  if (!Builder) {
@@ -298,7 +507,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
298
507
  return null;
299
508
  }
300
509
  return <div
301
- className={cn(defaultBorderMixin,
510
+ className={cls(defaultBorderMixin,
302
511
  "relative flex-grow w-full h-full overflow-auto ")}
303
512
  key={`custom_view_${customView.key}`}
304
513
  role="tabpanel">
@@ -306,7 +515,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
306
515
  {formContext && <Builder
307
516
  collection={collection}
308
517
  entity={usedEntity}
309
- modifiedValues={modifiedValues ?? usedEntity?.values}
518
+ modifiedValues={formex.values ?? usedEntity?.values}
310
519
  formContext={formContext}
311
520
  />}
312
521
  </ErrorBoundary>
@@ -323,7 +532,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
323
532
  (subcollection, colIndex) => {
324
533
  const subcollectionId = subcollection.id ?? subcollection.path;
325
534
  const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
326
- if (selectedTabRef.current !== subcollectionId)
535
+ if (selectedTab !== subcollectionId)
327
536
  return null;
328
537
  return (
329
538
  <div
@@ -359,21 +568,18 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
359
568
  }, []);
360
569
 
361
570
  const onSideTabClick = (value: string) => {
362
- selectedTabRef.current = value;
363
- sideEntityController.replace({
364
- path,
365
- entityId,
366
- selectedSubPath: value === MAIN_TAB_VALUE ? undefined : value,
367
- updateUrl: true,
368
- collection,
369
- });
571
+ setSelectedTab(value);
572
+ if (status === "existing") {
573
+ sideEntityController.replace({
574
+ path,
575
+ entityId,
576
+ selectedSubPath: value === MAIN_TAB_VALUE ? undefined : value,
577
+ updateUrl: true,
578
+ collection,
579
+ });
580
+ }
370
581
  };
371
582
 
372
- const onValuesChanged = useCallback((values?: EntityValues<M>) => {
373
- modifiedValuesRef.current = values;
374
- }, []);
375
-
376
- // eslint-disable-next-line n/handle-callback-err
377
583
  const onIdUpdateError = useCallback((error: any) => {
378
584
  snackbarController.open({
379
585
  type: "error",
@@ -390,28 +596,345 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
390
596
  : undefined);
391
597
  }, []);
392
598
 
393
- const onModified = (dirty: boolean) => {
394
- if (!autoSave)
395
- onValuesAreModified(dirty);
599
+ // useEffect(() => {
600
+ // baseDataSourceValuesRef.current = getDataSourceEntityValues(initialResolvedCollection, status, entity);
601
+ // const initialValues = formex.initialValues;
602
+ // if (!formex.isSubmitting && initialValues && status === "existing") {
603
+ // setUnderlyingChanges(
604
+ // Object.entries(resolvedCollection.properties)
605
+ // .map(([key, property]) => {
606
+ // if (isHidden(property)) {
607
+ // return {};
608
+ // }
609
+ // const initialValue = initialValues[key];
610
+ // const latestValue = baseDataSourceValuesRef.current[key];
611
+ // if (!equal(initialValue, latestValue)) {
612
+ // return { [key]: latestValue };
613
+ // }
614
+ // return {};
615
+ // })
616
+ // .reduce((a, b) => ({ ...a, ...b }), {}) as Partial<EntityValues<M>>
617
+ // );
618
+ // } else {
619
+ // setUnderlyingChanges({});
620
+ // }
621
+ // }, [entity, initialResolvedCollection, status]);
622
+
623
+ const pluginActions: React.ReactNode[] = [];
624
+
625
+ const plugins = customizationController.plugins;
626
+
627
+ if (plugins && inputCollection) {
628
+ const actionProps: PluginFormActionProps = {
629
+ entityId,
630
+ path,
631
+ status,
632
+ collection: inputCollection,
633
+ context,
634
+ currentEntityId: entityId,
635
+ formContext
636
+ };
637
+ pluginActions.push(...plugins.map((plugin, i) => (
638
+ plugin.form?.Actions
639
+ ? <plugin.form.Actions
640
+ key={`actions_${plugin.key}`} {...actionProps}/>
641
+ : null
642
+ )).filter(Boolean));
396
643
  }
397
644
 
645
+ const titlePropertyKey = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
646
+ const title = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : undefined;
647
+
648
+ const onIdUpdate = inputCollection.callbacks?.onIdUpdate;
649
+
650
+ const doOnIdUpdate = useCallback(async () => {
651
+ if (onIdUpdate && formex.values && (status === "new" || status === "copy")) {
652
+ setCustomIdLoading(true);
653
+ try {
654
+ const updatedId = await onIdUpdate({
655
+ collection: resolvedCollection,
656
+ path,
657
+ entityId,
658
+ values: formex.values,
659
+ context
660
+ });
661
+ setEntityId(updatedId);
662
+ } catch (e) {
663
+ onIdUpdateError && onIdUpdateError(e);
664
+ console.error(e);
665
+ }
666
+ setCustomIdLoading(false);
667
+ }
668
+ }, [entityId, formex.values, status]);
669
+
670
+ useEffect(() => {
671
+ doOnIdUpdate();
672
+ }, [doOnIdUpdate]);
673
+
674
+ const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
675
+
676
+ const uniqueFieldValidator: CustomFieldValidator = useCallback(({
677
+ name,
678
+ value,
679
+ property
680
+ }) => dataSource.checkUniqueField(path, name, value, entityId, collection),
681
+ [dataSource, path, entityId]);
682
+
683
+ const validationSchema = useMemo(() => entityId
684
+ ? getYupEntitySchema(
685
+ entityId,
686
+ resolvedCollection.properties,
687
+ uniqueFieldValidator)
688
+ : undefined,
689
+ [entityId, resolvedCollection.properties, uniqueFieldValidator]);
690
+
691
+ const getActionsForEntity = useCallback(({
692
+ entity,
693
+ customEntityActions
694
+ }: {
695
+ entity?: Entity<M>,
696
+ customEntityActions?: EntityAction[]
697
+ }): EntityAction[] => {
698
+ const createEnabled = canCreateEntity(inputCollection, authController, path, null);
699
+ const deleteEnabled = entity ? canDeleteEntity(inputCollection, authController, path, entity) : true;
700
+ const actions: EntityAction[] = [];
701
+ if (createEnabled)
702
+ actions.push(copyEntityAction);
703
+ if (deleteEnabled)
704
+ actions.push(deleteEntityAction);
705
+ if (customEntityActions)
706
+ actions.push(...customEntityActions);
707
+ return actions;
708
+ }, [authController, inputCollection, path]);
709
+
710
+ const modified = formex.dirty;
711
+ useEffect(() => {
712
+ if (!autoSave) {
713
+ onValuesAreModified(modified);
714
+ } else {
715
+ if (formex.values && !equal(formex.values, lastSavedValues.current)) {
716
+ save(formex.values);
717
+ }
718
+ }
719
+ }, [modified, formex.values]);
720
+
721
+ useEffect(() => {
722
+ if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
723
+ // we update the form fields from the Firestore data
724
+ // if they were not touched
725
+ Object.entries(underlyingChanges).forEach(([key, value]) => {
726
+ const formValue = formex.values[key];
727
+ if (!equal(value, formValue) && !formex.touched[key]) {
728
+ console.debug("Updated value from the datasource:", key, value);
729
+ formex.setFieldValue(key, value !== undefined ? value : null);
730
+ }
731
+ });
732
+ }
733
+ }, [formex.isSubmitting, autoSave, underlyingChanges, entity, formex.values, formex.touched, formex.setFieldValue]);
734
+
735
+ const formFields = (
736
+ <>
737
+ {(getFormFieldKeys(resolvedCollection))
738
+ .map((key) => {
739
+
740
+ const property = resolvedCollection.properties[key];
741
+ if (property) {
742
+
743
+ const underlyingValueHasChanged: boolean =
744
+ !!underlyingChanges &&
745
+ Object.keys(underlyingChanges).includes(key) &&
746
+ formex.touched[key];
747
+
748
+ const disabled = (!autoSave && formex.isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
749
+ const hidden = isHidden(property);
750
+ if (hidden) return null;
751
+ const cmsFormFieldProps: PropertyFieldBindingProps<any, M> = {
752
+ propertyKey: key,
753
+ disabled,
754
+ property,
755
+ includeDescription: property.description || property.longDescription,
756
+ underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
757
+ context: formContext,
758
+ partOfArray: false,
759
+ minimalistView: false,
760
+ autoFocus: false
761
+ };
762
+
763
+ return (
764
+ <div id={`form_field_${key}`}
765
+ key={`field_${resolvedCollection.name}_${key}`}>
766
+ <ErrorBoundary>
767
+ <PropertyFieldBinding {...cmsFormFieldProps}/>
768
+ </ErrorBoundary>
769
+ </div>
770
+ );
771
+ }
772
+
773
+ const additionalField = resolvedCollection.additionalFields?.find(f => f.key === key);
774
+ if (additionalField && entity) {
775
+ const Builder = additionalField.Builder;
776
+ if (!Builder && !additionalField.value) {
777
+ throw new Error("When using additional fields you need to provide a Builder or a value");
778
+ }
779
+
780
+ const child = Builder
781
+ ? <Builder entity={entity} context={context}/>
782
+ : <>{additionalField.value?.({
783
+ entity,
784
+ context
785
+ })}</>;
786
+ return (
787
+ <div>
788
+ <LabelWithIconAndTooltip
789
+ propertyKey={key}
790
+ icon={<NotesIcon size={"small"}/>}
791
+ title={additionalField.name}
792
+ className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
793
+ <div
794
+ className={cls(paperMixin, "min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
795
+
796
+ <ErrorBoundary>
797
+ {child}
798
+ </ErrorBoundary>
799
+
800
+ </div>
801
+ </div>
802
+ );
803
+ }
804
+
805
+ console.warn(`Property ${key} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
806
+ return null;
807
+ })
808
+ .filter(Boolean)}
809
+
810
+ </>
811
+ );
812
+
813
+ const disabled = formex.isSubmitting || (!modified && status === "existing");
814
+ const formRef = React.useRef<HTMLDivElement>(null);
815
+
816
+ const entityActions = getActionsForEntity({
817
+ entity,
818
+ customEntityActions: inputCollection.entityActions
819
+ });
820
+ const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
821
+
822
+ const dialogActions = <DialogActions position={"absolute"}>
823
+
824
+ {savingError &&
825
+ <div className="text-right">
826
+ <Typography color={"error"}>
827
+ {savingError.message}
828
+ </Typography>
829
+ </div>}
830
+
831
+ {entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
832
+ {formActions.map(action => (
833
+ <IconButton
834
+ key={action.name}
835
+ color="primary"
836
+ onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
837
+ event.stopPropagation();
838
+ if (entity)
839
+ action.onClick({
840
+ entity,
841
+ fullPath: resolvedCollection.path,
842
+ collection: resolvedCollection,
843
+ context,
844
+ sideEntityController
845
+ });
846
+ }}>
847
+ {action.icon}
848
+ </IconButton>
849
+ ))}
850
+ </div>}
851
+ {formex.isSubmitting && <CircularProgress size={"small"}/>}
852
+ <Button
853
+ variant="text"
854
+ disabled={disabled || formex.isSubmitting}
855
+ type="reset">
856
+ {status === "existing" ? "Discard" : "Clear"}
857
+ </Button>
858
+
859
+ <Button
860
+ variant="text"
861
+ color="primary"
862
+ type="submit"
863
+ disabled={disabled || formex.isSubmitting}
864
+ onClick={() => {
865
+ closeAfterSaveRef.current = false;
866
+ }}>
867
+ {status === "existing" && "Save"}
868
+ {status === "copy" && "Create copy"}
869
+ {status === "new" && "Create"}
870
+ </Button>
871
+
872
+ <Button
873
+ variant="filled"
874
+ color="primary"
875
+ type="submit"
876
+ disabled={disabled || formex.isSubmitting}
877
+ onClick={() => {
878
+ closeAfterSaveRef.current = true;
879
+ }}>
880
+ {status === "existing" && "Save and close"}
881
+ {status === "copy" && "Create copy and close"}
882
+ {status === "new" && "Create and close"}
883
+ </Button>
884
+
885
+ </DialogActions>;
886
+
398
887
  function buildForm() {
399
- const plugins = customizationController.plugins;
400
- let form = <EntityForm
401
- status={status}
402
- path={path}
403
- collection={collection}
404
- onEntitySaveRequested={onSaveEntityRequest}
405
- onDiscard={onDiscard}
406
- onValuesChanged={onValuesChanged}
407
- onModified={onModified}
408
- entity={usedEntity}
409
- onIdChange={onIdChange}
410
- onFormContextChange={setFormContext}
411
- hideId={collection.hideIdFromForm}
412
- autoSave={autoSave}
413
- onIdUpdateError={onIdUpdateError}
414
- />;
888
+
889
+ let form = <>
890
+
891
+ {pluginActions.length > 0 && <div
892
+ className={cls("w-full flex justify-end items-center sticky top-0 right-0 left-0 z-10 bg-opacity-60 bg-slate-200 dark:bg-opacity-60 dark:bg-slate-800 backdrop-blur-md")}>
893
+ {pluginActions}
894
+ </div>}
895
+
896
+ <div className="h-full overflow-auto">
897
+
898
+ <div className="pt-12 pb-16 pl-8 pr-8 md:pl-10 md:pr-10">
899
+ <div
900
+ className={`w-full py-2 flex flex-col items-start mt-${4 + (pluginActions ? 8 : 0)} lg:mt-${8 + (pluginActions ? 8 : 0)} mb-8`}>
901
+
902
+ <Typography
903
+ className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
904
+ variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
905
+ </Typography>
906
+ <Alert color={"base"} className={"w-full"} size={"small"}>
907
+ <code className={"text-xs select-all"}>{path}/{entityId}</code>
908
+ </Alert>
909
+ </div>
910
+
911
+ {!collection.hideIdFromForm &&
912
+ <CustomIdField customId={inputCollection.customId}
913
+ entityId={entityId}
914
+ status={status}
915
+ onChange={setEntityId}
916
+ error={entityIdError}
917
+ loading={customIdLoading}
918
+ entity={entity}/>}
919
+
920
+ {entityId && formContext && <>
921
+ <div className="mt-12 flex flex-col gap-8"
922
+ ref={formRef}>
923
+
924
+ {formFields}
925
+
926
+ <ErrorFocus containerRef={formRef}/>
927
+
928
+ </div>
929
+
930
+ <div className="h-14"/>
931
+
932
+ </>}
933
+
934
+ </div>
935
+ </div>
936
+ </>;
937
+
415
938
  if (plugins) {
416
939
  plugins.forEach((plugin: FireCMSPlugin) => {
417
940
  if (plugin.form?.provider) {
@@ -421,8 +944,6 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
421
944
  path={path}
422
945
  collection={collection}
423
946
  onDiscard={onDiscard}
424
- onValuesChanged={onValuesChanged}
425
- onModified={onModified}
426
947
  entity={usedEntity}
427
948
  context={context}
428
949
  formContext={formContext}
@@ -436,7 +957,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
436
957
  return <ErrorBoundary>{form}</ErrorBoundary>;
437
958
  }
438
959
 
439
- const form = (readOnly === undefined)
960
+ const entityView = (readOnly === undefined)
440
961
  ? <></>
441
962
  : (!readOnly
442
963
  ? buildForm()
@@ -451,6 +972,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
451
972
  entity={usedEntity as Entity<M>}
452
973
  path={path}
453
974
  collection={collection}/>
975
+
454
976
  </>
455
977
  ));
456
978
 
@@ -475,87 +997,132 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
475
997
  </Tab>
476
998
  );
477
999
 
1000
+ useEffect(() => {
1001
+ if (entityId && onIdChange)
1002
+ onIdChange(entityId);
1003
+ }, [entityId, onIdChange]);
1004
+
478
1005
  return (
479
- <div
480
- className="flex flex-col h-full w-full transition-width duration-250 ease-in-out">
481
- {
482
- <>
1006
+ <Formex value={formex}>
1007
+
1008
+ <div className="flex flex-col h-full w-full transition-width duration-250 ease-in-out">
1009
+
1010
+ <div
1011
+ className={cls(defaultBorderMixin, "no-scrollbar border-b pl-2 pr-2 pt-1 flex items-end overflow-scroll bg-gray-50 dark:bg-gray-950")}>
483
1012
 
484
1013
  <div
485
- className={cn(defaultBorderMixin, "no-scrollbar border-b pl-2 pr-2 pt-1 flex items-end overflow-scroll bg-gray-50 dark:bg-gray-950")}>
486
-
487
- <div
488
- className="pb-1 self-center">
489
- <IconButton
490
- onClick={() => {
491
- onClose?.();
492
- return sideDialogContext.close(false);
493
- }}
494
- size="large">
495
- <CloseIcon/>
496
- </IconButton>
497
- </div>
1014
+ className="pb-1 self-center">
1015
+ <IconButton
1016
+ onClick={() => {
1017
+ onClose?.();
1018
+ return sideDialogContext.close(false);
1019
+ }}>
1020
+ <CloseIcon size={"small"}/>
1021
+ </IconButton>
1022
+ </div>
498
1023
 
499
- <div className={"flex-grow"}/>
1024
+ <div className={"flex-grow"}/>
500
1025
 
501
- {globalLoading && <div
502
- className="self-center">
503
- <CircularProgress size={"small"}/>
504
- </div>}
1026
+ {globalLoading && <div
1027
+ className="self-center">
1028
+ <CircularProgress size={"small"}/>
1029
+ </div>}
505
1030
 
506
- <Tabs
507
- value={selectedTabRef.current}
508
- onValueChange={(value) => {
509
- onSideTabClick(value);
510
- }}
511
- className="pl-4 pr-4 pt-0">
1031
+ <Tabs
1032
+ value={selectedTab}
1033
+ onValueChange={(value) => {
1034
+ onSideTabClick(value);
1035
+ }}
1036
+ className="pl-4 pr-4 pt-0">
512
1037
 
513
- <Tab
514
- disabled={!hasAdditionalViews}
515
- value={MAIN_TAB_VALUE}
516
- className={`${
517
- !hasAdditionalViews ? "hidden" : ""
518
- } text-sm min-w-[140px]`}
519
- >{collection.singularName ?? collection.name}</Tab>
1038
+ <Tab
1039
+ disabled={!hasAdditionalViews}
1040
+ value={MAIN_TAB_VALUE}
1041
+ className={`${
1042
+ !hasAdditionalViews ? "hidden" : ""
1043
+ } text-sm min-w-[140px]`}
1044
+ >{collection.singularName ?? collection.name}</Tab>
520
1045
 
521
- {customViewTabs}
1046
+ {customViewTabs}
522
1047
 
523
- {subcollectionTabs}
524
- </Tabs>
1048
+ {subcollectionTabs}
1049
+ </Tabs>
525
1050
 
526
- </div>
1051
+ </div>
1052
+
1053
+ <form
1054
+ onSubmit={formex.handleSubmit}
1055
+ onReset={() => {
1056
+ formex.resetForm();
1057
+ return onDiscard && onDiscard();
1058
+ }}
1059
+ noValidate
1060
+ className={"flex-grow h-full flex overflow-auto flex-col w-full"}>
527
1061
 
528
1062
  <div
529
- className={"flex-grow h-full flex overflow-auto flex-row w-full "}
530
- style={{
531
- // width: `calc(${ADDITIONAL_TAB_WIDTH} + ${resolvedFormWidth})`,
532
- // maxWidth: "100%",
533
- // [`@media (max-width: ${resolvedFormWidth})`]: {
534
- // width: resolvedFormWidth
535
- // }
536
- }}>
537
-
538
- <div
539
- role="tabpanel"
540
- hidden={!mainViewVisible}
541
- id={`form_${path}`}
542
- className={" w-full"}>
543
-
544
- {globalLoading
545
- ? <CircularProgressCenter/>
546
- : form}
1063
+ role="tabpanel"
1064
+ hidden={!mainViewVisible}
1065
+ id={`form_${path}`}
1066
+ className={"relative w-full"}>
547
1067
 
548
- </div>
1068
+ {globalLoading
1069
+ ? <CircularProgressCenter/>
1070
+ : entityView}
549
1071
 
550
- {customViewsView}
1072
+ </div>
551
1073
 
552
- {subCollectionsViews}
1074
+ {customViewsView}
553
1075
 
554
- </div>
1076
+ {subCollectionsViews}
555
1077
 
556
- </>
557
- }
1078
+ {shouldShowEntityActions && dialogActions}
558
1079
 
559
- </div>
1080
+ </form>
1081
+
1082
+ </div>
1083
+ </Formex>
560
1084
  );
561
1085
  }
1086
+
1087
+ function getDataSourceEntityValues<M extends object>(initialResolvedCollection: ResolvedEntityCollection,
1088
+ status: "new" | "existing" | "copy",
1089
+ entity: Entity<M> | undefined): Partial<EntityValues<M>> {
1090
+
1091
+ const properties = initialResolvedCollection.properties;
1092
+ if ((status === "existing" || status === "copy") && entity) {
1093
+ return entity.values ?? getDefaultValuesFor(properties);
1094
+ } else if (status === "new") {
1095
+ return getDefaultValuesFor(properties);
1096
+ } else {
1097
+ console.error({
1098
+ status,
1099
+ entity
1100
+ });
1101
+ throw new Error("Form has not been initialised with the correct parameters");
1102
+ }
1103
+ }
1104
+
1105
+ export type EntityFormSaveParams<M extends Record<string, any>> = {
1106
+ collection: ResolvedEntityCollection<M>,
1107
+ path: string,
1108
+ entityId: string | undefined,
1109
+ values: EntityValues<M>,
1110
+ previousValues?: EntityValues<M>,
1111
+ closeAfterSave: boolean,
1112
+ autoSave: boolean
1113
+ };
1114
+
1115
+ export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
1116
+ let errors: Record<string, any> = {};
1117
+ if (yupError.inner) {
1118
+ if (yupError.inner.length === 0) {
1119
+ return setIn(errors, yupError.path!, yupError.message);
1120
+ }
1121
+ for (const err of yupError.inner) {
1122
+ if (!getIn(errors, err.path!)) {
1123
+ errors = setIn(errors, err.path!, err.message);
1124
+ }
1125
+ }
1126
+ }
1127
+ return errors;
1128
+ }