@protobase/ui 0.3.0 → 0.3.1

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.
@@ -29,5 +29,10 @@ const recentRecords = (model, view, recent, rows, basePath, activeKey) => {
29
29
  };
30
30
  });
31
31
  };
32
+ /** Whether a record read anew differs from its row in a sidebar group on the fields the group shows: the group is then out of date. */
33
+ const recentRowDiffers = (model, fields, rows, key, record) => {
34
+ const row = rows.find((entry) => encodeKey(recordKey(model, entry)) === key);
35
+ return row !== void 0 && fields.some((field) => !Object.is(row[field], record[field]));
36
+ };
32
37
  //#endregion
33
- export { recentRecords, recentRequest };
38
+ export { recentRecords, recentRequest, recentRowDiffers };
@@ -1,29 +1,56 @@
1
1
  import { keys } from "../data/query-keys.js";
2
2
  import { useClient } from "../data/api-provider.js";
3
3
  import { userMenuResources } from "./user-menu-from-meta.js";
4
- import { recentRecords, recentRequest } from "./nav-recent.js";
5
- import { useQueries } from "@tanstack/react-query";
4
+ import { recentRecords, recentRequest, recentRowDiffers } from "./nav-recent.js";
5
+ import { useEffect, useMemo } from "react";
6
+ import { useQueries, useQueryClient } from "@tanstack/react-query";
6
7
  //#region src/app/use-nav-recent.ts
8
+ const groupKey = ({ model, request }) => keys.navRecent(model.name, request.filter, request.orderBy, request.pageSize);
9
+ /**
10
+ * Refetches a group when a record of its resource is read anew (a record page, or an app following one) and shows
11
+ * something else than the group's row of it, so the sidebar follows the record without waiting for a write or focus.
12
+ */
13
+ const useFollowRecords = (groups) => {
14
+ const queryClient = useQueryClient();
15
+ useEffect(() => queryClient.getQueryCache().subscribe((event) => {
16
+ if (event.type !== "updated" || event.action.type !== "success") return;
17
+ const [scope, resource, key] = event.query.queryKey;
18
+ const record = event.action.data?.record;
19
+ if (scope !== "record" || typeof key !== "string" || !record) return;
20
+ for (const group of groups) {
21
+ if (group.model.name !== resource) continue;
22
+ const page = queryClient.getQueryData(groupKey(group));
23
+ if (page && recentRowDiffers(group.model, group.request.fields, page.items, key, record)) queryClient.invalidateQueries({
24
+ queryKey: groupKey(group),
25
+ exact: true
26
+ });
27
+ }
28
+ }), [queryClient, groups]);
29
+ };
7
30
  /**
8
31
  * The records of every sidebar group (`nav.recent`), one list request each, under the `list` query key so that any
9
- * write to the resource refreshes them. The server applies the caller's access, as on the list page.
32
+ * write to the resource refreshes them, as does reading one of their records anew. The server applies the caller's
33
+ * access, as on the list page.
10
34
  */
11
35
  const useNavRecent = (meta, basePath, active) => {
12
36
  const client = useClient();
13
- const inMenu = userMenuResources(meta);
14
- const groups = Object.values(meta.views).flatMap((view) => {
15
- const model = meta.resources[view.resource];
16
- const recent = view.nav?.recent;
17
- return model && recent && !view.nav?.hidden && !inMenu.has(model.name) ? [{
18
- model,
19
- view,
20
- recent,
21
- request: recentRequest(model, view, recent)
22
- }] : [];
23
- });
24
- const results = useQueries({ queries: groups.map(({ model, request }) => ({
25
- queryKey: keys.navRecent(model.name, request.filter, request.orderBy, request.pageSize),
26
- queryFn: () => client.list(model.name, request),
37
+ const groups = useMemo(() => {
38
+ const inMenu = userMenuResources(meta);
39
+ return Object.values(meta.views).flatMap((view) => {
40
+ const model = meta.resources[view.resource];
41
+ const recent = view.nav?.recent;
42
+ return model && recent && !view.nav?.hidden && !inMenu.has(model.name) ? [{
43
+ model,
44
+ view,
45
+ recent,
46
+ request: recentRequest(model, view, recent)
47
+ }] : [];
48
+ });
49
+ }, [meta]);
50
+ useFollowRecords(groups);
51
+ const results = useQueries({ queries: groups.map((group) => ({
52
+ queryKey: groupKey(group),
53
+ queryFn: () => client.list(group.model.name, group.request),
27
54
  refetchOnWindowFocus: true
28
55
  })) });
29
56
  return Object.fromEntries(groups.map(({ model, view, recent }, index) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/ui",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "The Protobase admin app and the React hooks and components for custom UI.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -38,9 +38,9 @@
38
38
  "react-dom": "^19.3.0",
39
39
  "recharts": "^3.10.1",
40
40
  "tailwindcss": "^4.3.3",
41
- "@protobase/client": "0.3.0",
42
- "@protobase/layout": "0.3.0",
43
- "@protobase/schema": "0.3.0"
41
+ "@protobase/client": "0.3.1",
42
+ "@protobase/layout": "0.3.1",
43
+ "@protobase/schema": "0.3.1"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "tsdown"