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

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