@protobase/ui 0.12.21 → 0.13.0

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.
Files changed (51) hide show
  1. package/dist/app/displays/card-layout.js +19 -0
  2. package/dist/app/displays/cards-display.js +17 -143
  3. package/dist/app/displays/display-controls.js +3 -2
  4. package/dist/app/displays/list-cards.js +50 -0
  5. package/dist/app/displays/list-displays.d.ts +1 -1
  6. package/dist/app/displays/list-displays.js +40 -6
  7. package/dist/app/displays/map-display.js +120 -0
  8. package/dist/app/displays/map-groups.js +55 -0
  9. package/dist/app/displays/record-card.js +171 -0
  10. package/dist/app/displays/virtual-cards.js +138 -0
  11. package/dist/app/embedded-list/embedded-columns.js +52 -0
  12. package/dist/app/embedded-list/embedded-display.js +26 -0
  13. package/dist/app/embedded-list/embedded-list.js +155 -0
  14. package/dist/app/embedded-list/use-embedded-display.js +23 -0
  15. package/dist/app/embedded-list/use-embedded-rows.js +33 -0
  16. package/dist/app/field-editor.js +1 -1
  17. package/dist/app/list-page.js +32 -11
  18. package/dist/app/list-scroll.js +25 -0
  19. package/dist/app/map/record-map.js +2 -2
  20. package/dist/app/map/records-map.js +106 -0
  21. package/dist/app/named-icon.js +3 -2
  22. package/dist/app/pages/blocks/table.js +21 -41
  23. package/dist/app/pages/field-value.js +2 -2
  24. package/dist/app/record-page.js +3 -2
  25. package/dist/app/related-section.js +30 -52
  26. package/dist/app/router.js +1 -0
  27. package/dist/data-grid/data-grid.js +19 -39
  28. package/dist/data-grid/geometry.js +7 -9
  29. package/dist/data-grid/use-grid-scroll.js +8 -1
  30. package/dist/data-grid/use-virtual-rows.js +31 -0
  31. package/dist/live/columns-from-model.js +6 -4
  32. package/dist/live/grid-loader.js +26 -10
  33. package/dist/package.js +1 -1
  34. package/dist/record-view/section.js +24 -17
  35. package/dist/shell/assets/app-DLjaMVJD.js +66 -0
  36. package/dist/shell/assets/index-BYPphaEm.css +2 -0
  37. package/dist/shell/assets/{index-BNrginzc.js → index-D7IcSr6n.js} +3 -3
  38. package/dist/shell/assets/maplibre-gl-Bt-3iOM-.js +1 -0
  39. package/dist/shell/assets/record-map-DL4b9dZO.js +814 -0
  40. package/dist/shell/assets/records-map-D0EOO5kZ.js +1 -0
  41. package/dist/shell/assets/shared-protobase-ui-B8UXOIeW.js +1 -0
  42. package/dist/shell/index.html +5 -5
  43. package/dist/shell/protobase.shell.json +1 -1
  44. package/package.json +4 -5
  45. package/dist/app/pages/record-table.js +0 -114
  46. package/dist/app/pages/use-relation-labels.js +0 -30
  47. package/dist/shell/assets/app-CdxyCTXE.js +0 -66
  48. package/dist/shell/assets/index-UCjnDdFX.css +0 -2
  49. package/dist/shell/assets/maplibre-gl-B1f3q0xK.js +0 -1
  50. package/dist/shell/assets/record-map-HYe7IrpM.js +0 -814
  51. package/dist/shell/assets/shared-protobase-ui-DkwITozF.js +0 -1
