@protobase/ui 0.21.11 → 0.22.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 (55) hide show
  1. package/dist/app/app.js +1 -1
  2. package/dist/app/comments/comment-composer.js +1 -1
  3. package/dist/app/create/create-page.js +1 -1
  4. package/dist/app/create/create-values.js +2 -1
  5. package/dist/app/current-address.d.ts +1 -0
  6. package/dist/app/current-address.js +2 -1
  7. package/dist/app/displays/board-column.js +1 -1
  8. package/dist/app/displays/calendar-display.js +1 -1
  9. package/dist/app/displays/list-cards.js +1 -1
  10. package/dist/app/displays/map-display.js +1 -1
  11. package/dist/app/embedded-list/embedded-list.js +1 -1
  12. package/dist/app/field-editor.js +3 -3
  13. package/dist/app/gallery/detail-fields.js +2 -1
  14. package/dist/app/gallery/gallery-section.js +1 -1
  15. package/dist/app/gallery/photo-details.js +1 -1
  16. package/dist/app/group-open.js +7 -0
  17. package/dist/app/hand-order/hand-sorted-list.js +1 -1
  18. package/dist/app/incoming-relations.js +2 -1
  19. package/dist/app/list-page.js +1 -1
  20. package/dist/app/pages/action-inputs/action-inputs-dialog.js +1 -1
  21. package/dist/app/pages/blocks/card-row.js +1 -1
  22. package/dist/app/pages/blocks/modal-form.js +1 -1
  23. package/dist/app/pages/blocks/record-card.js +1 -1
  24. package/dist/app/pages/record-actions.js +1 -1
  25. package/dist/app/record-mode/shown-layout.js +13 -6
  26. package/dist/app/record-page.js +81 -64
  27. package/dist/app/record-row/record-row-body.js +1 -1
  28. package/dist/app/record-row/saved-record-row.js +1 -1
  29. package/dist/app/related-section.js +1 -1
  30. package/dist/app/searches/saved-search-rows.js +1 -1
  31. package/dist/app/searches/saved-searches.js +1 -1
  32. package/dist/app/sensitive-field.js +1 -1
  33. package/dist/app/use-app-address.d.ts +1 -0
  34. package/dist/app/use-record-editor.js +1 -1
  35. package/dist/forms/public-form-page.js +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/live/field-values.js +1 -1
  38. package/dist/organizations/all-organizations.js +62 -0
  39. package/dist/organizations/organization-row.js +28 -0
  40. package/dist/organizations/organizations-page.js +4 -62
  41. package/dist/organizations/use-all-organizations.js +49 -0
  42. package/dist/package.js +1 -1
  43. package/dist/record-view/group-section.js +35 -0
  44. package/dist/shell/assets/app-DFxg-FwV.js +69 -0
  45. package/dist/shell/assets/{index-Dimu63Ym.js → index-DB2bNsqa.js} +1 -1
  46. package/dist/shell/assets/{index-CzqIlZPP.css → index-DhW8t8wq.css} +1 -1
  47. package/dist/shell/assets/{maplibre-ERswanhA.js → maplibre-DRs-NHQ9.js} +1 -1
  48. package/dist/shell/assets/{markdown-content-D-haiVrI.js → markdown-content-obHEqNW8.js} +1 -1
  49. package/dist/shell/assets/{record-map-C3i03kwc.js → record-map-zJ2TD-WG.js} +1 -1
  50. package/dist/shell/assets/{records-map-C1JyIuz3.js → records-map-BVaFY5HA.js} +1 -1
  51. package/dist/shell/assets/{shared-protobase-ui-Bt4hh6GB.js → shared-protobase-ui-DWzrRp3Z.js} +1 -1
  52. package/dist/shell/index.html +4 -4
  53. package/dist/shell/protobase.shell.json +1 -1
  54. package/package.json +4 -4
  55. package/dist/shell/assets/app-CiQapuHq.js +0 -69
@@ -5,10 +5,10 @@ import { useClient } from "../data/api-provider.js";
5
5
  import { AuthLayout } from "../auth/auth-layout.js";
6
6
  import { humanize } from "../live/naming.js";
7
7
  import { Field } from "../record-view/field.js";
8
+ import { Notice } from "../app/meta-gate.js";
8
9
  import { createBody, hasDatabaseDefault, initialDraft, isRequired } from "../app/create/create-values.js";
9
10
  import { useDocumentTitle } from "../app/document-title.js";
10
11
  import { ErrorBanner } from "../app/error-banner.js";
11
- import { Notice } from "../app/meta-gate.js";
12
12
  import { FieldEditor } from "../app/field-editor.js";
13
13
  import { fieldErrors, fieldMessage } from "../app/field-errors.js";
