@protobase/ui 0.59.2 → 0.59.3

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 (42) hide show
  1. package/dist/app/app.js +11 -3
  2. package/dist/app-shell/app-shell.js +2 -1
  3. package/dist/app-shell/profile-menu.d.ts +1 -0
  4. package/dist/app-shell/profile-menu.js +25 -3
  5. package/dist/app-shell/sidebar.js +3 -2
  6. package/dist/data/api-provider.d.ts +12 -0
  7. package/dist/data/meta-data.d.ts +2 -0
  8. package/dist/data/query-keys.js +8 -0
  9. package/dist/data/use-meta.js +1 -0
  10. package/dist/index.d.ts +4 -2
  11. package/dist/index.js +3 -1
  12. package/dist/organizations/members-page.js +7 -3
  13. package/dist/package.js +1 -1
  14. package/dist/people/person-row.js +8 -2
  15. package/dist/people/use-members.d.ts +9 -4
  16. package/dist/people/use-members.js +19 -3
  17. package/dist/shell/assets/app-CA2weCDB.js +9 -0
  18. package/dist/shell/assets/{index-mZM1MZhj.css → index-D0sx7n9i.css} +1 -1
  19. package/dist/shell/assets/{index-C-4shB1O.js → index-Dto4tygi.js} +2 -2
  20. package/dist/shell/assets/{records-map-CnJCsPQi.js → records-map-lppqdLea.js} +2 -2
  21. package/dist/shell/assets/shared-protobase-ui-C99fw_w0.js +2 -0
  22. package/dist/shell/index.html +4 -4
  23. package/dist/shell/protobase.shell.json +2 -2
  24. package/dist/shell/protobase.sw.js +2 -2
  25. package/dist/shell-source-maps/assets/app-CA2weCDB.js.map +1 -0
  26. package/dist/shell-source-maps/assets/{index-C-4shB1O.js.map → index-Dto4tygi.js.map} +1 -1
  27. package/dist/shell-source-maps/assets/{records-map-CnJCsPQi.js.map → records-map-lppqdLea.js.map} +1 -1
  28. package/dist/shell-source-maps/assets/shared-protobase-ui-C99fw_w0.js.map +1 -0
  29. package/dist/status/clear-time.js +40 -0
  30. package/dist/status/messages.js +58 -0
  31. package/dist/status/status-dialog.js +190 -0
  32. package/dist/status/status-draft.js +40 -0
  33. package/dist/status/status-mark.d.ts +24 -0
  34. package/dist/status/status-mark.js +51 -0
  35. package/dist/status/use-status-menu.js +55 -0
  36. package/dist/status/use-statuses.d.ts +15 -0
  37. package/dist/status/use-statuses.js +43 -0
  38. package/package.json +4 -4
  39. package/dist/shell/assets/app-b2Amc-iC.js +0 -9
  40. package/dist/shell/assets/shared-protobase-ui-CQPxMGrM.js +0 -2
  41. package/dist/shell-source-maps/assets/app-b2Amc-iC.js.map +0 -1
  42. package/dist/shell-source-maps/assets/shared-protobase-ui-CQPxMGrM.js.map +0 -1
package/dist/app/app.js CHANGED
@@ -10,6 +10,7 @@ import { humanize } from "../live/naming.js";
10
10
  import { text } from "./messages.js";
11
11
  import { Router, useRouter } from "./router.js";
12
12
  import { useAppAddress } from "./use-app-address.js";
13
+ import { useStatuses } from "../status/use-statuses.js";
13
14
  import { ProjectUiContext, useProjectUi } from "./pages/project-ui.js";
14
15
  import { RecordTitles, useRecordTitle } from "./titles/record-titles.js";
15
16
  import { isAdmin, userToShell } from "../auth/user-to-shell.js";
@@ -62,6 +63,7 @@ import { RuntimeUpdateButton } from "./runtime/runtime-update.js";
62
63
  import { StaffBanner } from "./staff-banner.js";
63
64
  import { useNavRecent } from "./use-nav-recent.js";
64
65
  import { LiveUpdateGate } from "./live-update/live-update-gate.js";
66
+ import { useStatusMenu } from "../status/use-status-menu.js";
65
67
  import { useCallback, useEffect, useMemo, useState } from "react";
66
68
  import { viewOfList } from "@protobase/schema/internal";
67
69
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -114,6 +116,8 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
114
116
  const Actions = shell?.actions;
115
117
  const RightPanel = shell?.rightPanel;
116
118
  const bell = meta.bell && user && !staff && /* @__PURE__ */ jsx(ShellBell, { own: shell?.bell });
