@firecms/core 3.0.0-canary.16 → 3.0.0-canary.161

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