14
14
  import { useFocusFirstInvalid } from "../app/use-focus-first-invalid.js";
package/dist/index.js CHANGED
@@ -18,9 +18,9 @@ import { useApiToken } from "./auth/use-api-token.js";
18
18
  import { useRecord } from "./data/use-record.js";
19
19
  import { Field } from "./record-view/field.js";
20
20
  import { Section } from "./record-view/section.js";
21
- import { Textarea } from "./primitives/textarea.js";
22
21
  import { useMeta } from "./data/use-meta.js";
23
22
  import { AppLink, useAppAddress } from "./app/use-app-address.js";
23
+ import { Textarea } from "./primitives/textarea.js";
24
24
  import { useList } from "./data/use-list.js";
25
25
  import { definePlugin, defineUi } from "./app/pages/project-ui.js";
26
26
  import { ChatMessage } from "./chat/chat-message.js";
@@ -1,5 +1,5 @@
1
- import { localeDecimal, parseNumberText } from "../format/number-text.js";
2
1
  import { fileDiffers, filePatchValue } from "./file-values.js";
2
+ import { localeDecimal, parseNumberText } from "../format/number-text.js";
3
3
  //#region src/live/field-values.ts
4
4
  /** What the editor's text input holds for a stored value. */
5
5
  const toDraft = (value) => value === null || value === void 0 ? "" : typeof value === "object" ? JSON.stringify(value) : String(value);
@@ -0,0 +1,62 @@
1
+ import { Input } from "../primitives/input.js";
2
+ import { useAuth } from "../auth/auth-provider.js";
3
+ import { Button } from "../primitives/button.js";
4
+ import { AccountSection, SectionError } from "../account/account-section.js";
5
+ import { useAction } from "./use-action.js";
6
+ import { reopenIn } from "./use-organizations.js";
7
+ import { OrganizationRow } from "./organization-row.js";
8
+ import { useAllOrganizations } from "./use-all-organizations.js";
9
+ import { useState } from "react";
10
+ import { Search } from "lucide-react";
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ //#region src/organizations/all-organizations.tsx
13
+ /** Every organization in the app for people with a global role, who may work in any of them. */
14
+ const AllOrganizations = ({ basePath, currentId }) => {
15
+ const { session } = useAuth();
16
+ const [query, setQuery] = useState("");
17
+ const { found, total, more, loading } = useAllOrganizations(session.organizations, query);
18
+ const { busy, error, run } = useAction();
19
+ return /* @__PURE__ */ jsxs(AccountSection, {
20
+ title: "All organizations",
21
+ description: "Every organization in the app. Your global roles let you work in any of them, with what those roles allow there. Opening one you are not a member of is written to the audit log.",
22
+ children: [
23
+ /* @__PURE__ */ jsx(SectionError, { message: error }),
24
+ /* @__PURE__ */ jsx(Input, {
25
+ "aria-label": "Find an organization",
26
+ placeholder: "Name or slug",
27
+ value: query,
28
+ onChange: (event) => setQuery(event.target.value),
29
+ leading: /* @__PURE__ */ jsx(Search, { className: "size-3.5" }),
30
+ wrapperClassName: "mb-2"
31
+ }),
32
+ /* @__PURE__ */ jsxs("p", {
33
+ className: "mb-3 text-xs text-muted-foreground",
34
+ children: [
35
+ found.length,
36
+ " of ",
37
+ total
38
+ ]
39
+ }),
40
+ found.length > 0 && /* @__PURE__ */ jsx("ul", {
41
+ className: "divide-y rounded-md border",
42
+ children: found.map((organization) => /* @__PURE__ */ jsx(OrganizationRow, {
43
+ organization,
44
+ current: organization.id === currentId,
45
+ busy,
46
+ onOpen: () => void run(async () => {
47
+ await session.organizations.switchTo(organization.id);
48
+ reopenIn(basePath);
49
+ })
50
+ }, organization.id))
51
+ }),
52
+ found.length < total && /* @__PURE__ */ jsx(Button, {
53
+ className: "mt-3",
54
+ loading,
55
+ onClick: () => void more(),
56
+ children: "Show more"
57
+ })
58
+ ]
59
+ });
60
+ };
61
+ //#endregion
62
+ export { AllOrganizations };
@@ -0,0 +1,28 @@
1
+ import { Badge } from "../primitives/badge.js";
2
+ import { Button } from "../primitives/button.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/organizations/organization-row.tsx
5
+ /** An organization with Open, or a Current badge when the person works in it. */
6
+ const OrganizationRow = ({ organization, current, busy, onOpen }) => /* @__PURE__ */ jsxs("li", {
7
+ className: "flex items-center gap-3 px-3 py-2.5",
8
+ children: [/* @__PURE__ */ jsxs("span", {
9
+ className: "min-w-0 flex-1",
10
+ children: [/* @__PURE__ */ jsx("span", {
11
+ className: "block truncate font-medium",
12
+ children: organization.name
13
+ }), /* @__PURE__ */ jsx("span", {
14
+ className: "block text-xs text-muted-foreground",
15
+ children: organization.slug
16
+ })]
17
+ }), current ? /* @__PURE__ */ jsx(Badge, {
18
+ tone: "green",
19
+ children: "Current"
20
+ }) : /* @__PURE__ */ jsx(Button, {
21
+ size: "sm",
22
+ loading: busy,
23
+ onClick: onOpen,
24
+ children: "Open"
25
+ })]
26
+ });
27
+ //#endregion
28
+ export { OrganizationRow };
@@ -1,4 +1,3 @@
1
- import { Badge } from "../primitives/badge.js";
2
1
  import { Input } from "../primitives/input.js";
