@firecms/core 3.4.0-canary.0ef7442 → 3.4.0-canary.1faa2a9
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/components/EntityCollectionView/EntityCollectionView.d.ts +3 -2
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +3 -1
- package/dist/components/ErrorBoundary.d.ts +1 -3
- package/dist/components/LicenseBanner.d.ts +23 -0
- package/dist/components/common/useDataSourceTableController.d.ts +4 -2
- package/dist/components/common/useTableSearchHelper.d.ts +3 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/contexts/LicenseStatusContext.d.ts +16 -0
- package/dist/core/EntityEditView.d.ts +2 -0
- package/dist/core/EntityEditViewFormActions.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-KtBROUvS.js +700 -0
- package/dist/de-KtBROUvS.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 +2 -0
- package/dist/form/EntityFormActions.d.ts +2 -0
- package/dist/fr-BRFSBaVh.js +700 -0
- package/dist/fr-BRFSBaVh.js.map +1 -0
- package/dist/hi-DwCkga5X.js +700 -0
- package/dist/hi-DwCkga5X.js.map +1 -0
- package/dist/hooks/data/delete.d.ts +1 -1
- package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
- package/dist/hooks/data/useEntityFetch.d.ts +4 -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 +3503 -11244
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +8028 -7006
- package/dist/index.umd.js.map +1 -1
- package/dist/it-B2YGgDsx.js +700 -0
- package/dist/it-B2YGgDsx.js.map +1 -0
- package/dist/pl-BxHkywK8.js +702 -0
- package/dist/pl-BxHkywK8.js.map +1 -0
- package/dist/pt-q6UV4diT.js +702 -0
- package/dist/pt-q6UV4diT.js.map +1 -0
- package/dist/types/analytics.d.ts +1 -1
- package/dist/types/collections.d.ts +7 -0
- package/dist/types/datasource.d.ts +8 -0
- 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 +54 -0
- package/dist/types/navigation.d.ts +34 -5
- package/dist/types/plugins.d.ts +22 -1
- package/dist/types/side_entity_controller.d.ts +3 -2
- package/dist/types/translations.d.ts +31 -2
- package/dist/util/date_fns_locales.d.ts +18 -0
- package/dist/util/entity_cache.d.ts +11 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/lazy_eager.d.ts +3 -3
- package/dist/util/navigation_utils.d.ts +84 -2
- package/dist/util/parent_references_from_path.d.ts +15 -0
- package/dist/util/paths.d.ts +28 -0
- package/dist/util/permissions.d.ts +10 -4
- package/package.json +3 -3
- package/src/app/Scaffold.tsx +7 -0
- package/src/components/DeleteEntityDialog.tsx +2 -0
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +8 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +40 -15
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +6 -2
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -0
- package/src/components/EntityCollectionView/useBoardDataController.tsx +7 -2
- package/src/components/EntityPreview.tsx +4 -1
- package/src/components/ErrorBoundary.tsx +12 -18
- package/src/components/LicenseBanner.tsx +209 -0
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +8 -2
- package/src/components/common/default_entity_actions.tsx +22 -2
- package/src/components/common/useDataSourceTableController.tsx +26 -8
- package/src/components/common/useTableSearchHelper.ts +5 -0
- package/src/components/index.tsx +1 -0
- package/src/contexts/LicenseStatusContext.tsx +50 -0
- package/src/core/DefaultAppBar.tsx +28 -16
- package/src/core/EntityEditView.tsx +8 -12
- package/src/core/EntityEditViewFormActions.tsx +3 -2
- package/src/core/EntitySidePanel.tsx +6 -4
- package/src/core/FireCMS.tsx +73 -47
- package/src/core/SideDialogs.tsx +44 -1
- 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 +22 -7
- package/src/form/EntityFormActions.tsx +2 -0
- package/src/hooks/data/delete.ts +8 -2
- package/src/hooks/data/save.ts +4 -1
- package/src/hooks/data/useCollectionFetch.tsx +9 -2
- package/src/hooks/data/useEntityFetch.tsx +19 -6
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/useBuildNavigationController.tsx +70 -15
- 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 +1 -1
- package/src/hooks/useTranslation.ts +3 -1
- package/src/i18n/FireCMSi18nProvider.tsx +199 -25
- 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 +13 -12
- package/src/internal/useBuildSideEntityController.tsx +13 -5
- package/src/locales/de.ts +16 -2
- package/src/locales/en.ts +18 -2
- package/src/locales/es.ts +16 -2
- package/src/locales/fr.ts +16 -2
- package/src/locales/hi.ts +16 -2
- package/src/locales/it.ts +16 -2
- package/src/locales/pl.ts +16 -2
- package/src/locales/pt.ts +16 -2
- package/src/preview/components/DatePreview.tsx +2 -4
- package/src/preview/components/ReferencePreview.tsx +7 -3
- package/src/routes/FireCMSRoute.tsx +9 -4
- package/src/types/analytics.ts +3 -0
- package/src/types/collections.ts +8 -0
- package/src/types/datasource.ts +8 -0
- 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 +62 -0
- package/src/types/navigation.ts +35 -5
- package/src/types/plugins.tsx +23 -1
- package/src/types/side_entity_controller.tsx +3 -2
- package/src/types/translations.ts +31 -2
- package/src/util/date_fns_locales.ts +168 -0
- package/src/util/entity_cache.ts +18 -0
- package/src/util/index.ts +1 -0
- package/src/util/lazy_eager.tsx +30 -8
- package/src/util/navigation_from_path.ts +6 -1
- package/src/util/navigation_utils.ts +204 -2
- package/src/util/parent_references_from_path.ts +32 -1
- package/src/util/paths.ts +40 -3
- package/src/util/permissions.ts +22 -10
- package/src/util/useStorageUploadController.tsx +6 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Locale as DateFnsLocale } from "date-fns";
|
|
2
|
+
import { Locale } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Load a date-fns locale, resolving from cache when it is already in memory.
|
|
5
|
+
* Concurrent callers for the same locale share one request.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadDateFnsLocale(locale: Locale): Promise<DateFnsLocale | undefined>;
|
|
8
|
+
/** The locale if it is already loaded, otherwise undefined. Never suspends. */
|
|
9
|
+
export declare function getLoadedDateFnsLocale(locale?: Locale): DateFnsLocale | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The date-fns locale for `locale`, loading it in the background on first use.
|
|
12
|
+
*
|
|
13
|
+
* Returns undefined until the chunk arrives, which formats the first paint with
|
|
14
|
+
* date-fns' built-in English and then re-renders. That is a change only for
|
|
15
|
+
* projects that set a locale, and only for the few hundred milliseconds before
|
|
16
|
+
* the chunk lands — in exchange for every project no longer waiting on 625KB.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useDateFnsLocale(locale?: Locale): DateFnsLocale | undefined;
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key identifying one entity in a cache.
|
|
3
|
+
*
|
|
4
|
+
* The id is escaped so that a `(path, entityId)` pair maps to exactly one key. Without it
|
|
5
|
+
* `("a", "b/c")` and `("a/b", "c")` both flatten to `"a/b/c"`, and one entity's cached
|
|
6
|
+
* values are served for the other.
|
|
7
|
+
*
|
|
8
|
+
* `encodeEntityId` is the identity for any id without "/", "?", "#" or "%", so this changes
|
|
9
|
+
* no key in an app whose ids cannot contain them.
|
|
10
|
+
*/
|
|
11
|
+
export declare function entityCacheKey(path: string | undefined, entityId: string | undefined): string;
|
|
1
12
|
/**
|
|
2
13
|
* Saves data to the in-memory cache and persists it individually in `localStorage`.
|
|
3
14
|
* @param path - The unique path/key for the data.
|
package/dist/util/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
/**
|
|
3
|
-
* Returns a React.lazy component that is also preloaded
|
|
4
|
-
*
|
|
5
|
-
* This ensures that chunks are split, but fetched
|
|
3
|
+
* Returns a React.lazy component that is also preloaded in the background, once
|
|
4
|
+
* the page has finished loading.
|
|
5
|
+
* This ensures that chunks are split, but fetched before they are actually needed.
|
|
6
6
|
*/
|
|
7
7
|
export declare function lazyEager<T extends React.ComponentType<any>>(factory: () => Promise<any>, exportName?: string): React.LazyExoticComponent<T>;
|
|
@@ -23,15 +23,97 @@ export declare function encodeEntityId(entityId: string): string;
|
|
|
23
23
|
* @group Hooks and utilities
|
|
24
24
|
*/
|
|
25
25
|
export declare function decodeEntityId(encodedEntityId: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Segments of a subcollection sitting under the entity `entityId` of `parentPathSegments`.
|
|
28
|
+
*
|
|
29
|
+
* The parent entity id is kept whole however many slashes it contains, while the
|
|
30
|
+
* subcollection's own configured path is spread — a collection path never contains an
|
|
31
|
+
* entity id, so splitting that one is unambiguous.
|
|
32
|
+
*
|
|
33
|
+
* So `["test"] + "test/test" + "accommodation"` yields three segments,
|
|
34
|
+
* `["test", "test/test", "accommodation"]`, where splitting the flattened path
|
|
35
|
+
* `"test/test/test/accommodation"` would wrongly yield four.
|
|
36
|
+
*
|
|
37
|
+
* Returns undefined when the parent segments are unknown: the parent chain cannot be
|
|
38
|
+
* recovered from a flattened path, so there is nothing honest to build on.
|
|
39
|
+
*
|
|
40
|
+
* @group Hooks and utilities
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildSubcollectionPathSegments(parentPathSegments: string[] | undefined, entityId: string | undefined, subcollectionPath: string): string[] | undefined;
|
|
26
43
|
export declare function getLastSegment(path: string): string;
|
|
27
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The outcome of walking a segment chain against a collection tree.
|
|
46
|
+
* @group Hooks and utilities
|
|
47
|
+
*/
|
|
48
|
+
export type PathSegmentsWalkStep = {
|
|
49
|
+
collection: EntityCollection;
|
|
50
|
+
/** Resolved path of this collection, e.g. `"products/pid/locales"`. */
|
|
51
|
+
collectionPath: string;
|
|
52
|
+
/** `collectionPath` split at its real segment boundaries. */
|
|
53
|
+
collectionSegments: string[];
|
|
54
|
+
/** The entity addressed inside it, when the chain continues past the collection. */
|
|
55
|
+
entityId?: string;
|
|
56
|
+
};
|
|
57
|
+
export type PathSegmentsWalk = {
|
|
58
|
+
/** Segments with every collection id replaced by the collection's real `path`. */
|
|
59
|
+
resolved: string[];
|
|
60
|
+
/** The collection the chain ends in, if the whole chain matched. */
|
|
61
|
+
collection?: EntityCollection;
|
|
62
|
+
/** One entry per collection level traversed, outermost first. */
|
|
63
|
+
steps: PathSegmentsWalkStep[];
|
|
64
|
+
/** The first segment that could not be matched, if the walk stopped early. */
|
|
65
|
+
unmatched?: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Walk a segment chain against a collection tree, matching a collection by either its
|
|
69
|
+
* `path` or its `id` and treating exactly one segment as each entity id.
|
|
70
|
+
*
|
|
71
|
+
* This is the primitive the string-based helpers cannot have: working on the flattened
|
|
72
|
+
* path they must *guess* where an entity id ends by reading up to the next "/", which is
|
|
73
|
+
* wrong for any id that contains one. Here the boundaries are given.
|
|
74
|
+
*
|
|
75
|
+
* Stops at the first segment it cannot match and reports it, rather than throwing.
|
|
76
|
+
*
|
|
77
|
+
* @group Hooks and utilities
|
|
78
|
+
*/
|
|
79
|
+
export declare function walkPathSegments(pathSegments: string[], allCollections: EntityCollection[]): PathSegmentsWalk;
|
|
80
|
+
/**
|
|
81
|
+
* Resolve collection aliases in a segment array: every collection id is replaced by the
|
|
82
|
+
* collection's real `path`, and every entity id is carried through untouched.
|
|
83
|
+
*
|
|
84
|
+
* The segment-wise counterpart of {@link resolveCollectionPathIds}, and the reason it
|
|
85
|
+
* exists: that one works on the flattened string, so an entity id containing "/" shifts
|
|
86
|
+
* the rest of the chain by a segment and resolution fails with
|
|
87
|
+
* "Collection definition not found for segment starting with …".
|
|
88
|
+
*
|
|
89
|
+
* Matching accepts either a collection's `path` or its `id`, so already-resolved segments
|
|
90
|
+
* pass through unchanged — the function is idempotent.
|
|
91
|
+
*
|
|
92
|
+
* Mirrors the string version when a chain cannot be matched: it warns and carries the
|
|
93
|
+
* remainder through unresolved, rather than throwing.
|
|
94
|
+
*
|
|
95
|
+
* @group Hooks and utilities
|
|
96
|
+
*/
|
|
97
|
+
export declare function resolveCollectionPathSegments(pathSegments: string[], allCollections: EntityCollection[]): string[];
|
|
98
|
+
/**
|
|
99
|
+
* Find the collection a segment chain addresses, whether it ends on the collection itself
|
|
100
|
+
* or on one of its entities.
|
|
101
|
+
*
|
|
102
|
+
* The segment-wise counterpart of {@link getCollectionByPathOrId}, which asserts an odd
|
|
103
|
+
* number of "/"-separated parts — an assertion a slash-bearing entity id fails, throwing
|
|
104
|
+
* where it should simply have resolved.
|
|
105
|
+
*
|
|
106
|
+
* @group Hooks and utilities
|
|
107
|
+
*/
|
|
108
|
+
export declare function getCollectionByPathSegments(pathSegments: string[], collections: EntityCollection[]): EntityCollection | undefined;
|
|
109
|
+
export declare function resolveCollectionPathIds(path: string, allCollections: EntityCollection[], pathSegments?: string[]): string;
|
|
28
110
|
/**
|
|
29
111
|
* Find the corresponding view at any depth for a given path.
|
|
30
112
|
* Note that path or segments of the paths can be collection aliases.
|
|
31
113
|
* @param pathOrId
|
|
32
114
|
* @param collections
|
|
33
115
|
*/
|
|
34
|
-
export declare function getCollectionByPathOrId(pathOrId: string, collections: EntityCollection[]): EntityCollection | undefined;
|
|
116
|
+
export declare function getCollectionByPathOrId(pathOrId: string, collections: EntityCollection[], pathSegments?: string[]): EntityCollection | undefined;
|
|
35
117
|
/**
|
|
36
118
|
* Get the subcollection combinations from a path:
|
|
37
119
|
* "sites/es/locales" => ["sites/es/locales", "sites"]
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import { EntityCollection, EntityReference } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* References to every parent entity in a path.
|
|
4
|
+
*
|
|
5
|
+
* NOTE on encoding: `path` is expected ESCAPED, because it comes from the URL — the entity
|
|
6
|
+
* ids in it are decoded on the way out. Pass `pathSegments` instead whenever the path at
|
|
7
|
+
* hand is a raw datasource path (`entity.path`), which is not escaped and whose ids may
|
|
8
|
+
* therefore contain a bare "/": splitting that would silently produce the wrong references.
|
|
9
|
+
*/
|
|
2
10
|
export declare function getParentReferencesFromPath(props: {
|
|
3
11
|
path: string;
|
|
4
12
|
collections: EntityCollection[] | undefined;
|
|
5
13
|
currentFullPath?: string;
|
|
6
14
|
currentPathSegments?: string[];
|
|
15
|
+
/** `path` split at its real segment boundaries, with RAW ids. Takes precedence. */
|
|
16
|
+
pathSegments?: string[];
|
|
7
17
|
}): EntityReference[];
|
|
18
|
+
/**
|
|
19
|
+
* The segment-wise counterpart: the boundaries are given, so an entity id keeps its
|
|
20
|
+
* slashes and nothing has to be decoded.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getParentReferencesFromPathSegments(pathSegments: string[], collections: EntityCollection[]): EntityReference[];
|
package/dist/util/paths.d.ts
CHANGED
|
@@ -9,6 +9,34 @@ export declare function segmentsToStrippedPath(paths: string[]): string;
|
|
|
9
9
|
/**
|
|
10
10
|
* Extract the collection path routes
|
|
11
11
|
* `products/B44RG6APH/locales` => [`products`, `locales`]
|
|
12
|
+
*
|
|
13
|
+
* Finds the entity ids by position, which only works while no id contains "/" — with one,
|
|
14
|
+
* every following segment shifts and the wrong elements are kept. Use
|
|
15
|
+
* {@link collectionSegmentsFrom} when the real boundaries are known.
|
|
16
|
+
*
|
|
12
17
|
* @param path
|
|
13
18
|
*/
|
|
14
19
|
export declare function fullPathToCollectionSegments(path: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* The collection chain of an already-segmented path:
|
|
22
|
+
* `['products', 'B44RG6APH', 'locales']` => [`products`, `locales`]
|
|
23
|
+
*
|
|
24
|
+
* The counterpart of {@link fullPathToCollectionSegments} for callers that know the real
|
|
25
|
+
* segment boundaries. Kept as a separate function rather than an extra parameter on that
|
|
26
|
+
* one: these helpers are small enough to be passed point-free, and `paths.map(fn)` would
|
|
27
|
+
* then hand the array index in as the new argument.
|
|
28
|
+
*/
|
|
29
|
+
export declare function collectionSegmentsFrom(pathSegments: string[]): string[];
|
|
30
|
+
/**
|
|
31
|
+
* The historical, positional reading of a flattened path:
|
|
32
|
+
* `products/B44RG6APH/locales` => [`products`, `locales`]
|
|
33
|
+
*
|
|
34
|
+
* This produces a **collection chain**, not a datasource `pathSegments` value, and must
|
|
35
|
+
* never be used as one — see `path_segments_no_fabrication.test.ts`. It is only correct
|
|
36
|
+
* while no entity id contains "/", which is why every caller prefers real segments when
|
|
37
|
+
* they are available, and why it is named for what it assumes.
|
|
38
|
+
*
|
|
39
|
+
* It is still the right reading for a path that came from a URL, where entity ids arrive
|
|
40
|
+
* escaped and so cannot contain a bare "/".
|
|
41
|
+
*/
|
|
42
|
+
export declare function positionalCollectionSegments(path: string): string[];
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { AuthController, Entity, EntityCollection, Permissions, User } from "../types";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/**
|
|
3
|
+
* `pathSegments` is `path` split at its real segment boundaries. It is forwarded so the
|
|
4
|
+
* collection chain handed to a permissions builder stays correct when an entity id contains
|
|
5
|
+
* "/" — splitting `path` positionally would keep the wrong elements. Optional everywhere:
|
|
6
|
+
* omitted, behaviour is exactly as before.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolvePermissions<M extends Record<string, any>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null, pathSegments?: string[]): Permissions | undefined;
|
|
9
|
+
export declare function canEditEntity<M extends Record<string, any>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null, pathSegments?: string[]): boolean;
|
|
10
|
+
export declare function canCreateEntity<M extends Record<string, any>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null, pathSegments?: string[]): boolean;
|
|
11
|
+
export declare function canDeleteEntity<M extends Record<string, any>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null, pathSegments?: string[]): boolean;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firecms/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.4.0-canary.
|
|
4
|
+
"version": "3.4.0-canary.1faa2a9",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/firecmsco"
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"@dnd-kit/core": "^6.3.1",
|
|
54
54
|
"@dnd-kit/modifiers": "^9.0.0",
|
|
55
55
|
"@dnd-kit/sortable": "^10.0.0",
|
|
56
|
-
"@firecms/formex": "3.4.0-canary.
|
|
57
|
-
"@firecms/ui": "3.4.0-canary.
|
|
56
|
+
"@firecms/formex": "3.4.0-canary.1faa2a9",
|
|
57
|
+
"@firecms/ui": "3.4.0-canary.1faa2a9",
|
|
58
58
|
"@floating-ui/dom": "^1.7.4",
|
|
59
59
|
"@radix-ui/react-portal": "^1.1.10",
|
|
60
60
|
"@radix-ui/react-slot": "^1.2.4",
|
package/src/app/Scaffold.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { cls, defaultBorderMixin, IconButton, MenuIcon, Sheet } from "@firecms/u
|
|
|
3
3
|
import equal from "react-fast-compare"
|
|
4
4
|
import { useLargeLayout } from "../hooks";
|
|
5
5
|
import { ErrorBoundary } from "../components";
|
|
6
|
+
import { LicenseBanner } from "../components/LicenseBanner";
|
|
6
7
|
import { AppContext } from "./useApp";
|
|
7
8
|
import { useTranslation } from "../hooks/useTranslation";
|
|
8
9
|
|
|
@@ -146,6 +147,12 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
146
147
|
|
|
147
148
|
{hasAppBar && <DrawerHeader />}
|
|
148
149
|
|
|
150
|
+
<LicenseBanner
|
|
151
|
+
className={cls("flex-shrink-0", {
|
|
152
|
+
"px-2 pb-1 lg:px-4 lg:pb-2": padding,
|
|
153
|
+
"pt-2 lg:pt-4": !hasAppBar
|
|
154
|
+
})}/>
|
|
155
|
+
|
|
149
156
|
<div
|
|
150
157
|
className={cls(defaultBorderMixin, "flex-grow overflow-auto m-0 ", {
|
|
151
158
|
"lg:mt-4": !hasAppBar,
|
|
@@ -96,6 +96,8 @@ export function DeleteEntityDialog<M extends Record<string, any>>({
|
|
|
96
96
|
deleteEntityWithCallbacks({
|
|
97
97
|
dataSource,
|
|
98
98
|
entity,
|
|
99
|
+
// The entity is the authority on where it lives; stated rather than left implicit.
|
|
100
|
+
pathSegments: entity.pathSegments,
|
|
99
101
|
collection: resolvedCollection,
|
|
100
102
|
callbacks,
|
|
101
103
|
onDeleteSuccess,
|
|
@@ -3,7 +3,7 @@ import React, { MouseEvent, useCallback } from "react";
|
|
|
3
3
|
import { CollectionSize, Entity, EntityAction, EntityCollection, SelectionController } from "../../types";
|
|
4
4
|
import { Badge, Checkbox, cls, IconButton, Menu, MenuItem, MoreVertIcon, Skeleton, Tooltip } from "@firecms/ui";
|
|
5
5
|
import { useFireCMSContext, useLargeLayout } from "../../hooks";
|
|
6
|
-
import { getEntityFromCache } from "../../util/entity_cache";
|
|
6
|
+
import { entityCacheKey, getEntityFromCache } from "../../util/entity_cache";
|
|
7
7
|
import { getLocalChangesBackup } from "../../util";
|
|
8
8
|
import { getChanges } from "../../form/EntityForm";
|
|
9
9
|
|
|
@@ -82,7 +82,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
|
|
|
82
82
|
const collapsedActions = actions.filter(a => a.collapsed || a.collapsed === undefined);
|
|
83
83
|
const uncollapsedActions = actions.filter(a => a.collapsed === false);
|
|
84
84
|
const enableLocalChangesBackup = collection ? getLocalChangesBackup(collection) : false;
|
|
85
|
-
const cachedData = enableLocalChangesBackup ? getEntityFromCache(fullPath
|
|
85
|
+
const cachedData = enableLocalChangesBackup ? getEntityFromCache(entityCacheKey(fullPath, entity.id)) : undefined;
|
|
86
86
|
const hasDraft = (() => {
|
|
87
87
|
if (!cachedData || typeof cachedData !== "object" || Object.keys(cachedData).length === 0) return false;
|
|
88
88
|
const realChanges = getChanges(cachedData as any, (entity?.values ?? {}) as any);
|
|
@@ -274,6 +274,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
274
274
|
|
|
275
275
|
currentDataSource.countEntities({
|
|
276
276
|
path: fullPath,
|
|
277
|
+
pathSegments,
|
|
277
278
|
collection: currentCollection
|
|
278
279
|
}).then(count => {
|
|
279
280
|
totalCount = count;
|
|
@@ -285,6 +286,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
285
286
|
|
|
286
287
|
currentDataSource.countEntities({
|
|
287
288
|
path: fullPath,
|
|
289
|
+
pathSegments,
|
|
288
290
|
collection: currentCollection,
|
|
289
291
|
filter: { [orderProperty]: ["!=", null] } as FilterValues<string>
|
|
290
292
|
}).then(count => {
|
|
@@ -436,6 +438,8 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
436
438
|
|
|
437
439
|
const saveProps: SaveEntityProps = {
|
|
438
440
|
path: entity.path,
|
|
441
|
+
// Paired with `entity.path`: the segments the entity was loaded with.
|
|
442
|
+
pathSegments: entity.pathSegments,
|
|
439
443
|
entityId: entity.id,
|
|
440
444
|
values: updatedValues as M,
|
|
441
445
|
previousValues: entity.values,
|
|
@@ -476,6 +480,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
476
480
|
console.log("Fetching all documents from collection...");
|
|
477
481
|
const allDocs = await dataSource.fetchCollection<M>({
|
|
478
482
|
path: fullPath,
|
|
483
|
+
pathSegments,
|
|
479
484
|
collection,
|
|
480
485
|
limit: 10000 // Fetch all
|
|
481
486
|
});
|
|
@@ -496,6 +501,8 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
496
501
|
|
|
497
502
|
const saveProps: SaveEntityProps = {
|
|
498
503
|
path: entity.path,
|
|
504
|
+
// Paired with `entity.path`: the segments the entity was loaded with.
|
|
505
|
+
pathSegments: entity.pathSegments,
|
|
499
506
|
entityId: entity.id,
|
|
500
507
|
values: updatedValues as M,
|
|
501
508
|
previousValues: entity.values,
|
|
@@ -667,6 +674,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
667
674
|
});
|
|
668
675
|
sideEntityController.open({
|
|
669
676
|
path: fullPath,
|
|
677
|
+
pathSegments,
|
|
670
678
|
collection,
|
|
671
679
|
entityId: undefined,
|
|
672
680
|
updateUrl: true,
|
|
@@ -90,6 +90,7 @@ import { useSelectionController } from "./useSelectionController";
|
|
|
90
90
|
import { EntityCollectionViewStartActions } from "./EntityCollectionViewStartActions";
|
|
91
91
|
import { addRecentId, getRecentIds } from "./utils";
|
|
92
92
|
import { useScrollRestoration } from "../common/useScrollRestoration";
|
|
93
|
+
import { entityCacheKey } from "../../util/entity_cache";
|
|
93
94
|
|
|
94
95
|
const DEFAULT_ENTITY_OPEN_MODE: "side_panel" | "full_screen" = "side_panel";
|
|
95
96
|
|
|
@@ -109,8 +110,9 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
|
|
|
109
110
|
/**
|
|
110
111
|
* `fullPath` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
|
|
111
112
|
* Forwarded to the datasource so a parent entity id containing "/" stays unambiguous.
|
|
112
|
-
*
|
|
113
|
-
*
|
|
113
|
+
*
|
|
114
|
+
* Optional, and never derived by splitting `fullPath` — a guess would be wrong in
|
|
115
|
+
* exactly the case the field exists for. Absent means "not known here", not "no slashes".
|
|
114
116
|
*/
|
|
115
117
|
pathSegments?: string[];
|
|
116
118
|
/**
|
|
@@ -198,7 +200,7 @@ export const EntityCollectionView = React.memo(
|
|
|
198
200
|
collectionRef.current = collection;
|
|
199
201
|
}, [collection]);
|
|
200
202
|
|
|
201
|
-
const canCreateEntities = canCreateEntity(collection, authController, fullPath, null);
|
|
203
|
+
const canCreateEntities = canCreateEntity(collection, authController, fullPath, null, pathSegments);
|
|
202
204
|
const [highlightedEntity, setHighlightedEntity] = useState<Entity<M> | undefined>(undefined);
|
|
203
205
|
const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
|
|
204
206
|
|
|
@@ -228,7 +230,7 @@ export const EntityCollectionView = React.memo(
|
|
|
228
230
|
|
|
229
231
|
const checkInlineEditing = useCallback((entity?: Entity<any>): boolean => {
|
|
230
232
|
const collection = collectionRef.current;
|
|
231
|
-
if (!canEditEntity(collection, authController, fullPath, entity ?? null)) {
|
|
233
|
+
if (!canEditEntity(collection, authController, fullPath, entity ?? null, pathSegments)) {
|
|
232
234
|
return false;
|
|
233
235
|
}
|
|
234
236
|
return collection.inlineEditing === undefined || collection.inlineEditing;
|
|
@@ -351,10 +353,16 @@ export const EntityCollectionView = React.memo(
|
|
|
351
353
|
addRecentId(collection.id, clickedEntity.id);
|
|
352
354
|
}
|
|
353
355
|
|
|
354
|
-
|
|
356
|
+
// In a collection group the row's own path is the real one, so the row's segments
|
|
357
|
+
// are the matching ones; otherwise both come from this view. `path` and
|
|
358
|
+
// `pathSegments` are picked from the same source so they cannot describe
|
|
359
|
+
// different chains.
|
|
360
|
+
const isCollectionGroup = Boolean(collection?.collectionGroup);
|
|
361
|
+
const path = isCollectionGroup ? clickedEntity.path : (fullPath ?? clickedEntity.path);
|
|
355
362
|
navigateToEntity({
|
|
356
363
|
navigation,
|
|
357
364
|
path,
|
|
365
|
+
pathSegments: isCollectionGroup ? clickedEntity.pathSegments : (pathSegments ?? clickedEntity.pathSegments),
|
|
358
366
|
fullIdPath,
|
|
359
367
|
sideEntityController,
|
|
360
368
|
openEntityMode,
|
|
@@ -461,7 +469,7 @@ export const EntityCollectionView = React.memo(
|
|
|
461
469
|
}
|
|
462
470
|
}, [setViewMode, userConfigPersistence, onCollectionModifiedForUser, fullPath, analyticsController, viewMode]);
|
|
463
471
|
|
|
464
|
-
const createEnabled = canCreateEntity(collection, authController, fullPath, null);
|
|
472
|
+
const createEnabled = canCreateEntity(collection, authController, fullPath, null, pathSegments);
|
|
465
473
|
|
|
466
474
|
const uniqueFieldValidator: UniqueFieldValidator = useCallback(
|
|
467
475
|
({
|
|
@@ -469,8 +477,8 @@ export const EntityCollectionView = React.memo(
|
|
|
469
477
|
value,
|
|
470
478
|
property,
|
|
471
479
|
entityId
|
|
472
|
-
}) => dataSource.checkUniqueField(fullPath, name, value, entityId, collection),
|
|
473
|
-
[fullPath]);
|
|
480
|
+
}) => dataSource.checkUniqueField(fullPath, name, value, entityId, collection, pathSegments),
|
|
481
|
+
[fullPath, pathSegments]);
|
|
474
482
|
|
|
475
483
|
const onValueChange: OnCellValueChange<any, any> = ({
|
|
476
484
|
value,
|
|
@@ -484,6 +492,8 @@ export const EntityCollectionView = React.memo(
|
|
|
484
492
|
|
|
485
493
|
const saveProps: SaveEntityProps = {
|
|
486
494
|
path: entity.path ?? fullPath,
|
|
495
|
+
// Paired with the `path` above: the row's segments describe the row's path.
|
|
496
|
+
pathSegments: entity.path ? entity.pathSegments : pathSegments,
|
|
487
497
|
entityId: entity.id,
|
|
488
498
|
values: updatedValues,
|
|
489
499
|
previousValues: entity.values,
|
|
@@ -514,7 +524,7 @@ export const EntityCollectionView = React.memo(
|
|
|
514
524
|
|
|
515
525
|
};
|
|
516
526
|
|
|
517
|
-
const resolvedFullPath = navigation.resolveIdsFrom(fullPath);
|
|
527
|
+
const resolvedFullPath = navigation.resolveIdsFrom(fullPath, pathSegments);
|
|
518
528
|
const resolvedCollection = useMemo(() => resolveCollection<M>({
|
|
519
529
|
collection,
|
|
520
530
|
path: fullPath,
|
|
@@ -676,13 +686,15 @@ export const EntityCollectionView = React.memo(
|
|
|
676
686
|
width: 260,
|
|
677
687
|
dependencies: [],
|
|
678
688
|
Builder: ({ entity }) => {
|
|
679
|
-
|
|
689
|
+
// `entity.path` is a RAW datasource path, so its segments are passed:
|
|
690
|
+
// splitting it would mis-read any parent id containing "/".
|
|
691
|
+
const collectionsWithPath = navigation.getParentReferencesFromPath(entity.path, entity.pathSegments);
|
|
680
692
|
return (
|
|
681
693
|
<div className={"flex flex-col gap-2 w-full"}>
|
|
682
694
|
{collectionsWithPath.map((reference) => {
|
|
683
695
|
return (
|
|
684
696
|
<ReferencePreview
|
|
685
|
-
key={reference.path
|
|
697
|
+
key={entityCacheKey(reference.path, reference.id)}
|
|
686
698
|
reference={reference}
|
|
687
699
|
size={"small"} />
|
|
688
700
|
);
|
|
@@ -713,7 +725,7 @@ export const EntityCollectionView = React.memo(
|
|
|
713
725
|
entity?: Entity<M>,
|
|
714
726
|
customEntityActions?: EntityAction[]
|
|
715
727
|
}): EntityAction[] => {
|
|
716
|
-
const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPath, entity) : true;
|
|
728
|
+
const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPath, entity, entity.pathSegments ?? pathSegments) : true;
|
|
717
729
|
const actions: EntityAction[] = [
|
|
718
730
|
{ ...editEntityAction, name: t("edit") }
|
|
719
731
|
];
|
|
@@ -843,6 +855,7 @@ export const EntityCollectionView = React.memo(
|
|
|
843
855
|
onTextSearchClick,
|
|
844
856
|
textSearchEnabled
|
|
845
857
|
} = useTableSearchHelper({
|
|
858
|
+
pathSegments,
|
|
846
859
|
collection,
|
|
847
860
|
fullPath: resolvedFullPath,
|
|
848
861
|
parentCollectionIds,
|
|
@@ -907,6 +920,7 @@ export const EntityCollectionView = React.memo(
|
|
|
907
920
|
collection={collection}
|
|
908
921
|
tableController={tableController}
|
|
909
922
|
path={fullPath}
|
|
923
|
+
pathSegments={pathSegments}
|
|
910
924
|
relativePath={collection.path}
|
|
911
925
|
selectionController={usedSelectionController}
|
|
912
926
|
collectionEntitiesCount={docsCount}
|
|
@@ -918,6 +932,7 @@ export const EntityCollectionView = React.memo(
|
|
|
918
932
|
onMultipleDeleteClick={onMultipleDeleteClick}
|
|
919
933
|
onNewClick={onNewClick}
|
|
920
934
|
path={fullPath}
|
|
935
|
+
pathSegments={pathSegments}
|
|
921
936
|
relativePath={collection.path}
|
|
922
937
|
selectionController={usedSelectionController}
|
|
923
938
|
selectionEnabled={selectionEnabled}
|
|
@@ -1026,6 +1041,7 @@ export const EntityCollectionView = React.memo(
|
|
|
1026
1041
|
getIdColumnWidth={getIdColumnWidth}
|
|
1027
1042
|
additionalIDHeaderWidget={<EntityIdHeaderWidget
|
|
1028
1043
|
path={fullPath}
|
|
1044
|
+
pathSegments={pathSegments}
|
|
1029
1045
|
fullIdPath={fullIdPath ?? fullPath}
|
|
1030
1046
|
collection={collection} />}
|
|
1031
1047
|
openEntityMode={openEntityMode}
|
|
@@ -1140,8 +1156,12 @@ function EntitiesCount({
|
|
|
1140
1156
|
|
|
1141
1157
|
const sortByProperty = sortBy ? sortBy[0] : undefined;
|
|
1142
1158
|
const currentSort = sortBy ? sortBy[1] : undefined;
|
|
1143
|
-
|
|
1144
|
-
|
|
1159
|
+
// Resolved together so the two representations always describe the same chain.
|
|
1160
|
+
// Segments are resolved, never derived (see useEntityFetch).
|
|
1161
|
+
const resolvedPath = useMemo(() => navigation.resolveIdsFrom(fullPath, pathSegments), [fullPath, pathSegments, navigation.resolveIdsFrom]);
|
|
1162
|
+
const resolvedPathSegments = useMemo(() => pathSegments
|
|
1163
|
+
? (navigation.resolveSegmentsFrom?.(pathSegments) ?? pathSegments)
|
|
1164
|
+
: undefined, [pathSegments, navigation.resolveSegmentsFrom]);
|
|
1145
1165
|
|
|
1146
1166
|
useEffect(() => {
|
|
1147
1167
|
if (dataSource.countEntities)
|
|
@@ -1182,10 +1202,13 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
|
|
|
1182
1202
|
function EntityIdHeaderWidget({
|
|
1183
1203
|
collection,
|
|
1184
1204
|
path,
|
|
1205
|
+
pathSegments,
|
|
1185
1206
|
fullIdPath
|
|
1186
1207
|
}: {
|
|
1187
1208
|
collection: EntityCollection,
|
|
1188
1209
|
path: string,
|
|
1210
|
+
/** `path` split at its real segment boundaries, when the caller knows them. */
|
|
1211
|
+
pathSegments?: string[],
|
|
1189
1212
|
fullIdPath: string
|
|
1190
1213
|
}) {
|
|
1191
1214
|
|
|
@@ -1225,6 +1248,7 @@ function EntityIdHeaderWidget({
|
|
|
1225
1248
|
collection,
|
|
1226
1249
|
entityId,
|
|
1227
1250
|
path,
|
|
1251
|
+
pathSegments,
|
|
1228
1252
|
fullIdPath,
|
|
1229
1253
|
sideEntityController,
|
|
1230
1254
|
navigation
|
|
@@ -1250,7 +1274,7 @@ function EntityIdHeaderWidget({
|
|
|
1250
1274
|
</form>
|
|
1251
1275
|
{recentIds && recentIds.length > 0 && <div className="flex flex-col gap-2 p-2">
|
|
1252
1276
|
{recentIds.map(id => (
|
|
1253
|
-
<ReferencePreview reference={new EntityReference(id, path)}
|
|
1277
|
+
<ReferencePreview reference={new EntityReference(id, path, undefined, pathSegments)}
|
|
1254
1278
|
key={id}
|
|
1255
1279
|
hover={true}
|
|
1256
1280
|
onClick={() => {
|
|
@@ -1260,6 +1284,7 @@ function EntityIdHeaderWidget({
|
|
|
1260
1284
|
collection,
|
|
1261
1285
|
entityId: id,
|
|
1262
1286
|
path,
|
|
1287
|
+
pathSegments,
|
|
1263
1288
|
fullIdPath,
|
|
1264
1289
|
sideEntityController,
|
|
1265
1290
|
navigation
|
|
@@ -22,6 +22,8 @@ import { useTranslation } from "../../hooks/useTranslation";
|
|
|
22
22
|
export type EntityCollectionViewActionsProps<M extends Record<string, any>> = {
|
|
23
23
|
collection: EntityCollection<M>;
|
|
24
24
|
path: string;
|
|
25
|
+
/** `path` split at its real segment boundaries, when known. Never derived from `path`. */
|
|
26
|
+
pathSegments?: string[];
|
|
25
27
|
relativePath: string;
|
|
26
28
|
parentCollectionIds: string[];
|
|
27
29
|
selectionEnabled: boolean;
|
|
@@ -40,6 +42,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
40
42
|
onMultipleDeleteClick,
|
|
41
43
|
selectionEnabled,
|
|
42
44
|
path,
|
|
45
|
+
pathSegments,
|
|
43
46
|
selectionController,
|
|
44
47
|
tableController,
|
|
45
48
|
collectionEntitiesCount,
|
|
@@ -56,7 +59,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
56
59
|
|
|
57
60
|
const selectedEntities = selectionController.selectedEntities;
|
|
58
61
|
|
|
59
|
-
const addButton = canCreateEntity(collection, authController, path, null) &&
|
|
62
|
+
const addButton = canCreateEntity(collection, authController, path, null, pathSegments) &&
|
|
60
63
|
onNewClick && (largeLayout
|
|
61
64
|
? <Button
|
|
62
65
|
id={`add_entity_${path}`}
|
|
@@ -75,7 +78,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
75
78
|
<AddIcon size={"small"} />
|
|
76
79
|
</Button>);
|
|
77
80
|
|
|
78
|
-
const multipleDeleteEnabled = canDeleteEntity(collection, authController, path, null);
|
|
81
|
+
const multipleDeleteEnabled = canDeleteEntity(collection, authController, path, null, pathSegments);
|
|
79
82
|
|
|
80
83
|
let multipleDeleteButton: React.ReactNode | undefined;
|
|
81
84
|
if (selectionEnabled) {
|
|
@@ -107,6 +110,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
107
110
|
|
|
108
111
|
const actionProps: CollectionActionsProps = {
|
|
109
112
|
path,
|
|
113
|
+
pathSegments,
|
|
110
114
|
relativePath,
|
|
111
115
|
parentCollectionIds,
|
|
112
116
|
collection,
|
|
@@ -17,6 +17,8 @@ import { useTranslation } from "../../hooks/useTranslation";
|
|
|
17
17
|
export type EntityCollectionViewStartActionsProps<M extends Record<string, any>> = {
|
|
18
18
|
collection: EntityCollection<M>;
|
|
19
19
|
path: string;
|
|
20
|
+
/** `path` split at its real segment boundaries, when known. Never derived from `path`. */
|
|
21
|
+
pathSegments?: string[];
|
|
20
22
|
relativePath: string;
|
|
21
23
|
parentCollectionIds: string[];
|
|
22
24
|
selectionController: SelectionController<M>;
|
|
@@ -33,6 +35,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
|
|
|
33
35
|
relativePath,
|
|
34
36
|
parentCollectionIds,
|
|
35
37
|
path,
|
|
38
|
+
pathSegments,
|
|
36
39
|
selectionController,
|
|
37
40
|
tableController,
|
|
38
41
|
collectionEntitiesCount,
|
|
@@ -57,6 +60,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
|
|
|
57
60
|
|
|
58
61
|
const actionProps: CollectionActionsProps = {
|
|
59
62
|
path,
|
|
63
|
+
pathSegments,
|
|
60
64
|
relativePath,
|
|
61
65
|
parentCollectionIds,
|
|
62
66
|
collection,
|
|
@@ -82,8 +82,12 @@ export function useBoardDataController<M extends Record<string, any> = any, COLU
|
|
|
82
82
|
const context = useFireCMSContext();
|
|
83
83
|
const dataSource = useDataSource(collection);
|
|
84
84
|
const navigation = useNavigationController();
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// Resolved together so the two representations always describe the same chain.
|
|
86
|
+
// Segments are resolved, never derived (see useEntityFetch).
|
|
87
|
+
const resolvedPath = useMemo(() => navigation.resolveIdsFrom(fullPath, pathSegments), [fullPath, pathSegments, navigation.resolveIdsFrom]);
|
|
88
|
+
const resolvedPathSegments = useMemo(() => pathSegments
|
|
89
|
+
? (navigation.resolveSegmentsFrom?.(pathSegments) ?? pathSegments)
|
|
90
|
+
: undefined, [pathSegments, navigation.resolveSegmentsFrom]);
|
|
87
91
|
|
|
88
92
|
// Stable refs for objects that shouldn't trigger re-subscriptions
|
|
89
93
|
const dataSourceRef = useRef(dataSource);
|
|
@@ -212,6 +216,7 @@ export function useBoardDataController<M extends Record<string, any> = any, COLU
|
|
|
212
216
|
currentCollection.callbacks!.onFetch!({
|
|
213
217
|
collection: currentCollection,
|
|
214
218
|
path: currentResolvedPath,
|
|
219
|
+
pathSegments: currentPathSegments,
|
|
215
220
|
entity,
|
|
216
221
|
context: currentContext
|
|
217
222
|
})
|