119
+ const status = useStatusMenu(Boolean(meta.status && user && !staff));
120
+ const statuses = useStatuses().data;
117
121
  const [assistantOpen, setAssistantOpen] = useState(false);
118
122
  const assistantUrl = meta.assistant?.url;
119
123
  const assistantButton = assistantUrl && /* @__PURE__ */ jsx(AssistantButton, {
@@ -187,7 +191,10 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
187
191
  }) : organizationPage && (path === "/-/organization/members" || path === "/-/organization/invitations") ? !managesOrganization(current) ? /* @__PURE__ */ jsx(Notice, {
188
192
  title: text("ownersOnly"),
189
193
  children: text("onlyOwners")
190
- }) : path === "/-/organization/members" ? /* @__PURE__ */ jsx(MembersPage, { current }) : /* @__PURE__ */ jsx(InvitationsPage, { current }) : organizationPage && path === "/-/organizations" || outsideOrganizations ? /* @__PURE__ */ jsx(OrganizationsPage, {
194
+ }) : path === "/-/organization/members" ? /* @__PURE__ */ jsx(MembersPage, {
195
+ current,
196
+ statuses
197
+ }) : /* @__PURE__ */ jsx(InvitationsPage, { current }) : organizationPage && path === "/-/organizations" || outsideOrganizations ? /* @__PURE__ */ jsx(OrganizationsPage, {
191
198
  state: organizations,
192
199
  basePath
193
200
  }) : !address ? path === "/" ? /* @__PURE__ */ jsx(Notice, {
@@ -210,7 +217,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
210
217
  recordKey: address.key
211
218
  }, `${address.resource}/${address.key}`) : /* @__PURE__ */ jsx(ListPage, { address }, navItemId(address.resource, address.list));
212
219
  const pluginNotice = admin && pluginsOff.length > 0;
213
- return /* @__PURE__ */ jsx(AppShell, {
220
+ return /* @__PURE__ */ jsxs(AppShell, {
214
221
  pageScroll: true,
215
222
  sidebarMode: sidebarMode ?? "text-small",
216
223
  activeItem: place ?? "",
@@ -242,6 +249,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
242
249
  preference: theme.preference,
243
250
  onChange: theme.choose
244
251
  },
252
+ ...status.menu && { status: status.menu },
245
253
  roles: role.current?.active ? {
246
254
  active: role.current.active,
247
255
  roles: role.current.roles,
@@ -279,7 +287,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
279
287
  fallback: null,
280
288
  children: /* @__PURE__ */ jsx(RightPanel, {})
281
289
  }), assistantPanel] }),
282
- children: content
290
+ children: [content, status.dialog]
283
291
  });
284
292
  };
