@firecms/core 3.3.0-canary.d3242eb → 3.4.0-canary.00f9be5
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 +4 -0
- package/dist/app/useApp.d.ts +1 -0
- package/dist/components/EntityCollectionTable/column_utils.d.ts +4 -3
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +6 -1
- package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +3 -1
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -1
- package/dist/components/EntityCollectionView/useBoardDataController.d.ts +3 -1
- package/dist/components/EntityPreview.d.ts +3 -1
- package/dist/components/ErrorBoundary.d.ts +1 -3
- package/dist/components/LicenseBanner.d.ts +23 -0
- package/dist/components/ReferenceTable/ReferenceSelectionTable.d.ts +10 -1
- package/dist/components/SelectableTable/filters/filter_text_input.d.ts +30 -0
- package/dist/components/SelectableTable/filters/filter_text_input.test.d.ts +1 -0
- package/dist/components/common/table_url_params.d.ts +24 -0
- package/dist/components/common/useDataSourceTableController.d.ts +9 -1
- package/dist/components/common/useTableSearchHelper.d.ts +9 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/contexts/LicenseStatusContext.d.ts +16 -0
- package/dist/core/DefaultDrawer.d.ts +10 -3
- package/dist/core/EntityEditView.d.ts +9 -1
- package/dist/core/EntityEditViewFormActions.d.ts +1 -1
- package/dist/core/field_configs.d.ts +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/plugin_translations.d.ts +38 -0
- package/dist/core/pro_plugins.d.ts +40 -0
- package/dist/de-BF5vcQPY.js +703 -0
- package/dist/de-BF5vcQPY.js.map +1 -0
- package/dist/editor/extensions/Image.d.ts +11 -0
- package/dist/editor/index.d.ts +2 -1
- package/dist/editor/lazy_editor.d.ts +6 -0
- package/dist/editor-BSo_aS1I.js +4563 -0
- package/dist/editor-BSo_aS1I.js.map +1 -0
- package/dist/form/EntityForm.d.ts +8 -1
- package/dist/form/EntityFormActions.d.ts +2 -0
- package/dist/form/field_bindings/GeopointFieldBinding.d.ts +5 -0
- package/dist/form/index.d.ts +1 -0
- package/dist/fr-DH9_wyFI.js +703 -0
- package/dist/fr-DH9_wyFI.js.map +1 -0
- package/dist/hi-BM8dm1Lu.js +703 -0
- package/dist/hi-BM8dm1Lu.js.map +1 -0
- package/dist/hooks/data/delete.d.ts +1 -1
- package/dist/hooks/data/save.d.ts +1 -1
- package/dist/hooks/data/useCollectionFetch.d.ts +7 -1
- package/dist/hooks/data/useEntityFetch.d.ts +9 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useLicenseStatus.d.ts +16 -0
- package/dist/hooks/useProjectLog.d.ts +11 -6
- package/dist/hooks/useTranslation.d.ts +1 -1
- package/dist/i18n/__tests__/FireCMSi18nProvider.test.d.ts +1 -0
- package/dist/i18n/__tests__/nested_provider_lifecycle.test.d.ts +1 -0
- package/dist/index.es.js +4429 -10479
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +9313 -6582
- package/dist/index.umd.js.map +1 -1
- package/dist/it-D3K1gfe4.js +703 -0
- package/dist/it-D3K1gfe4.js.map +1 -0
- package/dist/locales/pl.d.ts +2 -0
- package/dist/pl-CP1zeLzn.js +705 -0
- package/dist/pl-CP1zeLzn.js.map +1 -0
- package/dist/preview/index.d.ts +1 -0
- package/dist/preview/property_previews/GeopointPropertyPreview.d.ts +4 -0
- package/dist/pt-Cus7eKQR.js +705 -0
- package/dist/pt-Cus7eKQR.js.map +1 -0
- package/dist/types/analytics.d.ts +1 -1
- package/dist/types/collections.d.ts +31 -0
- package/dist/types/datasource.d.ts +67 -4
- package/dist/types/entities.d.ts +21 -1
- package/dist/types/entity_actions.d.ts +6 -0
- package/dist/types/entity_callbacks.d.ts +21 -0
- package/dist/types/firecms.d.ts +18 -1
- package/dist/types/firecms_context.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/license.d.ts +55 -0
- package/dist/types/navigation.d.ts +34 -5
- package/dist/types/plugins.d.ts +22 -1
- package/dist/types/properties.d.ts +17 -1
- package/dist/types/side_entity_controller.d.ts +7 -0
- package/dist/types/translations.d.ts +54 -5
- package/dist/util/__tests__/collections.test.d.ts +1 -0
- package/dist/util/__tests__/urls.test.d.ts +1 -0
- package/dist/util/collections.d.ts +1 -1
- package/dist/util/date_fns_locales.d.ts +18 -0
- package/dist/util/entities.d.ts +1 -0
- package/dist/util/entity_cache.d.ts +11 -0
- package/dist/util/geopoint.d.ts +22 -0
- package/dist/util/index.d.ts +3 -0
- package/dist/util/lazy_eager.d.ts +3 -3
- package/dist/util/navigation_blocking.d.ts +22 -0
- package/dist/util/navigation_from_path.d.ts +17 -0
- package/dist/util/navigation_utils.d.ts +107 -3
- package/dist/util/parent_references_from_path.d.ts +16 -0
- package/dist/util/paths.d.ts +28 -0
- package/dist/util/permissions.d.ts +10 -4
- package/dist/util/urls.d.ts +22 -0
- package/package.json +17 -11
- package/src/app/Scaffold.tsx +54 -45
- package/src/app/useApp.tsx +1 -0
- package/src/components/DeleteEntityDialog.tsx +2 -0
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +3 -1
- package/src/components/EntityCollectionTable/column_utils.tsx +11 -19
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +7 -0
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -1
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +71 -17
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +6 -2
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +8 -1
- package/src/components/EntityCollectionView/FiltersDialog.tsx +39 -28
- package/src/components/EntityCollectionView/useBoardDataController.tsx +17 -1
- package/src/components/EntityPreview.tsx +45 -41
- package/src/components/ErrorBoundary.tsx +12 -18
- package/src/components/HomePage/NavigationCardBinding.tsx +6 -3
- package/src/components/LicenseBanner.tsx +207 -0
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +19 -2
- package/src/components/ReferenceWidget.tsx +1 -1
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +33 -7
- package/src/components/SelectableTable/filters/filter_text_input.test.ts +184 -0
- package/src/components/SelectableTable/filters/filter_text_input.ts +71 -0
- package/src/components/VirtualTable/VirtualTable.tsx +19 -8
- package/src/components/common/default_entity_actions.tsx +22 -2
- package/src/components/common/table_url_params.ts +172 -0
- package/src/components/common/useDataSourceTableController.tsx +126 -163
- package/src/components/common/useTableSearchHelper.ts +26 -2
- package/src/components/index.tsx +1 -0
- package/src/contexts/LicenseStatusContext.tsx +50 -0
- package/src/contexts/SnackbarProvider.tsx +14 -1
- package/src/core/DefaultAppBar.tsx +28 -16
- package/src/core/DefaultDrawer.tsx +151 -65
- package/src/core/DrawerNavigationGroup.tsx +27 -29
- package/src/core/DrawerNavigationItem.tsx +10 -12
- package/src/core/EntityEditView.tsx +77 -35
- package/src/core/EntityEditViewFormActions.tsx +3 -2
- package/src/core/EntitySidePanel.tsx +8 -4
- package/src/core/FireCMS.tsx +73 -47
- package/src/core/SideDialogs.tsx +44 -1
- package/src/core/field_configs.tsx +15 -0
- package/src/core/index.tsx +1 -0
- package/src/core/plugin_translations.ts +87 -0
- package/src/core/pro_plugins.ts +79 -0
- package/src/editor/components/SlashCommandMenu.tsx +4 -1
- package/src/editor/extensions/Image.ts +17 -1
- package/src/editor/hooks/useProseMirror.ts +5 -0
- package/src/editor/index.ts +11 -1
- package/src/editor/lazy_editor.tsx +33 -0
- package/src/form/EntityForm.tsx +31 -10
- package/src/form/EntityFormActions.tsx +2 -0
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
- package/src/form/field_bindings/GeopointFieldBinding.tsx +139 -0
- package/src/form/index.tsx +1 -0
- package/src/hooks/data/delete.ts +8 -0
- package/src/hooks/data/save.ts +6 -1
- package/src/hooks/data/useCollectionFetch.tsx +21 -1
- package/src/hooks/data/useEntityFetch.tsx +35 -4
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/useBuildNavigationController.tsx +75 -16
- package/src/hooks/useFireCMSContext.tsx +7 -3
- package/src/hooks/useLicenseStatus.tsx +32 -0
- package/src/hooks/useProjectLog.tsx +27 -9
- package/src/hooks/useResolvedNavigationFrom.tsx +3 -1
- package/src/hooks/useTranslation.ts +3 -1
- package/src/hooks/useValidateAuthenticator.tsx +4 -0
- package/src/i18n/FireCMSi18nProvider.tsx +199 -23
- package/src/i18n/__tests__/FireCMSi18nProvider.test.tsx +91 -0
- package/src/i18n/__tests__/nested_provider_lifecycle.test.tsx +67 -0
- package/src/internal/useBuildDataSource.ts +37 -15
- package/src/internal/useBuildSideEntityController.tsx +20 -5
- package/src/locales/de.ts +37 -7
- package/src/locales/en.ts +41 -9
- package/src/locales/es.ts +37 -7
- package/src/locales/fr.ts +37 -7
- package/src/locales/hi.ts +37 -7
- package/src/locales/it.ts +37 -7
- package/src/locales/pl.ts +752 -0
- package/src/locales/pt.ts +37 -7
- package/src/preview/PropertyPreview.tsx +12 -0
- package/src/preview/components/DatePreview.tsx +2 -4
- package/src/preview/components/ReferencePreview.tsx +7 -3
- package/src/preview/components/StorageThumbnail.tsx +24 -2
- package/src/preview/index.ts +1 -0
- package/src/preview/property_previews/GeopointPropertyPreview.tsx +23 -0
- package/src/routes/FireCMSRoute.tsx +54 -27
- package/src/types/analytics.ts +3 -0
- package/src/types/collections.ts +34 -0
- package/src/types/datasource.ts +67 -4
- package/src/types/entities.ts +24 -1
- package/src/types/entity_actions.tsx +6 -0
- package/src/types/entity_callbacks.ts +24 -0
- package/src/types/firecms.tsx +19 -1
- package/src/types/firecms_context.tsx +9 -0
- package/src/types/index.ts +1 -0
- package/src/types/license.ts +63 -0
- package/src/types/navigation.ts +35 -5
- package/src/types/plugins.tsx +23 -1
- package/src/types/properties.ts +17 -0
- package/src/types/side_entity_controller.tsx +7 -0
- package/src/types/translations.ts +54 -5
- package/src/util/__tests__/collections.test.ts +32 -0
- package/src/util/__tests__/urls.test.ts +131 -0
- package/src/util/collections.ts +1 -1
- package/src/util/date_fns_locales.ts +168 -0
- package/src/util/entities.ts +16 -1
- package/src/util/entity_cache.ts +18 -0
- package/src/util/geopoint.ts +81 -0
- package/src/util/index.ts +3 -0
- package/src/util/lazy_eager.tsx +30 -8
- package/src/util/navigation_blocking.ts +45 -0
- package/src/util/navigation_from_path.ts +47 -7
- package/src/util/navigation_utils.ts +250 -5
- package/src/util/parent_references_from_path.ts +45 -4
- package/src/util/paths.ts +40 -3
- package/src/util/permissions.ts +22 -10
- package/src/util/previews.ts +8 -1
- package/src/util/resolutions.ts +11 -0
- package/src/util/urls.ts +52 -0
- package/src/util/useStorageUploadController.tsx +6 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import equal from "react-fast-compare"
|
|
3
3
|
import { useBlocker, useNavigate } from "react-router-dom";
|
|
4
|
+
import { filterPausedPlugins, isProPaused } from "../core/pro_plugins";
|
|
5
|
+
import { useLicenseStatus } from "./useLicenseStatus";
|
|
4
6
|
|
|
5
7
|
import {
|
|
6
8
|
AuthController,
|
|
@@ -22,11 +24,14 @@ import {
|
|
|
22
24
|
} from "../types";
|
|
23
25
|
import {
|
|
24
26
|
applyPermissionsFunctionIfEmpty,
|
|
27
|
+
collectionSegmentsFrom,
|
|
28
|
+
fullPathToCollectionSegments,
|
|
25
29
|
getCollectionByPathOrId,
|
|
26
30
|
mergeDeep,
|
|
27
31
|
removeFunctions,
|
|
28
32
|
removeInitialAndTrailingSlashes,
|
|
29
33
|
resolveCollectionPathIds,
|
|
34
|
+
resolveCollectionPathSegments,
|
|
30
35
|
resolvePermissions
|
|
31
36
|
} from "../util";
|
|
32
37
|
import { getParentReferencesFromPath } from "../util/parent_references_from_path";
|
|
@@ -112,7 +117,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
112
117
|
views: viewsProp,
|
|
113
118
|
adminViews: adminViewsProp,
|
|
114
119
|
viewsOrder,
|
|
115
|
-
plugins,
|
|
120
|
+
plugins: allPlugins,
|
|
116
121
|
userConfigPersistence,
|
|
117
122
|
dataSourceDelegate,
|
|
118
123
|
disabled,
|
|
@@ -121,6 +126,27 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
121
126
|
|
|
122
127
|
const navigate = useNavigate();
|
|
123
128
|
|
|
129
|
+
// While PRO is paused, PRO plugins contribute no collections, collection
|
|
130
|
+
// modifications, views or navigation entries. This hook runs above
|
|
131
|
+
// `FireCMS`, so the status arrives through the store `FireCMS` publishes to,
|
|
132
|
+
// and its arrival re-renders this hook, which resolves navigation again.
|
|
133
|
+
const licenseStatus = useLicenseStatus();
|
|
134
|
+
const proPaused = isProPaused(licenseStatus);
|
|
135
|
+
const plugins = useMemo(
|
|
136
|
+
() => filterPausedPlugins(allPlugins, licenseStatus),
|
|
137
|
+
[allPlugins, licenseStatus]
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Navigation resolves asynchronously, so a resolution started before the
|
|
141
|
+
// pause changed can finish after one started since. This ref holds the
|
|
142
|
+
// current pause so the older one is dropped instead of restoring what the
|
|
143
|
+
// paused plugins contributed. Declared before the effect that refreshes.
|
|
144
|
+
const proPausedRef = useRef(proPaused);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
proPausedRef.current = proPaused;
|
|
147
|
+
}, [proPaused]);
|
|
148
|
+
const resolvedProPausedRef = useRef<boolean | undefined>(undefined);
|
|
149
|
+
|
|
124
150
|
const collectionsRef = useRef<EntityCollection[] | undefined>(undefined);
|
|
125
151
|
const viewsRef = useRef<CMSView[] | undefined>(undefined);
|
|
126
152
|
const adminViewsRef = useRef<CMSView[] | undefined>(undefined);
|
|
@@ -299,7 +325,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
299
325
|
groups: uniqueGroups,
|
|
300
326
|
onNavigationEntriesUpdate: onNavigationEntriesUpdateCallback!,
|
|
301
327
|
};
|
|
302
|
-
}, [navigationGroupMappings, buildCMSUrlPath, buildUrlCollectionPath, pluginGroups]);
|
|
328
|
+
}, [navigationGroupMappings, buildCMSUrlPath, buildUrlCollectionPath, pluginGroups, plugins]);
|
|
303
329
|
|
|
304
330
|
const onNavigationEntriesOrderUpdate = useCallback((entries: NavigationGroupMapping[]) => {
|
|
305
331
|
if (!plugins) {
|
|
@@ -339,6 +365,8 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
339
365
|
|
|
340
366
|
console.debug("Refreshing navigation");
|
|
341
367
|
|
|
368
|
+
const resolvingWhilePaused = proPaused;
|
|
369
|
+
|
|
342
370
|
try {
|
|
343
371
|
|
|
344
372
|
const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
|
|
@@ -348,10 +376,21 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
348
376
|
]
|
|
349
377
|
);
|
|
350
378
|
|
|
379
|
+
// Superseded: a newer resolution runs with the current pause.
|
|
380
|
+
if (resolvingWhilePaused !== proPausedRef.current)
|
|
381
|
+
return;
|
|
382
|
+
|
|
351
383
|
const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder, undefined, onNavigationEntriesOrderUpdate);
|
|
352
384
|
|
|
353
|
-
|
|
354
|
-
|
|
385
|
+
// When the pause changes, everything below is replaced, including
|
|
386
|
+
// what the comparisons ignore: they skip functions, and a plugin may
|
|
387
|
+
// have added only collection callbacks, or only regrouped entries.
|
|
388
|
+
const pauseChanged = resolvedProPausedRef.current !== undefined
|
|
389
|
+
&& resolvedProPausedRef.current !== resolvingWhilePaused;
|
|
390
|
+
resolvedProPausedRef.current = resolvingWhilePaused;
|
|
391
|
+
|
|
392
|
+
let shouldUpdateTopLevelNav = pauseChanged;
|
|
393
|
+
if (pauseChanged || !areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
|
|
355
394
|
collectionsRef.current = resolvedCollections;
|
|
356
395
|
console.debug("Collections have changed", resolvedCollections);
|
|
357
396
|
shouldUpdateTopLevelNav = true;
|
|
@@ -360,7 +399,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
360
399
|
collectionsRef.current = resolvedCollections;
|
|
361
400
|
shouldUpdateTopLevelNav = true;
|
|
362
401
|
}
|
|
363
|
-
if (!equal(viewsRef.current, resolvedViews)) {
|
|
402
|
+
if (pauseChanged || !equal(viewsRef.current, resolvedViews)) {
|
|
364
403
|
viewsRef.current = resolvedViews;
|
|
365
404
|
shouldUpdateTopLevelNav = true;
|
|
366
405
|
}
|
|
@@ -397,6 +436,8 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
397
436
|
viewsProp,
|
|
398
437
|
adminViewsProp,
|
|
399
438
|
computeTopNavigation,
|
|
439
|
+
plugins,
|
|
440
|
+
proPaused
|
|
400
441
|
]);
|
|
401
442
|
|
|
402
443
|
useEffect(() => {
|
|
@@ -405,16 +446,21 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
405
446
|
|
|
406
447
|
const getCollection = useCallback((
|
|
407
448
|
idOrPath: string,
|
|
408
|
-
includeUserOverride = false
|
|
449
|
+
includeUserOverride = false,
|
|
450
|
+
pathSegments?: string[]
|
|
409
451
|
): EC | undefined => {
|
|
410
452
|
const collections = collectionsRef.current;
|
|
411
453
|
if (!collections)
|
|
412
454
|
return undefined;
|
|
413
455
|
|
|
414
|
-
const baseCollection = getCollectionByPathOrId(removeInitialAndTrailingSlashes(idOrPath), collections);
|
|
456
|
+
const baseCollection = getCollectionByPathOrId(removeInitialAndTrailingSlashes(idOrPath), collections, pathSegments);
|
|
415
457
|
|
|
416
458
|
const userOverride = includeUserOverride ? userConfigPersistence?.getCollectionConfig(idOrPath) : undefined;
|
|
417
|
-
|
|
459
|
+
let overriddenCollection = baseCollection;
|
|
460
|
+
if (baseCollection && userOverride) {
|
|
461
|
+
const { properties, ...rest } = userOverride;
|
|
462
|
+
overriddenCollection = mergeDeep(baseCollection, rest);
|
|
463
|
+
}
|
|
418
464
|
|
|
419
465
|
let result: Partial<EntityCollection> | undefined = overriddenCollection;
|
|
420
466
|
|
|
@@ -501,23 +547,33 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
501
547
|
throw Error("Expected path starting with " + fullCollectionPath);
|
|
502
548
|
}, [fullCollectionPath]);
|
|
503
549
|
|
|
504
|
-
const resolveIdsFrom = useCallback((path: string): string => {
|
|
550
|
+
const resolveIdsFrom = useCallback((path: string, pathSegments?: string[]): string => {
|
|
551
|
+
const collections = collectionsRef.current ?? [];
|
|
552
|
+
return resolveCollectionPathIds(path, collections, pathSegments);
|
|
553
|
+
}, []);
|
|
554
|
+
|
|
555
|
+
const resolveSegmentsFrom = useCallback((pathSegments: string[]): string[] => {
|
|
505
556
|
const collections = collectionsRef.current ?? [];
|
|
506
|
-
return
|
|
557
|
+
return resolveCollectionPathSegments(pathSegments, collections);
|
|
507
558
|
}, []);
|
|
508
559
|
|
|
509
|
-
const getAllParentReferencesForPath = useCallback((path: string): EntityReference[] => {
|
|
560
|
+
const getAllParentReferencesForPath = useCallback((path: string, pathSegments?: string[]): EntityReference[] => {
|
|
510
561
|
const collections = collectionsRef.current ?? [];
|
|
511
562
|
return getParentReferencesFromPath({
|
|
512
563
|
path,
|
|
564
|
+
pathSegments,
|
|
513
565
|
collections
|
|
514
566
|
});
|
|
515
567
|
}, []);
|
|
516
568
|
|
|
517
|
-
const getParentCollectionIds = useCallback((path: string): string[] => {
|
|
569
|
+
const getParentCollectionIds = useCallback((path: string, pathSegments?: string[]): string[] => {
|
|
518
570
|
|
|
519
|
-
|
|
520
|
-
|
|
571
|
+
// Positional filtering only picks out the collection segments while no entity id
|
|
572
|
+
// contains "/"; with one, every following segment shifts. Given the real boundaries
|
|
573
|
+
// the same rule applies correctly; without them this is the historical reading.
|
|
574
|
+
const oddPathSegments = pathSegments
|
|
575
|
+
? collectionSegmentsFrom(pathSegments)
|
|
576
|
+
: fullPathToCollectionSegments(path);
|
|
521
577
|
oddPathSegments.pop();
|
|
522
578
|
|
|
523
579
|
const result: string[][] = [];
|
|
@@ -563,13 +619,16 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
563
619
|
urlPathToDataPath,
|
|
564
620
|
buildUrlCollectionPath,
|
|
565
621
|
resolveIdsFrom,
|
|
622
|
+
resolveSegmentsFrom,
|
|
566
623
|
topLevelNavigation,
|
|
567
624
|
refreshNavigation,
|
|
568
625
|
getParentReferencesFromPath: getAllParentReferencesForPath,
|
|
569
626
|
getParentCollectionIds,
|
|
570
627
|
convertIdsToPaths,
|
|
571
628
|
navigate,
|
|
572
|
-
|
|
629
|
+
// All of them, paused or not: `FireCMS` sends every key to the license
|
|
630
|
+
// check and applies the same pause to what it renders.
|
|
631
|
+
plugins: allPlugins
|
|
573
632
|
};
|
|
574
633
|
}
|
|
575
634
|
|
|
@@ -12,6 +12,7 @@ import { useCustomizationController } from "./useCustomizationController";
|
|
|
12
12
|
import { useAnalyticsController } from "./useAnalyticsController";
|
|
13
13
|
import React, { useEffect } from "react";
|
|
14
14
|
import { useInternalUserManagementController } from "./useInternalUserManagementController";
|
|
15
|
+
import { useLicenseStatus } from "./useLicenseStatus";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Hook to retrieve the {@link FireCMSContext}.
|
|
@@ -36,6 +37,7 @@ export const useFireCMSContext = <USER extends User = User, AuthControllerType e
|
|
|
36
37
|
const customizationController = useCustomizationController();
|
|
37
38
|
const analyticsController = useAnalyticsController();
|
|
38
39
|
const userManagement = useInternalUserManagementController<USER>();
|
|
40
|
+
const licenseStatus = useLicenseStatus();
|
|
39
41
|
|
|
40
42
|
const fireCMSContextRef = React.useRef<FireCMSContext<USER, AuthControllerType>>({
|
|
41
43
|
authController,
|
|
@@ -49,7 +51,8 @@ export const useFireCMSContext = <USER extends User = User, AuthControllerType e
|
|
|
49
51
|
dialogsController,
|
|
50
52
|
customizationController,
|
|
51
53
|
analyticsController,
|
|
52
|
-
userManagement
|
|
54
|
+
userManagement,
|
|
55
|
+
licenseStatus
|
|
53
56
|
});
|
|
54
57
|
|
|
55
58
|
useEffect(() => {
|
|
@@ -65,9 +68,10 @@ export const useFireCMSContext = <USER extends User = User, AuthControllerType e
|
|
|
65
68
|
dialogsController,
|
|
66
69
|
customizationController,
|
|
67
70
|
analyticsController,
|
|
68
|
-
userManagement
|
|
71
|
+
userManagement,
|
|
72
|
+
licenseStatus
|
|
69
73
|
};
|
|
70
|
-
}, [authController, dialogsController, navigation, sideDialogsController]);
|
|
74
|
+
}, [authController, dialogsController, navigation, sideDialogsController, licenseStatus]);
|
|
71
75
|
|
|
72
76
|
return fireCMSContextRef.current;
|
|
73
77
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useContext, useSyncExternalStore } from "react";
|
|
2
|
+
import { AccessResponse } from "../types";
|
|
3
|
+
import {
|
|
4
|
+
getLicenseStatusSnapshot,
|
|
5
|
+
getServerLicenseStatusSnapshot,
|
|
6
|
+
LicenseStatusContext,
|
|
7
|
+
subscribeLicenseStatus
|
|
8
|
+
} from "../contexts/LicenseStatusContext";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The FireCMS license status of this project: whether PRO runs on a trial, is
|
|
12
|
+
* licensed, or is paused, plus the link to get a license.
|
|
13
|
+
*
|
|
14
|
+
* `null` until the license check answers, and when it is not sent at all
|
|
15
|
+
* (`telemetry={false}` without an `apiKey`). A response from an older server
|
|
16
|
+
* may lack `licenseState`; treat that as unknown. PRO features are paused when
|
|
17
|
+
* `blocked` is true; use `isProPaused(status)` for that check.
|
|
18
|
+
*
|
|
19
|
+
* Works inside `FireCMS` and also above it, e.g. in the component that calls
|
|
20
|
+
* `useBuildNavigationController`.
|
|
21
|
+
*
|
|
22
|
+
* @group Hooks and utilities
|
|
23
|
+
*/
|
|
24
|
+
export function useLicenseStatus(): AccessResponse | null {
|
|
25
|
+
const fromContext = useContext(LicenseStatusContext);
|
|
26
|
+
const published = useSyncExternalStore(
|
|
27
|
+
subscribeLicenseStatus,
|
|
28
|
+
getLicenseStatusSnapshot,
|
|
29
|
+
getServerLicenseStatusSnapshot
|
|
30
|
+
);
|
|
31
|
+
return fromContext !== undefined ? fromContext : published;
|
|
32
|
+
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { AuthController, DataSourceDelegate, FireCMSPlugin } from "../types";
|
|
2
|
+
import { AccessResponse, AuthController, DataSourceDelegate, FireCMSPlugin } from "../types";
|
|
3
|
+
import { PRO_PLUGIN_KEYS } from "../core/pro_plugins";
|
|
3
4
|
|
|
4
5
|
export const DEFAULT_SERVER_DEV = "https://api-kdoe6pj3qq-ey.a.run.app";
|
|
5
6
|
export const DEFAULT_SERVER = "https://api.firecms.co";
|
|
6
7
|
|
|
7
|
-
export type AccessResponse = {
|
|
8
|
-
blocked?: boolean;
|
|
9
|
-
message?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
8
|
async function makeRequest(authController: AuthController, dataSourceKey: string, pluginKeys: string[] | undefined, apiKey?: string): Promise<AccessResponse> {
|
|
13
9
|
let idToken: string | null;
|
|
14
10
|
try {
|
|
@@ -40,24 +36,46 @@ export interface UseProjectLogParams {
|
|
|
40
36
|
apiKey?: string;
|
|
41
37
|
authController: AuthController;
|
|
42
38
|
dataSourceDelegate: DataSourceDelegate;
|
|
39
|
+
/**
|
|
40
|
+
* Every plugin the app mounts, before any are paused: the server decides
|
|
41
|
+
* from these keys whether the project needs a license.
|
|
42
|
+
*/
|
|
43
43
|
plugins?: FireCMSPlugin<any, any, any>[];
|
|
44
|
+
/**
|
|
45
|
+
* When false, no request is sent, unless the request is a license check:
|
|
46
|
+
* with an `apiKey`, or with a PRO plugin mounted, it is always sent.
|
|
47
|
+
*/
|
|
48
|
+
telemetry?: boolean;
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
export function useProjectLog({
|
|
47
52
|
authController,
|
|
48
53
|
dataSourceDelegate,
|
|
49
54
|
plugins,
|
|
50
|
-
apiKey
|
|
55
|
+
apiKey,
|
|
56
|
+
telemetry = true
|
|
51
57
|
}: UseProjectLogParams): AccessResponse | null {
|
|
52
58
|
const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
|
|
53
59
|
const accessedUserRef = useRef<string | null>(null);
|
|
54
60
|
const dataSourceKey = dataSourceDelegate.key;
|
|
55
61
|
const pluginKeys = plugins?.map(plugin => plugin.key);
|
|
62
|
+
// Opting out is for apps without PRO plugins. With one mounted, the
|
|
63
|
+
// request is what starts the trial and pauses PRO when it ends, so
|
|
64
|
+
// skipping it would unlock PRO for good.
|
|
65
|
+
const runsProPlugin = (pluginKeys ?? []).some(key => PRO_PLUGIN_KEYS.includes(key));
|
|
66
|
+
const enabled = Boolean(apiKey) || runsProPlugin || telemetry !== false;
|
|
56
67
|
useEffect(() => {
|
|
68
|
+
if (!enabled) return;
|
|
57
69
|
if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
|
|
58
|
-
makeRequest(authController, dataSourceKey, pluginKeys, apiKey)
|
|
70
|
+
makeRequest(authController, dataSourceKey, pluginKeys, apiKey)
|
|
71
|
+
.then((response) => setAccessResponse(response && typeof response === "object" ? response : null))
|
|
72
|
+
.catch((error) => {
|
|
73
|
+
// The check never gates the CMS, so a failed one only
|
|
74
|
+
// leaves the license status unknown.
|
|
75
|
+
console.debug("FireCMS license check failed", error);
|
|
76
|
+
});
|
|
59
77
|
accessedUserRef.current = authController.user.uid;
|
|
60
78
|
}
|
|
61
|
-
}, [authController, dataSourceKey, pluginKeys]);
|
|
79
|
+
}, [authController, dataSourceKey, pluginKeys, enabled]);
|
|
62
80
|
return accessResponse;
|
|
63
81
|
}
|
|
@@ -80,12 +80,14 @@ export function resolveNavigationFrom<M extends Record<string, any>, USER extend
|
|
|
80
80
|
if (entry.type === "collection") {
|
|
81
81
|
return Promise.resolve(entry);
|
|
82
82
|
} else if (entry.type === "entity") {
|
|
83
|
-
const collection = navigation.getCollection(entry.path);
|
|
83
|
+
const collection = navigation.getCollection(entry.path, false, entry.pathSegments);
|
|
84
84
|
if (!collection) {
|
|
85
85
|
throw Error(`No collection defined in the navigation for the entity with path ${entry.path}`);
|
|
86
86
|
}
|
|
87
87
|
return dataSource.fetchEntity({
|
|
88
88
|
path: entry.path,
|
|
89
|
+
// The navigation entry already knows the real segment boundaries.
|
|
90
|
+
pathSegments: entry.pathSegments,
|
|
89
91
|
entityId: entry.entityId,
|
|
90
92
|
collection
|
|
91
93
|
})
|
|
@@ -23,8 +23,10 @@ export function useTranslation() {
|
|
|
23
23
|
* Also supports i18next interpolation variables, e.g.
|
|
24
24
|
* t("add_to_field", { fieldName: "Tags" })
|
|
25
25
|
* t("error_deleting", { message: err.message })
|
|
26
|
+
* Pass `count` as a number to pick a plural form (`key_one`, falling back
|
|
27
|
+
* to `key`); i18next ignores a string `count`.
|
|
26
28
|
*/
|
|
27
|
-
const typedT = (key: string, vars?: Record<string, string>): string =>
|
|
29
|
+
const typedT = (key: string, vars?: Record<string, string | number>): string =>
|
|
28
30
|
t(key, vars) as string;
|
|
29
31
|
|
|
30
32
|
return { t: typedT, i18n };
|
|
@@ -74,6 +74,10 @@ export function useValidateAuthenticator<USER extends User = any>
|
|
|
74
74
|
|
|
75
75
|
if (authenticator instanceof Function && delegateUser && !equal(checkedUserRef.current?.uid, delegateUser.uid)) {
|
|
76
76
|
setAuthLoading(true);
|
|
77
|
+
// Reset the error of any previous authentication attempt. Otherwise, a
|
|
78
|
+
// rejected user would keep `canAccessMainView` to false for every user
|
|
79
|
+
// logging in afterwards, locking them out of the CMS until a page reload.
|
|
80
|
+
setNotAllowedError(false);
|
|
77
81
|
try {
|
|
78
82
|
const allowed = await authenticator({
|
|
79
83
|
user: delegateUser,
|