@firecms/core 3.0.0-canary.17 → 3.0.0-canary.170
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 +3 -3
- package/dist/app/AppBar.d.ts +12 -0
- package/dist/app/Drawer.d.ts +16 -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 +23 -6
- package/dist/components/CircularProgressCenter.d.ts +1 -1
- package/dist/components/ClearFilterSortButton.d.ts +5 -0
- package/dist/components/{DeleteConfirmationDialog.d.ts → ConfirmationDialog.d.ts} +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +13 -13
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +22 -6
- 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/index.d.ts +1 -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 +7 -4
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +16 -2
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
- package/dist/components/EntityCollectionView/utils.d.ts +3 -0
- package/dist/components/EntityPreview.d.ts +7 -6
- 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/PropertyConfigBadge.d.ts +2 -1
- package/dist/components/PropertyIdCopyTooltip.d.ts +8 -0
- package/dist/components/ReferenceWidget.d.ts +3 -1
- package/dist/components/SelectableTable/SelectableTable.d.ts +13 -3
- package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +28 -12
- package/dist/components/VirtualTable/types.d.ts +3 -3
- package/dist/components/{EntityCollectionTable/internal → common}/default_entity_actions.d.ts +1 -3
- package/dist/components/common/index.d.ts +2 -1
- 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 +3 -1
- package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +13 -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 +4 -2
- package/dist/contexts/BreacrumbsContext.d.ts +8 -0
- 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 +41 -9
- package/dist/core/FireCMS.d.ts +2 -2
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +2 -3
- package/dist/core/index.d.ts +4 -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/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 -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/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 +3 -5
- 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 +3 -5
- package/dist/form/useClearRestoreValue.d.ts +2 -2
- package/dist/hooks/data/delete.d.ts +4 -4
- package/dist/hooks/data/save.d.ts +4 -5
- package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
- package/dist/hooks/data/useEntityFetch.d.ts +4 -3
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useAuthController.d.ts +1 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +3 -5
- package/dist/hooks/useFireCMSContext.d.ts +1 -1
- package/dist/hooks/useProjectLog.d.ts +8 -2
- package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
- package/dist/hooks/useValidateAuthenticator.d.ts +5 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +21352 -14215
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +23817 -588
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildDataSource.d.ts +1 -16
- package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
- 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/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/auth.d.ts +8 -10
- package/dist/types/collections.d.ts +69 -22
- package/dist/types/datasource.d.ts +52 -36
- package/dist/types/dialogs_controller.d.ts +7 -3
- package/dist/types/entities.d.ts +7 -2
- package/dist/types/entity_actions.d.ts +28 -4
- 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 +63 -41
- package/dist/types/firecms.d.ts +6 -2
- package/dist/types/firecms_context.d.ts +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/navigation.d.ts +34 -17
- package/dist/types/permissions.d.ts +4 -4
- package/dist/types/plugins.d.ts +23 -12
- package/dist/types/properties.d.ts +34 -21
- package/dist/types/property_config.d.ts +0 -2
- package/dist/types/side_dialogs_controller.d.ts +10 -0
- package/dist/types/side_entity_controller.d.ts +1 -1
- package/dist/types/storage.d.ts +86 -3
- package/dist/types/user.d.ts +1 -0
- package/dist/util/builders.d.ts +2 -2
- package/dist/util/entities.d.ts +3 -3
- package/dist/util/entity_actions.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/icon_list.d.ts +5 -1
- package/dist/util/icon_synonyms.d.ts +1 -98
- package/dist/util/icons.d.ts +6 -3
- package/dist/util/index.d.ts +2 -0
- package/dist/util/navigation_from_path.d.ts +5 -1
- package/dist/util/navigation_utils.d.ts +14 -3
- package/dist/util/objects.d.ts +2 -1
- package/dist/util/permissions.d.ts +4 -4
- package/dist/util/plurals.d.ts +0 -2
- package/dist/util/property_utils.d.ts +3 -3
- package/dist/util/resolutions.d.ts +30 -16
- package/dist/util/storage.d.ts +23 -2
- package/dist/util/useStorageUploadController.d.ts +3 -3
- package/package.json +58 -45
- package/src/app/AppBar.tsx +18 -0
- package/src/app/Drawer.tsx +24 -0
- package/src/app/Scaffold.tsx +243 -0
- package/src/app/index.ts +4 -0
- package/src/app/useApp.tsx +32 -0
- package/src/components/ArrayContainer.tsx +126 -40
- package/src/components/CircularProgressCenter.tsx +2 -2
- package/src/components/ClearFilterSortButton.tsx +41 -0
- package/src/components/{DeleteConfirmationDialog.tsx → ConfirmationDialog.tsx} +3 -3
- package/src/components/DeleteEntityDialog.tsx +10 -19
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +54 -37
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +38 -31
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +30 -9
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +47 -39
- package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +31 -17
- package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +19 -17
- package/src/components/EntityCollectionTable/index.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +31 -36
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +36 -34
- package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +20 -8
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +84 -77
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +206 -112
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
- package/src/components/EntityCollectionView/useSelectionController.tsx +20 -7
- package/src/components/EntityCollectionView/utils.ts +19 -0
- package/src/components/EntityPreview.tsx +66 -53
- package/src/components/EntityView.tsx +10 -10
- package/src/components/ErrorView.tsx +4 -4
- package/src/components/HomePage/DefaultHomePage.tsx +26 -19
- package/src/components/HomePage/FavouritesView.tsx +6 -11
- package/src/components/HomePage/NavigationCard.tsx +4 -4
- package/src/components/HomePage/NavigationCardBinding.tsx +5 -10
- package/src/components/HomePage/NavigationGroup.tsx +2 -2
- package/src/components/HomePage/SmallNavigationCard.tsx +5 -6
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/PropertyConfigBadge.tsx +9 -3
- package/src/components/PropertyIdCopyTooltip.tsx +48 -0
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +17 -12
- package/src/components/ReferenceWidget.tsx +22 -12
- package/src/components/SearchIconsView.tsx +10 -7
- package/src/components/SelectableTable/SelectableTable.tsx +27 -12
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +25 -8
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +30 -6
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +92 -23
- package/src/components/UnsavedChangesDialog.tsx +42 -0
- package/src/components/VirtualTable/VirtualTable.tsx +96 -48
- package/src/components/VirtualTable/VirtualTableCell.tsx +1 -9
- package/src/components/VirtualTable/VirtualTableHeader.tsx +10 -10
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
- package/src/components/VirtualTable/VirtualTableProps.tsx +33 -14
- package/src/components/VirtualTable/VirtualTableRow.tsx +5 -6
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +5 -5
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +3 -2
- package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +16 -28
- package/src/components/VirtualTable/types.tsx +2 -3
- package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +33 -40
- package/src/components/common/index.ts +2 -1
- 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 +24 -3
- package/src/components/common/{useDataSourceEntityCollectionTableController.tsx → useDataSourceTableController.tsx} +142 -22
- package/src/components/common/useDebouncedCallback.tsx +20 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/components/common/useTableSearchHelper.ts +53 -12
- package/src/components/index.tsx +4 -2
- package/src/contexts/BreacrumbsContext.tsx +38 -0
- package/src/contexts/DialogsProvider.tsx +5 -4
- package/src/contexts/SnackbarProvider.tsx +2 -0
- package/src/core/DefaultAppBar.tsx +201 -0
- package/src/core/DefaultDrawer.tsx +185 -0
- package/src/core/DrawerNavigationItem.tsx +66 -0
- package/src/core/EntityEditView.tsx +933 -204
- package/src/core/EntitySidePanel.tsx +73 -8
- package/src/core/FireCMS.tsx +56 -49
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +20 -34
- package/src/core/SideDialogs.tsx +12 -6
- package/src/core/field_configs.tsx +11 -12
- package/src/core/index.tsx +6 -5
- package/src/form/PropertyFieldBinding.tsx +74 -35
- package/src/form/components/CustomIdField.tsx +6 -2
- package/src/form/components/FieldHelperText.tsx +4 -4
- package/src/form/components/LabelWithIcon.tsx +27 -19
- package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
- package/src/form/components/StorageItemPreview.tsx +22 -12
- package/src/form/components/StorageUploadProgress.tsx +3 -4
- package/src/form/components/index.tsx +1 -1
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +29 -17
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +45 -35
- package/src/form/field_bindings/BlockFieldBinding.tsx +53 -33
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -14
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +60 -51
- package/src/form/field_bindings/MapFieldBinding.tsx +67 -48
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +150 -0
- package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +25 -20
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +11 -16
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +42 -31
- package/src/form/field_bindings/RepeatFieldBinding.tsx +52 -34
- package/src/form/field_bindings/SelectFieldBinding.tsx +22 -14
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +37 -47
- package/src/form/field_bindings/SwitchFieldBinding.tsx +28 -24
- package/src/form/field_bindings/TextFieldBinding.tsx +27 -24
- package/src/form/index.tsx +4 -9
- package/src/form/useClearRestoreValue.tsx +2 -2
- package/src/form/validation.ts +1 -17
- package/src/hooks/data/delete.ts +6 -5
- package/src/hooks/data/save.ts +13 -9
- package/src/hooks/data/useCollectionFetch.tsx +3 -3
- package/src/hooks/data/useDataSource.tsx +11 -3
- package/src/hooks/data/useEntityFetch.tsx +10 -6
- package/src/hooks/index.tsx +1 -0
- package/src/hooks/useAuthController.tsx +1 -1
- package/src/hooks/useBreadcrumbsController.tsx +31 -0
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +8 -10
- package/src/hooks/useBuildModeController.tsx +11 -5
- package/src/hooks/useBuildNavigationController.tsx +153 -55
- package/src/hooks/useFireCMSContext.tsx +5 -5
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useProjectLog.tsx +32 -10
- package/src/hooks/useResolvedNavigationFrom.tsx +10 -12
- package/src/hooks/useValidateAuthenticator.tsx +21 -41
- package/src/index.ts +1 -0
- package/src/internal/useBuildDataSource.ts +72 -82
- package/src/internal/useBuildSideDialogsController.tsx +4 -2
- package/src/internal/useBuildSideEntityController.tsx +230 -70
- package/src/internal/useUnsavedChangesDialog.tsx +127 -91
- package/src/preview/PropertyPreview.tsx +13 -20
- package/src/preview/PropertyPreviewProps.tsx +7 -1
- package/src/preview/components/BooleanPreview.tsx +2 -2
- package/src/preview/components/EmptyValue.tsx +1 -1
- package/src/preview/components/EnumValuesChip.tsx +2 -2
- package/src/preview/components/ImagePreview.tsx +25 -36
- 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 +2 -3
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +2 -3
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +1 -2
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -5
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -5
- package/src/preview/property_previews/MapPropertyPreview.tsx +9 -9
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +12 -12
- package/src/preview/property_previews/StringPropertyPreview.tsx +3 -3
- package/src/preview/util.ts +10 -10
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +230 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/auth.tsx +9 -13
- package/src/types/collections.ts +79 -24
- package/src/types/customization_controller.tsx +0 -1
- package/src/types/datasource.ts +61 -43
- package/src/types/dialogs_controller.tsx +7 -3
- package/src/types/entities.ts +12 -2
- package/src/types/entity_actions.tsx +32 -7
- 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 +69 -45
- package/src/types/firecms.tsx +7 -2
- package/src/types/firecms_context.tsx +1 -1
- package/src/types/index.ts +0 -1
- package/src/types/navigation.ts +40 -21
- package/src/types/permissions.ts +5 -5
- package/src/types/plugins.tsx +25 -14
- package/src/types/properties.ts +39 -22
- package/src/types/property_config.tsx +0 -2
- package/src/types/side_dialogs_controller.tsx +13 -0
- package/src/types/side_entity_controller.tsx +1 -1
- package/src/types/storage.ts +94 -3
- package/src/types/user.ts +2 -0
- package/src/util/builders.ts +6 -6
- package/src/util/entities.ts +9 -6
- package/src/util/entity_actions.ts +28 -0
- package/src/util/entity_cache.ts +196 -0
- package/src/util/enums.ts +1 -1
- package/src/util/icon_list.ts +16 -10
- package/src/util/icon_synonyms.ts +3 -100
- package/src/util/icons.tsx +17 -7
- package/src/util/index.ts +2 -0
- package/src/util/make_properties_editable.ts +13 -5
- package/src/util/navigation_from_path.ts +10 -2
- package/src/util/navigation_utils.ts +59 -7
- package/src/util/objects.ts +45 -28
- package/src/util/parent_references_from_path.ts +3 -3
- package/src/util/permissions.ts +9 -8
- package/src/util/plurals.ts +0 -2
- package/src/util/property_utils.tsx +11 -4
- package/src/util/references.ts +11 -2
- package/src/util/resolutions.ts +79 -46
- package/src/util/storage.ts +79 -21
- package/src/util/strings.ts +2 -2
- package/src/util/useStorageUploadController.tsx +34 -8
- 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/components/FormikArrayContainer.d.ts +0 -18
- 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/FireCMSAppBar.tsx +0 -165
- 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/components/FormikArrayContainer.tsx +0 -44
- 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
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -1,18 +1,44 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
1
|
+
import React, { useCallback, useDeferredValue, 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,
|
|
10
|
+
FireCMSContext,
|
|
8
11
|
FireCMSPlugin,
|
|
9
12
|
FormContext,
|
|
13
|
+
PluginFormActionProps,
|
|
14
|
+
PropertyFieldBindingProps,
|
|
15
|
+
ResolvedEntityCollection,
|
|
16
|
+
SideEntityController,
|
|
10
17
|
User
|
|
11
18
|
} from "../types";
|
|
12
|
-
import
|
|
19
|
+
import equal from "react-fast-compare"
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
CircularProgressCenter,
|
|
23
|
+
copyEntityAction,
|
|
24
|
+
deleteEntityAction,
|
|
25
|
+
EntityCollectionView,
|
|
26
|
+
EntityView,
|
|
27
|
+
ErrorBoundary,
|
|
28
|
+
getFormFieldKeys
|
|
29
|
+
} from "../components";
|
|
13
30
|
import {
|
|
31
|
+
canCreateEntity,
|
|
32
|
+
canDeleteEntity,
|
|
14
33
|
canEditEntity,
|
|
34
|
+
getDefaultValuesFor,
|
|
35
|
+
getEntityTitlePropertyKey,
|
|
36
|
+
getValueInPath,
|
|
37
|
+
isHidden,
|
|
38
|
+
isReadOnly,
|
|
39
|
+
mergeEntityActions,
|
|
15
40
|
removeInitialAndTrailingSlashes,
|
|
41
|
+
resolveCollection,
|
|
16
42
|
resolveDefaultSelectedView,
|
|
17
43
|
resolveEntityView,
|
|
18
44
|
useDebouncedCallback
|
|
@@ -25,53 +51,142 @@ import {
|
|
|
25
51
|
useDataSource,
|
|
26
52
|
useEntityFetch,
|
|
27
53
|
useFireCMSContext,
|
|
54
|
+
useLargeLayout,
|
|
28
55
|
useSideEntityController,
|
|
29
56
|
useSnackbarController
|
|
30
57
|
} from "../hooks";
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
import {
|
|
59
|
+
Alert,
|
|
60
|
+
Button,
|
|
61
|
+
CheckIcon,
|
|
62
|
+
Chip,
|
|
63
|
+
CircularProgress,
|
|
64
|
+
cls,
|
|
65
|
+
defaultBorderMixin,
|
|
66
|
+
DialogActions,
|
|
67
|
+
EditIcon,
|
|
68
|
+
IconButton,
|
|
69
|
+
LoadingButton,
|
|
70
|
+
NotesIcon,
|
|
71
|
+
paperMixin,
|
|
72
|
+
Tab,
|
|
73
|
+
Tabs,
|
|
74
|
+
Tooltip,
|
|
75
|
+
Typography
|
|
76
|
+
} from "@firecms/ui";
|
|
77
|
+
import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
|
|
78
|
+
import { useAnalyticsController } from "../hooks/useAnalyticsController";
|
|
79
|
+
import { CustomIdField } from "../form/components/CustomIdField";
|
|
80
|
+
import { CustomFieldValidator, getYupEntitySchema } from "../form/validation";
|
|
81
|
+
import { ErrorFocus } from "../form/components/ErrorFocus";
|
|
82
|
+
import { LabelWithIconAndTooltip, PropertyFieldBinding } from "../form";
|
|
83
|
+
import { ValidationError } from "yup";
|
|
84
|
+
import { getEntityFromCache, removeEntityFromCache, saveEntityToCache } from "../util/entity_cache";
|
|
36
85
|
|
|
37
86
|
const MAIN_TAB_VALUE = "main_##Q$SC^#S6";
|
|
38
87
|
|
|
88
|
+
export type OnUpdateParams = {
|
|
89
|
+
entity: Entity<any>,
|
|
90
|
+
status: EntityStatus,
|
|
91
|
+
path: string
|
|
92
|
+
entityId?: string;
|
|
93
|
+
selectedTab?: string;
|
|
94
|
+
collection: EntityCollection<any>
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type OnTabChangeParams<M extends Record<string, any>> = {
|
|
98
|
+
path: string;
|
|
99
|
+
entityId?: string;
|
|
100
|
+
selectedTab?: string;
|
|
101
|
+
collection: EntityCollection<M>;
|
|
102
|
+
};
|
|
103
|
+
|
|
39
104
|
export interface EntityEditViewProps<M extends Record<string, any>> {
|
|
40
105
|
path: string;
|
|
41
106
|
collection: EntityCollection<M>;
|
|
42
107
|
entityId?: string;
|
|
108
|
+
databaseId?: string;
|
|
43
109
|
copy?: boolean;
|
|
44
|
-
|
|
110
|
+
selectedTab?: string;
|
|
45
111
|
parentCollectionIds: string[];
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
onUpdate?: (params: { entity: Entity<any> }) => void;
|
|
112
|
+
onValuesModified?: (modified: boolean) => void;
|
|
113
|
+
onSaved?: (params: OnUpdateParams) => void;
|
|
49
114
|
onClose?: () => void;
|
|
115
|
+
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
116
|
+
layout?: "side_panel" | "full_screen";
|
|
117
|
+
barActions?: React.ReactNode;
|
|
50
118
|
}
|
|
51
119
|
|
|
52
120
|
/**
|
|
53
121
|
* This is the default view that is used as the content of a side panel when
|
|
54
122
|
* an entity is opened.
|
|
55
123
|
* You probably don't want to use this view directly since it is bound to the
|
|
56
|
-
* side panel.
|
|
124
|
+
* side panel.
|
|
57
125
|
*/
|
|
58
|
-
export function EntityEditView<M extends Record<string, any>,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
126
|
+
export function EntityEditView<M extends Record<string, any>, USER extends User>({
|
|
127
|
+
entityId,
|
|
128
|
+
...props
|
|
129
|
+
}: EntityEditViewProps<M>) {
|
|
130
|
+
const {
|
|
131
|
+
entity,
|
|
132
|
+
dataLoading,
|
|
133
|
+
// eslint-disable-next-line no-unused-vars
|
|
134
|
+
dataLoadingError
|
|
135
|
+
} = useEntityFetch<M, USER>({
|
|
136
|
+
path: props.path,
|
|
137
|
+
entityId: entityId,
|
|
138
|
+
collection: props.collection,
|
|
139
|
+
databaseId: props.databaseId,
|
|
140
|
+
useCache: false
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const cachedValues = entityId ? getEntityFromCache(props.path + "/" + entityId) : getEntityFromCache(props.path + "#new");
|
|
144
|
+
|
|
145
|
+
if (dataLoading && !cachedValues) {
|
|
146
|
+
return <CircularProgressCenter/>
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (entityId && !entity && !cachedValues) {
|
|
150
|
+
console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
|
|
151
|
+
}
|
|
70
152
|
|
|
153
|
+
return <EntityEditViewInner<M> {...props}
|
|
154
|
+
entityId={entityId}
|
|
155
|
+
entity={entity}
|
|
156
|
+
cachedDirtyValues={cachedValues as Partial<M>}
|
|
157
|
+
dataLoading={dataLoading}
|
|
158
|
+
/>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function EntityEditViewInner<M extends Record<string, any>>({
|
|
162
|
+
path,
|
|
163
|
+
entityId: entityIdProp,
|
|
164
|
+
selectedTab: selectedTabProp,
|
|
165
|
+
copy,
|
|
166
|
+
collection,
|
|
167
|
+
parentCollectionIds,
|
|
168
|
+
onValuesModified,
|
|
169
|
+
onSaved,
|
|
170
|
+
onClose,
|
|
171
|
+
onTabChange,
|
|
172
|
+
entity,
|
|
173
|
+
cachedDirtyValues,
|
|
174
|
+
dataLoading,
|
|
175
|
+
layout = "side_panel",
|
|
176
|
+
barActions
|
|
177
|
+
}: EntityEditViewProps<M> & {
|
|
178
|
+
entity?: Entity<M>,
|
|
179
|
+
cachedDirtyValues?: Partial<M>, // dirty cached entity in memory
|
|
180
|
+
dataLoading: boolean,
|
|
181
|
+
}) {
|
|
182
|
+
|
|
183
|
+
const largeLayout = useLargeLayout();
|
|
71
184
|
if (collection.customId && collection.formAutoSave) {
|
|
72
185
|
console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
|
|
73
186
|
}
|
|
74
187
|
|
|
188
|
+
const actionsAtTheBottom = !largeLayout || layout === "side_panel";
|
|
189
|
+
|
|
75
190
|
const [saving, setSaving] = useState(false);
|
|
76
191
|
/**
|
|
77
192
|
* These are the values that are being saved. They are debounced.
|
|
@@ -89,61 +204,82 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
89
204
|
});
|
|
90
205
|
}, false, 2000);
|
|
91
206
|
|
|
92
|
-
|
|
93
|
-
// const largeLayoutTabSelected = useRef(!largeLayout);
|
|
94
|
-
|
|
95
|
-
const resolvedFormWidth: string = typeof formWidth === "number" ? `${formWidth}px` : formWidth ?? FORM_CONTAINER_WIDTH;
|
|
207
|
+
const inputCollection = collection;
|
|
96
208
|
|
|
209
|
+
const authController = useAuthController();
|
|
97
210
|
const dataSource = useDataSource(collection);
|
|
98
|
-
const sideDialogContext = useSideDialogContext();
|
|
99
211
|
const sideEntityController = useSideEntityController();
|
|
100
212
|
const snackbarController = useSnackbarController();
|
|
101
213
|
const customizationController = useCustomizationController();
|
|
102
214
|
const context = useFireCMSContext();
|
|
103
|
-
const authController = useAuthController<UserType>();
|
|
104
215
|
|
|
105
|
-
const
|
|
216
|
+
const closeAfterSaveRef = useRef(false);
|
|
217
|
+
|
|
218
|
+
const analyticsController = useAnalyticsController();
|
|
106
219
|
|
|
107
|
-
const
|
|
220
|
+
const initialResolvedCollection = useMemo(() => resolveCollection({
|
|
221
|
+
collection: inputCollection,
|
|
222
|
+
path,
|
|
223
|
+
values: entity?.values,
|
|
224
|
+
propertyConfigs: customizationController.propertyConfigs
|
|
225
|
+
}), [entity?.values, path, customizationController.propertyConfigs]);
|
|
226
|
+
|
|
227
|
+
const initialStatus = copy ? "copy" : (entityIdProp ? "existing" : "new");
|
|
228
|
+
const [status, setStatus] = useState<EntityStatus>(initialStatus);
|
|
229
|
+
|
|
230
|
+
const mustSetCustomId: boolean = (status === "new" || status === "copy") &&
|
|
231
|
+
(Boolean(initialResolvedCollection.customId) && initialResolvedCollection.customId !== "optional");
|
|
232
|
+
|
|
233
|
+
const initialEntityId: string | undefined = useMemo((): string | undefined => {
|
|
234
|
+
if (status === "new" || status === "copy") {
|
|
235
|
+
if (mustSetCustomId) {
|
|
236
|
+
return undefined;
|
|
237
|
+
} else {
|
|
238
|
+
return dataSource.generateEntityId(path, collection);
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
return entityIdProp;
|
|
242
|
+
}
|
|
243
|
+
}, [entityIdProp, status]);
|
|
108
244
|
|
|
109
|
-
const
|
|
110
|
-
const modifiedValues = modifiedValuesRef.current;
|
|
245
|
+
const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
|
|
111
246
|
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const customViews = collection.entityViews;
|
|
115
|
-
const customViewsCount = customViews?.length ?? 0;
|
|
116
|
-
const autoSave = collection.formAutoSave && !collection.customId;
|
|
247
|
+
const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
|
|
248
|
+
const [savingError, setSavingError] = React.useState<Error | undefined>();
|
|
117
249
|
|
|
118
|
-
const
|
|
250
|
+
const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
|
|
119
251
|
|
|
120
|
-
const defaultSelectedView =
|
|
252
|
+
const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(
|
|
121
253
|
collection ? collection.defaultSelectedView : undefined,
|
|
122
254
|
{
|
|
123
255
|
status,
|
|
124
256
|
entityId
|
|
125
257
|
}
|
|
126
|
-
);
|
|
258
|
+
), []);
|
|
127
259
|
|
|
128
|
-
const
|
|
260
|
+
const [selectedTab, setSelectedTab] = useState<string>(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
129
261
|
|
|
130
|
-
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
if ((selectedTabProp ?? MAIN_TAB_VALUE) !== selectedTab) {
|
|
264
|
+
setSelectedTab(selectedTabProp ?? MAIN_TAB_VALUE);
|
|
265
|
+
}
|
|
266
|
+
}, [selectedTabProp]);
|
|
131
267
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
useCache: false
|
|
142
|
-
});
|
|
268
|
+
const mainViewVisible = selectedTab === MAIN_TAB_VALUE;
|
|
269
|
+
|
|
270
|
+
const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
|
|
271
|
+
const subcollectionsCount = subcollections?.length ?? 0;
|
|
272
|
+
const customViews = collection.entityViews;
|
|
273
|
+
const customViewsCount = customViews?.length ?? 0;
|
|
274
|
+
const autoSave = collection.formAutoSave && !collection.customId;
|
|
275
|
+
|
|
276
|
+
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
|
|
143
277
|
|
|
144
278
|
const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
|
|
145
279
|
const [readOnly, setReadOnly] = useState<boolean | undefined>(undefined);
|
|
146
280
|
|
|
281
|
+
const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>> | null>(cachedDirtyValues ?? getDataSourceEntityValues(initialResolvedCollection, status, usedEntity));
|
|
282
|
+
|
|
147
283
|
useEffect(() => {
|
|
148
284
|
if (entity)
|
|
149
285
|
setUsedEntity(entity);
|
|
@@ -177,8 +313,20 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
177
313
|
console.error(e);
|
|
178
314
|
}, [snackbarController]);
|
|
179
315
|
|
|
316
|
+
function clearDirtyCache() {
|
|
317
|
+
if (status === "new" || status === "copy") {
|
|
318
|
+
removeEntityFromCache(path + "#new");
|
|
319
|
+
} else {
|
|
320
|
+
removeEntityFromCache(path + "/" + entityId);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
180
324
|
const onSaveSuccess = (updatedEntity: Entity<M>, closeAfterSave: boolean) => {
|
|
181
325
|
|
|
326
|
+
clearDirtyCache();
|
|
327
|
+
|
|
328
|
+
onValuesModified?.(false);
|
|
329
|
+
|
|
182
330
|
setSaving(false);
|
|
183
331
|
if (!autoSave)
|
|
184
332
|
snackbarController.open({
|
|
@@ -188,26 +336,23 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
188
336
|
|
|
189
337
|
setUsedEntity(updatedEntity);
|
|
190
338
|
setStatus("existing");
|
|
339
|
+
setEntityId(updatedEntity.id);
|
|
191
340
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (closeAfterSave) {
|
|
198
|
-
sideDialogContext.setBlocked(false);
|
|
199
|
-
sideDialogContext.close(true);
|
|
200
|
-
onClose?.();
|
|
201
|
-
} else if (status !== "existing") {
|
|
202
|
-
sideEntityController.replace({
|
|
341
|
+
if (onSaved) {
|
|
342
|
+
onSaved({
|
|
343
|
+
entity: updatedEntity,
|
|
344
|
+
status,
|
|
203
345
|
path,
|
|
204
346
|
entityId: updatedEntity.id,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
collection,
|
|
347
|
+
selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab,
|
|
348
|
+
collection
|
|
208
349
|
});
|
|
209
350
|
}
|
|
210
351
|
|
|
352
|
+
if (closeAfterSave) {
|
|
353
|
+
onClose?.();
|
|
354
|
+
}
|
|
355
|
+
|
|
211
356
|
};
|
|
212
357
|
|
|
213
358
|
const onSaveFailure = useCallback((e: Error) => {
|
|
@@ -238,7 +383,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
238
383
|
closeAfterSave: boolean,
|
|
239
384
|
}) => {
|
|
240
385
|
setSaving(true);
|
|
241
|
-
saveEntityWithCallbacks({
|
|
386
|
+
return saveEntityWithCallbacks({
|
|
242
387
|
path,
|
|
243
388
|
entityId,
|
|
244
389
|
values,
|
|
@@ -269,7 +414,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
269
414
|
if (autoSave) {
|
|
270
415
|
setValuesToBeSaved(values);
|
|
271
416
|
} else {
|
|
272
|
-
saveEntity({
|
|
417
|
+
return saveEntity({
|
|
273
418
|
collection,
|
|
274
419
|
path,
|
|
275
420
|
entityId,
|
|
@@ -280,33 +425,139 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
280
425
|
}
|
|
281
426
|
};
|
|
282
427
|
|
|
428
|
+
const onSubmit = (values: EntityValues<M>, formexController: FormexController<EntityValues<M>>) => {
|
|
429
|
+
|
|
430
|
+
if (mustSetCustomId && !entityId) {
|
|
431
|
+
console.error("Missing custom Id");
|
|
432
|
+
setEntityIdError(true);
|
|
433
|
+
formexController.setSubmitting(false);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
setSavingError(undefined);
|
|
438
|
+
setEntityIdError(false);
|
|
439
|
+
|
|
440
|
+
if (status === "existing") {
|
|
441
|
+
if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
|
|
442
|
+
} else if (status === "new" || status === "copy") {
|
|
443
|
+
if (inputCollection.customId) {
|
|
444
|
+
if (inputCollection.customId !== "optional" && !entityId) {
|
|
445
|
+
throw Error("Form misconfiguration when saving, entityId should be set");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
throw Error("New FormType added, check EntityForm");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return save(values)
|
|
453
|
+
?.then(_ => {
|
|
454
|
+
formexController.resetForm({
|
|
455
|
+
values,
|
|
456
|
+
submitCount: 0,
|
|
457
|
+
touched: {}
|
|
458
|
+
});
|
|
459
|
+
})
|
|
460
|
+
.finally(() => {
|
|
461
|
+
formexController.setSubmitting(false);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const formex: FormexController<M> = useCreateFormex<M>({
|
|
467
|
+
initialValues: baseDataSourceValuesRef.current as M,
|
|
468
|
+
initialDirty: Boolean(cachedDirtyValues),
|
|
469
|
+
onSubmit,
|
|
470
|
+
validation: (values) => {
|
|
471
|
+
return validationSchema?.validate(values, { abortEarly: false })
|
|
472
|
+
.then(() => {
|
|
473
|
+
return {};
|
|
474
|
+
})
|
|
475
|
+
.catch((e: any) => {
|
|
476
|
+
// const errors: Record<string, string> = {};
|
|
477
|
+
// e.inner.forEach((error: any) => {
|
|
478
|
+
// errors[error.path] = error.message;
|
|
479
|
+
// });
|
|
480
|
+
return yupToFormErrors(e);
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
const resolvedCollection = resolveCollection<M>({
|
|
486
|
+
collection: inputCollection,
|
|
487
|
+
path,
|
|
488
|
+
entityId,
|
|
489
|
+
values: formex.values,
|
|
490
|
+
previousValues: formex.initialValues,
|
|
491
|
+
propertyConfigs: customizationController.propertyConfigs
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
const lastSavedValues = useRef<EntityValues<M> | undefined>(entity?.values);
|
|
495
|
+
|
|
496
|
+
const save = (values: EntityValues<M>): Promise<void> => {
|
|
497
|
+
lastSavedValues.current = values;
|
|
498
|
+
return onSaveEntityRequest({
|
|
499
|
+
collection: resolvedCollection,
|
|
500
|
+
path,
|
|
501
|
+
entityId,
|
|
502
|
+
values,
|
|
503
|
+
previousValues: entity?.values,
|
|
504
|
+
closeAfterSave: closeAfterSaveRef.current,
|
|
505
|
+
autoSave: autoSave ?? false
|
|
506
|
+
}).then(_ => {
|
|
507
|
+
const eventName: CMSAnalyticsEvent = status === "new"
|
|
508
|
+
? "new_entity_saved"
|
|
509
|
+
: (status === "copy" ? "entity_copied" : (status === "existing" ? "entity_edited" : "unmapped_event"));
|
|
510
|
+
analyticsController.onAnalyticsEvent?.(eventName, { path });
|
|
511
|
+
}).catch(e => {
|
|
512
|
+
console.error(e);
|
|
513
|
+
setSavingError(e);
|
|
514
|
+
}).finally(() => {
|
|
515
|
+
closeAfterSaveRef.current = false;
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const formContext: FormContext<M> = {
|
|
520
|
+
// @ts-ignore
|
|
521
|
+
setFieldValue: useCallback(formex.setFieldValue, []),
|
|
522
|
+
values: formex.values,
|
|
523
|
+
collection: resolvedCollection,
|
|
524
|
+
entityId: entityId as string,
|
|
525
|
+
path,
|
|
526
|
+
save,
|
|
527
|
+
formex
|
|
528
|
+
};
|
|
529
|
+
|
|
283
530
|
const resolvedEntityViews = customViews ? customViews
|
|
284
531
|
.map(e => resolveEntityView(e, customizationController.entityViews))
|
|
285
532
|
.filter(Boolean) as EntityCustomView[]
|
|
286
533
|
: [];
|
|
287
534
|
|
|
535
|
+
const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTab);
|
|
536
|
+
const shouldShowEntityActions = !autoSave && (selectedTab === MAIN_TAB_VALUE || selectedEntityView?.includeActions);
|
|
537
|
+
|
|
288
538
|
const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
|
|
289
539
|
.map(
|
|
290
540
|
(customView, colIndex) => {
|
|
291
541
|
if (!customView)
|
|
292
542
|
return null;
|
|
293
|
-
if (selectedTabRef.current !== customView.key)
|
|
294
|
-
return null;
|
|
295
543
|
const Builder = customView.Builder;
|
|
296
544
|
if (!Builder) {
|
|
297
545
|
console.error("customView.Builder is not defined");
|
|
298
546
|
return null;
|
|
299
547
|
}
|
|
300
548
|
return <div
|
|
301
|
-
className={
|
|
302
|
-
"relative flex-grow w-full h-full overflow-auto
|
|
549
|
+
className={cls(defaultBorderMixin,
|
|
550
|
+
"relative flex-grow w-full h-full overflow-auto",
|
|
551
|
+
{
|
|
552
|
+
"hidden": selectedTab !== customView.key
|
|
553
|
+
})}
|
|
303
554
|
key={`custom_view_${customView.key}`}
|
|
304
555
|
role="tabpanel">
|
|
305
556
|
<ErrorBoundary>
|
|
306
557
|
{formContext && <Builder
|
|
307
558
|
collection={collection}
|
|
308
559
|
entity={usedEntity}
|
|
309
|
-
modifiedValues={
|
|
560
|
+
modifiedValues={formex.values ?? usedEntity?.values}
|
|
310
561
|
formContext={formContext}
|
|
311
562
|
/>}
|
|
312
563
|
</ErrorBoundary>
|
|
@@ -317,21 +568,20 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
317
568
|
const globalLoading = (dataLoading && !usedEntity) ||
|
|
318
569
|
((!usedEntity || readOnly === undefined) && (status === "existing" || status === "copy"));
|
|
319
570
|
|
|
320
|
-
const loading = globalLoading || saving;
|
|
321
|
-
|
|
322
571
|
const subCollectionsViews = subcollections && subcollections.map(
|
|
323
572
|
(subcollection, colIndex) => {
|
|
324
573
|
const subcollectionId = subcollection.id ?? subcollection.path;
|
|
325
574
|
const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
|
|
326
|
-
if (selectedTabRef.current !== subcollectionId)
|
|
327
|
-
return null;
|
|
328
575
|
return (
|
|
329
576
|
<div
|
|
330
|
-
className={"relative flex-grow h-full overflow-auto w-full"
|
|
577
|
+
className={cls("relative flex-grow h-full overflow-auto w-full",
|
|
578
|
+
{
|
|
579
|
+
"hidden": selectedTab !== subcollectionId
|
|
580
|
+
})}
|
|
331
581
|
key={`subcol_${subcollectionId}`}
|
|
332
582
|
role="tabpanel">
|
|
333
583
|
|
|
334
|
-
{
|
|
584
|
+
{globalLoading || saving && <CircularProgressCenter/>}
|
|
335
585
|
|
|
336
586
|
{!globalLoading &&
|
|
337
587
|
(usedEntity && fullPath
|
|
@@ -339,7 +589,9 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
339
589
|
fullPath={fullPath}
|
|
340
590
|
parentCollectionIds={[...parentCollectionIds, collection.id]}
|
|
341
591
|
isSubCollection={true}
|
|
342
|
-
{
|
|
592
|
+
updateUrl={false}
|
|
593
|
+
{...subcollection}
|
|
594
|
+
openEntityMode={layout}/>
|
|
343
595
|
: <div
|
|
344
596
|
className="flex items-center justify-center w-full h-full p-3">
|
|
345
597
|
<Typography variant={"label"}>
|
|
@@ -355,25 +607,21 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
355
607
|
).filter(Boolean);
|
|
356
608
|
|
|
357
609
|
const onDiscard = useCallback(() => {
|
|
358
|
-
|
|
610
|
+
onValuesModified?.(false);
|
|
359
611
|
}, []);
|
|
360
612
|
|
|
361
613
|
const onSideTabClick = (value: string) => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
614
|
+
setSelectedTab(value);
|
|
615
|
+
if (status === "existing") {
|
|
616
|
+
onTabChange?.({
|
|
617
|
+
path,
|
|
618
|
+
entityId,
|
|
619
|
+
selectedTab: value === MAIN_TAB_VALUE ? undefined : value,
|
|
620
|
+
collection
|
|
621
|
+
});
|
|
622
|
+
}
|
|
370
623
|
};
|
|
371
624
|
|
|
372
|
-
const onValuesChanged = useCallback((values?: EntityValues<M>) => {
|
|
373
|
-
modifiedValuesRef.current = values;
|
|
374
|
-
}, []);
|
|
375
|
-
|
|
376
|
-
// eslint-disable-next-line n/handle-callback-err
|
|
377
625
|
const onIdUpdateError = useCallback((error: any) => {
|
|
378
626
|
snackbarController.open({
|
|
379
627
|
type: "error",
|
|
@@ -390,56 +638,305 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
390
638
|
: undefined);
|
|
391
639
|
}, []);
|
|
392
640
|
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
641
|
+
const pluginActions: React.ReactNode[] = [];
|
|
642
|
+
|
|
643
|
+
const plugins = customizationController.plugins;
|
|
644
|
+
|
|
645
|
+
if (plugins && inputCollection) {
|
|
646
|
+
const actionProps: PluginFormActionProps = {
|
|
647
|
+
entityId,
|
|
648
|
+
path,
|
|
649
|
+
status,
|
|
650
|
+
collection: inputCollection,
|
|
651
|
+
context,
|
|
652
|
+
currentEntityId: entityId,
|
|
653
|
+
formContext,
|
|
654
|
+
layout
|
|
655
|
+
};
|
|
656
|
+
pluginActions.push(...plugins.map((plugin, i) => (
|
|
657
|
+
plugin.form?.Actions
|
|
658
|
+
? <plugin.form.Actions
|
|
659
|
+
key={`actions_${plugin.key}`} {...actionProps}/>
|
|
660
|
+
: null
|
|
661
|
+
)).filter(Boolean));
|
|
396
662
|
}
|
|
397
663
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
664
|
+
const titlePropertyKey = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
|
|
665
|
+
const title = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : undefined;
|
|
666
|
+
|
|
667
|
+
const onIdUpdate = inputCollection.callbacks?.onIdUpdate;
|
|
668
|
+
|
|
669
|
+
const doOnIdUpdate = useCallback(async () => {
|
|
670
|
+
if (onIdUpdate && formex.values && (status === "new" || status === "copy")) {
|
|
671
|
+
setCustomIdLoading(true);
|
|
672
|
+
try {
|
|
673
|
+
const updatedId = await onIdUpdate({
|
|
674
|
+
collection: resolvedCollection,
|
|
675
|
+
path,
|
|
676
|
+
entityId,
|
|
677
|
+
values: formex.values,
|
|
678
|
+
context
|
|
679
|
+
});
|
|
680
|
+
setEntityId(updatedId);
|
|
681
|
+
} catch (e) {
|
|
682
|
+
onIdUpdateError?.(e);
|
|
683
|
+
console.error(e);
|
|
684
|
+
}
|
|
685
|
+
setCustomIdLoading(false);
|
|
686
|
+
}
|
|
687
|
+
}, [entityId, formex.values, status]);
|
|
688
|
+
|
|
689
|
+
useEffect(() => {
|
|
690
|
+
doOnIdUpdate();
|
|
691
|
+
}, [doOnIdUpdate]);
|
|
692
|
+
|
|
693
|
+
const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
|
|
694
|
+
|
|
695
|
+
const uniqueFieldValidator: CustomFieldValidator = useCallback(({
|
|
696
|
+
name,
|
|
697
|
+
value,
|
|
698
|
+
property
|
|
699
|
+
}) => dataSource.checkUniqueField(path, name, value, entityId, collection),
|
|
700
|
+
[dataSource, path, entityId]);
|
|
701
|
+
|
|
702
|
+
const validationSchema = useMemo(() => entityId
|
|
703
|
+
? getYupEntitySchema(
|
|
704
|
+
entityId,
|
|
705
|
+
resolvedCollection.properties,
|
|
706
|
+
uniqueFieldValidator)
|
|
707
|
+
: undefined,
|
|
708
|
+
[entityId, resolvedCollection.properties, uniqueFieldValidator]);
|
|
709
|
+
|
|
710
|
+
const getActionsForEntity = useCallback(({
|
|
711
|
+
entity,
|
|
712
|
+
customEntityActions
|
|
713
|
+
}: {
|
|
714
|
+
entity?: Entity<M>,
|
|
715
|
+
customEntityActions?: EntityAction[]
|
|
716
|
+
}): EntityAction[] => {
|
|
717
|
+
const createEnabled = canCreateEntity(inputCollection, authController, path, null);
|
|
718
|
+
const deleteEnabled = entity ? canDeleteEntity(inputCollection, authController, path, entity) : true;
|
|
719
|
+
const actions: EntityAction[] = [];
|
|
720
|
+
if (createEnabled)
|
|
721
|
+
actions.push(copyEntityAction);
|
|
722
|
+
if (deleteEnabled)
|
|
723
|
+
actions.push(deleteEntityAction);
|
|
724
|
+
if (customEntityActions)
|
|
725
|
+
return mergeEntityActions(actions, customEntityActions);
|
|
726
|
+
return actions;
|
|
727
|
+
}, [authController, inputCollection, path]);
|
|
728
|
+
|
|
729
|
+
const deferredValues = useDeferredValue(formex.values);
|
|
730
|
+
const modified = formex.dirty;
|
|
731
|
+
|
|
732
|
+
useEffect(() => {
|
|
733
|
+
const key = (status === "new" || status === "copy") ? path + "#new" : path + "/" + entityId;
|
|
734
|
+
if (modified) {
|
|
735
|
+
saveEntityToCache(key, deferredValues);
|
|
736
|
+
}
|
|
737
|
+
}, [deferredValues, modified]);
|
|
738
|
+
|
|
739
|
+
useEffect(() => {
|
|
740
|
+
if (!autoSave) {
|
|
741
|
+
onValuesModified?.(modified);
|
|
742
|
+
}
|
|
743
|
+
}, [modified]);
|
|
744
|
+
|
|
745
|
+
useOnAutoSave(autoSave, formex, lastSavedValues, save);
|
|
746
|
+
|
|
747
|
+
useEffect(() => {
|
|
748
|
+
if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
|
|
749
|
+
// we update the form fields from the Firestore data
|
|
750
|
+
// if they were not touched
|
|
751
|
+
Object.entries(underlyingChanges).forEach(([key, value]) => {
|
|
752
|
+
const formValue = formex.values[key];
|
|
753
|
+
if (!equal(value, formValue) && !formex.touched[key]) {
|
|
754
|
+
console.debug("Updated value from the datasource:", key, value);
|
|
755
|
+
formex.setFieldValue(key, value !== undefined ? value : null);
|
|
433
756
|
}
|
|
434
757
|
});
|
|
435
758
|
}
|
|
436
|
-
|
|
437
|
-
|
|
759
|
+
}, [formex.isSubmitting, autoSave, underlyingChanges, entity, formex.values, formex.touched, formex.setFieldValue]);
|
|
760
|
+
|
|
761
|
+
const formFields = (
|
|
762
|
+
<>
|
|
763
|
+
{(getFormFieldKeys(resolvedCollection))
|
|
764
|
+
.map((key) => {
|
|
765
|
+
|
|
766
|
+
const property = resolvedCollection.properties[key];
|
|
767
|
+
if (property) {
|
|
768
|
+
|
|
769
|
+
const underlyingValueHasChanged: boolean =
|
|
770
|
+
!!underlyingChanges &&
|
|
771
|
+
Object.keys(underlyingChanges).includes(key) &&
|
|
772
|
+
formex.touched[key];
|
|
773
|
+
|
|
774
|
+
const disabled = (!autoSave && formex.isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
|
|
775
|
+
const hidden = isHidden(property);
|
|
776
|
+
if (hidden) return null;
|
|
777
|
+
const cmsFormFieldProps: PropertyFieldBindingProps<any, M> = {
|
|
778
|
+
propertyKey: key,
|
|
779
|
+
disabled,
|
|
780
|
+
property,
|
|
781
|
+
includeDescription: property.description || property.longDescription,
|
|
782
|
+
underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
|
|
783
|
+
context: formContext,
|
|
784
|
+
partOfArray: false,
|
|
785
|
+
minimalistView: false,
|
|
786
|
+
autoFocus: false
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
return (
|
|
790
|
+
<div id={`form_field_${key}`}
|
|
791
|
+
key={`field_${resolvedCollection.name}_${key}`}>
|
|
792
|
+
<ErrorBoundary>
|
|
793
|
+
<PropertyFieldBinding {...cmsFormFieldProps}/>
|
|
794
|
+
</ErrorBoundary>
|
|
795
|
+
</div>
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const additionalField = resolvedCollection.additionalFields?.find(f => f.key === key);
|
|
800
|
+
if (additionalField && entity) {
|
|
801
|
+
const Builder = additionalField.Builder;
|
|
802
|
+
if (!Builder && !additionalField.value) {
|
|
803
|
+
throw new Error("When using additional fields you need to provide a Builder or a value");
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const child = Builder
|
|
807
|
+
? <Builder entity={entity} context={context}/>
|
|
808
|
+
: <>
|
|
809
|
+
{additionalField.value?.({
|
|
810
|
+
entity,
|
|
811
|
+
context
|
|
812
|
+
})?.toString()}
|
|
813
|
+
</>;
|
|
814
|
+
return (
|
|
815
|
+
<div key={`additional_${key}`}>
|
|
816
|
+
<LabelWithIconAndTooltip
|
|
817
|
+
propertyKey={key}
|
|
818
|
+
icon={<NotesIcon size={"small"}/>}
|
|
819
|
+
title={additionalField.name}
|
|
820
|
+
className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
821
|
+
<div
|
|
822
|
+
className={cls(paperMixin, "min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
|
|
823
|
+
|
|
824
|
+
<ErrorBoundary>
|
|
825
|
+
{child}
|
|
826
|
+
</ErrorBoundary>
|
|
827
|
+
|
|
828
|
+
</div>
|
|
829
|
+
</div>
|
|
830
|
+
);
|
|
831
|
+
}
|
|
438
832
|
|
|
439
|
-
|
|
833
|
+
console.warn(`Property ${key} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
|
|
834
|
+
return null;
|
|
835
|
+
})
|
|
836
|
+
.filter(Boolean)}
|
|
837
|
+
|
|
838
|
+
</>);
|
|
839
|
+
|
|
840
|
+
const disabled = formex.isSubmitting || (!modified && status === "existing");
|
|
841
|
+
const formRef = React.useRef<HTMLDivElement>(null);
|
|
842
|
+
|
|
843
|
+
const entityActions = getActionsForEntity({
|
|
844
|
+
entity,
|
|
845
|
+
customEntityActions: inputCollection.entityActions
|
|
846
|
+
});
|
|
847
|
+
const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
|
|
848
|
+
|
|
849
|
+
const dialogActions = actionsAtTheBottom
|
|
850
|
+
? buildBottomActions({
|
|
851
|
+
savingError: savingError,
|
|
852
|
+
entity: entity,
|
|
853
|
+
formActions: formActions,
|
|
854
|
+
resolvedCollection: resolvedCollection,
|
|
855
|
+
context: context,
|
|
856
|
+
sideEntityController: sideEntityController,
|
|
857
|
+
isSubmitting: formex.isSubmitting,
|
|
858
|
+
disabled: disabled,
|
|
859
|
+
status: status,
|
|
860
|
+
setPendingClose: (value: boolean) => {
|
|
861
|
+
closeAfterSaveRef.current = value;
|
|
862
|
+
},
|
|
863
|
+
pluginActions,
|
|
864
|
+
layout
|
|
865
|
+
})
|
|
866
|
+
: buildSideActions({
|
|
867
|
+
savingError: savingError,
|
|
868
|
+
entity: entity,
|
|
869
|
+
formActions: formActions,
|
|
870
|
+
resolvedCollection: resolvedCollection,
|
|
871
|
+
context: context,
|
|
872
|
+
sideEntityController: sideEntityController,
|
|
873
|
+
isSubmitting: formex.isSubmitting,
|
|
874
|
+
disabled: disabled,
|
|
875
|
+
status: status,
|
|
876
|
+
pluginActions,
|
|
877
|
+
layout
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
const entityView = (readOnly === undefined)
|
|
440
881
|
? <></>
|
|
441
882
|
: (!readOnly
|
|
442
|
-
?
|
|
883
|
+
? (
|
|
884
|
+
<ErrorBoundary>
|
|
885
|
+
<div className="flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10">
|
|
886
|
+
|
|
887
|
+
{formex.dirty
|
|
888
|
+
? <Tooltip title={"Unsaved changes"}
|
|
889
|
+
className={"self-end sticky top-4 z-10"}>
|
|
890
|
+
<Chip size={"small"} colorScheme={"orangeDarker"}>
|
|
891
|
+
<EditIcon size={"smallest"}/>
|
|
892
|
+
</Chip>
|
|
893
|
+
</Tooltip>
|
|
894
|
+
: <Tooltip title={"In sync with the database"}
|
|
895
|
+
className={"self-end sticky top-4 z-10"}>
|
|
896
|
+
<Chip size={"small"}>
|
|
897
|
+
<CheckIcon size={"smallest"}/>
|
|
898
|
+
</Chip>
|
|
899
|
+
</Tooltip>}
|
|
900
|
+
|
|
901
|
+
<div
|
|
902
|
+
className={"w-full py-2 flex flex-col items-start mt-4 lg:mt-8 mb-8"}>
|
|
903
|
+
|
|
904
|
+
<Typography
|
|
905
|
+
className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
|
|
906
|
+
variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
|
|
907
|
+
</Typography>
|
|
908
|
+
<Alert color={"base"} className={"w-full"} size={"small"}>
|
|
909
|
+
<code
|
|
910
|
+
className={"text-xs select-all text-text-secondary dark:text-text-secondary-dark"}>{path}/{entityId}</code>
|
|
911
|
+
</Alert>
|
|
912
|
+
</div>
|
|
913
|
+
|
|
914
|
+
{!collection.hideIdFromForm &&
|
|
915
|
+
<CustomIdField customId={inputCollection.customId}
|
|
916
|
+
entityId={entityId}
|
|
917
|
+
status={status}
|
|
918
|
+
onChange={setEntityId}
|
|
919
|
+
error={entityIdError}
|
|
920
|
+
loading={customIdLoading}
|
|
921
|
+
entity={entity}/>}
|
|
922
|
+
|
|
923
|
+
{entityId && formContext && <>
|
|
924
|
+
<div className="mt-12 flex flex-col gap-8"
|
|
925
|
+
ref={formRef}>
|
|
926
|
+
|
|
927
|
+
{formFields}
|
|
928
|
+
|
|
929
|
+
<ErrorFocus containerRef={formRef}/>
|
|
930
|
+
|
|
931
|
+
</div>
|
|
932
|
+
|
|
933
|
+
{actionsAtTheBottom && <div className="h-16"/>}
|
|
934
|
+
|
|
935
|
+
</>}
|
|
936
|
+
|
|
937
|
+
</div>
|
|
938
|
+
</ErrorBoundary>
|
|
939
|
+
)
|
|
443
940
|
: (
|
|
444
941
|
<>
|
|
445
942
|
<Typography
|
|
@@ -451,13 +948,14 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
451
948
|
entity={usedEntity as Entity<M>}
|
|
452
949
|
path={path}
|
|
453
950
|
collection={collection}/>
|
|
951
|
+
|
|
454
952
|
</>
|
|
455
953
|
));
|
|
456
954
|
|
|
457
955
|
const subcollectionTabs = subcollections && subcollections.map(
|
|
458
956
|
(subcollection) =>
|
|
459
957
|
<Tab
|
|
460
|
-
className="text-sm min-w-[
|
|
958
|
+
className="text-sm min-w-[120px]"
|
|
461
959
|
value={subcollection.id}
|
|
462
960
|
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
463
961
|
{subcollection.name}
|
|
@@ -468,94 +966,325 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
468
966
|
(view) =>
|
|
469
967
|
|
|
470
968
|
<Tab
|
|
471
|
-
className="text-sm min-w-[
|
|
969
|
+
className="text-sm min-w-[120px]"
|
|
472
970
|
value={view.key}
|
|
473
971
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
474
972
|
{view.name}
|
|
475
973
|
</Tab>
|
|
476
974
|
);
|
|
477
975
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
<>
|
|
976
|
+
useEffect(() => {
|
|
977
|
+
if (entityId && onIdChange)
|
|
978
|
+
onIdChange(entityId);
|
|
979
|
+
}, [entityId, onIdChange]);
|
|
483
980
|
|
|
484
|
-
|
|
485
|
-
className={cn(defaultBorderMixin, "no-scrollbar border-b pl-2 pr-2 pt-1 flex items-end overflow-scroll bg-gray-50 dark:bg-gray-950")}>
|
|
981
|
+
const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
|
|
486
982
|
|
|
487
|
-
|
|
488
|
-
className="pb-1 self-center">
|
|
489
|
-
<IconButton
|
|
490
|
-
onClick={() => {
|
|
491
|
-
onClose?.();
|
|
492
|
-
return sideDialogContext.close(false);
|
|
493
|
-
}}
|
|
494
|
-
size="large">
|
|
495
|
-
<CloseIcon/>
|
|
496
|
-
</IconButton>
|
|
497
|
-
</div>
|
|
983
|
+
let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-900">
|
|
498
984
|
|
|
499
|
-
|
|
985
|
+
{shouldShowTopBar && <div
|
|
986
|
+
className={cls("h-14 flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex items-end bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
|
|
500
987
|
|
|
501
|
-
|
|
502
|
-
className="self-center">
|
|
503
|
-
<CircularProgress size={"small"}/>
|
|
504
|
-
</div>}
|
|
988
|
+
{barActions}
|
|
505
989
|
|
|
506
|
-
|
|
507
|
-
value={selectedTabRef.current}
|
|
508
|
-
onValueChange={(value) => {
|
|
509
|
-
onSideTabClick(value);
|
|
510
|
-
}}
|
|
511
|
-
className="pl-4 pr-4 pt-0">
|
|
990
|
+
<div className={"flex-grow"}/>
|
|
512
991
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
!hasAdditionalViews ? "hidden" : ""
|
|
518
|
-
} text-sm min-w-[140px]`}
|
|
519
|
-
>{collection.singularName ?? collection.name}</Tab>
|
|
992
|
+
{globalLoading && <div
|
|
993
|
+
className="self-center">
|
|
994
|
+
<CircularProgress size={"small"}/>
|
|
995
|
+
</div>}
|
|
520
996
|
|
|
521
|
-
|
|
997
|
+
{hasAdditionalViews && <Tabs
|
|
998
|
+
value={selectedTab}
|
|
999
|
+
onValueChange={(value) => {
|
|
1000
|
+
onSideTabClick(value);
|
|
1001
|
+
}}>
|
|
522
1002
|
|
|
523
|
-
|
|
524
|
-
|
|
1003
|
+
<Tab
|
|
1004
|
+
disabled={!hasAdditionalViews}
|
|
1005
|
+
value={MAIN_TAB_VALUE}
|
|
1006
|
+
className={"text-sm min-w-[120px]"}
|
|
1007
|
+
>{collection.singularName ?? collection.name}</Tab>
|
|
525
1008
|
|
|
526
|
-
|
|
1009
|
+
{customViewTabs}
|
|
527
1010
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
style={{
|
|
531
|
-
// width: `calc(${ADDITIONAL_TAB_WIDTH} + ${resolvedFormWidth})`,
|
|
532
|
-
// maxWidth: "100%",
|
|
533
|
-
// [`@media (max-width: ${resolvedFormWidth})`]: {
|
|
534
|
-
// width: resolvedFormWidth
|
|
535
|
-
// }
|
|
536
|
-
}}>
|
|
1011
|
+
{subcollectionTabs}
|
|
1012
|
+
</Tabs>}
|
|
537
1013
|
|
|
538
|
-
|
|
539
|
-
role="tabpanel"
|
|
540
|
-
hidden={!mainViewVisible}
|
|
541
|
-
id={`form_${path}`}
|
|
542
|
-
className={" w-full"}>
|
|
1014
|
+
</div>}
|
|
543
1015
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1016
|
+
<form
|
|
1017
|
+
onSubmit={formex.handleSubmit}
|
|
1018
|
+
onReset={() => {
|
|
547
1019
|
|
|
548
|
-
|
|
1020
|
+
clearDirtyCache();
|
|
549
1021
|
|
|
550
|
-
|
|
1022
|
+
formex.resetForm({
|
|
1023
|
+
values: getDataSourceEntityValues(initialResolvedCollection, status, entity) as M,
|
|
1024
|
+
});
|
|
551
1025
|
|
|
552
|
-
|
|
1026
|
+
return onDiscard();
|
|
1027
|
+
}}
|
|
1028
|
+
noValidate
|
|
1029
|
+
className={"flex-1 flex flex-row w-full overflow-y-auto justify-center"}>
|
|
553
1030
|
|
|
554
|
-
|
|
1031
|
+
<div
|
|
1032
|
+
role="tabpanel"
|
|
1033
|
+
hidden={!mainViewVisible}
|
|
1034
|
+
id={`form_${path}`}
|
|
1035
|
+
className={cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-5xl w-full h-fit", {
|
|
1036
|
+
"hidden": !mainViewVisible
|
|
1037
|
+
})}>
|
|
555
1038
|
|
|
556
|
-
|
|
1039
|
+
{globalLoading
|
|
1040
|
+
? <div className="w-full pt-12 pb-16 px-4 sm:px-8 md:px-10"><CircularProgressCenter/></div>
|
|
1041
|
+
: entityView}
|
|
1042
|
+
|
|
1043
|
+
</div>
|
|
1044
|
+
|
|
1045
|
+
{mainViewVisible && shouldShowEntityActions && !actionsAtTheBottom && dialogActions}
|
|
1046
|
+
|
|
1047
|
+
{customViewsView}
|
|
1048
|
+
|
|
1049
|
+
{subCollectionsViews}
|
|
1050
|
+
|
|
1051
|
+
{shouldShowEntityActions && actionsAtTheBottom && dialogActions}
|
|
1052
|
+
|
|
1053
|
+
</form>
|
|
1054
|
+
|
|
1055
|
+
</div>;
|
|
1056
|
+
|
|
1057
|
+
if (plugins) {
|
|
1058
|
+
plugins.forEach((plugin: FireCMSPlugin) => {
|
|
1059
|
+
if (plugin.form?.provider) {
|
|
1060
|
+
result = (
|
|
1061
|
+
<plugin.form.provider.Component
|
|
1062
|
+
status={status}
|
|
1063
|
+
path={path}
|
|
1064
|
+
collection={collection}
|
|
1065
|
+
onDiscard={onDiscard}
|
|
1066
|
+
entity={usedEntity}
|
|
1067
|
+
context={context}
|
|
1068
|
+
formContext={formContext}
|
|
1069
|
+
{...plugin.form.provider.props}>
|
|
1070
|
+
{result}
|
|
1071
|
+
</plugin.form.provider.Component>
|
|
1072
|
+
);
|
|
557
1073
|
}
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return (
|
|
1078
|
+
<Formex value={formex}>
|
|
558
1079
|
|
|
559
|
-
|
|
1080
|
+
{result}
|
|
1081
|
+
</Formex>
|
|
560
1082
|
);
|
|
561
1083
|
}
|
|
1084
|
+
|
|
1085
|
+
function getDataSourceEntityValues<M extends object>(collection: ResolvedEntityCollection,
|
|
1086
|
+
status: "new" | "existing" | "copy",
|
|
1087
|
+
entity: Entity<M> | undefined): Partial<EntityValues<M>> {
|
|
1088
|
+
|
|
1089
|
+
const properties = collection.properties;
|
|
1090
|
+
if ((status === "existing" || status === "copy") && entity) {
|
|
1091
|
+
return entity.values ?? getDefaultValuesFor(properties);
|
|
1092
|
+
} else if (status === "new") {
|
|
1093
|
+
return getDefaultValuesFor(properties);
|
|
1094
|
+
} else {
|
|
1095
|
+
console.error({
|
|
1096
|
+
status,
|
|
1097
|
+
entity
|
|
1098
|
+
});
|
|
1099
|
+
throw new Error("Form has not been initialised with the correct parameters");
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
export type EntityFormSaveParams<M extends Record<string, any>> = {
|
|
1104
|
+
collection: ResolvedEntityCollection<M>,
|
|
1105
|
+
path: string,
|
|
1106
|
+
entityId: string | undefined,
|
|
1107
|
+
values: EntityValues<M>,
|
|
1108
|
+
previousValues?: EntityValues<M>,
|
|
1109
|
+
closeAfterSave: boolean,
|
|
1110
|
+
autoSave: boolean
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
|
|
1114
|
+
let errors: Record<string, any> = {};
|
|
1115
|
+
if (yupError.inner) {
|
|
1116
|
+
if (yupError.inner.length === 0) {
|
|
1117
|
+
return setIn(errors, yupError.path!, yupError.message);
|
|
1118
|
+
}
|
|
1119
|
+
for (const err of yupError.inner) {
|
|
1120
|
+
if (!getIn(errors, err.path!)) {
|
|
1121
|
+
errors = setIn(errors, err.path!, err.message);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return errors;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
type ActionsViewProps<M extends object> = {
|
|
1129
|
+
savingError: Error | undefined,
|
|
1130
|
+
entity: Entity<M> | undefined,
|
|
1131
|
+
formActions: EntityAction[],
|
|
1132
|
+
resolvedCollection: ResolvedEntityCollection,
|
|
1133
|
+
context: FireCMSContext,
|
|
1134
|
+
sideEntityController: SideEntityController,
|
|
1135
|
+
isSubmitting: boolean,
|
|
1136
|
+
disabled: boolean,
|
|
1137
|
+
status: "new" | "existing" | "copy",
|
|
1138
|
+
setPendingClose?: (value: boolean) => void,
|
|
1139
|
+
pluginActions?: React.ReactNode[],
|
|
1140
|
+
layout: "side_panel" | "full_screen";
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
function buildBottomActions<M extends object>({
|
|
1144
|
+
savingError,
|
|
1145
|
+
entity,
|
|
1146
|
+
formActions,
|
|
1147
|
+
resolvedCollection,
|
|
1148
|
+
context,
|
|
1149
|
+
sideEntityController,
|
|
1150
|
+
isSubmitting,
|
|
1151
|
+
disabled,
|
|
1152
|
+
status,
|
|
1153
|
+
setPendingClose,
|
|
1154
|
+
pluginActions,
|
|
1155
|
+
layout
|
|
1156
|
+
}: ActionsViewProps<M>) {
|
|
1157
|
+
|
|
1158
|
+
return <DialogActions position={"absolute"}>
|
|
1159
|
+
|
|
1160
|
+
{savingError &&
|
|
1161
|
+
<div className="text-right">
|
|
1162
|
+
<Typography color={"error"}>
|
|
1163
|
+
{savingError.message}
|
|
1164
|
+
</Typography>
|
|
1165
|
+
</div>}
|
|
1166
|
+
|
|
1167
|
+
{entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
|
|
1168
|
+
{formActions.map(action => (
|
|
1169
|
+
<IconButton
|
|
1170
|
+
key={action.name}
|
|
1171
|
+
color="primary"
|
|
1172
|
+
onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
1173
|
+
event.stopPropagation();
|
|
1174
|
+
if (entity)
|
|
1175
|
+
action.onClick({
|
|
1176
|
+
entity,
|
|
1177
|
+
fullPath: resolvedCollection.path,
|
|
1178
|
+
collection: resolvedCollection,
|
|
1179
|
+
context,
|
|
1180
|
+
sideEntityController,
|
|
1181
|
+
openEntityMode: layout
|
|
1182
|
+
});
|
|
1183
|
+
}}>
|
|
1184
|
+
{action.icon}
|
|
1185
|
+
</IconButton>
|
|
1186
|
+
))}
|
|
1187
|
+
</div>}
|
|
1188
|
+
|
|
1189
|
+
{pluginActions}
|
|
1190
|
+
|
|
1191
|
+
<Button
|
|
1192
|
+
variant="text"
|
|
1193
|
+
disabled={disabled || isSubmitting}
|
|
1194
|
+
type="reset">
|
|
1195
|
+
{status === "existing" ? "Discard" : "Clear"}
|
|
1196
|
+
</Button>
|
|
1197
|
+
|
|
1198
|
+
<Button
|
|
1199
|
+
variant="text"
|
|
1200
|
+
color="primary"
|
|
1201
|
+
type="submit"
|
|
1202
|
+
disabled={disabled || isSubmitting}
|
|
1203
|
+
onClick={() => {
|
|
1204
|
+
setPendingClose?.(false);
|
|
1205
|
+
}}>
|
|
1206
|
+
{status === "existing" && "Save"}
|
|
1207
|
+
{status === "copy" && "Create copy"}
|
|
1208
|
+
{status === "new" && "Create"}
|
|
1209
|
+
</Button>
|
|
1210
|
+
|
|
1211
|
+
<LoadingButton
|
|
1212
|
+
variant="filled"
|
|
1213
|
+
color="primary"
|
|
1214
|
+
type="submit"
|
|
1215
|
+
loading={isSubmitting}
|
|
1216
|
+
disabled={disabled}
|
|
1217
|
+
onClick={() => {
|
|
1218
|
+
setPendingClose?.(true);
|
|
1219
|
+
}}>
|
|
1220
|
+
{status === "existing" && "Save and close"}
|
|
1221
|
+
{status === "copy" && "Create copy and close"}
|
|
1222
|
+
{status === "new" && "Create and close"}
|
|
1223
|
+
</LoadingButton>
|
|
1224
|
+
|
|
1225
|
+
</DialogActions>;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function buildSideActions<M extends object>({
|
|
1229
|
+
savingError,
|
|
1230
|
+
entity,
|
|
1231
|
+
formActions,
|
|
1232
|
+
resolvedCollection,
|
|
1233
|
+
context,
|
|
1234
|
+
sideEntityController,
|
|
1235
|
+
isSubmitting,
|
|
1236
|
+
disabled,
|
|
1237
|
+
status,
|
|
1238
|
+
setPendingClose,
|
|
1239
|
+
pluginActions
|
|
1240
|
+
}: ActionsViewProps<M>) {
|
|
1241
|
+
|
|
1242
|
+
return <div
|
|
1243
|
+
className={cls("overflow-auto h-full flex flex-col gap-2 w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin)}>
|
|
1244
|
+
|
|
1245
|
+
<LoadingButton
|
|
1246
|
+
fullWidth={true}
|
|
1247
|
+
variant="filled"
|
|
1248
|
+
color="primary"
|
|
1249
|
+
type="submit"
|
|
1250
|
+
size={"large"}
|
|
1251
|
+
disabled={disabled || isSubmitting}
|
|
1252
|
+
onClick={() => {
|
|
1253
|
+
setPendingClose?.(false);
|
|
1254
|
+
}}>
|
|
1255
|
+
{status === "existing" && "Save"}
|
|
1256
|
+
{status === "copy" && "Create copy"}
|
|
1257
|
+
{status === "new" && "Create"}
|
|
1258
|
+
</LoadingButton>
|
|
1259
|
+
|
|
1260
|
+
<Button
|
|
1261
|
+
fullWidth={true}
|
|
1262
|
+
variant="text"
|
|
1263
|
+
disabled={disabled || isSubmitting}
|
|
1264
|
+
type="reset">
|
|
1265
|
+
{status === "existing" ? "Discard" : "Clear"}
|
|
1266
|
+
</Button>
|
|
1267
|
+
|
|
1268
|
+
{pluginActions}
|
|
1269
|
+
|
|
1270
|
+
{savingError &&
|
|
1271
|
+
<div className="text-right">
|
|
1272
|
+
<Typography color={"error"}>
|
|
1273
|
+
{savingError.message}
|
|
1274
|
+
</Typography>
|
|
1275
|
+
</div>}
|
|
1276
|
+
|
|
1277
|
+
</div>;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function useOnAutoSave(autoSave: undefined | boolean, formex: FormexController<any>, lastSavedValues: any, save: (values: EntityValues<any>) => Promise<void>) {
|
|
1281
|
+
if (!autoSave) return;
|
|
1282
|
+
useEffect(() => {
|
|
1283
|
+
if (autoSave) {
|
|
1284
|
+
if (formex.values && !equal(formex.values, lastSavedValues.current)) {
|
|
1285
|
+
save(formex.values);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}, [formex.values]);
|
|
1289
|
+
}
|
|
1290
|
+
|