@firecms/core 3.0.0-canary.6 → 3.0.0-canary.8
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/VirtualTable/VirtualTableProps.d.ts +1 -1
- package/dist/components/index.d.ts +4 -2
- package/dist/core/{EntityView.d.ts → EntityEditView.d.ts} +2 -2
- package/dist/core/SideEntityView.d.ts +2 -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/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/validation.d.ts +1 -1
- package/dist/hooks/data/useDataSource.d.ts +2 -2
- package/dist/hooks/useBuildNavigationController.d.ts +5 -2
- package/dist/hooks/useProjectLog.d.ts +5 -1
- package/dist/hooks/useStorageSource.d.ts +2 -2
- package/dist/index.es.js +8333 -8060
- 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/useRestoreScroll.d.ts +1 -1
- package/dist/preview/PropertyPreview.d.ts +1 -1
- 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 +8 -1
- package/dist/types/collections.d.ts +14 -1
- package/dist/types/entity_overrides.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/navigation.d.ts +10 -9
- package/dist/types/permissions.d.ts +5 -1
- package/dist/types/plugins.d.ts +15 -17
- package/dist/types/properties.d.ts +2 -2
- package/dist/types/property_config.d.ts +2 -2
- package/dist/util/collections.d.ts +9 -1
- package/dist/util/icons.d.ts +8 -2
- 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/package.json +23 -23
- package/src/components/DeleteEntityDialog.tsx +4 -4
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +273 -277
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +12 -13
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +8 -15
- 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 +13 -9
- 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 +3 -3
- 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 -2
- package/src/core/Drawer.tsx +66 -39
- package/src/core/{EntityView.tsx → EntityEditView.tsx} +20 -37
- 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 +2 -2
- package/src/form/EntityForm.tsx +19 -11
- package/src/form/components/StorageItemPreview.tsx +5 -3
- package/src/form/components/StorageUploadProgress.tsx +6 -5
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +8 -12
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +15 -15
- package/src/form/field_bindings/MapFieldBinding.tsx +15 -15
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +1 -1
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -0
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +14 -5
- package/src/form/field_bindings/TextFieldBinding.tsx +7 -5
- package/src/form/validation.ts +3 -4
- package/src/hooks/data/useCollectionFetch.tsx +1 -1
- package/src/hooks/data/useDataSource.tsx +8 -3
- package/src/hooks/data/useEntityFetch.tsx +1 -1
- package/src/hooks/useBuildNavigationController.tsx +79 -38
- package/src/hooks/useProjectLog.tsx +11 -3
- package/src/hooks/useReferenceDialog.tsx +2 -2
- package/src/hooks/useStorageSource.tsx +7 -2
- package/src/preview/PropertyPreview.tsx +1 -1
- package/src/preview/components/BooleanPreview.tsx +16 -3
- package/src/preview/components/EnumValuesChip.tsx +1 -1
- package/src/preview/components/ReferencePreview.tsx +54 -146
- package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
- package/src/types/analytics.ts +1 -0
- package/src/types/auth.tsx +11 -1
- package/src/types/collections.ts +16 -1
- package/src/types/entity_actions.tsx +4 -0
- package/src/types/entity_overrides.tsx +7 -0
- package/src/types/firecms.tsx +0 -1
- package/src/types/index.ts +1 -0
- package/src/types/navigation.ts +11 -10
- package/src/types/permissions.ts +6 -1
- package/src/types/plugins.tsx +22 -25
- package/src/types/properties.ts +3 -2
- package/src/types/property_config.tsx +2 -2
- package/src/types/side_entity_controller.tsx +1 -0
- package/src/util/collections.ts +22 -0
- package/src/util/icons.tsx +11 -3
- package/src/util/permissions.ts +11 -8
- package/src/util/references.ts +36 -5
- package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
import { useLocation } from "react-router-dom";
|
|
3
2
|
import equal from "react-fast-compare"
|
|
4
3
|
|
|
5
4
|
import {
|
|
@@ -11,12 +10,14 @@ import {
|
|
|
11
10
|
EntityCollectionsBuilder,
|
|
12
11
|
EntityReference,
|
|
13
12
|
NavigationController,
|
|
13
|
+
PermissionsBuilder,
|
|
14
14
|
TopNavigationEntry,
|
|
15
15
|
TopNavigationResult,
|
|
16
16
|
User,
|
|
17
17
|
UserConfigurationPersistence
|
|
18
18
|
} from "../types";
|
|
19
19
|
import {
|
|
20
|
+
applyPermissionsFunctionIfEmpty,
|
|
20
21
|
getCollectionByPathOrId,
|
|
21
22
|
mergeDeep,
|
|
22
23
|
removeInitialAndTrailingSlashes,
|
|
@@ -33,7 +34,10 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
|
|
|
33
34
|
baseCollectionPath?: string,
|
|
34
35
|
authController: AuthController<UserType>;
|
|
35
36
|
collections?: EC[] | EntityCollectionsBuilder<EC>;
|
|
37
|
+
collectionPermissions?: PermissionsBuilder;
|
|
36
38
|
views?: CMSView[] | CMSViewsBuilder;
|
|
39
|
+
adminViews?: CMSView[] | CMSViewsBuilder;
|
|
40
|
+
viewsOrder?: string[];
|
|
37
41
|
userConfigPersistence?: UserConfigurationPersistence;
|
|
38
42
|
dataSourceDelegate: DataSourceDelegate;
|
|
39
43
|
/**
|
|
@@ -46,22 +50,25 @@ type BuildNavigationContextProps<EC extends EntityCollection, UserType extends U
|
|
|
46
50
|
injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
|
|
47
51
|
};
|
|
48
52
|
|
|
49
|
-
export function useBuildNavigationController<EC extends EntityCollection, UserType extends User>({
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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;
|
|
61
67
|
|
|
62
68
|
const collectionsRef = useRef<EntityCollection[] | null>();
|
|
63
69
|
const [collections, setCollections] = useState<EntityCollection[] | undefined>();
|
|
64
70
|
const [views, setViews] = useState<CMSView[] | undefined>();
|
|
71
|
+
const [adminViews, setAdminViews] = useState<CMSView[] | undefined>();
|
|
65
72
|
const [initialised, setInitialised] = useState<boolean>(false);
|
|
66
73
|
|
|
67
74
|
const [topLevelNavigation, setTopLevelNavigation] = useState<TopNavigationResult | undefined>(undefined);
|
|
@@ -81,18 +88,18 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
81
88
|
const buildUrlCollectionPath = useCallback((path: string): string => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path)}`,
|
|
82
89
|
[baseCollectionPath]);
|
|
83
90
|
|
|
84
|
-
const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[]): TopNavigationResult => {
|
|
85
|
-
|
|
91
|
+
const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[], adminViews: CMSView[], viewsOrder?: string[]): TopNavigationResult => {
|
|
92
|
+
let navigationEntries: TopNavigationEntry[] = [
|
|
86
93
|
...(collections ?? []).map(collection => (!collection.hideFromNavigation
|
|
87
|
-
? {
|
|
94
|
+
? ({
|
|
88
95
|
url: buildUrlCollectionPath(collection.id ?? collection.path),
|
|
89
96
|
type: "collection",
|
|
90
97
|
name: collection.name.trim(),
|
|
91
98
|
path: collection.id ?? collection.path,
|
|
92
99
|
collection,
|
|
93
100
|
description: collection.description?.trim(),
|
|
94
|
-
group: collection.group?.trim()
|
|
95
|
-
}
|
|
101
|
+
group: collection.group?.trim() ?? "Views"
|
|
102
|
+
} satisfies TopNavigationEntry)
|
|
96
103
|
: undefined))
|
|
97
104
|
.filter(Boolean) as TopNavigationEntry[],
|
|
98
105
|
...(views ?? []).map(view =>
|
|
@@ -101,18 +108,50 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
101
108
|
url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
|
|
102
109
|
name: view.name.trim(),
|
|
103
110
|
type: "view",
|
|
111
|
+
path: view.path,
|
|
104
112
|
view,
|
|
105
113
|
description: view.description?.trim(),
|
|
106
|
-
group: view.group?.trim()
|
|
107
|
-
})
|
|
114
|
+
group: view.group?.trim() ?? "Views"
|
|
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)
|
|
108
129
|
: undefined)
|
|
109
130
|
.filter(Boolean) as TopNavigationEntry[]
|
|
110
131
|
];
|
|
111
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
|
+
|
|
112
150
|
const groups: string[] = Object.values(navigationEntries)
|
|
113
151
|
.map(e => e.group)
|
|
114
152
|
.filter(Boolean)
|
|
115
153
|
.filter((value, index, array) => array.indexOf(value) === index) as string[];
|
|
154
|
+
|
|
116
155
|
return {
|
|
117
156
|
navigationEntries,
|
|
118
157
|
groups
|
|
@@ -125,16 +164,23 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
125
164
|
return;
|
|
126
165
|
|
|
127
166
|
try {
|
|
128
|
-
const [resolvedCollections = [], resolvedViews = []] = await Promise.all([
|
|
129
|
-
resolveCollections(collectionsProp, authController, dataSourceDelegate, injectCollections),
|
|
130
|
-
resolveCMSViews(
|
|
167
|
+
const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
|
|
168
|
+
resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
|
|
169
|
+
resolveCMSViews(viewsProp, authController, dataSourceDelegate),
|
|
170
|
+
resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
|
|
131
171
|
]
|
|
132
172
|
);
|
|
133
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
!equal(collectionsRef.current, resolvedCollections) ||
|
|
175
|
+
!equal(views, resolvedViews) ||
|
|
176
|
+
!equal(adminViews, resolvedAdminViews) ||
|
|
177
|
+
!equal(topLevelNavigation, computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder))
|
|
178
|
+
) {
|
|
134
179
|
collectionsRef.current = resolvedCollections;
|
|
135
180
|
setCollections(resolvedCollections);
|
|
136
181
|
setViews(resolvedViews);
|
|
137
|
-
|
|
182
|
+
setAdminViews(resolvedAdminViews);
|
|
183
|
+
setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
|
|
138
184
|
}
|
|
139
185
|
} catch (e) {
|
|
140
186
|
console.error(e);
|
|
@@ -143,7 +189,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
143
189
|
|
|
144
190
|
setNavigationLoading(false);
|
|
145
191
|
setInitialised(true);
|
|
146
|
-
}, [collectionsProp, authController.user, authController.initialLoading,
|
|
192
|
+
}, [collectionsProp, collectionPermissions, authController.user, authController.initialLoading, viewsProp, adminViewsProp, computeTopNavigation, injectCollections]);
|
|
147
193
|
|
|
148
194
|
useEffect(() => {
|
|
149
195
|
refreshNavigation();
|
|
@@ -185,7 +231,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
185
231
|
}, [
|
|
186
232
|
basePath,
|
|
187
233
|
baseCollectionPath,
|
|
188
|
-
collections
|
|
234
|
+
collections
|
|
189
235
|
]);
|
|
190
236
|
|
|
191
237
|
const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
|
|
@@ -251,14 +297,6 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
251
297
|
return resolveCollectionPathIds(path, collections);
|
|
252
298
|
}, [collections]);
|
|
253
299
|
|
|
254
|
-
const state = location.state as any;
|
|
255
|
-
/**
|
|
256
|
-
* The location can be overridden if `base_location` is set in the
|
|
257
|
-
* state field of the current location. This can happen if you open
|
|
258
|
-
* a side entity, like `products`, from a different one, like `users`
|
|
259
|
-
*/
|
|
260
|
-
const baseLocation = state && state.base_location ? state.base_location : location;
|
|
261
|
-
|
|
262
300
|
const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
|
|
263
301
|
return getParentReferencesFromPath({
|
|
264
302
|
path,
|
|
@@ -300,6 +338,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
300
338
|
return {
|
|
301
339
|
collections,
|
|
302
340
|
views,
|
|
341
|
+
adminViews,
|
|
303
342
|
loading: !initialised || navigationLoading,
|
|
304
343
|
navigationLoadingError,
|
|
305
344
|
homeUrl,
|
|
@@ -316,7 +355,6 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
316
355
|
buildCMSUrlPath,
|
|
317
356
|
resolveAliasesFrom,
|
|
318
357
|
topLevelNavigation,
|
|
319
|
-
baseLocation,
|
|
320
358
|
refreshNavigation,
|
|
321
359
|
getParentReferencesFromPath: getAllParentReferencesForPath,
|
|
322
360
|
getParentCollectionIds,
|
|
@@ -341,8 +379,8 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
|
|
|
341
379
|
return resolvedCollections
|
|
342
380
|
.filter((c) => {
|
|
343
381
|
if (!c.permissions) return true;
|
|
344
|
-
const resolvedPermissions = resolvePermissions(c, authController,
|
|
345
|
-
return resolvedPermissions
|
|
382
|
+
const resolvedPermissions = resolvePermissions(c, authController, c.path, null)
|
|
383
|
+
return resolvedPermissions?.read !== false;
|
|
346
384
|
})
|
|
347
385
|
.map((c) => {
|
|
348
386
|
if (!c.subcollections) return c;
|
|
@@ -354,6 +392,7 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
|
|
|
354
392
|
}
|
|
355
393
|
|
|
356
394
|
async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
|
|
395
|
+
collectionPermissions: PermissionsBuilder | undefined,
|
|
357
396
|
authController: AuthController,
|
|
358
397
|
dataSource: DataSourceDelegate,
|
|
359
398
|
injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
|
|
@@ -368,6 +407,8 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
|
|
|
368
407
|
resolvedCollections = collections;
|
|
369
408
|
}
|
|
370
409
|
|
|
410
|
+
resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
|
|
411
|
+
|
|
371
412
|
resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
|
|
372
413
|
|
|
373
414
|
if (injectCollections) {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { AuthController } 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
|
+
export type AccessResponse = {
|
|
8
|
+
blocked?: boolean;
|
|
9
|
+
message?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
async function makeRequest(authController: AuthController) {
|
|
8
13
|
const firebaseToken = await authController.getAuthToken();
|
|
9
14
|
return fetch(DEFAULT_SERVER + "/access_log",
|
|
@@ -17,15 +22,18 @@ async function makeRequest(authController: AuthController) {
|
|
|
17
22
|
body: JSON.stringify({})
|
|
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): AccessResponse | null {
|
|
30
|
+
const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
|
|
24
31
|
const accessedUserRef = useRef<string | null>(null);
|
|
25
32
|
useEffect(() => {
|
|
26
33
|
if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
|
|
27
|
-
makeRequest(authController);
|
|
34
|
+
makeRequest(authController).then(setAccessResponse);
|
|
28
35
|
accessedUserRef.current = authController.user.uid;
|
|
29
36
|
}
|
|
30
37
|
}, [authController]);
|
|
38
|
+
return accessResponse;
|
|
31
39
|
}
|
|
@@ -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
|
+
};
|
|
@@ -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
|
}
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Checkbox } 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
|
+
size={size}
|
|
21
|
+
color={"secondary"}/>
|
|
22
|
+
{property.name && <span className={size === "tiny" ? "text-sm" : ""}>{property.name}</span>}
|
|
23
|
+
</div>;
|
|
11
24
|
}
|
|
@@ -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) {
|
|
@@ -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>>();
|