@protobase/ui 0.62.5 → 0.63.0

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 (46) hide show
  1. package/dist/app/app.d.ts +1 -1
  2. package/dist/app/app.js +22 -5
  3. package/dist/calls/call-check.js +2 -2
  4. package/dist/calls/call-controls.js +16 -4
  5. package/dist/calls/call-guests-panel.js +200 -0
  6. package/dist/calls/call-lobby-notice.js +34 -0
  7. package/dist/calls/call-message.js +4 -3
  8. package/dist/calls/call-people.js +3 -2
  9. package/dist/calls/call-stage.js +4 -2
  10. package/dist/calls/call-view.d.ts +9 -3
  11. package/dist/calls/call-view.js +37 -6
  12. package/dist/calls/call-waiting.js +20 -0
  13. package/dist/calls/guest-admission.js +40 -0
  14. package/dist/calls/guest-call-link.js +19 -0
  15. package/dist/calls/guest-call-page.js +82 -0
  16. package/dist/calls/messages.js +69 -3
  17. package/dist/calls/use-call-session.d.ts +7 -0
  18. package/dist/calls/use-call-session.js +27 -2
  19. package/dist/calls/use-guest-request.js +24 -0
  20. package/dist/data/api-provider.d.ts +8 -0
  21. package/dist/index.js +1 -1
  22. package/dist/organizations/messages.js +6 -0
  23. package/dist/organizations/organization-page.js +33 -1
  24. package/dist/package.js +1 -1
  25. package/dist/shell/assets/app-CKCVGFSR.js +9 -0
  26. package/dist/shell/assets/calls-DRnrKDC9.js +2 -0
  27. package/dist/shell/assets/{index-C6bDRZz0.js → index-BK-wuwB0.js} +2 -2
  28. package/dist/shell/assets/index-Be8nCyRW.css +2 -0
  29. package/dist/shell/assets/{records-map-B5MPNRQc.js → records-map-Bd5scHzJ.js} +2 -2
  30. package/dist/shell/assets/shared-protobase-ui-CU8QfOuc.js +2 -0
  31. package/dist/shell/index.html +4 -4
  32. package/dist/shell/protobase.shell.json +2 -2
  33. package/dist/shell/protobase.sw.js +2 -2
  34. package/dist/shell-source-maps/assets/app-CKCVGFSR.js.map +1 -0
  35. package/dist/shell-source-maps/assets/calls-DRnrKDC9.js.map +1 -0
  36. package/dist/shell-source-maps/assets/{index-C6bDRZz0.js.map → index-BK-wuwB0.js.map} +1 -1
  37. package/dist/shell-source-maps/assets/{records-map-B5MPNRQc.js.map → records-map-Bd5scHzJ.js.map} +1 -1
  38. package/dist/shell-source-maps/assets/shared-protobase-ui-CU8QfOuc.js.map +1 -0
  39. package/package.json +4 -4
  40. package/dist/shell/assets/app-z3__9x3P.js +0 -9
  41. package/dist/shell/assets/calls-C63xSTRj.js +0 -2
  42. package/dist/shell/assets/index-BgcF_WZ0.css +0 -2
  43. package/dist/shell/assets/shared-protobase-ui-COFhNI_R.js +0 -2
  44. package/dist/shell-source-maps/assets/app-z3__9x3P.js.map +0 -1
  45. package/dist/shell-source-maps/assets/calls-C63xSTRj.js.map +0 -1
  46. package/dist/shell-source-maps/assets/shared-protobase-ui-COFhNI_R.js.map +0 -1
