@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.
- package/dist/app/app.js +1 -1
- package/dist/app/comments/comment-composer.js +1 -1
- package/dist/app/create/create-page.js +1 -1
- package/dist/app/create/create-values.js +2 -1
- package/dist/app/current-address.d.ts +1 -0
- package/dist/app/current-address.js +2 -1
- package/dist/app/displays/board-column.js +1 -1
- package/dist/app/displays/calendar-display.js +1 -1
- package/dist/app/displays/list-cards.js +1 -1
- package/dist/app/displays/map-display.js +1 -1
- package/dist/app/embedded-list/embedded-list.js +1 -1
- package/dist/app/field-editor.js +3 -3
- package/dist/app/gallery/detail-fields.js +2 -1
- package/dist/app/gallery/gallery-section.js +1 -1
- package/dist/app/gallery/photo-details.js +1 -1
- package/dist/app/group-open.js +7 -0
- package/dist/app/hand-order/hand-sorted-list.js +1 -1
- package/dist/app/incoming-relations.js +2 -1
- package/dist/app/list-page.js +1 -1
- package/dist/app/pages/action-inputs/action-inputs-dialog.js +1 -1
- package/dist/app/pages/blocks/card-row.js +1 -1
- package/dist/app/pages/blocks/modal-form.js +1 -1
- package/dist/app/pages/blocks/record-card.js +1 -1
- package/dist/app/pages/record-actions.js +1 -1
- package/dist/app/record-mode/shown-layout.js +13 -6
- package/dist/app/record-page.js +81 -64
- package/dist/app/record-row/record-row-body.js +1 -1
- package/dist/app/record-row/saved-record-row.js +1 -1
- package/dist/app/related-section.js +1 -1
- package/dist/app/searches/saved-search-rows.js +1 -1
- package/dist/app/searches/saved-searches.js +1 -1
- package/dist/app/sensitive-field.js +1 -1
- package/dist/app/use-app-address.d.ts +1 -0
- package/dist/app/use-record-editor.js +1 -1
- package/dist/forms/public-form-page.js +1 -1
- package/dist/index.js +1 -1
- package/dist/live/field-values.js +1 -1
- package/dist/organizations/all-organizations.js +62 -0
- package/dist/organizations/organization-row.js +28 -0
- package/dist/organizations/organizations-page.js +4 -62
- package/dist/organizations/use-all-organizations.js +49 -0
- package/dist/package.js +1 -1
- package/dist/record-view/group-section.js +35 -0
- package/dist/shell/assets/app-DFxg-FwV.js +69 -0
- package/dist/shell/assets/{index-Dimu63Ym.js → index-DB2bNsqa.js} +1 -1
- package/dist/shell/assets/{index-CzqIlZPP.css → index-DhW8t8wq.css} +1 -1
- package/dist/shell/assets/{maplibre-ERswanhA.js → maplibre-DRs-NHQ9.js} +1 -1
- package/dist/shell/assets/{markdown-content-D-haiVrI.js → markdown-content-obHEqNW8.js} +1 -1
- package/dist/shell/assets/{record-map-C3i03kwc.js → record-map-zJ2TD-WG.js} +1 -1
- package/dist/shell/assets/{records-map-C1JyIuz3.js → records-map-BVaFY5HA.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-Bt4hh6GB.js → shared-protobase-ui-DWzrRp3Z.js} +1 -1
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +4 -4
- 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
|
|
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(
|
|
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
|
@@ -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 };
|