3
2
  import { PageHeader } from "../app-shell/page-header.js";
4
3
  import { useAuth } from "../auth/auth-provider.js";
@@ -8,33 +7,13 @@ import { roleLabel } from "./organization-paths.js";
8
7
  import { useAction } from "./use-action.js";
9
8
  import { isAdmin } from "../auth/user-to-shell.js";
10
9
  import { reopenIn } from "./use-organizations.js";
10
+ import { OrganizationRow } from "./organization-row.js";
11
+ import { AllOrganizations } from "./all-organizations.js";
11
12
  import { useEffect, useState } from "react";
12
- import { Search } from "lucide-react";
13
13
  import { jsx, jsxs } from "react/jsx-runtime";
14
14
  //#region src/organizations/organizations-page.tsx
15
15
  /** A slug from a name: lowercase ASCII words joined by dashes, as the server accepts them. */
16
16
  const slugOf = (name) => name.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
17
- const OrganizationRow = ({ organization, current, busy, onOpen }) => /* @__PURE__ */ jsxs("li", {
18
- className: "flex items-center gap-3 px-3 py-2.5",
19
- children: [/* @__PURE__ */ jsxs("span", {
20
- className: "min-w-0 flex-1",
21
- children: [/* @__PURE__ */ jsx("span", {
22
- className: "block truncate font-medium",
23
- children: organization.name
24
- }), /* @__PURE__ */ jsx("span", {
25
- className: "block text-xs text-muted-foreground",
26
- children: organization.slug
27
- })]
28
- }), current ? /* @__PURE__ */ jsx(Badge, {
29
- tone: "green",
30
- children: "Current"
31
- }) : /* @__PURE__ */ jsx(Button, {
32
- size: "sm",
33
- loading: busy,
34
- onClick: onOpen,
35
- children: "Open"
36
- })]
37
- });
38
17
  const NewOrganization = ({ basePath }) => {
39
18
  const { session } = useAuth();
40
19
  const [name, setName] = useState("");
@@ -82,43 +61,6 @@ const NewOrganization = ({ basePath }) => {
82
61
  })]
83
62
  });
84
63
  };
