@protobase/ui 0.21.6 → 0.21.7
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.d.ts +3 -3
- package/dist/app/app.js +32 -13
- package/dist/app/create/create-page.js +4 -4
- package/dist/app/custom-section.js +1 -1
- package/dist/app/displays/board-columns.js +1 -1
- package/dist/app/displays/board-display.js +1 -1
- package/dist/app/displays/list-displays.js +1 -1
- package/dist/app/displays/map-display.js +1 -1
- package/dist/app/displays/record-card.js +2 -2
- package/dist/app/displays/use-board-column.js +1 -1
- package/dist/app/displays/use-board-move.js +1 -1
- package/dist/app/displays/use-map-records.js +1 -1
- package/dist/app/embedded-list/embedded-list.js +3 -3
- package/dist/app/embedded-list/use-embedded-rows.js +1 -1
- package/dist/app/gallery/gallery-section.js +1 -1
- package/dist/app/gallery/lightbox.js +1 -1
- package/dist/app/hand-order/hand-sorted-list.js +3 -3
- package/dist/app/hand-order/use-hand-order.js +2 -2
- package/dist/app/import/import-dialog.js +1 -1
- package/dist/app/list-edit/use-list-edits.js +1 -1
- package/dist/app/list-page.js +4 -4
- package/dist/app/map/map-section.js +1 -1
- package/dist/app/pages/action-inputs/action-inputs-dialog.js +3 -3
- package/dist/app/pages/blocks/card-row.js +1 -1
- package/dist/app/pages/blocks/modal-form.js +3 -3
- package/dist/app/pages/blocks/record-card.js +1 -1
- package/dist/app/pages/field-value.js +1 -1
- package/dist/app/pages/use-run-action.js +1 -1
- package/dist/app/record-lines.js +1 -1
- package/dist/app/record-page.js +5 -5
- 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-dots/related-dots.js +1 -1
- package/dist/app/related-section.js +1 -1
- package/dist/app/relation-picker.js +1 -1
- package/dist/app/search/search-model.js +1 -1
- package/dist/app/searches/saved-search-rows.js +2 -2
- package/dist/app/searches/saved-searches.js +2 -2
- package/dist/app/searches/update-search-dialog.js +1 -1
- package/dist/app/searches/use-save-search.js +1 -1
- package/dist/app/searches/use-saved-searches.js +1 -1
- package/dist/app/use-app-address.js +1 -1
- package/dist/assistant-dock/widget-view.js +1 -1
- package/dist/auth/auth-layout.d.ts +4 -2
- package/dist/auth/auth-layout.js +4 -3
- package/dist/data/api-provider.d.ts +6 -0
- package/dist/data/query-keys.js +3 -1
- package/dist/forms/prove-human.js +24 -0
- package/dist/forms/public-form-page.js +142 -0
- package/dist/forms/use-form-address.js +11 -0
- package/dist/index.js +7 -7
- package/dist/live/columns-from-model.js +1 -1
- package/dist/live/use-filter-config.js +1 -1
- package/dist/package.js +1 -1
- package/dist/plugins/plugin-gate.js +1 -1
- package/dist/shell/assets/app-BbVdHzEY.js +69 -0
- package/dist/shell/assets/{index-BztYGbYc.js → index-CQc-60lQ.js} +1 -1
- package/dist/shell/assets/{maplibre-CVOEAJ62.js → maplibre-BvtgTvfx.js} +1 -1
- package/dist/shell/assets/{markdown-content-Bg8FC_aK.js → markdown-content-CJ8g5iKO.js} +1 -1
- package/dist/shell/assets/{record-map-ChUVZLR6.js → record-map-DSXYVoqB.js} +1 -1
- package/dist/shell/assets/{records-map-BAI2D9VS.js → records-map-cU_FAK7w.js} +1 -1
- package/dist/shell/assets/shared-protobase-ui-Bc0CoTXD.js +1 -0
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +5 -4
- package/dist/shell/assets/app-Dfqcc3td.js +0 -69
- package/dist/shell/assets/shared-protobase-ui-CzT3bJ35.js +0 -1
|
@@ -3,8 +3,10 @@ import { ReactNode } from "react";
|
|
|
3
3
|
export type AuthLayoutProps = {
|
|
4
4
|
title: string;
|
|
5
5
|
description?: string;
|
|
6
|
+
/** How wide the card is: `sm`, the default, for a few short inputs; `md` for a form with longer ones. */
|
|
7
|
+
size?: 'sm' | 'md';
|
|
6
8
|
children: ReactNode;
|
|
7
9
|
};
|
|
8
|
-
/** The frame of the sign-in and first-run pages: logo, title and a card. */
|
|
9
|
-
export declare const AuthLayout: ({ title, description, children }: AuthLayoutProps) => import("react").JSX.Element;
|
|
10
|
+
/** The frame of the sign-in and first-run pages and of public forms: logo, title and a card. */
|
|
11
|
+
export declare const AuthLayout: ({ title, description, size, children }: AuthLayoutProps) => import("react").JSX.Element;
|
|
10
12
|
//#endregion
|
package/dist/auth/auth-layout.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
1
2
|
import { LogoMark } from "../app-shell/logo.js";
|
|
2
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
4
|
//#region src/auth/auth-layout.tsx
|
|
4
|
-
/** The frame of the sign-in and first-run pages: logo, title and a card. */
|
|
5
|
-
const AuthLayout = ({ title, description, children }) => /* @__PURE__ */ jsx("div", {
|
|
5
|
+
/** The frame of the sign-in and first-run pages and of public forms: logo, title and a card. */
|
|
6
|
+
const AuthLayout = ({ title, description, size = "sm", children }) => /* @__PURE__ */ jsx("div", {
|
|
6
7
|
className: "flex min-h-full w-full items-center justify-center bg-surface px-4 py-10",
|
|
7
8
|
children: /* @__PURE__ */ jsxs("div", {
|
|
8
|
-
className: "w-full max-w-sm",
|
|
9
|
+
className: cn("w-full", size === "sm" ? "max-w-sm" : "max-w-lg"),
|
|
9
10
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
10
11
|
className: "mb-6 flex flex-col items-center text-center",
|
|
11
12
|
children: [
|
|
@@ -43,6 +43,9 @@ export declare const useClient: () => {
|
|
|
43
43
|
}) => Promise<import("@protobase/client").MetaResult>;
|
|
44
44
|
invoke: <T = unknown>(name: string, { method, body, path, query, headers }?: import("@protobase/client").InvokeOptions) => Promise<T>;
|
|
45
45
|
upload: (resource: import("@protobase/client").ResourceRef, field: string, file: Blob, { onProgress, signal }?: import("@protobase/client").UploadOptions) => Promise<import("@protobase/client").UploadResult>;
|
|
46
|
+
form: (name: string) => Promise<import("@protobase/client").PublicForm>;
|
|
47
|
+
formChallenge: (name: string) => Promise<import("@protobase/client").FormChallenge>;
|
|
48
|
+
submitForm: (name: string, values: Record<string, unknown>, proof: import("@protobase/client").FormProof) => Promise<void>;
|
|
46
49
|
};
|
|
47
50
|
/**
|
|
48
51
|
* The client of the API that serves `resource`: a source's for a resource the app draws from one, the app's own
|
|
@@ -88,6 +91,9 @@ export declare const useResourceClient: (resource: string) => {
|
|
|
88
91
|
}) => Promise<import("@protobase/client").MetaResult>;
|
|
89
92
|
invoke: <T = unknown>(name: string, { method, body, path, query, headers }?: import("@protobase/client").InvokeOptions) => Promise<T>;
|
|
90
93
|
upload: (resource: import("@protobase/client").ResourceRef, field: string, file: Blob, { onProgress, signal }?: import("@protobase/client").UploadOptions) => Promise<import("@protobase/client").UploadResult>;
|
|
94
|
+
form: (name: string) => Promise<import("@protobase/client").PublicForm>;
|
|
95
|
+
formChallenge: (name: string) => Promise<import("@protobase/client").FormChallenge>;
|
|
96
|
+
submitForm: (name: string, values: Record<string, unknown>, proof: import("@protobase/client").FormProof) => Promise<void>;
|
|
91
97
|
};
|
|
92
98
|
export type ApiProviderProps = {
|
|
93
99
|
/** Defaults to `/api/v1` on the current origin. */
|
package/dist/data/query-keys.js
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { solveChallenge } from "altcha-lib";
|
|
2
|
+
import { deriveKey } from "altcha-lib/algorithms/web/sha";
|
|
3
|
+
//#region src/forms/prove-human.ts
|
|
4
|
+
/**
|
|
5
|
+
* The bot check of a public form, done: fetches a fresh challenge and solves it in this browser, about a second of its
|
|
6
|
+
* work, so a check never expires while someone types.
|
|
7
|
+
*/
|
|
8
|
+
const proveHuman = async (client, name) => {
|
|
9
|
+
const challenge = await client.formChallenge(name);
|
|
10
|
+
const solution = await solveChallenge({
|
|
11
|
+
challenge,
|
|
12
|
+
deriveKey
|
|
13
|
+
});
|
|
14
|
+
if (!solution) throw new Error("The check that you are not a bot took too long; send the form again");
|
|
15
|
+
return {
|
|
16
|
+
challenge,
|
|
17
|
+
solution: {
|
|
18
|
+
counter: solution.counter,
|
|
19
|
+
derivedKey: solution.derivedKey
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { proveHuman };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Spinner } from "../primitives/spinner.js";
|
|
2
|
+
import { Button } from "../primitives/button.js";
|
|
3
|
+
import { keys } from "../data/query-keys.js";
|
|
4
|
+
import { useClient } from "../data/api-provider.js";
|
|
5
|
+
import { AuthLayout } from "../auth/auth-layout.js";
|
|
6
|
+
import { humanize } from "../live/naming.js";
|
|
7
|
+
import { Field } from "../record-view/field.js";
|
|
8
|
+
import { createBody, hasDatabaseDefault, initialDraft, isRequired } from "../app/create/create-values.js";
|
|
9
|
+
import { useDocumentTitle } from "../app/document-title.js";
|
|
10
|
+
import { ErrorBanner } from "../app/error-banner.js";
|
|
11
|
+
import { Notice } from "../app/meta-gate.js";
|
|
12
|
+
import { FieldEditor } from "../app/field-editor.js";
|
|
13
|
+
import { fieldErrors, fieldMessage } from "../app/field-errors.js";
|
|
14
|
+
import { useFocusFirstInvalid } from "../app/use-focus-first-invalid.js";
|
|
15
|
+
import { proveHuman } from "./prove-human.js";
|
|
16
|
+
import { useState } from "react";
|
|
17
|
+
import { isApiError } from "@protobase/client";
|
|
18
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
19
|
+
import { useQuery } from "@tanstack/react-query";
|
|
20
|
+
//#region src/forms/public-form-page.tsx
|
|
21
|
+
/**
|
|
22
|
+
* `/-/forms/<name>`: a public form, for anyone, signed in or not. It sends with a bot check its browser solves, and
|
|
23
|
+
* then shows the form's thanks in its place.
|
|
24
|
+
*/
|
|
25
|
+
const PublicFormPage = ({ name }) => {
|
|
26
|
+
const client = useClient();
|
|
27
|
+
const form = useQuery({
|
|
28
|
+
queryKey: keys.publicForm(name),
|
|
29
|
+
queryFn: () => client.form(name)
|
|
30
|
+
});
|
|
31
|
+
if (form.isPending) return /* @__PURE__ */ jsxs("div", {
|
|
32
|
+
className: "flex h-full min-h-48 items-center justify-center gap-2 text-muted-foreground",
|
|
33
|
+
children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
|
|
34
|
+
});
|
|
35
|
+
if (form.error) {
|
|
36
|
+
if (isApiError(form.error) && form.error.status === 404) return /* @__PURE__ */ jsx(Notice, {
|
|
37
|
+
title: "Not found",
|
|
38
|
+
children: "There is no form at this address."
|
|
39
|
+
});
|
|
40
|
+
return /* @__PURE__ */ jsx(Notice, {
|
|
41
|
+
title: "Cannot reach the API",
|
|
42
|
+
children: isApiError(form.error) ? form.error.message : "The server did not answer. Is it running?"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return /* @__PURE__ */ jsx(LoadedForm, { form: form.data });
|
|
46
|
+
};
|
|
47
|
+
const LoadedForm = ({ form }) => {
|
|
48
|
+
const client = useClient();
|
|
49
|
+
const { resource: model, view } = form;
|
|
50
|
+
const title = form.title ?? view.names?.singular ?? humanize(view.resource);
|
|
51
|
+
useDocumentTitle(title);
|
|
52
|
+
const [draft, setDraft] = useState(() => initialDraft(model, [{
|
|
53
|
+
title,
|
|
54
|
+
fields: form.fields
|
|
55
|
+
}]));
|
|
56
|
+
const [errors, setErrors] = useState({});
|
|
57
|
+
const [failure, setFailure] = useState();
|
|
58
|
+
const [sending, setSending] = useState(false);
|
|
59
|
+
const [sent, setSent] = useState(false);
|
|
60
|
+
const [rejections, setRejections] = useState(0);
|
|
61
|
+
const formRef = useFocusFirstInvalid(rejections);
|
|
62
|
+
if (sent) return /* @__PURE__ */ jsx(AuthLayout, {
|
|
63
|
+
title,
|
|
64
|
+
size: "md",
|
|
65
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
66
|
+
role: "status",
|
|
67
|
+
className: "rounded-md bg-success-soft px-3 py-2 text-xs font-medium text-success-text",
|
|
68
|
+
children: form.thanks
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
const change = (name, value) => {
|
|
72
|
+
setErrors((current) => Object.fromEntries(Object.entries(current).filter(([field]) => field !== name)));
|
|
73
|
+
setDraft((current) => ({
|
|
74
|
+
...current,
|
|
75
|
+
[name]: value
|
|
76
|
+
}));
|
|
77
|
+
};
|
|
78
|
+
const send = async () => {
|
|
79
|
+
setSending(true);
|
|
80
|
+
setFailure(void 0);
|
|
81
|
+
try {
|
|
82
|
+
await client.submitForm(form.name, createBody(model, {}, draft), await proveHuman(client, form.name));
|
|
83
|
+
setSent(true);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const found = fieldErrors(error);
|
|
86
|
+
setErrors(found);
|
|
87
|
+
if (Object.keys(found).length > 0) setRejections((count) => count + 1);
|
|
88
|
+
else setFailure(error);
|
|
89
|
+
} finally {
|
|
90
|
+
setSending(false);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
return /* @__PURE__ */ jsx(AuthLayout, {
|
|
94
|
+
title,
|
|
95
|
+
size: "md",
|
|
96
|
+
children: /* @__PURE__ */ jsxs("form", {
|
|
97
|
+
ref: formRef,
|
|
98
|
+
className: "flex flex-col gap-4",
|
|
99
|
+
onSubmit: (event) => {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
send();
|
|
102
|
+
},
|
|
103
|
+
children: [
|
|
104
|
+
form.fields.map((name) => {
|
|
105
|
+
const field = model.fields[name];
|
|
106
|
+
const hints = view.fields[name];
|
|
107
|
+
const label = hints?.label ?? humanize(name);
|
|
108
|
+
const problem = errors[name];
|
|
109
|
+
return /* @__PURE__ */ jsx(Field, {
|
|
110
|
+
name,
|
|
111
|
+
label,
|
|
112
|
+
help: hints?.help,
|
|
113
|
+
required: isRequired(field),
|
|
114
|
+
error: problem && fieldMessage(field, label, problem),
|
|
115
|
+
children: /* @__PURE__ */ jsx(FieldEditor, {
|
|
116
|
+
field,
|
|
117
|
+
hints,
|
|
118
|
+
value: draft[name] ?? (field.type === "boolean" ? false : ""),
|
|
119
|
+
stored: void 0,
|
|
120
|
+
placeholder: hasDatabaseDefault(field) ? "Set by the database" : void 0,
|
|
121
|
+
invalid: Boolean(problem),
|
|
122
|
+
onChange: (next) => change(name, next)
|
|
123
|
+
})
|
|
124
|
+
}, name);
|
|
125
|
+
}),
|
|
126
|
+
failure !== void 0 && /* @__PURE__ */ jsx(ErrorBanner, {
|
|
127
|
+
title: "Not sent",
|
|
128
|
+
error: failure
|
|
129
|
+
}),
|
|
130
|
+
/* @__PURE__ */ jsx(Button, {
|
|
131
|
+
variant: "primary",
|
|
132
|
+
type: "submit",
|
|
133
|
+
loading: sending,
|
|
134
|
+
className: "min-h-10",
|
|
135
|
+
children: "Send"
|
|
136
|
+
})
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
//#endregion
|
|
142
|
+
export { PublicFormPage };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useRouter } from "../app/router.js";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { readFormAddress } from "@protobase/schema";
|
|
4
|
+
//#region src/forms/use-form-address.ts
|
|
5
|
+
/** The public form the address shows, `/-/forms/<name>`, as logical values; `undefined` on any other page. */
|
|
6
|
+
const useFormAddress = () => {
|
|
7
|
+
const { path } = useRouter();
|
|
8
|
+
return useMemo(() => readFormAddress(path), [path]);
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { useFormAddress };
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,13 @@ import { usePlugin } from "./plugins/plugin-context.js";
|
|
|
16
16
|
import { pluginApi, protobaseVersion } from "./plugins/plugin-api.js";
|
|
17
17
|
import { useApiToken } from "./auth/use-api-token.js";
|
|
18
18
|
import { useRecord } from "./data/use-record.js";
|
|
19
|
+
import { Field } from "./record-view/field.js";
|
|
20
|
+
import { Section } from "./record-view/section.js";
|
|
21
|
+
import { Textarea } from "./primitives/textarea.js";
|
|
22
|
+
import { useMeta } from "./data/use-meta.js";
|
|
23
|
+
import { AppLink, useAppAddress } from "./app/use-app-address.js";
|
|
24
|
+
import { useList } from "./data/use-list.js";
|
|
25
|
+
import { definePlugin, defineUi } from "./app/pages/project-ui.js";
|
|
19
26
|
import { ChatMessage } from "./chat/chat-message.js";
|
|
20
27
|
import { ChatThread } from "./chat/chat-thread.js";
|
|
21
28
|
import { Composer } from "./chat/composer.js";
|
|
@@ -26,19 +33,12 @@ import { CompactTable } from "./compact-table/compact-table.js";
|
|
|
26
33
|
import { diffStats, parseDiff } from "./diff-view/diff.js";
|
|
27
34
|
import { DiffView } from "./diff-view/diff-view.js";
|
|
28
35
|
import { StepList } from "./step-list/step-list.js";
|
|
29
|
-
import { definePlugin, defineUi } from "./app/pages/project-ui.js";
|
|
30
36
|
import { PartView } from "./assistant-dock/part-view.js";
|
|
31
37
|
import { useBackendData } from "./assistant-dock/use-backend-data.js";
|
|
32
38
|
import { AssistantDock } from "./assistant-dock/assistant-dock.js";
|
|
33
39
|
import { useAssistant } from "./assistant-dock/use-assistant.js";
|
|
34
40
|
import { useAssistantClient } from "./assistant-dock/use-assistant-client.js";
|
|
35
|
-
import { useMeta } from "./data/use-meta.js";
|
|
36
41
|
import { ResponsiveDialog } from "./primitives/responsive-dialog.js";
|
|
37
|
-
import { Field } from "./record-view/field.js";
|
|
38
|
-
import { Section } from "./record-view/section.js";
|
|
39
|
-
import { Textarea } from "./primitives/textarea.js";
|
|
40
|
-
import { AppLink, useAppAddress } from "./app/use-app-address.js";
|
|
41
|
-
import { useList } from "./data/use-list.js";
|
|
42
42
|
import { ActionInputError } from "./app/pages/action-inputs/input-errors.js";
|
|
43
43
|
import { useLayoutRecord } from "./app/pages/record-scope.js";
|
|
44
44
|
import { useFacets, useHistogram, useSeries } from "./data/use-aggregates.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { recordTitleField } from "./model-helpers.js";
|
|
2
1
|
import { humanize } from "./naming.js";
|
|
3
2
|
import { badgeWidth, headerWidth } from "../data-grid/content-widths.js";
|
|
4
3
|
import { markdownExcerpt } from "../markdown/markdown-excerpt.js";
|
|
5
4
|
import { isMarkdown } from "../markdown/markdown-hint.js";
|
|
5
|
+
import { recordTitleField } from "./model-helpers.js";
|
|
6
6
|
import { enumLabel } from "@protobase/schema";
|
|
7
7
|
//#region src/live/columns-from-model.ts
|
|
8
8
|
const tones = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { keys } from "../data/query-keys.js";
|
|
2
2
|
import { useClient } from "../data/api-provider.js";
|
|
3
|
-
import { labelFieldOf } from "./model-helpers.js";
|
|
4
3
|
import { humanize } from "./naming.js";
|
|
4
|
+
import { labelFieldOf } from "./model-helpers.js";
|
|
5
5
|
import { fetchLabels } from "./relation-labels.js";
|
|
6
6
|
import { setRange } from "../filter-panel/filter-state.js";
|
|
7
7
|
import { stateToText } from "./filter-string.js";
|
package/dist/package.js
CHANGED
|
@@ -2,8 +2,8 @@ import { Spinner } from "../primitives/spinner.js";
|
|
|
2
2
|
import { mergePlugins } from "./merge-plugins.js";
|
|
3
3
|
import { usePluginSource } from "./plugin-source.js";
|
|
4
4
|
import { usePluginLoads } from "./use-plugin-loads.js";
|
|
5
|
-
import { ProjectUiContext } from "../app/pages/project-ui.js";
|
|
6
5
|
import { useAdminMeta } from "../app/meta-gate.js";
|
|
6
|
+
import { ProjectUiContext } from "../app/pages/project-ui.js";
|
|
7
7
|
import { createContext, useContext, useMemo } from "react";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
//#region src/plugins/plugin-gate.tsx
|