@@ -0,0 +1,82 @@
1
+ import { Button } from "../primitives/button.js";
2
+ import { Input } from "../primitives/input.js";
3
+ import { text } from "./messages.js";
4
+ import { guestCallLink } from "./guest-call-link.js";
5
+ import { guestAdmission } from "./guest-admission.js";
6
+ import { CallView } from "./call-view.js";
7
+ import { useCallback, useState } from "react";
8
+ import { guestCallsPath } from "@protobase/schema/internal";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ //#region src/calls/guest-call-page.tsx
11
+ /**
12
+ * The page a guest link opens, for someone with no account: their name, then the call view's check of their microphone
13
+ * and camera, Ask to join, the wait in the lobby until someone in the call lets them in, and the call. They reach
14
+ * nothing else of the app: the page asks only the lobby and the guest call socket.
15
+ */
16
+ const GuestCallPage = ({ link, systemPath, base }) => {
17
+ const [draft, setDraft] = useState("");
18
+ const [name, setName] = useState();
19
+ const [left, setLeft] = useState(false);
20
+ const admission = useCallback(async (signal) => {
21
+ const { guestLobby } = await import("@protobase/client/calls");
22
+ return guestAdmission(guestLobby({ systemPath }), link, name, signal);
23
+ }, [
24
+ systemPath,
25
+ link,
26
+ name
27
+ ]);
28
+ const submit = (event) => {
29
+ event.preventDefault();
30
+ if (draft.trim()) setName(draft.trim());
31
+ };
32
+ const here = guestCallLink(base, link);
33
+ if (left) return /* @__PURE__ */ jsx("p", {
34
+ role: "status",
35
+ className: "flex min-h-[100dvh] items-center justify-center bg-background p-4 text-sm text-foreground",
36
+ children: text("youLeft")
37
+ });
38
+ if (name === void 0) return /* @__PURE__ */ jsx("main", {
39
+ className: "flex min-h-[100dvh] items-center justify-center bg-background p-4",
40
+ children: /* @__PURE__ */ jsxs("form", {
41
+ onSubmit: submit,
42
+ className: "flex w-full max-w-sm flex-col gap-3",
43
+ children: [
44
+ /* @__PURE__ */ jsx("p", {
45
+ className: "text-sm text-foreground",
46
+ children: text("guestIntro")
47
+ }),
48
+ /* @__PURE__ */ jsxs("label", {
49
+ className: "flex flex-col gap-1 text-sm text-foreground",
50
+ children: [text("yourName"), /* @__PURE__ */ jsx(Input, {
51
+ autoFocus: true,
52
+ autoComplete: "name",
53
+ maxLength: 60,
54
+ value: draft,
55
+ onChange: (event) => setDraft(event.target.value)
56
+ })]
57
+ }),
58
+ /* @__PURE__ */ jsx(Button, {
59
+ type: "submit",
60
+ variant: "primary",
61
+ disabled: !draft.trim(),
62
+ children: text("continue")
63
+ })
64
+ ]
65
+ })
66
+ });
67
+ return /* @__PURE__ */ jsx("main", {
68
+ className: "h-[100dvh]",
69
+ children: /* @__PURE__ */ jsx(CallView, {
70
+ url: guestCallsPath(systemPath, "socket"),
71
+ admission,
72
+ name,
73
+ nameOf: () => text("someone"),
74
+ leave: {
75
+ href: here,
76
+ onFollow: () => setLeft(true)
77
+ }
78
+ })
79
+ });
80
+ };
81
+ //#endregion
82
+ export { GuestCallPage };
@@ -21,7 +21,7 @@ const text = defineMessages({
21
21
  reconnecting: "Reconnecting…",
22
22
  full: "This call is full",
23
23
  removed: "You were removed from this call",
24
- ended: "The call has ended",
24
+ ended: "The call ended",
25
25
  refused: "You cannot join this call",
26
26
  unavailable: "The call cannot be reached now. Try again in a moment.",
27
27
  callAgain: "Join again",
@@ -34,7 +34,40 @@ const text = defineMessages({
34
34
  sharing: (name) => `${name} is sharing their screen`,
35
35
  youAreSharing: "You are sharing your screen",
36
36
  speaking: "Speaking",
37
- preview: "Your camera before you join"
37
+ preview: "Your camera before you join",
38
+ guestName: (name) => `${name} (guest)`,
39
+ denied: "Nobody in the call let you in",
40
+ waiting: "Waiting for someone in the call to let you in…",
41
+ askToJoin: "Ask to join",
42
+ someone: "Someone in the call",
43
+ guests: "Guests",
44
+ waitingGuest: (name) => `${name} is waiting to join`,
45
+ admit: "Let in",
46
+ deny: "Turn away",
47
+ remove: "Remove",
48
+ revoke: "Revoke",
49
+ newLink: "Create guest link",
50
+ copyLink: "Copy link",
51
+ copied: "Copied",
52
+ guestLinks: "Guest links",
53
+ noLinks: "No guest link yet. Anyone with a link waits here until someone in the call lets them in.",
54
+ inLobby: "Waiting to join",
55
+ guestsInCall: "Guests in the call",
56
+ guestsMaySend: "Guests may send",
57
+ sendAll: "Everything",
58
+ sendCamera: "Camera and mic",
59
+ sendMicrophone: "Mic only",
60
+ linkUntil: (until) => `Until ${until}`,
61
+ linkUntilEnd: "Until the call ends",
62
+ linkLasts: "The link works",
63
+ forAnHour: "For an hour",
64
+ addressesVisible: "In a call between browsers, a guest and the people in the call can see each other’s network addresses.",
65
+ guestRefused: "This could not be done: the guest or link is gone, the call is full, or guest links are off.",
66
+ close: "Close",
67
+ yourName: "Your name",
68
+ guestIntro: "You are invited to a call. Give your name, check your microphone and camera, and someone in the call lets you in.",
69
+ continue: "Continue",
70
+ youLeft: "You left the call"
38
71
  },
39
72
  nl: {
40
73
  accept: "Opnemen",
@@ -69,7 +102,40 @@ const text = defineMessages({
69
102
  sharing: (name) => `${name} deelt het scherm`,
70
103
  youAreSharing: "Je deelt je scherm",
71
104
  speaking: "Spreekt",
72
- preview: "Je camera voordat je deelneemt"
105
+ preview: "Je camera voordat je deelneemt",
106
+ guestName: (name) => `${name} (gast)`,
107
+ denied: "Niemand in het gesprek heeft je binnengelaten",
108
+ waiting: "Wachten tot iemand in het gesprek je binnenlaat…",
109
+ askToJoin: "Vragen om deel te nemen",
110
+ someone: "Iemand in het gesprek",
111
+ guests: "Gasten",
112
+ waitingGuest: (name) => `${name} wacht om deel te nemen`,
113
+ admit: "Binnenlaten",
114
+ deny: "Weigeren",
115
+ remove: "Verwijderen",
116
+ revoke: "Intrekken",
117
+ newLink: "Gastlink maken",
118
+ copyLink: "Link kopiëren",
119
+ copied: "Gekopieerd",
120
+ guestLinks: "Gastlinks",
121
+ noLinks: "Nog geen gastlink. Wie een link heeft, wacht hier tot iemand in het gesprek hem binnenlaat.",
122
+ inLobby: "Wacht om deel te nemen",
123
+ guestsInCall: "Gasten in het gesprek",
124
+ guestsMaySend: "Gasten mogen sturen",
125
+ sendAll: "Alles",
126
+ sendCamera: "Camera en microfoon",
127
+ sendMicrophone: "Alleen geluid",
128
+ linkUntil: (until) => `Tot ${until}`,
129
+ linkUntilEnd: "Tot het gesprek afloopt",
130
+ linkLasts: "De link werkt",
131
+ forAnHour: "Een uur lang",
132
+ addressesVisible: "In een gesprek tussen browsers zien een gast en de mensen in het gesprek elkaars netwerkadressen.",
133
+ guestRefused: "Dit lukte niet: de gast of link is weg, het gesprek is vol, of gastlinks staan uit.",
134
+ close: "Sluiten",
135
+ yourName: "Je naam",
136
+ guestIntro: "Je bent uitgenodigd voor een gesprek. Geef je naam, controleer je microfoon en camera, en iemand in het gesprek laat je binnen.",
137
+ continue: "Verder",
138
+ youLeft: "Je hebt het gesprek verlaten"
73
139
  }
74
140
  });
75
141
  //#endregion
@@ -0,0 +1,7 @@
1
+ import "@protobase/client/calls";
2
+ //#region src/calls/use-call-session.d.ts
3
+ export type CallSessionOptions = {
4
+ /** For a guest: their wait in the lobby, which resolves with their token once let in, or rejects with `NotLetIn`. */
5
+ admission?: (signal: AbortSignal) => Promise<string>;
6
+ };
7
+ //#endregion
@@ -1,4 +1,5 @@
1
1
  import { openDevices } from "./devices.js";
2
+ import { NotLetIn } from "./guest-admission.js";
2
3
  import { useCallback, useEffect, useRef, useState } from "react";
3
4
  //#region src/calls/use-call-session.ts
4
5
  const connectionsEveryMs = 2e3;
@@ -8,10 +9,12 @@ const stop = (tracks) => {
8
9
  /**
9
10
  * The member's session in the call the app's function at `url` admits them to: their devices from the start, for the
10
11
  * check before joining; then the call, which `join` joins after the click, with what they send and receive, and who
11
- * is in it. Leaving the page leaves the call and stops the devices.
12
+ * is in it. A guest waits in the lobby first. Leaving the page leaves the call and stops the devices.
12
13
  */
13
- const useCallSession = (url) => {
14
+ const useCallSession = (url, { admission } = {}) => {
14
15
  const [phase, setPhase] = useState({ kind: "check" });
16
+ const [lobby, setLobby] = useState();
17
+ const waiting = useRef(new AbortController());
15
18
  const [devices, setDevices] = useState();
16
19
  const [microphoneOn, setMicrophoneOn] = useState(true);
17
20
  const [cameraOn, setCameraOn] = useState(true);
@@ -40,6 +43,7 @@ const useCallSession = (url) => {
40
43
  return () => {
41
44
  cancelled = true;
42
45
  gone.current = true;
46
+ waiting.current.abort();
43
47
  call.current?.leave();
44
48
  call.current = void 0;
45
49
  stop(own.current);
@@ -54,11 +58,28 @@ const useCallSession = (url) => {
54
58
  const timer = setInterval(() => void call.current?.connections().then(setConnections), connectionsEveryMs);
55
59
  return () => clearInterval(timer);
56
60
  }, [phase.kind]);
61
+ const admitted = async () => {
62
+ if (!admission) return {};
63
+ setPhase({ kind: "waiting" });
64
+ waiting.current = new AbortController();
65
+ const guest = await admission(waiting.current.signal).then((token) => token, (error) => {
66
+ if (!(error instanceof NotLetIn)) throw error;
67
+ setPhase({
68
+ kind: "left",
69
+ reason: error.reason
70
+ });
71
+ });
72
+ return guest === void 0 ? void 0 : { guest };
73
+ };
57
74
  const join = useCallback(async () => {
75
+ const guest = await admitted();
76
+ if (!guest || gone.current) return;
58
77
  setPhase({ kind: "joining" });
59
78
  const { joinCall, PublishRefused } = await import("@protobase/client/calls");
60
79
  const joined = await joinCall({
61
80
  url,
81
+ ...guest,
82
+ onLobby: setLobby,
62
83
  onTracks: setTracks,
63
84
  onMembers: setMembers,
64
85
  onState: (state) => {
@@ -89,6 +110,7 @@ const useCallSession = (url) => {
89
110
  });
90
111
  }, [
91
112
  url,
113
+ admission,
92
114
  microphoneOn,
93
115
  cameraOn
94
116
  ]);
@@ -147,6 +169,9 @@ const useCallSession = (url) => {
147
169
  refusedSending,
148
170
  seen: seen.current,
149
171
  self: call.current?.participant,
172
+ /** The lobby, once the server tells a member who may invite of it. */
173
+ lobby,
174
+ guests: call.current?.guests,
150
175
  join,
151
176
  toggleMicrophone,
152
177
  toggleCamera,
@@ -0,0 +1,24 @@
1
+ import { useCallback, useState } from "react";
2
+ //#region src/calls/use-guest-request.ts
3
+ /**
4
+ * Runs a member's guest request, and says when the server refused it, as `GuestRefused`; any other failure is thrown
5
+ * on, where the page's error handling sees it.
6
+ */
7
+ const useGuestRequest = () => {
8
+ const [refused, setRefused] = useState(false);
9
+ return {
10
+ run: useCallback((request) => {
11
+ setRefused(false);
12
+ (async () => {
13
+ const { GuestRefused } = await import("@protobase/client/calls");
14
+ await request().catch((error) => {
15
+ if (!(error instanceof GuestRefused)) throw error;
16
+ setRefused(true);
17
+ });
18
+ })();
19
+ }, []),
20
+ refused
21
+ };
22
+ };
23
+ //#endregion
24
+ export { useGuestRequest };
@@ -83,6 +83,10 @@ export declare const useClient: () => {
83
83
  list: () => Promise<import("@protobase/client").CallRing[]>;
84
84
  decline: (call: string) => Promise<void>;
85
85
  };
86
+ callGuests: {
87
+ allowed: () => Promise<boolean>;
88
+ setAllowed: (allowed: boolean) => Promise<void>;
89
+ };
86
90
  };
87
91
  /**
88
92
  * The client of the API that serves `resource`: a source's for a resource the app draws from one, the app's own
@@ -168,6 +172,10 @@ export declare const useResourceClient: (resource: string) => {
168
172
  list: () => Promise<import("@protobase/client").CallRing[]>;
169
173
  decline: (call: string) => Promise<void>;
170
174
  };
175
+ callGuests: {
176
+ allowed: () => Promise<boolean>;
177
+ setAllowed: (allowed: boolean) => Promise<void>;
178
+ };
171
179
  };
172
180
  export type ApiProviderProps = {
173
181
  /** Defaults to `/api/v1` on the current origin. */
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ import { usePlugin } from "./plugins/plugin-context.js";
35
35
  import { pluginApi, protobaseVersion } from "./plugins/plugin-api.js";
36
36
  import { useApiToken } from "./auth/use-api-token.js";
37
37
  import { useRecord } from "./data/use-record.js";
38
+ import { CallView } from "./calls/call-view.js";
38
39
  import { chatDayLabel, sameChatDay } from "./chat/chat-day.js";
39
40
  import { ChatDivider } from "./chat/chat-divider.js";
40
41
  import { ChatMessage } from "./chat/chat-message.js";
@@ -63,7 +64,6 @@ import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
63
64
  import { App } from "./app/app.js";
64
65
  import { RecordLink } from "./app/displays/record-link.js";
65
66
  import { useSignedInUser } from "./auth/use-signed-in-user.js";
66
- import { CallView } from "./calls/call-view.js";
67
67
  import { useRecordCallAddress } from "./calls/use-record-call-address.js";
68
68
  import { RecordCall } from "./calls/record-call.js";
69
69
  import { useRecordCalls } from "./calls/use-record-calls.js";
@@ -60,6 +60,9 @@ const text = defineMessages({
60
60
  details: "Details",
61
61
  detailsEditable: "The name people see, and the slug that names it in links.",
62
62
  detailsReadOnly: "Owners and admins can change these.",
63
+ guestLinks: "Guest links to calls",
64
+ guestLinksDescription: "Someone in a call can share a link that lets a person from outside wait in its lobby and join that one call once let in. Turning guest links off revokes every link and takes every guest out at once. Owners and admins can change this.",
65
+ guestLinksAllowed: "Allow guest links",
63
66
  handOver: "Hand over",
64
67
  handOverDescription: "Make another member the owner. You stay as an admin.",
65
68
  newOwner: "New owner",
@@ -149,6 +152,9 @@ const text = defineMessages({
149
152
  details: "Gegevens",
150
153
  detailsEditable: "De naam die mensen zien, en de slug die haar in links noemt.",
151
154
  detailsReadOnly: "Eigenaren en beheerders kunnen dit wijzigen.",
155
+ guestLinks: "Gastlinks voor gesprekken",
156
+ guestLinksDescription: "Iemand in een gesprek kan een link delen waarmee iemand van buiten in de wachtruimte wacht en, eenmaal binnengelaten, aan dat ene gesprek deelneemt. Gastlinks uitzetten trekt elke link in en haalt elke gast meteen uit het gesprek. Eigenaren en beheerders kunnen dit wijzigen.",
157
+ guestLinksAllowed: "Gastlinks toestaan",
152
158
  handOver: "Overdragen",
153
159
  handOverDescription: "Maak een ander lid eigenaar. Jij blijft beheerder.",
154
160
  newOwner: "Nieuwe eigenaar",
@@ -1,7 +1,10 @@
1
1
  import { useAuth } from "../auth/auth-provider.js";
2
+ import { useClient } from "../data/api-provider.js";
3
+ import { useMeta } from "../data/use-meta.js";
2
4
  import { Button } from "../primitives/button.js";
3
5
  import { Dialog } from "../primitives/dialog.js";
4
6
  import { Input } from "../primitives/input.js";
7
+ import { Switch } from "../primitives/switch.js";
5
8
  import { PageHeader } from "../app-shell/page-header.js";
6
9
  import { AccountSection, SectionError } from "../account/account-section.js";
7
10
  import { useAction } from "./use-action.js";
@@ -146,6 +149,30 @@ const Delete = ({ organization, basePath }) => {
146
149
  ]
147
150
  });
148
151
  };
152
+ const GuestLinks = ({ editable }) => {
153
+ const client = useClient();
154
+ const [allowed, setAllowed] = useState();
155
+ const { busy, error, run } = useAction();
156
+ useEffect(() => {
157
+ client.callGuests.allowed().then(setAllowed);
158
+ }, [client]);
159
+ if (allowed === void 0) return null;
160
+ return /* @__PURE__ */ jsxs(AccountSection, {
161
+ title: text("guestLinks"),
162
+ description: text("guestLinksDescription"),
163
+ children: [/* @__PURE__ */ jsx(SectionError, { message: error }), /* @__PURE__ */ jsxs("label", {
164
+ className: "flex items-center gap-2 text-sm text-foreground",
165
+ children: [/* @__PURE__ */ jsx(Switch, {
166
+ checked: allowed,
167
+ label: text("guestLinksAllowed"),
168
+ ...editable && !busy && { onChange: (next) => void run(async () => {
169
+ await client.callGuests.setAllowed(next);
170
+ setAllowed(next);
171
+ }) }
172
+ }), text("guestLinksAllowed")]
173
+ })]
174
+ });
175
+ };
149
176
  const Leave = ({ organization, basePath, owner }) => {
150
177
  const { session } = useAuth();
151
178
  const { busy, error, run } = useAction();
@@ -162,8 +189,12 @@ const Leave = ({ organization, basePath, owner }) => {
162
189
  })]
163
190
  });
164
191
  };
165
- /** The organization one works in: its details, and handing it over, leaving and deleting it, as one's role allows. */
192
+ /**
193
+ * The organization one works in: its details, whether its calls take guests, and handing it over, leaving and deleting
194
+ * it, as one's role allows.
195
+ */
166
196
  const OrganizationPage = ({ current, basePath, onChanged }) => {
197
+ const calls = useMeta().data?.calls === true;
167
198
  const organization = current?.organization;
168
199
  if (!organization) return /* @__PURE__ */ jsx("div", {
169
200
  className: "mx-auto w-full max-w-3xl px-4 py-6 md:px-6",
@@ -185,6 +216,7 @@ const OrganizationPage = ({ current, basePath, onChanged }) => {
185
216
  editable: managesOrganization(current),
186
217
  onSaved: onChanged
187
218
  }, `${organization.name}/${organization.slug}`),
219
+ calls && current.role && /* @__PURE__ */ jsx(GuestLinks, { editable: managesOrganization(current) }),
188
220
  owner && current.memberId && /* @__PURE__ */ jsx(HandOver, {
189
221
  organization,
190
222
  memberId: current.memberId
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.62.5";
2
+ var version = "0.63.0";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };