@protobase/ui 0.2.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.
package/dist/app/app.js CHANGED
@@ -28,7 +28,7 @@ const Routes = ({ workspace, sidebarMode, assistant }) => {
28
28
  const { state, signOut } = useAuth();
29
29
  if (state.kind !== "signed-in") throw new Error("The shell is only rendered for a signed-in user");
30
30
  const { user } = state;
31
- const { path, basePath, navigate } = useRouter();
31
+ const { path, params, basePath, navigate } = useRouter();
32
32
  const route = matchRoute(path);
33
33
  const search = useGlobalSearch();
34
34
  const recent = useNavRecent(meta, basePath, route);
@@ -60,6 +60,7 @@ const Routes = ({ workspace, sidebarMode, assistant }) => {
60
60
  const assistantPanel = assistantUrl && assistantOpen && /* @__PURE__ */ jsx(AssistantPanel, {
61
61
  url: assistantUrl,
62
62
  client: assistant,
63
+ page: params.size > 0 ? `${path}?${params}` : path,
63
64
  onClose: () => setAssistantOpen(false)
64
65
  });
65
66
  const breadcrumb = breadcrumbFor({
@@ -12,28 +12,31 @@ const AssistantButton = ({ open, onToggle }) => /* @__PURE__ */ jsxs(Button, {
12
12
  onClick: onToggle,
13
13
  children: [/* @__PURE__ */ jsx(Sparkles, { className: "size-3.5" }), "Assistant"]
14
14
  });
15
- const ConnectedDock = ({ client, onClose }) => {
15
+ const ConnectedDock = ({ client, page, onClose }) => {
16
16
  const { state, offline, error, send, act } = useAssistant(client);
17
17
  return /* @__PURE__ */ jsx(AssistantDock, {
18
18
  state,
19
19
  offline,
20
20
  error,
21
- onSend: send,
21
+ onSend: (text) => send(text, page),
22
22
  onAction: act,
23
23
  onClose
24
24
  });
25
25
  };
26
- const OwnDock = ({ url, onClose }) => /* @__PURE__ */ jsx(ConnectedDock, {
26
+ const OwnDock = ({ url, ...props }) => /* @__PURE__ */ jsx(ConnectedDock, {
27
27
  client: useAssistantClient(url),
28
- onClose
28
+ ...props
29
29
  });
30
- /** The dock for the backend `/meta` names, signed in as the user; `client` replaces it in stories and tests. */
31
- const AssistantPanel = ({ url, client, onClose }) => client ? /* @__PURE__ */ jsx(ConnectedDock, {
30
+ /**
31
+ * The dock for the backend `/meta` names, signed in as the user, sending each message with the `page` the user is on;
32
+ * `client` replaces it in stories and tests.
33
+ */
34
+ const AssistantPanel = ({ url, client, ...props }) => client ? /* @__PURE__ */ jsx(ConnectedDock, {
32
35
  client,
33
- onClose
36
+ ...props
34
37
  }) : /* @__PURE__ */ jsx(OwnDock, {
35
38
  url,
36
- onClose
39
+ ...props
37
40
  });
38
41
  //#endregion
39
42
  export { AssistantButton, AssistantPanel };
@@ -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) => {
@@ -5,7 +5,7 @@ export declare const useAssistant: (client: AssistantClient | undefined) => {
5
5
  state: import("@protobase/schema").AssistantState;
6
6
  offline: boolean;
7
7
  error: string | undefined;
8
- send: (text: string) => void | undefined;
8
+ send: (text: string, page?: string) => void | undefined;
9
9
  act: (partId: string, actionId: string) => void | undefined;
10
10
  };
11
11
  //#endregion
@@ -19,7 +19,7 @@ const useAssistant = (client) => {
19
19
  state,
20
20
  offline,
21
21
  error,
22
- send: (text) => client && deliver(client.send(text)),
22
+ send: (text, page) => client && deliver(client.send(text, page)),
23
23
  act: (partId, actionId) => client && deliver(client.act(partId, actionId))
24
24
  };
25
25
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/ui",
3
- "version": "0.2.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.2.0",
42
- "@protobase/layout": "0.2.0",
43
- "@protobase/schema": "0.2.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"