@protobase/ui 0.21.6 → 0.21.8
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/comments/comment-body.js +13 -0
- package/dist/app/comments/comment-composer.js +149 -0
- package/dist/app/comments/comments-section.js +78 -0
- package/dist/app/comments/mention-text.js +45 -0
- package/dist/app/comments/use-mentionable.js +18 -0
- package/dist/app/comments/use-post-comment.js +15 -0
- package/dist/app/comments/use-record-comments.js +24 -0
- package/dist/app/create/create-page.js +4 -4
- package/dist/app/current-address.d.ts +4 -3
- package/dist/app/current-address.js +1 -0
- 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/calendar-controls.js +69 -0
- package/dist/app/displays/calendar-day.js +33 -0
- package/dist/app/displays/calendar-display.js +174 -0
- package/dist/app/displays/calendar-dnd.js +27 -0
- package/dist/app/displays/calendar-event.js +72 -0
- package/dist/app/displays/calendar-range.js +125 -0
- package/dist/app/displays/list-displays.js +8 -2
- 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-calendar-move.js +68 -0
- package/dist/app/displays/use-calendar-rows.js +35 -0
- package/dist/app/displays/use-map-records.js +1 -1
- package/dist/app/embedded-list/embedded-list.js +26 -22
- package/dist/app/embedded-list/embedded-row-targets.js +65 -0
- 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 +20 -9
- 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 +9 -7
- package/dist/app/record-lines.js +1 -1
- package/dist/app/record-page.js +29 -15
- 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-opens.js +22 -0
- package/dist/app/related-section.js +5 -2
- 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.d.ts +3 -1
- package/dist/app/use-app-address.js +9 -10
- 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/auth/user-to-shell.js +3 -1
- package/dist/data/api-provider.d.ts +26 -0
- package/dist/data/query-keys.js +18 -1
- package/dist/data-grid/row-menu.js +8 -1
- package/dist/format/date.js +27 -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/primitives/segmented.js +8 -3
- package/dist/primitives/textarea.d.ts +4 -3
- package/dist/primitives/textarea.js +9 -4
- package/dist/shell/assets/app-C01lazBK.js +69 -0
- package/dist/shell/assets/{export-worker-CVd5saLq.js → export-worker-BnOpS_4Q.js} +1 -1
- package/dist/shell/assets/index-CBGigFxb.css +2 -0
- package/dist/shell/assets/{index-BztYGbYc.js → index-xN6_oZU2.js} +1 -1
- package/dist/shell/assets/{maplibre-CVOEAJ62.js → maplibre-Ba9RapjY.js} +1 -1
- package/dist/shell/assets/{markdown-content-Bg8FC_aK.js → markdown-content-BUxrbovf.js} +1 -1
- package/dist/shell/assets/{record-map-ChUVZLR6.js → record-map-14zotZ2s.js} +1 -1
- package/dist/shell/assets/{records-map-BAI2D9VS.js → records-map-wi-0pXqF.js} +1 -1
- package/dist/shell/assets/shared-protobase-ui-BVwhKZk9.js +1 -0
- package/dist/shell/index.html +4 -4
- 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/index-C1fzTv_X.css +0 -2
- package/dist/shell/assets/shared-protobase-ui-CzT3bJ35.js +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Skeleton } from "../primitives/skeleton.js";
|
|
2
2
|
import { keys } from "../data/query-keys.js";
|
|
3
3
|
import { useClient } from "../data/api-provider.js";
|
|
4
|
-
import { useAdminMeta, usePermissions } from "./meta-gate.js";
|
|
5
4
|
import { Section } from "../record-view/section.js";
|
|
6
5
|
import { ErrorBanner } from "./error-banner.js";
|
|
7
6
|
import { listColumnNames } from "../live/columns-from-model.js";
|
|
7
|
+
import { useAdminMeta, usePermissions } from "./meta-gate.js";
|
|
8
8
|
import { searchAll } from "../live/search-all.js";
|
|
9
|
+
import { relatedOpens } from "./related-opens.js";
|
|
9
10
|
import { EmbeddedDisplaySwitch, EmbeddedList } from "./embedded-list/embedded-list.js";
|
|
10
11
|
import { useEmbeddedDisplay } from "./embedded-list/use-embedded-display.js";
|
|
11
12
|
import { relatedHandOrder } from "./hand-order/hand-order.js";
|
|
@@ -82,6 +83,7 @@ const RelatedSection = ({ item, recordKey, owner }) => {
|
|
|
82
83
|
const permissions = usePermissions(item.resource);
|
|
83
84
|
const [sorting, setSorting] = useState(false);
|
|
84
85
|
if (!model) return null;
|
|
86
|
+
const opens = relatedOpens(item, model, resources);
|
|
85
87
|
const columns = item.columns ?? listColumnNames(model, view).filter((name) => name !== item.field);
|
|
86
88
|
const handOrder = relatedHandOrder(item, model, permissions);
|
|
87
89
|
const displaySwitch = /* @__PURE__ */ jsx(EmbeddedDisplaySwitch, {
|
|
@@ -124,7 +126,8 @@ const RelatedSection = ({ item, recordKey, owner }) => {
|
|
|
124
126
|
columns,
|
|
125
127
|
limit: item.limit ?? 10,
|
|
126
128
|
display,
|
|
127
|
-
empty: item.empty
|
|
129
|
+
empty: item.empty,
|
|
130
|
+
opens
|
|
128
131
|
}, match.filter)
|
|
129
132
|
});
|
|
130
133
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { Input } from "../primitives/input.js";
|
|
3
|
-
import { labelFieldOf } from "../live/model-helpers.js";
|
|
4
3
|
import { humanize } from "../live/naming.js";
|
|
4
|
+
import { labelFieldOf } from "../live/model-helpers.js";
|
|
5
5
|
import { useList } from "../data/use-list.js";
|
|
6
6
|
import { relationOptionsQuery, titleMatches } from "./relation-options.js";
|
|
7
7
|
import { useCallback, useEffect, useId, useRef, useState } from "react";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { recordKey, recordTitleField } from "../../live/model-helpers.js";
|
|
2
1
|
import { humanize } from "../../live/naming.js";
|
|
2
|
+
import { recordKey, recordTitleField } from "../../live/model-helpers.js";
|
|
3
3
|
import { appPath, createWhere, enumLabel, printFilter } from "@protobase/schema";
|
|
4
4
|
import { encodeKey, matchedSearchField } from "@protobase/schema/internal";
|
|
5
5
|
//#region src/app/search/search-model.ts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
2
|
-
import { recordKey } from "../../live/model-helpers.js";
|
|
3
2
|
import { humanize } from "../../live/naming.js";
|
|
4
|
-
import { useAdminMeta } from "../meta-gate.js";
|
|
5
3
|
import { ErrorBanner } from "../error-banner.js";
|
|
4
|
+
import { recordKey } from "../../live/model-helpers.js";
|
|
5
|
+
import { useAdminMeta } from "../meta-gate.js";
|
|
6
6
|
import { AppLink } from "../use-app-address.js";
|
|
7
7
|
import { InlineFilterChips } from "./filter-chip-list.js";
|
|
8
8
|
import { RecordRowBody } from "../record-row/record-row-body.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
2
2
|
import { PageHeader } from "../../app-shell/page-header.js";
|
|
3
|
-
import { recordKey } from "../../live/model-helpers.js";
|
|
4
3
|
import { humanize } from "../../live/naming.js";
|
|
5
|
-
import { Notice, useAdminMeta, usePermissions } from "../meta-gate.js";
|
|
6
4
|
import { ErrorBanner } from "../error-banner.js";
|
|
5
|
+
import { recordKey } from "../../live/model-helpers.js";
|
|
6
|
+
import { Notice, useAdminMeta, usePermissions } from "../meta-gate.js";
|
|
7
7
|
import { AppLink } from "../use-app-address.js";
|
|
8
8
|
import { can } from "../permissions.js";
|
|
9
9
|
import { useSavedSearches } from "./use-saved-searches.js";
|
|
@@ -2,8 +2,8 @@ import { Skeleton } from "../../primitives/skeleton.js";
|
|
|
2
2
|
import { Button } from "../../primitives/button.js";
|
|
3
3
|
import { useClient } from "../../data/api-provider.js";
|
|
4
4
|
import { humanize } from "../../live/naming.js";
|
|
5
|
-
import { ResponsiveDialog } from "../../primitives/responsive-dialog.js";
|
|
6
5
|
import { defaultOrderBy } from "../list-order.js";
|
|
6
|
+
import { ResponsiveDialog } from "../../primitives/responsive-dialog.js";
|
|
7
7
|
import { countQuery } from "../pages/use-layout-data.js";
|
|
8
8
|
import { useFilterChips } from "./use-filter-chips.js";
|
|
9
9
|
import { EmbeddedList } from "../embedded-list/embedded-list.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useClient } from "../../data/api-provider.js";
|
|
2
|
-
import { recordKey } from "../../live/model-helpers.js";
|
|
3
2
|
import { humanize } from "../../live/naming.js";
|
|
4
3
|
import { useToast } from "../../toasts/toast-provider.js";
|
|
4
|
+
import { recordKey } from "../../live/model-helpers.js";
|
|
5
5
|
import { useAdminMeta } from "../meta-gate.js";
|
|
6
6
|
import { useAppAddress } from "../use-app-address.js";
|
|
7
7
|
import { can } from "../permissions.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { keys } from "../../data/query-keys.js";
|
|
2
2
|
import { useClient } from "../../data/api-provider.js";
|
|
3
|
-
import { useAdminMeta } from "../meta-gate.js";
|
|
4
3
|
import { defaultOrderBy } from "../list-order.js";
|
|
4
|
+
import { useAdminMeta } from "../meta-gate.js";
|
|
5
5
|
import { useQuery } from "@tanstack/react-query";
|
|
6
6
|
//#region src/app/searches/use-saved-searches.ts
|
|
7
7
|
/** Where the user's saved searches of `resource` are cached; a list key, so a write to the resource refreshes it. */
|
|
@@ -3,13 +3,15 @@ import { AnchorHTMLAttributes } from "react";
|
|
|
3
3
|
import { AppAddress } from "@protobase/schema";
|
|
4
4
|
//#region src/app/use-app-address.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* The app's address as logical values, and the ways to change it: `set` changes the list in place, `
|
|
6
|
+
* The app's address as logical values, and the ways to change it: `set` changes the list in place, `listTo` is the
|
|
7
|
+
* list after a change for a link to it, `go` moves to
|
|
7
8
|
* another address, `href` is an address for an `<a href>` and `open` follows such an href. Every component reads and
|
|
8
9
|
* writes the address through this hook, never the query string or a path of its own.
|
|
9
10
|
*/
|
|
10
11
|
export declare const useAppAddress: () => {
|
|
11
12
|
address: CurrentAddress | undefined;
|
|
12
13
|
set: (change: ListChange) => void;
|
|
14
|
+
listTo: (change: ListChange) => AppAddress;
|
|
13
15
|
go: (to: AppAddress, options?: {
|
|
14
16
|
replace?: boolean;
|
|
15
17
|
}) => void;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { useIsXs } from "../lib/use-media-query.js";
|
|
2
2
|
import { Link, useRouter } from "./router.js";
|
|
3
|
-
import { useAdminMeta } from "./meta-gate.js";
|
|
4
3
|
import { changedList, currentAddress, listDefaults, listedResource } from "./current-address.js";
|
|
4
|
+
import { useAdminMeta } from "./meta-gate.js";
|
|
5
5
|
import { useCallback, useMemo } from "react";
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
import { appPath, readAppPath } from "@protobase/schema";
|
|
8
8
|
//#region src/app/use-app-address.tsx
|
|
9
9
|
/**
|
|
10
|
-
* The app's address as logical values, and the ways to change it: `set` changes the list in place, `
|
|
10
|
+
* The app's address as logical values, and the ways to change it: `set` changes the list in place, `listTo` is the
|
|
11
|
+
* list after a change for a link to it, `go` moves to
|
|
11
12
|
* another address, `href` is an address for an `<a href>` and `open` follows such an href. Every component reads and
|
|
12
13
|
* writes the address through this hook, never the query string or a path of its own.
|
|
13
14
|
*/
|
|
@@ -32,16 +33,14 @@ const useAppAddress = () => {
|
|
|
32
33
|
]);
|
|
33
34
|
const address = useMemo(() => currentAddress(read, defaults), [read, defaults]);
|
|
34
35
|
const go = useCallback((to, options) => navigate(appPath(to), options), [navigate]);
|
|
36
|
+
const listTo = useCallback((change) => {
|
|
37
|
+
if (read?.kind !== "list") throw new Error("listTo and set change the list the app shows; use go to move to another address");
|
|
38
|
+
return changedList(read, change, defaults(read.resource));
|
|
39
|
+
}, [read, defaults]);
|
|
35
40
|
return {
|
|
36
41
|
address,
|
|
37
|
-
set: useCallback((change) => {
|
|
38
|
-
|
|
39
|
-
go(changedList(read, change, defaults(read.resource)), { replace: true });
|
|
40
|
-
}, [
|
|
41
|
-
read,
|
|
42
|
-
defaults,
|
|
43
|
-
go
|
|
44
|
-
]),
|
|
42
|
+
set: useCallback((change) => go(listTo(change), { replace: true }), [listTo, go]),
|
|
43
|
+
listTo,
|
|
45
44
|
go,
|
|
46
45
|
href: useCallback((to) => appPath(to, basePath), [basePath]),
|
|
47
46
|
open: useCallback((target) => navigate(target.startsWith(`${basePath}/`) ? target.slice(basePath.length) : target), [basePath, navigate])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { reportRenderError } from "../app/error-reporting.js";
|
|
2
|
-
import { ActionCard } from "../action-card/action-card.js";
|
|
3
2
|
import { useProjectUi } from "../app/pages/project-ui.js";
|
|
3
|
+
import { ActionCard } from "../action-card/action-card.js";
|
|
4
4
|
import { Component } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
//#region src/assistant-dock/widget-view.tsx
|
|
@@ -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: [
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
//#region src/auth/user-to-shell.ts
|
|
2
|
+
/** The hue of someone's avatar, the same for the same id. */
|
|
2
3
|
const hueOf = (text) => [...text].reduce((sum, char) => (sum * 31 + char.charCodeAt(0)) % 360, 7);
|
|
4
|
+
/** The initials of someone's avatar: the first letters of the first two words of their name. */
|
|
3
5
|
const initialsOf = (name) => name.split(/[\s@.]+/).filter(Boolean).slice(0, 2).map((part) => part[0].toUpperCase()).join("");
|
|
4
6
|
const rolesOf = (user) => (user.role ?? "user").split(",").map((role) => role.trim());
|
|
5
7
|
/** The user holds the `admin` role, among others perhaps. */
|
|
@@ -18,4 +20,4 @@ const userToShell = (user) => {
|
|
|
18
20
|
};
|
|
19
21
|
};
|
|
20
22
|
//#endregion
|
|
21
|
-
export { isAdmin, userToShell };
|
|
23
|
+
export { hueOf, initialsOf, isAdmin, userToShell };
|
|
@@ -15,11 +15,21 @@ export declare const useClient: () => {
|
|
|
15
15
|
etag?: string;
|
|
16
16
|
}) => Promise<void>;
|
|
17
17
|
undelete: <R extends import("@protobase/client").ResourceRef>(resource: R, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[]) => Promise<import("@protobase/client").Stored<import("@protobase/client").RecordOf<R>>>;
|
|
18
|
+
generate: <R extends import("@protobase/client").ResourceRef>(resource: R, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], action: string) => Promise<import("@protobase/client").Stored<import("@protobase/client").RecordOf<R>>>;
|
|
18
19
|
reveal: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], field: string) => Promise<unknown>;
|
|
19
20
|
history: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], { pageSize, pageToken }?: {
|
|
20
21
|
pageSize?: number;
|
|
21
22
|
pageToken?: string;
|
|
22
23
|
}) => Promise<import("@protobase/client").HistoryPage>;
|
|
24
|
+
comments: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], { pageSize, pageToken }?: {
|
|
25
|
+
pageSize?: number;
|
|
26
|
+
pageToken?: string;
|
|
27
|
+
}) => Promise<import("@protobase/client").CommentPage>;
|
|
28
|
+
comment: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], { body, mentions }: {
|
|
29
|
+
body: string;
|
|
30
|
+
mentions?: string[];
|
|
31
|
+
}) => Promise<import("@protobase/client").RecordComment>;
|
|
32
|
+
mentionable: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], query: string) => Promise<import("@protobase/client").MentionableUser[]>;
|
|
23
33
|
batchWrite: (ops: import("@protobase/client").BatchOp[]) => Promise<import("@protobase/client").BatchResult>;
|
|
24
34
|
facets: (resource: import("@protobase/client").ResourceRef, field: string, o?: {
|
|
25
35
|
filter?: import("@protobase/client").FilterInput;
|
|
@@ -43,6 +53,9 @@ export declare const useClient: () => {
|
|
|
43
53
|
}) => Promise<import("@protobase/client").MetaResult>;
|
|
44
54
|
invoke: <T = unknown>(name: string, { method, body, path, query, headers }?: import("@protobase/client").InvokeOptions) => Promise<T>;
|
|
45
55
|
upload: (resource: import("@protobase/client").ResourceRef, field: string, file: Blob, { onProgress, signal }?: import("@protobase/client").UploadOptions) => Promise<import("@protobase/client").UploadResult>;
|
|
56
|
+
form: (name: string) => Promise<import("@protobase/client").PublicForm>;
|
|
57
|
+
formChallenge: (name: string) => Promise<import("@protobase/client").FormChallenge>;
|
|
58
|
+
submitForm: (name: string, values: Record<string, unknown>, proof: import("@protobase/client").FormProof) => Promise<void>;
|
|
46
59
|
};
|
|
47
60
|
/**
|
|
48
61
|
* The client of the API that serves `resource`: a source's for a resource the app draws from one, the app's own
|
|
@@ -60,11 +73,21 @@ export declare const useResourceClient: (resource: string) => {
|
|
|
60
73
|
etag?: string;
|
|
61
74
|
}) => Promise<void>;
|
|
62
75
|
undelete: <R extends import("@protobase/client").ResourceRef>(resource: R, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[]) => Promise<import("@protobase/client").Stored<import("@protobase/client").RecordOf<R>>>;
|
|
76
|
+
generate: <R extends import("@protobase/client").ResourceRef>(resource: R, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], action: string) => Promise<import("@protobase/client").Stored<import("@protobase/client").RecordOf<R>>>;
|
|
63
77
|
reveal: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], field: string) => Promise<unknown>;
|
|
64
78
|
history: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], { pageSize, pageToken }?: {
|
|
65
79
|
pageSize?: number;
|
|
66
80
|
pageToken?: string;
|
|
67
81
|
}) => Promise<import("@protobase/client").HistoryPage>;
|
|
82
|
+
comments: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], { pageSize, pageToken }?: {
|
|
83
|
+
pageSize?: number;
|
|
84
|
+
pageToken?: string;
|
|
85
|
+
}) => Promise<import("@protobase/client").CommentPage>;
|
|
86
|
+
comment: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], { body, mentions }: {
|
|
87
|
+
body: string;
|
|
88
|
+
mentions?: string[];
|
|
89
|
+
}) => Promise<import("@protobase/client").RecordComment>;
|
|
90
|
+
mentionable: (resource: import("@protobase/client").ResourceRef, key: import("@protobase/schema").KeyValue | readonly import("@protobase/schema").KeyValue[], query: string) => Promise<import("@protobase/client").MentionableUser[]>;
|
|
68
91
|
batchWrite: (ops: import("@protobase/client").BatchOp[]) => Promise<import("@protobase/client").BatchResult>;
|
|
69
92
|
facets: (resource: import("@protobase/client").ResourceRef, field: string, o?: {
|
|
70
93
|
filter?: import("@protobase/client").FilterInput;
|
|
@@ -88,6 +111,9 @@ export declare const useResourceClient: (resource: string) => {
|
|
|
88
111
|
}) => Promise<import("@protobase/client").MetaResult>;
|
|
89
112
|
invoke: <T = unknown>(name: string, { method, body, path, query, headers }?: import("@protobase/client").InvokeOptions) => Promise<T>;
|
|
90
113
|
upload: (resource: import("@protobase/client").ResourceRef, field: string, file: Blob, { onProgress, signal }?: import("@protobase/client").UploadOptions) => Promise<import("@protobase/client").UploadResult>;
|
|
114
|
+
form: (name: string) => Promise<import("@protobase/client").PublicForm>;
|
|
115
|
+
formChallenge: (name: string) => Promise<import("@protobase/client").FormChallenge>;
|
|
116
|
+
submitForm: (name: string, values: Record<string, unknown>, proof: import("@protobase/client").FormProof) => Promise<void>;
|
|
91
117
|
};
|
|
92
118
|
export type ApiProviderProps = {
|
|
93
119
|
/** Defaults to `/api/v1` on the current origin. */
|
package/dist/data/query-keys.js
CHANGED
|
@@ -66,6 +66,21 @@ const keys = {
|
|
|
66
66
|
"history",
|
|
67
67
|
pageSize
|
|
68
68
|
],
|
|
69
|
+
/** A record's comments; starts with `record`, so refreshing the record refreshes them too. */
|
|
70
|
+
comments: (resource, key) => [
|
|
71
|
+
"record",
|
|
72
|
+
resource,
|
|
73
|
+
key,
|
|
74
|
+
"comments"
|
|
75
|
+
],
|
|
76
|
+
/** The people a comment on a record may mention whose name contains `query`. */
|
|
77
|
+
mentionable: (resource, key, query) => [
|
|
78
|
+
"record",
|
|
79
|
+
resource,
|
|
80
|
+
key,
|
|
81
|
+
"mentionable",
|
|
82
|
+
query
|
|
83
|
+
],
|
|
69
84
|
facets: (resource, field, filter) => [
|
|
70
85
|
"facets",
|
|
71
86
|
resource,
|
|
@@ -134,7 +149,9 @@ const keys = {
|
|
|
134
149
|
resource,
|
|
135
150
|
key,
|
|
136
151
|
path
|
|
137
|
-
]
|
|
152
|
+
],
|
|
153
|
+
/** A public form, as `/-/forms/<name>` draws it. */
|
|
154
|
+
publicForm: (name) => ["public-form", name]
|
|
138
155
|
};
|
|
139
156
|
//#endregion
|
|
140
157
|
export { keys };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { RouteLink } from "../primitives/route-link.js";
|
|
2
3
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
4
|
import { MoreHorizontal } from "lucide-react";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -70,7 +71,13 @@ const RowMenu = ({ row, actions }) => {
|
|
|
70
71
|
className: "fixed z-50 min-w-36 rounded-md border bg-background py-1 shadow-pop",
|
|
71
72
|
children: actions.map((action) => /* @__PURE__ */ jsx("li", {
|
|
72
73
|
role: "none",
|
|
73
|
-
children: /* @__PURE__ */ jsx(
|
|
74
|
+
children: action.link ? /* @__PURE__ */ jsx(RouteLink, {
|
|
75
|
+
role: "menuitem",
|
|
76
|
+
to: action.link(row),
|
|
77
|
+
onClick: () => setOpen(false),
|
|
78
|
+
className: cn("block w-full px-3 py-1.5 text-left hover:bg-muted", action.destructive && "text-danger-text"),
|
|
79
|
+
children: action.label
|
|
80
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
74
81
|
type: "button",
|
|
75
82
|
role: "menuitem",
|
|
76
83
|
className: cn("block w-full px-3 py-1.5 text-left hover:bg-muted", action.destructive && "text-danger-text"),
|
package/dist/format/date.js
CHANGED
|
@@ -16,9 +16,35 @@ const time = new Intl.DateTimeFormat("en-GB", {
|
|
|
16
16
|
hour12: false,
|
|
17
17
|
timeZone: "UTC"
|
|
18
18
|
});
|
|
19
|
+
const month = new Intl.DateTimeFormat("en-GB", {
|
|
20
|
+
month: "long",
|
|
21
|
+
year: "numeric",
|
|
22
|
+
timeZone: "UTC"
|
|
23
|
+
});
|
|
24
|
+
const weekday = new Intl.DateTimeFormat("en-GB", {
|
|
25
|
+
weekday: "short",
|
|
26
|
+
timeZone: "UTC"
|
|
27
|
+
});
|
|
28
|
+
const weekdayDate = new Intl.DateTimeFormat("en-GB", {
|
|
29
|
+
weekday: "long",
|
|
30
|
+
day: "numeric",
|
|
31
|
+
month: "short",
|
|
32
|
+
year: "numeric",
|
|
33
|
+
timeZone: "UTC"
|
|
34
|
+
});
|
|
19
35
|
const formatDate = (ms) => day.format(ms);
|
|
20
36
|
const formatDayShort = (ms) => dayShort.format(ms);
|
|
21
37
|
const formatDateTime = (ms) => `${day.format(ms)}, ${time.format(ms)}`;
|
|
38
|
+
/** "October 2026". */
|
|
39
|
+
const formatMonth = (ms) => month.format(ms);
|
|
40
|
+
/** "Mon". */
|
|
41
|
+
const formatWeekday = (ms) => weekday.format(ms);
|
|
42
|
+
/** "Monday, 12 Oct 2026". */
|
|
43
|
+
const formatWeekdayDate = (ms) => weekdayDate.format(ms);
|
|
44
|
+
/** "12–18 Oct 2026", "28 Dec 2026 – 3 Jan 2027": the days from `from` to `to`, both included. */
|
|
45
|
+
const formatDayRange = (from, to) => day.formatRange(from, to);
|
|
46
|
+
/** "09:30". */
|
|
47
|
+
const formatTime = (ms) => time.format(ms);
|
|
22
48
|
const formatAgo = (seconds) => {
|
|
23
49
|
if (seconds < 60) return `${seconds} s ago`;
|
|
24
50
|
if (seconds < 3600) return `${Math.floor(seconds / 60)} min ago`;
|
|
@@ -40,4 +66,4 @@ const formatRelativeTime = (seconds) => {
|
|
|
40
66
|
return relative.format(-Math.floor(seconds / size), unit);
|
|
41
67
|
};
|
|
42
68
|
//#endregion
|
|
43
|
-
export { formatAgo, formatDate, formatDateTime, formatDayShort, formatRelativeTime };
|
|
69
|
+
export { formatAgo, formatDate, formatDateTime, formatDayRange, formatDayShort, formatMonth, formatRelativeTime, formatTime, formatWeekday, formatWeekdayDate };
|
|
@@ -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
|