285
293
  const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, assistant, runtime }) => {
@@ -11,7 +11,7 @@ import { useEffect, useRef, useState } from "react";
11
11
  import { jsx, jsxs } from "react/jsx-runtime";
12
12
  //#region src/app-shell/app-shell.tsx
13
13
  const drawerId = "app-drawer";
14
- const AppShell = ({ sidebarMode, activeItem, breadcrumb, user, workspace, appName, appIcon, logo, actions, rightPanel, sidebarNotice, banner, forceExpanded, profileMenuOpen, navGroups, userMenu, account, bottomBar, onNavigate, onBack, onSignOut, signIn, organizations, roles, theme, search, pageScroll: scrollsByDefault = false, className, children }) => {
14
+ const AppShell = ({ sidebarMode, activeItem, breadcrumb, user, workspace, appName, appIcon, logo, actions, rightPanel, sidebarNotice, banner, forceExpanded, profileMenuOpen, navGroups, userMenu, account, bottomBar, onNavigate, onBack, onSignOut, signIn, organizations, roles, theme, status, search, pageScroll: scrollsByDefault = false, className, children }) => {
15
15
  const desktop = useIsDesktop();
16
16
  const noticeShown = useSidebarNoticeShown(sidebarMode);
17
17
  const [drawerOpen, setDrawerOpen] = useState(false);
@@ -46,6 +46,7 @@ const AppShell = ({ sidebarMode, activeItem, breadcrumb, user, workspace, appNam
46
46
  organizations,
47
47
  roles,
48
48
  theme,
49
+ status,
49
50
  logo
50
51
  });
51
52
  return /* @__PURE__ */ jsx(PageScrollContext.Provider, {
@@ -1,2 +1,3 @@
1
1
  import "../primitives/badge.js";
2
+ import { ReactNode } from "react";
2
3
  import "lucide-react";
@@ -5,12 +5,12 @@ import { Badge } from "../primitives/badge.js";
5
5
  import { SwitchGroup } from "./switch-group.js";
6
6
  import { ThemeSwitch } from "./theme-switch.js";
7
7
  import { useEffect, useRef, useState } from "react";
8
- import { ChevronsUpDown, LogOut } from "lucide-react";
8
+ import { ChevronsUpDown, LogOut, SmilePlus } from "lucide-react";
9
9
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
10
  //#region src/app-shell/profile-menu.tsx
11
11
  const menuRow = "flex min-h-11 w-full items-center gap-2.5 px-3 py-1.5 text-left text-muted-foreground hover:bg-muted md:min-h-0";
12
12
  /** The person at the bottom of the sidebar; the menu is fixed to the viewport so a narrow sidebar cannot clip it. */
13
- const ProfileMenu = ({ user, compact, open: controlled, items = [], account, onNavigate, onSignOut, organizations, roles, theme }) => {
13
+ const ProfileMenu = ({ user, compact, open: controlled, items = [], account, onNavigate, onSignOut, organizations, roles, theme, status }) => {
14
14
  const activeRole = roles?.roles.find((role) => role.name === roles.active);
15
15
  const [local, setLocal] = useState(false);
16
16
  const open = controlled ?? local;
@@ -60,7 +60,13 @@ const ProfileMenu = ({ user, compact, open: controlled, items = [], account, onN
60
60
  className: "ring-0"
61
61
  }), !compact && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
62
62
  className: "min-w-0 flex-1 leading-tight",
63
- children: [/* @__PURE__ */ jsx("span", {
63
+ children: [status?.mark ? /* @__PURE__ */ jsxs("span", {
64
+ className: "flex min-w-0 items-center gap-1",
65
+ children: [/* @__PURE__ */ jsx("span", {
66
+ className: "truncate font-medium",
67
+ children: user.name
68
+ }), status.mark]
69
+ }) : /* @__PURE__ */ jsx("span", {
64
70
  className: "block truncate font-medium",
65
71
  children: user.name
66
72
  }), /* @__PURE__ */ jsx(Badge, {
@@ -100,6 +106,22 @@ const ProfileMenu = ({ user, compact, open: controlled, items = [], account, onN
100
106
  children: user.email
101
107
  })]
102
108
  }),
109
+ status && /* @__PURE__ */ jsx("div", {
110
+ className: "border-b py-1",
111
+ children: /* @__PURE__ */ jsxs("button", {
112
+ type: "button",
113
+ role: "menuitem",
114
+ className: menuRow,
115
+ onClick: () => {
116
+ setLocal(false);
117
+ status.onOpen();
118
+ },
119
+ children: [/* @__PURE__ */ jsx(SmilePlus, { className: "size-4 shrink-0" }), /* @__PURE__ */ jsx("span", {
120
+ className: "min-w-0 flex-1 truncate",
121
+ children: status.label
122
+ })]
123
+ })
124
+ }),
103
125
  organizations && (organizations.current || organizations.others.length > 0) && /* @__PURE__ */ jsx(SwitchGroup, {
104
126
  label: text("organizations"),
105
127
  ...organizations.current && { current: {
@@ -13,7 +13,7 @@ import { Fragment } from "react";
13
13
  import { LogIn } from "lucide-react";
14
14
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
15
15
  //#region src/app-shell/sidebar.tsx
16
- const Sidebar = ({ mode, activeItem, user, workspace, appName, appIcon, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, account, onNavigate, onSignOut, signIn, organizations, roles, theme, sticky, logo: SidebarLogo = Logo, notice }) => {
16
+ const Sidebar = ({ mode, activeItem, user, workspace, appName, appIcon, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, account, onNavigate, onSignOut, signIn, organizations, roles, theme, status, sticky, logo: SidebarLogo = Logo, notice }) => {
17
17
  const expanding = mode === "icon-expand";
18
18
  const expanded = expanding && forceExpanded;
19
19
  const inline = isTextMode(mode) || expanding;
@@ -70,7 +70,8 @@ const Sidebar = ({ mode, activeItem, user, workspace, appName, appIcon, forceExp
70
70
  onSignOut,
71
71
  organizations,
72
72
  roles,
73
- theme
73
+ theme,
74
+ status
74
75
  }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [theme && inline && /* @__PURE__ */ jsx(ThemeSwitch, {
75
76
  theme,
76
77
  className: "flex min-h-11 w-full items-center gap-2.5 whitespace-nowrap p-1.5 text-muted-foreground md:min-h-0"
@@ -71,6 +71,12 @@ export declare const useClient: () => {
71
71
  markRead: (id: string) => Promise<void>;
72
72
  markAllRead: (upTo: string) => Promise<void>;
73
73
  };
74
+ status: {
75
+ own: () => Promise<import("@protobase/client").PersonStatus | null>;
76
+ set: (status: import("@protobase/client").StatusInput) => Promise<import("@protobase/client").PersonStatus | null>;
77
+ clear: () => Promise<void>;
78
+ people: () => Promise<import("@protobase/client").PersonStatus[]>;
79
+ };
74
80
  };
75
81
  /**
76
82
  * The client of the API that serves `resource`: a source's for a resource the app draws from one, the app's own
@@ -144,6 +150,12 @@ export declare const useResourceClient: (resource: string) => {
144
150
  markRead: (id: string) => Promise<void>;
145
151
  markAllRead: (upTo: string) => Promise<void>;
146
152
  };
153
+ status: {
154
+ own: () => Promise<import("@protobase/client").PersonStatus | null>;
155
+ set: (status: import("@protobase/client").StatusInput) => Promise<import("@protobase/client").PersonStatus | null>;
156
+ clear: () => Promise<void>;
157
+ people: () => Promise<import("@protobase/client").PersonStatus[]>;
158
+ };
147
159
  };
148
160
  export type ApiProviderProps = {
149
161
  /** Defaults to `/api/v1` on the current origin. */
@@ -50,6 +50,8 @@ export type MetaData = {
50
50
  };
51
51
  /** Whether the top bar draws the notification bell: the app has notifications and their `ui` is not false (see `ShellBell`). */
52
52
  bell?: boolean;
53
+ /** Whether people set their own status and see those they share an organization with (see `useStatuses`). */
54
+ status?: boolean;
53
55
  /** The language the app's screens are in for everyone, when it sets one (see `AppLocale`). */
54
56
  locale?: Locale;
55
57
  /** The hosts besides the app's own whose images are drawn with the page's referrer (see `FileImage`). */
@@ -164,6 +164,14 @@ const keys = {
164
164
  inboxPages: ["inbox", "pages"],
165
165
  /** The people of the organization the session of this user works in (`useMembers`). */
166
166
  members: (userId) => ["members", userId],
167
+ /** The statuses of everyone who shares an organization with this user (`useStatuses`). */
168
+ statuses: (userId) => ["statuses", userId],
169
+ /** This user's own status (`useOwnStatus`). */
170
+ ownStatus: (userId) => [
171
+ "statuses",
172
+ userId,
173
+ "own"
174
+ ],
167
175
  /** A public form, as `/-/forms/<name>` draws it. */
168
176
  publicForm: (name) => ["public-form", name]
169
177
  };
@@ -45,6 +45,7 @@ const useMeta = () => {
45
45
  ...result.meta.errorReporting && { errorReporting: result.meta.errorReporting },
46
46
  ...result.meta.maps && { maps: result.meta.maps },
47
47
  ...result.meta.notifications?.bell && { bell: true },
48
+ ...result.meta.status && { status: true },
48
49
  ...result.meta.locale && { locale: result.meta.locale },
49
50
  ...result.meta.files && { files: result.meta.files },
50
51
  ...result.meta.plugins && { plugins: result.meta.plugins },
package/dist/index.d.ts CHANGED
@@ -30,7 +30,9 @@ import { RecordLink } from "./app/displays/record-link.js";
30
30
  import { useApiToken } from "./auth/use-api-token.js";
31
31
  import { PushState } from "./notifications/push/web-push.js";
32
32
  import { SignedInUser, useSignedInUser } from "./auth/use-signed-in-user.js";
33
- import { MembersResult, useMembers } from "./people/use-members.js";
33
+ import { Member, MembersResult, useMembers } from "./people/use-members.js";
34
+ import { AvailabilityMark, StatusMark, StatusMarkProps } from "./status/status-mark.js";
35
+ import { statusRefreshMs, useOwnStatus, useStatuses } from "./status/use-statuses.js";
34
36
  import { usePushNotifications } from "./notifications/push/use-push-notifications.js";
35
37
  import { useInbox, useMarkNotificationsRead, useUnreadNotifications } from "./notifications/bell/use-inbox.js";
36
38
  import { SignInPrompt, SignInPromptProps } from "./auth/sign-in-prompt.js";
@@ -84,4 +86,4 @@ import { Section } from "./record-view/section.js";
84
86
  import { Form, FormProps } from "./app/create/form.js";
85
87
  import { RecordPicker, RecordPickerProps } from "./app/record-picker.js";
86
88
  import { Spinner } from "./primitives/spinner.js";
87
- export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ActionInputError, type ActionInputProps, ApiProvider, type ApiProviderProps, App, AppLink, type AppProps, AssistantDock, type AssistantDockProps, AuthLayout, type AuthLayoutProps, type BackendData, type BackendRefresh, type BeforeSignInProps, type BoardDrop, type BoardDropContext, type BoardDropUndo, type BoardDropWrite, Button, type ButtonProps, type CardAction, type CardTone, Carousel, type CarouselImage, type CarouselProps, type ChatAuthor, ChatDivider, type ChatDividerProps, ChatMessage, type ChatMessageProps, ChatThread, type ChatThreadProps, type ChoiceOption, Choices, type ChoicesProps, CodeForm, type CodeFormProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type CurrentAddress, Dialog, type DialogProps, type DiffLine, DiffView, type DiffViewProps, type Drop, type DropRule, Field, FieldList, type FieldProps, type FieldWidget, type FieldWidgetEditorProps, type FieldWidgetProps, Form, type FormProps, Input, type InputProps, type LayoutRecord, type ListAddress, type ListChange, type ListDisplay, type ListDisplayRow, LogoMark, Markdown, type MarkdownContentProps as MarkdownProps, type MarksHook, type MembersResult, MentionBox, type MentionBoxProps, type MentionDraft, MentionedText, MenuSelect, type MenuSelectProps, type MetaData, OtpInput, type OtpInputProps, PartView, type PeopleSearch, type PickedUser, type PluginInfo, type ProjectUi, type PushState, type RecordCardProps, RecordLink, type RecordMark, RecordPicker, type RecordPickerProps, ResendCodeButton, type ResendCodeButtonProps, ResponsiveDialog, type ResponsiveDialogProps, Section, Segmented, type SegmentedOption, type SegmentedProps, type ShellLogoProps, SidePanel, type SidePanelProps, SignInPrompt, type SignInPromptProps, type SignedInUser, Spinner, type Step, StepList, type StepState, Textarea, type TextareaProps, type TitleReader, type TitleRule, allowDelayMs, buttonClass, chatDayLabel, definePlugin, defineUi, diffStats, emptyDraft, isConflict, isFramed, mentionedIds, mergeUi, parseDiff, pluginApi, protobaseVersion, sameChatDay, uiFromModule, useApiToken, useAppAddress, useArmedAfterFocus, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useGuardedPress, useHistogram, useInbox, useLayoutRecord, useList, useListDisplay, useMarkNotificationsRead, useMembers, useMeta, usePlugin, usePushNotifications, useRecord, useResourceClient, useSeries, useSignedInUser, useUnreadNotifications, useUpdateRecord };
89
+ export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ActionInputError, type ActionInputProps, ApiProvider, type ApiProviderProps, App, AppLink, type AppProps, AssistantDock, type AssistantDockProps, AuthLayout, type AuthLayoutProps, AvailabilityMark, type BackendData, type BackendRefresh, type BeforeSignInProps, type BoardDrop, type BoardDropContext, type BoardDropUndo, type BoardDropWrite, Button, type ButtonProps, type CardAction, type CardTone, Carousel, type CarouselImage, type CarouselProps, type ChatAuthor, ChatDivider, type ChatDividerProps, ChatMessage, type ChatMessageProps, ChatThread, type ChatThreadProps, type ChoiceOption, Choices, type ChoicesProps, CodeForm, type CodeFormProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type CurrentAddress, Dialog, type DialogProps, type DiffLine, DiffView, type DiffViewProps, type Drop, type DropRule, Field, FieldList, type FieldProps, type FieldWidget, type FieldWidgetEditorProps, type FieldWidgetProps, Form, type FormProps, Input, type InputProps, type LayoutRecord, type ListAddress, type ListChange, type ListDisplay, type ListDisplayRow, LogoMark, Markdown, type MarkdownContentProps as MarkdownProps, type MarksHook, type Member, type MembersResult, MentionBox, type MentionBoxProps, type MentionDraft, MentionedText, MenuSelect, type MenuSelectProps, type MetaData, OtpInput, type OtpInputProps, PartView, type PeopleSearch, type PickedUser, type PluginInfo, type ProjectUi, type PushState, type RecordCardProps, RecordLink, type RecordMark, RecordPicker, type RecordPickerProps, ResendCodeButton, type ResendCodeButtonProps, ResponsiveDialog, type ResponsiveDialogProps, Section, Segmented, type SegmentedOption, type SegmentedProps, type ShellLogoProps, SidePanel, type SidePanelProps, SignInPrompt, type SignInPromptProps, type SignedInUser, Spinner, StatusMark, type StatusMarkProps, type Step, StepList, type StepState, Textarea, type TextareaProps, type TitleReader, type TitleRule, allowDelayMs, buttonClass, chatDayLabel, definePlugin, defineUi, diffStats, emptyDraft, isConflict, isFramed, mentionedIds, mergeUi, parseDiff, pluginApi, protobaseVersion, sameChatDay, statusRefreshMs, uiFromModule, useApiToken, useAppAddress, useArmedAfterFocus, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useGuardedPress, useHistogram, useInbox, useLayoutRecord, useList, useListDisplay, useMarkNotificationsRead, useMembers, useMeta, useOwnStatus, usePlugin, usePushNotifications, useRecord, useResourceClient, useSeries, useSignedInUser, useStatuses, useUnreadNotifications, useUpdateRecord };
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import { AppLink, useAppAddress } from "./app/use-app-address.js";
12
12
  import { Segmented } from "./primitives/segmented.js";
13
13
  import { Textarea } from "./primitives/textarea.js";
14
14
  import { useList } from "./data/use-list.js";
15
+ import { statusRefreshMs, useOwnStatus, useStatuses } from "./status/use-statuses.js";
15
16
  import { useMembers } from "./people/use-members.js";
16
17
  import { definePlugin, defineUi } from "./app/pages/project-ui.js";
17
18
  import { OtpInput } from "./primitives/otp-input.js";
@@ -54,6 +55,7 @@ import { AssistantDock } from "./assistant-dock/assistant-dock.js";
54
55
  import { useAssistant } from "./assistant-dock/use-assistant.js";
55
56
  import { useAssistantClient } from "./assistant-dock/use-assistant-client.js";
56
57
  import { Form } from "./app/create/form.js";
58
+ import { AvailabilityMark, StatusMark } from "./status/status-mark.js";
57
59
  import { usePushNotifications } from "./notifications/push/use-push-notifications.js";
58
60
  import { useInbox, useMarkNotificationsRead, useUnreadNotifications } from "./notifications/bell/use-inbox.js";
59
61
  import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
@@ -63,4 +65,4 @@ import { useSignedInUser } from "./auth/use-signed-in-user.js";
63
65
  import { Carousel } from "./carousel/carousel.js";
64
66
  import { Choices } from "./primitives/choices.js";
65
67
  import { RecordPicker } from "./app/record-picker.js";
66
- export { ActionCard, ActionInputError, ApiProvider, App, AppLink, AssistantDock, AuthLayout, Button, Carousel, ChatDivider, ChatMessage, ChatThread, Choices, CodeForm, CompactTable, Composer, Dialog, DiffView, Field, FieldList, Form, Input, LogoMark, Markdown, MentionBox, MentionedText, MenuSelect, OtpInput, PartView, RecordLink, RecordPicker, ResendCodeButton, ResponsiveDialog, Section, Segmented, SidePanel, SignInPrompt, Spinner, StepList, Textarea, allowDelayMs, buttonClass, chatDayLabel, definePlugin, defineUi, diffStats, emptyDraft, isConflict, isFramed, mentionedIds, mergeUi, parseDiff, pluginApi, protobaseVersion, sameChatDay, uiFromModule, useApiToken, useAppAddress, useArmedAfterFocus, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useGuardedPress, useHistogram, useInbox, useLayoutRecord, useList, useListDisplay, useMarkNotificationsRead, useMembers, useMeta, usePlugin, usePushNotifications, useRecord, useResourceClient, useSeries, useSignedInUser, useUnreadNotifications, useUpdateRecord };
68
+ export { ActionCard, ActionInputError, ApiProvider, App, AppLink, AssistantDock, AuthLayout, AvailabilityMark, Button, Carousel, ChatDivider, ChatMessage, ChatThread, Choices, CodeForm, CompactTable, Composer, Dialog, DiffView, Field, FieldList, Form, Input, LogoMark, Markdown, MentionBox, MentionedText, MenuSelect, OtpInput, PartView, RecordLink, RecordPicker, ResendCodeButton, ResponsiveDialog, Section, Segmented, SidePanel, SignInPrompt, Spinner, StatusMark, StepList, Textarea, allowDelayMs, buttonClass, chatDayLabel, definePlugin, defineUi, diffStats, emptyDraft, isConflict, isFramed, mentionedIds, mergeUi, parseDiff, pluginApi, protobaseVersion, sameChatDay, statusRefreshMs, uiFromModule, useApiToken, useAppAddress, useArmedAfterFocus, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useGuardedPress, useHistogram, useInbox, useLayoutRecord, useList, useListDisplay, useMarkNotificationsRead, useMembers, useMeta, useOwnStatus, usePlugin, usePushNotifications, useRecord, useResourceClient, useSeries, useSignedInUser, useStatuses, useUnreadNotifications, useUpdateRecord };
@@ -9,6 +9,7 @@ import { text } from "./messages.js";
9
9
  import { AppRolesPicker } from "./app-roles-picker.js";
10
10
  import { grantableRoles, organizationRoleLabel } from "./organization-paths.js";
11
11
  import { PersonRow } from "../people/person-row.js";
12
+ import { StatusMark } from "../status/status-mark.js";
12
13
  import { useCallback, useEffect, useState } from "react";
13
14
  import { AuthError } from "@protobase/client";
14
15
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -19,7 +20,7 @@ const assignableOrganizationRoles = (current) => current?.role === "owner" ? [
19
20
  "admin",
20
21
  "member"
21
22
  ] : ["admin", "member"];
22
- const MemberRow = ({ member, current, onChanged }) => {
23
+ const MemberRow = ({ member, current, status, onChanged }) => {
23
24
  const { session, organizations } = useAuth();
24
25
  const organizationId = current.organization.id;
25
26
  const { busy, error, run } = useAction();
@@ -33,6 +34,7 @@ const MemberRow = ({ member, current, onChanged }) => {
33
34
  email: member.email,
34
35
  self,
35
36
  error,
37
+ ...status && { status: /* @__PURE__ */ jsx(StatusMark, { status }) },
36
38
  actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("select", {
37
39
  "aria-label": text("roleOf", member.name),
38
40
  value: member.role,
@@ -71,9 +73,10 @@ const MemberRow = ({ member, current, onChanged }) => {
71
73
  };
72
74
  /**
73
75
  * The members of the organization one works in, for its owners and admins: organization roles, which decide who
74
- * manages it, and app roles, which decide what each sees and does; only roles the person may give can change.
76
+ * manages it, and app roles, which decide what each sees and does; only roles the person may give can change. In an
77
+ * app with status, `statuses` puts each member's beside their name (see `useStatuses`).
75
78
  */
76
- const MembersPage = ({ current }) => {
79
+ const MembersPage = ({ current, statuses }) => {
77
80
  const { session } = useAuth();
78
81
  const [members, setMembers] = useState();
79
82
  const [error, setError] = useState();
@@ -96,6 +99,7 @@ const MembersPage = ({ current }) => {
96
99
  children: members.map((member) => /* @__PURE__ */ jsx(MemberRow, {
97
100
  member,
98
101
  current,
102
+ status: statuses?.find((status) => status.userId === member.userId),
99
103
  onChanged: load
100
104
  }, `${member.id}/${member.role}/${member.appRoles.join()}`))
101
105
  }) : !error && /* @__PURE__ */ jsxs("div", {
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.59.2";
2
+ var version = "0.59.3";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -2,14 +2,20 @@ import { SectionError } from "../account/account-section.js";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  //#region src/people/person-row.tsx
4
4
  /** Someone in a list of people: their name, " (you)" on one's own row, their address, what can be done and their roles. */
5
- const PersonRow = ({ name, email, self, actions, children, error }) => /* @__PURE__ */ jsxs("li", {
5
+ const PersonRow = ({ name, email, self, actions, children, error, status }) => /* @__PURE__ */ jsxs("li", {
6
6
  className: "flex flex-col gap-2 px-3 py-3",
7
7
  children: [
8
8
  /* @__PURE__ */ jsxs("div", {
9
9
  className: "flex flex-wrap items-center gap-2",
10
10
  children: [/* @__PURE__ */ jsxs("span", {
11
11
  className: "min-w-0 flex-1",
12
- children: [/* @__PURE__ */ jsxs("span", {
12
+ children: [status ? /* @__PURE__ */ jsxs("span", {
13
+ className: "flex min-w-0 items-center gap-1.5",
14
+ children: [/* @__PURE__ */ jsxs("span", {
15
+ className: "truncate font-medium",
16
+ children: [name, self && " (you)"]
17
+ }), status]
18
+ }) : /* @__PURE__ */ jsxs("span", {
13
19
  className: "block truncate font-medium",
14
20
  children: [name, self && " (you)"]
15
21
  }), /* @__PURE__ */ jsx("span", {
@@ -1,14 +1,19 @@
1
- import { OrganizationPerson } from "@protobase/client";
1
+ import { OrganizationPerson, PersonStatus } from "@protobase/client";
2
2
  import { UseQueryResult } from "@tanstack/react-query";
3
3
  //#region src/people/use-members.d.ts
4
- export type MembersResult = UseQueryResult<OrganizationPerson[]> & {
4
+ /** A member, with their status in an app with `status: {}` when they have one that holds (see `useStatuses`). */
5
+ export type Member = OrganizationPerson & {
6
+ status?: PersonStatus;
7
+ };
8
+ export type MembersResult = UseQueryResult<Member[]> & {
5
9
  /** The member with this user id; an unknown one reads the list again (see `asksAgainFor`). */
6
- member: (id: string) => OrganizationPerson | undefined;
10
+ member: (id: string) => Member | undefined;
7
11
  };
8
12
  /**
9
13
  * The members of the organization the session works in, as `{ id, name, image? }` with `id` the user id that
10
14
  * `.author()` stores and mentions hold, by name, readable by every member and without addresses. Read again on focus
11
- * once 30 s old, and when `member` is asked for an id it lacks. Without organizations or the app's auth it fails
15
+ * once 30 s old, and when `member` is asked for an id it lacks. In an app with `status: {}`, each carries their
16
+ * status, which is read again every 15 seconds (see `useStatuses`). Without organizations or the app's auth it fails
12
17
  * saying so.
13
18
  */
14
19
  export declare const useMembers: () => MembersResult;
@@ -1,5 +1,6 @@
1
1
  import { useOptionalAuth } from "../auth/auth-provider.js";
2
2
  import { keys } from "../data/query-keys.js";
3
+ import { useStatuses } from "../status/use-statuses.js";
3
4
  import { useCallback, useMemo } from "react";
4
5
  import { useQuery } from "@tanstack/react-query";
5
6
  //#region src/people/use-members.ts
@@ -7,10 +8,22 @@ import { useQuery } from "@tanstack/react-query";
7
8
  const unknownIdRefetchMs = 1e4;
8
9
  /** Whether an id the list lacks reads it again: once it has loaded, is not being read, and is old enough. */
9
10
  const asksAgainFor = (query, now = Date.now()) => query.isSuccess && !query.isFetching && now - query.dataUpdatedAt >= 1e4;
11
+ /** The members, each with their status when they have one. */
12
+ const withStatuses = (people, statuses) => {
13
+ const byUser = new Map(statuses.map((status) => [status.userId, status]));
14
+ return people.map((person) => {
15
+ const status = byUser.get(person.id);
16
+ return status ? {
17
+ ...person,
18
+ status
19
+ } : person;
20
+ });
21
+ };
10
22
  /**
11
23
  * The members of the organization the session works in, as `{ id, name, image? }` with `id` the user id that
12
24
  * `.author()` stores and mentions hold, by name, readable by every member and without addresses. Read again on focus
13
- * once 30 s old, and when `member` is asked for an id it lacks. Without organizations or the app's auth it fails
25
+ * once 30 s old, and when `member` is asked for an id it lacks. In an app with `status: {}`, each carries their
26
+ * status, which is read again every 15 seconds (see `useStatuses`). Without organizations or the app's auth it fails
14
27
  * saying so.
15
28
  */
16
29
  const useMembers = () => {
@@ -24,7 +37,9 @@ const useMembers = () => {
24
37
  return auth.session.organizations.people();
25
38
  }
26
39
  });
27
- const byId = useMemo(() => new Map((query.data ?? []).map((person) => [person.id, person])), [query.data]);
40
+ const statuses = useStatuses().data;
41
+ const data = useMemo(() => query.data && (statuses ? withStatuses(query.data, statuses) : query.data), [query.data, statuses]);
42
+ const byId = useMemo(() => new Map((data ?? []).map((person) => [person.id, person])), [data]);
28
43
  const { refetch } = query;
29
44
  const member = useCallback((id) => {
30
45
  const found = byId.get(id);
@@ -37,8 +52,9 @@ const useMembers = () => {
37
52
  ]);
38
53
  return {
39
54
  ...query,
55
+ data,
40
56
  member
41
57
  };
42
58
  };
43
59
  //#endregion
44
- export { asksAgainFor, unknownIdRefetchMs, useMembers };
60
+ export { asksAgainFor, unknownIdRefetchMs, useMembers, withStatuses };