@@ -0,0 +1,31 @@
1
+ import { rowsInView } from "./geometry.js";
2
+ import { useGridScroll } from "./use-grid-scroll.js";
3
+ //#region src/data-grid/use-virtual-rows.ts
4
+ const overscan = 6;
5
+ /**
6
+ * Rows of a fixed height over a proxy scroller, so any number of rows scrolls at native speed: `ref` and `onScroll` go
7
+ * on the scrolling element, and each drawn row sits at `offset` from the top of the viewport's rows, below `inset`.
8
+ * `first` and `last` are the indexes of the drawn rows, overscan included.
9
+ */
10
+ const useVirtualRows = ({ total, rowHeight, initialIndex, inset = 0, settle }) => {
11
+ const scroll = useGridScroll(total, rowHeight, initialIndex);
12
+ const { ref, onScroll, viewport, geometry, scrollTo } = scroll;
13
+ const top = settle ? settle(scroll.top) : scroll.top;
14
+ const { first, last } = rowsInView(total, top, rowHeight, Math.max(0, viewport - inset), overscan);
15
+ return {
16
+ ref,
17
+ onScroll,
18
+ viewport,
19
+ top,
20
+ geometry,
21
+ scrollTo,
22
+ rows: Array.from({ length: last - first + 1 }, (_, offset) => ({
23
+ index: first + offset,
24
+ offset: (first + offset) * rowHeight - top
25
+ })),
26
+ first,
27
+ last
28
+ };
29
+ };
30
+ //#endregion
31
+ export { useVirtualRows };
@@ -61,9 +61,11 @@ const kindOf = (field, view, index) => {
61
61
  }
62
62
  };
63
63
  const currencyCents = (value) => Math.round(Number.parseFloat(String(value)) * 100);
