@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.
Files changed (33) hide show
  1. package/dist/app/app.d.ts +2 -2
  2. package/dist/app/app.js +12 -9
  3. package/dist/app/custom-section.js +1 -1
  4. package/dist/app/displays/custom-display.js +1 -1
  5. package/dist/app/displays/map-display.js +1 -1
  6. package/dist/app/displays/record-card.js +1 -1
  7. package/dist/app/pages/merge-ui.d.ts +1 -1
  8. package/dist/app/pages/merge-ui.js +12 -2
  9. package/dist/app/pages/project-ui.d.ts +24 -4
  10. package/dist/app/pages/project-ui.js +3 -3
  11. package/dist/app/pages/render-node.js +1 -1
  12. package/dist/assistant-dock/widget-view.js +1 -1
  13. package/dist/auth/auth-gate.js +7 -1
  14. package/dist/auth/auth-provider.js +10 -0
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +4 -4
  17. package/dist/package.js +1 -1
  18. package/dist/plugins/before-sign-in.js +49 -0
  19. package/dist/plugins/plugin-gate.js +8 -8
  20. package/dist/plugins/plugin-source.js +32 -0
  21. package/dist/plugins/plugin-ui.js +5 -3
  22. package/dist/plugins/use-plugin-loads.js +3 -4
  23. package/dist/shell/assets/app-DYl-j-Nf.js +66 -0
  24. package/dist/shell/assets/{index-CLXqLf4p.js → index-Bes91xE3.js} +1 -1
  25. package/dist/shell/assets/{maplibre-r-WEXdlV.js → maplibre-BDe9GhVw.js} +2 -2
  26. package/dist/shell/assets/{record-map-Bvls7qdn.js → record-map-CY2LFq1z.js} +1 -1
  27. package/dist/shell/assets/{records-map-iNBfciYy.js → records-map-B_DHVvmx.js} +1 -1
  28. package/dist/shell/assets/shared-protobase-ui-Bb2W4Il8.js +1 -0
  29. package/dist/shell/index.html +3 -3
  30. package/dist/shell/protobase.shell.json +1 -1
  31. package/package.json +4 -4
  32. package/dist/shell/assets/app-BS7ufaBT.js +0 -66
  33. 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 `/meta` names are loaded, for stories and tests; the page's `fetch` and `import()` by default. */
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, pluginLoader }) => {
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(AuthGate, {
278
- workspace,
279
- children: /* @__PURE__ */ jsx(Authenticated, {
277
+ children: /* @__PURE__ */ jsx(PluginSourceProvider, {
278
+ ...pluginLoader && { loader: pluginLoader },
279
+ children: /* @__PURE__ */ jsx(AuthGate, {
280
280
  workspace,
281
- ...rest
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 plugin's
51
- * `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in a
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 plugin's
7
- * `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in a
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";
@@ -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
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.19.0";
2
+ var version = "0.19.1";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -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 merged in. It draws its children
15
- * once they have loaded, or failed, or after a wait (see `usePluginLoads`); a plugin that fails is left out, and its
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, loader = browserLoader, children }) => {
18
+ const PluginGate = ({ ui, children }) => {
19
19
  const { plugins = [] } = useAdminMeta();
20
- const { loads, settled } = usePluginLoads(plugins, loader);
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 and shell slots draw inside the plugin. Throws `UiConflictError`
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, loader) => {
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
- loadPlugin(ref, loader, window.location.href).then((load) => {
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, loader]);
45
+ }, [key, source]);
47
46
  if (refs.length === 0) return {
48
47
  loads: none,
49
48
  settled: true