@firecms/core 3.0.0-canary.4 → 3.0.0-canary.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/components/ClearFilterSortButton.d.ts +5 -0
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +11 -11
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +2 -2
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +12 -3
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
- package/dist/components/EntityCollectionView/useSelectionController.d.ts +2 -0
- package/dist/components/EntityPreview.d.ts +25 -7
- package/dist/components/EntityView.d.ts +11 -0
- package/dist/components/FieldCaption.d.ts +5 -0
- package/dist/components/HomePage/NavigationCard.d.ts +8 -0
- package/dist/components/HomePage/{NavigationCollectionCard.d.ts → NavigationCardBinding.d.ts} +2 -2
- package/dist/components/HomePage/SmallNavigationCard.d.ts +6 -0
- package/dist/components/HomePage/index.d.ts +3 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +1 -1
- package/dist/components/index.d.ts +4 -3
- package/dist/contexts/AuthControllerContext.d.ts +1 -1
- package/dist/{internal/EntityView.d.ts → core/EntityEditView.d.ts} +2 -2
- package/dist/core/SideEntityView.d.ts +7 -0
- package/dist/core/index.d.ts +0 -2
- package/dist/form/EntityForm.d.ts +1 -1
- package/dist/form/components/ErrorFocus.d.ts +1 -1
- package/dist/form/components/StorageItemPreview.d.ts +3 -2
- package/dist/form/components/StorageUploadProgress.d.ts +1 -1
- package/dist/form/components/index.d.ts +1 -0
- 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/StorageUploadFieldBinding.d.ts +4 -3
- package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -2
- package/dist/form/index.d.ts +1 -0
- package/dist/form/validation.d.ts +1 -1
- package/dist/hooks/data/delete.d.ts +2 -2
- package/dist/hooks/data/save.d.ts +1 -1
- package/dist/hooks/data/useDataSource.d.ts +2 -2
- package/dist/hooks/data/useEntityFetch.d.ts +3 -3
- package/dist/hooks/index.d.ts +3 -1
- package/dist/{core → hooks}/useBuildModeController.d.ts +1 -1
- package/dist/hooks/useBuildNavigationController.d.ts +6 -4
- package/dist/hooks/useProjectLog.d.ts +6 -2
- package/dist/hooks/useStorageSource.d.ts +2 -2
- package/dist/hooks/useValidateAuthenticator.d.ts +25 -0
- package/dist/index.es.js +8258 -7767
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +5 -5
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildDataSource.d.ts +4 -0
- package/dist/preview/PropertyPreview.d.ts +1 -1
- package/dist/preview/PropertyPreviewProps.d.ts +1 -4
- package/dist/preview/components/BooleanPreview.d.ts +5 -1
- package/dist/preview/components/EnumValuesChip.d.ts +1 -1
- package/dist/preview/components/ReferencePreview.d.ts +1 -7
- package/dist/types/analytics.d.ts +1 -1
- package/dist/types/auth.d.ts +37 -1
- package/dist/types/collections.d.ts +22 -5
- package/dist/types/datasource.d.ts +1 -1
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_callbacks.d.ts +2 -2
- package/dist/types/entity_overrides.d.ts +6 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/navigation.d.ts +14 -13
- package/dist/types/permissions.d.ts +5 -1
- package/dist/types/plugins.d.ts +20 -20
- package/dist/types/properties.d.ts +2 -2
- package/dist/types/property_config.d.ts +2 -2
- package/dist/types/roles.d.ts +31 -0
- package/dist/types/storage.d.ts +11 -3
- package/dist/types/user.d.ts +5 -0
- package/dist/util/collections.d.ts +9 -1
- package/dist/util/entities.d.ts +1 -1
- package/dist/util/icons.d.ts +8 -2
- package/dist/util/navigation_utils.d.ts +2 -2
- package/dist/util/permissions.d.ts +4 -4
- package/dist/util/references.d.ts +4 -2
- package/dist/util/resolutions.d.ts +6 -6
- package/dist/util/useTraceUpdate.d.ts +1 -0
- package/package.json +27 -24
- package/src/components/ClearFilterSortButton.tsx +41 -0
- package/src/components/DeleteEntityDialog.tsx +4 -4
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +268 -277
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +13 -13
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +9 -16
- package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +3 -3
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +27 -32
- package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +9 -5
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +51 -52
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
- package/src/components/EntityCollectionView/useSelectionController.tsx +30 -0
- package/src/components/EntityPreview.tsx +207 -70
- package/src/components/EntityView.tsx +84 -0
- package/src/components/FieldCaption.tsx +14 -0
- package/src/components/FireCMSAppBar.tsx +8 -0
- package/src/components/HomePage/DefaultHomePage.tsx +14 -10
- package/src/components/HomePage/NavigationCard.tsx +69 -0
- package/src/components/HomePage/NavigationCardBinding.tsx +116 -0
- package/src/components/HomePage/SmallNavigationCard.tsx +45 -0
- package/src/components/HomePage/index.tsx +3 -1
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -4
- package/src/components/ReferenceWidget.tsx +4 -4
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +23 -8
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +35 -24
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
- package/src/components/VirtualTable/VirtualTable.tsx +17 -7
- package/src/components/VirtualTable/VirtualTableProps.tsx +1 -1
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
- package/src/components/common/useDataSourceEntityCollectionTableController.tsx +1 -1
- package/src/components/index.tsx +4 -3
- package/src/contexts/AuthControllerContext.tsx +1 -1
- package/src/core/Drawer.tsx +66 -39
- package/src/{internal/EntityView.tsx → core/EntityEditView.tsx} +22 -39
- package/src/core/EntitySidePanel.tsx +2 -2
- package/src/core/FireCMS.tsx +18 -3
- package/src/core/NavigationRoutes.tsx +8 -0
- package/src/core/SideEntityView.tsx +38 -0
- package/src/core/field_configs.tsx +1 -2
- package/src/core/index.tsx +0 -2
- package/src/form/EntityForm.tsx +20 -12
- package/src/form/components/StorageItemPreview.tsx +5 -3
- package/src/form/components/StorageUploadProgress.tsx +6 -5
- package/src/form/components/index.tsx +1 -0
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -3
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +12 -15
- package/src/form/field_bindings/BlockFieldBinding.tsx +2 -3
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +4 -4
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +18 -18
- package/src/form/field_bindings/MapFieldBinding.tsx +17 -17
- package/src/form/field_bindings/MarkdownFieldBinding.tsx +1 -2
- package/src/form/field_bindings/MultiSelectBinding.tsx +2 -3
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +3 -3
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +5 -3
- package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
- package/src/form/field_bindings/SelectFieldBinding.tsx +2 -3
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +15 -6
- package/src/form/field_bindings/SwitchFieldBinding.tsx +2 -3
- package/src/form/field_bindings/TextFieldBinding.tsx +10 -9
- package/src/form/index.tsx +1 -0
- package/src/form/validation.ts +3 -4
- package/src/hooks/data/delete.ts +3 -3
- package/src/hooks/data/save.ts +1 -1
- package/src/hooks/data/useCollectionFetch.tsx +1 -1
- package/src/hooks/data/useDataSource.tsx +8 -3
- package/src/hooks/data/useEntityFetch.tsx +4 -4
- package/src/hooks/index.tsx +5 -1
- package/src/{core → hooks}/useBuildLocalConfigurationPersistence.tsx +9 -10
- package/src/{core → hooks}/useBuildModeController.tsx +12 -6
- package/src/hooks/useBuildNavigationController.tsx +197 -79
- package/src/hooks/useProjectLog.tsx +17 -7
- package/src/hooks/useReferenceDialog.tsx +2 -2
- package/src/hooks/useStorageSource.tsx +7 -2
- package/src/hooks/useValidateAuthenticator.tsx +135 -0
- package/src/internal/useBuildDataSource.ts +6 -1
- package/src/internal/useBuildSideEntityController.tsx +18 -12
- package/src/preview/PropertyPreview.tsx +1 -1
- package/src/preview/PropertyPreviewProps.tsx +1 -11
- package/src/preview/components/BooleanPreview.tsx +19 -4
- package/src/preview/components/EnumValuesChip.tsx +1 -1
- package/src/preview/components/ReferencePreview.tsx +55 -147
- package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
- package/src/types/analytics.ts +1 -0
- package/src/types/auth.tsx +50 -1
- package/src/types/collections.ts +24 -5
- package/src/types/datasource.ts +1 -1
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +4 -0
- package/src/types/entity_callbacks.ts +2 -2
- package/src/types/entity_overrides.tsx +7 -0
- package/src/types/firecms.tsx +0 -1
- package/src/types/index.ts +2 -0
- package/src/types/navigation.ts +17 -16
- package/src/types/permissions.ts +6 -1
- package/src/types/plugins.tsx +26 -28
- package/src/types/properties.ts +3 -2
- package/src/types/property_config.tsx +2 -2
- package/src/types/roles.ts +41 -0
- package/src/types/side_entity_controller.tsx +1 -0
- package/src/types/storage.ts +12 -3
- package/src/types/user.ts +7 -0
- package/src/util/collections.ts +22 -0
- package/src/util/entities.ts +1 -1
- package/src/util/icons.tsx +11 -3
- package/src/util/navigation_utils.ts +6 -6
- package/src/util/permissions.ts +11 -8
- package/src/util/references.ts +36 -5
- package/src/util/strings.ts +2 -2
- package/src/util/useTraceUpdate.tsx +2 -1
- package/dist/internal/useLocaleConfig.d.ts +0 -1
- package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
- package/src/internal/useLocaleConfig.tsx +0 -18
- /package/dist/{components → form/components}/LabelWithIcon.d.ts +0 -0
- /package/dist/{core → hooks}/useBuildLocalConfigurationPersistence.d.ts +0 -0
- /package/src/{components → form/components}/LabelWithIcon.tsx +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import equal from "react-fast-compare";
|
|
3
|
+
|
|
4
|
+
import { AppCheckTokenResult, AuthController, Authenticator, DataSourceDelegate, StorageSource, User } from "../index";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This hook is used internally for validating an authenticator.
|
|
8
|
+
*
|
|
9
|
+
* @param authController
|
|
10
|
+
* @param authentication
|
|
11
|
+
* @param getAppCheckToken
|
|
12
|
+
* @param appCheckForceRefresh
|
|
13
|
+
* @param storageSource
|
|
14
|
+
* @param dataSourceDelegate
|
|
15
|
+
*/
|
|
16
|
+
export function useValidateAuthenticator<UserType extends User = User, Controller extends AuthController<UserType> = AuthController<UserType>>({
|
|
17
|
+
disabled,
|
|
18
|
+
authController,
|
|
19
|
+
authenticator,
|
|
20
|
+
getAppCheckToken,
|
|
21
|
+
appCheckForceRefresh = false,
|
|
22
|
+
storageSource,
|
|
23
|
+
dataSourceDelegate
|
|
24
|
+
}:
|
|
25
|
+
{
|
|
26
|
+
disabled?: boolean,
|
|
27
|
+
authController: Controller,
|
|
28
|
+
authenticator?: boolean | Authenticator<UserType, Controller>,
|
|
29
|
+
getAppCheckToken?: (forceRefresh: boolean) => Promise<AppCheckTokenResult> | undefined,
|
|
30
|
+
appCheckForceRefresh?: boolean,
|
|
31
|
+
dataSourceDelegate: DataSourceDelegate;
|
|
32
|
+
storageSource: StorageSource;
|
|
33
|
+
}): {
|
|
34
|
+
canAccessMainView: boolean,
|
|
35
|
+
authLoading: boolean,
|
|
36
|
+
notAllowedError: any,
|
|
37
|
+
authVerified: boolean,
|
|
38
|
+
} {
|
|
39
|
+
|
|
40
|
+
const authenticationEnabled = Boolean(authenticator);
|
|
41
|
+
|
|
42
|
+
const [authLoading, setAuthLoading] = useState<boolean>(authenticationEnabled);
|
|
43
|
+
const [notAllowedError, setNotAllowedError] = useState<any>(false);
|
|
44
|
+
const [authVerified, setAuthVerified] = useState<boolean>(!authenticationEnabled || Boolean(authController.loginSkipped));
|
|
45
|
+
|
|
46
|
+
const canAccessMainView = (authVerified) &&
|
|
47
|
+
(!authenticationEnabled || Boolean(authController.user) || Boolean(authController.loginSkipped)) &&
|
|
48
|
+
!notAllowedError;
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (authController.loginSkipped)
|
|
52
|
+
setAuthVerified(true);
|
|
53
|
+
}, [authController.loginSkipped]);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* We use this ref to check the authentication only if the user has
|
|
57
|
+
* changed.
|
|
58
|
+
*/
|
|
59
|
+
const checkedUserRef = useRef<User | undefined>();
|
|
60
|
+
|
|
61
|
+
const checkAuthentication = useCallback(async () => {
|
|
62
|
+
|
|
63
|
+
if (disabled) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (authController.initialLoading) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!authController.user && !authController.loginSkipped) {
|
|
72
|
+
checkedUserRef.current = undefined;
|
|
73
|
+
setAuthLoading(false);
|
|
74
|
+
setAuthVerified(false);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const delegateUser = authController.user;
|
|
79
|
+
console.debug("Checking authentication for user", delegateUser);
|
|
80
|
+
|
|
81
|
+
if (getAppCheckToken) {
|
|
82
|
+
try {
|
|
83
|
+
if (!await getAppCheckToken(appCheckForceRefresh)) {
|
|
84
|
+
setNotAllowedError("App Check failed.");
|
|
85
|
+
authController.signOut();
|
|
86
|
+
} else {
|
|
87
|
+
console.debug("App Check success.");
|
|
88
|
+
}
|
|
89
|
+
} catch (e: any) {
|
|
90
|
+
setNotAllowedError(e.message);
|
|
91
|
+
authController.signOut();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (authenticator instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
|
|
96
|
+
setAuthLoading(true);
|
|
97
|
+
try {
|
|
98
|
+
const allowed = await authenticator({
|
|
99
|
+
user: delegateUser,
|
|
100
|
+
authController,
|
|
101
|
+
dataSourceDelegate,
|
|
102
|
+
storageSource
|
|
103
|
+
});
|
|
104
|
+
if (!allowed) {
|
|
105
|
+
authController.signOut();
|
|
106
|
+
setNotAllowedError(true);
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {
|
|
109
|
+
setNotAllowedError(e);
|
|
110
|
+
authController.signOut();
|
|
111
|
+
}
|
|
112
|
+
setAuthLoading(false);
|
|
113
|
+
setAuthVerified(true);
|
|
114
|
+
checkedUserRef.current = delegateUser;
|
|
115
|
+
} else {
|
|
116
|
+
setAuthLoading(false);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!authController.initialLoading && !delegateUser) {
|
|
120
|
+
setAuthVerified(true);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}, [disabled, authController, authenticator, getAppCheckToken, appCheckForceRefresh, dataSourceDelegate, storageSource]);
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
checkAuthentication();
|
|
127
|
+
}, [checkAuthentication]);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
canAccessMainView,
|
|
131
|
+
authLoading: authenticationEnabled && authLoading,
|
|
132
|
+
notAllowedError,
|
|
133
|
+
authVerified
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -330,6 +330,10 @@ export function useBuildDataSource({
|
|
|
330
330
|
* bindings.
|
|
331
331
|
* Also, Firestore references are replaced with {@link EntityReference}
|
|
332
332
|
* @param data
|
|
333
|
+
* @param buildReference
|
|
334
|
+
* @param buildGeoPoint
|
|
335
|
+
* @param buildDate
|
|
336
|
+
* @param buildDelete
|
|
333
337
|
* @group Firestore
|
|
334
338
|
*/
|
|
335
339
|
export function cmsToDelegateModel(data: any,
|
|
@@ -340,6 +344,8 @@ export function cmsToDelegateModel(data: any,
|
|
|
340
344
|
): any {
|
|
341
345
|
if (data === undefined) {
|
|
342
346
|
return buildDelete();
|
|
347
|
+
} else if (data === null) {
|
|
348
|
+
return null;
|
|
343
349
|
} else if (Array.isArray(data)) {
|
|
344
350
|
return data.map(v => cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete));
|
|
345
351
|
} else if (data.isEntityReference && data.isEntityReference()) {
|
|
@@ -355,4 +361,3 @@ export function cmsToDelegateModel(data: any,
|
|
|
355
361
|
}
|
|
356
362
|
return data;
|
|
357
363
|
}
|
|
358
|
-
|
|
@@ -34,6 +34,7 @@ export const useBuildSideEntityController = (navigation: NavigationController,
|
|
|
34
34
|
// only on initialisation, create panels from URL
|
|
35
35
|
useEffect(() => {
|
|
36
36
|
if (!navigation.loading && !initialised.current) {
|
|
37
|
+
console.debug("Initialising side entity controller");
|
|
37
38
|
if (navigation.isUrlCollectionPath(location.pathname)) {
|
|
38
39
|
const newFlag = location.hash === `#${NEW_URL_HASH}`;
|
|
39
40
|
const entityOrCollectionPath = navigation.urlPathToDataPath(location.pathname);
|
|
@@ -42,15 +43,17 @@ export const useBuildSideEntityController = (navigation: NavigationController,
|
|
|
42
43
|
const panel = panelsFromUrl[i];
|
|
43
44
|
setTimeout(() => {
|
|
44
45
|
if (i === 0)
|
|
45
|
-
sideDialogsController.replace(propsToSidePanel(panel, navigation, smallLayout));
|
|
46
|
+
sideDialogsController.replace(propsToSidePanel(panel, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout));
|
|
46
47
|
else
|
|
47
|
-
sideDialogsController.open(propsToSidePanel(panel, navigation, smallLayout))
|
|
48
|
+
sideDialogsController.open(propsToSidePanel(panel, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout))
|
|
48
49
|
}, 1);
|
|
49
50
|
}
|
|
51
|
+
} else {
|
|
52
|
+
// console.warn("Location path is not a collection path");
|
|
50
53
|
}
|
|
51
54
|
initialised.current = true;
|
|
52
55
|
}
|
|
53
|
-
}, [location, navigation, sideDialogsController, smallLayout]);
|
|
56
|
+
}, [location, navigation.loading, navigation.isUrlCollectionPath, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, sideDialogsController, smallLayout, navigation]);
|
|
54
57
|
|
|
55
58
|
const close = useCallback(() => {
|
|
56
59
|
sideDialogsController.close();
|
|
@@ -73,9 +76,9 @@ export const useBuildSideEntityController = (navigation: NavigationController,
|
|
|
73
76
|
sideDialogsController.open(propsToSidePanel({
|
|
74
77
|
selectedSubPath: defaultSelectedView,
|
|
75
78
|
...props,
|
|
76
|
-
}, navigation, smallLayout));
|
|
79
|
+
}, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout));
|
|
77
80
|
|
|
78
|
-
}, [sideDialogsController, navigation, smallLayout]);
|
|
81
|
+
}, [sideDialogsController, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout]);
|
|
79
82
|
|
|
80
83
|
const replace = useCallback((props: EntitySidePanelProps<any>) => {
|
|
81
84
|
|
|
@@ -83,9 +86,9 @@ export const useBuildSideEntityController = (navigation: NavigationController,
|
|
|
83
86
|
throw Error("If you want to copy an entity you need to provide an entityId");
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
sideDialogsController.replace(propsToSidePanel(props, navigation, smallLayout));
|
|
89
|
+
sideDialogsController.replace(propsToSidePanel(props, navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, smallLayout));
|
|
87
90
|
|
|
88
|
-
}, [navigation, sideDialogsController, smallLayout]);
|
|
91
|
+
}, [navigation.buildUrlCollectionPath, navigation.resolveAliasesFrom, sideDialogsController, smallLayout]);
|
|
89
92
|
|
|
90
93
|
return {
|
|
91
94
|
close,
|
|
@@ -146,14 +149,17 @@ export function buildSidePanelsFromUrl(path: string, collections: EntityCollecti
|
|
|
146
149
|
return sidePanels;
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
const propsToSidePanel = (props: EntitySidePanelProps<any>,
|
|
152
|
+
const propsToSidePanel = (props: EntitySidePanelProps<any>,
|
|
153
|
+
buildUrlCollectionPath: (path: string) => string,
|
|
154
|
+
resolveAliasesFrom: (pathWithAliases: string) => string,
|
|
155
|
+
smallLayout: boolean): SideDialogPanelProps => {
|
|
150
156
|
|
|
151
157
|
const collectionPath = removeInitialAndTrailingSlashes(props.path);
|
|
152
158
|
|
|
153
159
|
const newPath = props.entityId
|
|
154
|
-
?
|
|
155
|
-
:
|
|
156
|
-
const resolvedPath =
|
|
160
|
+
? buildUrlCollectionPath(`${collectionPath}/${props.entityId}/${props.selectedSubPath || ""}`)
|
|
161
|
+
: buildUrlCollectionPath(`${collectionPath}#${NEW_URL_HASH}`);
|
|
162
|
+
const resolvedPath = resolveAliasesFrom(props.path);
|
|
157
163
|
|
|
158
164
|
const resolvedPanelProps: EntitySidePanelProps<any> = {
|
|
159
165
|
...props,
|
|
@@ -164,7 +170,7 @@ const propsToSidePanel = (props: EntitySidePanelProps<any>, navigation: Navigati
|
|
|
164
170
|
key: `${props.path}/${props.entityId}`,
|
|
165
171
|
component: <EntitySidePanel {...resolvedPanelProps}/>,
|
|
166
172
|
urlPath: newPath,
|
|
167
|
-
parentUrlPath:
|
|
173
|
+
parentUrlPath: buildUrlCollectionPath(collectionPath),
|
|
168
174
|
width: getEntityViewWidth(props, smallLayout),
|
|
169
175
|
onClose: props.onClose
|
|
170
176
|
});
|
|
@@ -195,7 +195,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
195
195
|
|
|
196
196
|
} else if (property.dataType === "boolean") {
|
|
197
197
|
if (typeof value === "boolean") {
|
|
198
|
-
content = <BooleanPreview value={value}/>;
|
|
198
|
+
content = <BooleanPreview value={value} size={size} property={property}/>;
|
|
199
199
|
} else {
|
|
200
200
|
content = buildWrongValueType(propertyKey, property.dataType, value);
|
|
201
201
|
}
|
|
@@ -8,7 +8,7 @@ export type PreviewSize = "medium" | "small" | "tiny";
|
|
|
8
8
|
/**
|
|
9
9
|
* @group Preview components
|
|
10
10
|
*/
|
|
11
|
-
export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
|
|
11
|
+
export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any> {
|
|
12
12
|
/**
|
|
13
13
|
* Name of the property
|
|
14
14
|
*/
|
|
@@ -24,11 +24,6 @@ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
|
|
|
24
24
|
*/
|
|
25
25
|
property: Property<T> | ResolvedProperty<T>;
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
* Click handler
|
|
29
|
-
*/
|
|
30
|
-
// onClick?: () => void;
|
|
31
|
-
|
|
32
27
|
/**
|
|
33
28
|
* Desired size of the preview, depending on the context.
|
|
34
29
|
*/
|
|
@@ -51,9 +46,4 @@ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
|
|
|
51
46
|
*/
|
|
52
47
|
customProps?: CustomProps;
|
|
53
48
|
|
|
54
|
-
/**
|
|
55
|
-
* Entity this property refers to
|
|
56
|
-
*/
|
|
57
|
-
// entity?: Entity<M>;
|
|
58
|
-
|
|
59
49
|
}
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Checkbox } from "@firecms/ui";
|
|
2
|
+
import { Checkbox, cn } from "@firecms/ui";
|
|
3
|
+
import { PreviewSize } from "../PropertyPreviewProps";
|
|
4
|
+
import { Property } from "../../types";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* @group Preview components
|
|
6
8
|
*/
|
|
7
|
-
export function BooleanPreview({
|
|
8
|
-
|
|
9
|
+
export function BooleanPreview({
|
|
10
|
+
value,
|
|
11
|
+
size,
|
|
12
|
+
property
|
|
13
|
+
}: {
|
|
14
|
+
value: boolean,
|
|
15
|
+
size: PreviewSize,
|
|
16
|
+
property: Property,
|
|
9
17
|
}): React.ReactElement {
|
|
10
|
-
return <
|
|
18
|
+
return <div className={"flex flex-row gap-2 items-center"}>
|
|
19
|
+
<Checkbox checked={value}
|
|
20
|
+
padding={false}
|
|
21
|
+
size={size}
|
|
22
|
+
color={"secondary"}/>
|
|
23
|
+
{property.name && <span
|
|
24
|
+
className={cn("text-text-secondary dark:text-text-secondary-dark", size === "tiny" ? "text-sm" : "")}>{property.name}</span>}
|
|
25
|
+
</div>;
|
|
11
26
|
}
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
2
|
|
|
4
|
-
import { Entity, EntityCollection, EntityReference
|
|
5
|
-
|
|
6
|
-
import { getReferencePreviewKeys, getValueInPath, resolveCollection } from "../../util";
|
|
3
|
+
import { Entity, EntityCollection, EntityReference } from "../../types";
|
|
7
4
|
import {
|
|
8
5
|
useCustomizationController,
|
|
9
6
|
useEntityFetch,
|
|
10
7
|
useNavigationController,
|
|
11
8
|
useSideEntityController
|
|
12
9
|
} from "../../hooks";
|
|
13
|
-
import { PropertyPreview } from "../PropertyPreview";
|
|
14
10
|
import { PreviewSize } from "../PropertyPreviewProps";
|
|
15
|
-
import {
|
|
16
|
-
import { cn, IconButton, KeyboardTabIcon, Skeleton, Tooltip, Typography } from "@firecms/ui";
|
|
11
|
+
import { IconButton, KeyboardTabIcon, Skeleton, Tooltip } from "@firecms/ui";
|
|
17
12
|
import { ErrorView } from "../../components";
|
|
18
13
|
import { useAnalyticsController } from "../../hooks/useAnalyticsController";
|
|
14
|
+
import { EntityPreview, EntityPreviewContainer } from "../../components/EntityPreview";
|
|
19
15
|
|
|
20
16
|
export type ReferencePreviewProps = {
|
|
21
17
|
disabled?: boolean;
|
|
@@ -23,7 +19,7 @@ export type ReferencePreviewProps = {
|
|
|
23
19
|
size: PreviewSize;
|
|
24
20
|
previewProperties?: string[];
|
|
25
21
|
onClick?: (e: React.SyntheticEvent) => void;
|
|
26
|
-
|
|
22
|
+
hover?: boolean;
|
|
27
23
|
allowEntityNavigation?: boolean;
|
|
28
24
|
};
|
|
29
25
|
|
|
@@ -34,12 +30,12 @@ export const ReferencePreview = React.memo<ReferencePreviewProps>(function Refer
|
|
|
34
30
|
const reference = props.reference;
|
|
35
31
|
if (!(typeof reference === "object" && "isEntityReference" in reference && reference.isEntityReference())) {
|
|
36
32
|
console.warn("Reference preview received value of type", typeof reference);
|
|
37
|
-
return <
|
|
33
|
+
return <EntityPreviewContainer
|
|
38
34
|
onClick={props.onClick}
|
|
39
35
|
size={props.size}>
|
|
40
36
|
<ErrorView error={"Unexpected value. Click to edit"}
|
|
41
37
|
tooltip={JSON.stringify(reference)}/>
|
|
42
|
-
</
|
|
38
|
+
</EntityPreviewContainer>;
|
|
43
39
|
}
|
|
44
40
|
return <ReferencePreviewInternal {...props} />;
|
|
45
41
|
}, areEqual) as React.FunctionComponent<ReferencePreviewProps>;
|
|
@@ -47,7 +43,7 @@ export const ReferencePreview = React.memo<ReferencePreviewProps>(function Refer
|
|
|
47
43
|
function areEqual(prevProps: ReferencePreviewProps, nextProps: ReferencePreviewProps) {
|
|
48
44
|
return prevProps.disabled === nextProps.disabled &&
|
|
49
45
|
prevProps.size === nextProps.size &&
|
|
50
|
-
prevProps.
|
|
46
|
+
prevProps.hover === nextProps.hover &&
|
|
51
47
|
prevProps.reference?.id === nextProps.reference?.id &&
|
|
52
48
|
prevProps.reference?.path === nextProps.reference?.path &&
|
|
53
49
|
prevProps.allowEntityNavigation === nextProps.allowEntityNavigation
|
|
@@ -59,7 +55,7 @@ function ReferencePreviewInternal<M extends Record<string, any>>({
|
|
|
59
55
|
reference,
|
|
60
56
|
previewProperties,
|
|
61
57
|
size,
|
|
62
|
-
|
|
58
|
+
hover,
|
|
63
59
|
onClick,
|
|
64
60
|
allowEntityNavigation = true
|
|
65
61
|
}: ReferencePreviewProps) {
|
|
@@ -68,7 +64,7 @@ function ReferencePreviewInternal<M extends Record<string, any>>({
|
|
|
68
64
|
|
|
69
65
|
const navigationController = useNavigationController();
|
|
70
66
|
|
|
71
|
-
const collection = navigationController.getCollection
|
|
67
|
+
const collection = navigationController.getCollection(reference.path);
|
|
72
68
|
if (!collection) {
|
|
73
69
|
if (customizationController.components?.missingReference) {
|
|
74
70
|
return <customizationController.components.missingReference path={reference.path}/>;
|
|
@@ -85,18 +81,22 @@ function ReferencePreviewInternal<M extends Record<string, any>>({
|
|
|
85
81
|
disabled={disabled}
|
|
86
82
|
allowEntityNavigation={allowEntityNavigation}
|
|
87
83
|
onClick={onClick}
|
|
88
|
-
|
|
84
|
+
hover={hover}/>
|
|
89
85
|
}
|
|
90
86
|
|
|
91
|
-
function ReferencePreviewExisting<M extends Record<string, any> = any>({
|
|
87
|
+
function ReferencePreviewExisting<M extends Record<string, any> = any>({
|
|
88
|
+
reference,
|
|
89
|
+
collection,
|
|
90
|
+
previewProperties,
|
|
91
|
+
size,
|
|
92
|
+
disabled,
|
|
93
|
+
allowEntityNavigation,
|
|
94
|
+
onClick,
|
|
95
|
+
hover
|
|
96
|
+
}: ReferencePreviewProps & {
|
|
92
97
|
collection: EntityCollection<M>
|
|
93
98
|
}) {
|
|
94
99
|
|
|
95
|
-
const customizationController = useCustomizationController();
|
|
96
|
-
|
|
97
|
-
const analyticsController = useAnalyticsController();
|
|
98
|
-
const sideEntityController = useSideEntityController();
|
|
99
|
-
|
|
100
100
|
const {
|
|
101
101
|
entity,
|
|
102
102
|
dataLoading,
|
|
@@ -114,145 +114,53 @@ function ReferencePreviewExisting<M extends Record<string, any> = any>({ referen
|
|
|
114
114
|
|
|
115
115
|
const usedEntity = entity ?? referencesCache.get(reference.pathWithId);
|
|
116
116
|
|
|
117
|
-
const resolvedCollection = useMemo(() => resolveCollection({
|
|
118
|
-
collection,
|
|
119
|
-
path: reference.path,
|
|
120
|
-
values: usedEntity?.values,
|
|
121
|
-
fields: customizationController.propertyConfigs
|
|
122
|
-
}), [collection]);
|
|
123
|
-
|
|
124
|
-
const listProperties = useMemo(() => getReferencePreviewKeys(resolvedCollection, customizationController.propertyConfigs, previewProperties, size === "small" || size === "medium" ? 3 : 1),
|
|
125
|
-
[previewProperties, resolvedCollection, size]);
|
|
126
|
-
|
|
127
117
|
let body: React.ReactNode;
|
|
128
118
|
|
|
129
|
-
if (!resolvedCollection) {
|
|
130
|
-
return <ErrorView
|
|
131
|
-
error={"Could not find collection with id " + resolvedCollection}/>
|
|
132
|
-
}
|
|
133
|
-
|
|
134
119
|
if (!reference) {
|
|
135
120
|
body = <ErrorView error={"Reference not set"}/>;
|
|
136
121
|
} else if (usedEntity && !usedEntity.values) {
|
|
137
122
|
body = <ErrorView error={"Reference does not exist"}
|
|
138
123
|
tooltip={reference.path}/>;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
size !== "medium"
|
|
149
|
-
? "block whitespace-nowrap overflow-hidden truncate"
|
|
150
|
-
: ""
|
|
151
|
-
}`}>
|
|
152
|
-
<Typography variant={"caption"}
|
|
153
|
-
className={"font-mono"}>
|
|
154
|
-
{reference.id}
|
|
155
|
-
</Typography>
|
|
156
|
-
</div>
|
|
157
|
-
: <Skeleton/>)}
|
|
158
|
-
|
|
159
|
-
{listProperties && listProperties.map((key) => {
|
|
160
|
-
const childProperty = resolvedCollection.properties[key as string];
|
|
161
|
-
if (!childProperty) return null;
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<div key={"ref_prev_" + (key as string)}
|
|
165
|
-
className={listProperties.length > 1 ? "my-0.5" : "my-0"}>
|
|
166
|
-
{
|
|
167
|
-
usedEntity
|
|
168
|
-
? <PropertyPreview
|
|
169
|
-
propertyKey={key as string}
|
|
170
|
-
value={getValueInPath(usedEntity.values, key)}
|
|
171
|
-
property={childProperty as ResolvedProperty}
|
|
172
|
-
// entity={usedEntity}
|
|
173
|
-
size={"tiny"}/>
|
|
174
|
-
: <SkeletonPropertyComponent
|
|
175
|
-
property={childProperty as ResolvedProperty}
|
|
176
|
-
size={"tiny"}/>
|
|
177
|
-
}
|
|
178
|
-
</div>
|
|
179
|
-
);
|
|
180
|
-
})}
|
|
181
|
-
|
|
182
|
-
</div>
|
|
183
|
-
<div className={`my-${size === "tiny" ? 2 : 4}`}>
|
|
184
|
-
{!disabled && usedEntity && allowEntityNavigation &&
|
|
185
|
-
<Tooltip title={`See details for ${usedEntity.id}`}>
|
|
186
|
-
<IconButton
|
|
187
|
-
color={"inherit"}
|
|
188
|
-
size={"small"}
|
|
189
|
-
onClick={(e) => {
|
|
190
|
-
e.stopPropagation();
|
|
191
|
-
analyticsController.onAnalyticsEvent?.("entity_click_from_reference", {
|
|
192
|
-
path: usedEntity.path,
|
|
193
|
-
entityId: usedEntity.id
|
|
194
|
-
});
|
|
195
|
-
sideEntityController.open({
|
|
196
|
-
entityId: usedEntity.id,
|
|
197
|
-
path: usedEntity.path,
|
|
198
|
-
collection: resolvedCollection,
|
|
199
|
-
updateUrl: true
|
|
200
|
-
});
|
|
201
|
-
}}>
|
|
202
|
-
<KeyboardTabIcon size={"small"}/>
|
|
203
|
-
</IconButton>
|
|
204
|
-
</Tooltip>}
|
|
205
|
-
</div>
|
|
206
|
-
</>
|
|
124
|
+
}
|
|
125
|
+
if (body) {
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<EntityPreviewContainer onClick={disabled ? undefined : onClick}
|
|
129
|
+
hover={disabled ? undefined : hover}
|
|
130
|
+
size={size}>
|
|
131
|
+
{body}
|
|
132
|
+
</EntityPreviewContainer>
|
|
207
133
|
);
|
|
208
134
|
}
|
|
209
135
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
export function ReferencePreviewContainer({
|
|
220
|
-
children,
|
|
221
|
-
onHover,
|
|
222
|
-
size,
|
|
223
|
-
onClick
|
|
224
|
-
}: {
|
|
225
|
-
children: React.ReactNode;
|
|
226
|
-
onHover?: boolean;
|
|
227
|
-
size: PreviewSize;
|
|
228
|
-
onClick?: (e: React.SyntheticEvent) => void;
|
|
229
|
-
}) {
|
|
230
|
-
return <Typography variant={"label"}
|
|
231
|
-
className={cn("bg-opacity-70 bg-gray-100 dark:bg-gray-800 dark:bg-opacity-60",
|
|
232
|
-
"w-full",
|
|
233
|
-
"flex",
|
|
234
|
-
"rounded-md",
|
|
235
|
-
"overflow-hidden",
|
|
236
|
-
onHover ? "hover:bg-opacity-90 dark:hover:bg-opacity-90" : "",
|
|
237
|
-
size === "medium" ? "p-2" : "p-1",
|
|
238
|
-
size === "tiny" ? "items-center" : "",
|
|
239
|
-
"transition-colors duration-300 ease-in-out ",
|
|
240
|
-
// onHover ? "shadow-outline" : "",
|
|
241
|
-
onClick ? "cursor-pointer" : "")}
|
|
242
|
-
style={{
|
|
243
|
-
// @ts-ignore
|
|
244
|
-
tabindex: 0
|
|
245
|
-
}}
|
|
246
|
-
onClick={(event) => {
|
|
247
|
-
if (onClick) {
|
|
248
|
-
event.preventDefault();
|
|
249
|
-
onClick(event);
|
|
250
|
-
}
|
|
251
|
-
}}>
|
|
136
|
+
if (dataLoading && !usedEntity) {
|
|
137
|
+
return (
|
|
138
|
+
<EntityPreviewContainer onClick={disabled ? undefined : onClick}
|
|
139
|
+
hover={disabled ? undefined : hover}
|
|
140
|
+
size={size}>
|
|
141
|
+
<Skeleton/>
|
|
142
|
+
</EntityPreviewContainer>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
252
145
|
|
|
253
|
-
|
|
146
|
+
if (!usedEntity) {
|
|
147
|
+
return (
|
|
148
|
+
<EntityPreviewContainer onClick={disabled ? undefined : onClick}
|
|
149
|
+
hover={disabled ? undefined : hover}
|
|
150
|
+
size={size}>
|
|
151
|
+
<ErrorView error={"Entity not found"}/>
|
|
152
|
+
</EntityPreviewContainer>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return <EntityPreview size={size}
|
|
156
|
+
previewProperties={previewProperties}
|
|
157
|
+
disabled={disabled}
|
|
158
|
+
entity={usedEntity}
|
|
159
|
+
collection={collection}
|
|
160
|
+
onClick={onClick}
|
|
161
|
+
includeEntityNavigation={allowEntityNavigation}
|
|
162
|
+
hover={hover}/>;
|
|
254
163
|
|
|
255
|
-
</Typography>
|
|
256
164
|
}
|
|
257
165
|
|
|
258
166
|
const referencesCache = new Map<string, Entity<any>>();
|
|
@@ -6,7 +6,7 @@ import { PreviewType } from "../../types";
|
|
|
6
6
|
import { UrlComponentPreview } from "../components/UrlComponentPreview";
|
|
7
7
|
import { PropertyPreviewProps } from "../PropertyPreviewProps";
|
|
8
8
|
import { ErrorBoundary } from "../../components";
|
|
9
|
-
import { Chip, getColorSchemeForSeed } from "@firecms/ui";
|
|
9
|
+
import { Chip, cn, getColorSchemeForSeed } from "@firecms/ui";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @group Preview components
|
|
@@ -24,14 +24,14 @@ export function StringPropertyPreview({
|
|
|
24
24
|
return <EnumValuesChip
|
|
25
25
|
enumKey={enumKey}
|
|
26
26
|
enumValues={resolvedProperty.enumValues}
|
|
27
|
-
size={size
|
|
27
|
+
size={size}/>;
|
|
28
28
|
} else if (property.previewAsTag) {
|
|
29
29
|
const colorScheme = getColorSchemeForSeed(propertyKey ?? "");
|
|
30
30
|
return (
|
|
31
31
|
<ErrorBoundary>
|
|
32
32
|
<Chip
|
|
33
33
|
colorScheme={colorScheme}
|
|
34
|
-
size={size
|
|
34
|
+
size={size}>
|
|
35
35
|
{value}
|
|
36
36
|
</Chip>
|
|
37
37
|
</ErrorBoundary>);
|
|
@@ -45,15 +45,16 @@ export function StringPropertyPreview({
|
|
|
45
45
|
if (!value) return <></>;
|
|
46
46
|
const lines = value.split("\n");
|
|
47
47
|
return value && value.includes("\n")
|
|
48
|
-
? <div className={"overflow-x-scroll"}>
|
|
48
|
+
? <div className={cn("overflow-x-scroll", size === "tiny" ? "text-sm" : "")}>
|
|
49
49
|
{lines.map((str, index) =>
|
|
50
50
|
<React.Fragment key={`string_preview_${index}`}>
|
|
51
51
|
<span>{str}</span>
|
|
52
52
|
{index !== lines.length - 1 && <br/>}
|
|
53
53
|
</React.Fragment>)}
|
|
54
54
|
</div>
|
|
55
|
-
:
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
: (size === "tiny"
|
|
56
|
+
? <span className={"text-sm"}>{value}</span>
|
|
57
|
+
: <>{value}</>
|
|
58
|
+
);
|
|
58
59
|
}
|
|
59
60
|
}
|