@protobase/ui 0.62.4 → 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.
- package/dist/account/account-page.js +3 -0
- package/dist/account/blocks/connected-accounts-block.js +1 -1
- package/dist/account/blocks/personal-mail-block.js +36 -0
- package/dist/account/messages.js +4 -0
- package/dist/account/sign-in-providers-section.js +3 -3
- package/dist/app/app.d.ts +1 -1
- package/dist/app/app.js +22 -5
- package/dist/app/pages/render-node.js +2 -0
- package/dist/auth/auth-provider.js +7 -0
- package/dist/calls/call-check.js +2 -2
- package/dist/calls/call-controls.js +16 -4
- package/dist/calls/call-guests-panel.js +200 -0
- package/dist/calls/call-lobby-notice.js +34 -0
- package/dist/calls/call-message.js +4 -3
- package/dist/calls/call-people.js +3 -2
- package/dist/calls/call-stage.js +4 -2
- package/dist/calls/call-view.d.ts +9 -3
- package/dist/calls/call-view.js +37 -6
- package/dist/calls/call-waiting.js +20 -0
- package/dist/calls/guest-admission.js +40 -0
- package/dist/calls/guest-call-link.js +19 -0
- package/dist/calls/guest-call-page.js +82 -0
- package/dist/calls/messages.js +69 -3
- package/dist/calls/use-call-session.d.ts +7 -0
- package/dist/calls/use-call-session.js +27 -2
- package/dist/calls/use-guest-request.js +24 -0
- package/dist/data/api-provider.d.ts +8 -0
- package/dist/index.js +1 -1
- package/dist/organizations/messages.js +6 -0
- package/dist/organizations/organization-page.js +33 -1
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-CKCVGFSR.js +9 -0
- package/dist/shell/assets/calls-DRnrKDC9.js +2 -0
- package/dist/shell/assets/{index-D8oHLXjS.js → index-BK-wuwB0.js} +2 -2
- package/dist/shell/assets/index-Be8nCyRW.css +2 -0
- package/dist/shell/assets/{records-map-DOXcutuQ.js → records-map-Bd5scHzJ.js} +2 -2
- package/dist/shell/assets/shared-protobase-ui-CU8QfOuc.js +2 -0
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +2 -2
- package/dist/shell/protobase.sw.js +2 -2
- package/dist/shell-source-maps/assets/app-CKCVGFSR.js.map +1 -0
- package/dist/shell-source-maps/assets/calls-DRnrKDC9.js.map +1 -0
- package/dist/shell-source-maps/assets/{index-D8oHLXjS.js.map → index-BK-wuwB0.js.map} +1 -1
- package/dist/shell-source-maps/assets/{records-map-DOXcutuQ.js.map → records-map-Bd5scHzJ.js.map} +1 -1
- package/dist/shell-source-maps/assets/shared-protobase-ui-CU8QfOuc.js.map +1 -0
- package/package.json +4 -4
- package/dist/shell/assets/app-B_ojAtG3.js +0 -9
- package/dist/shell/assets/calls-C63xSTRj.js +0 -2
- package/dist/shell/assets/index-BgcF_WZ0.css +0 -2
- package/dist/shell/assets/shared-protobase-ui-CX80Ectk.js +0 -2
- package/dist/shell-source-maps/assets/app-B_ojAtG3.js.map +0 -1
- package/dist/shell-source-maps/assets/calls-C63xSTRj.js.map +0 -1
- package/dist/shell-source-maps/assets/shared-protobase-ui-CX80Ectk.js.map +0 -1
|
@@ -18,11 +18,13 @@ const useAccountPage = () => {
|
|
|
18
18
|
const auth = useAuth();
|
|
19
19
|
const { mcp, accountDeletion } = auth;
|
|
20
20
|
const linkable = linkableProviders(auth).length > 0;
|
|
21
|
+
const mailboxes = auth.personalMail.length > 0;
|
|
21
22
|
return useMemo(() => {
|
|
22
23
|
if (!account) return void 0;
|
|
23
24
|
const hidden = /* @__PURE__ */ new Set([
|
|
24
25
|
...mcp ? [] : ["ConnectedApps"],
|
|
25
26
|
...linkable ? [] : ["ConnectedAccounts"],
|
|
27
|
+
...mailboxes ? [] : ["PersonalMail"],
|
|
26
28
|
...accountDeletion ? [] : ["DeleteAccount"]
|
|
27
29
|
]);
|
|
28
30
|
return {
|
|
@@ -33,6 +35,7 @@ const useAccountPage = () => {
|
|
|
33
35
|
account,
|
|
34
36
|
mcp,
|
|
35
37
|
linkable,
|
|
38
|
+
mailboxes,
|
|
36
39
|
accountDeletion
|
|
37
40
|
]);
|
|
38
41
|
};
|
|
@@ -34,4 +34,4 @@ const ConnectedAccountsBlock = () => {
|
|
|
34
34
|
return linkable.length > 0 ? /* @__PURE__ */ jsx(Linked, { providers: linkable }) : null;
|
|
35
35
|
};
|
|
36
36
|
//#endregion
|
|
37
|
-
export { ConnectedAccountsBlock, linkableProviders };
|
|
37
|
+
export { ConnectedAccountsBlock, linkableProviders, takeLinkError };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { text } from "../messages.js";
|
|
2
|
+
import { useAuth } from "../../auth/auth-provider.js";
|
|
3
|
+
import { providerMessage } from "../../auth/auth-messages.js";
|
|
4
|
+
import { SignInProvidersSection } from "../sign-in-providers-section.js";
|
|
5
|
+
import { AccountLoading } from "./account-loading.js";
|
|
6
|
+
import { useAccountData } from "./use-account-data.js";
|
|
7
|
+
import { takeLinkError } from "./connected-accounts-block.js";
|
|
8
|
+
import { useCallback, useState } from "react";
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
//#region src/account/blocks/personal-mail-block.tsx
|
|
11
|
+
const Mailboxes = ({ links }) => {
|
|
12
|
+
const { session, linkPersonalMail } = useAuth();
|
|
13
|
+
const { account } = session;
|
|
14
|
+
const [linkError] = useState(takeLinkError);
|
|
15
|
+
const { data: linked, error } = useAccountData(useCallback(() => account.linkedPersonalMail(links), [account, links]), text("couldNotReadMethods"));
|
|
16
|
+
if (!linked) return /* @__PURE__ */ jsx(AccountLoading, { error });
|
|
17
|
+
const connect = (id) => linkPersonalMail(links.find((link) => link.id === id));
|
|
18
|
+
return /* @__PURE__ */ jsx(SignInProvidersSection, {
|
|
19
|
+
title: text("mailbox"),
|
|
20
|
+
description: text("mailboxDescription"),
|
|
21
|
+
providers: links.map(({ id, name }) => ({
|
|
22
|
+
id,
|
|
23
|
+
name
|
|
24
|
+
})),
|
|
25
|
+
linked,
|
|
26
|
+
onConnect: connect,
|
|
27
|
+
error: linkError && providerMessage(linkError, links.length === 1 ? links[0].name : text("theProvider"))
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
/** The `PersonalMail` block: the mailboxes the signed-in person can link for the app to send mail from, hidden when the app has none. */
|
|
31
|
+
const PersonalMailBlock = () => {
|
|
32
|
+
const { personalMail } = useAuth();
|
|
33
|
+
return personalMail.length > 0 ? /* @__PURE__ */ jsx(Mailboxes, { links: personalMail }) : null;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { PersonalMailBlock };
|
package/dist/account/messages.js
CHANGED
|
@@ -71,6 +71,8 @@ const text = defineMessages({
|
|
|
71
71
|
connectedAccountsDescription: "Sign in with these instead of your password. Connecting one asks you to sign in there; its address may differ from yours here.",
|
|
72
72
|
connected: "Connected",
|
|
73
73
|
connect: (name) => `Connect ${name}`,
|
|
74
|
+
mailbox: "Mailbox",
|
|
75
|
+
mailboxDescription: "Mail this app sends for you goes out from your own mailbox. Connecting one asks you to sign in there and allow sending mail; it cannot read your mail.",
|
|
74
76
|
stopped: (time) => `Stopped ${time}`,
|
|
75
77
|
ended: (time) => `Ended ${time}`,
|
|
76
78
|
signedInUntil: (time) => `Signed in until ${time}`,
|
|
@@ -234,6 +236,8 @@ const text = defineMessages({
|
|
|
234
236
|
connectedAccountsDescription: "Log hiermee in in plaats van met je wachtwoord. Koppelen vraagt je daar in te loggen; het adres daar kan anders zijn dan hier.",
|
|
235
237
|
connected: "Gekoppeld",
|
|
236
238
|
connect: (name) => `${name} koppelen`,
|
|
239
|
+
mailbox: "Mailbox",
|
|
240
|
+
mailboxDescription: "Mail die deze app voor je verstuurt, gaat uit vanuit je eigen mailbox. Koppelen vraagt je daar in te loggen en het versturen van mail toe te staan; je mail lezen kan de app niet.",
|
|
237
241
|
stopped: (time) => `Gestopt ${time}`,
|
|
238
242
|
ended: (time) => `Afgelopen ${time}`,
|
|
239
243
|
signedInUntil: (time) => `Ingelogd tot ${time}`,
|
|
@@ -9,7 +9,7 @@ import { AuthError } from "@protobase/client";
|
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
//#region src/account/sign-in-providers-section.tsx
|
|
11
11
|
/** The providers someone can sign in with besides a password, and a way to connect one whatever address it has. */
|
|
12
|
-
const SignInProvidersSection = ({ providers, linked, onConnect, error: returned }) => {
|
|
12
|
+
const SignInProvidersSection = ({ providers, linked, onConnect, error: returned, title = text("connectedAccounts"), description = text("connectedAccountsDescription") }) => {
|
|
13
13
|
const [busy, setBusy] = useState();
|
|
14
14
|
const [error, setError] = useState(returned);
|
|
15
15
|
const connect = async (provider) => {
|
|
@@ -24,8 +24,8 @@ const SignInProvidersSection = ({ providers, linked, onConnect, error: returned
|
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
return /* @__PURE__ */ jsxs(AccountSection, {
|
|
27
|
-
title
|
|
28
|
-
description
|
|
27
|
+
title,
|
|
28
|
+
description,
|
|
29
29
|
children: [/* @__PURE__ */ jsx(SectionError, { message: error }), /* @__PURE__ */ jsx("ul", {
|
|
30
30
|
className: "divide-y rounded-md border",
|
|
31
31
|
children: providers.map((provider) => /* @__PURE__ */ jsxs("li", {
|
package/dist/app/app.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export type AppProps = {
|
|
|
35
35
|
nativeBridge?: NativeBridge;
|
|
36
36
|
};
|
|
37
37
|
/**
|
|
38
|
-
* The admin: a public form at `/-/forms/<name>` for anyone, the sign-in route first, `/-/sign-in`, or browsing as a
|
|
38
|
+
* The admin: a public form at `/-/forms/<name>` for anyone, a guest link's call at `/-/call-guest` for its guest, the sign-in route first, `/-/sign-in`, or browsing as a
|
|
39
39
|
* visitor when the project's anonymous role reads, then a sidebar from `/meta`, `/:page` composed pages, `/:resource`
|
|
40
40
|
* lists and `/:resource/:key` records. Fills its parent, in the theme this browser chose, light, dark or the system's.
|
|
41
41
|
*/
|
package/dist/app/app.js
CHANGED
|
@@ -32,6 +32,8 @@ import { useLiveUpdates } from "../data/use-live-updates.js";
|
|
|
32
32
|
import { useOpenedRecord } from "../data/use-record.js";
|
|
33
33
|
import { PublicFormPage } from "../forms/public-form-page.js";
|
|
34
34
|
import { useFormAddress } from "../forms/use-form-address.js";
|
|
35
|
+
import { useGuestCallAddress } from "../calls/guest-call-link.js";
|
|
36
|
+
import { GuestCallPage } from "../calls/guest-call-page.js";
|
|
35
37
|
import { ThemeProvider, useTheme } from "../theme/theme-provider.js";
|
|
36
38
|
import { AssistantButton, AssistantPanel } from "./assistant-shell.js";
|
|
37
39
|
import { bottomBarFromMeta } from "./bottom-bar-from-meta.js";
|
|
@@ -332,8 +334,20 @@ const PublicFormGate = ({ baseUrl, client, ui, children }) => {
|
|
|
332
334
|
})
|
|
333
335
|
});
|
|
334
336
|
};
|
|
337
|
+
const GuestCallGate = ({ baseUrl, children }) => {
|
|
338
|
+
const address = useGuestCallAddress();
|
|
339
|
+
const { basePath } = useRouter();
|
|
340
|
+
if (!address) return children;
|
|
341
|
+
const api = new URL(baseUrl ?? "/api/v1", window.location.href);
|
|
342
|
+
const systemPath = `${api.origin}${api.pathname.slice(0, api.pathname.lastIndexOf("/"))}`;
|
|
343
|
+
return /* @__PURE__ */ jsx(GuestCallPage, {
|
|
344
|
+
link: address.token,
|
|
345
|
+
systemPath,
|
|
346
|
+
base: `${window.location.origin}${basePath}`
|
|
347
|
+
});
|
|
348
|
+
};
|
|
335
349
|
/**
|
|
336
|
-
* The admin: a public form at `/-/forms/<name>` for anyone, the sign-in route first, `/-/sign-in`, or browsing as a
|
|
350
|
+
* The admin: a public form at `/-/forms/<name>` for anyone, a guest link's call at `/-/call-guest` for its guest, the sign-in route first, `/-/sign-in`, or browsing as a
|
|
337
351
|
* visitor when the project's anonymous role reads, then a sidebar from `/meta`, `/:page` composed pages, `/:resource`
|
|
338
352
|
* lists and `/:resource/:key` records. Fills its parent, in the theme this browser chose, light, dark or the system's.
|
|
339
353
|
*/
|
|
@@ -356,11 +370,14 @@ const App = ({ auth, workspace, basePath, initialUrl, pluginLoader, locale, nati
|
|
|
356
370
|
baseUrl: rest.baseUrl,
|
|
357
371
|
client: rest.client,
|
|
358
372
|
ui: rest.ui,
|
|
359
|
-
children: /* @__PURE__ */ jsx(
|
|
360
|
-
|
|
361
|
-
children: /* @__PURE__ */ jsx(
|
|
373
|
+
children: /* @__PURE__ */ jsx(GuestCallGate, {
|
|
374
|
+
...rest.baseUrl && { baseUrl: rest.baseUrl },
|
|
375
|
+
children: /* @__PURE__ */ jsx(AuthGate, {
|
|
362
376
|
workspace,
|
|
363
|
-
|
|
377
|
+
children: /* @__PURE__ */ jsx(Authenticated, {
|
|
378
|
+
workspace,
|
|
379
|
+
...rest
|
|
380
|
+
})
|
|
364
381
|
})
|
|
365
382
|
})
|
|
366
383
|
}) })
|
|
@@ -23,6 +23,7 @@ import { TableBlock } from "./blocks/table.js";
|
|
|
23
23
|
import { TabBlock, TabsBlock } from "./blocks/tabs.js";
|
|
24
24
|
import { UsersBlock } from "../../users/users-block.js";
|
|
25
25
|
import { ConnectedAccountsBlock } from "../../account/blocks/connected-accounts-block.js";
|
|
26
|
+
import { PersonalMailBlock } from "../../account/blocks/personal-mail-block.js";
|
|
26
27
|
import { ConnectedAppsBlock } from "../../account/blocks/connected-apps-block.js";
|
|
27
28
|
import { DeleteAccountBlock } from "../../account/blocks/delete-account-block.js";
|
|
28
29
|
import { DevicesBlock } from "../../account/blocks/devices-block.js";
|
|
@@ -61,6 +62,7 @@ const blocks = {
|
|
|
61
62
|
Passkeys: PasskeysBlock,
|
|
62
63
|
TwoFactor: TwoFactorBlock,
|
|
63
64
|
ConnectedAccounts: ConnectedAccountsBlock,
|
|
65
|
+
PersonalMail: PersonalMailBlock,
|
|
64
66
|
Devices: DevicesBlock,
|
|
65
67
|
ConnectedApps: ConnectedAppsBlock,
|
|
66
68
|
DeleteAccount: DeleteAccountBlock
|
|
@@ -31,6 +31,7 @@ const AuthProvider = ({ session: given, origin, nativeBridge, children }) => {
|
|
|
31
31
|
const [passkeyDomains, setPasskeyDomains] = useState([]);
|
|
32
32
|
const [socialProviders, setSocialProviders] = useState([]);
|
|
33
33
|
const [platformSignIn, setPlatformSignIn] = useState();
|
|
34
|
+
const [personalMail, setPersonalMail] = useState([]);
|
|
34
35
|
const [samlProviders, setSamlProviders] = useState([]);
|
|
35
36
|
const [staffSignIn, setStaffSignIn] = useState();
|
|
36
37
|
const [organizations, setOrganizations] = useState();
|
|
@@ -117,6 +118,7 @@ const AuthProvider = ({ session: given, origin, nativeBridge, children }) => {
|
|
|
117
118
|
setPasskeyDomains(status.passkeyDomains ?? []);
|
|
118
119
|
setSocialProviders(status.socialProviders);
|
|
119
120
|
setPlatformSignIn(status.platformSignIn);
|
|
121
|
+
setPersonalMail(status.personalMail ?? []);
|
|
120
122
|
setSamlProviders(status.samlProviders);
|
|
121
123
|
setStaffSignIn(status.staffSignIn);
|
|
122
124
|
setOrganizations(status.organizations);
|
|
@@ -183,6 +185,10 @@ const AuthProvider = ({ session: given, origin, nativeBridge, children }) => {
|
|
|
183
185
|
linkProvider: async (provider) => {
|
|
184
186
|
window.location.assign(await session.account.linkProvider(provider, new URL(withoutSignInLink(window.location.href), window.location.href).href));
|
|
185
187
|
},
|
|
188
|
+
personalMail,
|
|
189
|
+
linkPersonalMail: async (link) => {
|
|
190
|
+
window.location.assign(await session.account.linkPersonalMail(link, new URL(withoutSignInLink(window.location.href), window.location.href).href));
|
|
191
|
+
},
|
|
186
192
|
stopStaffSession: async () => {
|
|
187
193
|
await push.forget();
|
|
188
194
|
await session.staff.stop();
|
|
@@ -213,6 +219,7 @@ const AuthProvider = ({ session: given, origin, nativeBridge, children }) => {
|
|
|
213
219
|
passkeyDomains,
|
|
214
220
|
socialProviders,
|
|
215
221
|
platformSignIn,
|
|
222
|
+
personalMail,
|
|
216
223
|
samlProviders,
|
|
217
224
|
staffSignIn,
|
|
218
225
|
organizations,
|
package/dist/calls/call-check.js
CHANGED
|
@@ -13,7 +13,7 @@ const toggle = (on) => cn("size-10 rounded-full", !on && "bg-danger-soft text-da
|
|
|
13
13
|
* The check of the microphone and camera before joining: the member's own camera as the others will see it, or their
|
|
14
14
|
* initials, microphone and camera on and off, plain messages for a blocked or missing device, and Join.
|
|
15
15
|
*/
|
|
16
|
-
const CallCheck = ({ name, member, camera, microphoneOn, cameraOn, microphone, cameraAvailable, notices, onMicrophone, onCamera, onJoin, cancel }) => /* @__PURE__ */ jsxs("section", {
|
|
16
|
+
const CallCheck = ({ name, member, camera, microphoneOn, cameraOn, microphone, cameraAvailable, notices, onMicrophone, onCamera, onJoin, cancel, joinLabel = text("joinCall") }) => /* @__PURE__ */ jsxs("section", {
|
|
17
17
|
"aria-label": text("preview"),
|
|
18
18
|
className: "flex size-full min-h-0 flex-col items-center justify-center gap-3 bg-background p-4",
|
|
19
19
|
children: [
|
|
@@ -63,7 +63,7 @@ const CallCheck = ({ name, member, camera, microphoneOn, cameraOn, microphone, c
|
|
|
63
63
|
variant: "primary",
|
|
64
64
|
className: "h-10 rounded-full px-5",
|
|
65
65
|
onClick: onJoin,
|
|
66
|
-
children:
|
|
66
|
+
children: joinLabel
|
|
67
67
|
}),
|
|
68
68
|
/* @__PURE__ */ jsx(RouteLink, {
|
|
69
69
|
to: cancel,
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { Button, buttonClass } from "../primitives/button.js";
|
|
3
3
|
import { RouteLink } from "../primitives/route-link.js";
|
|
4
|
+
import { CountBadge } from "../primitives/count-badge.js";
|
|
4
5
|
import { text } from "./messages.js";
|
|
5
|
-
import { Mic, MicOff, MonitorUp, MonitorX, PhoneOff, Video, VideoOff } from "lucide-react";
|
|
6
|
+
import { Mic, MicOff, MonitorUp, MonitorX, PhoneOff, UserPlus, Video, VideoOff } from "lucide-react";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
//#region src/calls/call-controls.tsx
|
|
8
9
|
const toggle = (on) => cn("size-10 rounded-full", !on && "bg-danger-soft text-danger-text");
|
|
9
|
-
/** The member's controls: microphone, camera and screen share on and off, and Leave, a link out of the call. */
|
|
10
|
-
const CallControls = ({ microphoneOn, cameraOn, sharing, onMicrophone, onCamera, onShare, leave }) => /* @__PURE__ */ jsxs("div", {
|
|
10
|
+
/** The member's controls: microphone, camera and screen share on and off, the guests, and Leave, a link out of the call. */
|
|
11
|
+
const CallControls = ({ microphoneOn, cameraOn, sharing, onMicrophone, onCamera, onShare, guests, leave }) => /* @__PURE__ */ jsxs("div", {
|
|
11
12
|
role: "toolbar",
|
|
12
13
|
"aria-label": text("joinCall"),
|
|
13
|
-
className: "flex shrink-0 items-center justify-center gap-2 py-2",
|
|
14
|
+
className: "flex shrink-0 flex-wrap items-center justify-center gap-2 py-2",
|
|
14
15
|
children: [
|
|
15
16
|
/* @__PURE__ */ jsx(Button, {
|
|
16
17
|
size: "icon",
|
|
@@ -39,6 +40,17 @@ const CallControls = ({ microphoneOn, cameraOn, sharing, onMicrophone, onCamera,
|
|
|
39
40
|
onClick: onShare,
|
|
40
41
|
children: sharing ? /* @__PURE__ */ jsx(MonitorX, { className: "size-4" }) : /* @__PURE__ */ jsx(MonitorUp, { className: "size-4" })
|
|
41
42
|
}),
|
|
43
|
+
guests && /* @__PURE__ */ jsxs(Button, {
|
|
44
|
+
size: "icon",
|
|
45
|
+
"aria-label": text("guests"),
|
|
46
|
+
title: text("guests"),
|
|
47
|
+
className: "relative size-10 rounded-full",
|
|
48
|
+
onClick: guests.onOpen,
|
|
49
|
+
children: [/* @__PURE__ */ jsx(UserPlus, { className: "size-4" }), /* @__PURE__ */ jsx(CountBadge, {
|
|
50
|
+
count: guests.waiting,
|
|
51
|
+
className: "absolute -top-1 -right-1"
|
|
52
|
+
})]
|
|
53
|
+
}),
|
|
42
54
|
/* @__PURE__ */ jsxs(RouteLink, {
|
|
43
55
|
to: leave,
|
|
44
56
|
className: cn(buttonClass({ variant: "danger" }), "h-10 rounded-full px-4"),
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Button } from "../primitives/button.js";
|
|
2
|
+
import { ResponsiveDialog } from "../primitives/responsive-dialog.js";
|
|
3
|
+
import { Input } from "../primitives/input.js";
|
|
4
|
+
import { Segmented } from "../primitives/segmented.js";
|
|
5
|
+
import { text } from "./messages.js";
|
|
6
|
+
import { useGuestRequest } from "./use-guest-request.js";
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
import { Check, Copy } from "lucide-react";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
//#region src/calls/call-guests-panel.tsx
|
|
11
|
+
const sends = {
|
|
12
|
+
all: [
|
|
13
|
+
"camera",
|
|
14
|
+
"microphone",
|
|
15
|
+
"screen",
|
|
16
|
+
"screen-audio"
|
|
17
|
+
],
|
|
18
|
+
camera: ["camera", "microphone"],
|
|
19
|
+
microphone: ["microphone"]
|
|
20
|
+
};
|
|
21
|
+
const lasting = {
|
|
22
|
+
call: void 0,
|
|
23
|
+
hour: 36e5
|
|
24
|
+
};
|
|
25
|
+
const Row = ({ name, children }) => /* @__PURE__ */ jsxs("li", {
|
|
26
|
+
className: "flex items-center justify-between gap-2 py-1.5",
|
|
27
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
28
|
+
className: "min-w-0 truncate text-sm text-foreground",
|
|
29
|
+
children: name
|
|
30
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
31
|
+
className: "flex shrink-0 gap-1.5",
|
|
32
|
+
children
|
|
33
|
+
})]
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* A member's guests of the call: who waits in the lobby, to let in or turn away; the guests in the call, to remove; and
|
|
37
|
+
* the guest links, to make, copy and revoke, with what their guests may send. Names show as the guests wrote them.
|
|
38
|
+
*/
|
|
39
|
+
const CallGuestsPanel = ({ lobby, inCall, guests, linkOf, open, onClose }) => {
|
|
40
|
+
const [send, setSend] = useState("camera");
|
|
41
|
+
const [lasts, setLasts] = useState("call");
|
|
42
|
+
const [made, setMade] = useState();
|
|
43
|
+
const [copied, setCopied] = useState(false);
|
|
44
|
+
const { run, refused } = useGuestRequest();
|
|
45
|
+
const create = () => run(async () => {
|
|
46
|
+
const ms = lasting[lasts];
|
|
47
|
+
const { link, token } = await guests.link({
|
|
48
|
+
publish: sends[send],
|
|
49
|
+
...ms !== void 0 && { expiresAt: new Date(Date.now() + ms) }
|
|
50
|
+
});
|
|
51
|
+
setMade({
|
|
52
|
+
id: link.id,
|
|
53
|
+
url: linkOf(token)
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
const copy = (url) => void navigator.clipboard.writeText(url).then(() => {
|
|
57
|
+
setCopied(true);
|
|
58
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
59
|
+
});
|
|
60
|
+
return /* @__PURE__ */ jsx(ResponsiveDialog, {
|
|
61
|
+
title: text("guests"),
|
|
62
|
+
description: text("addressesVisible"),
|
|
63
|
+
open,
|
|
64
|
+
onClose,
|
|
65
|
+
actions: /* @__PURE__ */ jsx(Button, {
|
|
66
|
+
onClick: onClose,
|
|
67
|
+
children: text("close")
|
|
68
|
+
}),
|
|
69
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
70
|
+
className: "flex flex-col gap-4",
|
|
71
|
+
children: [
|
|
72
|
+
refused && /* @__PURE__ */ jsx("p", {
|
|
73
|
+
role: "alert",
|
|
74
|
+
className: "rounded-md bg-warning-soft px-3 py-1.5 text-xs text-foreground",
|
|
75
|
+
children: text("guestRefused")
|
|
76
|
+
}),
|
|
77
|
+
lobby.guests.length > 0 && /* @__PURE__ */ jsxs("section", {
|
|
78
|
+
"aria-label": text("inLobby"),
|
|
79
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
80
|
+
className: "text-xs font-medium text-muted-foreground",
|
|
81
|
+
children: text("inLobby")
|
|
82
|
+
}), /* @__PURE__ */ jsx("ul", { children: lobby.guests.map(({ guest, name }) => /* @__PURE__ */ jsxs(Row, {
|
|
83
|
+
name,
|
|
84
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
85
|
+
size: "sm",
|
|
86
|
+
variant: "primary",
|
|
87
|
+
onClick: () => run(() => guests.admit(guest)),
|
|
88
|
+
children: text("admit")
|
|
89
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
90
|
+
size: "sm",
|
|
91
|
+
onClick: () => run(() => guests.deny(guest)),
|
|
92
|
+
children: text("deny")
|
|
93
|
+
})]
|
|
94
|
+
}, guest)) })]
|
|
95
|
+
}),
|
|
96
|
+
inCall.length > 0 && /* @__PURE__ */ jsxs("section", {
|
|
97
|
+
"aria-label": text("guestsInCall"),
|
|
98
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
99
|
+
className: "text-xs font-medium text-muted-foreground",
|
|
100
|
+
children: text("guestsInCall")
|
|
101
|
+
}), /* @__PURE__ */ jsx("ul", { children: inCall.map(({ guest, name }) => /* @__PURE__ */ jsx(Row, {
|
|
102
|
+
name,
|
|
103
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
104
|
+
size: "sm",
|
|
105
|
+
variant: "danger",
|
|
106
|
+
onClick: () => run(() => guests.remove(guest)),
|
|
107
|
+
children: text("remove")
|
|
108
|
+
})
|
|
109
|
+
}, guest)) })]
|
|
110
|
+
}),
|
|
111
|
+
/* @__PURE__ */ jsxs("section", {
|
|
112
|
+
"aria-label": text("guestLinks"),
|
|
113
|
+
className: "flex flex-col gap-2",
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ jsx("h3", {
|
|
116
|
+
className: "text-xs font-medium text-muted-foreground",
|
|
117
|
+
children: text("guestLinks")
|
|
118
|
+
}),
|
|
119
|
+
lobby.links.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
120
|
+
className: "text-sm text-muted-foreground",
|
|
121
|
+
children: text("noLinks")
|
|
122
|
+
}),
|
|
123
|
+
/* @__PURE__ */ jsx("ul", { children: lobby.links.map((link) => /* @__PURE__ */ jsxs("li", {
|
|
124
|
+
className: "flex flex-col gap-1.5 py-1.5",
|
|
125
|
+
children: [made?.id === link.id && /* @__PURE__ */ jsx(Input, {
|
|
126
|
+
readOnly: true,
|
|
127
|
+
value: made.url,
|
|
128
|
+
"aria-label": text("copyLink"),
|
|
129
|
+
onFocus: (event) => event.currentTarget.select(),
|
|
130
|
+
trailing: /* @__PURE__ */ jsx("button", {
|
|
131
|
+
type: "button",
|
|
132
|
+
"aria-label": copied ? text("copied") : text("copyLink"),
|
|
133
|
+
onClick: () => copy(made.url),
|
|
134
|
+
className: "inline-flex size-8 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted pointer-coarse:size-11",
|
|
135
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "size-4 text-success" }) : /* @__PURE__ */ jsx(Copy, { className: "size-4" })
|
|
136
|
+
})
|
|
137
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
138
|
+
className: "flex items-center justify-between gap-2 text-xs text-muted-foreground",
|
|
139
|
+
children: [link.expires_at ? text("linkUntil", new Date(link.expires_at).toLocaleString()) : text("linkUntilEnd"), /* @__PURE__ */ jsx(Button, {
|
|
140
|
+
size: "sm",
|
|
141
|
+
onClick: () => run(() => guests.revoke(link.id)),
|
|
142
|
+
children: text("revoke")
|
|
143
|
+
})]
|
|
144
|
+
})]
|
|
145
|
+
}, link.id)) }),
|
|
146
|
+
/* @__PURE__ */ jsxs("div", {
|
|
147
|
+
className: "flex flex-col items-start gap-2",
|
|
148
|
+
children: [
|
|
149
|
+
/* @__PURE__ */ jsxs("span", {
|
|
150
|
+
className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground",
|
|
151
|
+
children: [text("guestsMaySend"), /* @__PURE__ */ jsx(Segmented, {
|
|
152
|
+
label: text("guestsMaySend"),
|
|
153
|
+
value: send,
|
|
154
|
+
onChange: setSend,
|
|
155
|
+
options: [
|
|
156
|
+
{
|
|
157
|
+
value: "all",
|
|
158
|
+
label: text("sendAll")
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
value: "camera",
|
|
162
|
+
label: text("sendCamera")
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
value: "microphone",
|
|
166
|
+
label: text("sendMicrophone")
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
})]
|
|
170
|
+
}),
|
|
171
|
+
/* @__PURE__ */ jsxs("span", {
|
|
172
|
+
className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground",
|
|
173
|
+
children: [text("linkLasts"), /* @__PURE__ */ jsx(Segmented, {
|
|
174
|
+
label: text("linkLasts"),
|
|
175
|
+
value: lasts,
|
|
176
|
+
onChange: setLasts,
|
|
177
|
+
options: [{
|
|
178
|
+
value: "call",
|
|
179
|
+
label: text("linkUntilEnd")
|
|
180
|
+
}, {
|
|
181
|
+
value: "hour",
|
|
182
|
+
label: text("forAnHour")
|
|
183
|
+
}]
|
|
184
|
+
})]
|
|
185
|
+
}),
|
|
186
|
+
/* @__PURE__ */ jsx(Button, {
|
|
187
|
+
variant: "primary",
|
|
188
|
+
onClick: create,
|
|
189
|
+
children: text("newLink")
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
})
|
|
193
|
+
]
|
|
194
|
+
})
|
|
195
|
+
]
|
|
196
|
+
})
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
//#endregion
|
|
200
|
+
export { CallGuestsPanel };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Button } from "../primitives/button.js";
|
|
2
|
+
import { text } from "./messages.js";
|
|
3
|
+
import { useGuestRequest } from "./use-guest-request.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/calls/call-lobby-notice.tsx
|
|
6
|
+
/** Who waits first in the lobby, with Let in and Turn away, so a member sees it without opening the guests. */
|
|
7
|
+
const CallLobbyNotice = ({ waiting, guests }) => {
|
|
8
|
+
const { run, refused } = useGuestRequest();
|
|
9
|
+
const first = waiting[0];
|
|
10
|
+
if (!first) return null;
|
|
11
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
12
|
+
role: "status",
|
|
13
|
+
className: "flex shrink-0 flex-wrap items-center gap-2 rounded-md bg-primary-soft px-3 py-1.5 text-xs text-foreground",
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx("span", {
|
|
16
|
+
className: "min-w-0 flex-1 truncate",
|
|
17
|
+
children: refused ? text("guestRefused") : text("waitingGuest", first.name)
|
|
18
|
+
}),
|
|
19
|
+
/* @__PURE__ */ jsx(Button, {
|
|
20
|
+
size: "sm",
|
|
21
|
+
variant: "primary",
|
|
22
|
+
onClick: () => run(() => guests.admit(first.guest)),
|
|
23
|
+
children: text("admit")
|
|
24
|
+
}),
|
|
25
|
+
/* @__PURE__ */ jsx(Button, {
|
|
26
|
+
size: "sm",
|
|
27
|
+
onClick: () => run(() => guests.deny(first.guest)),
|
|
28
|
+
children: text("deny")
|
|
29
|
+
})
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { CallLobbyNotice };
|
|
@@ -7,16 +7,17 @@ const said = {
|
|
|
7
7
|
removed: "removed",
|
|
8
8
|
full: "full",
|
|
9
9
|
refused: "refused",
|
|
10
|
-
unavailable: "unavailable"
|
|
10
|
+
unavailable: "unavailable",
|
|
11
|
+
denied: "denied"
|
|
11
12
|
};
|
|
12
|
-
/** Why the member is not in the call, in plain words, and, unless they were removed, a way to join again. */
|
|
13
|
+
/** Why the member is not in the call, in plain words, and, unless they were removed or refused, a way to join again. */
|
|
13
14
|
const CallMessage = ({ reason, onJoinAgain }) => /* @__PURE__ */ jsxs("section", {
|
|
14
15
|
role: "status",
|
|
15
16
|
className: "flex size-full min-h-0 flex-col items-center justify-center gap-3 bg-background p-4 text-center",
|
|
16
17
|
children: [/* @__PURE__ */ jsx("p", {
|
|
17
18
|
className: "text-sm text-foreground",
|
|
18
19
|
children: text(reason === "rejoin" ? "ended" : said[reason])
|
|
19
|
-
}), onJoinAgain && reason !== "removed" && reason !== "refused" && /* @__PURE__ */ jsx(Button, {
|
|
20
|
+
}), onJoinAgain && reason !== "removed" && reason !== "refused" && reason !== "denied" && /* @__PURE__ */ jsx(Button, {
|
|
20
21
|
onClick: onJoinAgain,
|
|
21
22
|
children: text("callAgain")
|
|
22
23
|
})]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { text } from "./messages.js";
|
|
1
2
|
import { cannotConnect, connectionQuality } from "./call-layout.js";
|
|
2
3
|
//#region src/calls/call-people.ts
|
|
3
4
|
const received = (tracks, source) => tracks.find((track) => track.source === source && track.track);
|
|
@@ -6,10 +7,10 @@ const callPeople = ({ members, self, tracks, own, nameOf, speaker, connections,
|
|
|
6
7
|
return (members.some((member) => member.member === self) ? members : [{
|
|
7
8
|
member: self,
|
|
8
9
|
joined_at: ""
|
|
9
|
-
}, ...members]).map(({ member }) => {
|
|
10
|
+
}, ...members]).map(({ member, name }) => {
|
|
10
11
|
const shared = {
|
|
11
12
|
member,
|
|
12
|
-
name: nameOf(member),
|
|
13
|
+
name: member.startsWith("guest:") ? text("guestName", name ?? "") : name ?? nameOf(member),
|
|
13
14
|
...speaker === member && { speaking: true }
|
|
14
15
|
};
|
|
15
16
|
if (member === self) return {
|
package/dist/calls/call-stage.js
CHANGED
|
@@ -64,7 +64,7 @@ const Tiles = ({ layout, people, narrow }) => {
|
|
|
64
64
|
* share large with the people beside it, or under it on a phone; the member alone; then the controls. The others'
|
|
65
65
|
* sound plays unseen.
|
|
66
66
|
*/
|
|
67
|
-
const CallStage = ({ people, state, notices = [], controls, className }) => {
|
|
67
|
+
const CallStage = ({ people, state, notices = [], controls, aside, children, className }) => {
|
|
68
68
|
const { ref, width } = useWidth();
|
|
69
69
|
const narrow = width > 0 && width < 640;
|
|
70
70
|
const layout = callLayout({
|
|
@@ -87,6 +87,7 @@ const CallStage = ({ people, state, notices = [], controls, className }) => {
|
|
|
87
87
|
children: [message === text("reconnecting") && /* @__PURE__ */ jsx(Spinner, { className: "size-3" }), message]
|
|
88
88
|
}, message))
|
|
89
89
|
}),
|
|
90
|
+
aside,
|
|
90
91
|
/* @__PURE__ */ jsx("div", {
|
|
91
92
|
className: "min-h-0 flex-1",
|
|
92
93
|
children: state === "joining" ? /* @__PURE__ */ jsxs("p", {
|
|
@@ -103,7 +104,8 @@ const CallStage = ({ people, state, notices = [], controls, className }) => {
|
|
|
103
104
|
people.filter((person) => !person.self).flatMap((person) => [["microphone", person.microphone], ["screen-audio", person.screenAudio]].map(([source, track]) => track && {
|
|
104
105
|
key: `${person.member} ${source}`,
|
|
105
106
|
track
|
|
106
|
-
})).filter((audio) => audio !== void 0).map(({ key, track }) => /* @__PURE__ */ jsx(TrackAudio, { track }, key))
|
|
107
|
+
})).filter((audio) => audio !== void 0).map(({ key, track }) => /* @__PURE__ */ jsx(TrackAudio, { track }, key)),
|
|
108
|
+
children
|
|
107
109
|
]
|
|
108
110
|
});
|
|
109
111
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RouteTarget } from "../primitives/route-link.js";
|
|
2
|
+
import { CallSessionOptions } from "./use-call-session.js";
|
|
2
3
|
//#region src/calls/call-view.d.ts
|
|
3
4
|
export type CallViewProps = {
|
|
4
5
|
/** The socket of the app's function that admits the member, as `joinCall` takes it, such as `/api/functions/call?conversation=7`. */
|
|
@@ -7,14 +8,19 @@ export type CallViewProps = {
|
|
|
7
8
|
leave: RouteTarget;
|
|
8
9
|
/** A member's name, by the member the app's function admits them as, such as a user id; the member itself by default. */
|
|
9
10
|
nameOf?: (member: string) => string;
|
|
11
|
+
/** For a guest: their wait in the lobby after Join, which resolves with their token; see `GuestCallPage`. */
|
|
12
|
+
admission?: CallSessionOptions['admission'];
|
|
13
|
+
/** The member's own name before joining, when no one is signed in, as a guest's. */
|
|
14
|
+
name?: string;
|
|
10
15
|
className?: string;
|
|
11
16
|
};
|
|
12
17
|
/**
|
|
13
18
|
* A call, ready-made, that fills its parent, such as a column beside a conversation's messages: a check of the
|
|
14
19
|
* microphone and camera first, then, after Join, everyone in the call with the one speaking ringed, a screen share
|
|
15
20
|
* large, microphone, camera, screen share and Leave, the quality of each connection, Reconnecting while the link is
|
|
16
|
-
* restored, and plain words for a full call, a blocked device or someone the browser cannot connect to.
|
|
17
|
-
*
|
|
21
|
+
* restored, and plain words for a full call, a blocked device or someone the browser cannot connect to. A member the app
|
|
22
|
+
* lets invite also gets the call's guests: who waits in the lobby, and its guest links. Made for small peer-to-peer
|
|
23
|
+
* calls, at a desktop's width or a phone's.
|
|
18
24
|
*/
|
|
19
|
-
export declare const CallView: ({ url, leave, nameOf, className }: CallViewProps) => import("react").JSX.Element;
|
|
25
|
+
export declare const CallView: ({ url, leave, nameOf, admission, name, className }: CallViewProps) => import("react").JSX.Element;
|
|
20
26
|
//#endregion
|