@firecms/core 3.0.0-canary.1 → 3.0.0-canary.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +2 -2
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +1 -2
- 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/ReferenceWidget.d.ts +3 -3
- 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/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 +5 -2
- 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 +8118 -7754
- 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 +17 -4
- package/dist/types/datasource.d.ts +1 -1
- package/dist/types/entities.d.ts +1 -0
- 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 +10 -9
- package/dist/types/permissions.d.ts +5 -1
- package/dist/types/plugins.d.ts +17 -19
- 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/user.d.ts +5 -0
- package/dist/util/collections.d.ts +9 -1
- package/dist/util/icons.d.ts +8 -2
- package/dist/util/index.d.ts +1 -0
- package/dist/util/permissions.d.ts +4 -4
- package/dist/util/references.d.ts +4 -2
- package/dist/util/resolutions.d.ts +1 -1
- package/dist/util/useTraceUpdate.d.ts +1 -0
- package/package.json +23 -23
- package/src/components/DeleteEntityDialog.tsx +4 -4
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -3
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +273 -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 +1 -1
- package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +9 -5
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +28 -49
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
- package/src/components/EntityCollectionView/useSelectionController.tsx +30 -0
- package/src/components/EntityPreview.tsx +204 -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 +7 -7
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +11 -19
- package/src/components/VirtualTable/VirtualTableProps.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 -2
- package/src/core/NavigationRoutes.tsx +8 -0
- package/src/core/SideEntityView.tsx +38 -0
- 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 +3 -3
- 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 +6 -4
- 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}/useBuildModeController.tsx +1 -1
- package/src/hooks/useBuildNavigationController.tsx +134 -75
- package/src/hooks/useProjectLog.tsx +16 -6
- 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 +7 -2
- package/src/preview/PropertyPreview.tsx +2 -2
- 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 +19 -4
- package/src/types/datasource.ts +1 -1
- package/src/types/entities.ts +4 -0
- 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 +11 -10
- package/src/types/permissions.ts +6 -1
- package/src/types/plugins.tsx +24 -27
- 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/user.ts +7 -0
- package/src/util/collections.ts +22 -0
- package/src/util/icons.tsx +11 -3
- package/src/util/index.ts +1 -0
- package/src/util/permissions.ts +11 -8
- package/src/util/references.ts +36 -5
- package/src/util/useTraceUpdate.tsx +1 -1
- package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
- /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
- /package/src/{core → hooks}/useBuildLocalConfigurationPersistence.tsx +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useCallback, useEffect,
|
|
2
|
-
import
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import equal from "react-fast-compare"
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
AuthController,
|
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
EntityCollectionsBuilder,
|
|
11
11
|
EntityReference,
|
|
12
12
|
NavigationController,
|
|
13
|
+
PermissionsBuilder,
|
|
13
14
|
TopNavigationEntry,
|
|
14
15
|
TopNavigationResult,
|
|
15
16
|
User,
|
|
16
17
|
UserConfigurationPersistence
|
|
17
18
|
} from "../types";
|
|
18
19
|
import {
|
|
20
|
+
applyPermissionsFunctionIfEmpty,
|
|
19
21
|
getCollectionByPathOrId,
|
|
20
22
|
mergeDeep,
|
|
21
23
|
removeInitialAndTrailingSlashes,
|
|
@@ -32,7 +34,10 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
|
|
|
32
34
|
baseCollectionPath?: string,
|
|
33
35
|
authController: AuthController<UserType>;
|
|
34
36
|
collections?: EC[] | EntityCollectionsBuilder<EC>;
|
|
37
|
+
collectionPermissions?: PermissionsBuilder;
|
|
35
38
|
views?: CMSView[] | CMSViewsBuilder;
|
|
39
|
+
adminViews?: CMSView[] | CMSViewsBuilder;
|
|
40
|
+
viewsOrder?: string[];
|
|
36
41
|
userConfigPersistence?: UserConfigurationPersistence;
|
|
37
42
|
dataSourceDelegate: DataSourceDelegate;
|
|
38
43
|
/**
|
|
@@ -45,21 +50,25 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
|
|
|
45
50
|
injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
|
|
46
51
|
};
|
|
47
52
|
|
|
48
|
-
export function useBuildNavigationController<EC extends EntityCollection, UserType extends User>({
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
53
|
+
export function useBuildNavigationController<EC extends EntityCollection, UserType extends User>(props: BuildNavigationContextProps<EC, UserType>): NavigationController {
|
|
54
|
+
const {
|
|
55
|
+
basePath = DEFAULT_BASE_PATH,
|
|
56
|
+
baseCollectionPath = DEFAULT_COLLECTION_PATH,
|
|
57
|
+
authController,
|
|
58
|
+
collections: collectionsProp,
|
|
59
|
+
collectionPermissions,
|
|
60
|
+
views: viewsProp,
|
|
61
|
+
adminViews: adminViewsProp,
|
|
62
|
+
viewsOrder,
|
|
63
|
+
userConfigPersistence,
|
|
64
|
+
dataSourceDelegate,
|
|
65
|
+
injectCollections
|
|
66
|
+
} = props;
|
|
67
|
+
|
|
68
|
+
const collectionsRef = useRef<EntityCollection[] | undefined>();
|
|
69
|
+
const viewsRef = useRef<CMSView[] | undefined>();
|
|
70
|
+
const adminViewsRef = useRef<CMSView[] | undefined>();
|
|
71
|
+
|
|
63
72
|
const [initialised, setInitialised] = useState<boolean>(false);
|
|
64
73
|
|
|
65
74
|
const [topLevelNavigation, setTopLevelNavigation] = useState<TopNavigationResult | undefined>(undefined);
|
|
@@ -79,19 +88,18 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
79
88
|
const buildUrlCollectionPath = useCallback((path: string): string => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path)}`,
|
|
80
89
|
[baseCollectionPath]);
|
|
81
90
|
|
|
82
|
-
const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[]): TopNavigationResult => {
|
|
83
|
-
|
|
84
|
-
const navigationEntries: TopNavigationEntry[] = [
|
|
91
|
+
const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[], adminViews: CMSView[], viewsOrder?: string[]): TopNavigationResult => {
|
|
92
|
+
let navigationEntries: TopNavigationEntry[] = [
|
|
85
93
|
...(collections ?? []).map(collection => (!collection.hideFromNavigation
|
|
86
|
-
? {
|
|
94
|
+
? ({
|
|
87
95
|
url: buildUrlCollectionPath(collection.id ?? collection.path),
|
|
88
96
|
type: "collection",
|
|
89
97
|
name: collection.name.trim(),
|
|
90
98
|
path: collection.id ?? collection.path,
|
|
91
99
|
collection,
|
|
92
100
|
description: collection.description?.trim(),
|
|
93
|
-
group: collection
|
|
94
|
-
}
|
|
101
|
+
group: getGroup(collection)
|
|
102
|
+
} satisfies TopNavigationEntry)
|
|
95
103
|
: undefined))
|
|
96
104
|
.filter(Boolean) as TopNavigationEntry[],
|
|
97
105
|
...(views ?? []).map(view =>
|
|
@@ -100,18 +108,50 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
100
108
|
url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
|
|
101
109
|
name: view.name.trim(),
|
|
102
110
|
type: "view",
|
|
111
|
+
path: view.path,
|
|
103
112
|
view,
|
|
104
113
|
description: view.description?.trim(),
|
|
105
|
-
group: view
|
|
106
|
-
})
|
|
114
|
+
group: getGroup(view)
|
|
115
|
+
} satisfies TopNavigationEntry)
|
|
116
|
+
: undefined)
|
|
117
|
+
.filter(Boolean) as TopNavigationEntry[],
|
|
118
|
+
...(adminViews ?? []).map(view =>
|
|
119
|
+
!view.hideFromNavigation
|
|
120
|
+
? ({
|
|
121
|
+
url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
|
|
122
|
+
name: view.name.trim(),
|
|
123
|
+
type: "admin",
|
|
124
|
+
path: view.path,
|
|
125
|
+
view,
|
|
126
|
+
description: view.description?.trim(),
|
|
127
|
+
group: "Admin"
|
|
128
|
+
} satisfies TopNavigationEntry)
|
|
107
129
|
: undefined)
|
|
108
130
|
.filter(Boolean) as TopNavigationEntry[]
|
|
109
131
|
];
|
|
110
132
|
|
|
133
|
+
if (viewsOrder) {
|
|
134
|
+
navigationEntries = navigationEntries.sort((a, b) => {
|
|
135
|
+
const aIndex = viewsOrder.indexOf(a.path);
|
|
136
|
+
const bIndex = viewsOrder.indexOf(b.path);
|
|
137
|
+
if (aIndex === -1 && bIndex === -1) {
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
if (aIndex === -1) {
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
if (bIndex === -1) {
|
|
144
|
+
return -1;
|
|
145
|
+
}
|
|
146
|
+
return aIndex - bIndex;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
111
150
|
const groups: string[] = Object.values(navigationEntries)
|
|
112
151
|
.map(e => e.group)
|
|
113
152
|
.filter(Boolean)
|
|
114
153
|
.filter((value, index, array) => array.indexOf(value) === index) as string[];
|
|
154
|
+
|
|
115
155
|
return {
|
|
116
156
|
navigationEntries,
|
|
117
157
|
groups
|
|
@@ -124,15 +164,24 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
124
164
|
return;
|
|
125
165
|
|
|
126
166
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
167
|
+
console.debug("Refreshing navigation")
|
|
168
|
+
const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
|
|
169
|
+
resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
|
|
170
|
+
resolveCMSViews(viewsProp, authController, dataSourceDelegate),
|
|
171
|
+
resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
|
|
130
172
|
]
|
|
131
173
|
);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
174
|
+
if (
|
|
175
|
+
!equal(collectionsRef.current, resolvedCollections) ||
|
|
176
|
+
!equal(viewsRef.current, resolvedViews) ||
|
|
177
|
+
!equal(adminViewsRef.current, resolvedAdminViews) ||
|
|
178
|
+
!equal(topLevelNavigation, computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder))
|
|
179
|
+
) {
|
|
180
|
+
collectionsRef.current = resolvedCollections;
|
|
181
|
+
viewsRef.current = resolvedViews;
|
|
182
|
+
adminViewsRef.current = resolvedAdminViews;
|
|
183
|
+
setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
|
|
184
|
+
}
|
|
136
185
|
} catch (e) {
|
|
137
186
|
console.error(e);
|
|
138
187
|
setNavigationLoadingError(e as any);
|
|
@@ -140,7 +189,17 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
140
189
|
|
|
141
190
|
setNavigationLoading(false);
|
|
142
191
|
setInitialised(true);
|
|
143
|
-
|
|
192
|
+
|
|
193
|
+
}, [
|
|
194
|
+
collectionsProp,
|
|
195
|
+
collectionPermissions,
|
|
196
|
+
authController.user,
|
|
197
|
+
authController.initialLoading,
|
|
198
|
+
viewsProp,
|
|
199
|
+
adminViewsProp,
|
|
200
|
+
computeTopNavigation,
|
|
201
|
+
injectCollections
|
|
202
|
+
]);
|
|
144
203
|
|
|
145
204
|
useEffect(() => {
|
|
146
205
|
refreshNavigation();
|
|
@@ -151,7 +210,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
151
210
|
entityId?: string,
|
|
152
211
|
includeUserOverride = false
|
|
153
212
|
): EC | undefined => {
|
|
154
|
-
|
|
213
|
+
const collections = collectionsRef.current;
|
|
155
214
|
if (!collections)
|
|
156
215
|
return undefined;
|
|
157
216
|
|
|
@@ -182,11 +241,12 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
182
241
|
}, [
|
|
183
242
|
basePath,
|
|
184
243
|
baseCollectionPath,
|
|
185
|
-
collections,
|
|
186
244
|
]);
|
|
187
245
|
|
|
188
246
|
const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
|
|
189
|
-
|
|
247
|
+
|
|
248
|
+
const collections = collectionsRef.current;
|
|
249
|
+
let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
|
|
190
250
|
if (!currentCollections)
|
|
191
251
|
throw Error("Collections have not been initialised yet");
|
|
192
252
|
|
|
@@ -202,10 +262,12 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
202
262
|
|
|
203
263
|
return undefined;
|
|
204
264
|
|
|
205
|
-
}, [
|
|
265
|
+
}, []);
|
|
206
266
|
|
|
207
267
|
const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
|
|
208
|
-
|
|
268
|
+
|
|
269
|
+
const collections = collectionsRef.current;
|
|
270
|
+
let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
|
|
209
271
|
if (!currentCollections)
|
|
210
272
|
throw Error("Collections have not been initialised yet");
|
|
211
273
|
|
|
@@ -221,7 +283,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
221
283
|
|
|
222
284
|
return undefined;
|
|
223
285
|
|
|
224
|
-
}, [
|
|
286
|
+
}, []);
|
|
225
287
|
|
|
226
288
|
const isUrlCollectionPath = useCallback(
|
|
227
289
|
(path: string): boolean => removeInitialAndTrailingSlashes(path + "/").startsWith(removeInitialAndTrailingSlashes(fullCollectionPath) + "/"),
|
|
@@ -243,25 +305,19 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
243
305
|
[]);
|
|
244
306
|
|
|
245
307
|
const resolveAliasesFrom = useCallback((path: string): string => {
|
|
308
|
+
const collections = collectionsRef.current;
|
|
246
309
|
if (!collections)
|
|
247
310
|
throw Error("Collections have not been initialised yet");
|
|
248
311
|
return resolveCollectionPathIds(path, collections);
|
|
249
|
-
}, [
|
|
250
|
-
|
|
251
|
-
const state = location.state as any;
|
|
252
|
-
/**
|
|
253
|
-
* The location can be overridden if `base_location` is set in the
|
|
254
|
-
* state field of the current location. This can happen if you open
|
|
255
|
-
* a side entity, like `products`, from a different one, like `users`
|
|
256
|
-
*/
|
|
257
|
-
const baseLocation = state && state.base_location ? state.base_location : location;
|
|
312
|
+
}, []);
|
|
258
313
|
|
|
259
314
|
const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
|
|
315
|
+
const collections = collectionsRef.current ?? [];
|
|
260
316
|
return getParentReferencesFromPath({
|
|
261
317
|
path,
|
|
262
318
|
collections
|
|
263
319
|
});
|
|
264
|
-
}, [
|
|
320
|
+
}, []);
|
|
265
321
|
|
|
266
322
|
const getParentCollectionIds = useCallback((path: string): string[] => {
|
|
267
323
|
|
|
@@ -276,35 +332,28 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
276
332
|
}
|
|
277
333
|
|
|
278
334
|
// for each odd path segment, get the collection
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
getCollectionFromPaths(oddPathSegments);
|
|
282
|
-
|
|
283
|
-
// const allParentCollectionsForPath = getAllParentReferencesForPath(path);
|
|
284
|
-
// console.log("allParentCollectionsForPath", allParentCollectionsForPath);
|
|
285
|
-
// const parentCollectionIds = allParentCollectionsForPath.map(r => r.id);
|
|
286
|
-
console.log("getParentCollectionIds", path, parentCollectionIds);
|
|
287
|
-
return parentCollectionIds;
|
|
335
|
+
return result.map(r => getCollectionFromPaths(r)?.id).filter(Boolean) as string[];
|
|
288
336
|
}, [getAllParentReferencesForPath])
|
|
289
337
|
|
|
290
338
|
const convertIdsToPaths = useCallback((ids: string[]): string[] => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
return paths;
|
|
339
|
+
const collections = collectionsRef.current;
|
|
340
|
+
let currentCollections = collections;
|
|
341
|
+
const paths: string[] = [];
|
|
342
|
+
for (let i = 0; i < ids.length; i++) {
|
|
343
|
+
const id = ids[i];
|
|
344
|
+
const collection: EntityCollection | undefined = currentCollections!.find(c => c.id === id);
|
|
345
|
+
if (!collection)
|
|
346
|
+
throw Error(`Collection with id ${id} not found`);
|
|
347
|
+
paths.push(collection.path);
|
|
348
|
+
currentCollections = collection.subcollections;
|
|
302
349
|
}
|
|
303
|
-
|
|
350
|
+
return paths;
|
|
351
|
+
}, [getCollectionFromIds]);
|
|
304
352
|
|
|
305
353
|
return {
|
|
306
|
-
collections,
|
|
307
|
-
views,
|
|
354
|
+
collections: collectionsRef.current,
|
|
355
|
+
views: viewsRef.current,
|
|
356
|
+
adminViews: adminViewsRef.current,
|
|
308
357
|
loading: !initialised || navigationLoading,
|
|
309
358
|
navigationLoadingError,
|
|
310
359
|
homeUrl,
|
|
@@ -321,7 +370,6 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
321
370
|
buildCMSUrlPath,
|
|
322
371
|
resolveAliasesFrom,
|
|
323
372
|
topLevelNavigation,
|
|
324
|
-
baseLocation,
|
|
325
373
|
refreshNavigation,
|
|
326
374
|
getParentReferencesFromPath: getAllParentReferencesForPath,
|
|
327
375
|
getParentCollectionIds,
|
|
@@ -346,8 +394,8 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
|
|
|
346
394
|
return resolvedCollections
|
|
347
395
|
.filter((c) => {
|
|
348
396
|
if (!c.permissions) return true;
|
|
349
|
-
const resolvedPermissions = resolvePermissions(c, authController,
|
|
350
|
-
return resolvedPermissions
|
|
397
|
+
const resolvedPermissions = resolvePermissions(c, authController, c.path, null)
|
|
398
|
+
return resolvedPermissions?.read !== false;
|
|
351
399
|
})
|
|
352
400
|
.map((c) => {
|
|
353
401
|
if (!c.subcollections) return c;
|
|
@@ -359,6 +407,7 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
|
|
|
359
407
|
}
|
|
360
408
|
|
|
361
409
|
async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
|
|
410
|
+
collectionPermissions: PermissionsBuilder | undefined,
|
|
362
411
|
authController: AuthController,
|
|
363
412
|
dataSource: DataSourceDelegate,
|
|
364
413
|
injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
|
|
@@ -373,6 +422,8 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
|
|
|
373
422
|
resolvedCollections = collections;
|
|
374
423
|
}
|
|
375
424
|
|
|
425
|
+
resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
|
|
426
|
+
|
|
376
427
|
resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
|
|
377
428
|
|
|
378
429
|
if (injectCollections) {
|
|
@@ -395,3 +446,11 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
|
|
|
395
446
|
}
|
|
396
447
|
return resolvedViews;
|
|
397
448
|
}
|
|
449
|
+
|
|
450
|
+
function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
|
|
451
|
+
const trimmed = collectionOrView.group?.trim();
|
|
452
|
+
if (!trimmed || trimmed === "") {
|
|
453
|
+
return "Views";
|
|
454
|
+
}
|
|
455
|
+
return trimmed ?? "Views";
|
|
456
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { AuthController } from "../types";
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { AuthController, FireCMSPlugin } from "../types";
|
|
3
3
|
|
|
4
4
|
export const DEFAULT_SERVER_DEV = "https://api-kdoe6pj3qq-ey.a.run.app";
|
|
5
5
|
export const DEFAULT_SERVER = "https://api-drplyi3b6q-ey.a.run.app";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
export type AccessResponse = {
|
|
8
|
+
blocked?: boolean;
|
|
9
|
+
message?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function makeRequest(authController: AuthController, pluginKeys: string | undefined) {
|
|
8
13
|
const firebaseToken = await authController.getAuthToken();
|
|
9
14
|
return fetch(DEFAULT_SERVER + "/access_log",
|
|
10
15
|
{
|
|
@@ -14,18 +19,23 @@ async function makeRequest(authController: AuthController) {
|
|
|
14
19
|
"Content-Type": "application/json",
|
|
15
20
|
Authorization: `Basic ${firebaseToken}`,
|
|
16
21
|
},
|
|
17
|
-
body: JSON.stringify({})
|
|
22
|
+
body: JSON.stringify({ plugins: pluginKeys })
|
|
18
23
|
})
|
|
19
24
|
.then(async (res) => {
|
|
25
|
+
return res.json();
|
|
20
26
|
});
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
export function useProjectLog(authController: AuthController
|
|
29
|
+
export function useProjectLog(authController: AuthController,
|
|
30
|
+
plugins?: FireCMSPlugin<any, any, any>[]): AccessResponse | null {
|
|
31
|
+
const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
|
|
24
32
|
const accessedUserRef = useRef<string | null>(null);
|
|
33
|
+
const pluginKeys = plugins?.map(plugin => plugin.key).join(",");
|
|
25
34
|
useEffect(() => {
|
|
26
35
|
if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
|
|
27
|
-
makeRequest(authController);
|
|
36
|
+
makeRequest(authController, pluginKeys).then(setAccessResponse);
|
|
28
37
|
accessedUserRef.current = authController.user.uid;
|
|
29
38
|
}
|
|
30
39
|
}, [authController]);
|
|
40
|
+
return accessResponse;
|
|
31
41
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useSideDialogsController } from "./useSideDialogsController";
|
|
2
|
-
import {
|
|
2
|
+
import { ReferenceSelectionInnerProps, ReferenceSelectionTable } from "../components";
|
|
3
3
|
import { useCallback } from "react";
|
|
4
4
|
import { useNavigationController } from "./useNavigationController";
|
|
5
5
|
|
|
@@ -27,7 +27,7 @@ export function useReferenceDialog<M extends Record<string, any>>(referenceDialo
|
|
|
27
27
|
if (!usedCollection)
|
|
28
28
|
usedCollection = navigation.getCollection(referenceDialogProps.path);
|
|
29
29
|
if (!usedCollection)
|
|
30
|
-
throw Error("Not able to resolve the collection in useReferenceDialog");
|
|
30
|
+
throw Error("Not able to resolve the collection in useReferenceDialog. Make sure a collection is registered in path " + referenceDialogProps.path);
|
|
31
31
|
sideDialogsController.open({
|
|
32
32
|
key: `reference_${referenceDialogProps.path}`,
|
|
33
33
|
component:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StorageSource } from "../types";
|
|
1
|
+
import { EntityCollection, StorageSource } from "../types";
|
|
2
2
|
import { StorageSourceContext } from "../contexts/StorageSourceContext";
|
|
3
3
|
import { useContext } from "react";
|
|
4
4
|
|
|
@@ -6,4 +6,9 @@ import { useContext } from "react";
|
|
|
6
6
|
* Use this hook to get the storage source being used
|
|
7
7
|
* @group Hooks and utilities
|
|
8
8
|
*/
|
|
9
|
-
export const useStorageSource = (): StorageSource =>
|
|
9
|
+
export const useStorageSource = (collection?: EntityCollection): StorageSource => {
|
|
10
|
+
const defaultStorageSource = useContext(StorageSourceContext);
|
|
11
|
+
if (collection?.overrides?.storageSource)
|
|
12
|
+
return collection.overrides.storageSource;
|
|
13
|
+
return defaultStorageSource;
|
|
14
|
+
};
|
|
@@ -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 dataSource
|
|
15
|
+
*/
|
|
16
|
+
export function useValidateAuthenticator<UserType extends User = User, Controller extends AuthController<UserType> = AuthController<UserType>>({
|
|
17
|
+
disabled,
|
|
18
|
+
authController,
|
|
19
|
+
authentication,
|
|
20
|
+
getAppCheckToken,
|
|
21
|
+
appCheckForceRefresh = false,
|
|
22
|
+
storageSource,
|
|
23
|
+
dataSourceDelegate
|
|
24
|
+
}:
|
|
25
|
+
{
|
|
26
|
+
disabled?: boolean,
|
|
27
|
+
authController: Controller,
|
|
28
|
+
authentication?: 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(authentication);
|
|
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 (authentication instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
|
|
96
|
+
setAuthLoading(true);
|
|
97
|
+
try {
|
|
98
|
+
const allowed = await authentication({
|
|
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, authentication, 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,9 +344,11 @@ 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
|
-
} else if (data
|
|
351
|
+
} else if (data.isEntityReference && data.isEntityReference()) {
|
|
346
352
|
return buildReference(data);
|
|
347
353
|
} else if (data instanceof GeoPoint) {
|
|
348
354
|
return buildGeoPoint(data);
|
|
@@ -355,4 +361,3 @@ export function cmsToDelegateModel(data: any,
|
|
|
355
361
|
}
|
|
356
362
|
return data;
|
|
357
363
|
}
|
|
358
|
-
|
|
@@ -178,7 +178,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
178
178
|
}
|
|
179
179
|
} else if (property.dataType === "reference") {
|
|
180
180
|
if (typeof property.path === "string") {
|
|
181
|
-
if (value
|
|
181
|
+
if (typeof value === "object" && "isEntityReference" in value && value.isEntityReference()) {
|
|
182
182
|
content = <ReferencePreview
|
|
183
183
|
disabled={!property.path}
|
|
184
184
|
previewProperties={property.previewProperties}
|
|
@@ -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
|
}
|