@firecms/core 3.0.0-beta.7 → 3.0.0-beta.9

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 (216) hide show
  1. package/dist/app/AppBar.d.ts +12 -0
  2. package/dist/app/Drawer.d.ts +17 -0
  3. package/dist/app/Scaffold.d.ts +30 -0
  4. package/dist/app/index.d.ts +4 -0
  5. package/dist/app/useApp.d.ts +16 -0
  6. package/dist/components/ArrayContainer.d.ts +2 -1
  7. package/dist/components/CircularProgressCenter.d.ts +1 -1
  8. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +0 -1
  9. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +1 -1
  10. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +1 -1
  11. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +1 -1
  12. package/dist/components/EntityPreview.d.ts +1 -1
  13. package/dist/components/ErrorView.d.ts +1 -1
  14. package/dist/components/HomePage/DefaultHomePage.d.ts +1 -1
  15. package/dist/components/HomePage/NavigationCardBinding.d.ts +1 -1
  16. package/dist/components/PropertyIdCopyTooltip.d.ts +8 -0
  17. package/dist/components/VirtualTable/VirtualTableProps.d.ts +14 -6
  18. package/dist/components/common/useColumnsIds.d.ts +2 -1
  19. package/dist/components/index.d.ts +2 -1
  20. package/dist/{components/FireCMSAppBar.d.ts → core/DefaultAppBar.d.ts} +3 -8
  21. package/dist/core/DefaultDrawer.d.ts +19 -0
  22. package/dist/core/DrawerNavigationItem.d.ts +2 -1
  23. package/dist/core/EntityEditView.d.ts +18 -4
  24. package/dist/core/FireCMS.d.ts +1 -1
  25. package/dist/core/NavigationRoutes.d.ts +2 -2
  26. package/dist/core/index.d.ts +2 -2
  27. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  28. package/dist/form/components/FieldHelperText.d.ts +3 -3
  29. package/dist/form/components/FormikArrayContainer.d.ts +2 -1
  30. package/dist/form/components/LabelWithIcon.d.ts +1 -1
  31. package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
  32. package/dist/form/components/StorageItemPreview.d.ts +4 -4
  33. package/dist/form/components/index.d.ts +1 -0
  34. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  35. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  36. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  37. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  38. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  39. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +11 -0
  40. package/dist/form/field_bindings/{MultiSelectBinding.d.ts → MultiSelectFieldBinding.d.ts} +1 -1
  41. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  42. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  43. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  44. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -5
  45. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  46. package/dist/form/index.d.ts +3 -5
  47. package/dist/form/useClearRestoreValue.d.ts +2 -2
  48. package/dist/hooks/useProjectLog.d.ts +2 -2
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.es.js +14434 -11300
  51. package/dist/index.es.js.map +1 -1
  52. package/dist/index.umd.js +19987 -587
  53. package/dist/index.umd.js.map +1 -1
  54. package/dist/preview/PropertyPreviewProps.d.ts +6 -1
  55. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  56. package/dist/preview/components/ReferencePreview.d.ts +1 -1
  57. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  58. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  59. package/dist/types/auth.d.ts +2 -2
  60. package/dist/types/collections.d.ts +26 -3
  61. package/dist/types/datasource.d.ts +53 -30
  62. package/dist/types/entities.d.ts +1 -0
  63. package/dist/types/entity_overrides.d.ts +2 -2
  64. package/dist/types/fields.d.ts +47 -41
  65. package/dist/types/navigation.d.ts +1 -0
  66. package/dist/types/plugins.d.ts +11 -3
  67. package/dist/types/properties.d.ts +25 -20
  68. package/dist/types/side_dialogs_controller.d.ts +10 -0
  69. package/dist/types/storage.d.ts +75 -0
  70. package/dist/util/entities.d.ts +1 -1
  71. package/dist/util/icon_list.d.ts +5 -1
  72. package/dist/util/icons.d.ts +3 -2
  73. package/dist/util/index.d.ts +1 -0
  74. package/dist/util/plurals.d.ts +0 -2
  75. package/dist/util/property_utils.d.ts +1 -1
  76. package/dist/util/resolutions.d.ts +15 -1
  77. package/dist/util/storage.d.ts +23 -2
  78. package/dist/util/useStorageUploadController.d.ts +3 -3
  79. package/package.json +18 -29
  80. package/src/app/AppBar.tsx +18 -0
  81. package/src/app/Drawer.tsx +25 -0
  82. package/src/{core → app}/Scaffold.tsx +71 -122
  83. package/src/app/index.ts +4 -0
  84. package/src/app/useApp.tsx +32 -0
  85. package/src/components/ArrayContainer.tsx +15 -10
  86. package/src/components/CircularProgressCenter.tsx +1 -1
  87. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +11 -18
  88. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +2 -2
  89. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +4 -2
  90. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +21 -14
  91. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +12 -4
  92. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +17 -19
  93. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +19 -7
  94. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -6
  95. package/src/components/EntityCollectionView/EntityCollectionView.tsx +33 -29
  96. package/src/components/EntityCollectionView/useSelectionController.tsx +19 -7
  97. package/src/components/EntityPreview.tsx +30 -31
  98. package/src/components/ErrorView.tsx +1 -1
  99. package/src/components/HomePage/DefaultHomePage.tsx +24 -18
  100. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  101. package/src/components/HomePage/SmallNavigationCard.tsx +1 -2
  102. package/src/components/PropertyIdCopyTooltip.tsx +48 -0
  103. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +7 -6
  104. package/src/components/SearchIconsView.tsx +5 -2
  105. package/src/components/SelectableTable/SelectableTable.tsx +2 -4
  106. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +1 -1
  107. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +54 -7
  108. package/src/components/VirtualTable/VirtualTable.tsx +36 -10
  109. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -9
  110. package/src/components/VirtualTable/VirtualTableProps.tsx +17 -8
  111. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -3
  112. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +2 -1
  113. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
  114. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +14 -28
  115. package/src/components/common/useColumnsIds.tsx +13 -0
  116. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +12 -12
  117. package/src/components/common/useTableSearchHelper.ts +53 -12
  118. package/src/components/index.tsx +2 -1
  119. package/src/contexts/DialogsProvider.tsx +2 -2
  120. package/src/{components/FireCMSAppBar.tsx → core/DefaultAppBar.tsx} +28 -30
  121. package/src/core/DefaultDrawer.tsx +183 -0
  122. package/src/core/DrawerNavigationItem.tsx +31 -27
  123. package/src/core/EntityEditView.tsx +712 -145
  124. package/src/core/EntitySidePanel.tsx +0 -1
  125. package/src/core/FireCMS.tsx +2 -2
  126. package/src/core/NavigationRoutes.tsx +4 -4
  127. package/src/core/SideDialogs.tsx +1 -0
  128. package/src/core/field_configs.tsx +5 -5
  129. package/src/core/index.tsx +2 -2
  130. package/src/form/PropertyFieldBinding.tsx +53 -19
  131. package/src/form/components/CustomIdField.tsx +4 -2
  132. package/src/form/components/FieldHelperText.tsx +4 -4
  133. package/src/form/components/FormikArrayContainer.tsx +4 -1
  134. package/src/form/components/LabelWithIcon.tsx +27 -19
  135. package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
  136. package/src/form/components/StorageItemPreview.tsx +20 -10
  137. package/src/form/components/StorageUploadProgress.tsx +0 -1
  138. package/src/form/components/index.tsx +1 -0
  139. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +26 -15
  140. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +28 -25
  141. package/src/form/field_bindings/BlockFieldBinding.tsx +13 -11
  142. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -14
  143. package/src/form/field_bindings/KeyValueFieldBinding.tsx +48 -42
  144. package/src/form/field_bindings/MapFieldBinding.tsx +17 -17
  145. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +150 -0
  146. package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +25 -20
  147. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +9 -14
  148. package/src/form/field_bindings/ReferenceFieldBinding.tsx +27 -18
  149. package/src/form/field_bindings/RepeatFieldBinding.tsx +32 -19
  150. package/src/form/field_bindings/SelectFieldBinding.tsx +17 -10
  151. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +33 -43
  152. package/src/form/field_bindings/SwitchFieldBinding.tsx +9 -5
  153. package/src/form/field_bindings/TextFieldBinding.tsx +26 -23
  154. package/src/form/index.tsx +4 -9
  155. package/src/form/useClearRestoreValue.tsx +2 -2
  156. package/src/form/validation.ts +1 -17
  157. package/src/hooks/data/delete.ts +2 -1
  158. package/src/hooks/data/save.ts +5 -2
  159. package/src/hooks/data/useDataSource.tsx +11 -3
  160. package/src/hooks/useBuildNavigationController.tsx +10 -3
  161. package/src/hooks/useProjectLog.tsx +18 -7
  162. package/src/index.ts +1 -0
  163. package/src/internal/useBuildDataSource.ts +69 -74
  164. package/src/internal/useBuildSideDialogsController.tsx +1 -0
  165. package/src/internal/useBuildSideEntityController.tsx +17 -4
  166. package/src/internal/useUnsavedChangesDialog.tsx +3 -1
  167. package/src/preview/PropertyPreview.tsx +7 -4
  168. package/src/preview/PropertyPreviewProps.tsx +7 -1
  169. package/src/preview/components/BooleanPreview.tsx +1 -1
  170. package/src/preview/components/EnumValuesChip.tsx +1 -1
  171. package/src/preview/components/ImagePreview.tsx +24 -34
  172. package/src/preview/components/ReferencePreview.tsx +2 -12
  173. package/src/preview/components/StorageThumbnail.tsx +5 -1
  174. package/src/preview/components/UrlComponentPreview.tsx +44 -11
  175. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -1
  176. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +1 -1
  177. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +1 -1
  178. package/src/preview/property_previews/ArrayOneOfPreview.tsx +2 -2
  179. package/src/preview/property_previews/ArrayPropertyPreview.tsx +1 -1
  180. package/src/preview/property_previews/MapPropertyPreview.tsx +1 -1
  181. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +2 -2
  182. package/src/preview/property_previews/StringPropertyPreview.tsx +2 -2
  183. package/src/preview/util.ts +2 -2
  184. package/src/types/auth.tsx +2 -2
  185. package/src/types/collections.ts +29 -4
  186. package/src/types/customization_controller.tsx +0 -1
  187. package/src/types/datasource.ts +62 -43
  188. package/src/types/entities.ts +2 -0
  189. package/src/types/entity_overrides.tsx +2 -2
  190. package/src/types/fields.tsx +51 -45
  191. package/src/types/navigation.ts +1 -0
  192. package/src/types/plugins.tsx +12 -3
  193. package/src/types/properties.ts +25 -19
  194. package/src/types/side_dialogs_controller.tsx +13 -0
  195. package/src/types/storage.ts +82 -0
  196. package/src/util/entities.ts +5 -4
  197. package/src/util/icon_list.ts +14 -9
  198. package/src/util/icons.tsx +8 -2
  199. package/src/util/index.ts +1 -0
  200. package/src/util/objects.ts +17 -7
  201. package/src/util/permissions.ts +1 -0
  202. package/src/util/plurals.ts +0 -2
  203. package/src/util/property_utils.tsx +1 -1
  204. package/src/util/references.ts +3 -0
  205. package/src/util/resolutions.ts +43 -11
  206. package/src/util/storage.ts +79 -21
  207. package/src/util/useStorageUploadController.tsx +33 -7
  208. package/dist/components/PropertyIdCopyTooltipContent.d.ts +0 -3
  209. package/dist/core/Drawer.d.ts +0 -16
  210. package/dist/core/Scaffold.d.ts +0 -51
  211. package/dist/form/EntityForm.d.ts +0 -77
  212. package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
  213. package/src/components/PropertyIdCopyTooltipContent.tsx +0 -27
  214. package/src/core/Drawer.tsx +0 -139
  215. package/src/form/EntityForm.tsx +0 -736
  216. package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -695
