@protobase/ui 0.46.9 → 0.46.11
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/create/create-page.js +2 -2
- package/dist/app/create/create-values.js +17 -2
- package/dist/app/nav-from-meta.js +7 -3
- package/dist/app-shell/sidebar.js +1 -1
- package/dist/auth/auth-messages.js +4 -1
- package/dist/auth/user-to-shell.js +4 -1
- package/dist/format/messages.js +2 -2
- package/dist/format/number.js +1 -1
- package/dist/forms/public-form-page.js +2 -2
- package/dist/organizations/messages.js +2 -0
- package/dist/organizations/organization-paths.js +3 -2
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-KkSCyh3l.js +9 -0
- package/dist/shell/assets/export-worker-IJWS5-Wc.js +3 -0
- package/dist/shell/assets/{index-1Ue_dZiA.js → index-qw49QDJt.js} +2 -2
- package/dist/shell/assets/{records-map-DPA8Y3C8.js → records-map-1-IIJOiJ.js} +2 -2
- package/dist/shell/assets/{shared-protobase-ui-dWIf89ff.js → shared-protobase-ui-Da6Lujxh.js} +2 -2
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +2 -2
- package/dist/shell-source-maps/assets/app-KkSCyh3l.js.map +1 -0
- package/dist/shell-source-maps/assets/export-worker-IJWS5-Wc.js.map +1 -0
- package/dist/shell-source-maps/assets/{index-1Ue_dZiA.js.map → index-qw49QDJt.js.map} +1 -1
- package/dist/shell-source-maps/assets/{records-map-DPA8Y3C8.js.map → records-map-1-IIJOiJ.js.map} +1 -1
- package/dist/shell-source-maps/assets/{shared-protobase-ui-dWIf89ff.js.map → shared-protobase-ui-Da6Lujxh.js.map} +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-axeIZ-10.js +0 -9
- package/dist/shell/assets/export-worker-ClACk00b.js +0 -3
- package/dist/shell-source-maps/assets/app-axeIZ-10.js.map +0 -1
- package/dist/shell-source-maps/assets/export-worker-ClACk00b.js.map +0 -1
|
@@ -6,7 +6,7 @@ import { text } from "../messages.js";
|
|
|
6
6
|
import { useNavigationGuard } from "../router.js";
|
|
7
7
|
import { useAppAddress } from "../use-app-address.js";
|
|
8
8
|
import { text as text$1 } from "./messages.js";
|
|
9
|
-
import { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isRequired } from "./create-values.js";
|
|
9
|
+
import { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isRequired, numberDefault } from "./create-values.js";
|
|
10
10
|
import { FieldEditor } from "../field-editor.js";
|
|
11
11
|
import { can } from "../permissions.js";
|
|
12
12
|
import { fetchLabels } from "../../live/relation-labels.js";
|
|
@@ -133,7 +133,7 @@ const LoadedCreate = ({ resource, values }) => {
|
|
|
133
133
|
value: isChosen(value) ? value.key : value ?? (field.type === "boolean" ? false : ""),
|
|
134
134
|
stored: void 0,
|
|
135
135
|
relationLabel: isChosen(value) ? value.label : void 0,
|
|
136
|
-
placeholder: hasDatabaseDefault(field) ? text$1("setByDatabase") :
|
|
136
|
+
placeholder: hasDatabaseDefault(field) ? text$1("setByDatabase") : numberDefault(field),
|
|
137
137
|
relationPicker: target ? {
|
|
138
138
|
target,
|
|
139
139
|
view: meta.views[target.name]
|
|
@@ -59,17 +59,32 @@ const givenValue = (field, value) => {
|
|
|
59
59
|
if (field.type === "boolean") return value === "true";
|
|
60
60
|
return value;
|
|
61
61
|
};
|
|
62
|
+
const numeric = /* @__PURE__ */ new Set([
|
|
63
|
+
"integer",
|
|
64
|
+
"bigint",
|
|
65
|
+
"decimal"
|
|
66
|
+
]);
|
|
67
|
+
/**
|
|
68
|
+
* A number field's literal default, which its empty input shows as a placeholder rather than as its value: typing into a
|
|
69
|
+
* prefilled `0` would add to it, and the create applies the default to an empty field.
|
|
70
|
+
*/
|
|
71
|
+
const numberDefault = (field) => {
|
|
72
|
+
if (!numeric.has(field.type) || !field.default || !("value" in field.default)) return void 0;
|
|
73
|
+
const value = field.default.value;
|
|
74
|
+
return value === null || value === void 0 ? void 0 : String(value);
|
|
75
|
+
};
|
|
62
76
|
/**
|
|
63
77
|
* The form's starting values: the values the address gives (`/notes/new?listing_id=42`), for the fields the form has,
|
|
64
|
-
* else literal defaults from `/meta
|
|
78
|
+
* else literal defaults from `/meta`, but for numbers. Database defaults stay empty so the database fills them.
|
|
65
79
|
*/
|
|
66
80
|
const initialDraft = (model, sections, given = {}) => Object.fromEntries(sections.flatMap((section) => section.fields.flatMap((name) => {
|
|
67
81
|
const field = model.fields[name];
|
|
68
82
|
if (Object.hasOwn(given, name)) return [[name, givenValue(field, given[name])]];
|
|
69
83
|
const fallback = field.default;
|
|
84
|
+
if (numberDefault(field) !== void 0) return [];
|
|
70
85
|
if (!fallback || !("value" in fallback) || fallback.value === null || fallback.value === void 0) return [];
|
|
71
86
|
return [[name, typeof fallback.value === "boolean" ? fallback.value : String(fallback.value)]];
|
|
72
87
|
})));
|
|
73
88
|
const hasDatabaseDefault = (field) => Boolean(field.default && "db" in field.default);
|
|
74
89
|
//#endregion
|
|
75
|
-
export { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isCreatable, isRequired, keyValue };
|
|
90
|
+
export { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isCreatable, isRequired, keyValue, numberDefault };
|
|
@@ -16,8 +16,13 @@ const groupLabels = {
|
|
|
16
16
|
core: "admin",
|
|
17
17
|
hr: "hr"
|
|
18
18
|
};
|
|
19
|
+
/** The group of a schema's resources: none for the default `public` schema, whose name would read as "visible to anyone". */
|
|
20
|
+
const schemaGroup = (schema = "public") => {
|
|
21
|
+
if (schema === "public") return "";
|
|
22
|
+
return groupLabels[schema] ? text(groupLabels[schema]) : humanize(schema);
|
|
23
|
+
};
|
|
19
24
|
/**
|
|
20
|
-
* One sidebar group per `nav.group` (default: the database schema, and "Pages" for pages); `nav.hidden` and a place in
|
|
25
|
+
* One sidebar group per `nav.group` (default: the database schema, none for `public`, and "Pages" for pages); `nav.hidden` and a place in
|
|
21
26
|
* the user menu leave an entry out, `nav.order` sorts within a group, and `recent` (from `useNavRecent`) makes an entry
|
|
22
27
|
* collapsible. Groups with pages come first, so a dashboard leads the sidebar. The resources of a source that cannot be
|
|
23
28
|
* read are listed under its label, each with the problem. A visitor also gets the resources whose views ask them to sign
|
|
@@ -50,8 +55,7 @@ const navFromMeta = (meta, basePath = "", recent = {}) => {
|
|
|
50
55
|
for (const model of Object.values(meta.resources)) {
|
|
51
56
|
const view = meta.views[model.name];
|
|
52
57
|
if (!view || inMenu.has(model.name)) continue;
|
|
53
|
-
const
|
|
54
|
-
const defaultGroup = view.nav?.group ?? (groupLabels[schema] ? text(groupLabels[schema]) : humanize(schema));
|
|
58
|
+
const defaultGroup = view.nav?.group ?? schemaGroup(model.table.schema);
|
|
55
59
|
const add = (groupName, item, order) => {
|
|
56
60
|
const group = groups.get(groupName) ?? {
|
|
57
61
|
label: groupName,
|
|
@@ -34,7 +34,7 @@ const Sidebar = ({ mode, activeItem, user, workspace, appName, appIcon, forceExp
|
|
|
34
34
|
className: cn("flex-1 overflow-y-auto overflow-x-hidden pb-2", mode === "icon-label" ? "px-2" : "px-2"),
|
|
35
35
|
children: groups.map((group, index) => /* @__PURE__ */ jsxs("div", {
|
|
36
36
|
className: cn("flex flex-col gap-0.5", index > 0 && "mt-3"),
|
|
37
|
-
children: [inline ? /* @__PURE__ */ jsx("div", {
|
|
37
|
+
children: [inline ? group.label && /* @__PURE__ */ jsx("div", {
|
|
38
38
|
className: cn("px-3 pb-1 pt-1 text-xs font-medium uppercase tracking-wide text-faint-foreground", expanding && "h-4 whitespace-nowrap py-0 opacity-0 transition-opacity group-hover/side:opacity-100 group-focus-within/side:opacity-100", expanded && "opacity-100"),
|
|
39
39
|
children: group.label
|
|
40
40
|
}) : index > 0 && /* @__PURE__ */ jsx("div", { className: "mx-auto mb-1 h-px w-6 bg-border" }), group.items.map((item) => item.recent && isTextMode(mode) ? /* @__PURE__ */ jsx(SidebarGroup, {
|
|
@@ -7,6 +7,7 @@ const text = defineMessages({
|
|
|
7
7
|
wrongEmailOrPassword: "The email or password is not right.",
|
|
8
8
|
wrongPassword: "The password is not right.",
|
|
9
9
|
wrongCode: "The code is not right.",
|
|
10
|
+
wrongOrExpiredCode: "The code is not right, or has expired. Ask for a new one.",
|
|
10
11
|
codeExpired: "The code has expired. Ask for a new one.",
|
|
11
12
|
tooManyCodes: "Too many wrong codes. Ask for a new one.",
|
|
12
13
|
wrongBackupCode: "The backup code is not right, or was used before.",
|
|
@@ -35,6 +36,7 @@ const text = defineMessages({
|
|
|
35
36
|
wrongEmailOrPassword: "Het e-mailadres of wachtwoord klopt niet.",
|
|
36
37
|
wrongPassword: "Het wachtwoord klopt niet.",
|
|
37
38
|
wrongCode: "De code klopt niet.",
|
|
39
|
+
wrongOrExpiredCode: "De code klopt niet, of is verlopen. Vraag een nieuwe aan.",
|
|
38
40
|
codeExpired: "De code is verlopen. Vraag een nieuwe aan.",
|
|
39
41
|
tooManyCodes: "Te veel verkeerde codes. Vraag een nieuwe aan.",
|
|
40
42
|
wrongBackupCode: "De back-upcode klopt niet, of is al eerder gebruikt.",
|
|
@@ -81,8 +83,9 @@ const providerText = defineMessages({
|
|
|
81
83
|
const messages = {
|
|
82
84
|
INVALID_EMAIL_OR_PASSWORD: "wrongEmailOrPassword",
|
|
83
85
|
INVALID_PASSWORD: "wrongPassword",
|
|
84
|
-
INVALID_OTP: "
|
|
86
|
+
INVALID_OTP: "wrongOrExpiredCode",
|
|
85
87
|
OTP_EXPIRED: "codeExpired",
|
|
88
|
+
OTP_HAS_EXPIRED: "codeExpired",
|
|
86
89
|
TOO_MANY_ATTEMPTS: "tooManyCodes",
|
|
87
90
|
INVALID_CODE: "wrongCode",
|
|
88
91
|
INVALID_BACKUP_CODE: "wrongBackupCode",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { text } from "./messages.js";
|
|
2
|
+
import { capitalise } from "../live/naming.js";
|
|
2
3
|
//#region src/auth/user-to-shell.ts
|
|
3
4
|
/** The hue of someone's avatar, the same for the same id. */
|
|
4
5
|
const hueOf = (text) => [...text].reduce((sum, char) => (sum * 31 + char.charCodeAt(0)) % 360, 7);
|
|
@@ -7,6 +8,8 @@ const initialsOf = (name) => name.split(/[\s@.]+/).filter(Boolean).slice(0, 2).m
|
|
|
7
8
|
const rolesOf = (user) => (user.role ?? "user").split(",").map((role) => role.trim());
|
|
8
9
|
/** The user holds the `admin` role, among others perhaps. */
|
|
9
10
|
const isAdmin = (user) => rolesOf(user).includes("admin");
|
|
11
|
+
/** A role's name as words: `full_access` becomes "Full access". */
|
|
12
|
+
const roleLabel = (role) => capitalise(role.replace(/[_-]+/g, " ").trim());
|
|
10
13
|
/** What the sidebar's profile menu shows for a signed-in user. */
|
|
11
14
|
const userToShell = (user) => {
|
|
12
15
|
const roles = rolesOf(user);
|
|
@@ -16,7 +19,7 @@ const userToShell = (user) => {
|
|
|
16
19
|
email: user.email,
|
|
17
20
|
initials: initialsOf(user.name || user.email) || "?",
|
|
18
21
|
hue: hueOf(user.id || user.email),
|
|
19
|
-
role: admin ? text("admin") : (roles[0] ?? "user")
|
|
22
|
+
role: admin ? text("admin") : roleLabel(roles[0] ?? "user"),
|
|
20
23
|
roleTone: admin ? "violet" : "blue"
|
|
21
24
|
};
|
|
22
25
|
};
|
package/dist/format/messages.js
CHANGED
|
@@ -2,7 +2,7 @@ import { defineMessages } from "../locale/messages.js";
|
|
|
2
2
|
//#region src/format/messages.ts
|
|
3
3
|
const text = defineMessages({
|
|
4
4
|
en: {
|
|
5
|
-
rows: (count) => `${count} rows`,
|
|
5
|
+
rows: (count, one) => `${count} ${one ? "row" : "rows"}`,
|
|
6
6
|
aboutRows: (count) => `about ${count} rows`,
|
|
7
7
|
secondsAgo: (count) => `${count} s ago`,
|
|
8
8
|
minutesAgo: (count) => `${count} min ago`,
|
|
@@ -11,7 +11,7 @@ const text = defineMessages({
|
|
|
11
11
|
justNow: "just now"
|
|
12
12
|
},
|
|
13
13
|
nl: {
|
|
14
|
-
rows: (count) => `${count} rijen`,
|
|
14
|
+
rows: (count, one) => `${count} ${one ? "rij" : "rijen"}`,
|
|
15
15
|
aboutRows: (count) => `ongeveer ${count} rijen`,
|
|
16
16
|
secondsAgo: (count) => `${count} s geleden`,
|
|
17
17
|
minutesAgo: (count) => `${count} min geleden`,
|
package/dist/format/number.js
CHANGED
|
@@ -6,7 +6,7 @@ const formatInt = (value) => grouped().format(value);
|
|
|
6
6
|
const formatSigned = (value) => value > 0 ? `+${grouped().format(value)}` : grouped().format(value).replace("-", "−");
|
|
7
7
|
const formatPercent = (value) => `${value}%`;
|
|
8
8
|
const formatApproxRows = (count) => {
|
|
9
|
-
if (count < 1e4) return text("rows", formatInt(count));
|
|
9
|
+
if (count < 1e4) return text("rows", formatInt(count), count === 1);
|
|
10
10
|
const magnitude = 10 ** (Math.floor(Math.log10(count)) - 3);
|
|
11
11
|
return text("aboutRows", formatInt(Math.round(count / magnitude) * magnitude));
|
|
12
12
|
};
|
|
@@ -6,7 +6,7 @@ import { Notice } from "../app/meta-gate.js";
|
|
|
6
6
|
import { Button } from "../primitives/button.js";
|
|
7
7
|
import { Field } from "../record-view/field.js";
|
|
8
8
|
import { humanize } from "../live/naming.js";
|
|
9
|
-
import { createBody, hasDatabaseDefault, initialDraft, isRequired } from "../app/create/create-values.js";
|
|
9
|
+
import { createBody, hasDatabaseDefault, initialDraft, isRequired, numberDefault } from "../app/create/create-values.js";
|
|
10
10
|
import { ErrorBanner } from "../app/error-banner.js";
|
|
11
11
|
import { FieldEditor } from "../app/field-editor.js";
|
|
12
12
|
import { fieldErrors, fieldMessage } from "../app/field-errors.js";
|
|
@@ -123,7 +123,7 @@ const LoadedForm = ({ form }) => {
|
|
|
123
123
|
hints,
|
|
124
124
|
value: draft[name] ?? (field.type === "boolean" ? false : ""),
|
|
125
125
|
stored: void 0,
|
|
126
|
-
placeholder: hasDatabaseDefault(field) ? text$1("setByDatabase") :
|
|
126
|
+
placeholder: hasDatabaseDefault(field) ? text$1("setByDatabase") : numberDefault(field),
|
|
127
127
|
invalid: Boolean(problem),
|
|
128
128
|
onChange: (next) => change(name, next)
|
|
129
129
|
})
|
|
@@ -3,6 +3,7 @@ import { defineMessages } from "../locale/messages.js";
|
|
|
3
3
|
const text = defineMessages({
|
|
4
4
|
en: {
|
|
5
5
|
organizations: "Organizations",
|
|
6
|
+
manageOrganizations: "Manage organizations",
|
|
6
7
|
organization: "Organization",
|
|
7
8
|
members: "Members",
|
|
8
9
|
invitations: "Invitations",
|
|
@@ -91,6 +92,7 @@ const text = defineMessages({
|
|
|
91
92
|
},
|
|
92
93
|
nl: {
|
|
93
94
|
organizations: "Organisaties",
|
|
95
|
+
manageOrganizations: "Organisaties beheren",
|
|
94
96
|
organization: "Organisatie",
|
|
95
97
|
members: "Leden",
|
|
96
98
|
invitations: "Uitnodigingen",
|
|
@@ -24,7 +24,8 @@ const roleLabel = (roles, name) => roles.find((role) => role.name === name)?.lab
|
|
|
24
24
|
const managesOrganization = (current) => current?.role === "owner" || current?.role === "admin";
|
|
25
25
|
/**
|
|
26
26
|
* The organization pages in the profile menu: every organization of the person, and for the one they work in its
|
|
27
|
-
* details, and for its owners and admins its members and invitations.
|
|
27
|
+
* details, and for its owners and admins its members and invitations. The first is "Manage organizations", as the
|
|
28
|
+
* switcher above it already lists the organizations.
|
|
28
29
|
*/
|
|
29
30
|
const organizationMenuItems = (basePath, path, current) => {
|
|
30
31
|
const item = (id, label, icon, target) => ({
|
|
@@ -35,7 +36,7 @@ const organizationMenuItems = (basePath, path, current) => {
|
|
|
35
36
|
active: path === target
|
|
36
37
|
});
|
|
37
38
|
return [
|
|
38
|
-
item("organizations", text("
|
|
39
|
+
item("organizations", text("manageOrganizations"), Network, organizationsPath),
|
|
39
40
|
...current?.organization ? [item("organization", text("organization"), Building2, organizationPath)] : [],
|
|
40
41
|
...managesOrganization(current) ? [item("members", text("members"), Users, membersPath), item("invitations", text("invitations"), Mail, invitationsPath)] : []
|
|
41
42
|
];
|
package/dist/package.js
CHANGED