@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.5.2-canary.09e1281",
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.09e1281",
57
- "@firecms/ui": "3.5.2-canary.09e1281",
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 { addRecentId, getRecentIds } from "./utils";
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
- // Kept per full path rather than per collection: a subcollection has the same id under
33
- // every parent, and an id found under one parent does not exist under the others.
34
- const [recentIds, setRecentIds] = React.useState<string[]>(() => getRecentIds(fullIdPath));
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 this header mounted are recent too
46
- if (open) setRecentIds(getRecentIds(fullIdPath));
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
- setRecentIds(addRecentId(fullIdPath, entityId));
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
+ }