@@ -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,10 +51,29 @@ import {
28
51
  useSideEntityController,
29
52
  useSnackbarController
30
53
  } from "../hooks";
31
- import { EntityForm } from "../form";
32
- import { CircularProgress, CloseIcon, cls, defaultBorderMixin, IconButton, Tab, Tabs, Typography } from "@firecms/ui";
33
- import { EntityFormSaveParams } from "../form/EntityForm";
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";
34
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";
35
77
 
36
78
  const MAIN_TAB_VALUE = "main_##Q$SC^#S6";
37
79
 
@@ -42,7 +84,6 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
42
84
  copy?: boolean;
43
85
  selectedSubPath?: string;
44
86
  parentCollectionIds: string[];
45
- formWidth?: number | string;
46
87
  onValuesAreModified: (modified: boolean) => void;
47
88
  onUpdate?: (params: { entity: Entity<any> }) => void;
48
89
  onClose?: () => void;
@@ -52,20 +93,54 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
52
93
  * This is the default view that is used as the content of a side panel when
53
94
  * an entity is opened.
54
95
  * You probably don't want to use this view directly since it is bound to the
55
- * side panel. Instead, you might want to use {@link EntityForm} or {@link EntityCollectionView}
96
+ * side panel.
56
97
  */
57
98
  export function EntityEditView<M extends Record<string, any>, UserType extends User>({
58
- path,
59
99
  entityId,
60
- selectedSubPath,
61
- copy,
62
- collection,
63
- parentCollectionIds,
64
- onValuesAreModified,
65
- formWidth,
66
- onUpdate,
67
- onClose,
100
+ ...props
68
101
  }: EntityEditViewProps<M>) {
102
+ const {
103
+ entity,
104
+ dataLoading,
105
+ // eslint-disable-next-line no-unused-vars
106
+ dataLoadingError
107
+ } = useEntityFetch<M, UserType>({
108
+ path: props.path,
109
+ entityId: entityId,
110
+ collection: props.collection,
111
+ useCache: false
112
+ });
113
+
114
+ if (dataLoading) {
115
+ return <CircularProgressCenter/>
116
+ }
117
+
118
+ if (entityId && !entity) {
119
+ console.error(`Entity with id ${entityId} not found in collection ${props.collection.path}`);
120
+ }
121
+
122
+ return <EntityEditViewInner<M> {...props}
123
+ entityId={entityId}
124
+ entity={entity}
125
+ dataLoading={dataLoading}/>;
126
+ }
127
+
128
+ export function EntityEditViewInner<M extends Record<string, any>>({
129
+ path,
130
+ entityId: entityIdProp,
131
+ selectedSubPath: selectedSubPathProp,
132
+ copy,
133
+ collection,
134
+ parentCollectionIds,
135
+ onValuesAreModified,
136
+ onUpdate,
137
+ onClose,
138
+ entity,
139
+ dataLoading
140
+ }: EntityEditViewProps<M> & {
141
+ entity?: Entity<M>,
142
+ dataLoading: boolean
143
+ }) {
69
144
 
70
145
  if (collection.customId && collection.formAutoSave) {
71
146
  console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
@@ -92,30 +167,63 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
92
167
  // const largeLayoutTabSelected = useRef(!largeLayout);
93
168
  // const resolvedFormWidth: string = typeof formWidth === "number" ? `${formWidth}px` : formWidth ?? FORM_CONTAINER_WIDTH;
94
169
 
170
+ const inputCollection = collection;
171
+
172
+ const authController = useAuthController();
95
173
  const dataSource = useDataSource(collection);
96
174
  const sideDialogContext = useSideDialogContext();
97
175
  const sideEntityController = useSideEntityController();
98
176
  const snackbarController = useSnackbarController();
99
177
  const customizationController = useCustomizationController();
100
178
  const context = useFireCMSContext();
101
- const authController = useAuthController<UserType>();
102
179
 
103
- const [formContext, setFormContext] = useState<FormContext<M> | undefined>(undefined);
180
+ const closeAfterSaveRef = useRef(false);
181
+
182
+ const analyticsController = useAnalyticsController();
183
+
184
+ const initialResolvedCollection = useMemo(() => resolveCollection({
185
+ collection: inputCollection,
186
+ path,
187
+ values: entity?.values,
188
+ fields: customizationController.propertyConfigs
189
+ }), [entity?.values, path, customizationController.propertyConfigs]);
190
+
191
+ const initialStatus = copy ? "copy" : (entityIdProp ? "existing" : "new");
192
+ const [status, setStatus] = useState<EntityStatus>(initialStatus);
193
+
194
+ const mustSetCustomId: boolean = (status === "new" || status === "copy") &&
195
+ (Boolean(initialResolvedCollection.customId) && initialResolvedCollection.customId !== "optional");
196
+
197
+ const initialEntityId: string | undefined = useMemo((): string | undefined => {
198
+ if (status === "new" || status === "copy") {
199
+ if (mustSetCustomId) {
200
+ return undefined;
201
+ } else {
202
+ return dataSource.generateEntityId(path, collection);
203
+ }
204
+ } else {
205
+ return entityIdProp;
206
+ }
207
+ }, [entityIdProp, status]);
104
208
 
105
- const [status, setStatus] = useState<EntityStatus>(copy ? "copy" : (entityId ? "existing" : "new"));
209
+ const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
106
210
 
107
- const modifiedValuesRef = useRef<EntityValues<M> | undefined>(undefined);
108
- const modifiedValues = modifiedValuesRef.current;
211
+ // const doOnValuesChanges = (values?: EntityValues<M>) => {
212
+ // const initialValues = formex.initialValues;
213
+ // setInternalValues(values);
214
+ // if (onValuesChanged)
215
+ // onValuesChanged(values);
216
+ // if (autoSave && values && !equal(values, initialValues)) {
217
+ // save(values);
218
+ // }
219
+ // };
109
220
 
110
- const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
111
- const subcollectionsCount = subcollections?.length ?? 0;
112
- const customViews = collection.entityViews;
113
- const customViewsCount = customViews?.length ?? 0;
114
- const autoSave = collection.formAutoSave && !collection.customId;
221
+ const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
222
+ const [savingError, setSavingError] = React.useState<Error | undefined>();
115
223
 
116
- const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
224
+ const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
117
225
 
118
- const defaultSelectedView = selectedSubPath ?? resolveDefaultSelectedView(
226
+ const defaultSelectedView = selectedSubPathProp ?? resolveDefaultSelectedView(
119
227
  collection ? collection.defaultSelectedView : undefined,
120
228
  {
121
229
  status,
@@ -123,25 +231,23 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
123
231
  }
124
232
  );
125
233
 
126
- const selectedTabRef = useRef<string>(defaultSelectedView ?? MAIN_TAB_VALUE);
234
+ const [selectedTab, setSelectedTab] = useState<string>(defaultSelectedView ?? MAIN_TAB_VALUE);
127
235
 
128
- const mainViewVisible = selectedTabRef.current === MAIN_TAB_VALUE;
236
+ const mainViewVisible = selectedTab === MAIN_TAB_VALUE;
129
237
 
130
- const {
131
- entity,
132
- dataLoading,
133
- // eslint-disable-next-line no-unused-vars
134
- dataLoadingError
135
- } = useEntityFetch<M, UserType>({
136
- path,
137
- entityId,
138
- collection,
139
- useCache: false
140
- });
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;
141
245
 
142
246
  const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
143
247
  const [readOnly, setReadOnly] = useState<boolean | undefined>(undefined);
144
248
 
249
+ const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, usedEntity));
250
+
145
251
  useEffect(() => {
146
252
  if (entity)
147
253
  setUsedEntity(entity);
@@ -193,7 +299,6 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
193
299
  onUpdate({ entity: updatedEntity });
194
300
 
195
301
  if (closeAfterSave) {
196
- console.log("Closing side dialog")
197
302
  sideDialogContext.setBlocked(false);
198
303
  sideDialogContext.close(true);
199
304
  onClose?.();
@@ -201,7 +306,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
201
306
  sideEntityController.replace({
202
307
  path,
203
308
  entityId: updatedEntity.id,
204
- selectedSubPath: selectedTabRef.current,
309
+ selectedSubPath: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab,
205
310
  updateUrl: true,
206
311
  collection,
207
312
  });
@@ -279,17 +384,121 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
279
384
  }
280
385
  };
281
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
+ fields: 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
+
282
488
  const resolvedEntityViews = customViews ? customViews
283
489
  .map(e => resolveEntityView(e, customizationController.entityViews))
284
490
  .filter(Boolean) as EntityCustomView[]
285
491
  : [];
286
492
 
493
+ const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTab);
494
+ const shouldShowEntityActions = !autoSave && (selectedTab === MAIN_TAB_VALUE || selectedEntityView?.includeActions);
495
+
287
496
  const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
288
497
  .map(
289
498
  (customView, colIndex) => {
290
499
  if (!customView)
291
500
  return null;
292
- if (selectedTabRef.current !== customView.key)
501
+ if (selectedTab !== customView.key)
293
502
  return null;
294
503
  const Builder = customView.Builder;
295
504
  if (!Builder) {
@@ -305,7 +514,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
305
514
  {formContext && <Builder
306
515
  collection={collection}
307
516
  entity={usedEntity}
308
- modifiedValues={modifiedValues ?? usedEntity?.values}
517
+ modifiedValues={formex.values ?? usedEntity?.values}
309
518
  formContext={formContext}
310
519
  />}
311
520
  </ErrorBoundary>
@@ -322,7 +531,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
322
531
  (subcollection, colIndex) => {
323
532
  const subcollectionId = subcollection.id ?? subcollection.path;
324
533
  const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
325
- if (selectedTabRef.current !== subcollectionId)
534
+ if (selectedTab !== subcollectionId)
326
535
  return null;
327
536
  return (
328
537
  <div
@@ -358,21 +567,18 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
358
567
  }, []);
359
568
 
360
569
  const onSideTabClick = (value: string) => {
361
- selectedTabRef.current = value;
362
- sideEntityController.replace({
363
- path,
364
- entityId,
365
- selectedSubPath: value === MAIN_TAB_VALUE ? undefined : value,
366
- updateUrl: true,
367
- collection,
368
- });
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
+ }
369
580
  };
370
581
 
371
- const onValuesChanged = useCallback((values?: EntityValues<M>) => {
372
- modifiedValuesRef.current = values;
373
- }, []);
374
-
375
- // eslint-disable-next-line n/handle-callback-err
376
582
  const onIdUpdateError = useCallback((error: any) => {
377
583
  snackbarController.open({
378
584
  type: "error",
@@ -389,28 +595,345 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
389
595
  : undefined);
390
596
  }, []);
391
597
 
392
- const onModified = (dirty: boolean) => {
393
- if (!autoSave)
394
- 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));
395
642
  }
396
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 && 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
+ : <>{additionalField.value?.({
782
+ entity,
783
+ context
784
+ })}</>;
785
+ return (
786
+ <div>
787
+ <LabelWithIconAndTooltip
788
+ propertyKey={key}
789
+ icon={<NotesIcon size={"small"}/>}
790
+ title={additionalField.name}
791
+ className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
792
+ <div
793
+ className={cls(paperMixin, "min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
794
+
795
+ <ErrorBoundary>
796
+ {child}
797
+ </ErrorBoundary>
798
+
799
+ </div>
800
+ </div>
801
+ );
802
+ }
803
+
804
+ console.warn(`Property ${key} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
805
+ return null;
806
+ })
807
+ .filter(Boolean)}
808
+
809
+ </>
810
+ );
811
+
812
+ const disabled = formex.isSubmitting || (!modified && status === "existing");
813
+ const formRef = React.useRef<HTMLDivElement>(null);
814
+
815
+ const entityActions = getActionsForEntity({
816
+ entity,
817
+ customEntityActions: inputCollection.entityActions
818
+ });
819
+ const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
820
+
821
+ const dialogActions = <DialogActions position={"absolute"}>
822
+
823
+ {savingError &&
824
+ <div className="text-right">
825
+ <Typography color={"error"}>
826
+ {savingError.message}
827
+ </Typography>
828
+ </div>}
829
+
830
+ {entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
831
+ {formActions.map(action => (
832
+ <IconButton
833
+ key={action.name}
834
+ color="primary"
835
+ onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
836
+ event.stopPropagation();
837
+ if (entity)
838
+ action.onClick({
839
+ entity,
840
+ fullPath: resolvedCollection.path,
841
+ collection: resolvedCollection,
842
+ context,
843
+ sideEntityController
844
+ });
845
+ }}>
846
+ {action.icon}
847
+ </IconButton>
848
+ ))}
849
+ </div>}
850
+ {formex.isSubmitting && <CircularProgress size={"small"}/>}
851
+ <Button
852
+ variant="text"
853
+ disabled={disabled || formex.isSubmitting}
854
+ type="reset">
855
+ {status === "existing" ? "Discard" : "Clear"}
856
+ </Button>
857
+
858
+ <Button
859
+ variant="text"
860
+ color="primary"
861
+ type="submit"
862
+ disabled={disabled || formex.isSubmitting}
863
+ onClick={() => {
864
+ closeAfterSaveRef.current = false;
865
+ }}>
866
+ {status === "existing" && "Save"}
867
+ {status === "copy" && "Create copy"}
868
+ {status === "new" && "Create"}
869
+ </Button>
870
+
871
+ <Button
872
+ variant="filled"
873
+ color="primary"
874
+ type="submit"
875
+ disabled={disabled || formex.isSubmitting}
876
+ onClick={() => {
877
+ closeAfterSaveRef.current = true;
878
+ }}>
879
+ {status === "existing" && "Save and close"}
880
+ {status === "copy" && "Create copy and close"}
881
+ {status === "new" && "Create and close"}
882
+ </Button>
883
+
884
+ </DialogActions>;
885
+
397
886
  function buildForm() {
398
- const plugins = customizationController.plugins;
399
- let form = <EntityForm
400
- status={status}
401
- path={path}
402
- collection={collection}
403
- onEntitySaveRequested={onSaveEntityRequest}
404
- onDiscard={onDiscard}
405
- onValuesChanged={onValuesChanged}
406
- onModified={onModified}
407
- entity={usedEntity}
408
- onIdChange={onIdChange}
409
- onFormContextChange={setFormContext}
410
- hideId={collection.hideIdFromForm}
411
- autoSave={autoSave}
412
- onIdUpdateError={onIdUpdateError}
413
- />;
887
+
888
+ let form = <>
889
+
890
+ {pluginActions.length > 0 && <div
891
+ 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")}>
892
+ {pluginActions}
893
+ </div>}
894
+
895
+ <div className="h-full overflow-auto">
896
+
897
+ <div className="pt-12 pb-16 pl-8 pr-8 md:pl-10 md:pr-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
+ </div>
935
+ </>;
936
+
414
937
  if (plugins) {
415
938
  plugins.forEach((plugin: FireCMSPlugin) => {
416
939
  if (plugin.form?.provider) {
@@ -420,8 +943,6 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
420
943
  path={path}
421
944
  collection={collection}
422
945
  onDiscard={onDiscard}
423
- onValuesChanged={onValuesChanged}
424
- onModified={onModified}
425
946
  entity={usedEntity}
426
947
  context={context}
427
948
  formContext={formContext}
@@ -435,7 +956,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
435
956
  return <ErrorBoundary>{form}</ErrorBoundary>;
436
957
  }
437
958
 
438
- const form = (readOnly === undefined)
959
+ const entityView = (readOnly === undefined)
439
960
  ? <></>
440
961
  : (!readOnly
441
962
  ? buildForm()
@@ -450,6 +971,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
450
971
  entity={usedEntity as Entity<M>}
451
972
  path={path}
452
973
  collection={collection}/>
974
+
453
975
  </>
454
976
  ));
455
977
 
@@ -474,87 +996,132 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
474
996
  </Tab>
475
997
  );
476
998
 
999
+ useEffect(() => {
1000
+ if (entityId && onIdChange)
1001
+ onIdChange(entityId);
1002
+ }, [entityId, onIdChange]);
1003
+
477
1004
  return (
478
- <div
479
- className="flex flex-col h-full w-full transition-width duration-250 ease-in-out">
480
- {
481
- <>
1005
+ <Formex value={formex}>
1006
+
1007
+ <div className="flex flex-col h-full w-full transition-width duration-250 ease-in-out">
1008
+
1009
+ <div
1010
+ 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")}>
482
1011
 
483
1012
  <div
484
- 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")}>
485
-
486
- <div
487
- className="pb-1 self-center">
488
- <IconButton
489
- onClick={() => {
490
- onClose?.();
491
- return sideDialogContext.close(false);
492
- }}
493
- size="large">
494
- <CloseIcon/>
495
- </IconButton>
496
- </div>
1013
+ className="pb-1 self-center">
1014
+ <IconButton
1015
+ onClick={() => {
1016
+ onClose?.();
1017
+ return sideDialogContext.close(false);
1018
+ }}>
1019
+ <CloseIcon size={"small"}/>
1020
+ </IconButton>
1021
+ </div>
497
1022
 
498
- <div className={"flex-grow"}/>
1023
+ <div className={"flex-grow"}/>
499
1024
 
500
- {globalLoading && <div
501
- className="self-center">
502
- <CircularProgress size={"small"}/>
503
- </div>}
1025
+ {globalLoading && <div
1026
+ className="self-center">
1027
+ <CircularProgress size={"small"}/>
1028
+ </div>}
504
1029
 
505
- <Tabs
506
- value={selectedTabRef.current}
507
- onValueChange={(value) => {
508
- onSideTabClick(value);
509
- }}
510
- className="pl-4 pr-4 pt-0">
1030
+ <Tabs
1031
+ value={selectedTab}
1032
+ onValueChange={(value) => {
1033
+ onSideTabClick(value);
1034
+ }}
1035
+ className="pl-4 pr-4 pt-0">
511
1036
 
512
- <Tab
513
- disabled={!hasAdditionalViews}
514
- value={MAIN_TAB_VALUE}
515
- className={`${
516
- !hasAdditionalViews ? "hidden" : ""
517
- } text-sm min-w-[140px]`}
518
- >{collection.singularName ?? collection.name}</Tab>
1037
+ <Tab
1038
+ disabled={!hasAdditionalViews}
1039
+ value={MAIN_TAB_VALUE}
1040
+ className={`${
1041
+ !hasAdditionalViews ? "hidden" : ""
1042
+ } text-sm min-w-[140px]`}
1043
+ >{collection.singularName ?? collection.name}</Tab>
519
1044
 
520
- {customViewTabs}
1045
+ {customViewTabs}
521
1046
 
522
- {subcollectionTabs}
523
- </Tabs>
1047
+ {subcollectionTabs}
1048
+ </Tabs>
524
1049
 
525
- </div>
1050
+ </div>
1051
+
1052
+ <form
1053
+ onSubmit={formex.handleSubmit}
1054
+ onReset={() => {
1055
+ formex.resetForm();
1056
+ return onDiscard && onDiscard();
1057
+ }}
1058
+ noValidate
1059
+ className={"flex-grow h-full flex overflow-auto flex-col w-full"}>
526
1060
 
527
1061
  <div
528
- className={"flex-grow h-full flex overflow-auto flex-row w-full "}
529
- style={{
530
- // width: `calc(${ADDITIONAL_TAB_WIDTH} + ${resolvedFormWidth})`,
531
- // maxWidth: "100%",
532
- // [`@media (max-width: ${resolvedFormWidth})`]: {
533
- // width: resolvedFormWidth
534
- // }
535
- }}>
536
-
537
- <div
538
- role="tabpanel"
539
- hidden={!mainViewVisible}
540
- id={`form_${path}`}
541
- className={" w-full"}>
542
-
543
- {globalLoading
544
- ? <CircularProgressCenter/>
545
- : form}
1062
+ role="tabpanel"
1063
+ hidden={!mainViewVisible}
1064
+ id={`form_${path}`}
1065
+ className={"relative w-full"}>
546
1066
 
547
- </div>
1067
+ {globalLoading
1068
+ ? <CircularProgressCenter/>
1069
+ : entityView}
548
1070
 
549
- {customViewsView}
1071
+ </div>
550
1072
 
551
- {subCollectionsViews}
1073
+ {customViewsView}
552
1074
 
553
- </div>
1075
+ {subCollectionsViews}
554
1076
 
555
- </>
556
- }
1077
+ {shouldShowEntityActions && dialogActions}
557
1078
 
558
- </div>
1079
+ </form>
1080
+
1081
+ </div>
1082
+ </Formex>
559
1083
  );
560
1084
  }
1085
+
1086
+ function getDataSourceEntityValues<M extends object>(initialResolvedCollection: ResolvedEntityCollection,
1087
+ status: "new" | "existing" | "copy",
1088
+ entity: Entity<M> | undefined): Partial<EntityValues<M>> {
1089
+
1090
+ const properties = initialResolvedCollection.properties;
1091
+ if ((status === "existing" || status === "copy") && entity) {
1092
+ return entity.values ?? getDefaultValuesFor(properties);
1093
+ } else if (status === "new") {
1094
+ return getDefaultValuesFor(properties);
1095
+ } else {
1096
+ console.error({
1097
+ status,
1098
+ entity
1099
+ });
1100
+ throw new Error("Form has not been initialised with the correct parameters");
1101
+ }
1102
+ }
1103
+
1104
+ export type EntityFormSaveParams<M extends Record<string, any>> = {
1105
+ collection: ResolvedEntityCollection<M>,
1106
+ path: string,
1107
+ entityId: string | undefined,
1108
+ values: EntityValues<M>,
1109
+ previousValues?: EntityValues<M>,
1110
+ closeAfterSave: boolean,
1111
+ autoSave: boolean
1112
+ };
1113
+
1114
+ export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
1115
+ let errors: Record<string, any> = {};
1116
+ if (yupError.inner) {
1117
+ if (yupError.inner.length === 0) {
1118
+ return setIn(errors, yupError.path!, yupError.message);
1119
+ }
1120
+ for (const err of yupError.inner) {
1121
+ if (!getIn(errors, err.path!)) {
1122
+ errors = setIn(errors, err.path!, err.message);
1123
+ }
1124
+ }
1125
+ }
1126
+ return errors;
1127
+ }