@firecms/core 3.0.0-beta.4.pre.2 → 3.0.0-beta.6
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/ClearFilterSortButton.d.ts +5 -0
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +11 -11
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +1 -1
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +11 -1
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
- package/dist/components/EntityPreview.d.ts +2 -2
- package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
- package/dist/components/common/types.d.ts +1 -1
- package/dist/contexts/AuthControllerContext.d.ts +1 -1
- package/dist/form/components/ErrorFocus.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 +1 -1
- package/dist/hooks/data/useEntityFetch.d.ts +3 -3
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useBuildNavigationController.d.ts +1 -2
- package/dist/hooks/useProjectLog.d.ts +2 -2
- package/dist/hooks/useValidateAuthenticator.d.ts +21 -0
- package/dist/index.es.js +3948 -3737
- 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 +1 -16
- package/dist/internal/useRestoreScroll.d.ts +1 -1
- package/dist/preview/PropertyPreviewProps.d.ts +1 -4
- package/dist/types/auth.d.ts +30 -1
- package/dist/types/collections.d.ts +8 -4
- package/dist/types/datasource.d.ts +3 -6
- package/dist/types/entities.d.ts +5 -1
- package/dist/types/entity_callbacks.d.ts +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/navigation.d.ts +4 -4
- package/dist/types/plugins.d.ts +5 -3
- package/dist/types/properties.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 +1 -1
- package/dist/util/entities.d.ts +1 -1
- package/dist/util/navigation_utils.d.ts +2 -2
- package/dist/util/resolutions.d.ts +5 -5
- package/dist/util/useTraceUpdate.d.ts +1 -0
- package/package.json +23 -20
- package/src/components/ClearFilterSortButton.tsx +41 -0
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -11
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +16 -19
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +27 -32
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +11 -6
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +28 -5
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
- package/src/components/EntityPreview.tsx +5 -2
- package/src/components/EntityView.tsx +1 -1
- package/src/components/HomePage/DefaultHomePage.tsx +2 -2
- package/src/components/HomePage/NavigationCard.tsx +1 -1
- package/src/components/ReferenceWidget.tsx +1 -1
- package/src/components/SelectableTable/SelectableTable.tsx +1 -1
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +23 -8
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +24 -5
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
- package/src/components/VirtualTable/VirtualTable.tsx +28 -20
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
- package/src/components/common/types.tsx +1 -1
- package/src/contexts/AuthControllerContext.tsx +1 -1
- package/src/core/FireCMS.tsx +2 -3
- package/src/core/field_configs.tsx +1 -2
- package/src/form/EntityForm.tsx +1 -1
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
- package/src/hooks/data/delete.ts +3 -3
- package/src/hooks/data/save.ts +2 -1
- package/src/hooks/data/useDataSource.tsx +1 -1
- package/src/hooks/data/useEntityFetch.tsx +3 -3
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +9 -10
- package/src/hooks/useBuildModeController.tsx +11 -5
- package/src/hooks/useBuildNavigationController.tsx +136 -59
- package/src/hooks/useProjectLog.tsx +8 -6
- package/src/hooks/useValidateAuthenticator.tsx +115 -0
- package/src/internal/useBuildDataSource.ts +42 -47
- package/src/internal/useBuildSideEntityController.tsx +18 -12
- package/src/preview/PropertyPreview.tsx +2 -12
- package/src/preview/PropertyPreviewProps.tsx +1 -11
- package/src/preview/components/BooleanPreview.tsx +4 -2
- package/src/preview/components/ReferencePreview.tsx +1 -1
- package/src/types/auth.tsx +40 -1
- package/src/types/collections.ts +8 -4
- package/src/types/datasource.ts +8 -5
- package/src/types/entities.ts +9 -1
- package/src/types/entity_callbacks.ts +2 -2
- package/src/types/index.ts +1 -1
- package/src/types/navigation.ts +6 -6
- package/src/types/plugins.tsx +6 -5
- package/src/types/properties.ts +5 -4
- package/src/types/roles.ts +41 -0
- package/src/types/storage.ts +12 -3
- package/src/types/user.ts +7 -0
- package/src/util/collections.ts +1 -1
- package/src/util/entities.ts +1 -1
- package/src/util/navigation_utils.ts +6 -6
- package/src/util/strings.ts +2 -2
- package/src/util/useTraceUpdate.tsx +2 -1
- package/dist/internal/useLocaleConfig.d.ts +0 -1
- package/dist/types/appcheck.d.ts +0 -26
- package/src/internal/useLocaleConfig.tsx +0 -18
- package/src/types/appcheck.ts +0 -29
|
@@ -29,7 +29,7 @@ import { getParentReferencesFromPath } from "../util/parent_references_from_path
|
|
|
29
29
|
const DEFAULT_BASE_PATH = "/";
|
|
30
30
|
const DEFAULT_COLLECTION_PATH = "/c";
|
|
31
31
|
|
|
32
|
-
type BuildNavigationContextProps<EC extends EntityCollection, UserType extends User> = {
|
|
32
|
+
export type BuildNavigationContextProps<EC extends EntityCollection, UserType extends User> = {
|
|
33
33
|
basePath?: string,
|
|
34
34
|
baseCollectionPath?: string,
|
|
35
35
|
authController: AuthController<UserType>;
|
|
@@ -65,10 +65,10 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
65
65
|
injectCollections
|
|
66
66
|
} = props;
|
|
67
67
|
|
|
68
|
-
const collectionsRef = useRef<EntityCollection[] |
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
68
|
+
const collectionsRef = useRef<EntityCollection[] | undefined>();
|
|
69
|
+
const viewsRef = useRef<CMSView[] | undefined>();
|
|
70
|
+
const adminViewsRef = useRef<CMSView[] | undefined>();
|
|
71
|
+
|
|
72
72
|
const [initialised, setInitialised] = useState<boolean>(false);
|
|
73
73
|
|
|
74
74
|
const [topLevelNavigation, setTopLevelNavigation] = useState<TopNavigationResult | undefined>(undefined);
|
|
@@ -98,7 +98,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
98
98
|
path: collection.id ?? collection.path,
|
|
99
99
|
collection,
|
|
100
100
|
description: collection.description?.trim(),
|
|
101
|
-
group: collection
|
|
101
|
+
group: getGroup(collection)
|
|
102
102
|
} satisfies TopNavigationEntry)
|
|
103
103
|
: undefined))
|
|
104
104
|
.filter(Boolean) as TopNavigationEntry[],
|
|
@@ -111,8 +111,8 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
111
111
|
path: view.path,
|
|
112
112
|
view,
|
|
113
113
|
description: view.description?.trim(),
|
|
114
|
-
group: view
|
|
115
|
-
}satisfies TopNavigationEntry)
|
|
114
|
+
group: getGroup(view)
|
|
115
|
+
} satisfies TopNavigationEntry)
|
|
116
116
|
: undefined)
|
|
117
117
|
.filter(Boolean) as TopNavigationEntry[],
|
|
118
118
|
...(adminViews ?? []).map(view =>
|
|
@@ -130,6 +130,30 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
130
130
|
.filter(Boolean) as TopNavigationEntry[]
|
|
131
131
|
];
|
|
132
132
|
|
|
133
|
+
// Sort by group, entries with group "Admin" will go last, and second to last will be the group "Views"
|
|
134
|
+
navigationEntries = navigationEntries.sort((a, b) => {
|
|
135
|
+
if (a.group !== "Views" && a.group !== "Admin" && (b.group === "Views" || b.group === "Admin")) {
|
|
136
|
+
return -1;
|
|
137
|
+
}
|
|
138
|
+
if (b.group !== "Views" && b.group !== "Admin" && (a.group === "Views" || a.group === "Admin")) {
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
if (a.group === "Admin" && b.group !== "Admin") {
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
if (a.group !== "Admin" && b.group === "Admin") {
|
|
145
|
+
return -1;
|
|
146
|
+
}
|
|
147
|
+
if (a.group === "Views" && b.group !== "Views") {
|
|
148
|
+
return -1;
|
|
149
|
+
}
|
|
150
|
+
if (a.group !== "Views" && b.group === "Views") {
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
return 0;
|
|
154
|
+
|
|
155
|
+
});
|
|
156
|
+
|
|
133
157
|
if (viewsOrder) {
|
|
134
158
|
navigationEntries = navigationEntries.sort((a, b) => {
|
|
135
159
|
const aIndex = viewsOrder.indexOf(a.path);
|
|
@@ -163,24 +187,34 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
163
187
|
if (authController.initialLoading)
|
|
164
188
|
return;
|
|
165
189
|
|
|
190
|
+
console.debug("Refreshing navigation");
|
|
191
|
+
|
|
166
192
|
try {
|
|
193
|
+
|
|
167
194
|
const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
|
|
168
195
|
resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
|
|
169
196
|
resolveCMSViews(viewsProp, authController, dataSourceDelegate),
|
|
170
197
|
resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
|
|
171
198
|
]
|
|
172
199
|
);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
!equal(adminViews, resolvedAdminViews) ||
|
|
177
|
-
!equal(topLevelNavigation, computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder))
|
|
178
|
-
) {
|
|
200
|
+
|
|
201
|
+
let shouldUpdateTopLevelNav = false;
|
|
202
|
+
if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
|
|
179
203
|
collectionsRef.current = resolvedCollections;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
204
|
+
shouldUpdateTopLevelNav = true;
|
|
205
|
+
}
|
|
206
|
+
if (!equal(viewsRef.current, resolvedViews)) {
|
|
207
|
+
viewsRef.current = resolvedViews;
|
|
208
|
+
shouldUpdateTopLevelNav = true;
|
|
209
|
+
}
|
|
210
|
+
if (!equal(adminViewsRef.current, resolvedAdminViews)) {
|
|
211
|
+
adminViewsRef.current = resolvedAdminViews;
|
|
212
|
+
shouldUpdateTopLevelNav = true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder);
|
|
216
|
+
if (shouldUpdateTopLevelNav && !equal(topLevelNavigation, computedTopLevelNav)) {
|
|
217
|
+
setTopLevelNavigation(computedTopLevelNav);
|
|
184
218
|
}
|
|
185
219
|
} catch (e) {
|
|
186
220
|
console.error(e);
|
|
@@ -189,25 +223,34 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
189
223
|
|
|
190
224
|
setNavigationLoading(false);
|
|
191
225
|
setInitialised(true);
|
|
192
|
-
|
|
226
|
+
|
|
227
|
+
}, [
|
|
228
|
+
collectionsProp,
|
|
229
|
+
collectionPermissions,
|
|
230
|
+
authController.user,
|
|
231
|
+
authController.initialLoading,
|
|
232
|
+
viewsProp,
|
|
233
|
+
adminViewsProp,
|
|
234
|
+
computeTopNavigation,
|
|
235
|
+
injectCollections
|
|
236
|
+
]);
|
|
193
237
|
|
|
194
238
|
useEffect(() => {
|
|
195
239
|
refreshNavigation();
|
|
196
240
|
}, [refreshNavigation]);
|
|
197
241
|
|
|
198
|
-
const getCollection = useCallback(
|
|
242
|
+
const getCollection = useCallback((
|
|
199
243
|
idOrPath: string,
|
|
200
244
|
entityId?: string,
|
|
201
245
|
includeUserOverride = false
|
|
202
246
|
): EC | undefined => {
|
|
203
|
-
|
|
247
|
+
const collections = collectionsRef.current;
|
|
204
248
|
if (!collections)
|
|
205
249
|
return undefined;
|
|
206
250
|
|
|
207
251
|
const baseCollection = getCollectionByPathOrId(removeInitialAndTrailingSlashes(idOrPath), collections);
|
|
208
252
|
|
|
209
253
|
const userOverride = includeUserOverride ? userConfigPersistence?.getCollectionConfig(idOrPath) : undefined;
|
|
210
|
-
|
|
211
254
|
const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride) : undefined;
|
|
212
255
|
|
|
213
256
|
let result: Partial<EntityCollection> | undefined = overriddenCollection;
|
|
@@ -228,16 +271,14 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
228
271
|
|
|
229
272
|
return { ...overriddenCollection, ...result } as EC;
|
|
230
273
|
|
|
231
|
-
}, [
|
|
232
|
-
basePath,
|
|
233
|
-
baseCollectionPath,
|
|
234
|
-
collections
|
|
235
|
-
]);
|
|
274
|
+
}, [userConfigPersistence]);
|
|
236
275
|
|
|
237
276
|
const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
277
|
+
|
|
278
|
+
const collections = collectionsRef.current;
|
|
279
|
+
if (collections === undefined)
|
|
280
|
+
throw Error("getCollectionFromPaths: Collections have not been initialised yet");
|
|
281
|
+
let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
|
|
241
282
|
|
|
242
283
|
for (let i = 0; i < pathSegments.length; i++) {
|
|
243
284
|
const pathSegment = pathSegments[i];
|
|
@@ -251,12 +292,14 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
251
292
|
|
|
252
293
|
return undefined;
|
|
253
294
|
|
|
254
|
-
}, [
|
|
295
|
+
}, []);
|
|
255
296
|
|
|
256
297
|
const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
298
|
+
|
|
299
|
+
const collections = collectionsRef.current;
|
|
300
|
+
if (collections === undefined)
|
|
301
|
+
throw Error("getCollectionFromIds: Collections have not been initialised yet");
|
|
302
|
+
let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
|
|
260
303
|
|
|
261
304
|
for (let i = 0; i < ids.length; i++) {
|
|
262
305
|
const id = ids[i];
|
|
@@ -270,7 +313,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
270
313
|
|
|
271
314
|
return undefined;
|
|
272
315
|
|
|
273
|
-
}, [
|
|
316
|
+
}, []);
|
|
274
317
|
|
|
275
318
|
const isUrlCollectionPath = useCallback(
|
|
276
319
|
(path: string): boolean => removeInitialAndTrailingSlashes(path + "/").startsWith(removeInitialAndTrailingSlashes(fullCollectionPath) + "/"),
|
|
@@ -292,17 +335,17 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
292
335
|
[]);
|
|
293
336
|
|
|
294
337
|
const resolveAliasesFrom = useCallback((path: string): string => {
|
|
295
|
-
|
|
296
|
-
throw Error("Collections have not been initialised yet");
|
|
338
|
+
const collections = collectionsRef.current ?? [];
|
|
297
339
|
return resolveCollectionPathIds(path, collections);
|
|
298
|
-
}, [
|
|
340
|
+
}, []);
|
|
299
341
|
|
|
300
342
|
const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
|
|
343
|
+
const collections = collectionsRef.current ?? [];
|
|
301
344
|
return getParentReferencesFromPath({
|
|
302
345
|
path,
|
|
303
346
|
collections
|
|
304
347
|
});
|
|
305
|
-
}, [
|
|
348
|
+
}, []);
|
|
306
349
|
|
|
307
350
|
const getParentCollectionIds = useCallback((path: string): string[] => {
|
|
308
351
|
|
|
@@ -321,24 +364,24 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
|
|
|
321
364
|
}, [getAllParentReferencesForPath])
|
|
322
365
|
|
|
323
366
|
const convertIdsToPaths = useCallback((ids: string[]): string[] => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return paths;
|
|
367
|
+
const collections = collectionsRef.current;
|
|
368
|
+
let currentCollections = collections;
|
|
369
|
+
const paths: string[] = [];
|
|
370
|
+
for (let i = 0; i < ids.length; i++) {
|
|
371
|
+
const id = ids[i];
|
|
372
|
+
const collection: EntityCollection | undefined = currentCollections!.find(c => c.id === id);
|
|
373
|
+
if (!collection)
|
|
374
|
+
throw Error(`Collection with id ${id} not found`);
|
|
375
|
+
paths.push(collection.path);
|
|
376
|
+
currentCollections = collection.subcollections;
|
|
335
377
|
}
|
|
336
|
-
|
|
378
|
+
return paths;
|
|
379
|
+
}, [getCollectionFromIds]);
|
|
337
380
|
|
|
338
381
|
return {
|
|
339
|
-
collections,
|
|
340
|
-
views,
|
|
341
|
-
adminViews,
|
|
382
|
+
collections: collectionsRef.current,
|
|
383
|
+
views: viewsRef.current,
|
|
384
|
+
adminViews: adminViewsRef.current,
|
|
342
385
|
loading: !initialised || navigationLoading,
|
|
343
386
|
navigationLoadingError,
|
|
344
387
|
homeUrl,
|
|
@@ -395,7 +438,7 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
|
|
|
395
438
|
collectionPermissions: PermissionsBuilder | undefined,
|
|
396
439
|
authController: AuthController,
|
|
397
440
|
dataSource: DataSourceDelegate,
|
|
398
|
-
injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
|
|
441
|
+
injectCollections?: (collections: EntityCollection[]) => EntityCollection[]): Promise<EntityCollection[]> {
|
|
399
442
|
let resolvedCollections: EntityCollection[] = [];
|
|
400
443
|
if (typeof collections === "function") {
|
|
401
444
|
resolvedCollections = await collections({
|
|
@@ -407,14 +450,14 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
|
|
|
407
450
|
resolvedCollections = collections;
|
|
408
451
|
}
|
|
409
452
|
|
|
410
|
-
resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
|
|
411
|
-
|
|
412
|
-
resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
|
|
413
|
-
|
|
414
453
|
if (injectCollections) {
|
|
415
454
|
resolvedCollections = injectCollections(resolvedCollections ?? []);
|
|
416
455
|
}
|
|
417
456
|
|
|
457
|
+
resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
|
|
458
|
+
|
|
459
|
+
resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
|
|
460
|
+
|
|
418
461
|
return resolvedCollections;
|
|
419
462
|
}
|
|
420
463
|
|
|
@@ -431,3 +474,37 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
|
|
|
431
474
|
}
|
|
432
475
|
return resolvedViews;
|
|
433
476
|
}
|
|
477
|
+
|
|
478
|
+
function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
|
|
479
|
+
const trimmed = collectionOrView.group?.trim();
|
|
480
|
+
if (!trimmed || trimmed === "") {
|
|
481
|
+
return "Views";
|
|
482
|
+
}
|
|
483
|
+
return trimmed ?? "Views";
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
|
|
487
|
+
if (a.length !== b.length) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
const aCopy = [...a];
|
|
491
|
+
const bCopy = [...b];
|
|
492
|
+
const aSorted = aCopy.sort((x, y) => x.id.localeCompare(y.id));
|
|
493
|
+
const bSorted = bCopy.sort((x, y) => x.id.localeCompare(y.id));
|
|
494
|
+
return aSorted.every((value, index) => areCollectionsEqual(value, bSorted[index]));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
|
|
498
|
+
const {
|
|
499
|
+
subcollections: subcollectionsA,
|
|
500
|
+
...restA
|
|
501
|
+
} = a;
|
|
502
|
+
const {
|
|
503
|
+
subcollections: subcollectionsB,
|
|
504
|
+
...restB
|
|
505
|
+
} = b;
|
|
506
|
+
if (!areCollectionListsEqual(subcollectionsA ?? [], subcollectionsB ?? [])) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
return equal(restA, restB);
|
|
510
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { AuthController } from "../types";
|
|
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";
|
|
@@ -9,7 +9,7 @@ export type AccessResponse = {
|
|
|
9
9
|
message?: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
async function makeRequest(authController: AuthController) {
|
|
12
|
+
async function makeRequest(authController: AuthController, pluginKeys: string[] | undefined) {
|
|
13
13
|
const firebaseToken = await authController.getAuthToken();
|
|
14
14
|
return fetch(DEFAULT_SERVER + "/access_log",
|
|
15
15
|
{
|
|
@@ -19,21 +19,23 @@ async function makeRequest(authController: AuthController) {
|
|
|
19
19
|
"Content-Type": "application/json",
|
|
20
20
|
Authorization: `Basic ${firebaseToken}`,
|
|
21
21
|
},
|
|
22
|
-
body: JSON.stringify({})
|
|
22
|
+
body: JSON.stringify({ plugins: pluginKeys })
|
|
23
23
|
})
|
|
24
24
|
.then(async (res) => {
|
|
25
25
|
return res.json();
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export function useProjectLog(authController: AuthController
|
|
29
|
+
export function useProjectLog(authController: AuthController,
|
|
30
|
+
plugins?: FireCMSPlugin<any, any, any>[]): AccessResponse | null {
|
|
30
31
|
const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
|
|
31
32
|
const accessedUserRef = useRef<string | null>(null);
|
|
33
|
+
const pluginKeys = plugins?.map(plugin => plugin.key);
|
|
32
34
|
useEffect(() => {
|
|
33
35
|
if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
|
|
34
|
-
makeRequest(authController).then(setAccessResponse);
|
|
36
|
+
makeRequest(authController, pluginKeys).then(setAccessResponse);
|
|
35
37
|
accessedUserRef.current = authController.user.uid;
|
|
36
38
|
}
|
|
37
|
-
}, [authController]);
|
|
39
|
+
}, [authController, pluginKeys]);
|
|
38
40
|
return accessResponse;
|
|
39
41
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import equal from "react-fast-compare";
|
|
3
|
+
|
|
4
|
+
import { 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 storageSource
|
|
12
|
+
* @param dataSourceDelegate
|
|
13
|
+
*/
|
|
14
|
+
export function useValidateAuthenticator<UserType extends User = User, Controller extends AuthController<UserType> = AuthController<UserType>>({
|
|
15
|
+
disabled,
|
|
16
|
+
authController,
|
|
17
|
+
authenticator,
|
|
18
|
+
storageSource,
|
|
19
|
+
dataSourceDelegate
|
|
20
|
+
}:
|
|
21
|
+
{
|
|
22
|
+
disabled?: boolean,
|
|
23
|
+
authController: Controller,
|
|
24
|
+
authenticator?: boolean | Authenticator<UserType, Controller>,
|
|
25
|
+
dataSourceDelegate: DataSourceDelegate;
|
|
26
|
+
storageSource: StorageSource;
|
|
27
|
+
}): {
|
|
28
|
+
canAccessMainView: boolean,
|
|
29
|
+
authLoading: boolean,
|
|
30
|
+
notAllowedError: any,
|
|
31
|
+
authVerified: boolean,
|
|
32
|
+
} {
|
|
33
|
+
|
|
34
|
+
const authenticationEnabled = Boolean(authenticator);
|
|
35
|
+
|
|
36
|
+
const [authLoading, setAuthLoading] = useState<boolean>(authenticationEnabled);
|
|
37
|
+
const [notAllowedError, setNotAllowedError] = useState<any>(false);
|
|
38
|
+
const [authVerified, setAuthVerified] = useState<boolean>(!authenticationEnabled || Boolean(authController.loginSkipped));
|
|
39
|
+
|
|
40
|
+
const canAccessMainView = (authVerified) &&
|
|
41
|
+
(!authenticationEnabled || Boolean(authController.user) || Boolean(authController.loginSkipped)) &&
|
|
42
|
+
!notAllowedError;
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (authController.loginSkipped)
|
|
46
|
+
setAuthVerified(true);
|
|
47
|
+
}, [authController.loginSkipped]);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* We use this ref to check the authentication only if the user has
|
|
51
|
+
* changed.
|
|
52
|
+
*/
|
|
53
|
+
const checkedUserRef = useRef<User | undefined>();
|
|
54
|
+
|
|
55
|
+
const checkAuthentication = useCallback(async () => {
|
|
56
|
+
|
|
57
|
+
if (disabled) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (authController.initialLoading) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!authController.user && !authController.loginSkipped) {
|
|
66
|
+
checkedUserRef.current = undefined;
|
|
67
|
+
setAuthLoading(false);
|
|
68
|
+
setAuthVerified(false);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const delegateUser = authController.user;
|
|
73
|
+
console.debug("Checking authentication for user", delegateUser);
|
|
74
|
+
|
|
75
|
+
if (authenticator instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
|
|
76
|
+
setAuthLoading(true);
|
|
77
|
+
try {
|
|
78
|
+
const allowed = await authenticator({
|
|
79
|
+
user: delegateUser,
|
|
80
|
+
authController,
|
|
81
|
+
dataSourceDelegate,
|
|
82
|
+
storageSource
|
|
83
|
+
});
|
|
84
|
+
if (!allowed) {
|
|
85
|
+
authController.signOut();
|
|
86
|
+
setNotAllowedError(true);
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
setNotAllowedError(e);
|
|
90
|
+
authController.signOut();
|
|
91
|
+
}
|
|
92
|
+
setAuthLoading(false);
|
|
93
|
+
setAuthVerified(true);
|
|
94
|
+
checkedUserRef.current = delegateUser;
|
|
95
|
+
} else {
|
|
96
|
+
setAuthLoading(false);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!authController.initialLoading && !delegateUser) {
|
|
100
|
+
setAuthVerified(true);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}, [disabled, authController, authenticator, dataSourceDelegate, storageSource]);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
checkAuthentication();
|
|
107
|
+
}, [checkAuthentication]);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
canAccessMainView,
|
|
111
|
+
authLoading: authenticationEnabled && authLoading,
|
|
112
|
+
notAllowedError,
|
|
113
|
+
authVerified
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -5,12 +5,10 @@ import {
|
|
|
5
5
|
DeleteEntityProps,
|
|
6
6
|
Entity,
|
|
7
7
|
EntityCollection,
|
|
8
|
-
EntityReference,
|
|
9
8
|
EntityValues,
|
|
10
9
|
FetchCollectionProps,
|
|
11
10
|
FetchEntityProps,
|
|
12
11
|
FilterValues,
|
|
13
|
-
GeoPoint,
|
|
14
12
|
ListenCollectionProps,
|
|
15
13
|
ListenEntityProps,
|
|
16
14
|
NavigationController,
|
|
@@ -207,13 +205,10 @@ export function useBuildDataSource({
|
|
|
207
205
|
|
|
208
206
|
const properties: ResolvedProperties<M> | undefined = resolvedCollection?.properties;
|
|
209
207
|
|
|
210
|
-
const firestoreValues = cmsToDelegateModel(
|
|
208
|
+
const firestoreValues = delegate.cmsToDelegateModel(
|
|
211
209
|
values,
|
|
212
|
-
delegate.buildReference,
|
|
213
|
-
delegate.buildGeoPoint,
|
|
214
|
-
delegate.buildDate,
|
|
215
|
-
delegate.buildDeleteFieldValue
|
|
216
210
|
);
|
|
211
|
+
|
|
217
212
|
const updatedFirestoreValues: EntityValues<M> = properties
|
|
218
213
|
? updateDateAutoValues(
|
|
219
214
|
{
|
|
@@ -316,48 +311,48 @@ export function useBuildDataSource({
|
|
|
316
311
|
sortBy
|
|
317
312
|
}
|
|
318
313
|
)
|
|
319
|
-
}, [delegate.isFilterCombinationValid])
|
|
314
|
+
}, [delegate.isFilterCombinationValid]),
|
|
320
315
|
|
|
321
316
|
};
|
|
322
317
|
|
|
323
318
|
}
|
|
324
319
|
|
|
325
|
-
/**
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
export function cmsToDelegateModel(data: any,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
): any {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
320
|
+
// /**
|
|
321
|
+
// * Recursive function that converts Firestore data types into CMS or plain
|
|
322
|
+
// * JS types.
|
|
323
|
+
// * FireCMS uses Javascript dates internally instead of Firestore timestamps.
|
|
324
|
+
// * This makes it easier to interact with the rest of the libraries and
|
|
325
|
+
// * bindings.
|
|
326
|
+
// * Also, Firestore references are replaced with {@link EntityReference}
|
|
327
|
+
// * @param data
|
|
328
|
+
// * @param buildReference
|
|
329
|
+
// * @param buildGeoPoint
|
|
330
|
+
// * @param buildDate
|
|
331
|
+
// * @param buildDelete
|
|
332
|
+
// * @group Firestore
|
|
333
|
+
// */
|
|
334
|
+
// export function cmsToDelegateModel(data: any,
|
|
335
|
+
// buildReference: (reference: EntityReference) => any,
|
|
336
|
+
// buildGeoPoint: (geoPoint: GeoPoint) => any,
|
|
337
|
+
// buildDate: (date: Date) => any,
|
|
338
|
+
// buildDelete: () => any
|
|
339
|
+
// ): any {
|
|
340
|
+
// if (data === undefined) {
|
|
341
|
+
// return buildDelete();
|
|
342
|
+
// } else if (data === null) {
|
|
343
|
+
// return null;
|
|
344
|
+
// } else if (Array.isArray(data)) {
|
|
345
|
+
// return data.map(v => cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete));
|
|
346
|
+
// } else if (data.isEntityReference && data.isEntityReference()) {
|
|
347
|
+
// return buildReference(data);
|
|
348
|
+
// } else if (data instanceof GeoPoint) {
|
|
349
|
+
// return buildGeoPoint(data);
|
|
350
|
+
// } else if (data instanceof Date) {
|
|
351
|
+
// return buildDate(data);
|
|
352
|
+
// } else if (data && typeof data === "object") {
|
|
353
|
+
// return Object.entries(data)
|
|
354
|
+
// .map(([key, v]) => ({ [key]: cmsToDelegateModel(v, buildReference, buildGeoPoint, buildDate, buildDelete) }))
|
|
355
|
+
// .reduce((a, b) => ({ ...a, ...b }), {});
|
|
356
|
+
// }
|
|
357
|
+
// return data;
|
|
358
|
+
// }
|