64
- /** Grid columns for a view's list: kinds follow the field types and the view's format hints. */
65
- const columnsFromModel = (resource, view, highlight = []) => {
66
- return (view?.list?.columns ?? Object.keys(resource.fields).slice(0, 6)).flatMap((name, index) => {
64
+ /** The fields a view's list shows as columns: its list columns, else the resource's first six fields. */
65
+ const listColumnNames = (resource, view) => view?.list?.columns ?? Object.keys(resource.fields).slice(0, 6);
66
+ /** Grid columns for `names`, by default the view's list: kinds follow the field types and the view's format hints. */
67
+ const columnsFromModel = (resource, view, highlight = [], names = listColumnNames(resource, view)) => {
68
+ return names.flatMap((name, index) => {
67
69
  const field = resource.fields[name];
68
70
  if (!field) return [];
69
71
  const viewField = view?.fields[name];
@@ -97,4 +99,4 @@ const columnsFromModel = (resource, view, highlight = []) => {
97
99
  });
98
100
  };
99
101
  //#endregion
100
- export { columnsFromModel, headerWidth, toneFor };
102
+ export { columnsFromModel, headerWidth, listColumnNames, toneFor };
@@ -4,11 +4,12 @@ import { withLabels } from "./relation-labels.js";
4
4
  //#region src/live/grid-loader.ts
5
5
  const exactBelow = 5e3;
6
6
  /**
7
- * Loads fixed-size pages by number. Sequential scrolling follows `next_page_token`; a jump asks `:seek` for the
8
- * page at that row position. Every page is cached by TanStack Query.
7
+ * Loads fixed-size pages by number. The page after a cached or loading one follows that page's `next_page_token`; a
8
+ * jump to a page whose predecessor is neither asks `:seek` for the page at that row position, so the pages of one jump
9
+ * are read on from a single seek. Every page is cached by TanStack Query.
9
10
  */
10
11
  const createGridLoader = ({ queryClient, client, resources, views, model, columns, filter, orderBy }) => {
11
- const tokens = /* @__PURE__ */ new Map();
12
+ const pageKey = (number) => keys.page(model.name, filter, orderBy, 40, number);
12
13
  /** The server's total is a planner estimate, off by a lot for small results; settle those exactly. */
13
14
  const firstPageTotal = async (result) => {
14
15
  if (result.nextPageToken === "") return result.items.length;
@@ -20,36 +21,51 @@ const createGridLoader = ({ queryClient, client, resources, views, model, column
20
21
  count: "exact"
21
22
  })).totalSize ?? result.totalSizeEstimate;
22
23
  };
24
+ /** The token that continues after the page before `number`, or undefined when that page is neither cached nor loading. */
25
+ const tokenBefore = async (number) => {
26
+ if (number === 0) return "";
27
+ const key = pageKey(number - 1);
28
+ const cached = queryClient.getQueryData(key);
29
+ if (cached) return cached.nextPageToken;
30
+ if (queryClient.getQueryState(key)?.fetchStatus !== "fetching") return void 0;
31
+ return (await page(number - 1)).nextPageToken;
32
+ };
23
33
  const fetchPage = async (page) => {
24
- const token = tokens.get(page);
34
+ const token = await tokenBefore(page);
35
+ if (page > 0 && token === "") return {
36
+ rows: [],
37
+ total: 0,
38
+ nextPageToken: ""
39
+ };
25
40
  const params = {
26
41
  filter,
27
42
  orderBy,
28
43
  pageSize: 40
29
44
  };
30
- const result = page === 0 || token !== void 0 ? await client.list(model.name, {
45
+ const result = token !== void 0 ? await client.list(model.name, {
31
46
  ...params,
32
- pageToken: token ?? ""
47
+ pageToken: token
33
48
  }) : await client.seek(model.name, {
34
49
  ...params,
35
50
  position: page * 40
36
51
  });
37
52
  if (result.items.length === 0 && page > 0) return {
38
53
  rows: [],
39
- total: 0
54
+ total: 0,
55
+ nextPageToken: ""
40
56
  };
41
- tokens.set(page + 1, result.nextPageToken);
42
57
  const ids = result.items.map((record) => recordId(model, record));
43
58
  return {
44
59
  rows: (await withLabels(queryClient, client, resources, views, model, columns, result.items)).map((record, index) => ({
45
60
  ...record,
46
61
  id: ids[index]
47
62
  })),
48
- total: page === 0 ? await firstPageTotal(result) : result.totalSizeEstimate
63
+ total: page === 0 ? await firstPageTotal(result) : result.totalSizeEstimate,
64
+ nextPageToken: result.nextPageToken
49
65
  };
50
66
  };
51
67
  const page = (number) => queryClient.fetchQuery({
52
- queryKey: keys.page(model.name, filter, orderBy, 40, number),
68
+ queryKey: pageKey(number),
53
69
  staleTime: 6e4,
54
70
  queryFn: () => fetchPage(number)
55
71
  });
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.12.21";
2
+ var version = "0.13.0";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -1,21 +1,28 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  //#region src/record-view/section.tsx
3
- const Section = ({ title, help, children }) => /* @__PURE__ */ jsxs("section", {
4
- className: "py-8 first:pt-8",
5
- children: [
6
- /* @__PURE__ */ jsx("h2", {
7
- className: "text-[15px] font-semibold text-foreground",
8
- children: title
9
- }),
10
- help && /* @__PURE__ */ jsx("p", {
11
- className: "mt-1 max-w-xl text-[13px] leading-relaxed text-muted-foreground",
12
- children: help
13
- }),
14
- /* @__PURE__ */ jsx("div", {
15
- className: "mt-5",
16
- children
17
- })
18
- ]
19
- });
3
+ /** A titled part of the record page; `actions` sit at the end of the title's line. */
4
+ const Section = ({ title, help, actions, children }) => {
5
+ const heading = /* @__PURE__ */ jsx("h2", {
6
+ className: "text-[15px] font-semibold text-foreground",
7
+ children: title
8
+ });
9
+ return /* @__PURE__ */ jsxs("section", {
10
+ className: "py-8 first:pt-8",
11
+ children: [
12
+ actions ? /* @__PURE__ */ jsxs("div", {
13
+ className: "flex flex-wrap items-center justify-between gap-x-4 gap-y-2",
14
+ children: [heading, actions]
15
+ }) : heading,
16
+ help && /* @__PURE__ */ jsx("p", {
17
+ className: "mt-1 max-w-xl text-[13px] leading-relaxed text-muted-foreground",
18
+ children: help
19
+ }),
20
+ /* @__PURE__ */ jsx("div", {
21
+ className: "mt-5",
22
+ children
23
+ })
24
+ ]
25
+ });
26
+ };
20
27
  //#endregion
21
28
  export { Section };