@protobase/ui 0.19.0 → 0.19.1
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 +2 -2
- package/dist/app/app.js +12 -9
- package/dist/app/custom-section.js +1 -1
- package/dist/app/displays/custom-display.js +1 -1
- package/dist/app/displays/map-display.js +1 -1
- package/dist/app/displays/record-card.js +1 -1
- package/dist/app/pages/merge-ui.d.ts +1 -1
- package/dist/app/pages/merge-ui.js +12 -2
- package/dist/app/pages/project-ui.d.ts +24 -4
- package/dist/app/pages/project-ui.js +3 -3
- package/dist/app/pages/render-node.js +1 -1
- package/dist/assistant-dock/widget-view.js +1 -1
- package/dist/auth/auth-gate.js +7 -1
- package/dist/auth/auth-provider.js +10 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/package.js +1 -1
- package/dist/plugins/before-sign-in.js +49 -0
- package/dist/plugins/plugin-gate.js +8 -8
- package/dist/plugins/plugin-source.js +32 -0
- package/dist/plugins/plugin-ui.js +5 -3
- package/dist/plugins/use-plugin-loads.js +3 -4
- package/dist/shell/assets/app-DYl-j-Nf.js +66 -0
- package/dist/shell/assets/{index-CLXqLf4p.js → index-Bes91xE3.js} +1 -1
- package/dist/shell/assets/{maplibre-r-WEXdlV.js → maplibre-BDe9GhVw.js} +2 -2
- package/dist/shell/assets/{record-map-Bvls7qdn.js → record-map-CY2LFq1z.js} +1 -1
- package/dist/shell/assets/{records-map-iNBfciYy.js → records-map-B_DHVvmx.js} +1 -1
- package/dist/shell/assets/shared-protobase-ui-Bb2W4Il8.js +1 -0
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-BS7ufaBT.js +0 -66
- package/dist/shell/assets/shared-protobase-ui-9PLdWR62.js +0 -1
package/dist/app/app.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type AppProps = {
|
|
|
25
25
|
assistant?: AssistantClient;
|
|
26
26
|
/** A ready-made client of the runtime endpoint, for stories and tests; used only when `/meta` names one. */
|
|
27
27
|
runtime?: RuntimeClient;
|
|
28
|
-
/** How the plugins
|
|
28
|
+
/** How the plugins are loaded, for stories and tests; the page's `fetch` and `import()` by default. */
|
|
29
29
|
pluginLoader?: PluginLoader;
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
@@ -33,5 +33,5 @@ export type AppProps = {
|
|
|
33
33
|
* then a sidebar from `/meta`, `/:page` composed pages, `/:resource` lists and `/:resource/:key` records. Fills its
|
|
34
34
|
* parent, in the theme this browser chose, light, dark or the system's.
|
|
35
35
|
*/
|
|
36
|
-
export declare const App: ({ auth, workspace, basePath, initialUrl, ...rest }: AppProps) => import("react").JSX.Element;
|
|
36
|
+
export declare const App: ({ auth, workspace, basePath, initialUrl, pluginLoader, ...rest }: AppProps) => import("react").JSX.Element;
|
|
37
37
|
//#endregion
|
package/dist/app/app.js
CHANGED
|
@@ -7,6 +7,9 @@ import { ApiProvider } from "../data/api-provider.js";
|
|
|
7
7
|
import { Router, useRouter } from "./router.js";
|
|
8
8
|
import { useSignInAddress } from "../auth/use-sign-in-address.js";
|
|
9
9
|
import { managesOrganization, organizationMenuItems, organizationPageTitles } from "../organizations/organization-paths.js";
|
|
10
|
+
import { startErrorReporting } from "./error-reporting.js";
|
|
11
|
+
import { CustomBoundary } from "./pages/custom-boundary.js";
|
|
12
|
+
import { PluginSourceProvider } from "../plugins/plugin-source.js";
|
|
10
13
|
import { AuthGate } from "../auth/auth-gate.js";
|
|
11
14
|
import { isAdmin, userToShell } from "../auth/user-to-shell.js";
|
|
12
15
|
import { useRecord } from "../data/use-record.js";
|
|
@@ -14,7 +17,6 @@ import { recordTitle } from "../live/model-helpers.js";
|
|
|
14
17
|
import { humanize } from "../live/naming.js";
|
|
15
18
|
import { ThemeProvider, useTheme } from "../theme/theme-provider.js";
|
|
16
19
|
import { ToastProvider } from "../toasts/toast-provider.js";
|
|
17
|
-
import { startErrorReporting } from "./error-reporting.js";
|
|
18
20
|
import { useProjectUi } from "./pages/project-ui.js";
|
|
19
21
|
import { AssistantButton, AssistantPanel } from "./assistant-shell.js";
|
|
20
22
|
import { bottomBarFromMeta } from "./bottom-bar-from-meta.js";
|
|
@@ -22,7 +24,6 @@ import { breadcrumbFor } from "./breadcrumb-for.js";
|
|
|
22
24
|
import { sourceStatusOf } from "../data/with-sources.js";
|
|
23
25
|
import { MetaGate, Notice, useAdminMeta } from "./meta-gate.js";
|
|
24
26
|
import { useAppAddress } from "./use-app-address.js";
|
|
25
|
-
import { CustomBoundary } from "./pages/custom-boundary.js";
|
|
26
27
|
import { CreatePage } from "./create/create-page.js";
|
|
27
28
|
import { ListPage } from "./list-page.js";
|
|
28
29
|
import { MembersPage } from "../organizations/members-page.js";
|
|
@@ -244,7 +245,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
|
244
245
|
]
|
|
245
246
|
});
|
|
246
247
|
};
|
|
247
|
-
const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, assistant, runtime
|
|
248
|
+
const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, assistant, runtime }) => {
|
|
248
249
|
const { state, session, markSignedOut } = useAuth();
|
|
249
250
|
return /* @__PURE__ */ jsx(ApiProvider, {
|
|
250
251
|
baseUrl,
|
|
@@ -254,7 +255,6 @@ const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, a
|
|
|
254
255
|
onUnauthenticated: markSignedOut,
|
|
255
256
|
children: /* @__PURE__ */ jsx(MetaGate, { children: /* @__PURE__ */ jsx(ToastProvider, { children: /* @__PURE__ */ jsx(PluginGate, {
|
|
256
257
|
ui,
|
|
257
|
-
...pluginLoader && { loader: pluginLoader },
|
|
258
258
|
children: /* @__PURE__ */ jsx(Routes, {
|
|
259
259
|
workspace,
|
|
260
260
|
sidebarMode,
|
|
@@ -269,16 +269,19 @@ const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, a
|
|
|
269
269
|
* then a sidebar from `/meta`, `/:page` composed pages, `/:resource` lists and `/:resource/:key` records. Fills its
|
|
270
270
|
* parent, in the theme this browser chose, light, dark or the system's.
|
|
271
271
|
*/
|
|
272
|
-
const App = ({ auth, workspace, basePath, initialUrl, ...rest }) => /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(AuthProvider, {
|
|
272
|
+
const App = ({ auth, workspace, basePath, initialUrl, pluginLoader, ...rest }) => /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(AuthProvider, {
|
|
273
273
|
session: auth,
|
|
274
274
|
children: /* @__PURE__ */ jsx(Router, {
|
|
275
275
|
basePath,
|
|
276
276
|
initialUrl,
|
|
277
|
-
children: /* @__PURE__ */ jsx(
|
|
278
|
-
|
|
279
|
-
children: /* @__PURE__ */ jsx(
|
|
277
|
+
children: /* @__PURE__ */ jsx(PluginSourceProvider, {
|
|
278
|
+
...pluginLoader && { loader: pluginLoader },
|
|
279
|
+
children: /* @__PURE__ */ jsx(AuthGate, {
|
|
280
280
|
workspace,
|
|
281
|
-
|
|
281
|
+
children: /* @__PURE__ */ jsx(Authenticated, {
|
|
282
|
+
workspace,
|
|
283
|
+
...rest
|
|
284
|
+
})
|
|
282
285
|
})
|
|
283
286
|
})
|
|
284
287
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useProjectUi } from "./pages/project-ui.js";
|
|
2
1
|
import { Problem } from "./pages/problem.js";
|
|
3
2
|
import { CustomBoundary } from "./pages/custom-boundary.js";
|
|
3
|
+
import { useProjectUi } from "./pages/project-ui.js";
|
|
4
4
|
import { Section } from "../record-view/section.js";
|
|
5
5
|
import { SidebarCard } from "../record-view/record-sidebar.js";
|
|
6
6
|
import { RecordScope } from "./pages/record-scope.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useProjectUi } from "../pages/project-ui.js";
|
|
2
1
|
import { Problem } from "../pages/problem.js";
|
|
3
2
|
import { CustomBoundary } from "../pages/custom-boundary.js";
|
|
3
|
+
import { useProjectUi } from "../pages/project-ui.js";
|
|
4
4
|
import { ListDisplayProvider } from "./display-context.js";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { unmarkFieldRefs } from "@protobase/schema/internal";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
2
|
import { Button } from "../../primitives/button.js";
|
|
3
|
+
import { Problem } from "../pages/problem.js";
|
|
3
4
|
import { humanize } from "../../live/naming.js";
|
|
4
5
|
import { mapCards } from "./list-displays.js";
|
|
5
6
|
import { useAdminMeta } from "../meta-gate.js";
|
|
6
7
|
import { ErrorBanner } from "../error-banner.js";
|
|
7
|
-
import { Problem } from "../pages/problem.js";
|
|
8
8
|
import { useDebounced } from "../../lib/use-debounced.js";
|
|
9
9
|
import { RecordCard } from "./record-card.js";
|
|
10
10
|
import { boundsAround, worldArea } from "../map/map-area.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Badge } from "../../primitives/badge.js";
|
|
2
|
+
import { CustomBoundary } from "../pages/custom-boundary.js";
|
|
2
3
|
import { recordKey } from "../../live/model-helpers.js";
|
|
3
4
|
import { humanize } from "../../live/naming.js";
|
|
4
5
|
import { useProjectUi } from "../pages/project-ui.js";
|
|
@@ -6,7 +7,6 @@ import { displayValue } from "../../live/display-value.js";
|
|
|
6
7
|
import { amountParts, cardFields, filledAmount, filledMeta } from "./list-displays.js";
|
|
7
8
|
import { AppLink } from "../use-app-address.js";
|
|
8
9
|
import { shownWidget } from "../field-types/shown-widget.js";
|
|
9
|
-
import { CustomBoundary } from "../pages/custom-boundary.js";
|
|
10
10
|
import { WidgetPart } from "../field-types/widget-part.js";
|
|
11
11
|
import { namedIcon } from "../named-icon.js";
|
|
12
12
|
import { FavoriteButton } from "../saved/favorite-button.js";
|
|
@@ -3,7 +3,7 @@ import { ProjectUi } from "./project-ui.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* One UI config from several, each after the ones it `extends`, the last being the app's own: components, action
|
|
5
5
|
* handlers and field types are all kept, and one name defined by two configs is an error naming both; every shell slot
|
|
6
|
-
* is drawn, the extended configs' first.
|
|
6
|
+
* and `beforeSignIn` component is drawn, the extended configs' first.
|
|
7
7
|
*/
|
|
8
8
|
export declare const mergeUi: (...uis: ProjectUi[]) => ProjectUi;
|
|
9
9
|
//#endregion
|
|
@@ -25,10 +25,18 @@ const slot = (slots) => {
|
|
|
25
25
|
const All = () => slots.map((Slot, index) => /* @__PURE__ */ jsx(Slot, {}, index));
|
|
26
26
|
return All;
|
|
27
27
|
};
|
|
28
|
+
const around = (wrappers) => {
|
|
29
|
+
if (wrappers.length <= 1) return wrappers[0];
|
|
30
|
+
const All = ({ children, ...given }) => wrappers.reduceRight((inner, Wrapper) => /* @__PURE__ */ jsx(Wrapper, {
|
|
31
|
+
...given,
|
|
32
|
+
children: inner
|
|
33
|
+
}), children);
|
|
34
|
+
return All;
|
|
35
|
+
};
|
|
28
36
|
/**
|
|
29
37
|
* One UI config from several, each after the ones it `extends`, the last being the app's own: components, action
|
|
30
38
|
* handlers and field types are all kept, and one name defined by two configs is an error naming both; every shell slot
|
|
31
|
-
* is drawn, the extended configs' first.
|
|
39
|
+
* and `beforeSignIn` component is drawn, the extended configs' first.
|
|
32
40
|
*/
|
|
33
41
|
const mergeUi = (...uis) => {
|
|
34
42
|
const named = uis.flatMap((ui, index) => flatten(ui, ui.name ?? `UI config ${index + 1}`));
|
|
@@ -37,6 +45,7 @@ const mergeUi = (...uis) => {
|
|
|
37
45
|
const fields = union("The field type", named, (ui) => ui.fields);
|
|
38
46
|
const actionSlot = slot(named.flatMap(({ ui }) => ui.shell?.actions ? [ui.shell.actions] : []));
|
|
39
47
|
const rightPanel = slot(named.flatMap(({ ui }) => ui.shell?.rightPanel ? [ui.shell.rightPanel] : []));
|
|
48
|
+
const beforeSignIn = around(named.flatMap(({ ui }) => ui.beforeSignIn ? [ui.beforeSignIn] : []));
|
|
40
49
|
const name = uis.at(-1)?.name;
|
|
41
50
|
return {
|
|
42
51
|
...name && { name },
|
|
@@ -46,7 +55,8 @@ const mergeUi = (...uis) => {
|
|
|
46
55
|
...(actionSlot || rightPanel) && { shell: {
|
|
47
56
|
...actionSlot && { actions: actionSlot },
|
|
48
57
|
...rightPanel && { rightPanel }
|
|
49
|
-
} }
|
|
58
|
+
} },
|
|
59
|
+
...beforeSignIn && { beforeSignIn }
|
|
50
60
|
};
|
|
51
61
|
};
|
|
52
62
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FieldWidget } from "../field-types/field-widget.js";
|
|
2
|
-
import { ComponentType } from "react";
|
|
2
|
+
import { ComponentType, ReactNode } from "react";
|
|
3
3
|
import { Client } from "@protobase/client";
|
|
4
4
|
import { AppAddress } from "@protobase/schema";
|
|
5
5
|
//#region src/app/pages/project-ui.d.ts
|
|
@@ -19,6 +19,21 @@ export type ActionContext = {
|
|
|
19
19
|
navigate: (to: AppAddress) => void;
|
|
20
20
|
};
|
|
21
21
|
export type ActionHandler = (context: ActionContext) => void | Promise<void>;
|
|
22
|
+
/** What a plugin's `beforeSignIn` component gets from the app, drawn around the pages someone signed out sees. */
|
|
23
|
+
export type BeforeSignInProps = {
|
|
24
|
+
/** What the app shows someone signed out: the sign-in page, or the app a visitor browses. */
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* Enters the app with a session something other than the sign-in page started, such as an endpoint that traded a
|
|
28
|
+
* one-time code for the session cookie: reads the session again and shows the app signed in. Resolves `false`, and
|
|
29
|
+
* changes nothing, when there is no session.
|
|
30
|
+
*/
|
|
31
|
+
signedIn: () => Promise<boolean>;
|
|
32
|
+
/** The query parameters of the page's address, such as a one-time code a link carries. */
|
|
33
|
+
params: URLSearchParams;
|
|
34
|
+
/** Takes these query parameters, such as a used one-time code, out of the address, in place of it in the tab's history. */
|
|
35
|
+
removeParams: (names: string[]) => void;
|
|
36
|
+
};
|
|
22
37
|
/**
|
|
23
38
|
* The project's own React code for composed pages: custom components by the name their layout declares
|
|
24
39
|
* (`component('UsageChart')`) or a widget part names, handlers for named actions that have no built-in behaviour,
|
|
@@ -38,6 +53,11 @@ export type ProjectUi = {
|
|
|
38
53
|
actions?: ComponentType;
|
|
39
54
|
rightPanel?: ComponentType;
|
|
40
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Drawn around the pages someone signed out sees, only for a plugin the app loads before sign-in (`beforeSignIn` in
|
|
58
|
+
* `options.plugins`): its code runs for them, and it can sign them in (see `BeforeSignInProps`).
|
|
59
|
+
*/
|
|
60
|
+
beforeSignIn?: ComponentType<BeforeSignInProps>;
|
|
41
61
|
};
|
|
42
62
|
/** A `protobase.ui.tsx` module as `import * as` gives it: components exported by name, the rest as the default export. */
|
|
43
63
|
export type UiModule = {
|
|
@@ -47,9 +67,9 @@ export type UiModule = {
|
|
|
47
67
|
/** Typed identity, for `export default defineUi({ extends, components, actions })` in `protobase.ui.tsx`. */
|
|
48
68
|
export declare const defineUi: (ui: ProjectUi) => ProjectUi;
|
|
49
69
|
/**
|
|
50
|
-
* Typed identity, for `export default definePlugin({ name, components, actions, fields, shell })` in a
|
|
51
|
-
* `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in
|
|
52
|
-
* project's. A project's own protobase.ui.tsx is one too, which `protobase build` builds as the plugin `app`.
|
|
70
|
+
* Typed identity, for `export default definePlugin({ name, components, actions, fields, shell, beforeSignIn })` in a
|
|
71
|
+
* plugin's `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in
|
|
72
|
+
* a project's. A project's own protobase.ui.tsx is one too, which `protobase build` builds as the plugin `app`.
|
|
53
73
|
*/
|
|
54
74
|
export declare const definePlugin: (plugin: ProjectUi) => ProjectUi;
|
|
55
75
|
//#endregion
|
|
@@ -3,9 +3,9 @@ import { createContext, useContext } from "react";
|
|
|
3
3
|
/** Typed identity, for `export default defineUi({ extends, components, actions })` in `protobase.ui.tsx`. */
|
|
4
4
|
const defineUi = (ui) => ui;
|
|
5
5
|
/**
|
|
6
|
-
* Typed identity, for `export default definePlugin({ name, components, actions, fields, shell })` in a
|
|
7
|
-
* `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in
|
|
8
|
-
* project's. A project's own protobase.ui.tsx is one too, which `protobase build` builds as the plugin `app`.
|
|
6
|
+
* Typed identity, for `export default definePlugin({ name, components, actions, fields, shell, beforeSignIn })` in a
|
|
7
|
+
* plugin's `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in
|
|
8
|
+
* a project's. A project's own protobase.ui.tsx is one too, which `protobase build` builds as the plugin `app`.
|
|
9
9
|
*/
|
|
10
10
|
const definePlugin = (plugin) => plugin;
|
|
11
11
|
/** The project's UI, merged with the UI configs it extends and the plugins loaded; `PluginGate` or `ProjectUiProvider` provides it. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useProjectUi } from "./project-ui.js";
|
|
2
1
|
import { Problem } from "./problem.js";
|
|
3
2
|
import { CustomBoundary } from "./custom-boundary.js";
|
|
3
|
+
import { useProjectUi } from "./project-ui.js";
|
|
4
4
|
import { ActionBlock } from "./blocks/action.js";
|
|
5
5
|
import { CardBlock } from "./blocks/card.js";
|
|
6
6
|
import { CardRowBlock } from "./blocks/card-row.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ActionCard } from "../action-card/action-card.js";
|
|
2
1
|
import { reportRenderError } from "../app/error-reporting.js";
|
|
2
|
+
import { ActionCard } from "../action-card/action-card.js";
|
|
3
3
|
import { useProjectUi } from "../app/pages/project-ui.js";
|
|
4
4
|
import { Component } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
package/dist/auth/auth-gate.js
CHANGED
|
@@ -19,6 +19,7 @@ import { TwoFactorPage } from "./two-factor-page.js";
|
|
|
19
19
|
import { useSignInAddress } from "./use-sign-in-address.js";
|
|
20
20
|
import { readInvitationLink, withoutInvitationLink } from "../organizations/invitation-link.js";
|
|
21
21
|
import { InvitationPage } from "../organizations/invitation-page.js";
|
|
22
|
+
import { BeforeSignIn } from "../plugins/before-sign-in.js";
|
|
22
23
|
import { useCallback, useEffect, useState } from "react";
|
|
23
24
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
24
25
|
import { signInPath } from "@protobase/schema";
|
|
@@ -62,9 +63,14 @@ const Redirect = ({ to }) => {
|
|
|
62
63
|
* browse, and the right page otherwise: the step of the sign-in route the address names, or the page an emailed reset
|
|
63
64
|
* link, an invitation link or a staff link opened. Someone signed out without a visitor's browsing goes to the sign-in
|
|
64
65
|
* route, and signing in comes back to the page the route was opened from. An invited person with an account signs in
|
|
65
|
-
* first and comes back to the invitation.
|
|
66
|
+
* first and comes back to the invitation. The plugins the app loads before sign-in draw around what someone signed
|
|
67
|
+
* out sees (see `BeforeSignIn`), while the gate keeps its state.
|
|
66
68
|
*/
|
|
67
69
|
const AuthGate = ({ workspace, children }) => {
|
|
70
|
+
const page = useAuthPage(workspace, children);
|
|
71
|
+
return /* @__PURE__ */ jsx(BeforeSignIn, { children: page });
|
|
72
|
+
};
|
|
73
|
+
const useAuthPage = (workspace, children) => {
|
|
68
74
|
const { state, recheck, passwordReset, signOut, signInProviders, canSignUp, browse } = useAuth();
|
|
69
75
|
const { path, params, basePath, navigate } = useRouter();
|
|
70
76
|
const { address, next, open, link, leave } = useSignInAddress();
|
|
@@ -21,6 +21,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
21
21
|
const [staffSignIn, setStaffSignIn] = useState();
|
|
22
22
|
const [organizations, setOrganizations] = useState();
|
|
23
23
|
const [anonymous, setAnonymous] = useState(false);
|
|
24
|
+
const [beforeSignIn, setBeforeSignIn] = useState([]);
|
|
24
25
|
const signedOutState = useCallback(() => anonymous ? { kind: "visitor" } : { kind: "signed-out" }, [anonymous]);
|
|
25
26
|
const markSignedOut = useCallback(() => setState((current) => current.kind === "signed-in" ? { kind: "signed-out" } : current), []);
|
|
26
27
|
const signedOut = useRef(markSignedOut);
|
|
@@ -54,6 +55,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
54
55
|
setStaffSignIn(status.staffSignIn);
|
|
55
56
|
setOrganizations(status.organizations);
|
|
56
57
|
setAnonymous(Boolean(status.anonymous));
|
|
58
|
+
setBeforeSignIn(status.plugins ?? []);
|
|
57
59
|
if (status.needsAdmin) return setState({ kind: "needs-admin" });
|
|
58
60
|
const user = await session.session();
|
|
59
61
|
return user ? enter(user) : setState({ kind: status.anonymous ? "visitor" : "signed-out" });
|
|
@@ -79,6 +81,13 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
79
81
|
...organizations && { organizations },
|
|
80
82
|
markSignedOut,
|
|
81
83
|
recheck,
|
|
84
|
+
beforeSignIn,
|
|
85
|
+
adoptSession: async () => {
|
|
86
|
+
const user = await session.session();
|
|
87
|
+
if (!user) return false;
|
|
88
|
+
await enter(user);
|
|
89
|
+
return true;
|
|
90
|
+
},
|
|
82
91
|
...anonymous && { browse: () => {
|
|
83
92
|
setState((current) => current.kind === "signed-out" ? { kind: "visitor" } : current);
|
|
84
93
|
} },
|
|
@@ -116,6 +125,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
116
125
|
staffSignIn,
|
|
117
126
|
organizations,
|
|
118
127
|
anonymous,
|
|
128
|
+
beforeSignIn,
|
|
119
129
|
signedOutState,
|
|
120
130
|
markSignedOut,
|
|
121
131
|
recheck,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FieldWidget, FieldWidgetEditorProps, FieldWidgetProps } from "./app/field-types/field-widget.js";
|
|
2
|
-
import { ActionContext, ActionHandler, ProjectUi, definePlugin, defineUi } from "./app/pages/project-ui.js";
|
|
2
|
+
import { ActionContext, ActionHandler, BeforeSignInProps, ProjectUi, definePlugin, defineUi } from "./app/pages/project-ui.js";
|
|
3
3
|
import { PluginInfo, usePlugin } from "./plugins/plugin-context.js";
|
|
4
4
|
import { App, AppProps } from "./app/app.js";
|
|
5
5
|
import "./app/index.js";
|
|
@@ -42,4 +42,4 @@ import { Step, StepList, StepState } from "./step-list/step-list.js";
|
|
|
42
42
|
import { DiffView, DiffViewProps } from "./diff-view/diff-view.js";
|
|
43
43
|
import { DiffLine, diffStats, parseDiff } from "./diff-view/diff.js";
|
|
44
44
|
import { CompactTable, CompactTableProps } from "./compact-table/compact-table.js";
|
|
45
|
-
export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ApiProvider, type ApiProviderProps, App, AppLink, type AppProps, AssistantDock, type AssistantDockProps, type BackendData, type BackendRefresh, type CardAction, type CardTone, ChatMessage, ChatThread, type ChatThreadProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type CurrentAddress, type DiffLine, DiffView, type DiffViewProps, FieldList, type FieldWidget, type FieldWidgetEditorProps, type FieldWidgetProps, type LayoutRecord, type ListAddress, type ListChange, type ListDisplay, type ListDisplayRow, type MetaData, OtpInput, type OtpInputProps, PartView, type PluginInfo, type ProjectUi, type RecordCardProps, RecordLink, ResponsiveDialog, type ResponsiveDialogProps, SidePanel, type SidePanelProps, SignInPrompt, type SignInPromptProps, type Step, StepList, type StepState, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
|
|
45
|
+
export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ApiProvider, type ApiProviderProps, App, AppLink, type AppProps, AssistantDock, type AssistantDockProps, type BackendData, type BackendRefresh, type BeforeSignInProps, type CardAction, type CardTone, ChatMessage, ChatThread, type ChatThreadProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type CurrentAddress, type DiffLine, DiffView, type DiffViewProps, FieldList, type FieldWidget, type FieldWidgetEditorProps, type FieldWidgetProps, type LayoutRecord, type ListAddress, type ListChange, type ListDisplay, type ListDisplayRow, type MetaData, OtpInput, type OtpInputProps, PartView, type PluginInfo, type ProjectUi, type RecordCardProps, RecordLink, ResponsiveDialog, type ResponsiveDialogProps, SidePanel, type SidePanelProps, SignInPrompt, type SignInPromptProps, type Step, StepList, type StepState, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { OtpInput } from "./primitives/otp-input.js";
|
|
2
2
|
import { ApiProvider, useClient, useResourceClient } from "./data/api-provider.js";
|
|
3
|
+
import { uiFromModule } from "./app/pages/ui-from-module.js";
|
|
4
|
+
import { mergeUi } from "./app/pages/merge-ui.js";
|
|
5
|
+
import { usePlugin } from "./plugins/plugin-context.js";
|
|
6
|
+
import { pluginApi, protobaseVersion } from "./plugins/plugin-api.js";
|
|
3
7
|
import { useApiToken } from "./auth/use-api-token.js";
|
|
4
8
|
import { useRecord } from "./data/use-record.js";
|
|
5
9
|
import { ChatMessage } from "./chat/chat-message.js";
|
|
@@ -25,10 +29,6 @@ import { SignInPrompt } from "./auth/sign-in-prompt.js";
|
|
|
25
29
|
import { useListDisplay } from "./app/displays/display-context.js";
|
|
26
30
|
import { useFacets, useHistogram, useSeries } from "./data/use-aggregates.js";
|
|
27
31
|
import { useLayoutRecord } from "./app/pages/record-scope.js";
|
|
28
|
-
import { uiFromModule } from "./app/pages/ui-from-module.js";
|
|
29
|
-
import { mergeUi } from "./app/pages/merge-ui.js";
|
|
30
|
-
import { usePlugin } from "./plugins/plugin-context.js";
|
|
31
|
-
import { pluginApi, protobaseVersion } from "./plugins/plugin-api.js";
|
|
32
32
|
import { useList } from "./data/use-list.js";
|
|
33
33
|
import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
|
|
34
34
|
import { App } from "./app/app.js";
|
package/dist/package.js
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Spinner } from "../primitives/spinner.js";
|
|
2
|
+
import { useAuth } from "../auth/auth-provider.js";
|
|
3
|
+
import { useRouter } from "../app/router.js";
|
|
4
|
+
import { CustomBoundary } from "../app/pages/custom-boundary.js";
|
|
5
|
+
import { mergePlugins } from "./merge-plugins.js";
|
|
6
|
+
import { usePluginSource } from "./plugin-source.js";
|
|
7
|
+
import { usePluginLoads } from "./use-plugin-loads.js";
|
|
8
|
+
import { useCallback, useMemo } from "react";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
//#region src/plugins/before-sign-in.tsx
|
|
11
|
+
/**
|
|
12
|
+
* Loads the plugins the setup status names to everyone, from the app's plugin source, and draws their `beforeSignIn`
|
|
13
|
+
* components around `children` for someone signed out or a visitor, once they have loaded, or failed, or after a wait
|
|
14
|
+
* (see `usePluginLoads`). Signed in, it draws `children` alone, and the plugins stay loaded for the app to merge with
|
|
15
|
+
* the others `/meta` names. A `beforeSignIn` that throws leaves `children` as they are without it.
|
|
16
|
+
*/
|
|
17
|
+
const BeforeSignIn = ({ children }) => {
|
|
18
|
+
const { state, beforeSignIn, adoptSession } = useAuth();
|
|
19
|
+
const { path, params, navigate } = useRouter();
|
|
20
|
+
const { loads, settled } = usePluginLoads(beforeSignIn, usePluginSource());
|
|
21
|
+
const Around = useMemo(() => mergePlugins(void 0, loads).ui.beforeSignIn, [loads]);
|
|
22
|
+
const removeParams = useCallback((names) => {
|
|
23
|
+
const kept = new URLSearchParams(params);
|
|
24
|
+
for (const name of names) kept.delete(name);
|
|
25
|
+
navigate(kept.size > 0 ? `${path}?${kept}` : path, { replace: true });
|
|
26
|
+
}, [
|
|
27
|
+
path,
|
|
28
|
+
params,
|
|
29
|
+
navigate
|
|
30
|
+
]);
|
|
31
|
+
if (state.kind !== "signed-out" && state.kind !== "visitor") return children;
|
|
32
|
+
if (!settled) return /* @__PURE__ */ jsxs("div", {
|
|
33
|
+
className: "flex h-full min-h-48 items-center justify-center gap-2 text-[13px] text-muted-foreground",
|
|
34
|
+
children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
|
|
35
|
+
});
|
|
36
|
+
if (!Around) return children;
|
|
37
|
+
return /* @__PURE__ */ jsx(CustomBoundary, {
|
|
38
|
+
name: "beforeSignIn",
|
|
39
|
+
fallback: children,
|
|
40
|
+
children: /* @__PURE__ */ jsx(Around, {
|
|
41
|
+
signedIn: adoptSession,
|
|
42
|
+
params,
|
|
43
|
+
removeParams,
|
|
44
|
+
children
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { BeforeSignIn };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Spinner } from "../primitives/spinner.js";
|
|
2
|
-
import { ProjectUiContext } from "../app/pages/project-ui.js";
|
|
3
|
-
import { useAdminMeta } from "../app/meta-gate.js";
|
|
4
|
-
import { browserLoader } from "./load-plugin.js";
|
|
5
2
|
import { mergePlugins } from "./merge-plugins.js";
|
|
3
|
+
import { usePluginSource } from "./plugin-source.js";
|
|
6
4
|
import { usePluginLoads } from "./use-plugin-loads.js";
|
|
5
|
+
import { ProjectUiContext } from "../app/pages/project-ui.js";
|
|
6
|
+
import { useAdminMeta } from "../app/meta-gate.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
|
|
@@ -11,13 +11,13 @@ const PluginsOffContext = createContext([]);
|
|
|
11
11
|
/** The plugins `/meta` names that the app did not load, and why. */
|
|
12
12
|
const usePluginsOff = () => useContext(PluginsOffContext);
|
|
13
13
|
/**
|
|
14
|
-
* Loads the plugins `/meta` names and provides the project's UI config with theirs
|
|
15
|
-
* once they have loaded, or failed, or after a wait (see `usePluginLoads`); a plugin
|
|
16
|
-
* components draw what any missing component draws.
|
|
14
|
+
* Loads the plugins `/meta` names, from the app's plugin source, and provides the project's UI config with theirs
|
|
15
|
+
* merged in. It draws its children once they have loaded, or failed, or after a wait (see `usePluginLoads`); a plugin
|
|
16
|
+
* that fails is left out, and its components draw what any missing component draws.
|
|
17
17
|
*/
|
|
18
|
-
const PluginGate = ({ ui,
|
|
18
|
+
const PluginGate = ({ ui, children }) => {
|
|
19
19
|
const { plugins = [] } = useAdminMeta();
|
|
20
|
-
const { loads, settled } = usePluginLoads(plugins,
|
|
20
|
+
const { loads, settled } = usePluginLoads(plugins, usePluginSource());
|
|
21
21
|
const merged = useMemo(() => mergePlugins(ui, loads), [ui, loads]);
|
|
22
22
|
if (!settled) return /* @__PURE__ */ jsxs("div", {
|
|
23
23
|
className: "flex h-full min-h-48 items-center justify-center gap-2 text-[13px] text-muted-foreground",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { browserLoader, loadPlugin } from "./load-plugin.js";
|
|
2
|
+
import { createContext, useContext, useMemo } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/plugins/plugin-source.tsx
|
|
5
|
+
/** A source that loads with `loader` and keeps each plugin's load, so one named before and after sign-in loads once. */
|
|
6
|
+
const createPluginSource = (loader) => {
|
|
7
|
+
const loads = /* @__PURE__ */ new Map();
|
|
8
|
+
return { load: (ref) => {
|
|
9
|
+
const key = JSON.stringify(ref);
|
|
10
|
+
const known = loads.get(key);
|
|
11
|
+
if (known) return known;
|
|
12
|
+
const load = loadPlugin(ref, loader, window.location.href);
|
|
13
|
+
loads.set(key, load);
|
|
14
|
+
return load;
|
|
15
|
+
} };
|
|
16
|
+
};
|
|
17
|
+
const PluginSourceContext = createContext(null);
|
|
18
|
+
/** The app's plugin source, loading with `loader`: the page's `fetch` and `import()` by default. */
|
|
19
|
+
const PluginSourceProvider = ({ loader = browserLoader, children }) => {
|
|
20
|
+
const source = useMemo(() => createPluginSource(loader), [loader]);
|
|
21
|
+
return /* @__PURE__ */ jsx(PluginSourceContext.Provider, {
|
|
22
|
+
value: source,
|
|
23
|
+
children
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
const usePluginSource = () => {
|
|
27
|
+
const source = useContext(PluginSourceContext);
|
|
28
|
+
if (!source) throw new Error("usePluginSource must be used inside PluginSourceProvider");
|
|
29
|
+
return source;
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
export { PluginSourceProvider, createPluginSource, usePluginSource };
|
|
@@ -19,8 +19,8 @@ const fieldWithin = (plugin, name, widget) => ({
|
|
|
19
19
|
});
|
|
20
20
|
/**
|
|
21
21
|
* A plugin's UI config from its module, read as a `protobase.ui.tsx` module and merged with the configs it extends,
|
|
22
|
-
* named after the plugin: its components, field types
|
|
23
|
-
* when the configs it merges define a name twice.
|
|
22
|
+
* named after the plugin: its components, field types, shell slots and `beforeSignIn` draw inside the plugin. Throws
|
|
23
|
+
* `UiConflictError` when the configs it merges define a name twice.
|
|
24
24
|
*/
|
|
25
25
|
const pluginUi = (plugin, module) => {
|
|
26
26
|
const ui = mergeUi({
|
|
@@ -33,12 +33,14 @@ const pluginUi = (plugin, module) => {
|
|
|
33
33
|
...ui.shell.actions && { actions: within(plugin, ui.shell.actions, "shell.actions") },
|
|
34
34
|
...ui.shell.rightPanel && { rightPanel: within(plugin, ui.shell.rightPanel, "shell.rightPanel") }
|
|
35
35
|
};
|
|
36
|
+
const beforeSignIn = ui.beforeSignIn && within(plugin, ui.beforeSignIn, "beforeSignIn");
|
|
36
37
|
return {
|
|
37
38
|
name: plugin.name,
|
|
38
39
|
components,
|
|
39
40
|
...ui.actions && { actions: ui.actions },
|
|
40
41
|
...fields && { fields },
|
|
41
|
-
...shell && { shell }
|
|
42
|
+
...shell && { shell },
|
|
43
|
+
...beforeSignIn && { beforeSignIn }
|
|
42
44
|
};
|
|
43
45
|
};
|
|
44
46
|
//#endregion
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { loadPlugin } from "./load-plugin.js";
|
|
2
1
|
import { useEffect, useState } from "react";
|
|
3
2
|
//#region src/plugins/use-plugin-loads.ts
|
|
4
3
|
/** How long the app waits for its plugins before it draws without the ones still loading, which arrive later. */
|
|
@@ -8,7 +7,7 @@ const none = [];
|
|
|
8
7
|
* The plugins `refs` names, in their order, as they load. `settled` once every one has loaded or failed, or after
|
|
9
8
|
* `pluginWaitMs`, so a slow plugin delays the app only that long. Without plugins it is settled at once.
|
|
10
9
|
*/
|
|
11
|
-
const usePluginLoads = (refs,
|
|
10
|
+
const usePluginLoads = (refs, source) => {
|
|
12
11
|
const key = JSON.stringify(refs);
|
|
13
12
|
const [state, setState] = useState({
|
|
14
13
|
key: "[]",
|
|
@@ -34,7 +33,7 @@ const usePluginLoads = (refs, loader) => {
|
|
|
34
33
|
publish();
|
|
35
34
|
}, pluginWaitMs);
|
|
36
35
|
refs.forEach((ref, index) => {
|
|
37
|
-
|
|
36
|
+
source.load(ref).then((load) => {
|
|
38
37
|
results[index] = load;
|
|
39
38
|
publish();
|
|
40
39
|
});
|
|
@@ -43,7 +42,7 @@ const usePluginLoads = (refs, loader) => {
|
|
|
43
42
|
active = false;
|
|
44
43
|
clearTimeout(timer);
|
|
45
44
|
};
|
|
46
|
-
}, [key,
|
|
45
|
+
}, [key, source]);
|
|
47
46
|
if (refs.length === 0) return {
|
|
48
47
|
loads: none,
|
|
49
48
|
settled: true
|