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