@firecms/core 3.0.0-canary.99 → 3.0.0-rc.1
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 +2 -2
- package/dist/app/Drawer.d.ts +0 -1
- package/dist/app/Scaffold.d.ts +4 -0
- package/dist/components/ArrayContainer.d.ts +31 -12
- package/dist/components/{DeleteConfirmationDialog.d.ts → ConfirmationDialog.d.ts} +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +3 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +17 -3
- package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +1 -1
- package/dist/components/EntityCollectionTable/index.d.ts +1 -1
- package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +6 -3
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
- package/dist/components/EntityCollectionView/utils.d.ts +3 -0
- package/dist/components/EntityJsonPreview.d.ts +3 -0
- package/dist/components/EntityPreview.d.ts +8 -6
- package/dist/components/HomePage/DefaultHomePage.d.ts +2 -15
- package/dist/components/HomePage/HomePageDnD.d.ts +76 -0
- package/dist/components/HomePage/NavigationCard.d.ts +3 -1
- package/dist/components/HomePage/NavigationCardBinding.d.ts +3 -2
- package/dist/components/HomePage/NavigationGroup.d.ts +8 -1
- package/dist/components/HomePage/RenameGroupDialog.d.ts +9 -0
- package/dist/components/PropertyConfigBadge.d.ts +2 -1
- package/dist/components/PropertyIdCopyTooltip.d.ts +8 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +13 -3
- package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -2
- package/dist/components/common/default_entity_actions.d.ts +0 -2
- package/dist/components/common/index.d.ts +1 -1
- package/dist/components/common/useColumnsIds.d.ts +1 -0
- package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
- package/dist/components/common/useDebouncedCallback.d.ts +1 -0
- package/dist/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/contexts/BreacrumbsContext.d.ts +8 -0
- package/dist/core/DefaultAppBar.d.ts +8 -2
- package/dist/core/DrawerNavigationItem.d.ts +2 -1
- package/dist/core/EntityEditView.d.ts +40 -22
- package/dist/core/EntityEditViewFormActions.d.ts +2 -0
- package/dist/core/FireCMS.d.ts +2 -2
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +0 -1
- package/dist/core/SideDialogs.d.ts +4 -2
- package/dist/core/field_configs.d.ts +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/form/EntityForm.d.ts +50 -0
- package/dist/form/EntityFormActions.d.ts +21 -0
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/form/components/FormEntry.d.ts +6 -0
- package/dist/form/components/FormLayout.d.ts +5 -0
- package/dist/form/components/LabelWithIcon.d.ts +1 -1
- package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
- package/dist/form/components/index.d.ts +3 -1
- 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 → MultiSelectFieldBinding.d.ts} +1 -1
- package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +9 -0
- package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -2
- 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 +4 -10
- package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -2
- package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
- package/dist/form/index.d.ts +17 -16
- package/dist/form/useClearRestoreValue.d.ts +2 -2
- package/dist/hooks/data/delete.d.ts +4 -4
- package/dist/hooks/data/save.d.ts +3 -3
- package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
- package/dist/hooks/data/useEntityFetch.d.ts +4 -3
- package/dist/hooks/useAuthController.d.ts +1 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +57 -12
- package/dist/hooks/useFireCMSContext.d.ts +1 -1
- package/dist/hooks/useModeController.d.ts +1 -2
- package/dist/hooks/useProjectLog.d.ts +7 -1
- package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
- package/dist/hooks/useValidateAuthenticator.d.ts +3 -3
- package/dist/index.es.js +20108 -14471
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +20039 -14407
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildDataSource.d.ts +3 -2
- package/dist/internal/useBuildSideEntityController.d.ts +3 -3
- package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
- package/dist/preview/PropertyPreviewProps.d.ts +1 -1
- package/dist/preview/components/EnumValuesChip.d.ts +1 -1
- package/dist/preview/components/ReferencePreview.d.ts +2 -2
- package/dist/preview/util.d.ts +3 -3
- package/dist/routes/CustomCMSRoute.d.ts +4 -0
- package/dist/routes/FireCMSRoute.d.ts +1 -0
- package/dist/routes/HomePageRoute.d.ts +3 -0
- package/dist/types/analytics.d.ts +1 -1
- package/dist/types/auth.d.ts +7 -9
- package/dist/types/collections.d.ts +86 -25
- package/dist/types/customization_controller.d.ts +8 -0
- package/dist/types/datasource.d.ts +19 -17
- package/dist/types/dialogs_controller.d.ts +7 -3
- package/dist/types/entities.d.ts +2 -1
- package/dist/types/entity_actions.d.ts +58 -8
- package/dist/types/entity_callbacks.d.ts +16 -16
- package/dist/types/entity_overrides.d.ts +2 -2
- package/dist/types/export_import.d.ts +4 -4
- package/dist/types/fields.d.ts +43 -17
- package/dist/types/firecms.d.ts +16 -3
- package/dist/types/firecms_context.d.ts +1 -1
- package/dist/types/navigation.d.ts +60 -17
- package/dist/types/permissions.d.ts +4 -4
- package/dist/types/plugins.d.ts +42 -9
- package/dist/types/properties.d.ts +65 -22
- package/dist/types/property_config.d.ts +1 -3
- package/dist/types/roles.d.ts +3 -0
- package/dist/types/side_dialogs_controller.d.ts +10 -0
- package/dist/types/side_entity_controller.d.ts +14 -1
- package/dist/types/storage.d.ts +75 -0
- package/dist/types/user.d.ts +1 -0
- package/dist/util/builders.d.ts +3 -3
- package/dist/util/callbacks.d.ts +2 -0
- package/dist/util/createFormexStub.d.ts +2 -0
- package/dist/util/entities.d.ts +2 -2
- package/dist/util/entity_actions.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/icon_synonyms.d.ts +0 -1
- package/dist/util/icons.d.ts +5 -2
- package/dist/util/index.d.ts +3 -0
- package/dist/util/navigation_from_path.d.ts +10 -1
- package/dist/util/navigation_utils.d.ts +13 -1
- package/dist/util/objects.d.ts +2 -1
- package/dist/util/permissions.d.ts +4 -4
- package/dist/util/property_utils.d.ts +4 -4
- package/dist/util/references.d.ts +2 -2
- package/dist/util/resolutions.d.ts +30 -6
- package/dist/util/storage.d.ts +1 -1
- package/dist/util/useStorageUploadController.d.ts +2 -2
- package/package.json +133 -125
- package/src/app/Drawer.tsx +0 -1
- package/src/app/Scaffold.tsx +33 -29
- package/src/components/ArrayContainer.tsx +447 -229
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/ClearFilterSortButton.tsx +1 -1
- package/src/components/{DeleteConfirmationDialog.tsx → ConfirmationDialog.tsx} +12 -11
- package/src/components/DeleteEntityDialog.tsx +13 -20
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +59 -25
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +23 -17
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +20 -3
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +35 -9
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +21 -16
- package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +6 -12
- package/src/components/EntityCollectionTable/index.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +6 -6
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +35 -26
- package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +20 -8
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +132 -101
- package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +9 -9
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +178 -85
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +7 -4
- package/src/components/EntityCollectionView/useSelectionController.tsx +5 -4
- package/src/components/EntityCollectionView/utils.ts +19 -0
- package/src/components/EntityJsonPreview.tsx +66 -0
- package/src/components/EntityPreview.tsx +75 -57
- package/src/components/EntityView.tsx +8 -5
- package/src/components/ErrorView.tsx +3 -3
- package/src/components/FireCMSLogo.tsx +7 -51
- package/src/components/HomePage/DefaultHomePage.tsx +522 -160
- package/src/components/HomePage/FavouritesView.tsx +9 -14
- package/src/components/HomePage/HomePageDnD.tsx +642 -0
- package/src/components/HomePage/NavigationCard.tsx +47 -38
- package/src/components/HomePage/NavigationCardBinding.tsx +16 -15
- package/src/components/HomePage/NavigationGroup.tsx +144 -30
- package/src/components/HomePage/RenameGroupDialog.tsx +117 -0
- package/src/components/HomePage/SmallNavigationCard.tsx +1 -2
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/PropertyConfigBadge.tsx +9 -3
- package/src/components/PropertyIdCopyTooltip.tsx +47 -0
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +22 -13
- package/src/components/SearchIconsView.tsx +2 -2
- package/src/components/SelectableTable/SelectableTable.tsx +154 -142
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +4 -2
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +10 -8
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +59 -10
- package/src/components/UnsavedChangesDialog.tsx +46 -0
- package/src/components/VirtualTable/VirtualTable.tsx +65 -44
- package/src/components/VirtualTable/VirtualTableCell.tsx +0 -8
- package/src/components/VirtualTable/VirtualTableHeader.tsx +8 -8
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +1 -1
- package/src/components/VirtualTable/VirtualTableProps.tsx +12 -2
- package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +4 -4
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +2 -2
- package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +16 -28
- package/src/components/common/default_entity_actions.tsx +62 -42
- package/src/components/common/index.ts +1 -1
- package/src/components/common/useColumnsIds.tsx +1 -1
- package/src/components/common/useDataSourceTableController.tsx +420 -0
- package/src/components/common/useDebouncedCallback.tsx +20 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/components/common/useTableSearchHelper.ts +1 -0
- package/src/components/index.tsx +4 -1
- package/src/contexts/BreacrumbsContext.tsx +38 -0
- package/src/contexts/DialogsProvider.tsx +3 -2
- package/src/contexts/ModeController.tsx +1 -3
- package/src/contexts/SnackbarProvider.tsx +2 -0
- package/src/core/DefaultAppBar.tsx +124 -85
- package/src/core/DefaultDrawer.tsx +30 -22
- package/src/core/DrawerNavigationItem.tsx +32 -28
- package/src/core/EntityEditView.tsx +388 -995
- package/src/core/EntityEditViewFormActions.tsx +329 -0
- package/src/core/EntitySidePanel.tsx +88 -20
- package/src/core/FireCMS.tsx +46 -25
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +23 -32
- package/src/core/SideDialogs.tsx +22 -12
- package/src/core/field_configs.tsx +24 -10
- package/src/core/index.tsx +4 -2
- package/src/form/EntityForm.tsx +814 -0
- package/src/form/EntityFormActions.tsx +211 -0
- package/src/form/PropertyFieldBinding.tsx +55 -41
- package/src/form/components/CustomIdField.tsx +9 -3
- package/src/form/components/FieldHelperText.tsx +1 -1
- package/src/form/components/FormEntry.tsx +22 -0
- package/src/form/components/FormLayout.tsx +16 -0
- package/src/form/components/LabelWithIcon.tsx +30 -19
- package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
- package/src/form/components/StorageItemPreview.tsx +5 -4
- package/src/form/components/StorageUploadProgress.tsx +2 -3
- package/src/form/components/index.tsx +3 -1
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +30 -18
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +47 -36
- package/src/form/field_bindings/BlockFieldBinding.tsx +55 -33
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -14
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +19 -15
- package/src/form/field_bindings/MapFieldBinding.tsx +72 -62
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +159 -0
- package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +26 -21
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +10 -8
- package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +135 -0
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +28 -19
- package/src/form/field_bindings/RepeatFieldBinding.tsx +56 -32
- package/src/form/field_bindings/SelectFieldBinding.tsx +22 -13
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +247 -168
- package/src/form/field_bindings/SwitchFieldBinding.tsx +29 -24
- package/src/form/field_bindings/TextFieldBinding.tsx +28 -24
- package/src/form/index.tsx +17 -37
- package/src/form/useClearRestoreValue.tsx +2 -2
- package/src/form/validation.ts +12 -6
- package/src/hooks/data/delete.ts +6 -5
- package/src/hooks/data/save.ts +26 -35
- package/src/hooks/data/useCollectionFetch.tsx +3 -3
- package/src/hooks/data/useDataSource.tsx +10 -2
- package/src/hooks/data/useEntityFetch.tsx +10 -6
- package/src/hooks/useAuthController.tsx +1 -1
- package/src/hooks/useBreadcrumbsController.tsx +31 -0
- package/src/hooks/useBrowserTitleAndIcon.tsx +1 -1
- package/src/hooks/useBuildModeController.tsx +15 -28
- package/src/hooks/useBuildNavigationController.tsx +386 -124
- package/src/hooks/useFireCMSContext.tsx +3 -33
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useModeController.tsx +1 -2
- package/src/hooks/useProjectLog.tsx +16 -5
- package/src/hooks/useResolvedNavigationFrom.tsx +9 -11
- package/src/hooks/useValidateAuthenticator.tsx +3 -3
- package/src/internal/useBuildDataSource.ts +67 -80
- package/src/internal/useBuildSideDialogsController.tsx +4 -2
- package/src/internal/useBuildSideEntityController.tsx +149 -86
- package/src/internal/useUnsavedChangesDialog.tsx +127 -91
- package/src/preview/PropertyPreview.tsx +28 -12
- package/src/preview/PropertyPreviewProps.tsx +1 -1
- package/src/preview/components/BooleanPreview.tsx +1 -1
- package/src/preview/components/EmptyValue.tsx +1 -1
- package/src/preview/components/EnumValuesChip.tsx +1 -1
- package/src/preview/components/ImagePreview.tsx +10 -9
- package/src/preview/components/ReferencePreview.tsx +6 -16
- package/src/preview/components/UrlComponentPreview.tsx +20 -21
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +6 -5
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +5 -4
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +5 -3
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +6 -4
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +5 -3
- package/src/preview/property_previews/MapPropertyPreview.tsx +7 -6
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +13 -13
- package/src/preview/property_previews/StringPropertyPreview.tsx +2 -2
- package/src/preview/util.ts +10 -10
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +246 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/analytics.ts +3 -0
- package/src/types/auth.tsx +8 -12
- package/src/types/collections.ts +101 -28
- package/src/types/customization_controller.tsx +9 -0
- package/src/types/datasource.ts +21 -20
- package/src/types/dialogs_controller.tsx +7 -3
- package/src/types/entities.ts +3 -1
- package/src/types/entity_actions.tsx +71 -8
- package/src/types/entity_callbacks.ts +18 -18
- package/src/types/entity_overrides.tsx +2 -2
- package/src/types/export_import.ts +4 -4
- package/src/types/fields.tsx +52 -19
- package/src/types/firecms.tsx +18 -4
- package/src/types/firecms_context.tsx +1 -1
- package/src/types/navigation.ts +76 -22
- package/src/types/permissions.ts +5 -5
- package/src/types/plugins.tsx +50 -9
- package/src/types/properties.ts +74 -22
- package/src/types/property_config.tsx +1 -2
- package/src/types/roles.ts +3 -0
- package/src/types/side_dialogs_controller.tsx +15 -0
- package/src/types/side_entity_controller.tsx +16 -1
- package/src/types/storage.ts +82 -0
- package/src/types/user.ts +2 -0
- package/src/util/builders.ts +10 -8
- package/src/util/callbacks.ts +119 -0
- package/src/util/createFormexStub.tsx +62 -0
- package/src/util/entities.ts +5 -3
- package/src/util/entity_actions.ts +28 -0
- package/src/util/entity_cache.ts +204 -0
- package/src/util/icon_list.ts +1 -1
- package/src/util/icon_synonyms.ts +0 -1
- package/src/util/icons.tsx +36 -11
- package/src/util/index.ts +3 -0
- package/src/util/join_collections.ts +9 -2
- package/src/util/make_properties_editable.ts +13 -5
- package/src/util/navigation_from_path.ts +33 -12
- package/src/util/navigation_utils.ts +135 -19
- package/src/util/objects.ts +74 -14
- package/src/util/parent_references_from_path.ts +3 -3
- package/src/util/permissions.ts +8 -8
- package/src/util/property_utils.tsx +17 -6
- package/src/util/references.ts +19 -8
- package/src/util/resolutions.ts +93 -24
- package/src/util/storage.ts +6 -2
- package/src/util/useStorageUploadController.tsx +74 -29
- package/dist/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.d.ts +0 -5
- package/dist/components/PropertyIdCopyTooltipContent.d.ts +0 -3
- package/dist/form/PropertiesForm.d.ts +0 -8
- package/dist/form/components/FormikArrayContainer.d.ts +0 -18
- package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
- package/src/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.tsx +0 -59
- package/src/components/PropertyIdCopyTooltipContent.tsx +0 -27
- package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
- package/src/form/PropertiesForm.tsx +0 -81
- package/src/form/components/FormikArrayContainer.tsx +0 -44
- package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -695
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -1,1133 +1,526 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
2
2
|
import {
|
|
3
|
-
CMSAnalyticsEvent,
|
|
4
3
|
Entity,
|
|
5
|
-
EntityAction,
|
|
6
4
|
EntityCollection,
|
|
7
|
-
EntityCustomView,
|
|
8
5
|
EntityStatus,
|
|
9
|
-
EntityValues,
|
|
10
6
|
FireCMSPlugin,
|
|
11
7
|
FormContext,
|
|
12
8
|
PluginFormActionProps,
|
|
13
|
-
PropertyFieldBindingProps,
|
|
14
|
-
ResolvedEntityCollection,
|
|
15
9
|
User
|
|
16
10
|
} from "../types";
|
|
17
|
-
import equal from "react-fast-compare"
|
|
18
11
|
|
|
12
|
+
import { CircularProgressCenter, EntityCollectionView, EntityView, ErrorBoundary } from "../components";
|
|
19
13
|
import {
|
|
20
|
-
CircularProgressCenter,
|
|
21
|
-
copyEntityAction,
|
|
22
|
-
deleteEntityAction,
|
|
23
|
-
EntityCollectionView,
|
|
24
|
-
EntityView,
|
|
25
|
-
ErrorBoundary,
|
|
26
|
-
getFormFieldKeys,
|
|
27
|
-
} from "../components";
|
|
28
|
-
import {
|
|
29
|
-
canCreateEntity,
|
|
30
|
-
canDeleteEntity,
|
|
31
14
|
canEditEntity,
|
|
32
|
-
getDefaultValuesFor,
|
|
33
|
-
getEntityTitlePropertyKey,
|
|
34
|
-
getValueInPath,
|
|
35
|
-
isHidden,
|
|
36
|
-
isReadOnly,
|
|
37
15
|
removeInitialAndTrailingSlashes,
|
|
38
16
|
resolveCollection,
|
|
39
17
|
resolveDefaultSelectedView,
|
|
40
|
-
|
|
41
|
-
useDebouncedCallback
|
|
18
|
+
resolvedSelectedEntityView
|
|
42
19
|
} from "../util";
|
|
43
20
|
|
|
44
21
|
import {
|
|
45
|
-
saveEntityWithCallbacks,
|
|
46
22
|
useAuthController,
|
|
47
23
|
useCustomizationController,
|
|
48
|
-
useDataSource,
|
|
49
24
|
useEntityFetch,
|
|
50
25
|
useFireCMSContext,
|
|
51
|
-
|
|
52
|
-
useSnackbarController
|
|
26
|
+
useLargeLayout
|
|
53
27
|
} from "../hooks";
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const MAIN_TAB_VALUE = "main_##Q$SC^#S6";
|
|
28
|
+
import { CircularProgress, cls, CodeIcon, defaultBorderMixin, Tab, Tabs, Typography } from "@firecms/ui";
|
|
29
|
+
import { getEntityFromCache } from "../util/entity_cache";
|
|
30
|
+
import { EntityForm, EntityFormProps } from "../form";
|
|
31
|
+
import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
|
|
32
|
+
import { EntityJsonPreview } from "../components/EntityJsonPreview";
|
|
33
|
+
import { createFormexStub } from "../util/createFormexStub";
|
|
34
|
+
|
|
35
|
+
export const MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
|
|
36
|
+
export const JSON_TAB_VALUE = "__json";
|
|
37
|
+
|
|
38
|
+
export type OnUpdateParams = {
|
|
39
|
+
entity: Entity<any>,
|
|
40
|
+
status: EntityStatus,
|
|
41
|
+
path: string,
|
|
42
|
+
entityId?: string;
|
|
43
|
+
selectedTab?: string;
|
|
44
|
+
collection: EntityCollection<any>
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type OnTabChangeParams<M extends Record<string, any>> = {
|
|
48
|
+
path: string;
|
|
49
|
+
entityId?: string;
|
|
50
|
+
selectedTab?: string;
|
|
51
|
+
collection: EntityCollection<M>;
|
|
52
|
+
|
|
53
|
+
};
|
|
81
54
|
|
|
82
55
|
export interface EntityEditViewProps<M extends Record<string, any>> {
|
|
56
|
+
/**
|
|
57
|
+
* The database path of the entity, e.g. "users" or "products".
|
|
58
|
+
*/
|
|
83
59
|
path: string;
|
|
60
|
+
/**
|
|
61
|
+
* The navigation path to the entity.
|
|
62
|
+
*/
|
|
63
|
+
fullIdPath?: string;
|
|
84
64
|
collection: EntityCollection<M>;
|
|
85
65
|
entityId?: string;
|
|
66
|
+
databaseId?: string;
|
|
86
67
|
copy?: boolean;
|
|
87
|
-
|
|
68
|
+
selectedTab?: string;
|
|
88
69
|
parentCollectionIds: string[];
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
70
|
+
onValuesModified?: (modified: boolean) => void;
|
|
71
|
+
onSaved?: (params: OnUpdateParams) => void;
|
|
72
|
+
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
73
|
+
layout?: "side_panel" | "full_screen";
|
|
74
|
+
barActions?: React.ReactNode;
|
|
75
|
+
formProps?: Partial<EntityFormProps<M>>,
|
|
92
76
|
}
|
|
93
77
|
|
|
94
78
|
/**
|
|
95
79
|
* This is the default view that is used as the content of a side panel when
|
|
96
80
|
* an entity is opened.
|
|
97
|
-
* You probably don't want to use this view directly since it is bound to the
|
|
98
|
-
* side panel. Instead, you might want to use {@link EntityForm} or {@link EntityCollectionView}
|
|
99
81
|
*/
|
|
100
|
-
export function EntityEditView<M extends Record<string, any>,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
82
|
+
export function EntityEditView<M extends Record<string, any>, USER extends User>({
|
|
83
|
+
entityId,
|
|
84
|
+
...props
|
|
85
|
+
}: EntityEditViewProps<M>) {
|
|
86
|
+
|
|
104
87
|
const {
|
|
105
88
|
entity,
|
|
106
89
|
dataLoading,
|
|
107
90
|
// eslint-disable-next-line no-unused-vars
|
|
108
91
|
dataLoadingError
|
|
109
|
-
} = useEntityFetch<M,
|
|
92
|
+
} = useEntityFetch<M, USER>({
|
|
110
93
|
path: props.path,
|
|
111
94
|
entityId: entityId,
|
|
112
95
|
collection: props.collection,
|
|
96
|
+
databaseId: props.databaseId,
|
|
113
97
|
useCache: false
|
|
114
98
|
});
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
|
|
100
|
+
const cachedValues = entityId
|
|
101
|
+
? getEntityFromCache(props.path + "/" + entityId)
|
|
102
|
+
: getEntityFromCache(props.path + "#new");
|
|
103
|
+
|
|
104
|
+
const authController = useAuthController();
|
|
105
|
+
|
|
106
|
+
const initialStatus = props.copy ? "copy" : (entityId ? "existing" : "new");
|
|
107
|
+
const [status, setStatus] = useState<EntityStatus>(initialStatus);
|
|
108
|
+
|
|
109
|
+
const canEdit = useMemo(() => {
|
|
110
|
+
if (status === "new" || status === "copy") {
|
|
111
|
+
return true;
|
|
112
|
+
} else {
|
|
113
|
+
return entity ? canEditEntity(props.collection, authController, props.path, entity ?? null) : undefined;
|
|
114
|
+
}
|
|
115
|
+
}, [authController, entity, status]);
|
|
116
|
+
|
|
117
|
+
if ((dataLoading && !cachedValues) || (!entity || canEdit === undefined) && (status === "existing" || status === "copy")) {
|
|
118
|
+
return <CircularProgressCenter/>;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
if (entityId && !entity) {
|
|
121
|
-
console.error(`Entity with id ${entityId} not found in collection ${props.
|
|
121
|
+
if (entityId && !entity && !cachedValues) {
|
|
122
|
+
console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
return <EntityEditViewInner<M> {...props}
|
|
125
126
|
entityId={entityId}
|
|
126
127
|
entity={entity}
|
|
127
|
-
|
|
128
|
+
cachedDirtyValues={cachedValues as Partial<M>}
|
|
129
|
+
dataLoading={dataLoading}
|
|
130
|
+
status={status}
|
|
131
|
+
setStatus={setStatus}
|
|
132
|
+
canEdit={canEdit}
|
|
133
|
+
/>;
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
export function EntityEditViewInner<M extends Record<string, any>>({
|
|
131
137
|
path,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
fullIdPath,
|
|
139
|
+
entityId,
|
|
140
|
+
selectedTab: selectedTabProp,
|
|
135
141
|
collection,
|
|
136
142
|
parentCollectionIds,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
onValuesModified,
|
|
144
|
+
onSaved,
|
|
145
|
+
onTabChange,
|
|
140
146
|
entity,
|
|
147
|
+
cachedDirtyValues,
|
|
141
148
|
dataLoading,
|
|
149
|
+
layout = "side_panel",
|
|
150
|
+
barActions,
|
|
151
|
+
status,
|
|
152
|
+
setStatus,
|
|
153
|
+
formProps,
|
|
154
|
+
canEdit
|
|
142
155
|
}: EntityEditViewProps<M> & {
|
|
143
156
|
entity?: Entity<M>,
|
|
144
|
-
|
|
157
|
+
cachedDirtyValues?: Partial<M>, // dirty cached entity in memory
|
|
158
|
+
dataLoading: boolean,
|
|
159
|
+
status: EntityStatus,
|
|
160
|
+
setStatus: (status: EntityStatus) => void,
|
|
161
|
+
canEdit?: boolean,
|
|
145
162
|
}) {
|
|
146
163
|
|
|
147
|
-
if (collection.customId && collection.formAutoSave) {
|
|
148
|
-
console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const [saving, setSaving] = useState(false);
|
|
152
|
-
/**
|
|
153
|
-
* These are the values that are being saved. They are debounced.
|
|
154
|
-
* We use this only when autoSave is enabled.
|
|
155
|
-
*/
|
|
156
|
-
const [valuesToBeSaved, setValuesToBeSaved] = useState<EntityValues<M> | undefined>(undefined);
|
|
157
|
-
useDebouncedCallback(valuesToBeSaved, () => {
|
|
158
|
-
if (valuesToBeSaved)
|
|
159
|
-
saveEntity({
|
|
160
|
-
entityId: usedEntity?.id,
|
|
161
|
-
collection,
|
|
162
|
-
path,
|
|
163
|
-
values: valuesToBeSaved,
|
|
164
|
-
closeAfterSave: false
|
|
165
|
-
});
|
|
166
|
-
}, false, 2000);
|
|
167
|
-
|
|
168
|
-
// const largeLayout = useLargeLayout();
|
|
169
|
-
// const largeLayoutTabSelected = useRef(!largeLayout);
|
|
170
|
-
// const resolvedFormWidth: string = typeof formWidth === "number" ? `${formWidth}px` : formWidth ?? FORM_CONTAINER_WIDTH;
|
|
171
|
-
|
|
172
|
-
const inputCollection = collection;
|
|
173
|
-
|
|
174
|
-
const authController = useAuthController();
|
|
175
|
-
const dataSource = useDataSource(collection);
|
|
176
|
-
const sideDialogContext = useSideDialogContext();
|
|
177
|
-
const sideEntityController = useSideEntityController();
|
|
178
|
-
const snackbarController = useSnackbarController();
|
|
179
|
-
const customizationController = useCustomizationController();
|
|
180
164
|
const context = useFireCMSContext();
|
|
181
165
|
|
|
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]);
|
|
208
|
-
|
|
209
|
-
const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
|
|
210
|
-
|
|
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
|
-
// };
|
|
220
|
-
|
|
221
|
-
const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
|
|
222
|
-
const [savingError, setSavingError] = React.useState<Error | undefined>();
|
|
223
|
-
|
|
224
|
-
const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
|
|
225
|
-
|
|
226
|
-
const defaultSelectedView = selectedSubPathProp ?? resolveDefaultSelectedView(
|
|
227
|
-
collection ? collection.defaultSelectedView : undefined,
|
|
228
|
-
{
|
|
229
|
-
status,
|
|
230
|
-
entityId
|
|
231
|
-
}
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const selectedTabRef = useRef<string>(defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
235
|
-
const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, entity));
|
|
236
|
-
|
|
237
|
-
const mainViewVisible = selectedTabRef.current === MAIN_TAB_VALUE;
|
|
238
|
-
|
|
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;
|
|
252
|
-
|
|
253
166
|
const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
|
|
254
|
-
const [readOnly, setReadOnly] = useState<boolean | undefined>(undefined);
|
|
255
167
|
|
|
256
168
|
useEffect(() => {
|
|
257
169
|
if (entity)
|
|
258
170
|
setUsedEntity(entity);
|
|
259
171
|
}, [entity]);
|
|
260
172
|
|
|
261
|
-
|
|
262
|
-
if (status === "new") {
|
|
263
|
-
setReadOnly(false);
|
|
264
|
-
} else {
|
|
265
|
-
const editEnabled = usedEntity ? canEditEntity(collection, authController, path, usedEntity ?? null) : false;
|
|
266
|
-
if (usedEntity)
|
|
267
|
-
setReadOnly(!editEnabled);
|
|
268
|
-
}
|
|
269
|
-
}, [authController, usedEntity, status]);
|
|
270
|
-
|
|
271
|
-
const onPreSaveHookError = useCallback((e: Error) => {
|
|
272
|
-
setSaving(false);
|
|
273
|
-
snackbarController.open({
|
|
274
|
-
type: "error",
|
|
275
|
-
message: "Error before saving: " + e?.message
|
|
276
|
-
});
|
|
277
|
-
console.error(e);
|
|
278
|
-
}, [snackbarController]);
|
|
279
|
-
|
|
280
|
-
const onSaveSuccessHookError = useCallback((e: Error) => {
|
|
281
|
-
setSaving(false);
|
|
282
|
-
snackbarController.open({
|
|
283
|
-
type: "error",
|
|
284
|
-
message: "Error after saving (entity is saved): " + e?.message
|
|
285
|
-
});
|
|
286
|
-
console.error(e);
|
|
287
|
-
}, [snackbarController]);
|
|
288
|
-
|
|
289
|
-
const onSaveSuccess = (updatedEntity: Entity<M>, closeAfterSave: boolean) => {
|
|
290
|
-
|
|
291
|
-
setSaving(false);
|
|
292
|
-
if (!autoSave)
|
|
293
|
-
snackbarController.open({
|
|
294
|
-
type: "success",
|
|
295
|
-
message: `${collection.singularName ?? collection.name}: Saved correctly`
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
setUsedEntity(updatedEntity);
|
|
299
|
-
setStatus("existing");
|
|
300
|
-
|
|
301
|
-
onValuesAreModified(false);
|
|
302
|
-
|
|
303
|
-
if (onUpdate)
|
|
304
|
-
onUpdate({ entity: updatedEntity });
|
|
305
|
-
|
|
306
|
-
if (closeAfterSave) {
|
|
307
|
-
console.log("Closing side dialog")
|
|
308
|
-
sideDialogContext.setBlocked(false);
|
|
309
|
-
sideDialogContext.close(true);
|
|
310
|
-
onClose?.();
|
|
311
|
-
} else if (status !== "existing") {
|
|
312
|
-
sideEntityController.replace({
|
|
313
|
-
path,
|
|
314
|
-
entityId: updatedEntity.id,
|
|
315
|
-
selectedSubPath: selectedTabRef.current,
|
|
316
|
-
updateUrl: true,
|
|
317
|
-
collection,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
const onSaveFailure = useCallback((e: Error) => {
|
|
324
|
-
|
|
325
|
-
setSaving(false);
|
|
326
|
-
snackbarController.open({
|
|
327
|
-
type: "error",
|
|
328
|
-
message: "Error saving: " + e?.message
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
console.error("Error saving entity", path, entityId);
|
|
332
|
-
console.error(e);
|
|
333
|
-
}, [entityId, path, snackbarController]);
|
|
334
|
-
|
|
335
|
-
const saveEntity = ({
|
|
336
|
-
values,
|
|
337
|
-
previousValues,
|
|
338
|
-
closeAfterSave,
|
|
339
|
-
entityId,
|
|
340
|
-
collection,
|
|
341
|
-
path
|
|
342
|
-
}: {
|
|
343
|
-
collection: EntityCollection<M>,
|
|
344
|
-
path: string,
|
|
345
|
-
entityId: string | undefined,
|
|
346
|
-
values: M,
|
|
347
|
-
previousValues?: M,
|
|
348
|
-
closeAfterSave: boolean,
|
|
349
|
-
}) => {
|
|
350
|
-
setSaving(true);
|
|
351
|
-
return saveEntityWithCallbacks({
|
|
352
|
-
path,
|
|
353
|
-
entityId,
|
|
354
|
-
values,
|
|
355
|
-
previousValues,
|
|
356
|
-
collection,
|
|
357
|
-
status,
|
|
358
|
-
dataSource,
|
|
359
|
-
context,
|
|
360
|
-
onSaveSuccess: (updatedEntity: Entity<M>) => onSaveSuccess(updatedEntity, closeAfterSave),
|
|
361
|
-
onSaveFailure,
|
|
362
|
-
onPreSaveHookError,
|
|
363
|
-
onSaveSuccessHookError
|
|
364
|
-
}).then();
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
const onSaveEntityRequest = async ({
|
|
368
|
-
collection,
|
|
369
|
-
path,
|
|
370
|
-
entityId,
|
|
371
|
-
values,
|
|
372
|
-
previousValues,
|
|
373
|
-
closeAfterSave,
|
|
374
|
-
autoSave
|
|
375
|
-
}: EntityFormSaveParams<M>): Promise<void> => {
|
|
376
|
-
if (!status)
|
|
377
|
-
return;
|
|
378
|
-
|
|
379
|
-
if (autoSave) {
|
|
380
|
-
setValuesToBeSaved(values);
|
|
381
|
-
} else {
|
|
382
|
-
return saveEntity({
|
|
383
|
-
collection,
|
|
384
|
-
path,
|
|
385
|
-
entityId,
|
|
386
|
-
values,
|
|
387
|
-
previousValues,
|
|
388
|
-
closeAfterSave
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
};
|
|
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
|
-
|
|
494
|
-
const resolvedEntityViews = customViews ? customViews
|
|
495
|
-
.map(e => resolveEntityView(e, customizationController.entityViews))
|
|
496
|
-
.filter(Boolean) as EntityCustomView[]
|
|
497
|
-
: [];
|
|
498
|
-
|
|
499
|
-
const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTabRef.current);
|
|
500
|
-
const shouldShowEntityActions = !autoSave && (selectedTabRef.current === MAIN_TAB_VALUE || selectedEntityView?.includeActions);
|
|
501
|
-
|
|
502
|
-
const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
|
|
503
|
-
.map(
|
|
504
|
-
(customView, colIndex) => {
|
|
505
|
-
if (!customView)
|
|
506
|
-
return null;
|
|
507
|
-
if (selectedTabRef.current !== customView.key)
|
|
508
|
-
return null;
|
|
509
|
-
const Builder = customView.Builder;
|
|
510
|
-
if (!Builder) {
|
|
511
|
-
console.error("customView.Builder is not defined");
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
return <div
|
|
515
|
-
className={cls(defaultBorderMixin,
|
|
516
|
-
"relative flex-grow w-full h-full overflow-auto ")}
|
|
517
|
-
key={`custom_view_${customView.key}`}
|
|
518
|
-
role="tabpanel">
|
|
519
|
-
<ErrorBoundary>
|
|
520
|
-
{formContext && <Builder
|
|
521
|
-
collection={collection}
|
|
522
|
-
entity={usedEntity}
|
|
523
|
-
modifiedValues={formex.values ?? usedEntity?.values}
|
|
524
|
-
formContext={formContext}
|
|
525
|
-
/>}
|
|
526
|
-
</ErrorBoundary>
|
|
527
|
-
</div>;
|
|
528
|
-
}
|
|
529
|
-
).filter(Boolean);
|
|
530
|
-
|
|
531
|
-
const globalLoading = (dataLoading && !usedEntity) ||
|
|
532
|
-
((!usedEntity || readOnly === undefined) && (status === "existing" || status === "copy"));
|
|
533
|
-
|
|
534
|
-
const loading = globalLoading || saving;
|
|
535
|
-
|
|
536
|
-
const subCollectionsViews = subcollections && subcollections.map(
|
|
537
|
-
(subcollection, colIndex) => {
|
|
538
|
-
const subcollectionId = subcollection.id ?? subcollection.path;
|
|
539
|
-
const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
|
|
540
|
-
if (selectedTabRef.current !== subcollectionId)
|
|
541
|
-
return null;
|
|
542
|
-
return (
|
|
543
|
-
<div
|
|
544
|
-
className={"relative flex-grow h-full overflow-auto w-full"}
|
|
545
|
-
key={`subcol_${subcollectionId}`}
|
|
546
|
-
role="tabpanel">
|
|
547
|
-
|
|
548
|
-
{loading && <CircularProgressCenter/>}
|
|
549
|
-
|
|
550
|
-
{!globalLoading &&
|
|
551
|
-
(usedEntity && fullPath
|
|
552
|
-
? <EntityCollectionView
|
|
553
|
-
fullPath={fullPath}
|
|
554
|
-
parentCollectionIds={[...parentCollectionIds, collection.id]}
|
|
555
|
-
isSubCollection={true}
|
|
556
|
-
{...subcollection}/>
|
|
557
|
-
: <div
|
|
558
|
-
className="flex items-center justify-center w-full h-full p-3">
|
|
559
|
-
<Typography variant={"label"}>
|
|
560
|
-
You need to save your entity before
|
|
561
|
-
adding additional collections
|
|
562
|
-
</Typography>
|
|
563
|
-
</div>)
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
</div>
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
).filter(Boolean);
|
|
570
|
-
|
|
571
|
-
const onDiscard = useCallback(() => {
|
|
572
|
-
onValuesAreModified(false);
|
|
573
|
-
}, []);
|
|
574
|
-
|
|
575
|
-
const onSideTabClick = (value: string) => {
|
|
576
|
-
selectedTabRef.current = value;
|
|
577
|
-
sideEntityController.replace({
|
|
578
|
-
path,
|
|
579
|
-
entityId,
|
|
580
|
-
selectedSubPath: value === MAIN_TAB_VALUE ? undefined : value,
|
|
581
|
-
updateUrl: true,
|
|
582
|
-
collection,
|
|
583
|
-
});
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
const onIdUpdateError = useCallback((error: any) => {
|
|
587
|
-
snackbarController.open({
|
|
588
|
-
type: "error",
|
|
589
|
-
message: "Error updating id, check the console"
|
|
590
|
-
});
|
|
591
|
-
}, []);
|
|
173
|
+
const [formContext, setFormContext] = useState<FormContext<M> | undefined>(undefined);
|
|
592
174
|
|
|
593
|
-
const
|
|
594
|
-
setUsedEntity((prevEntity) => prevEntity
|
|
595
|
-
? {
|
|
596
|
-
...prevEntity,
|
|
597
|
-
id
|
|
598
|
-
}
|
|
599
|
-
: undefined);
|
|
600
|
-
}, []);
|
|
601
|
-
|
|
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[] = [];
|
|
175
|
+
const largeLayout = useLargeLayout();
|
|
627
176
|
|
|
177
|
+
const customizationController = useCustomizationController();
|
|
628
178
|
const plugins = customizationController.plugins;
|
|
179
|
+
const pluginActionsTop: React.ReactNode[] = [];
|
|
629
180
|
|
|
630
|
-
if (plugins &&
|
|
181
|
+
if (plugins && collection) {
|
|
631
182
|
const actionProps: PluginFormActionProps = {
|
|
632
183
|
entityId,
|
|
184
|
+
parentCollectionIds,
|
|
633
185
|
path,
|
|
634
186
|
status,
|
|
635
|
-
collection
|
|
187
|
+
collection,
|
|
636
188
|
context,
|
|
637
|
-
|
|
638
|
-
|
|
189
|
+
formContext,
|
|
190
|
+
openEntityMode: layout,
|
|
191
|
+
disabled: false
|
|
639
192
|
};
|
|
640
|
-
|
|
641
|
-
plugin.form?.
|
|
642
|
-
? <plugin.form.
|
|
643
|
-
key={`actions_${plugin.key}`} {...actionProps}/>
|
|
193
|
+
pluginActionsTop.push(...plugins.map((plugin) => (
|
|
194
|
+
plugin.form?.ActionsTop
|
|
195
|
+
? <plugin.form.ActionsTop
|
|
196
|
+
key={`actions_${plugin.key}`} {...actionProps} />
|
|
644
197
|
: null
|
|
645
198
|
)).filter(Boolean));
|
|
646
199
|
}
|
|
647
200
|
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
}
|
|
201
|
+
const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(
|
|
202
|
+
collection ? collection.defaultSelectedView : undefined,
|
|
203
|
+
{
|
|
204
|
+
status,
|
|
205
|
+
entityId
|
|
721
206
|
}
|
|
722
|
-
|
|
207
|
+
), []);
|
|
723
208
|
|
|
209
|
+
const [selectedTab, setSelectedTab] = useState<string>(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
724
210
|
useEffect(() => {
|
|
725
|
-
if (
|
|
726
|
-
|
|
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
|
-
});
|
|
211
|
+
if ((selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE) !== selectedTab) {
|
|
212
|
+
setSelectedTab(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
735
213
|
}
|
|
736
|
-
}, [
|
|
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"}>
|
|
214
|
+
}, [selectedTabProp]);
|
|
831
215
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
216
|
+
const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
|
|
217
|
+
const subcollectionsCount = subcollections?.length ?? 0;
|
|
218
|
+
const customViews = collection.entityViews ?? [];
|
|
219
|
+
const customViewsCount = customViews?.length ?? 0;
|
|
220
|
+
const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
|
|
221
|
+
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView;
|
|
838
222
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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
|
-
|
|
895
|
-
function buildForm() {
|
|
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>}
|
|
223
|
+
const {
|
|
224
|
+
resolvedEntityViews,
|
|
225
|
+
selectedEntityView,
|
|
226
|
+
selectedSecondaryForm
|
|
227
|
+
} = resolvedSelectedEntityView(customViews, customizationController, selectedTab, canEdit);
|
|
903
228
|
|
|
904
|
-
|
|
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}/>}
|
|
229
|
+
const actionsAtTheBottom = !largeLayout || layout === "side_panel" || selectedEntityView?.includeActions === "bottom";
|
|
925
230
|
|
|
926
|
-
|
|
927
|
-
<div className="mt-12 flex flex-col gap-8"
|
|
928
|
-
ref={formRef}>
|
|
231
|
+
const mainViewVisible = selectedTab === MAIN_TAB_VALUE || Boolean(selectedSecondaryForm);
|
|
929
232
|
|
|
930
|
-
|
|
233
|
+
const authController = useAuthController();
|
|
931
234
|
|
|
932
|
-
|
|
235
|
+
const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
|
|
236
|
+
.filter(e => !e.includeActions)
|
|
237
|
+
.map((customView) => {
|
|
933
238
|
|
|
934
|
-
|
|
239
|
+
if (!customView)
|
|
240
|
+
return null;
|
|
241
|
+
const Builder = customView.Builder;
|
|
242
|
+
if (!Builder) {
|
|
243
|
+
console.error("INTERNAL: customView.Builder is not defined");
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
935
246
|
|
|
936
|
-
|
|
247
|
+
if (!entityId) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
937
250
|
|
|
938
|
-
|
|
251
|
+
const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
|
|
252
|
+
const usedFormContext: FormContext = formContext ?? {
|
|
253
|
+
entityId,
|
|
254
|
+
disabled: false,
|
|
255
|
+
openEntityMode: layout,
|
|
256
|
+
status: status,
|
|
257
|
+
values: usedEntity?.values ?? {},
|
|
258
|
+
setFieldValue: (key: string, value: any) => {
|
|
259
|
+
throw new Error("You can't update values in read only mode");
|
|
260
|
+
},
|
|
261
|
+
save: () => {
|
|
262
|
+
throw new Error("You can't save in read only mode");
|
|
263
|
+
},
|
|
264
|
+
collection: resolveCollection<M>({
|
|
265
|
+
collection,
|
|
266
|
+
path,
|
|
267
|
+
entityId,
|
|
268
|
+
values: usedEntity?.values ?? {},
|
|
269
|
+
previousValues: usedEntity?.values ?? {},
|
|
270
|
+
propertyConfigs: customizationController.propertyConfigs,
|
|
271
|
+
authController
|
|
272
|
+
}),
|
|
273
|
+
path,
|
|
274
|
+
entity: usedEntity,
|
|
275
|
+
savingError: undefined,
|
|
276
|
+
formex: formexStub
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return <div
|
|
280
|
+
className={cls(defaultBorderMixin,
|
|
281
|
+
"relative flex-1 w-full h-full overflow-auto",
|
|
282
|
+
{ "hidden": selectedTab !== customView.key }
|
|
283
|
+
)}
|
|
284
|
+
key={`custom_view_${customView.key}`}
|
|
285
|
+
role="tabpanel">
|
|
286
|
+
<ErrorBoundary>
|
|
287
|
+
{usedFormContext && <Builder
|
|
288
|
+
collection={collection}
|
|
289
|
+
parentCollectionIds={parentCollectionIds}
|
|
290
|
+
entity={usedEntity}
|
|
291
|
+
modifiedValues={usedFormContext?.formex?.values ?? usedEntity?.values}
|
|
292
|
+
formContext={usedFormContext}
|
|
293
|
+
/>}
|
|
294
|
+
</ErrorBoundary>
|
|
295
|
+
</div>;
|
|
296
|
+
}).filter(Boolean);
|
|
297
|
+
|
|
298
|
+
const globalLoading = dataLoading && !usedEntity;
|
|
299
|
+
|
|
300
|
+
const jsonView = <div
|
|
301
|
+
className={cls("relative flex-1 h-full overflow-auto w-full",
|
|
302
|
+
{ "hidden": selectedTab !== JSON_TAB_VALUE })}
|
|
303
|
+
key={"json_view"}
|
|
304
|
+
role="tabpanel">
|
|
305
|
+
<ErrorBoundary>
|
|
306
|
+
<EntityJsonPreview
|
|
307
|
+
values={formContext?.values ?? entity?.values ?? {}}/>
|
|
308
|
+
</ErrorBoundary>
|
|
309
|
+
</div>;
|
|
310
|
+
|
|
311
|
+
const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
|
|
312
|
+
const subcollectionId = subcollection.id ?? subcollection.path;
|
|
313
|
+
const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollection.path)}` : undefined;
|
|
314
|
+
const newFullIdPath = fullIdPath ? `${fullIdPath}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
|
|
315
|
+
|
|
316
|
+
if (selectedTab !== subcollectionId) return null;
|
|
317
|
+
return (
|
|
318
|
+
<div
|
|
319
|
+
className={"relative flex-1 h-full overflow-auto w-full"}
|
|
320
|
+
key={`subcol_${subcollectionId}`}
|
|
321
|
+
role="tabpanel">
|
|
322
|
+
|
|
323
|
+
{globalLoading && <CircularProgressCenter/>}
|
|
324
|
+
|
|
325
|
+
{!globalLoading &&
|
|
326
|
+
(usedEntity && newFullPath
|
|
327
|
+
? <EntityCollectionView
|
|
328
|
+
fullPath={newFullPath}
|
|
329
|
+
fullIdPath={newFullIdPath}
|
|
330
|
+
parentCollectionIds={[...parentCollectionIds, collection.id]}
|
|
331
|
+
isSubCollection={true}
|
|
332
|
+
updateUrl={false}
|
|
333
|
+
{...subcollection}
|
|
334
|
+
openEntityMode={layout}/>
|
|
335
|
+
: <div className="flex items-center justify-center w-full h-full p-3">
|
|
336
|
+
<Typography variant={"label"}>
|
|
337
|
+
You need to save your entity before
|
|
338
|
+
adding additional collections
|
|
339
|
+
</Typography>
|
|
340
|
+
</div>)
|
|
341
|
+
}
|
|
939
342
|
|
|
940
343
|
</div>
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
entity={usedEntity}
|
|
953
|
-
context={context}
|
|
954
|
-
formContext={formContext}
|
|
955
|
-
{...plugin.form.provider.props}>
|
|
956
|
-
{form}
|
|
957
|
-
</plugin.form.provider.Component>
|
|
958
|
-
);
|
|
959
|
-
}
|
|
344
|
+
);
|
|
345
|
+
}).filter(Boolean);
|
|
346
|
+
|
|
347
|
+
const onSideTabClick = (value: string) => {
|
|
348
|
+
setSelectedTab(value);
|
|
349
|
+
if (status === "existing") {
|
|
350
|
+
onTabChange?.({
|
|
351
|
+
path: fullIdPath ?? path,
|
|
352
|
+
entityId,
|
|
353
|
+
selectedTab: value === MAIN_TAB_VALUE ? undefined : value,
|
|
354
|
+
collection
|
|
960
355
|
});
|
|
961
356
|
}
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
const entityView = (readOnly === undefined)
|
|
966
|
-
? <></>
|
|
967
|
-
: (!readOnly
|
|
968
|
-
? buildForm()
|
|
969
|
-
: (
|
|
970
|
-
<>
|
|
971
|
-
<Typography
|
|
972
|
-
className={"mt-16 mb-8 mx-8"}
|
|
973
|
-
variant={"h4"}>{collection.singularName ?? collection.name}
|
|
974
|
-
</Typography>
|
|
975
|
-
<EntityView
|
|
976
|
-
className={"px-12"}
|
|
977
|
-
entity={usedEntity as Entity<M>}
|
|
978
|
-
path={path}
|
|
979
|
-
collection={collection}/>
|
|
357
|
+
};
|
|
980
358
|
|
|
981
|
-
|
|
982
|
-
|
|
359
|
+
const entityReadOnlyView = !canEdit && entity ? <div
|
|
360
|
+
className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", (canEdit || !mainViewVisible || selectedSecondaryForm) ? "hidden" : "")}>
|
|
361
|
+
<div
|
|
362
|
+
className={cls("relative flex flex-col max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit")}>
|
|
363
|
+
<Typography className={"mt-16 mb-8 mx-8"} variant={"h4"}>
|
|
364
|
+
{collection.singularName ?? collection.name}
|
|
365
|
+
</Typography>
|
|
366
|
+
<EntityView
|
|
367
|
+
className={"px-8 h-full overflow-auto"}
|
|
368
|
+
entity={entity}
|
|
369
|
+
path={path}
|
|
370
|
+
collection={collection}/>
|
|
371
|
+
<div className="h-16"/>
|
|
372
|
+
</div>
|
|
373
|
+
</div> : null;
|
|
374
|
+
|
|
375
|
+
const entityView = <EntityForm<M>
|
|
376
|
+
fullIdPath={fullIdPath}
|
|
377
|
+
collection={collection}
|
|
378
|
+
path={path}
|
|
379
|
+
entityId={entityId ?? usedEntity?.id}
|
|
380
|
+
onValuesModified={onValuesModified}
|
|
381
|
+
entity={entity}
|
|
382
|
+
initialDirtyValues={cachedDirtyValues}
|
|
383
|
+
openEntityMode={layout}
|
|
384
|
+
forceActionsAtTheBottom={actionsAtTheBottom}
|
|
385
|
+
initialStatus={status}
|
|
386
|
+
className={cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className)}
|
|
387
|
+
EntityFormActionsComponent={EntityEditViewFormActions}
|
|
388
|
+
disabled={!canEdit}
|
|
389
|
+
{...formProps}
|
|
390
|
+
onEntityChange={(entity) => {
|
|
391
|
+
setUsedEntity(entity);
|
|
392
|
+
formProps?.onEntityChange?.(entity);
|
|
393
|
+
}}
|
|
394
|
+
onStatusChange={(status) => {
|
|
395
|
+
setStatus(status);
|
|
396
|
+
formProps?.onStatusChange?.(status);
|
|
397
|
+
}}
|
|
398
|
+
onFormContextReady={(formContext) => {
|
|
399
|
+
setFormContext(formContext);
|
|
400
|
+
formProps?.onFormContextReady?.(formContext);
|
|
401
|
+
}}
|
|
402
|
+
onSaved={(params) => {
|
|
403
|
+
const res = {
|
|
404
|
+
...params,
|
|
405
|
+
selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab
|
|
406
|
+
};
|
|
407
|
+
onSaved?.(res);
|
|
408
|
+
formProps?.onSaved?.(res);
|
|
409
|
+
}}
|
|
410
|
+
Builder={selectedSecondaryForm?.Builder}
|
|
411
|
+
/>;
|
|
412
|
+
|
|
413
|
+
const subcollectionTabs = subcollections && subcollections.map((subcollection) =>
|
|
414
|
+
<Tab
|
|
415
|
+
className="text-sm min-w-[120px]"
|
|
416
|
+
value={subcollection.id}
|
|
417
|
+
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
418
|
+
{subcollection.name}
|
|
419
|
+
</Tab>
|
|
420
|
+
);
|
|
983
421
|
|
|
984
|
-
const
|
|
985
|
-
(
|
|
422
|
+
const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start")
|
|
423
|
+
.map((view) =>
|
|
986
424
|
<Tab
|
|
987
|
-
className="text-sm min-w-[
|
|
988
|
-
value={
|
|
989
|
-
key={`entity_detail_collection_tab_${
|
|
990
|
-
{
|
|
425
|
+
className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
|
|
426
|
+
value={view.key}
|
|
427
|
+
key={`entity_detail_collection_tab_${view.name}`}>
|
|
428
|
+
{view.tabComponent ?? view.name}
|
|
991
429
|
</Tab>
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
(view) =>
|
|
996
|
-
|
|
430
|
+
);
|
|
431
|
+
const customViewTabsEnd = resolvedEntityViews.filter(view => !view.position || view.position === "end")
|
|
432
|
+
.map((view) =>
|
|
997
433
|
<Tab
|
|
998
|
-
className="text-sm min-w-[
|
|
434
|
+
className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
|
|
999
435
|
value={view.key}
|
|
1000
436
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
1001
|
-
{view.name}
|
|
437
|
+
{view.tabComponent ?? view.name}
|
|
1002
438
|
</Tab>
|
|
1003
|
-
|
|
439
|
+
);
|
|
1004
440
|
|
|
1005
|
-
|
|
1006
|
-
if (entityId && onIdChange)
|
|
1007
|
-
onIdChange(entityId);
|
|
1008
|
-
}, [entityId, onIdChange]);
|
|
1009
|
-
|
|
1010
|
-
return (
|
|
1011
|
-
<Formex value={formex}>
|
|
441
|
+
const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
|
|
1012
442
|
|
|
1013
|
-
|
|
443
|
+
let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-900">
|
|
1014
444
|
|
|
1015
|
-
|
|
1016
|
-
|
|
445
|
+
{shouldShowTopBar && <div
|
|
446
|
+
className={cls("h-14 items-center flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
|
|
1017
447
|
|
|
1018
|
-
|
|
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>
|
|
448
|
+
{barActions}
|
|
1028
449
|
|
|
1029
|
-
|
|
450
|
+
<div className={"flex-grow"}/>
|
|
1030
451
|
|
|
1031
|
-
|
|
1032
|
-
className="self-center">
|
|
1033
|
-
<CircularProgress size={"small"}/>
|
|
1034
|
-
</div>}
|
|
452
|
+
{pluginActionsTop}
|
|
1035
453
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
onSideTabClick(value);
|
|
1040
|
-
}}
|
|
1041
|
-
className="pl-4 pr-4 pt-0">
|
|
454
|
+
{globalLoading && <div className="self-center">
|
|
455
|
+
<CircularProgress size={"small"}/>
|
|
456
|
+
</div>}
|
|
1042
457
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
>{collection.singularName ?? collection.name}</Tab>
|
|
458
|
+
{hasAdditionalViews && <Tabs
|
|
459
|
+
className={"self-end"}
|
|
460
|
+
value={selectedTab}
|
|
461
|
+
onValueChange={(value) => {
|
|
462
|
+
onSideTabClick(value);
|
|
463
|
+
}}>
|
|
1050
464
|
|
|
1051
|
-
|
|
465
|
+
{includeJsonView && <Tab
|
|
466
|
+
disabled={!hasAdditionalViews}
|
|
467
|
+
value={JSON_TAB_VALUE}
|
|
468
|
+
className={"text-sm"}>
|
|
469
|
+
<CodeIcon size={"small"}/>
|
|
470
|
+
</Tab>}
|
|
1052
471
|
|
|
1053
|
-
|
|
1054
|
-
</Tabs>
|
|
472
|
+
{customViewTabsStart}
|
|
1055
473
|
|
|
1056
|
-
|
|
474
|
+
<Tab
|
|
475
|
+
disabled={!hasAdditionalViews}
|
|
476
|
+
value={MAIN_TAB_VALUE}
|
|
477
|
+
className={"text-sm min-w-[120px]"}>
|
|
478
|
+
{collection.singularName ?? collection.name}
|
|
479
|
+
</Tab>
|
|
1057
480
|
|
|
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
481
|
|
|
1067
|
-
|
|
1068
|
-
role="tabpanel"
|
|
1069
|
-
hidden={!mainViewVisible}
|
|
1070
|
-
id={`form_${path}`}
|
|
1071
|
-
className={" w-full"}>
|
|
482
|
+
{customViewTabsEnd}
|
|
1072
483
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
484
|
+
{subcollectionTabs}
|
|
485
|
+
</Tabs>}
|
|
486
|
+
</div>}
|
|
1076
487
|
|
|
1077
|
-
|
|
488
|
+
{globalLoading
|
|
489
|
+
? <div className="w-full pt-12 pb-16 px-4 sm:px-8 md:px-10">
|
|
490
|
+
<CircularProgressCenter/>
|
|
491
|
+
</div>
|
|
492
|
+
: <>
|
|
493
|
+
{entityReadOnlyView}
|
|
494
|
+
{entityView}
|
|
495
|
+
</>}
|
|
1078
496
|
|
|
1079
|
-
|
|
497
|
+
{jsonView}
|
|
1080
498
|
|
|
1081
|
-
|
|
499
|
+
{customViewsView}
|
|
1082
500
|
|
|
1083
|
-
|
|
501
|
+
{subCollectionsViews}
|
|
1084
502
|
|
|
1085
|
-
|
|
503
|
+
</div>;
|
|
1086
504
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
status,
|
|
1104
|
-
entity
|
|
505
|
+
if (plugins) {
|
|
506
|
+
plugins.forEach((plugin: FireCMSPlugin) => {
|
|
507
|
+
if (plugin.form?.provider) {
|
|
508
|
+
result = (
|
|
509
|
+
<plugin.form.provider.Component
|
|
510
|
+
status={status}
|
|
511
|
+
path={path}
|
|
512
|
+
collection={collection}
|
|
513
|
+
entity={usedEntity}
|
|
514
|
+
context={context}
|
|
515
|
+
formContext={formContext}
|
|
516
|
+
{...plugin.form.provider.props}>
|
|
517
|
+
{result}
|
|
518
|
+
</plugin.form.provider.Component>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
1105
521
|
});
|
|
1106
|
-
throw new Error("Form has not been initialised with the correct parameters");
|
|
1107
522
|
}
|
|
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
523
|
|
|
1120
|
-
|
|
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;
|
|
524
|
+
return result;
|
|
1133
525
|
}
|
|
526
|
+
|