@firecms/core 3.5.2-canary.09e1281 → 3.5.2-canary.a36570e
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/dist/components/EntityCollectionView/utils.d.ts +1 -0
- package/dist/index.es.js +69 -185
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +69 -185
- package/dist/index.umd.js.map +1 -1
- package/package.json +3 -3
- package/src/components/EntityCollectionView/EntityIdHeaderWidget.tsx +50 -8
- package/src/components/EntityCollectionView/utils.ts +6 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firecms/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.5.2-canary.
|
|
4
|
+
"version": "3.5.2-canary.a36570e",
|
|
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.5.2-canary.
|
|
57
|
-
"@firecms/ui": "3.5.2-canary.
|
|
56
|
+
"@firecms/formex": "3.5.2-canary.a36570e",
|
|
57
|
+
"@firecms/ui": "3.5.2-canary.a36570e",
|
|
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",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useCallback, useRef } from "react";
|
|
2
2
|
import { Button, cls, focusedDisabled, IconButton, KeyboardTabIcon, Popover, SearchIcon, Tooltip } from "@firecms/ui";
|
|
3
3
|
import { EntityCollection, EntityReference } from "../../types";
|
|
4
4
|
import { navigateToEntity } from "../../util/navigation_utils";
|
|
@@ -6,7 +6,8 @@ import { ReferencePreview } from "../../preview";
|
|
|
6
6
|
import { useNavigationController } from "../../hooks/useNavigationController";
|
|
7
7
|
import { useSideEntityController } from "../../hooks/useSideEntityController";
|
|
8
8
|
import { useTranslation } from "../../hooks/useTranslation";
|
|
9
|
-
import {
|
|
9
|
+
import { useDataSource } from "../../hooks/data/useDataSource";
|
|
10
|
+
import { addRecentId, getRecentIds, removeRecentIds } from "./utils";
|
|
10
11
|
|
|
11
12
|
const DEFAULT_ENTITY_OPEN_MODE: "side_panel" | "full_screen" = "side_panel";
|
|
12
13
|
|
|
@@ -27,14 +28,52 @@ export function EntityIdHeaderWidget({
|
|
|
27
28
|
}) {
|
|
28
29
|
|
|
29
30
|
const navigation = useNavigationController();
|
|
31
|
+
const dataSource = useDataSource(collection);
|
|
30
32
|
const [openPopup, setOpenPopup] = React.useState(false);
|
|
31
33
|
const [searchString, setSearchString] = React.useState("");
|
|
32
|
-
//
|
|
33
|
-
// every parent, and an id found under one parent does not exist under
|
|
34
|
-
|
|
34
|
+
// The recent ids are kept per full path rather than per collection: a subcollection has
|
|
35
|
+
// the same id under every parent, and an id found under one parent does not exist under
|
|
36
|
+
// the others. Only the ones known to exist are listed.
|
|
37
|
+
const [recentIds, setRecentIds] = React.useState<string[]>([]);
|
|
38
|
+
const latestCheck = useRef(0);
|
|
35
39
|
const sideEntityController = useSideEntityController();
|
|
36
40
|
const { t } = useTranslation();
|
|
37
41
|
|
|
42
|
+
// Resolved as useEntityFetch resolves them, so the check reads what the preview shows.
|
|
43
|
+
const resolvedPath = navigation.resolveIdsFrom(path, pathSegments);
|
|
44
|
+
const resolvedPathSegments = pathSegments
|
|
45
|
+
? (navigation.resolveSegmentsFrom?.(pathSegments) ?? pathSegments)
|
|
46
|
+
: undefined;
|
|
47
|
+
|
|
48
|
+
/** Whether the entity exists, or `undefined` when that could not be told. */
|
|
49
|
+
const entityExists = useCallback(async (entityId: string): Promise<boolean | undefined> => {
|
|
50
|
+
try {
|
|
51
|
+
const entity = await dataSource.fetchEntity({
|
|
52
|
+
path: resolvedPath,
|
|
53
|
+
pathSegments: resolvedPathSegments,
|
|
54
|
+
entityId,
|
|
55
|
+
collection
|
|
56
|
+
});
|
|
57
|
+
return Boolean(entity?.values);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}, [dataSource, resolvedPath, resolvedPathSegments, collection]);
|
|
62
|
+
|
|
63
|
+
// A recent id that no longer exists would be fetched and shown as "not found" on every
|
|
64
|
+
// opening, so it is forgotten. One that could not be checked is kept for next time.
|
|
65
|
+
const refreshRecentIds = () => {
|
|
66
|
+
const ids = getRecentIds(fullIdPath);
|
|
67
|
+
const check = ++latestCheck.current;
|
|
68
|
+
Promise.all(ids.map(entityExists)).then((results) => {
|
|
69
|
+
if (check !== latestCheck.current) return;
|
|
70
|
+
const missing = ids.filter((_, i) => results[i] === false);
|
|
71
|
+
if (missing.length > 0)
|
|
72
|
+
removeRecentIds(fullIdPath, missing);
|
|
73
|
+
setRecentIds(ids.filter((_, i) => results[i] === true));
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
38
77
|
const openEntityMode = collection?.openEntityMode ?? DEFAULT_ENTITY_OPEN_MODE;
|
|
39
78
|
|
|
40
79
|
return (
|
|
@@ -42,8 +81,8 @@ export function EntityIdHeaderWidget({
|
|
|
42
81
|
<Popover
|
|
43
82
|
open={openPopup}
|
|
44
83
|
onOpenChange={(open) => {
|
|
45
|
-
// rows opened since
|
|
46
|
-
if (open)
|
|
84
|
+
// read on each opening: rows opened since the last one are recent too
|
|
85
|
+
if (open) refreshRecentIds();
|
|
47
86
|
setOpenPopup(open);
|
|
48
87
|
}}
|
|
49
88
|
sideOffset={0}
|
|
@@ -64,7 +103,10 @@ export function EntityIdHeaderWidget({
|
|
|
64
103
|
// id would open a new entity instead
|
|
65
104
|
if (!entityId) return;
|
|
66
105
|
setOpenPopup(false);
|
|
67
|
-
|
|
106
|
+
// remembered only once found, or every opening would fetch it again
|
|
107
|
+
entityExists(entityId).then((exists) => {
|
|
108
|
+
if (exists) addRecentId(fullIdPath, entityId);
|
|
109
|
+
});
|
|
68
110
|
navigateToEntity({
|
|
69
111
|
openEntityMode,
|
|
70
112
|
collection,
|
|
@@ -17,3 +17,9 @@ export function saveSearchedIdsLocally(key: string, ids: string[]) {
|
|
|
17
17
|
export function getRecentIds(key: string): string[] {
|
|
18
18
|
return getLocalStorageJSON("recent_id_searches::" + key, []);
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
export function removeRecentIds(key: string, ids: string[]) {
|
|
22
|
+
const remaining = getRecentIds(key).filter(id => !ids.includes(id));
|
|
23
|
+
saveSearchedIdsLocally(key, remaining);
|
|
24
|
+
return remaining;
|
|
25
|
+
}
|