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

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 (247) 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/useColumnsIds.d.ts +2 -1
  34. package/dist/components/common/useDataSourceEntityCollectionTableController.d.ts +3 -0
  35. package/dist/components/index.d.ts +2 -1
  36. package/dist/contexts/AuthControllerContext.d.ts +1 -1
  37. package/dist/{components/FireCMSAppBar.d.ts → core/DefaultAppBar.d.ts} +6 -9
  38. package/dist/core/DefaultDrawer.d.ts +19 -0
  39. package/dist/core/DrawerNavigationItem.d.ts +9 -0
  40. package/dist/core/EntityEditView.d.ts +17 -3
  41. package/dist/core/FireCMS.d.ts +1 -1
  42. package/dist/core/NavigationRoutes.d.ts +3 -3
  43. package/dist/core/index.d.ts +3 -4
  44. package/dist/form/PropertiesForm.d.ts +8 -0
  45. package/dist/form/components/ErrorFocus.d.ts +1 -1
  46. package/dist/form/components/FieldHelperText.d.ts +3 -3
  47. package/dist/form/components/StorageItemPreview.d.ts +4 -4
  48. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  49. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +2 -4
  50. package/dist/form/index.d.ts +0 -2
  51. package/dist/hooks/data/delete.d.ts +2 -2
  52. package/dist/hooks/data/save.d.ts +2 -3
  53. package/dist/hooks/data/useDataSource.d.ts +1 -1
  54. package/dist/hooks/data/useEntityFetch.d.ts +3 -3
  55. package/dist/hooks/index.d.ts +2 -0
  56. package/dist/hooks/useBuildNavigationController.d.ts +1 -2
  57. package/dist/hooks/useProjectLog.d.ts +2 -2
  58. package/dist/hooks/useValidateAuthenticator.d.ts +21 -0
  59. package/dist/index.d.ts +1 -0
  60. package/dist/index.es.js +15825 -12165
  61. package/dist/index.es.js.map +1 -1
  62. package/dist/index.umd.js +19684 -7
  63. package/dist/index.umd.js.map +1 -1
  64. package/dist/internal/useBuildDataSource.d.ts +1 -16
  65. package/dist/internal/useRestoreScroll.d.ts +1 -1
  66. package/dist/preview/PropertyPreviewProps.d.ts +6 -4
  67. package/dist/preview/components/ReferencePreview.d.ts +2 -1
  68. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  69. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  70. package/dist/types/auth.d.ts +26 -2
  71. package/dist/types/collections.d.ts +31 -7
  72. package/dist/types/datasource.d.ts +34 -20
  73. package/dist/types/entities.d.ts +5 -1
  74. package/dist/types/entity_actions.d.ts +14 -0
  75. package/dist/types/entity_callbacks.d.ts +2 -2
  76. package/dist/types/fields.d.ts +31 -30
  77. package/dist/types/index.d.ts +0 -1
  78. package/dist/types/navigation.d.ts +5 -5
  79. package/dist/types/plugins.d.ts +16 -6
  80. package/dist/types/properties.d.ts +17 -4
  81. package/dist/types/storage.d.ts +11 -3
  82. package/dist/util/collections.d.ts +1 -1
  83. package/dist/util/entities.d.ts +1 -1
  84. package/dist/util/icon_synonyms.d.ts +1 -97
  85. package/dist/util/icons.d.ts +2 -2
  86. package/dist/util/navigation_utils.d.ts +2 -2
  87. package/dist/util/objects.d.ts +1 -1
  88. package/dist/util/plurals.d.ts +0 -2
  89. package/dist/util/resolutions.d.ts +13 -13
  90. package/dist/util/storage.d.ts +23 -2
  91. package/dist/util/useStorageUploadController.d.ts +1 -1
  92. package/dist/util/useTraceUpdate.d.ts +1 -0
  93. package/package.json +130 -119
  94. package/src/app/AppBar.tsx +18 -0
  95. package/src/app/Drawer.tsx +25 -0
  96. package/src/app/Scaffold.tsx +249 -0
  97. package/src/app/index.ts +4 -0
  98. package/src/app/useApp.tsx +32 -0
  99. package/src/components/CircularProgressCenter.tsx +1 -1
  100. package/src/components/ClearFilterSortButton.tsx +41 -0
  101. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +9 -18
  102. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +21 -20
  103. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +10 -6
  104. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +38 -34
  105. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  106. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +11 -2
  107. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +14 -6
  108. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +29 -34
  109. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +16 -12
  110. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +4 -5
  111. package/src/components/EntityCollectionView/EntityCollectionView.tsx +69 -45
  112. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  113. package/src/components/EntityCollectionView/useSelectionController.tsx +19 -7
  114. package/src/components/EntityPreview.tsx +15 -9
  115. package/src/components/EntityView.tsx +5 -5
  116. package/src/components/ErrorView.tsx +1 -1
  117. package/src/components/HomePage/DefaultHomePage.tsx +3 -3
  118. package/src/components/HomePage/NavigationCard.tsx +3 -3
  119. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  120. package/src/components/HomePage/SmallNavigationCard.tsx +5 -5
  121. package/src/components/PropertyIdCopyTooltipContent.tsx +2 -3
  122. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +1 -0
  123. package/src/components/ReferenceWidget.tsx +22 -12
  124. package/src/components/SearchIconsView.tsx +5 -5
  125. package/src/components/SelectableTable/SelectableTable.tsx +7 -7
  126. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  127. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +22 -7
  128. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +28 -6
  129. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
  130. package/src/components/VirtualTable/VirtualTable.tsx +70 -37
  131. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -1
  132. package/src/components/VirtualTable/VirtualTableHeader.tsx +4 -4
  133. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
  134. package/src/components/VirtualTable/VirtualTableProps.tsx +18 -14
  135. package/src/components/VirtualTable/VirtualTableRow.tsx +4 -5
  136. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
  137. package/src/components/VirtualTable/types.tsx +2 -3
  138. package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +2 -2
  139. package/src/components/common/index.ts +1 -0
  140. package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
  141. package/src/components/common/types.tsx +4 -6
  142. package/src/components/common/useColumnsIds.tsx +23 -2
  143. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +11 -0
  144. package/src/components/common/useTableSearchHelper.ts +52 -12
  145. package/src/components/index.tsx +2 -1
  146. package/src/contexts/AuthControllerContext.tsx +1 -1
  147. package/src/contexts/DialogsProvider.tsx +2 -2
  148. package/src/{components/FireCMSAppBar.tsx → core/DefaultAppBar.tsx} +52 -37
  149. package/src/core/DefaultDrawer.tsx +177 -0
  150. package/src/core/DrawerNavigationItem.tsx +62 -0
  151. package/src/core/EntityEditView.tsx +711 -139
  152. package/src/core/EntitySidePanel.tsx +1 -2
  153. package/src/core/FireCMS.tsx +39 -44
  154. package/src/core/NavigationRoutes.tsx +7 -8
  155. package/src/core/field_configs.tsx +2 -3
  156. package/src/core/index.tsx +3 -4
  157. package/src/form/PropertiesForm.tsx +81 -0
  158. package/src/form/PropertyFieldBinding.tsx +29 -7
  159. package/src/form/components/FieldHelperText.tsx +3 -3
  160. package/src/form/components/StorageItemPreview.tsx +20 -11
  161. package/src/form/components/StorageUploadProgress.tsx +3 -3
  162. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +8 -5
  163. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +8 -5
  164. package/src/form/field_bindings/BlockFieldBinding.tsx +2 -2
  165. package/src/form/field_bindings/KeyValueFieldBinding.tsx +44 -39
  166. package/src/form/field_bindings/MapFieldBinding.tsx +11 -3
  167. package/src/form/field_bindings/MarkdownFieldBinding.tsx +2 -2
  168. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -9
  169. package/src/form/field_bindings/ReferenceFieldBinding.tsx +15 -13
  170. package/src/form/field_bindings/RepeatFieldBinding.tsx +10 -7
  171. package/src/form/field_bindings/SelectFieldBinding.tsx +3 -3
  172. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +22 -43
  173. package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -1
  174. package/src/form/index.tsx +4 -4
  175. package/src/form/validation.ts +1 -17
  176. package/src/hooks/data/delete.ts +3 -3
  177. package/src/hooks/data/save.ts +4 -2
  178. package/src/hooks/data/useDataSource.tsx +2 -2
  179. package/src/hooks/data/useEntityFetch.tsx +3 -3
  180. package/src/hooks/index.tsx +3 -0
  181. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +8 -10
  182. package/src/hooks/useBuildModeController.tsx +11 -5
  183. package/src/hooks/useBuildNavigationController.tsx +137 -61
  184. package/src/hooks/useProjectLog.tsx +21 -8
  185. package/src/hooks/useResolvedNavigationFrom.tsx +1 -1
  186. package/src/hooks/useValidateAuthenticator.tsx +115 -0
  187. package/src/index.ts +1 -0
  188. package/src/internal/useBuildDataSource.ts +56 -49
  189. package/src/internal/useBuildSideEntityController.tsx +88 -21
  190. package/src/preview/PropertyPreview.tsx +9 -16
  191. package/src/preview/PropertyPreviewProps.tsx +4 -8
  192. package/src/preview/components/BooleanPreview.tsx +4 -2
  193. package/src/preview/components/EnumValuesChip.tsx +1 -1
  194. package/src/preview/components/ImagePreview.tsx +21 -33
  195. package/src/preview/components/ReferencePreview.tsx +23 -23
  196. package/src/preview/components/StorageThumbnail.tsx +5 -1
  197. package/src/preview/components/UrlComponentPreview.tsx +44 -11
  198. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +0 -1
  199. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +2 -1
  200. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +0 -1
  201. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +0 -1
  202. package/src/preview/property_previews/ArrayOneOfPreview.tsx +2 -3
  203. package/src/preview/property_previews/ArrayPropertyPreview.tsx +2 -3
  204. package/src/preview/property_previews/MapPropertyPreview.tsx +5 -5
  205. package/src/preview/property_previews/StringPropertyPreview.tsx +2 -2
  206. package/src/types/auth.tsx +35 -2
  207. package/src/types/collections.ts +37 -8
  208. package/src/types/customization_controller.tsx +0 -1
  209. package/src/types/datasource.ts +41 -24
  210. package/src/types/entities.ts +9 -1
  211. package/src/types/entity_actions.tsx +16 -3
  212. package/src/types/entity_callbacks.ts +2 -2
  213. package/src/types/fields.tsx +33 -33
  214. package/src/types/index.ts +0 -1
  215. package/src/types/navigation.ts +6 -7
  216. package/src/types/plugins.tsx +18 -8
  217. package/src/types/properties.ts +22 -6
  218. package/src/types/storage.ts +12 -3
  219. package/src/util/collections.ts +1 -1
  220. package/src/util/entities.ts +5 -4
  221. package/src/util/enums.ts +1 -1
  222. package/src/util/icon_list.ts +2 -2
  223. package/src/util/icon_synonyms.ts +3 -99
  224. package/src/util/navigation_utils.ts +6 -6
  225. package/src/util/objects.ts +25 -28
  226. package/src/util/permissions.ts +1 -0
  227. package/src/util/plurals.ts +0 -2
  228. package/src/util/resolutions.ts +32 -31
  229. package/src/util/storage.ts +75 -21
  230. package/src/util/strings.ts +2 -2
  231. package/src/util/useStorageUploadController.tsx +21 -3
  232. package/src/util/useTraceUpdate.tsx +2 -1
  233. package/dist/components/VirtualTable/common.d.ts +0 -2
  234. package/dist/core/Drawer.d.ts +0 -23
  235. package/dist/core/Scaffold.d.ts +0 -55
  236. package/dist/core/SideEntityView.d.ts +0 -7
  237. package/dist/form/EntityForm.d.ts +0 -77
  238. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  239. package/dist/internal/useLocaleConfig.d.ts +0 -1
  240. package/dist/types/appcheck.d.ts +0 -26
  241. package/src/core/Drawer.tsx +0 -191
  242. package/src/core/Scaffold.tsx +0 -281
  243. package/src/core/SideEntityView.tsx +0 -38
  244. package/src/form/EntityForm.tsx +0 -728
  245. package/src/internal/useBuildCustomizationController.tsx +0 -5
  246. package/src/internal/useLocaleConfig.tsx +0 -18
  247. package/src/types/appcheck.ts +0 -29
