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