@firecms/core 3.0.0-beta.11 → 3.0.0-beta.13
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/dist/app/Scaffold.d.ts +4 -0
- package/dist/components/ArrayContainer.d.ts +14 -3
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +15 -1
- package/dist/components/EntityCollectionTable/index.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
- package/dist/components/EntityJsonPreview.d.ts +3 -0
- package/dist/components/PropertyIdCopyTooltip.d.ts +1 -1
- package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
- 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 +13 -0
- 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/contexts/BreacrumbsContext.d.ts +8 -0
- package/dist/core/DefaultAppBar.d.ts +8 -2
- package/dist/core/EntityEditView.d.ts +36 -21
- 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/index.d.ts +2 -1
- package/dist/form/EntityForm.d.ts +49 -0
- package/dist/form/EntityFormActions.d.ts +17 -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/index.d.ts +2 -0
- package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
- package/dist/form/index.d.ts +16 -16
- package/dist/hooks/data/save.d.ts +1 -1
- package/dist/hooks/data/useEntityFetch.d.ts +2 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +4 -1
- package/dist/hooks/useModeController.d.ts +1 -2
- package/dist/index.es.js +15842 -14396
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +15833 -14389
- 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/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/collections.d.ts +36 -10
- package/dist/types/datasource.d.ts +2 -2
- package/dist/types/dialogs_controller.d.ts +7 -3
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_actions.d.ts +10 -0
- package/dist/types/fields.d.ts +13 -3
- package/dist/types/firecms.d.ts +1 -1
- package/dist/types/navigation.d.ts +33 -12
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/properties.d.ts +40 -4
- package/dist/types/side_entity_controller.d.ts +6 -1
- package/dist/util/builders.d.ts +1 -1
- package/dist/util/createFormexStub.d.ts +2 -0
- package/dist/util/entity_actions.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/navigation_from_path.d.ts +6 -1
- package/dist/util/navigation_utils.d.ts +13 -1
- package/dist/util/objects.d.ts +1 -1
- package/dist/util/property_utils.d.ts +2 -2
- package/dist/util/references.d.ts +2 -2
- package/dist/util/resolutions.d.ts +12 -2
- package/package.json +20 -21
- package/src/app/Scaffold.tsx +13 -3
- package/src/components/ArrayContainer.tsx +60 -24
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/DeleteEntityDialog.tsx +5 -3
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +46 -23
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -4
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +18 -1
- package/src/components/EntityCollectionTable/index.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +10 -13
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +105 -47
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
- package/src/components/EntityJsonPreview.tsx +66 -0
- package/src/components/EntityPreview.tsx +10 -3
- package/src/components/EntityView.tsx +4 -1
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/PropertyIdCopyTooltip.tsx +2 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +14 -7
- package/src/components/SelectableTable/SelectableTable.tsx +18 -3
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +7 -7
- package/src/components/UnsavedChangesDialog.tsx +42 -0
- package/src/components/VirtualTable/VirtualTable.tsx +48 -3
- package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
- package/src/components/common/default_entity_actions.tsx +37 -37
- 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/contexts/BreacrumbsContext.tsx +38 -0
- package/src/contexts/DialogsProvider.tsx +3 -2
- package/src/contexts/ModeController.tsx +1 -3
- package/src/core/DefaultAppBar.tsx +59 -17
- package/src/core/DefaultDrawer.tsx +1 -1
- package/src/core/EntityEditView.tsx +354 -1000
- package/src/core/EntityEditViewFormActions.tsx +199 -0
- package/src/core/EntitySidePanel.tsx +85 -15
- package/src/core/FireCMS.tsx +20 -16
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +23 -32
- package/src/core/SideDialogs.tsx +20 -11
- package/src/core/index.tsx +4 -2
- package/src/form/EntityForm.tsx +782 -0
- package/src/form/EntityFormActions.tsx +169 -0
- package/src/form/PropertyFieldBinding.tsx +21 -16
- package/src/form/components/FormEntry.tsx +22 -0
- package/src/form/components/FormLayout.tsx +16 -0
- package/src/form/components/LabelWithIcon.tsx +6 -3
- package/src/form/components/StorageUploadProgress.tsx +1 -1
- package/src/form/components/index.tsx +2 -0
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +8 -5
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +5 -2
- package/src/form/field_bindings/BlockFieldBinding.tsx +5 -3
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -0
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +14 -16
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +15 -8
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +3 -3
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RepeatFieldBinding.tsx +13 -4
- package/src/form/field_bindings/SelectFieldBinding.tsx +2 -2
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +11 -6
- package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -0
- package/src/form/field_bindings/TextFieldBinding.tsx +1 -0
- package/src/form/index.tsx +16 -32
- package/src/form/validation.ts +12 -6
- package/src/hooks/data/save.ts +25 -30
- package/src/hooks/data/useCollectionFetch.tsx +1 -1
- package/src/hooks/data/useEntityFetch.tsx +5 -1
- package/src/hooks/useBreadcrumbsController.tsx +31 -0
- package/src/hooks/useBuildModeController.tsx +15 -28
- package/src/hooks/useBuildNavigationController.tsx +81 -24
- package/src/hooks/useFireCMSContext.tsx +0 -30
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useModeController.tsx +1 -2
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildDataSource.ts +9 -5
- package/src/internal/useBuildSideDialogsController.tsx +3 -2
- package/src/internal/useBuildSideEntityController.tsx +135 -88
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/preview/PropertyPreview.tsx +12 -10
- package/src/preview/components/UrlComponentPreview.tsx +17 -18
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
- package/src/preview/property_previews/MapPropertyPreview.tsx +3 -2
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +246 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +45 -13
- package/src/types/datasource.ts +2 -1
- package/src/types/dialogs_controller.tsx +7 -3
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +12 -0
- package/src/types/fields.tsx +18 -3
- package/src/types/firecms.tsx +1 -1
- package/src/types/navigation.ts +41 -16
- package/src/types/plugins.tsx +2 -0
- package/src/types/properties.ts +48 -4
- package/src/types/side_dialogs_controller.tsx +2 -0
- package/src/types/side_entity_controller.tsx +11 -1
- package/src/util/builders.ts +5 -3
- package/src/util/createFormexStub.tsx +62 -0
- package/src/util/entity_actions.ts +28 -0
- package/src/util/entity_cache.ts +204 -0
- package/src/util/index.ts +1 -0
- package/src/util/join_collections.ts +6 -1
- package/src/util/navigation_from_path.ts +18 -7
- package/src/util/navigation_utils.ts +115 -14
- package/src/util/objects.ts +9 -2
- package/src/util/parent_references_from_path.ts +3 -3
- package/src/util/property_utils.tsx +7 -3
- package/src/util/references.ts +8 -6
- package/src/util/resolutions.ts +41 -12
- package/src/util/useStorageUploadController.tsx +60 -23
- package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import {
|
|
3
|
+
AuthController,
|
|
4
|
+
CustomizationController,
|
|
3
5
|
EntityCollection,
|
|
4
6
|
EntitySidePanelProps,
|
|
5
7
|
NavigationController,
|
|
@@ -8,18 +10,31 @@ import {
|
|
|
8
10
|
SideDialogsController,
|
|
9
11
|
SideEntityController
|
|
10
12
|
} from "../types";
|
|
11
|
-
import {
|
|
13
|
+
import { getNavigationEntriesFromPath, NavigationViewInternal } from "../util/navigation_from_path";
|
|
12
14
|
import { useLocation } from "react-router-dom";
|
|
13
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
removeInitialAndTrailingSlashes,
|
|
17
|
+
resolveCollection,
|
|
18
|
+
resolveDefaultSelectedView,
|
|
19
|
+
resolvedSelectedEntityView
|
|
20
|
+
} from "../util";
|
|
14
21
|
import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, FORM_CONTAINER_WIDTH } from "./common";
|
|
15
|
-
import { useLargeLayout } from "../hooks";
|
|
22
|
+
import { useCustomizationController, useLargeLayout } from "../hooks";
|
|
16
23
|
import { EntitySidePanel } from "../core/EntitySidePanel";
|
|
24
|
+
import { JSON_TAB_VALUE } from "../core/EntityEditView";
|
|
17
25
|
|
|
18
|
-
const NEW_URL_HASH = "
|
|
26
|
+
const NEW_URL_HASH = "new_side";
|
|
27
|
+
const SIDE_URL_HASH = "side";
|
|
19
28
|
|
|
20
|
-
export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: boolean): string {
|
|
29
|
+
export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: boolean, customizationController: CustomizationController, authController: AuthController): string {
|
|
21
30
|
if (small) return CONTAINER_FULL_WIDTH;
|
|
22
|
-
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
selectedSecondaryForm
|
|
34
|
+
} = resolvedSelectedEntityView(props.collection?.entityViews, customizationController, props.selectedTab);
|
|
35
|
+
|
|
36
|
+
const shouldUseSmallLayout = !props.selectedTab || props.selectedTab === JSON_TAB_VALUE || Boolean(selectedSecondaryForm);
|
|
37
|
+
|
|
23
38
|
let resolvedWidth: string | undefined;
|
|
24
39
|
if (props.width) {
|
|
25
40
|
resolvedWidth = typeof props.width === "number" ? `${props.width}px` : props.width;
|
|
@@ -27,7 +42,7 @@ export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: bool
|
|
|
27
42
|
resolvedWidth = typeof props.collection.sideDialogWidth === "number" ? `${props.collection.sideDialogWidth}px` : props.collection.sideDialogWidth;
|
|
28
43
|
}
|
|
29
44
|
|
|
30
|
-
if (!
|
|
45
|
+
if (!shouldUseSmallLayout) {
|
|
31
46
|
return `calc(${ADDITIONAL_TAB_WIDTH} + ${resolvedWidth ?? FORM_CONTAINER_WIDTH})`
|
|
32
47
|
} else {
|
|
33
48
|
if (resolvedWidth) {
|
|
@@ -35,21 +50,22 @@ export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: bool
|
|
|
35
50
|
} else if (!props.collection) {
|
|
36
51
|
return FORM_CONTAINER_WIDTH;
|
|
37
52
|
} else {
|
|
38
|
-
return calculateCollectionDesiredWidth(props.collection);
|
|
53
|
+
return calculateCollectionDesiredWidth(props.collection, authController);
|
|
39
54
|
}
|
|
40
55
|
}
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
const collectionViewWidthCache: { [key: string]: string } = {};
|
|
44
59
|
|
|
45
|
-
function calculateCollectionDesiredWidth(collection: EntityCollection<any
|
|
60
|
+
function calculateCollectionDesiredWidth(collection: EntityCollection<any>, authController: AuthController): string {
|
|
46
61
|
if (collectionViewWidthCache[collection.id]) {
|
|
47
62
|
return collectionViewWidthCache[collection.id];
|
|
48
63
|
}
|
|
49
64
|
const resolvedCollection = resolveCollection({
|
|
50
65
|
collection,
|
|
51
66
|
path: "__ignored",
|
|
52
|
-
ignoreMissingFields: true
|
|
67
|
+
ignoreMissingFields: true,
|
|
68
|
+
authController
|
|
53
69
|
});
|
|
54
70
|
|
|
55
71
|
let result = FORM_CONTAINER_WIDTH
|
|
@@ -84,41 +100,64 @@ function getNestedPropertiesDepth(property: ResolvedProperty, accumulator: numbe
|
|
|
84
100
|
}
|
|
85
101
|
|
|
86
102
|
export const useBuildSideEntityController = (navigation: NavigationController,
|
|
87
|
-
sideDialogsController: SideDialogsController
|
|
103
|
+
sideDialogsController: SideDialogsController,
|
|
104
|
+
authController: AuthController
|
|
105
|
+
): SideEntityController => {
|
|
88
106
|
|
|
89
107
|
const location = useLocation();
|
|
90
108
|
const initialised = useRef<boolean>(false);
|
|
109
|
+
const customizationController = useCustomizationController();
|
|
91
110
|
|
|
92
111
|
const smallLayout = !useLargeLayout();
|
|
93
112
|
|
|
94
|
-
// only on initialisation, create panels from URL
|
|
95
113
|
useEffect(() => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
114
|
+
|
|
115
|
+
const newFlag = location.hash === `#${NEW_URL_HASH}`;
|
|
116
|
+
const sideFlag = location.hash === `#${SIDE_URL_HASH}`;
|
|
117
|
+
|
|
118
|
+
if (!navigation.loading) {
|
|
119
|
+
if ((newFlag || sideFlag) && navigation.isUrlCollectionPath(location.pathname)) {
|
|
100
120
|
const entityOrCollectionPath = navigation.urlPathToDataPath(location.pathname);
|
|
101
121
|
const panelsFromUrl = buildSidePanelsFromUrl(entityOrCollectionPath, navigation.collections ?? [], newFlag);
|
|
102
122
|
for (let i = 0; i < panelsFromUrl.length; i++) {
|
|
103
123
|
const props = panelsFromUrl[i];
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
sideDialogsController.open(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout))
|
|
109
|
-
}, 1);
|
|
124
|
+
if (i === 0)
|
|
125
|
+
sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController, authController));
|
|
126
|
+
else
|
|
127
|
+
sideDialogsController.open(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController, authController))
|
|
110
128
|
}
|
|
111
|
-
} else {
|
|
112
|
-
// console.warn("Location path is not a collection path");
|
|
113
129
|
}
|
|
114
130
|
initialised.current = true;
|
|
115
131
|
}
|
|
116
|
-
}, [
|
|
132
|
+
}, [navigation.loading]);
|
|
117
133
|
|
|
134
|
+
// sync panels if URL changes with #side
|
|
135
|
+
const currentPanelKeys = sideDialogsController.sidePanels.map(p => p.key);
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (initialised.current) {
|
|
138
|
+
const sideFlag = location.hash === `#${SIDE_URL_HASH}`;
|
|
139
|
+
if (sideFlag) {
|
|
140
|
+
const entityOrCollectionPath = navigation.urlPathToDataPath(location.pathname);
|
|
141
|
+
const panelsFromUrl = buildSidePanelsFromUrl(entityOrCollectionPath, navigation.collections ?? [], false);
|
|
142
|
+
// if we have more panels than determined by the url, we ignore the url. We might have references open
|
|
143
|
+
if (panelsFromUrl.length <= currentPanelKeys.length) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const lastPanel = panelsFromUrl[panelsFromUrl.length - 1];
|
|
147
|
+
const panelProps = propsToSidePanel(lastPanel, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController, authController);
|
|
148
|
+
const lastCurrentPanel = currentPanelKeys.length > 0 ? currentPanelKeys[currentPanelKeys.length - 1] : undefined;
|
|
149
|
+
if (!lastCurrentPanel || lastCurrentPanel !== panelProps.key) {
|
|
150
|
+
sideDialogsController.replace(panelProps);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}, [location.pathname, location.hash, currentPanelKeys]);
|
|
155
|
+
|
|
156
|
+
// update side panels to match browser size
|
|
118
157
|
useEffect(() => {
|
|
119
158
|
const updatedSidePanels = sideDialogsController.sidePanels.map(sidePanelProps => {
|
|
120
159
|
if (sidePanelProps.additional) {
|
|
121
|
-
return propsToSidePanel(sidePanelProps.additional, navigation.buildUrlCollectionPath, navigation.
|
|
160
|
+
return propsToSidePanel(sidePanelProps.additional, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController, authController);
|
|
122
161
|
}
|
|
123
162
|
return sidePanelProps;
|
|
124
163
|
});
|
|
@@ -143,12 +182,19 @@ export const useBuildSideEntityController = (navigation: NavigationController,
|
|
|
143
182
|
}
|
|
144
183
|
);
|
|
145
184
|
|
|
146
|
-
sideDialogsController.open(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
185
|
+
sideDialogsController.open(
|
|
186
|
+
propsToSidePanel({
|
|
187
|
+
selectedTab: defaultSelectedView,
|
|
188
|
+
...props
|
|
189
|
+
},
|
|
190
|
+
navigation.buildUrlCollectionPath,
|
|
191
|
+
navigation.resolveIdsFrom,
|
|
192
|
+
smallLayout,
|
|
193
|
+
customizationController,
|
|
194
|
+
authController
|
|
195
|
+
));
|
|
150
196
|
|
|
151
|
-
}, [sideDialogsController, navigation.buildUrlCollectionPath, navigation.
|
|
197
|
+
}, [sideDialogsController, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, authController.user]);
|
|
152
198
|
|
|
153
199
|
const replace = useCallback((props: EntitySidePanelProps<any>) => {
|
|
154
200
|
|
|
@@ -156,9 +202,9 @@ export const useBuildSideEntityController = (navigation: NavigationController,
|
|
|
156
202
|
throw Error("If you want to copy an entity you need to provide an entityId");
|
|
157
203
|
}
|
|
158
204
|
|
|
159
|
-
sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.
|
|
205
|
+
sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, smallLayout, customizationController, authController));
|
|
160
206
|
|
|
161
|
-
}, [navigation.buildUrlCollectionPath, navigation.
|
|
207
|
+
}, [navigation.buildUrlCollectionPath, navigation.resolveIdsFrom, sideDialogsController, smallLayout, authController.user]);
|
|
162
208
|
|
|
163
209
|
return {
|
|
164
210
|
close,
|
|
@@ -169,12 +215,12 @@ export const useBuildSideEntityController = (navigation: NavigationController,
|
|
|
169
215
|
|
|
170
216
|
export function buildSidePanelsFromUrl(path: string, collections: EntityCollection[], newFlag: boolean): EntitySidePanelProps<any>[] {
|
|
171
217
|
|
|
172
|
-
const navigationViewsForPath: NavigationViewInternal<any>[] =
|
|
218
|
+
const navigationViewsForPath: NavigationViewInternal<any>[] = getNavigationEntriesFromPath({
|
|
173
219
|
path,
|
|
174
220
|
collections
|
|
175
221
|
});
|
|
176
222
|
|
|
177
|
-
|
|
223
|
+
let sidePanel: EntitySidePanelProps<any> | undefined = undefined;
|
|
178
224
|
let lastCollectionPath = "";
|
|
179
225
|
for (let i = 0; i < navigationViewsForPath.length; i++) {
|
|
180
226
|
const navigationEntry = navigationViewsForPath[i];
|
|
@@ -183,70 +229,71 @@ export function buildSidePanelsFromUrl(path: string, collections: EntityCollecti
|
|
|
183
229
|
lastCollectionPath = navigationEntry.path;
|
|
184
230
|
}
|
|
185
231
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const lastSidePanel: EntitySidePanelProps<any> = sidePanels[sidePanels.length - 1];
|
|
205
|
-
if (lastSidePanel)
|
|
206
|
-
lastSidePanel.selectedSubPath = navigationEntry.collection.id ?? navigationEntry.collection.path;
|
|
207
|
-
}
|
|
232
|
+
const previousEntry = navigationViewsForPath[i - 1];
|
|
233
|
+
if (navigationEntry.type === "entity") {
|
|
234
|
+
sidePanel = {
|
|
235
|
+
path: navigationEntry.path,
|
|
236
|
+
// fullIdPath: navigationEntry.path,
|
|
237
|
+
entityId: navigationEntry.entityId,
|
|
238
|
+
copy: false,
|
|
239
|
+
width: navigationEntry.parentCollection?.sideDialogWidth
|
|
240
|
+
};
|
|
241
|
+
} else if (navigationEntry.type === "custom_view") {
|
|
242
|
+
if (previousEntry?.type === "entity") {
|
|
243
|
+
if (sidePanel)
|
|
244
|
+
sidePanel.selectedTab = navigationEntry.view.key;
|
|
245
|
+
}
|
|
246
|
+
} else if (navigationEntry.type === "collection") {
|
|
247
|
+
if (previousEntry?.type === "entity") {
|
|
248
|
+
if (sidePanel)
|
|
249
|
+
sidePanel.selectedTab = navigationEntry.collection.id ?? navigationEntry.collection.path;
|
|
208
250
|
}
|
|
209
251
|
}
|
|
210
252
|
|
|
211
253
|
}
|
|
212
254
|
|
|
213
255
|
if (newFlag) {
|
|
214
|
-
|
|
256
|
+
sidePanel = {
|
|
215
257
|
path: lastCollectionPath,
|
|
258
|
+
// fullIdPath: lastCollectionPath,
|
|
216
259
|
copy: false
|
|
217
|
-
}
|
|
260
|
+
}
|
|
218
261
|
}
|
|
219
262
|
|
|
220
|
-
return
|
|
263
|
+
return sidePanel ? [sidePanel] : [];
|
|
221
264
|
}
|
|
222
265
|
|
|
223
266
|
const propsToSidePanel = (props: EntitySidePanelProps,
|
|
224
267
|
buildUrlCollectionPath: (path: string) => string,
|
|
225
|
-
|
|
226
|
-
smallLayout: boolean
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
268
|
+
resolveIdsFrom: (pathWithAliases: string) => string,
|
|
269
|
+
smallLayout: boolean,
|
|
270
|
+
customizationController: CustomizationController,
|
|
271
|
+
authController: AuthController
|
|
272
|
+
): SideDialogPanelProps => {
|
|
273
|
+
|
|
274
|
+
const collectionPath = removeInitialAndTrailingSlashes(props.path);
|
|
275
|
+
|
|
276
|
+
const newPath = props.entityId
|
|
277
|
+
? buildUrlCollectionPath(`${collectionPath}/${props.entityId}${props.selectedTab ? "/" + props.selectedTab : ""}#${SIDE_URL_HASH}`)
|
|
278
|
+
: buildUrlCollectionPath(`${collectionPath}#${NEW_URL_HASH}`);
|
|
279
|
+
|
|
280
|
+
const resolvedPanelProps: EntitySidePanelProps<any> = {
|
|
281
|
+
...props,
|
|
282
|
+
formProps: props.formProps
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const entityViewWidth = getEntityViewWidth(props, smallLayout, customizationController, authController);
|
|
286
|
+
return {
|
|
287
|
+
key: `${props.path}/${props.entityId}`,
|
|
288
|
+
component: <EntitySidePanel {...resolvedPanelProps}/>,
|
|
289
|
+
urlPath: newPath,
|
|
290
|
+
parentUrlPath: buildUrlCollectionPath(collectionPath),
|
|
291
|
+
width: entityViewWidth,
|
|
292
|
+
onClose: props.onClose,
|
|
293
|
+
additional: props
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isSideUrl(url: string): boolean {
|
|
298
|
+
return url.endsWith("#" + SIDE_URL_HASH) || url.endsWith("#" + NEW_URL_HASH);
|
|
299
|
+
}
|
|
@@ -1,108 +1,142 @@
|
|
|
1
|
-
import React, { useCallback } from "react";
|
|
2
|
-
import { Blocker, Transition } from "history";
|
|
3
|
-
import { UNSAFE_NavigationContext, useNavigate } from "react-router-dom";
|
|
1
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
4
2
|
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@firecms/ui";
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Type representing a pending navigation action.
|
|
6
|
+
*/
|
|
7
|
+
type PendingNavigation =
|
|
8
|
+
| {
|
|
9
|
+
type: "popstate";
|
|
10
|
+
delta: number;
|
|
11
|
+
}
|
|
12
|
+
| {
|
|
13
|
+
type: "link";
|
|
14
|
+
href: string;
|
|
15
|
+
}
|
|
16
|
+
| null;
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Custom hook to handle navigation blocking when there are unsaved changes.
|
|
20
|
+
*
|
|
21
|
+
* @param when - Indicates whether to block navigation.
|
|
22
|
+
* @param onSuccess - Callback invoked when navigation is confirmed.
|
|
23
|
+
* @returns An object containing the state of navigation blocking and handlers.
|
|
24
|
+
*/
|
|
25
|
+
export function useNavigationUnsavedChangesDialog(
|
|
26
|
+
when: boolean,
|
|
27
|
+
onSuccess: () => void
|
|
28
|
+
): {
|
|
29
|
+
navigationWasBlocked: boolean;
|
|
30
|
+
handleCancel: () => void;
|
|
31
|
+
handleOk: () => void;
|
|
32
|
+
} {
|
|
33
|
+
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
|
|
34
|
+
const [pendingNavigation, setPendingNavigation] = useState<PendingNavigation>(null);
|
|
17
35
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Handler to cancel the navigation attempt.
|
|
38
|
+
*/
|
|
39
|
+
const handleCancel = useCallback(() => {
|
|
40
|
+
setIsDialogOpen(false);
|
|
41
|
+
setPendingNavigation(null);
|
|
42
|
+
}, []);
|
|
21
43
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Handler to confirm and proceed with the navigation.
|
|
46
|
+
*/
|
|
47
|
+
const handleOk = useCallback(() => {
|
|
48
|
+
setIsDialogOpen(false);
|
|
49
|
+
if (pendingNavigation) {
|
|
50
|
+
onSuccess();
|
|
51
|
+
if (pendingNavigation.type === "popstate") {
|
|
52
|
+
window.history.go(pendingNavigation.delta);
|
|
53
|
+
} else if (pendingNavigation.type === "link") {
|
|
54
|
+
window.location.href = pendingNavigation.href;
|
|
55
|
+
}
|
|
56
|
+
setPendingNavigation(null);
|
|
57
|
+
}
|
|
58
|
+
}, [onSuccess, pendingNavigation]);
|
|
27
59
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return;
|
|
60
|
+
/**
|
|
61
|
+
* Event handler for beforeunload to handle page refresh or tab close.
|
|
62
|
+
*/
|
|
63
|
+
const handleBeforeUnload = useCallback(
|
|
64
|
+
(e: BeforeUnloadEvent) => {
|
|
65
|
+
if (when) {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
e.returnValue = "";
|
|
37
68
|
}
|
|
38
|
-
|
|
39
|
-
|
|
69
|
+
},
|
|
70
|
+
[when]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Event handler for popstate to handle back and forward browser buttons.
|
|
75
|
+
*/
|
|
76
|
+
const handlePopState = useCallback(
|
|
77
|
+
(e: PopStateEvent) => {
|
|
78
|
+
if (when) {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
// Assuming backward navigation; adjust delta as needed
|
|
81
|
+
setPendingNavigation({
|
|
82
|
+
type: "popstate",
|
|
83
|
+
delta: -1
|
|
84
|
+
});
|
|
85
|
+
setIsDialogOpen(true);
|
|
40
86
|
}
|
|
41
|
-
}
|
|
42
|
-
|
|
87
|
+
},
|
|
88
|
+
[when]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Event handler to intercept link clicks within the application.
|
|
93
|
+
*/
|
|
94
|
+
const handleLinkClick = useCallback(
|
|
95
|
+
(e: MouseEvent) => {
|
|
96
|
+
if (!when) return;
|
|
43
97
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
98
|
+
const target = e.target as HTMLElement;
|
|
99
|
+
const anchor = target.closest("a[href]") as HTMLAnchorElement | null;
|
|
100
|
+
if (anchor && anchor.host === window.location.host) {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
const href = anchor.getAttribute("href");
|
|
103
|
+
if (href) {
|
|
104
|
+
setPendingNavigation({
|
|
105
|
+
type: "link",
|
|
106
|
+
href
|
|
107
|
+
});
|
|
108
|
+
setIsDialogOpen(true);
|
|
54
109
|
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[when]
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Effect hook to add and clean up event listeners based on the `when` condition.
|
|
117
|
+
*/
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (when) {
|
|
120
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
121
|
+
window.addEventListener("popstate", handlePopState);
|
|
122
|
+
document.addEventListener("click", handleLinkClick);
|
|
123
|
+
} else {
|
|
124
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
125
|
+
window.removeEventListener("popstate", handlePopState);
|
|
126
|
+
document.removeEventListener("click", handleLinkClick);
|
|
127
|
+
}
|
|
58
128
|
|
|
59
|
-
|
|
60
|
-
|
|
129
|
+
// Cleanup on unmount or when `when` changes
|
|
130
|
+
return () => {
|
|
131
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
132
|
+
window.removeEventListener("popstate", handlePopState);
|
|
133
|
+
document.removeEventListener("click", handleLinkClick);
|
|
134
|
+
};
|
|
135
|
+
}, [when, handleBeforeUnload, handlePopState, handleLinkClick]);
|
|
61
136
|
|
|
62
137
|
return {
|
|
63
|
-
navigationWasBlocked:
|
|
138
|
+
navigationWasBlocked: isDialogOpen,
|
|
64
139
|
handleCancel,
|
|
65
|
-
handleOk
|
|
140
|
+
handleOk,
|
|
66
141
|
};
|
|
67
142
|
}
|
|
68
|
-
|
|
69
|
-
export interface UnsavedChangesDialogProps {
|
|
70
|
-
open: boolean;
|
|
71
|
-
body?: React.ReactNode;
|
|
72
|
-
title?: string;
|
|
73
|
-
handleOk: () => void;
|
|
74
|
-
handleCancel: () => void;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function UnsavedChangesDialog({
|
|
78
|
-
open,
|
|
79
|
-
handleOk,
|
|
80
|
-
handleCancel,
|
|
81
|
-
body,
|
|
82
|
-
title
|
|
83
|
-
}: UnsavedChangesDialogProps) {
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<Dialog
|
|
87
|
-
onEscapeKeyDown={() => {
|
|
88
|
-
handleCancel();
|
|
89
|
-
}}
|
|
90
|
-
open={open}
|
|
91
|
-
>
|
|
92
|
-
<DialogTitle variant={"h6"}>{title}</DialogTitle>
|
|
93
|
-
<DialogContent>
|
|
94
|
-
|
|
95
|
-
{body}
|
|
96
|
-
|
|
97
|
-
<Typography>
|
|
98
|
-
Are you sure you want to leave this page?
|
|
99
|
-
</Typography>
|
|
100
|
-
|
|
101
|
-
</DialogContent>
|
|
102
|
-
<DialogActions>
|
|
103
|
-
<Button variant="text" onClick={handleCancel} autoFocus> Cancel </Button>
|
|
104
|
-
<Button onClick={handleOk}> Ok </Button>
|
|
105
|
-
</DialogActions>
|
|
106
|
-
</Dialog>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { resolveProperty } from "../util";
|
|
14
14
|
|
|
15
15
|
import { PropertyPreviewProps } from "./PropertyPreviewProps";
|
|
16
|
-
import { useCustomizationController } from "../hooks";
|
|
16
|
+
import { useAuthController, useCustomizationController } from "../hooks";
|
|
17
17
|
import { EmptyValue } from "./components/EmptyValue";
|
|
18
18
|
import { UrlComponentPreview } from "./components/UrlComponentPreview";
|
|
19
19
|
import { StorageThumbnail } from "./components/StorageThumbnail";
|
|
@@ -37,6 +37,7 @@ import { ErrorView } from "../components";
|
|
|
37
37
|
*/
|
|
38
38
|
export const PropertyPreview = React.memo(function PropertyPreview<T extends CMSType>(props: PropertyPreviewProps<T>) {
|
|
39
39
|
|
|
40
|
+
const authController = useAuthController();
|
|
40
41
|
const customizationController = useCustomizationController();
|
|
41
42
|
|
|
42
43
|
let content: React.ReactNode | any;
|
|
@@ -53,7 +54,8 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
53
54
|
const property = resolveProperty({
|
|
54
55
|
propertyKey,
|
|
55
56
|
propertyOrBuilder: inputProperty,
|
|
56
|
-
propertyConfigs: customizationController.propertyConfigs
|
|
57
|
+
propertyConfigs: customizationController.propertyConfigs,
|
|
58
|
+
authController
|
|
57
59
|
});
|
|
58
60
|
|
|
59
61
|
if (property === null) {
|
|
@@ -75,7 +77,14 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
75
77
|
} else if (property.dataType === "string") {
|
|
76
78
|
const stringProperty = property as ResolvedStringProperty;
|
|
77
79
|
if (typeof value === "string") {
|
|
78
|
-
if (stringProperty.
|
|
80
|
+
if (stringProperty.storage) {
|
|
81
|
+
const filePath = stringProperty.storage.previewUrl ? stringProperty.storage.previewUrl(value) : value;
|
|
82
|
+
content = <StorageThumbnail
|
|
83
|
+
interactive={interactive}
|
|
84
|
+
storeUrl={property.storage?.storeUrl ?? false}
|
|
85
|
+
size={props.size}
|
|
86
|
+
storagePathOrDownloadUrl={filePath}/>;
|
|
87
|
+
} else if (stringProperty.url) {
|
|
79
88
|
if (typeof stringProperty.url === "boolean")
|
|
80
89
|
content =
|
|
81
90
|
<UrlComponentPreview size={props.size}
|
|
@@ -88,13 +97,6 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
88
97
|
previewType={stringProperty.url}/>;
|
|
89
98
|
} else if (stringProperty.markdown) {
|
|
90
99
|
content = <Markdown source={value} size={"small"}/>;
|
|
91
|
-
} else if (stringProperty.storage) {
|
|
92
|
-
const filePath = stringProperty.storage.previewUrl ? stringProperty.storage.previewUrl(value) : value;
|
|
93
|
-
content = <StorageThumbnail
|
|
94
|
-
interactive={interactive}
|
|
95
|
-
storeUrl={property.storage?.storeUrl ?? false}
|
|
96
|
-
size={props.size}
|
|
97
|
-
storagePathOrDownloadUrl={filePath}/>;
|
|
98
100
|
} else {
|
|
99
101
|
content = <StringPropertyPreview {...props}
|
|
100
102
|
property={stringProperty}
|