@@ -1,728 +0,0 @@
1
- import React, { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
-
3
- import {
4
- CMSAnalyticsEvent,
5
- Entity,
6
- EntityAction,
7
- EntityCollection,
8
- EntityStatus,
9
- EntityValues,
10
- FormContext,
11
- PluginFormActionProps,
12
- PropertyFieldBindingProps,
13
- ResolvedEntityCollection
14
- } from "../types";
15
- import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
16
- import { PropertyFieldBinding } from "./PropertyFieldBinding";
17
- import { CustomFieldValidator, getYupEntitySchema } from "./validation";
18
- import equal from "react-fast-compare"
19
- import {
20
- canCreateEntity,
21
- canDeleteEntity,
22
- getDefaultValuesFor,
23
- getEntityTitlePropertyKey,
24
- getValueInPath,
25
- isHidden,
26
- isReadOnly,
27
- resolveCollection
28
- } from "../util";
29
- import {
30
- useAuthController,
31
- useCustomizationController,
32
- useDataSource,
33
- useFireCMSContext,
34
- useSideEntityController
35
- } from "../hooks";
36
- import { ErrorFocus } from "./components/ErrorFocus";
37
- import { CustomIdField } from "./components/CustomIdField";
38
- import { Alert, Button, cn, DialogActions, IconButton, Tooltip, Typography } from "@firecms/ui";
39
- import { ErrorBoundary } from "../components";
40
- import {
41
- copyEntityAction,
42
- deleteEntityAction
43
- } from "../components/EntityCollectionTable/internal/default_entity_actions";
44
- import { useAnalyticsController } from "../hooks/useAnalyticsController";
45
- import { ValidationError } from "yup";
46
- import { PropertyIdCopyTooltipContent } from "../components/PropertyIdCopyTooltipContent";
47
-
48
- /**
49
- * @group Components
50
- */
51
- export interface EntityFormProps<M extends Record<string, any>> {
52
-
53
- /**
54
- * New or existing status
55
- */
56
- status: EntityStatus;
57
-
58
- /**
59
- * Path of the collection this entity is located
60
- */
61
- path: string;
62
-
63
- /**
64
- * The collection is used to build the fields of the form
65
- */
66
- collection: EntityCollection<M>
67
-
68
- /**
69
- * The updated entity is passed from the parent component when the underlying data
70
- * has changed in the datasource
71
- */
72
- entity?: Entity<M>;
73
-
74
- /**
75
- * The callback function called when Save is clicked and validation is correct
76
- */
77
- onEntitySaveRequested: (
78
- props: EntityFormSaveParams<M>
79
- ) => Promise<void>;
80
-
81
- /**
82
- * The callback function called when discard is clicked
83
- */
84
- onDiscard?: () => void;
85
-
86
- /**
87
- * The callback function when the form is dirty, so the values are different
88
- * from the original ones
89
- */
90
- onModified?: (dirty: boolean) => void;
91
-
92
- /**
93
- * The callback function when the form original values have been modified
94
- */
95
- onValuesChanged?: (values?: EntityValues<M>) => void;
96
-
97
- /**
98
- *
99
- * @param id
100
- */
101
- onIdChange?: (id: string) => void;
102
-
103
- currentEntityId?: string;
104
-
105
- onFormContextChange?: (formContext: FormContext<M>) => void;
106
-
107
- hideId?: boolean;
108
-
109
- autoSave?: boolean;
110
-
111
- onIdUpdateError?: (error: any) => void;
112
-
113
- }
114
-
115
- export type EntityFormSaveParams<M extends Record<string, any>> = {
116
- collection: ResolvedEntityCollection<M>,
117
- path: string,
118
- entityId: string | undefined,
119
- values: EntityValues<M>,
120
- previousValues?: EntityValues<M>,
121
- closeAfterSave: boolean,
122
- autoSave: boolean
123
- };
124
-
125
- /**
126
- * This is the form used internally by the CMS
127
- * @param status
128
- * @param path
129
- * @param collection
130
- * @param entity
131
- * @param onEntitySave
132
- * @param onDiscard
133
- * @param onModified
134
- * @param onValuesChanged
135
- * @constructor
136
- * @group Components
137
- */
138
- export const EntityForm = React.memo<EntityFormProps<any>>(EntityFormInternal,
139
- (a: EntityFormProps<any>, b: EntityFormProps<any>) => {
140
- return a.status === b.status &&
141
- a.path === b.path &&
142
- equal(a.entity?.values, b.entity?.values);
143
- }) as typeof EntityFormInternal;
144
-
145
- function getDataSourceEntityValues<M extends object>(initialResolvedCollection: ResolvedEntityCollection,
146
- status: "new" | "existing" | "copy",
147
- entity: Entity<M> | undefined): Partial<EntityValues<M>> {
148
- const properties = initialResolvedCollection.properties;
149
- if ((status === "existing" || status === "copy") && entity) {
150
- return entity.values ?? getDefaultValuesFor(properties);
151
- } else if (status === "new") {
152
- return getDefaultValuesFor(properties);
153
- } else {
154
- console.error({
155
- status,
156
- entity
157
- });
158
- throw new Error("Form has not been initialised with the correct parameters");
159
- }
160
- }
161
-
162
- function EntityFormInternal<M extends Record<string, any>>({
163
- status,
164
- path,
165
- collection: inputCollection,
166
- entity,
167
- onEntitySaveRequested,
168
- onDiscard,
169
- onModified,
170
- onValuesChanged,
171
- onIdChange,
172
- onFormContextChange,
173
- hideId,
174
- autoSave,
175
- onIdUpdateError,
176
- }: EntityFormProps<M>) {
177
-
178
- const analyticsController = useAnalyticsController();
179
-
180
- const customizationController = useCustomizationController();
181
-
182
- const context = useFireCMSContext();
183
- const dataSource = useDataSource(inputCollection);
184
- const plugins = customizationController.plugins;
185
-
186
- const initialResolvedCollection = useMemo(() => resolveCollection({
187
- collection: inputCollection,
188
- path,
189
- values: entity?.values,
190
- fields: customizationController.propertyConfigs
191
- }), [entity?.values, path]);
192
-
193
- const mustSetCustomId: boolean = (status === "new" || status === "copy") &&
194
- (Boolean(initialResolvedCollection.customId) && initialResolvedCollection.customId !== "optional");
195
-
196
- const initialEntityId = useMemo(() => {
197
- if (status === "new" || status === "copy") {
198
- if (mustSetCustomId) {
199
- return undefined;
200
- } else {
201
- return dataSource.generateEntityId(path);
202
- }
203
- } else {
204
- return entity?.id;
205
- }
206
- }, []);
207
-
208
- const closeAfterSaveRef = useRef(false);
209
-
210
- const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, entity));
211
-
212
- const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
213
- const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
214
- const [savingError, setSavingError] = React.useState<Error | undefined>();
215
-
216
- const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
217
-
218
- // const initialValuesRef = useRef<EntityValues<M>>(entity?.values ?? baseDataSourceValues as EntityValues<M>);
219
- const [internalValues, setInternalValues] = useState<EntityValues<M> | undefined>(entity?.values ?? baseDataSourceValuesRef.current as EntityValues<M>);
220
-
221
- const save = (values: EntityValues<M>): Promise<void> => {
222
- return onEntitySaveRequested({
223
- collection: resolvedCollection,
224
- path,
225
- entityId,
226
- values,
227
- previousValues: entity?.values,
228
- closeAfterSave: closeAfterSaveRef.current,
229
- autoSave: autoSave ?? false
230
- }).then(_ => {
231
- const eventName: CMSAnalyticsEvent = status === "new"
232
- ? "new_entity_saved"
233
- : (status === "copy" ? "entity_copied" : (status === "existing" ? "entity_edited" : "unmapped_event"));
234
- analyticsController.onAnalyticsEvent?.(eventName, { path });
235
- }).catch(e => {
236
- console.error(e);
237
- setSavingError(e);
238
- }).finally(() => {
239
- closeAfterSaveRef.current = false;
240
- });
241
- };
242
-
243
- const onSubmit = (values: EntityValues<M>, formexController: FormexController<EntityValues<M>>) => {
244
-
245
- if (mustSetCustomId && !entityId) {
246
- console.error("Missing custom Id");
247
- setEntityIdError(true);
248
- formexController.setSubmitting(false);
249
- return;
250
- }
251
-
252
- setSavingError(undefined);
253
- setEntityIdError(false);
254
-
255
- if (status === "existing") {
256
- if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
257
- } else if (status === "new" || status === "copy") {
258
- if (inputCollection.customId) {
259
- if (inputCollection.customId !== "optional" && !entityId) {
260
- throw Error("Form misconfiguration when saving, entityId should be set");
261
- }
262
- }
263
- } else {
264
- throw Error("New FormType added, check EntityForm");
265
- }
266
-
267
- return save(values)
268
- ?.then(_ => {
269
- formexController.resetForm({
270
- values,
271
- submitCount: 0,
272
- touched: {}
273
- });
274
- })
275
- .finally(() => {
276
- formexController.setSubmitting(false);
277
- });
278
-
279
- };
280
-
281
- const formex: FormexController<M> = useCreateFormex<M>({
282
- initialValues: baseDataSourceValuesRef.current as M,
283
- onSubmit,
284
- validation: (values) => {
285
- return validationSchema?.validate(values, { abortEarly: false })
286
- .then(() => {
287
- return {};
288
- })
289
- .catch((e) => {
290
-
291
- const errors: Record<string, string> = {};
292
- e.inner.forEach((error: any) => {
293
- errors[error.path] = error.message;
294
- });
295
- return yupToFormErrors(e);
296
- });
297
- }
298
- });
299
-
300
- useEffect(() => {
301
- baseDataSourceValuesRef.current = getDataSourceEntityValues(initialResolvedCollection, status, entity);
302
- const initialValues = formex.initialValues;
303
- if (!formex.isSubmitting && initialValues && status === "existing") {
304
- setUnderlyingChanges(
305
- Object.entries(resolvedCollection.properties)
306
- .map(([key, property]) => {
307
- if (isHidden(property)) {
308
- return {};
309
- }
310
- const initialValue = initialValues[key];
311
- const latestValue = baseDataSourceValuesRef.current[key];
312
- if (!equal(initialValue, latestValue)) {
313
- return { [key]: latestValue };
314
- }
315
- return {};
316
- })
317
- .reduce((a, b) => ({ ...a, ...b }), {}) as Partial<EntityValues<M>>
318
- );
319
- } else {
320
- setUnderlyingChanges({});
321
- }
322
- }, [entity, initialResolvedCollection, status]);
323
-
324
- const doOnValuesChanges = (values?: EntityValues<M>) => {
325
- const initialValues = formex.initialValues;
326
- setInternalValues(values);
327
- if (onValuesChanged)
328
- onValuesChanged(values);
329
- if (autoSave && values && !equal(values, initialValues)) {
330
- save(values);
331
- }
332
- };
333
-
334
- useEffect(() => {
335
- if (entityId && onIdChange)
336
- onIdChange(entityId);
337
- }, [entityId, onIdChange]);
338
-
339
- const resolvedCollection = resolveCollection<M>({
340
- collection: inputCollection,
341
- path,
342
- entityId,
343
- values: internalValues,
344
- previousValues: formex.initialValues,
345
- fields: customizationController.propertyConfigs
346
- });
347
-
348
- const titlePropertyKey = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
349
- const title = internalValues && titlePropertyKey ? getValueInPath(internalValues, titlePropertyKey) : undefined;
350
-
351
- const onIdUpdate = inputCollection.callbacks?.onIdUpdate;
352
-
353
- const doOnIdUpdate = useCallback(async () => {
354
- if (onIdUpdate && internalValues && (status === "new" || status === "copy")) {
355
- setCustomIdLoading(true);
356
- try {
357
- const updatedId = await onIdUpdate({
358
- collection: resolvedCollection,
359
- path,
360
- entityId,
361
- values: internalValues,
362
- context
363
- });
364
- setEntityId(updatedId);
365
- } catch (e) {
366
- onIdUpdateError && onIdUpdateError(e);
367
- console.error(e);
368
- }
369
- setCustomIdLoading(false);
370
- }
371
- }, [entityId, internalValues, status]);
372
-
373
- useEffect(() => {
374
- doOnIdUpdate();
375
- }, [doOnIdUpdate]);
376
-
377
- const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
378
-
379
- const uniqueFieldValidator: CustomFieldValidator = useCallback(({
380
- name,
381
- value,
382
- property
383
- }) => dataSource.checkUniqueField(path, name, value, entityId),
384
- [dataSource, path, entityId]);
385
-
386
- const validationSchema = useMemo(() => entityId
387
- ? getYupEntitySchema(
388
- entityId,
389
- resolvedCollection.properties,
390
- uniqueFieldValidator)
391
- : undefined,
392
- [entityId, resolvedCollection.properties, uniqueFieldValidator]);
393
-
394
- const authController = useAuthController();
395
-
396
- const getActionsForEntity = useCallback(({
397
- entity,
398
- customEntityActions
399
- }: {
400
- entity?: Entity<M>,
401
- customEntityActions?: EntityAction[]
402
- }): EntityAction[] => {
403
- const createEnabled = canCreateEntity(inputCollection, authController, path, null);
404
- const deleteEnabled = entity ? canDeleteEntity(inputCollection, authController, path, entity) : true;
405
- const actions: EntityAction[] = [];
406
- if (createEnabled)
407
- actions.push(copyEntityAction);
408
- if (deleteEnabled)
409
- actions.push(deleteEntityAction);
410
- if (customEntityActions)
411
- actions.push(...customEntityActions);
412
- return actions;
413
- }, [authController, inputCollection, path]);
414
-
415
- const pluginActions: React.ReactNode[] = [];
416
-
417
- const formContext: FormContext<M> = {
418
- setFieldValue: formex.setFieldValue,
419
- values: formex.values,
420
- collection: resolvedCollection,
421
- entityId,
422
- path,
423
- save
424
- };
425
-
426
- // eslint-disable-next-line react-hooks/rules-of-hooks
427
- useEffect(() => {
428
- if (onFormContextChange) {
429
- onFormContextChange(formContext);
430
- }
431
- }, [onFormContextChange, formContext]);
432
-
433
- if (plugins && inputCollection) {
434
- const actionProps: PluginFormActionProps = {
435
- entityId,
436
- path,
437
- status,
438
- collection: inputCollection,
439
- context,
440
- currentEntityId: entityId,
441
- formContext
442
- };
443
- pluginActions.push(...plugins.map((plugin, i) => (
444
- plugin.form?.Actions
445
- ? <plugin.form.Actions
446
- key={`actions_${plugin.name}`} {...actionProps}/>
447
- : null
448
- )).filter(Boolean));
449
- }
450
-
451
- return <Formex value={formex}>
452
- <div className="h-full overflow-auto">
453
-
454
- {pluginActions.length > 0 && <div
455
- className={cn("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")}>
456
- {pluginActions}
457
- </div>}
458
-
459
- <div className="pt-12 pb-16 pl-8 pr-8 md:pl-10 md:pr-10">
460
- <div
461
- className={`w-full py-2 flex flex-col items-start mt-${4 + (pluginActions ? 8 : 0)} lg:mt-${8 + (pluginActions ? 8 : 0)} mb-8`}>
462
-
463
- <Typography
464
- className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
465
- variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
466
- </Typography>
467
- <Alert color={"base"} className={"w-full"} size={"small"}>
468
- <code className={"text-xs select-all"}>{path}/{entityId}</code>
469
- </Alert>
470
- </div>
471
-
472
- {!hideId &&
473
- <CustomIdField customId={inputCollection.customId}
474
- entityId={entityId}
475
- status={status}
476
- onChange={setEntityId}
477
- error={entityIdError}
478
- loading={customIdLoading}
479
- entity={entity}/>}
480
-
481
- {entityId && <InnerForm
482
- {...formex}
483
- initialValues={formex.initialValues}
484
- onModified={onModified}
485
- onDiscard={onDiscard}
486
- onValuesChanged={doOnValuesChanges}
487
- underlyingChanges={underlyingChanges}
488
- entity={entity}
489
- resolvedCollection={resolvedCollection}
490
- formContext={formContext}
491
- status={status}
492
- savingError={savingError}
493
- closeAfterSaveRef={closeAfterSaveRef}
494
- autoSave={autoSave}
495
- entityActions={getActionsForEntity({
496
- entity,
497
- customEntityActions: inputCollection.entityActions
498
- })}/>}
499
-
500
- </div>
501
- </div>
502
- </Formex>
503
- }
504
-
505
- function InnerForm<M extends Record<string, any>>(props: FormexController<M> & {
506
- initialValues: EntityValues<M>,
507
- onModified: ((modified: boolean) => void) | undefined,
508
- onValuesChanged?: (changedValues?: EntityValues<M>) => void,
509
- underlyingChanges: Partial<M>,
510
- entity: Entity<M> | undefined,
511
- resolvedCollection: ResolvedEntityCollection<M>,
512
- formContext: FormContext<M>,
513
- onDiscard?: () => void,
514
- status: "new" | "existing" | "copy",
515
- savingError?: Error,
516
- closeAfterSaveRef: MutableRefObject<boolean>,
517
- autoSave?: boolean,
518
- entityActions: EntityAction[],
519
- }) {
520
-
521
- const {
522
- values,
523
- onDiscard,
524
- onModified,
525
- onValuesChanged,
526
- underlyingChanges,
527
- formContext,
528
- entity,
529
- touched,
530
- setFieldValue,
531
- resolvedCollection,
532
- isSubmitting,
533
- status,
534
- handleSubmit,
535
- resetForm,
536
- savingError,
537
- dirty,
538
- closeAfterSaveRef,
539
- autoSave,
540
- entityActions,
541
- } = props;
542
-
543
- const context = useFireCMSContext();
544
- const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
545
- const sideEntityController = useSideEntityController();
546
-
547
- const modified = dirty;
548
- useEffect(() => {
549
- if (onModified)
550
- onModified(modified);
551
- if (onValuesChanged)
552
- onValuesChanged(values);
553
- }, [modified, values]);
554
-
555
- useEffect(() => {
556
- if (!autoSave && !isSubmitting && underlyingChanges && entity) {
557
- // we update the form fields from the Firestore data
558
- // if they were not touched
559
- Object.entries(underlyingChanges).forEach(([key, value]) => {
560
- const formValue = values[key];
561
- if (!equal(value, formValue) && !touched[key]) {
562
- console.debug("Updated value from the datasource:", key, value);
563
- setFieldValue(key, value !== undefined ? value : null);
564
- }
565
- });
566
- }
567
- }, [isSubmitting, autoSave, underlyingChanges, entity, values, touched, setFieldValue]);
568
-
569
- const formFields = (
570
- <div className={"flex flex-col gap-8"}>
571
- {(resolvedCollection.propertiesOrder ?? Object.keys(resolvedCollection.properties))
572
- .map((key) => {
573
-
574
- const property = resolvedCollection.properties[key];
575
- if (!property) {
576
- console.warn(`Property ${key} not found in collection ${resolvedCollection.name}`);
577
- return null;
578
- }
579
-
580
- const underlyingValueHasChanged: boolean =
581
- !!underlyingChanges &&
582
- Object.keys(underlyingChanges).includes(key) &&
583
- !!touched[key];
584
-
585
- const disabled = (!autoSave && isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
586
- const hidden = isHidden(property);
587
- if (hidden) return null;
588
- const cmsFormFieldProps: PropertyFieldBindingProps<any, M> = {
589
- propertyKey: key,
590
- disabled,
591
- property,
592
- includeDescription: property.description || property.longDescription,
593
- underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
594
- context: formContext,
595
- tableMode: false,
596
- partOfArray: false,
597
- partOfBlock: false,
598
- autoFocus: false
599
- };
600
-
601
- return (
602
- <div id={`form_field_${key}`}
603
- key={`field_${resolvedCollection.name}_${key}`}>
604
- <ErrorBoundary>
605
- <Tooltip title={<PropertyIdCopyTooltipContent propertyId={key}/>}
606
- delayDuration={800}
607
- side={"left"}
608
- align={"start"}
609
- sideOffset={16}>
610
- <PropertyFieldBinding {...cmsFormFieldProps}/>
611
- </Tooltip>
612
- </ErrorBoundary>
613
- </div>
614
- );
615
- })
616
- .filter(Boolean)}
617
-
618
- </div>
619
- );
620
-
621
- const disabled = isSubmitting || (!modified && status === "existing");
622
- const formRef = React.useRef<HTMLDivElement>(null);
623
-
624
- return (
625
-
626
- <form onSubmit={handleSubmit}
627
- onReset={() => {
628
- console.debug("Resetting form")
629
- resetForm();
630
- return onDiscard && onDiscard();
631
- }}
632
- noValidate>
633
- <div className="mt-12"
634
- ref={formRef}>
635
-
636
- {formFields}
637
-
638
- <ErrorFocus containerRef={formRef}/>
639
-
640
- </div>
641
-
642
- <div className="h-14"/>
643
-
644
- {!autoSave && <DialogActions position={"absolute"}>
645
-
646
- {savingError &&
647
- <div className="text-right">
648
- <Typography color={"error"}>
649
- {savingError.message}
650
- </Typography>
651
- </div>}
652
-
653
- {entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
654
- {formActions.map(action => (
655
- <IconButton
656
- key={action.name}
657
- color="primary"
658
- onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
659
- event.stopPropagation();
660
- if (entity)
661
- action.onClick({
662
- entity,
663
- fullPath: resolvedCollection.path,
664
- collection: resolvedCollection,
665
- context,
666
- sideEntityController,
667
- });
668
- }}>
669
- {action.icon}
670
- </IconButton>
671
- ))}
672
- </div>}
673
-
674
- <Button
675
- variant="text"
676
- disabled={disabled}
677
- type="reset"
678
- >
679
- {status === "existing" ? "Discard" : "Clear"}
680
- </Button>
681
-
682
- <Button
683
- variant="text"
684
- color="primary"
685
- type="submit"
686
- disabled={disabled}
687
- onClick={() => {
688
- closeAfterSaveRef.current = false;
689
- }}
690
- >
691
- {status === "existing" && "Save"}
692
- {status === "copy" && "Create copy"}
693
- {status === "new" && "Create"}
694
- </Button>
695
-
696
- <Button
697
- variant="filled"
698
- color="primary"
699
- type="submit"
700
- disabled={disabled}
701
- onClick={() => {
702
- closeAfterSaveRef.current = true;
703
- }}
704
- >
705
- {status === "existing" && "Save and close"}
706
- {status === "copy" && "Create copy and close"}
707
- {status === "new" && "Create and close"}
708
- </Button>
709
-
710
- </DialogActions>}
711
- </form>
712
- );
713
- }
714
-
715
- export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
716
- let errors: Record<string, any> = {};
717
- if (yupError.inner) {
718
- if (yupError.inner.length === 0) {
719
- return setIn(errors, yupError.path!, yupError.message);
720
- }
721
- for (const err of yupError.inner) {
722
- if (!getIn(errors, err.path!)) {
723
- errors = setIn(errors, err.path!, err.message);
724
- }
725
- }
726
- }
727
- return errors;
728
- }