85
- const FindOrganization = ({ basePath, currentId }) => {
86
- const { session } = useAuth();
87
- const [query, setQuery] = useState("");
88
- const [found, setFound] = useState([]);
89
- const { busy, error, run } = useAction();
90
- useEffect(() => {
91
- const timer = setTimeout(() => void session.organizations.search(query).then(setFound, () => setFound([])), 200);
92
- return () => clearTimeout(timer);
93
- }, [session, query]);
94
- return /* @__PURE__ */ jsxs(AccountSection, {
95
- title: "Find an organization",
96
- description: "Your global roles let you work in any organization, with what those roles allow there. Opening one you are not a member of is written to the audit log.",
97
- children: [
98
- /* @__PURE__ */ jsx(SectionError, { message: error }),
99
- /* @__PURE__ */ jsx(Input, {
100
- "aria-label": "Find an organization",
101
- placeholder: "Name or slug",
102
- value: query,
103
- onChange: (event) => setQuery(event.target.value),
104
- leading: /* @__PURE__ */ jsx(Search, { className: "size-3.5" }),
105
- wrapperClassName: "mb-3"
106
- }),
107
- found.length > 0 && /* @__PURE__ */ jsx("ul", {
108
- className: "divide-y rounded-md border",
109
- children: found.map((organization) => /* @__PURE__ */ jsx(OrganizationRow, {
110
- organization,
111
- current: organization.id === currentId,
112
- busy,
113
- onOpen: () => void run(async () => {
114
- await session.organizations.switchTo(organization.id);
115
- reopenIn(basePath);
116
- })
117
- }, organization.id))
118
- })
119
- ]
120
- });
121
- };
122
64
  const YourInvitations = () => {
123
65
  const { session, organizations } = useAuth();
124
66
  const [invitations, setInvitations] = useState([]);
@@ -152,7 +94,7 @@ const YourInvitations = () => {
152
94
  };
153
95
  /**
154
96
  * The person's organizations: switching between them, making a new one when the app allows it, and for people with a
155
- * global role finding any organization. Someone in none sees their invitations here.
97
+ * global role every organization. Someone in none sees their invitations here.
156
98
  */
157
99
  const OrganizationsPage = ({ state, basePath }) => {
158
100
  const { session, organizations: settings, state: auth } = useAuth();
@@ -185,7 +127,7 @@ const OrganizationsPage = ({ state, basePath }) => {
185
127
  }),
186
128
  /* @__PURE__ */ jsx(YourInvitations, {}),
187
129
  mayCreate && /* @__PURE__ */ jsx(NewOrganization, { basePath }),
188
- (state.current?.globalRoles.length ?? 0) > 0 && /* @__PURE__ */ jsx(FindOrganization, {
130
+ (state.current?.globalRoles.length ?? 0) > 0 && /* @__PURE__ */ jsx(AllOrganizations, {
189
131
  basePath,
190
132
  currentId
191
133
  })
@@ -0,0 +1,49 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ //#region src/organizations/use-all-organizations.ts
3
+ /**
4
+ * Every organization, 20 at a time, narrowed by `query` (searched 200 ms after the text stops changing). `more` adds the
5
+ * next page. A page that arrives after the text changed is dropped, so it never adds rows of the old filter.
6
+ */
7
+ const useAllOrganizations = (client, query) => {
8
+ const [found, setFound] = useState([]);
9
+ const [total, setTotal] = useState(0);
10
+ const [loading, setLoading] = useState(false);
11
+ const latest = useRef(query);
12
+ latest.current = query;
13
+ useEffect(() => {
14
+ const timer = setTimeout(() => {
15
+ setLoading(true);
16
+ client.search(query).then((page) => {
17
+ if (latest.current !== query) return;
18
+ setFound(page.organizations);
19
+ setTotal(page.total);
20
+ setLoading(false);
21
+ }, () => {
22
+ if (latest.current !== query) return;
23
+ setFound([]);
24
+ setTotal(0);
25
+ setLoading(false);
26
+ });
27
+ }, 200);
28
+ return () => clearTimeout(timer);
29
+ }, [client, query]);
30
+ const more = async () => {
31
+ const asked = query;
32
+ setLoading(true);
33
+ const page = await client.search(asked, found.length).catch(() => void 0);
34
+ if (latest.current !== asked) return;
35
+ if (page) {
36
+ setFound((shown) => [...shown, ...page.organizations]);
37
+ setTotal(page.total);
38
+ }
39
+ setLoading(false);
40
+ };
41
+ return {
42
+ found,
43
+ total,
44
+ more,
45
+ loading
46
+ };
47
+ };
48
+ //#endregion
49
+ export { useAllOrganizations };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.21.11";
2
+ var version = "0.22.0";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -0,0 +1,35 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { AppLink } from "../app/use-app-address.js";
3
+ import { useId } from "react";
4
+ import { ChevronRight } from "lucide-react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/record-view/group-section.tsx
7
+ const headingClass = "text-[16px] font-semibold text-foreground";
8
+ /**
9
+ * A heading over sections of the record page. With `toggle`, the address that opens or closes it, the heading is a
10
+ * link, so a copied link opens the page with the group open; the items show only while `open`. Without it the group
11
+ * is always open.
12
+ */
13
+ const GroupSection = ({ title, open, toggle, children }) => {
14
+ const id = useId();
15
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
16
+ className: cn("py-8", open && "pb-0"),
17
+ children: toggle ? /* @__PURE__ */ jsxs(AppLink, {
18
+ to: toggle,
19
+ replace: true,
20
+ "aria-expanded": open,
21
+ "aria-controls": id,
22
+ className: cn("inline-flex items-center gap-1.5", headingClass),
23
+ children: [/* @__PURE__ */ jsx(ChevronRight, { className: cn("size-4 transition-transform", open && "rotate-90") }), title]
24
+ }) : /* @__PURE__ */ jsx("span", {
25
+ className: headingClass,
26
+ children: title
27
+ })
28
+ }), open && /* @__PURE__ */ jsx("div", {
29
+ id,
30
+ className: "divide-y divide-border",
31
+ children
32
+ })] });
33
+ };
34
+ //#endregion
35
+ export { GroupSection };